-
Notifications
You must be signed in to change notification settings - Fork 205
Remove object lifetime cast #564
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
Conversation
|
@BoxyUwU Hi there~ Reading through the issue you linked (thanks for that!), I believe I grok the underlying problem, which appears to be that that raw pointer casts trivially allow extending the lifetime of trait object types behind a pointer, and in particular that this allows violating where clauses in specific cases. Looking at the change in this PR, my two questions are:
Specific to the second question, I was caught up on your initial mention of casting |
|
In your case this breakage would indeed be more "preventative" than due to an actual soundness issue in your crate. Technically speaking your trait methods do already have lifetime bounds involving The risk with casting lifetimes of dyn types only really comes into play when raw pointers (directly or indirectly) can be used as the receiver of an object safe trait. For example if
The Rust Reference has a page on lifetime elision for dyn types, it might have some of the information you're looking for. I can work through all the elided/unspecified lifetimes involved in your current implementation of
I realise in my updated version of your code I left some of the lifetimes elided still, it could alternatively be written like so (I can update the PR to be written like this instead if you'd prefer): let recorder_ptr = unsafe {
std::mem::transmute::<*const (dyn Recorder + 'a), *mut (dyn Recorder + 'static)>(
recorder as &'a (dyn Recorder + 'a),
)
};Hopefully all of this helps you understand exactly what's going on with all of the elided lifetimes in your current code? Please let me know if you have questions :-) Footnotes
|
|
Thank you so much for the explanation! That roughly fits with my existing understanding, but it was helpful to see it confirmed with your step-by-step example. I'll give this another pass, since I think we'll want to tweak the placeholder |
|
Meant to write in the review message that you can feel free to desugar the lifetimes on the reference we pass to I'm also curious if you agree with my suggested explanation of the safety comment above the |
|
I've updated to specify a safety comment and explicitly write out the lifetimes 👍 When looking over the safety comment I did realise something which is that I believe #[test]
fn unsoundness() {
struct Foo<'a>(&'a str);
impl<'a> Recorder for Foo<'a> {
fn describe_counter(
&self,
key: crate::KeyName,
unit: Option<crate::Unit>,
description: crate::SharedString,
) {
dbg!(self.0);
}
/* snip... */
}
let s = "foo".to_string();
let recorder = Foo(&s);
let guard = set_default_local_recorder(&recorder);
std::mem::forget(guard);
drop(s);
with_recorder(|rec| rec.describe_counter(KeyName::from_const_str(""), None, "".into()));
}Running this under MIRI results in the following output: It seems that Unfortunately I don't have enough context about how This should probably be broken out into its own issue as it's orthogonal to this PR 🤔 I do believe your safety comment is correct and the fault lies in // SAFETY: If we have a local recorder, we know that it is valid because it can only be set during the
// duration of a closure that is passed to `with_local_recorder`, which is the only time this method can be
// called and have a local recorder set. This ensures that the lifetime of the recorder is valid for the
// duration of this method call.Which makes no note of |
|
I also wanted to check if There's been some discussion on the lang side about whether to try and keep this code working in older editions and only break it over an edition so that people depending on older versions of metrics aren't broken. The motivation for this would likely be a lot less strong if these crates can just run a trivial |
Thank you. 🙏🏻
Ah, yeah.. I know my personal feelings here don't change the objective nature of that qualifying as a soundness hole, although if someone is explicitly forgetting the guard, I'd say they've dug their own UB grave. 😅 Overall, though, I agree: a real, albeit orthogonal, issue that warrants its own issue. I'll open an issue in the next day or two once I can think through it a little more.
We could definitely do this, yes. I opened #570 to track this. |
|
I haven't (yet) released this as part of |
|
Released as part of Thanks again for your contribution! |
Forbid freely casting lifetime bounds of dyn-types Fixes #136702 Reference PR: - rust-lang/reference#1951 Background reading about VTable calls/dyn compatibility: https://hackmd.io/zUp-sgZ0RFuFgsNfD4JqYw This PR causes us to start enforcing that lifetimes of dyn types are constrained through pointer casts. Currently on stable casting `*mut dyn Trait + 'a` to `*mut dyn Trait + 'b` passes with no requirements on `'a` or `'b`. Under this PR we now require `'a` to outlive `'b`. Even though the pointee of `*mut` pointers is considered to be invariant, we still use subtyping rather than equality. This mirrors how we support coercing `&mut dyn Trait + 'a` to `&mut dyn Trait + 'b` while requiring only `'a: 'b`. I believe this coercion is sound as there is no way for safe code to `mem::swap` two `dyn Trait`'s, and the same is definitely true of raw pointers. See the changes to this test: https://github.com/rust-lang/rust/pull/136776/files#diff-5523f20a800287a89c9f3e92646c887f3f7599be006b29dd9315f734a2137764 We also do not enforce any constraints on the lifetime of the dyn types if there are multiple pointer indirections. For example `*mut *mut dyn Trait + 'a` is allowed to be casted to `*mut *mut dyn Trait + 'b` with no requirements on `'a` or 'b`. This case is just a normal thin pointer cast where we do not care about the pointee type as there is no VTable in play. Test: https://github.com/rust-lang/rust/pull/136776/files#diff-3b6c8da342bb6530524158d686455a545bb8fd6f59cf5ff50d1d991ce74c9649 Finally, this is about *any* cast where the pointee is *unsized* with dyn-type metadata, not just *literally* the pointee type being a dyn-type. E.g. casting `*mut Wrapper<dyn Trait + 'a>` to `*mut Wrapper<dyn Trait + 'b>` requires `'a: 'b` under this PR. Test: https://github.com/rust-lang/rust/pull/136776/files#diff-ca0c44df62ae1ad1be70f892f01a59714336c7baf78602a5887ac1cf81145c96 ### Breakage This is a breaking change. Crater Report Comment: #136776 (comment) Generated Report: https://crater-reports.s3.amazonaws.com/pr-136776-2/index.html The majority of the breakage is caused by the `metrics` crate with 142 of the regressions, and the `may` crate with 14 of the regressions. The `metrics` crate has been fixed and has backported the fix to previous versions of the crate that were also affected. The`may` crate has also been fixed. PRs against affected crates have been opened and can be seen here: - belalang-project/belalang#6 - tyilo/multi-vec#1 - luksan/lox#1 - pfzetto/bring-your-own-memory-demo#1 - vitorhnn/bfr#1 - gipsyh/PPSMC#1 - orengine/orengine#33 - maroider/async_scoped_task#1 - WorldSEnder/scoped_worker_thread#1 - Wind-Corporation/trapiron#5 - Thombrom/snek#1 - Xudong-Huang/may#113 - metrics-rs/metrics#564 - DouglasDwyer/micropool#1 - Magicolo/phylactery#8 - HellButcher/pulz#29 - UxuginPython/rrtk#1 - wvwwvwwv/scalable-delayed-dealloc#4 - ultimaweapon/tsuki#32 There were six regressions I've not filed PRs against: - https://github.com/weiznich/diesel_benches depends on a ~6year old version of diesel (where the regression is) - https://crates.io/crates/cogo/0.1.36 is an old version of cogo, since that release cogo has already been updated to not depend on pattern this PR breaks - https://github.com/cruise-automation/webviz-rust-framework is an archived read only repo so 🤷♀️ - makepad_render, doesn't seem to have source available and is 6 years old 🤷♀️ - outsource-heap - not on github - zaplib - I couldn't get it to compile locally as it failed to compile a dependency r? `@ghost`
Forbid freely casting lifetime bounds of dyn-types Fixes rust-lang/rust#136702 Reference PR: - rust-lang/reference#1951 Background reading about VTable calls/dyn compatibility: https://hackmd.io/zUp-sgZ0RFuFgsNfD4JqYw This PR causes us to start enforcing that lifetimes of dyn types are constrained through pointer casts. Currently on stable casting `*mut dyn Trait + 'a` to `*mut dyn Trait + 'b` passes with no requirements on `'a` or `'b`. Under this PR we now require `'a` to outlive `'b`. Even though the pointee of `*mut` pointers is considered to be invariant, we still use subtyping rather than equality. This mirrors how we support coercing `&mut dyn Trait + 'a` to `&mut dyn Trait + 'b` while requiring only `'a: 'b`. I believe this coercion is sound as there is no way for safe code to `mem::swap` two `dyn Trait`'s, and the same is definitely true of raw pointers. See the changes to this test: https://github.com/rust-lang/rust/pull/136776/files#diff-5523f20a800287a89c9f3e92646c887f3f7599be006b29dd9315f734a2137764 We also do not enforce any constraints on the lifetime of the dyn types if there are multiple pointer indirections. For example `*mut *mut dyn Trait + 'a` is allowed to be casted to `*mut *mut dyn Trait + 'b` with no requirements on `'a` or 'b`. This case is just a normal thin pointer cast where we do not care about the pointee type as there is no VTable in play. Test: https://github.com/rust-lang/rust/pull/136776/files#diff-3b6c8da342bb6530524158d686455a545bb8fd6f59cf5ff50d1d991ce74c9649 Finally, this is about *any* cast where the pointee is *unsized* with dyn-type metadata, not just *literally* the pointee type being a dyn-type. E.g. casting `*mut Wrapper<dyn Trait + 'a>` to `*mut Wrapper<dyn Trait + 'b>` requires `'a: 'b` under this PR. Test: https://github.com/rust-lang/rust/pull/136776/files#diff-ca0c44df62ae1ad1be70f892f01a59714336c7baf78602a5887ac1cf81145c96 ### Breakage This is a breaking change. Crater Report Comment: rust-lang/rust#136776 (comment) Generated Report: https://crater-reports.s3.amazonaws.com/pr-136776-2/index.html The majority of the breakage is caused by the `metrics` crate with 142 of the regressions, and the `may` crate with 14 of the regressions. The `metrics` crate has been fixed and has backported the fix to previous versions of the crate that were also affected. The`may` crate has also been fixed. PRs against affected crates have been opened and can be seen here: - belalang-project/belalang#6 - tyilo/multi-vec#1 - luksan/lox#1 - pfzetto/bring-your-own-memory-demo#1 - vitorhnn/bfr#1 - gipsyh/PPSMC#1 - orengine/orengine#33 - maroider/async_scoped_task#1 - WorldSEnder/scoped_worker_thread#1 - Wind-Corporation/trapiron#5 - Thombrom/snek#1 - Xudong-Huang/may#113 - metrics-rs/metrics#564 - DouglasDwyer/micropool#1 - Magicolo/phylactery#8 - HellButcher/pulz#29 - UxuginPython/rrtk#1 - wvwwvwwv/scalable-delayed-dealloc#4 - ultimaweapon/tsuki#32 There were six regressions I've not filed PRs against: - https://github.com/weiznich/diesel_benches depends on a ~6year old version of diesel (where the regression is) - https://crates.io/crates/cogo/0.1.36 is an old version of cogo, since that release cogo has already been updated to not depend on pattern this PR breaks - https://github.com/cruise-automation/webviz-rust-framework is an archived read only repo so 🤷♀️ - makepad_render, doesn't seem to have source available and is 6 years old 🤷♀️ - outsource-heap - not on github - zaplib - I couldn't get it to compile locally as it failed to compile a dependency r? `@ghost`
Hi there o/
I'm a member of the Rust Language Types Team; as part of stabilizing the
arbitrary_self_typesandderive_coerce_pointeefeatures we may need to change what raw pointer casts are legal (rust-lang/rust#136702). Specifically, casting*const dyn Trait + 'ato*const dyn Trait + 'bwhere it is not able to be proven that'aoutlives'bmay become an error.Without going into too much detail, the justification for this change be that casting trait object's lifetime bound to a lifetime that lives for a longer time could invalidate the VTable of the trait object, allowing for dispatching to methods that should not be callable. See this example from the linked issue:
Unfortunately, going through the usual "future compatibility warning" process may turn out to not be possible as checking lifetime constraints without emitting an error is prohibitively difficult to do in the implementation of the borrow checker. This means that you may wind up never receiving a FCW and its associated grace period to update your code to be compatible with the new rules.
I have attempted to update your codebase to the new rules for you so that it will still compile if we make this change. I hope this potential breakage won't cause you too much trouble and that you might even find the post-migration code to be better than how it was previously :-)
Finally, it has not been decided yet whether we are to go through with this breaking change. If you feel that your code should continue to work as-is then please let me know and we'll try to take that into account when evaluating whether to go through with this breakage. Additionally if you have any questions about why this change is required please let me know and I'll do my best to further explain the justification.