Skip to content

Commit 5043f61

Browse files
committed
interpret: dyn trait metadata check: equate traits in a proper way
1 parent 0de24a5 commit 5043f61

File tree

8 files changed

+182
-132
lines changed

8 files changed

+182
-132
lines changed

compiler/rustc_const_eval/src/interpret/cast.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -401,13 +401,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
401401
let (old_data, old_vptr) = val.to_scalar_pair();
402402
let old_data = old_data.to_pointer(self)?;
403403
let old_vptr = old_vptr.to_pointer(self)?;
404-
let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?;
405-
if old_trait != data_a.principal() {
406-
throw_ub!(InvalidVTableTrait {
407-
expected_trait: data_a,
408-
vtable_trait: old_trait,
409-
});
410-
}
404+
let ty = self.get_ptr_vtable_ty(old_vptr, Some(data_a))?;
411405
let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
412406
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
413407
}

compiler/rustc_const_eval/src/interpret/memory.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -867,19 +867,26 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
867867
.ok_or_else(|| err_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))).into())
868868
}
869869

870-
pub fn get_ptr_vtable(
870+
/// Get the dynamic type of the given vtable pointer.
871+
/// If `expected_trait` is `Some`, it must be a vtable for the given trait.
872+
pub fn get_ptr_vtable_ty(
871873
&self,
872874
ptr: Pointer<Option<M::Provenance>>,
873-
) -> InterpResult<'tcx, (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>)> {
875+
expected_trait: Option<&'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>>,
876+
) -> InterpResult<'tcx, Ty<'tcx>> {
874877
trace!("get_ptr_vtable({:?})", ptr);
875878
let (alloc_id, offset, _tag) = self.ptr_get_alloc_id(ptr)?;
876879
if offset.bytes() != 0 {
877880
throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset)))
878881
}
879-
match self.tcx.try_get_global_alloc(alloc_id) {
880-
Some(GlobalAlloc::VTable(ty, trait_ref)) => Ok((ty, trait_ref)),
881-
_ => throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset))),
882+
let Some(GlobalAlloc::VTable(ty, trait_ref)) = self.tcx.try_get_global_alloc(alloc_id)
883+
else {
884+
throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset)))
885+
};
886+
if let Some(expected_trait) = expected_trait {
887+
self.check_vtable_for_type(trait_ref, expected_trait)?;
882888
}
889+
Ok(ty)
883890
}
884891

885892
pub fn alloc_mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> {

compiler/rustc_const_eval/src/interpret/place.rs

-49
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use tracing::{instrument, trace};
99

1010
use rustc_ast::Mutability;
1111
use rustc_middle::mir;
12-
use rustc_middle::ty;
1312
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
1413
use rustc_middle::ty::Ty;
1514
use rustc_middle::{bug, span_bug};
@@ -1017,54 +1016,6 @@ where
10171016
let layout = self.layout_of(raw.ty)?;
10181017
Ok(self.ptr_to_mplace(ptr.into(), layout))
10191018
}
1020-
1021-
/// Turn a place with a `dyn Trait` type into a place with the actual dynamic type.
1022-
/// Aso returns the vtable.
1023-
pub(super) fn unpack_dyn_trait(
1024-
&self,
1025-
mplace: &MPlaceTy<'tcx, M::Provenance>,
1026-
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
1027-
) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, Pointer<Option<M::Provenance>>)> {
1028-
assert!(
1029-
matches!(mplace.layout.ty.kind(), ty::Dynamic(_, _, ty::Dyn)),
1030-
"`unpack_dyn_trait` only makes sense on `dyn*` types"
1031-
);
1032-
let vtable = mplace.meta().unwrap_meta().to_pointer(self)?;
1033-
let (ty, vtable_trait) = self.get_ptr_vtable(vtable)?;
1034-
if expected_trait.principal() != vtable_trait {
1035-
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
1036-
}
1037-
// This is a kind of transmute, from a place with unsized type and metadata to
1038-
// a place with sized type and no metadata.
1039-
let layout = self.layout_of(ty)?;
1040-
let mplace =
1041-
MPlaceTy { mplace: MemPlace { meta: MemPlaceMeta::None, ..mplace.mplace }, layout };
1042-
Ok((mplace, vtable))
1043-
}
1044-
1045-
/// Turn a `dyn* Trait` type into an value with the actual dynamic type.
1046-
/// Also returns the vtable.
1047-
pub(super) fn unpack_dyn_star<P: Projectable<'tcx, M::Provenance>>(
1048-
&self,
1049-
val: &P,
1050-
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
1051-
) -> InterpResult<'tcx, (P, Pointer<Option<M::Provenance>>)> {
1052-
assert!(
1053-
matches!(val.layout().ty.kind(), ty::Dynamic(_, _, ty::DynStar)),
1054-
"`unpack_dyn_star` only makes sense on `dyn*` types"
1055-
);
1056-
let data = self.project_field(val, 0)?;
1057-
let vtable = self.project_field(val, 1)?;
1058-
let vtable = self.read_pointer(&vtable.to_op(self)?)?;
1059-
let (ty, vtable_trait) = self.get_ptr_vtable(vtable)?;
1060-
if expected_trait.principal() != vtable_trait {
1061-
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
1062-
}
1063-
// `data` is already the right thing but has the wrong type. So we transmute it.
1064-
let layout = self.layout_of(ty)?;
1065-
let data = data.transmute(layout, self)?;
1066-
Ok((data, vtable))
1067-
}
10681019
}
10691020

10701021
// Some nodes are used a lot. Make sure they don't unintentionally get bigger.

compiler/rustc_const_eval/src/interpret/terminator.rs

+39-40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::borrow::Cow;
22

33
use either::Either;
4+
use rustc_middle::ty::TyCtxt;
45
use tracing::trace;
56

67
use rustc_middle::span_bug;
@@ -827,49 +828,47 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
827828
};
828829

829830
// Obtain the underlying trait we are working on, and the adjusted receiver argument.
830-
let (vptr, dyn_ty, adjusted_receiver) = if let ty::Dynamic(data, _, ty::DynStar) =
831-
receiver_place.layout.ty.kind()
832-
{
833-
let (recv, vptr) = self.unpack_dyn_star(&receiver_place, data)?;
834-
let (dyn_ty, _dyn_trait) = self.get_ptr_vtable(vptr)?;
831+
let (dyn_trait, dyn_ty, adjusted_receiver) =
832+
if let ty::Dynamic(data, _, ty::DynStar) = receiver_place.layout.ty.kind() {
833+
let recv = self.unpack_dyn_star(&receiver_place, data)?;
834+
835+
(data.principal(), recv.layout.ty, recv.ptr())
836+
} else {
837+
// Doesn't have to be a `dyn Trait`, but the unsized tail must be `dyn Trait`.
838+
// (For that reason we also cannot use `unpack_dyn_trait`.)
839+
let receiver_tail = self.tcx.struct_tail_erasing_lifetimes(
840+
receiver_place.layout.ty,
841+
self.param_env,
842+
);
843+
let ty::Dynamic(receiver_trait, _, ty::Dyn) = receiver_tail.kind() else {
844+
span_bug!(
845+
self.cur_span(),
846+
"dynamic call on non-`dyn` type {}",
847+
receiver_tail
848+
)
849+
};
850+
assert!(receiver_place.layout.is_unsized());
835851

836-
(vptr, dyn_ty, recv.ptr())
837-
} else {
838-
// Doesn't have to be a `dyn Trait`, but the unsized tail must be `dyn Trait`.
839-
// (For that reason we also cannot use `unpack_dyn_trait`.)
840-
let receiver_tail = self
841-
.tcx
842-
.struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env);
843-
let ty::Dynamic(data, _, ty::Dyn) = receiver_tail.kind() else {
844-
span_bug!(
845-
self.cur_span(),
846-
"dynamic call on non-`dyn` type {}",
847-
receiver_tail
848-
)
849-
};
850-
assert!(receiver_place.layout.is_unsized());
851-
852-
// Get the required information from the vtable.
853-
let vptr = receiver_place.meta().unwrap_meta().to_pointer(self)?;
854-
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
855-
if dyn_trait != data.principal() {
856-
throw_ub!(InvalidVTableTrait {
857-
expected_trait: data,
858-
vtable_trait: dyn_trait,
859-
});
860-
}
852+
// Get the required information from the vtable.
853+
let vptr = receiver_place.meta().unwrap_meta().to_pointer(self)?;
854+
let dyn_ty = self.get_ptr_vtable_ty(vptr, Some(receiver_trait))?;
861855

862-
// It might be surprising that we use a pointer as the receiver even if this
863-
// is a by-val case; this works because by-val passing of an unsized `dyn
864-
// Trait` to a function is actually desugared to a pointer.
865-
(vptr, dyn_ty, receiver_place.ptr())
866-
};
856+
// It might be surprising that we use a pointer as the receiver even if this
857+
// is a by-val case; this works because by-val passing of an unsized `dyn
858+
// Trait` to a function is actually desugared to a pointer.
859+
(receiver_trait.principal(), dyn_ty, receiver_place.ptr())
860+
};
867861

868862
// Now determine the actual method to call. We can do that in two different ways and
869863
// compare them to ensure everything fits.
870-
let Some(ty::VtblEntry::Method(fn_inst)) =
871-
self.get_vtable_entries(vptr)?.get(idx).copied()
872-
else {
864+
let vtable_entries = if let Some(dyn_trait) = dyn_trait {
865+
let trait_ref = dyn_trait.with_self_ty(*self.tcx, dyn_ty);
866+
let trait_ref = self.tcx.erase_regions(trait_ref);
867+
self.tcx.vtable_entries(trait_ref)
868+
} else {
869+
TyCtxt::COMMON_VTABLE_ENTRIES
870+
};
871+
let Some(ty::VtblEntry::Method(fn_inst)) = vtable_entries.get(idx).copied() else {
873872
// FIXME(fee1-dead) these could be variants of the UB info enum instead of this
874873
throw_ub_custom!(fluent::const_eval_dyn_call_not_a_method);
875874
};
@@ -974,11 +973,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
974973
let place = match place.layout.ty.kind() {
975974
ty::Dynamic(data, _, ty::Dyn) => {
976975
// Dropping a trait object. Need to find actual drop fn.
977-
self.unpack_dyn_trait(&place, data)?.0
976+
self.unpack_dyn_trait(&place, data)?
978977
}
979978
ty::Dynamic(data, _, ty::DynStar) => {
980979
// Dropping a `dyn*`. Need to find actual drop fn.
981-
self.unpack_dyn_star(&place, data)?.0
980+
self.unpack_dyn_star(&place, data)?
982981
}
983982
_ => {
984983
debug_assert_eq!(

compiler/rustc_const_eval/src/interpret/traits.rs

+81-18
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
use rustc_infer::infer::TyCtxtInferExt;
2+
use rustc_infer::traits::ObligationCause;
13
use rustc_middle::mir::interpret::{InterpResult, Pointer};
24
use rustc_middle::ty::layout::LayoutOf;
3-
use rustc_middle::ty::{self, Ty, TyCtxt};
5+
use rustc_middle::ty::{self, Ty};
46
use rustc_target::abi::{Align, Size};
7+
use rustc_trait_selection::traits::ObligationCtxt;
58
use tracing::trace;
69

710
use super::util::ensure_monomorphic_enough;
8-
use super::{InterpCx, Machine};
11+
use super::{throw_ub, InterpCx, MPlaceTy, Machine, MemPlaceMeta, OffsetMode, Projectable};
912

1013
impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
1114
/// Creates a dynamic vtable for the given type and vtable origin. This is used only for
@@ -33,28 +36,88 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
3336
Ok(vtable_ptr.into())
3437
}
3538

36-
/// Returns a high-level representation of the entries of the given vtable.
37-
pub fn get_vtable_entries(
38-
&self,
39-
vtable: Pointer<Option<M::Provenance>>,
40-
) -> InterpResult<'tcx, &'tcx [ty::VtblEntry<'tcx>]> {
41-
let (ty, poly_trait_ref) = self.get_ptr_vtable(vtable)?;
42-
Ok(if let Some(poly_trait_ref) = poly_trait_ref {
43-
let trait_ref = poly_trait_ref.with_self_ty(*self.tcx, ty);
44-
let trait_ref = self.tcx.erase_regions(trait_ref);
45-
self.tcx.vtable_entries(trait_ref)
46-
} else {
47-
TyCtxt::COMMON_VTABLE_ENTRIES
48-
})
49-
}
50-
5139
pub fn get_vtable_size_and_align(
5240
&self,
5341
vtable: Pointer<Option<M::Provenance>>,
5442
) -> InterpResult<'tcx, (Size, Align)> {
55-
let (ty, _trait_ref) = self.get_ptr_vtable(vtable)?;
43+
let ty = self.get_ptr_vtable_ty(vtable, None)?;
5644
let layout = self.layout_of(ty)?;
5745
assert!(layout.is_sized(), "there are no vtables for unsized types");
5846
Ok((layout.size, layout.align.abi))
5947
}
48+
49+
/// Check that the given vtable trait is valid for a pointer/reference/place with the given
50+
/// expected trait type.
51+
pub(super) fn check_vtable_for_type(
52+
&self,
53+
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
54+
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
55+
) -> InterpResult<'tcx> {
56+
// Fast path: if they are equal, it's all fine.
57+
if expected_trait.principal() == vtable_trait {
58+
return Ok(());
59+
}
60+
if let (Some(expected_trait), Some(vtable_trait)) =
61+
(expected_trait.principal(), vtable_trait)
62+
{
63+
// Slow path: spin up an inference context to check if these traits are sufficiently equal.
64+
let infcx = self.tcx.infer_ctxt().build();
65+
let ocx = ObligationCtxt::new(&infcx);
66+
let cause = ObligationCause::dummy_with_span(self.cur_span());
67+
// equate the two trait refs
68+
if ocx.eq(&cause, self.param_env, expected_trait, vtable_trait).is_ok() {
69+
if ocx.select_all_or_error().is_empty() {
70+
// All good.
71+
return Ok(());
72+
}
73+
}
74+
eprintln!("*not* equal: {expected_trait:?} {vtable_trait:?}");
75+
}
76+
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
77+
}
78+
79+
/// Turn a place with a `dyn Trait` type into a place with the actual dynamic type.
80+
pub(super) fn unpack_dyn_trait(
81+
&self,
82+
mplace: &MPlaceTy<'tcx, M::Provenance>,
83+
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
84+
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
85+
assert!(
86+
matches!(mplace.layout.ty.kind(), ty::Dynamic(_, _, ty::Dyn)),
87+
"`unpack_dyn_trait` only makes sense on `dyn*` types"
88+
);
89+
let vtable = mplace.meta().unwrap_meta().to_pointer(self)?;
90+
let ty = self.get_ptr_vtable_ty(vtable, Some(expected_trait))?;
91+
// This is a kind of transmute, from a place with unsized type and metadata to
92+
// a place with sized type and no metadata.
93+
let layout = self.layout_of(ty)?;
94+
let mplace = mplace.offset_with_meta(
95+
Size::ZERO,
96+
OffsetMode::Wrapping,
97+
MemPlaceMeta::None,
98+
layout,
99+
self,
100+
)?;
101+
Ok(mplace)
102+
}
103+
104+
/// Turn a `dyn* Trait` type into an value with the actual dynamic type.
105+
pub(super) fn unpack_dyn_star<P: Projectable<'tcx, M::Provenance>>(
106+
&self,
107+
val: &P,
108+
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
109+
) -> InterpResult<'tcx, P> {
110+
assert!(
111+
matches!(val.layout().ty.kind(), ty::Dynamic(_, _, ty::DynStar)),
112+
"`unpack_dyn_star` only makes sense on `dyn*` types"
113+
);
114+
let data = self.project_field(val, 0)?;
115+
let vtable = self.project_field(val, 1)?;
116+
let vtable = self.read_pointer(&vtable.to_op(self)?)?;
117+
let ty = self.get_ptr_vtable_ty(vtable, Some(expected_trait))?;
118+
// `data` is already the right thing but has the wrong type. So we transmute it.
119+
let layout = self.layout_of(ty)?;
120+
let data = data.transmute(layout, self)?;
121+
Ok(data)
122+
}
60123
}

compiler/rustc_const_eval/src/interpret/validity.rs

+7-11
Original file line numberDiff line numberDiff line change
@@ -343,20 +343,16 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
343343
match tail.kind() {
344344
ty::Dynamic(data, _, ty::Dyn) => {
345345
let vtable = meta.unwrap_meta().to_pointer(self.ecx)?;
346-
// Make sure it is a genuine vtable pointer.
347-
let (_dyn_ty, dyn_trait) = try_validation!(
348-
self.ecx.get_ptr_vtable(vtable),
346+
// Make sure it is a genuine vtable pointer for the right trait.
347+
try_validation!(
348+
self.ecx.get_ptr_vtable_ty(vtable, Some(data)),
349349
self.path,
350350
Ub(DanglingIntPointer(..) | InvalidVTablePointer(..)) =>
351-
InvalidVTablePtr { value: format!("{vtable}") }
351+
InvalidVTablePtr { value: format!("{vtable}") },
352+
Ub(InvalidVTableTrait { expected_trait, vtable_trait }) => {
353+
InvalidMetaWrongTrait { expected_trait, vtable_trait: *vtable_trait }
354+
},
352355
);
353-
// Make sure it is for the right trait.
354-
if dyn_trait != data.principal() {
355-
throw_validation_failure!(
356-
self.path,
357-
InvalidMetaWrongTrait { expected_trait: data, vtable_trait: dyn_trait }
358-
);
359-
}
360356
}
361357
ty::Slice(..) | ty::Str => {
362358
let _len = meta.unwrap_meta().to_target_usize(self.ecx)?;

compiler/rustc_const_eval/src/interpret/visitor.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized {
9595
// unsized values are never immediate, so we can assert_mem_place
9696
let op = v.to_op(self.ecx())?;
9797
let dest = op.assert_mem_place();
98-
let inner_mplace = self.ecx().unpack_dyn_trait(&dest, data)?.0;
98+
let inner_mplace = self.ecx().unpack_dyn_trait(&dest, data)?;
9999
trace!("walk_value: dyn object layout: {:#?}", inner_mplace.layout);
100100
// recurse with the inner type
101101
return self.visit_field(v, 0, &inner_mplace.into());
@@ -104,7 +104,7 @@ pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized {
104104
// DynStar types. Very different from a dyn type (but strangely part of the
105105
// same variant in `TyKind`): These are pairs where the 2nd component is the
106106
// vtable, and the first component is the data (which must be ptr-sized).
107-
let data = self.ecx().unpack_dyn_star(v, data)?.0;
107+
let data = self.ecx().unpack_dyn_star(v, data)?;
108108
return self.visit_field(v, 0, &data);
109109
}
110110
// Slices do not need special handling here: they have `Array` field

0 commit comments

Comments
 (0)