Skip to content

Commit 5d4b70b

Browse files
authored
Unrolled build for rust-lang#135296
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
2 parents 4e1356b + a90cb05 commit 5d4b70b

File tree

6 files changed

+89
-56
lines changed

6 files changed

+89
-56
lines changed

compiler/rustc_const_eval/src/interpret/cast.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -430,10 +430,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
430430
};
431431
let erased_trait_ref =
432432
ty::ExistentialTraitRef::erase_self_ty(*self.tcx, upcast_trait_ref);
433-
assert!(data_b.principal().is_some_and(|b| self.eq_in_param_env(
434-
erased_trait_ref,
435-
self.tcx.instantiate_bound_regions_with_erased(b)
436-
)));
433+
assert_eq!(
434+
data_b.principal().map(|b| {
435+
self.tcx.normalize_erasing_late_bound_regions(self.typing_env, b)
436+
}),
437+
Some(erased_trait_ref),
438+
);
437439
} else {
438440
// In this case codegen would keep using the old vtable. We don't want to do
439441
// that as it has the wrong trait. The reason codegen can do this is that

compiler/rustc_const_eval/src/interpret/eval_context.rs

+1-39
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ use either::{Left, Right};
44
use rustc_abi::{Align, HasDataLayout, Size, TargetDataLayout};
55
use rustc_errors::DiagCtxtHandle;
66
use rustc_hir::def_id::DefId;
7-
use rustc_infer::infer::TyCtxtInferExt;
8-
use rustc_infer::infer::at::ToTrace;
9-
use rustc_infer::traits::ObligationCause;
107
use rustc_middle::mir::interpret::{ErrorHandled, InvalidMetaKind, ReportedErrorInfo};
118
use rustc_middle::query::TyCtxtAt;
129
use rustc_middle::ty::layout::{
@@ -17,8 +14,7 @@ use rustc_middle::{mir, span_bug};
1714
use rustc_session::Limit;
1815
use rustc_span::Span;
1916
use rustc_target::callconv::FnAbi;
20-
use rustc_trait_selection::traits::ObligationCtxt;
21-
use tracing::{debug, instrument, trace};
17+
use tracing::{debug, trace};
2218

2319
use super::{
2420
Frame, FrameInfo, GlobalId, InterpErrorInfo, InterpErrorKind, InterpResult, MPlaceTy, Machine,
@@ -320,40 +316,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
320316
}
321317
}
322318

323-
/// Check if the two things are equal in the current param_env, using an infcx to get proper
324-
/// equality checks.
325-
#[instrument(level = "trace", skip(self), ret)]
326-
pub(super) fn eq_in_param_env<T>(&self, a: T, b: T) -> bool
327-
where
328-
T: PartialEq + TypeFoldable<TyCtxt<'tcx>> + ToTrace<'tcx>,
329-
{
330-
// Fast path: compare directly.
331-
if a == b {
332-
return true;
333-
}
334-
// Slow path: spin up an inference context to check if these traits are sufficiently equal.
335-
let (infcx, param_env) = self.tcx.infer_ctxt().build_with_typing_env(self.typing_env);
336-
let ocx = ObligationCtxt::new(&infcx);
337-
let cause = ObligationCause::dummy_with_span(self.cur_span());
338-
// equate the two trait refs after normalization
339-
let a = ocx.normalize(&cause, param_env, a);
340-
let b = ocx.normalize(&cause, param_env, b);
341-
342-
if let Err(terr) = ocx.eq(&cause, param_env, a, b) {
343-
trace!(?terr);
344-
return false;
345-
}
346-
347-
let errors = ocx.select_all_or_error();
348-
if !errors.is_empty() {
349-
trace!(?errors);
350-
return false;
351-
}
352-
353-
// All good.
354-
true
355-
}
356-
357319
/// Walks up the callstack from the intrinsic's callsite, searching for the first callsite in a
358320
/// frame which is not `#[track_caller]`. This matches the `caller_location` intrinsic,
359321
/// and is primarily intended for the panic machinery.

compiler/rustc_const_eval/src/interpret/traits.rs

+7-13
Original file line numberDiff line numberDiff line change
@@ -86,21 +86,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
8686
throw_ub!(InvalidVTableTrait { vtable_dyn_type, expected_dyn_type });
8787
}
8888

89+
// This checks whether there is a subtyping relation between the predicates in either direction.
90+
// For example:
91+
// - casting between `dyn for<'a> Trait<fn(&'a u8)>` and `dyn Trait<fn(&'static u8)>` is OK
92+
// - casting between `dyn Trait<for<'a> fn(&'a u8)>` and either of the above is UB
8993
for (a_pred, b_pred) in std::iter::zip(sorted_vtable, sorted_expected) {
90-
let is_eq = match (a_pred.skip_binder(), b_pred.skip_binder()) {
91-
(
92-
ty::ExistentialPredicate::Trait(a_data),
93-
ty::ExistentialPredicate::Trait(b_data),
94-
) => self.eq_in_param_env(a_pred.rebind(a_data), b_pred.rebind(b_data)),
94+
let a_pred = self.tcx.normalize_erasing_late_bound_regions(self.typing_env, a_pred);
95+
let b_pred = self.tcx.normalize_erasing_late_bound_regions(self.typing_env, b_pred);
9596

96-
(
97-
ty::ExistentialPredicate::Projection(a_data),
98-
ty::ExistentialPredicate::Projection(b_data),
99-
) => self.eq_in_param_env(a_pred.rebind(a_data), b_pred.rebind(b_data)),
100-
101-
_ => false,
102-
};
103-
if !is_eq {
97+
if a_pred != b_pred {
10498
throw_ub!(InvalidVTableTrait { vtable_dyn_type, expected_dyn_type });
10599
}
106100
}
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+
}
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+

src/tools/miri/tests/pass/dyn-upcast.rs

+30
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)