Rollup merge of #130727 - compiler-errors:objects, r=RalfJung
Check vtable projections for validity in miri
Currently, miri does not catch when we transmute `dyn Trait<Assoc = A>` to `dyn Trait<Assoc = B>`. This PR implements such a check, and fixes https://github.com/rust-lang/miri/issues/3905.
To do this, we modify `GlobalAlloc::VTable` to contain the *whole* list of `PolyExistentialPredicate`, and then modify `check_vtable_for_type` to validate the `PolyExistentialProjection`s of the vtable, along with the principal trait that was already being validated.
cc ``@RalfJung``
r? ``@lcnr`` or types
I also tweaked the diagnostics a bit.
---
**Open question:** We don't validate the auto traits. You can transmute `dyn Foo` into `dyn Foo + Send`. Should we check that? We currently have a test that *exercises* this as not being UB:
6c6d210089/src/tools/miri/tests/pass/dyn-upcast.rs (L14-L20)
I'm not actually sure if we ever decided that's actually UB or not 🤔
We could perhaps still check that the underlying type of the object (i.e. the concrete type that was unsized) implements the auto traits, to catch UB like:
```rust
fn main() {
let x: &dyn Trait = &std::ptr::null_mut::<()>();
let _: &(dyn Trait + Send) = std::mem::transmute(x);
//~^ this vtable is not allocated for a type that is `Send`!
}
```
This commit is contained in:
@@ -365,8 +365,10 @@ pub enum UndefinedBehaviorInfo<'tcx> {
|
||||
InvalidVTablePointer(Pointer<AllocId>),
|
||||
/// Using a vtable for the wrong trait.
|
||||
InvalidVTableTrait {
|
||||
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
||||
/// The vtable that was actually referenced by the wide pointer metadata.
|
||||
vtable_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
/// The vtable that was expected at the point in MIR that it was accessed.
|
||||
expected_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
},
|
||||
/// Using a string that is not valid UTF-8,
|
||||
InvalidStr(std::str::Utf8Error),
|
||||
@@ -479,8 +481,10 @@ pub enum ValidationErrorKind<'tcx> {
|
||||
value: String,
|
||||
},
|
||||
InvalidMetaWrongTrait {
|
||||
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
||||
/// The vtable that was actually referenced by the wide pointer metadata.
|
||||
vtable_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
/// The vtable that was expected at the point in MIR that it was accessed.
|
||||
expected_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
},
|
||||
InvalidMetaSliceTooLarge {
|
||||
ptr_kind: PointerKind,
|
||||
|
||||
@@ -232,9 +232,8 @@ impl<'s> AllocDecodingSession<'s> {
|
||||
}
|
||||
AllocDiscriminant::VTable => {
|
||||
trace!("creating vtable alloc ID");
|
||||
let ty = <Ty<'_> as Decodable<D>>::decode(decoder);
|
||||
let poly_trait_ref =
|
||||
<Option<ty::PolyExistentialTraitRef<'_>> as Decodable<D>>::decode(decoder);
|
||||
let ty = Decodable::decode(decoder);
|
||||
let poly_trait_ref = Decodable::decode(decoder);
|
||||
trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}");
|
||||
decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref, CTFE_ALLOC_SALT)
|
||||
}
|
||||
@@ -259,7 +258,10 @@ pub enum GlobalAlloc<'tcx> {
|
||||
/// The alloc ID is used as a function pointer.
|
||||
Function { instance: Instance<'tcx> },
|
||||
/// This alloc ID points to a symbolic (not-reified) vtable.
|
||||
VTable(Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>),
|
||||
/// We remember the full dyn type, not just the principal trait, so that
|
||||
/// const-eval and Miri can detect UB due to invalid transmutes of
|
||||
/// `dyn Trait` types.
|
||||
VTable(Ty<'tcx>, &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>),
|
||||
/// The alloc ID points to a "lazy" static variable that did not get computed (yet).
|
||||
/// This is also used to break the cycle in recursive statics.
|
||||
Static(DefId),
|
||||
@@ -293,7 +295,7 @@ impl<'tcx> GlobalAlloc<'tcx> {
|
||||
#[inline]
|
||||
pub fn unwrap_vtable(&self) -> (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>) {
|
||||
match *self {
|
||||
GlobalAlloc::VTable(ty, poly_trait_ref) => (ty, poly_trait_ref),
|
||||
GlobalAlloc::VTable(ty, dyn_ty) => (ty, dyn_ty.principal()),
|
||||
_ => bug!("expected vtable, got {:?}", self),
|
||||
}
|
||||
}
|
||||
@@ -398,10 +400,10 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||
pub fn reserve_and_set_vtable_alloc(
|
||||
self,
|
||||
ty: Ty<'tcx>,
|
||||
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
||||
dyn_ty: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
salt: usize,
|
||||
) -> AllocId {
|
||||
self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, poly_trait_ref), salt)
|
||||
self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, dyn_ty), salt)
|
||||
}
|
||||
|
||||
/// Interns the `Allocation` and return a new `AllocId`, even if there's already an identical
|
||||
|
||||
@@ -1536,11 +1536,8 @@ pub fn write_allocations<'tcx>(
|
||||
// gracefully handle it and allow buggy rustc to be debugged via allocation printing.
|
||||
None => write!(w, " (deallocated)")?,
|
||||
Some(GlobalAlloc::Function { instance, .. }) => write!(w, " (fn: {instance})")?,
|
||||
Some(GlobalAlloc::VTable(ty, Some(trait_ref))) => {
|
||||
write!(w, " (vtable: impl {trait_ref} for {ty})")?
|
||||
}
|
||||
Some(GlobalAlloc::VTable(ty, None)) => {
|
||||
write!(w, " (vtable: impl <auto trait> for {ty})")?
|
||||
Some(GlobalAlloc::VTable(ty, dyn_ty)) => {
|
||||
write!(w, " (vtable: impl {dyn_ty} for {ty})")?
|
||||
}
|
||||
Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => {
|
||||
match tcx.eval_static_initializer(did) {
|
||||
|
||||
Reference in New Issue
Block a user