miri: require (almost) all 1-ZST arguments to be actually passed#156085
miri: require (almost) all 1-ZST arguments to be actually passed#156085RalfJung wants to merge 1 commit intorust-lang:mainfrom
Conversation
|
rustbot has assigned @nikomatsakis. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
@bors try @rust-timer queue |
|
Awaiting bors try build completion. @rustbot label: +S-waiting-on-perf |
miri: require (almost) all 1-ZST arguments to be actually passed
403e9b0 to
e7467e2
Compare
| /// Determine whether this argument can be safely ignored. | ||
| /// We have to ignore closures that didn't capture anything to ensure that their | ||
| /// coercion to function pointers is sound. We don't want to ignore anything else | ||
| /// since we don't actually guarantee that anything is ever ignored. | ||
| fn is_ignored_arg(arg: &ArgAbi<'tcx, Ty<'tcx>>) -> bool { | ||
| let can_ignore = matches!(arg.layout.ty.kind(), ty::Closure (_def, closure_args) if { | ||
| closure_args.as_closure().upvar_tys().is_empty() | ||
| }); |
There was a problem hiding this comment.
I feel like this should mention that normally ZSTs are ignored. But maybe you disagree and think that's base knowledge for Rust argument passing, which would be fine with me.
The connection to closure coercion is turning my brain into a pretzel. Does this mean that we still ignore the argument in this?
fn func<T>(_: T) {}
func(|| {});There was a problem hiding this comment.
I have extended the comment, does that help?
The connection to closure coercion is turning my brain into a pretzel. Does this mean that we still ignore the argument in this?
Yes. That's unfortunate, ideally we'd still report UB if that argument gets ignored. However, I'm not sure it's worth trying to restrict this. It seems non-trivial to figure out whether the callee is actually a closure. But maybe you have a good idea for how to do that?
There was a problem hiding this comment.
Conceptually, I think of the closure captures being implicitly passed to closure calls, not literally self. But of course the FnOnce trait takes self so I suspect trying to implement my mental model would be really fiddly, if even possible.
There was a problem hiding this comment.
In MIR closures are just normal functions -- closure conversion already happened at that point. So we can't really treat them as anything else.
The best we could do is check the DefId of the callee and figure out if it is a closure, and then treat the first parameter different.
e7467e2 to
3427f02
Compare
|
The new explanation is much easier for me to follow. Thanks. r? saethlin |
|
@bors try- |
|
Unknown command "try-". Run |
|
@bors try cancel |
|
Try build cancelled. Cancelled workflows: |
This comment has been minimized.
This comment has been minimized.
3427f02 to
ec802b2
Compare
|
This pull request was unapproved. |
…hlin miri: require (almost) all 1-ZST arguments to be actually passed We can't ignore *all* of them since the compiler itself relies on non-capturing closure arguments being ignored. Fixes rust-lang/miri#4993 Cc @folkertdev since it also changes the checks for variadics.
This comment has been minimized.
This comment has been minimized.
|
This PR causes the following code to be UB according to Miri. I assume that this is a bug? use std::mem::transmute;
#[repr(transparent)]
struct Wrap<T>(T);
fn foo<T>(_: T) {}
fn bar<T>(value: T) {
let f = unsafe { transmute::<fn(T), fn(Wrap<T>)>(foo::<T>) };
f(Wrap(value));
}
fn main() {
bar(|| {});
}Error output |
ec802b2 to
9bb8594
Compare
This comment has been minimized.
This comment has been minimized.
|
No that was deliberate. Non-capturing closures being ignored is an internal implementation detail of rustc, so I don't think |
|
@RalfJung So, a |
|
Oh I think I see what you mean, it's not about relying on them being ignored... |
|
Sorry. I thought that was self-explanatory. Apparently not. I'll add comments in my future code snippets. |
9bb8594 to
4729dc8
Compare
|
I'm not sure how to even implement this check. :/ We can easily restrict this to when the callee is a closure, which seems to always be the case for the target of these coercions. But beyond that... we can't do it based on the types because then we have to ignore all 1-ZST due to the usual "repr(transparent) but it's not clear which field we mean". We could easily say that on the callee side we just skip the first argument, but then it becomes tricky to line up the caller's arguments with the callee's. So what I just pushed doesn't fix the problem for closure calls. |
52f69b7 to
ae6448f
Compare
|
Okay I think I found a check that works. This is a complete rewrite of the PR, so needs another review. |
|
I found this strange behavior. Is this intended? #![feature(fn_traits, unboxed_closures)]
use std::mem::transmute;
#[repr(align(4))]
struct Zst;
fn foo<F: FnOnce()>(_: F) {
// Calls the given F FnOnce, but passing an over-aligned ZST instead of the closure / function item
let f = unsafe { transmute::<extern "rust-call" fn(F, ()), fn(Zst)>(F::call_once) };
f(Zst)
}
fn main() {
// This is fine
foo(move || {
println!("non-capturing closure");
});
// This is UB
foo(function);
}
fn function() {
println!("function");
}Output |
|
Nice example. :) |
ae6448f to
7aad1ad
Compare
7aad1ad to
2763bb3
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
Miri reports no UB for this code with this PR. Is this intended? use std::mem::transmute;
fn main() {
let f: fn(i32) = |x: i32| {
println!("{x}");
};
let g: fn((), i32) = unsafe { transmute(f) };
g((), 123);
}I think Miri is treating the |
|
It is less intended and more unavoidable. Miri also doesn't report UB for code that incorrectly relies on |
View all comments
We can't ignore all of them since the compiler itself relies on non-capturing closure arguments being ignored.
Fixes rust-lang/miri#4993
Cc @folkertdev since it also changes the checks for variadics.