Skip to content

Commit dcdc06e

Browse files
author
Lukas Markeffsky
committed
interpret: adjust vtable validity check for higher-ranked types
1 parent 6afee11 commit dcdc06e

File tree

6 files changed

+117
-8
lines changed

6 files changed

+117
-8
lines changed

compiler/rustc_const_eval/src/interpret/cast.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
434434
assert!(
435435
data_b
436436
.principal()
437-
.is_some_and(|b| self.eq_in_param_env(erased_trait_ref, b))
437+
.is_some_and(|b| self.relate_bivariantly(erased_trait_ref, b))
438438
);
439439
} else {
440440
// In this case codegen would keep using the old vtable. We don't want to do

compiler/rustc_const_eval/src/interpret/eval_context.rs

+32-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_middle::query::TyCtxtAt;
1212
use rustc_middle::ty::layout::{
1313
self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers, TyAndLayout,
1414
};
15+
use rustc_middle::ty::relate::Relate;
1516
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeFoldable, TypingEnv, Variance};
1617
use rustc_middle::{mir, span_bug};
1718
use rustc_session::Limit;
@@ -323,12 +324,25 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
323324
}
324325
}
325326

326-
/// Check if the two things are equal in the current param_env, using an infcx to get proper
327-
/// equality checks.
327+
/// Check whether there is a subtyping relation between two things in either direction.
328+
///
329+
/// This is called on [`ty::PolyExistentialTraitRef`] or [`ty::PolyExistentialProjection`]
330+
/// to check whether the predicates of two trait objects are sufficiently equal to allow
331+
/// transmutes between them.
332+
///
333+
/// # Examples
334+
///
335+
/// - returns `true` for `dyn for<'a> Trait<fn(&'a u8)>` and `dyn Trait<fn(&'static u8)>`
336+
/// - returns `false` for `dyn Trait<for<'a> fn(&'a u8)>` and either of the above
328337
#[instrument(level = "trace", skip(self), ret)]
329-
pub(super) fn eq_in_param_env<T>(&self, a: T, b: T) -> bool
338+
pub(super) fn relate_bivariantly<T>(
339+
&self,
340+
a: ty::Binder<'tcx, T>,
341+
b: ty::Binder<'tcx, T>,
342+
) -> bool
330343
where
331-
T: PartialEq + TypeFoldable<TyCtxt<'tcx>> + ToTrace<'tcx>,
344+
T: PartialEq + Copy + TypeFoldable<TyCtxt<'tcx>> + Relate<TyCtxt<'tcx>>,
345+
ty::Binder<'tcx, T>: ToTrace<'tcx>,
332346
{
333347
// Fast path: compare directly.
334348
if a == b {
@@ -338,11 +352,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
338352
let (infcx, param_env) = self.tcx.infer_ctxt().build_with_typing_env(self.typing_env);
339353
let ocx = ObligationCtxt::new(&infcx);
340354
let cause = ObligationCause::dummy_with_span(self.cur_span());
355+
let trace = ToTrace::to_trace(&cause, a, b);
356+
357+
// Instantiate the binder with erased instead of fresh vars, because in MIR
358+
// all free regions are erased anyway, so it doesn't make a difference.
359+
let a = self.tcx.instantiate_bound_regions_with_erased(a);
360+
let b = self.tcx.instantiate_bound_regions_with_erased(b);
361+
341362
// equate the two trait refs after normalization
342363
let a = ocx.normalize(&cause, param_env, a);
343364
let b = ocx.normalize(&cause, param_env, b);
344365

345-
if let Err(terr) = ocx.eq(&cause, param_env, a, b) {
366+
if let Err(terr) = ocx.eq_trace(&cause, param_env, trace, a, b) {
346367
trace!(?terr);
347368
return false;
348369
}
@@ -353,6 +374,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
353374
return false;
354375
}
355376

377+
// Do a leak check to ensure that e.g. `for<'a> fn(&'a u8)` and `fn(&'static u8)` are not equal.
378+
if let Err(terr) = infcx.leak_check(ty::UniverseIndex::ROOT, None) {
379+
trace!(?terr, "failed leak check");
380+
return false;
381+
}
382+
356383
// All good.
357384
true
358385
}

compiler/rustc_const_eval/src/interpret/traits.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
9090
(
9191
ty::ExistentialPredicate::Trait(a_data),
9292
ty::ExistentialPredicate::Trait(b_data),
93-
) => self.eq_in_param_env(a_pred.rebind(a_data), b_pred.rebind(b_data)),
93+
) => self.relate_bivariantly(a_pred.rebind(a_data), b_pred.rebind(b_data)),
9494

