-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Coercion to Box<fn()>
or Box<dyn Fn()>
ignores lifetime restrictions on method of struct with lifetime
#85456
Comments
Box<fn()>
Box<dyn Fn()>`
Box<dyn Fn()>` ignores lifetime restrictions on method of struct with lifetimeBox<fn()>
or Box<dyn Fn()>
ignores lifetime restrictions on method of struct with lifetime
Note that if you make trait Query {
fn query();
}
impl<'a> Query for TemporaryStruct<'a> {
fn query() {
println!("TemporaryStruct queried");
}
}
fn evil<'a>(_: &'a Box<i32>) -> Box<dyn Fn()> {
Box::new(TemporaryStruct::<'a>::query)
}
But the error vanishes again if you either make the return type fn evil<'a>(_: &'a Box<i32>) -> Box<fn()> {
Box::new(TemporaryStruct::<'a>::query)
} fn evil<'a>(_: &'a Box<i32>) -> Box<dyn Fn()> {
Box::new(|| TemporaryStruct::<'a>::query())
} |
I'm not quite sure this is unsound, |
My understanding is that it's a safety invariant that you cannot call a method outside the lifetime of its type, and thus, unsafe code is permitted to rely on the fact that this does not happen. (So if such unsafe code caused a memory error, that would be a bug in Rust rather than a bug in that unsafe code.) It would be nice to get confirmation of this from someone who has a deeper understanding of the safety guarantees, but I'm pretty confident that it's true. And I ran into this issue while attempting to rely on this invariant to avoid needing runtime checks, so it's not a useless invariant [edit: or maybe I didn't actually need it - see below], even if it's complicated to describe how to benefit from it. Note that you could construct a value of type Self in that method, e.g. |
AFAIK it's guaranteed that function items can be coerced to function pointers with the same signature, so your statement is true for methods because they carry the type signature due to the
It would be nice to have a practical example of how this could be used. Also, why do you need it, can't you add a
It's still unclear how this could escape the function. Since it has to be valid for any lifetime it has no way to know if the lifetime is |
so, what you're saying is that because fn evil<T: Query>() -> fn() {
T::query
} because the lifetime of If this is intentionally permitted, can you explain why the trait-method example gives an error?
Oh, right - at worst, I could have a ZST guard that carries the lifetime I need. (It's just less convenient for users - which isn't as important as reliability, of course - and I ran into this issue before I thought of that approach.) |
It can always coerce to a function pointer (not a function) with the same signature (
They are different.
It doesn't matter what's the lifetime of
IMO that's the actual bug, it shouldn't give an error there. The cast to |
Yeah, it would make sense to me if the error message was the real bug here, although I still have some doubts about what the rules are. After you mentioned the Reference and the Nomicon, I checked them, and I was thoroughly disappointed that neither of them gives any detail about the meaning of lifetimes of types. I still can't convince myself that the coercion to struct TemporaryStruct<'a>(PhantomData<(&'a mut &'a mut i32)>);
impl<'a> Query<'a> for TemporaryStruct<'a> {
fn query() {
let foo: TemporaryStruct<'a> = TemporaryStruct(PhantomData);
}
} which produces a value of type Either way, for context, here's what I was trying to do originally. To be clear, I have no idea whether this is (or should be) legal; it was just something I was messing around with when I ran into this issue. The idea is to have a type that represents access to a specific |
IMO unless some other code can observe the invalid lifetime or an invalid value with that lifetime then it is sound. However:
Another argument could be that you can already create values with invalid lifetimes, for example by leaking memory, and that's also sound. |
As a small quibble, this is different than what you do by leaking. When you leak, you fail to run destructors, but it's still impossible to refer to the value after it's leaked - so you only have meaningless data lying around somewhere in memory which code will never interact with, not a concrete variable that has an out-of-scope lifetime written in it. Your other points seem like they could make sense though. I think what we need is someone with a deeper theoretical understanding of the lifetime system, to explain the theoretical basis behind this oddity. In any case, it seems like there's some bug here - if the original code is sound, then the error message in the trait case is probably a bug. |
I think this is the same as #89667 (or vice-versa), where
|
Triage: Let's close this as duplicate to #89667 even if it is newer, because it is more to-the-point. |
In this code [playground]:
evil
stores the methodTemporaryStruct::<'a>::query
in aBox<dyn Fn()>
, which shouldn't be possible. (The same thing also happens if the return type isBox<fn()>
.) I believe this is a soundness hole in stable Rust (1.52.1).(The rest of the code is just to demonstrate how this is unsafe;
main
callsevil
with'a
equal to the lifetime of the local variabletemporary
, then uses the boxed Fn to call the query method outside the lifetime ofTemporaryStruct::<'a>
. I haven't found a way to produce actual memory errors in safe code using this, but I've been assured that it's a violation of Rust's safety invariants.)The text was updated successfully, but these errors were encountered: