Skip to content

Commit 4e9829b

Browse files
authored
Rollup merge of #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/rust#120217 and rust-lang/rust#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/rust#135230 cc `@rust-lang/opsem` r? `@compiler-errors` for the implementation
2 parents 5dc0064 + 0cd96b6 commit 4e9829b

File tree

3 files changed

+75
-0
lines changed

3 files changed

+75
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Test that transmuting from `&dyn Trait<fn(&'static ())>` to `&dyn Trait<for<'a> fn(&'a ())>` is UB.
2+
//
3+
// The vtable of `() as Trait<fn(&'static ())>` and `() as Trait<for<'a> fn(&'a ())>` can have
4+
// different entries and, because in the former the entry for `foo` is vacant, this test will
5+
// segfault at runtime.
6+
7+
trait Trait<U> {
8+
fn foo(&self)
9+
where
10+
U: HigherRanked,
11+
{
12+
}
13+
}
14+
impl<T, U> Trait<U> for T {}
15+
16+
trait HigherRanked {}
17+
impl HigherRanked for for<'a> fn(&'a ()) {}
18+
19+
// 2nd candidate is required so that selecting `(): Trait<fn(&'static ())>` will
20+
// evaluate the candidates and fail the leak check instead of returning the
21+
// only applicable candidate.
22+
trait Unsatisfied {}
23+
impl<T: Unsatisfied> HigherRanked for T {}
24+
25+
fn main() {
26+
let x: &dyn Trait<fn(&'static ())> = &();
27+
let y: &dyn Trait<for<'a> fn(&'a ())> = unsafe { std::mem::transmute(x) };
28+
//~^ ERROR: wrong trait in wide pointer vtable
29+
y.foo();
30+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `Trait<for<'a> fn(&'a ())>`, but encountered `Trait<fn(&())>`
2+
--> tests/fail/validity/dyn-transmute-inner-binder.rs:LL:CC
3+
|
4+
LL | let y: &dyn Trait<for<'a> fn(&'a ())> = unsafe { std::mem::transmute(x) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `Trait<for<'a> fn(&'a ())>`, but encountered `Trait<fn(&())>`
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at tests/fail/validity/dyn-transmute-inner-binder.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+

tests/pass/dyn-upcast.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ fn main() {
99
drop_principal();
1010
modulo_binder();
1111
modulo_assoc();
12+
bidirectional_subtyping();
1213
}
1314

1415
fn vtable_nop_cast() {
@@ -531,3 +532,32 @@ fn modulo_assoc() {
531532

532533
(&() as &dyn Trait as &dyn Middle<()>).say_hello(&0);
533534
}
535+
536+
fn bidirectional_subtyping() {
537+
// Test that transmuting between subtypes of dyn traits is fine, even in the
538+
// "wrong direction", i.e. going from a lower-ranked to a higher-ranked dyn trait.
539+
// Note that compared to the `dyn-transmute-inner-binder` test, the `for` is on the
540+
// *outside* here!
541+
542+
trait Trait<U: ?Sized> {}
543+
impl<T, U: ?Sized> Trait<U> for T {}
544+
545+
struct Wrapper<T: ?Sized>(T);
546+
547+
let x: &dyn Trait<fn(&'static ())> = &();
548+
let _y: &dyn for<'a> Trait<fn(&'a ())> = unsafe { std::mem::transmute(x) };
549+
550+
let x: &dyn for<'a> Trait<fn(&'a ())> = &();
551+
let _y: &dyn Trait<fn(&'static ())> = unsafe { std::mem::transmute(x) };
552+
553+
let x: &dyn Trait<dyn Trait<fn(&'static ())>> = &();
554+
let _y: &dyn for<'a> Trait<dyn Trait<fn(&'a ())>> = unsafe { std::mem::transmute(x) };
555+
556+
let x: &dyn for<'a> Trait<dyn Trait<fn(&'a ())>> = &();
557+
let _y: &dyn Trait<dyn Trait<fn(&'static ())>> = unsafe { std::mem::transmute(x) };
558+
559+
// This lowers to a ptr-to-ptr cast (which behaves like a transmute)
560+
// and not an unsizing coercion:
561+
let x: *const dyn for<'a> Trait<&'a ()> = &();
562+
let _y: *const Wrapper<dyn Trait<&'static ()>> = x as _;
563+
}

0 commit comments

Comments
 (0)