9595
(
9696
ty::ExistentialPredicate::Projection(a_data),
9797
ty::ExistentialPredicate::Projection(b_data),
98-
) => self.eq_in_param_env(a_pred.rebind(a_data), b_pred.rebind(b_data)),
98+
) => self.relate_bivariantly(a_pred.rebind(a_data), b_pred.rebind(b_data)),
9999

100100
_ => false,
101101
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Test that transmuting from `&dyn Dyn<fn(&'static ())>` to `&dyn Dyn<for<'a> fn(&'a ())>` is UB.
2+
//
3+
// The vtable of `() as Dyn<fn(&'static ())>` and `() as Dyn<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 Dyn<U> {
8+
fn foo(&self)
9+
where
10+
U: HigherRanked,
11+
{
12+
U::call()
13+
}
14+
}
15+
impl<T, U> Dyn<U> for T {}
16+
17+
trait HigherRanked {
18+
fn call();
19+
}
20+
impl HigherRanked for for<'a> fn(&'a ()) {
21+
fn call() {
22+
println!("higher ranked");
23+
}
24+
}
25+
26+
// 2nd candidate is required so that selecting `(): Dyn<fn(&'static ())>` will
27+
// evaluate the candidates and fail the leak check instead of returning the
28+
// only applicable candidate.
29+
trait Unsatisfied {}
30+
impl<T: Unsatisfied> HigherRanked for T {
31+
fn call() {
32+
unreachable!();
33+
}
34+
}
35+
36+
fn main() {
37+
let x: &dyn Dyn<fn(&'static ())> = &();
38+
let y: &dyn Dyn<for<'a> fn(&'a ())> = unsafe { std::mem::transmute(x) };
39+
//~^ ERROR: wrong trait in wide pointer vtable
40+
y.foo();
41+
}
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 `Dyn<for<'a> fn(&'a ())>`, but encountered `Dyn<fn(&())>`
2+
--> tests/fail/validity/dyn-trait-leak-check.rs:LL:CC
3+
|
4+
LL | let y: &dyn Dyn<for<'a> fn(&'a ())> = unsafe { std::mem::transmute(x) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `Dyn<for<'a> fn(&'a ())>`, but encountered `Dyn<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-trait-leak-check.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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Test that transmuting between subtypes of dyn traits is fine, even in the
2+
// "wrong direction", i.e. going from a lower-ranked to a higher-ranked dyn trait.
3+
4+
trait Dyn<U: ?Sized> {}
5+
impl<T, U: ?Sized> Dyn<U> for T {}
6+
7+
struct Wrapper<T: ?Sized>(T);
8+
9+
fn main() {
10+
let x: &dyn Dyn<fn(&'static ())> = &();
11+
let _y: &dyn for<'a> Dyn<fn(&'a ())> = unsafe { std::mem::transmute(x) };
12+
13+
let x: &dyn for<'a> Dyn<fn(&'a ())> = &();
14+
let _y: &dyn Dyn<fn(&'static ())> = unsafe { std::mem::transmute(x) };
15+
16+
let x: &dyn Dyn<dyn Dyn<fn(&'static ())>> = &();
17+
let _y: &dyn for<'a> Dyn<dyn Dyn<fn(&'a ())>> = unsafe { std::mem::transmute(x) };
18+
19+
let x: &dyn for<'a> Dyn<dyn Dyn<fn(&'a ())>> = &();
20+
let _y: &dyn Dyn<dyn Dyn<fn(&'static ())>> = unsafe { std::mem::transmute(x) };
21+
22+
// This lowers to a ptr-to-ptr cast (which behaves like a transmute)
23+
// and not an unsizing coercion:
24+
let x: *const dyn for<'a> Dyn<&'a ()> = &();
25+
let _y: *const Wrapper<dyn Dyn<&'static ()>> = x as _;
26+
}

0 commit comments

Comments
 (0)