-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
interpret: adjust vtable validity check for higher-ranked types #135296
Conversation
Some changes occurred to the CTFE machinery cc @rust-lang/wg-const-eval The Miri subtree was changed cc @rust-lang/miri Some changes occurred to the CTFE / Miri interpreter cc @rust-lang/miri |
dcdc06e
to
0536ee7
Compare
This comment has been minimized.
This comment has been minimized.
0536ee7
to
a7d6eae
Compare
#[instrument(level = "trace", skip(self), ret)] | ||
pub(super) fn eq_in_param_env<T>(&self, a: T, b: T) -> bool | ||
pub(super) fn relate_dyn_predicates_bivariantly<T>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bivariance is different from being bidirectional subtypes
change it to eq_modulo_regions
? 🤔
👍 on manually adding the leak-check here. The alternative would be to instantiate all regions with existentials and do a proper region check at the end, not totally sure whether a leak-check will always be sufficient given that it doesn't have to be catch all errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bivariance is different from being bidirectional subtypes
Hm, this comment here made me believe that they are supposed to be the same:
rust/compiler/rustc_infer/src/infer/at.rs
Lines 241 to 246 in 336209e
// We could make this make sense but it's not readily | |
// exposed and I don't feel like dealing with it. Note | |
// that bivariance in general does a bit more than just | |
// *nothing*, it checks that the types are the same | |
// "modulo variance" basically. | |
ty::Bivariant => panic!("Bivariant given to `relate()`"), |
I see now that bivariance as implemented does indeed just do nothing, so 👍 on renaming this to avoid confusion.
Also, after thinking about this more, I think we can also just structurally compare the predicates (after instantiating the binder + normalizing), no leak check required, because the soundness of TypeId
requires structural equality anyway (#97156).
Is there any reason why this would be a bad idea?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this comment here made me believe that they are supposed to be the same:
This comment is wrong then 😁
Also, after thinking about this more, I think we can also just structurally compare the predicates (after instantiating the binder + normalizing), no leak check required, because the soundness of TypeId requires structural equality anyway (#97156).
Is there any reason why this would be a bad idea?
don't think so, would have some false negatives in generic code, but 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, replaced this with just ==
now.
#[instrument(level = "trace", skip(self), ret)] | ||
pub(super) fn eq_in_param_env<T>(&self, a: T, b: T) -> bool | ||
pub(super) fn relate_dyn_predicates_bivariantly<T>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better place for this function? Somewhere in rustc_ty_utils or whatever? I am entirely clueless about its implementation so maybe const_eval is the wrong crate for this. ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is gone now, because turns out that this entire complicated operation is (almost) just equivalent to ==
^^
src/tools/miri/tests/pass/validity/dyn-trait-bivariant-transmutes.rs
Outdated
Show resolved
Hide resolved
src/tools/miri/tests/pass/validity/dyn-trait-bivariant-transmutes.rs
Outdated
Show resolved
Hide resolved
a7d6eae
to
a7c9e79
Compare
The rustc-dev-guide subtree was changed. If this PR only touches the dev guide consider submitting a PR directly to rust-lang/rustc-dev-guide otherwise thank you for updating the dev guide with your changes. cc @BoxyUwU, @jieyouxu, @Kobzol Some changes occurred in compiler/rustc_codegen_cranelift cc @bjorn3 Some changes occurred in compiler/rustc_codegen_gcc |
(this now includes #135318 to avoid conflicts, sorry for the pings) |
a7c9e79
to
cc20826
Compare
cc20826
to
ed1eb06
Compare
☔ The latest upstream changes (presumably #135848) made this pull request unmergeable. Please resolve the merge conflicts. |
ed1eb06
to
a90cb05
Compare
@rustbot review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Sorry for taking a bit to review this.
I don't believe this needs an FCP, because it simply fixes a shortcoming in vtable validation code -- this is morally equivalent1 to changing the eq_in_param_env
function to instantiate the binder of the principal trait ref (à la #135318) and then performing a leak check at the end of processing obligations.
The fact that we weren't previously leak-checking (or equivalent) allows certain kinds of transmutes to pass miri today that clearly allow changing the principal trait ref in a way that constitutes different types (e.g. fn(&'static ())
and for<'a> fn(&'a ())
).
Footnotes
-
Though simpler b/c we don't need to actually go through the whole dance of using an infcx if we expect the types to be fully normalized and region erased. ↩
cc @rust-lang/opsem and @rust-lang/types once again, if someone does think this needs an FCP then please explain why. Otherwise I'll approve this in a couple of days. |
Making some more dyn trait type transmutes UB in a very gray area seems entirely reasonable to me. Our reference says that:
|
@bors r+ |
…iaskrgr Rollup of 9 pull requests Successful merges: - rust-lang#135296 (interpret: adjust vtable validity check for higher-ranked types) - rust-lang#137106 (Add customized compare for Link in rustdoc) - rust-lang#137253 (Restrict `bevy_ecs` `ParamSet` hack) - rust-lang#137262 (Make fewer crates depend on `rustc_ast_ir`) - rust-lang#137263 (Register `USAGE_OF_TYPE_IR_INHERENT`, remove inherent usages) - rust-lang#137266 (MIR visitor tweaks) - rust-lang#137269 (Pattern Migration 2024: properly label `&` patterns whose subpatterns are from macro expansions) - rust-lang#137277 (stabilize `inherent_str_constructors`) - rust-lang#137281 (Tweak "expected ident" parse error to avoid talking about doc comments) r? `@ghost` `@rustbot` modify labels: rollup
Rollup merge of rust-lang#135296 - lukas-code:dyn-leak-check, r=compiler-errors interpret: adjust vtable validity check for higher-ranked types ## What Transmuting between trait objects where a generic argument or associated type only differs in bound regions (not bound at or above the trait object's binder) is now UB. For example * transmuting between `&dyn Trait<for<'a> fn(&'a u8)>` and `&dyn Trait<fn(&'static u8)>` is UB. * transmuting between `&dyn Trait<Assoc = for<'a> fn(&'a u8)>` and `&dyn Trait<Assoc = fn(&'static u8)>` is UB. * transmuting between `&dyn Trait<for<'a> fn(&'a u8) -> (&'a u8, &'static u8)>` and `&dyn Trait<for<'a> fn(&'a u8) -> (&'static u8, &'a u8)>` is UB. Transmuting between subtypes (in either direction) is still allowed, which means that bound regions that are bound at or above the trait object's binder can still be changed: * transmuting between `&dyn for<'a> Trait<fn(&'a u8)>` and `&dyn for Trait<fn(&'static u8)>` is fine. * transmuting between `&dyn for<'a> Trait<dyn Trait<fn(&'a u8)>>` and `&dyn for Trait<dyn Trait<fn(&'static u8)>>` is fine. ## Why Very similar to rust-lang#120217 and rust-lang#120222, changing a trait object's generic argument to a type that only differs in bound regions can still affect the vtable layout and lead to segfaults at runtime (for an example see `src/tools/miri/tests/fail/validity/dyn-transmute-inner-binder.rs`). Since we already already require that the trait object predicates must be equal modulo bound regions, it is only natural to extend this check to also require type equality considering bound regions. However, it also makes sense to allow transmutes between a type and a subtype thereof. For example `&dyn for<'a> Trait<&'a u8>` is a subtype of `&dyn Trait<&'static ()>` and they are guaranteed to have the same vtable, so it makes sense to allow this transmute. So that's why bound lifetimes that are bound to the trait object itself are treated as free lifetime for the purpose of this check. Note that codegen already relies on the property that subtyping cannot change the the vtable and this is asserted here (note the leak check): https://github.com/rust-lang/rust/blob/251206c27b619ccf3a08e2ac4c525dc343f08492/compiler/rustc_codegen_ssa/src/base.rs#L106-L153 Furthermore, we allow some pointer-to-pointer casts like `*const dyn for<'a> Trait<&'a u8>` to `*const Wrapper<dyn Trait<&'static u8>>` that instantiate the trait object binder and are currently lowered to a single pointer-to-pointer cast in MIR (`CastKind::PtrToPtr`) and *not* an unsizing coercion (`CastKind::PointerCoercion(Unsize)`), so the current MIR lowering of these would be UB if we didn't allow subtyping transmutes. --- fixes rust-lang#135230 cc `@rust-lang/opsem` r? `@compiler-errors` for the implementation
What
Transmuting between trait objects where a generic argument or associated type only differs in bound regions (not bound at or above the trait object's binder) is now UB. For example
&dyn Trait<for<'a> fn(&'a u8)>
and&dyn Trait<fn(&'static u8)>
is UB.&dyn Trait<Assoc = for<'a> fn(&'a u8)>
and&dyn Trait<Assoc = fn(&'static u8)>
is UB.&dyn Trait<for<'a> fn(&'a u8) -> (&'a u8, &'static u8)>
and&dyn Trait<for<'a> fn(&'a u8) -> (&'static u8, &'a u8)>
is UB.Transmuting between subtypes (in either direction) is still allowed, which means that bound regions that are bound at or above the trait object's binder can still be changed:
&dyn for<'a> Trait<fn(&'a u8)>
and&dyn for Trait<fn(&'static u8)>
is fine.&dyn for<'a> Trait<dyn Trait<fn(&'a u8)>>
and&dyn for Trait<dyn Trait<fn(&'static u8)>>
is fine.Why
Very similar to #120217 and #120222, changing a trait object's generic argument to a type that only differs in bound regions can still affect the vtable layout and lead to segfaults at runtime (for an example see
src/tools/miri/tests/fail/validity/dyn-transmute-inner-binder.rs
).Since we already already require that the trait object predicates must be equal modulo bound regions, it is only natural to extend this check to also require type equality considering bound regions.
However, it also makes sense to allow transmutes between a type and a subtype thereof. For example
&dyn for<'a> Trait<&'a u8>
is a subtype of&dyn Trait<&'static ()>
and they are guaranteed to have the same vtable, so it makes sense to allow this transmute. So that's why bound lifetimes that are bound to the trait object itself are treated as free lifetime for the purpose of this check.Note that codegen already relies on the property that subtyping cannot change the the vtable and this is asserted here (note the leak check):
rust/compiler/rustc_codegen_ssa/src/base.rs
Lines 106 to 153 in 251206c
Furthermore, we allow some pointer-to-pointer casts like
*const dyn for<'a> Trait<&'a u8>
to*const Wrapper<dyn Trait<&'static u8>>
that instantiate the trait object binder and are currently lowered to a single pointer-to-pointer cast in MIR (CastKind::PtrToPtr
) and not an unsizing coercion (CastKind::PointerCoercion(Unsize)
), so the current MIR lowering of these would be UB if we didn't allow subtyping transmutes.fixes #135230
cc @rust-lang/opsem
r? @compiler-errors for the implementation