mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 06:44:35 +00:00
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:
commit
ec1ccff8ce
@ -161,13 +161,13 @@ pub(crate) fn codegen_const_value<'tcx>(
|
|||||||
fx.module.declare_func_in_func(func_id, &mut fx.bcx.func);
|
fx.module.declare_func_in_func(func_id, &mut fx.bcx.func);
|
||||||
fx.bcx.ins().func_addr(fx.pointer_type, local_func_id)
|
fx.bcx.ins().func_addr(fx.pointer_type, local_func_id)
|
||||||
}
|
}
|
||||||
GlobalAlloc::VTable(ty, trait_ref) => {
|
GlobalAlloc::VTable(ty, dyn_ty) => {
|
||||||
let data_id = data_id_for_vtable(
|
let data_id = data_id_for_vtable(
|
||||||
fx.tcx,
|
fx.tcx,
|
||||||
&mut fx.constants_cx,
|
&mut fx.constants_cx,
|
||||||
fx.module,
|
fx.module,
|
||||||
ty,
|
ty,
|
||||||
trait_ref,
|
dyn_ty.principal(),
|
||||||
);
|
);
|
||||||
let local_data_id =
|
let local_data_id =
|
||||||
fx.module.declare_data_in_func(data_id, &mut fx.bcx.func);
|
fx.module.declare_data_in_func(data_id, &mut fx.bcx.func);
|
||||||
@ -456,8 +456,8 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant
|
|||||||
GlobalAlloc::Memory(target_alloc) => {
|
GlobalAlloc::Memory(target_alloc) => {
|
||||||
data_id_for_alloc_id(cx, module, alloc_id, target_alloc.inner().mutability)
|
data_id_for_alloc_id(cx, module, alloc_id, target_alloc.inner().mutability)
|
||||||
}
|
}
|
||||||
GlobalAlloc::VTable(ty, trait_ref) => {
|
GlobalAlloc::VTable(ty, dyn_ty) => {
|
||||||
data_id_for_vtable(tcx, cx, module, ty, trait_ref)
|
data_id_for_vtable(tcx, cx, module, ty, dyn_ty.principal())
|
||||||
}
|
}
|
||||||
GlobalAlloc::Static(def_id) => {
|
GlobalAlloc::Static(def_id) => {
|
||||||
if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::THREAD_LOCAL)
|
if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::THREAD_LOCAL)
|
||||||
|
@ -224,10 +224,10 @@ impl<'gcc, 'tcx> ConstCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> {
|
|||||||
value
|
value
|
||||||
}
|
}
|
||||||
GlobalAlloc::Function { instance, .. } => self.get_fn_addr(instance),
|
GlobalAlloc::Function { instance, .. } => self.get_fn_addr(instance),
|
||||||
GlobalAlloc::VTable(ty, trait_ref) => {
|
GlobalAlloc::VTable(ty, dyn_ty) => {
|
||||||
let alloc = self
|
let alloc = self
|
||||||
.tcx
|
.tcx
|
||||||
.global_alloc(self.tcx.vtable_allocation((ty, trait_ref)))
|
.global_alloc(self.tcx.vtable_allocation((ty, dyn_ty.principal())))
|
||||||
.unwrap_memory();
|
.unwrap_memory();
|
||||||
let init = const_alloc_to_gcc(self, alloc);
|
let init = const_alloc_to_gcc(self, alloc);
|
||||||
self.static_addr_of(init, alloc.inner().align, None)
|
self.static_addr_of(init, alloc.inner().align, None)
|
||||||
|
@ -290,10 +290,10 @@ impl<'ll, 'tcx> ConstCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
|
|||||||
self.get_fn_addr(instance.polymorphize(self.tcx)),
|
self.get_fn_addr(instance.polymorphize(self.tcx)),
|
||||||
self.data_layout().instruction_address_space,
|
self.data_layout().instruction_address_space,
|
||||||
),
|
),
|
||||||
GlobalAlloc::VTable(ty, trait_ref) => {
|
GlobalAlloc::VTable(ty, dyn_ty) => {
|
||||||
let alloc = self
|
let alloc = self
|
||||||
.tcx
|
.tcx
|
||||||
.global_alloc(self.tcx.vtable_allocation((ty, trait_ref)))
|
.global_alloc(self.tcx.vtable_allocation((ty, dyn_ty.principal())))
|
||||||
.unwrap_memory();
|
.unwrap_memory();
|
||||||
let init = const_alloc_to_llvm(self, alloc, /*static*/ false);
|
let init = const_alloc_to_llvm(self, alloc, /*static*/ false);
|
||||||
let value = self.static_addr_of(init, alloc.inner().align, None);
|
let value = self.static_addr_of(init, alloc.inner().align, None);
|
||||||
|
@ -198,7 +198,7 @@ const_eval_invalid_vtable_pointer =
|
|||||||
using {$pointer} as vtable pointer but it does not point to a vtable
|
using {$pointer} as vtable pointer but it does not point to a vtable
|
||||||
|
|
||||||
const_eval_invalid_vtable_trait =
|
const_eval_invalid_vtable_trait =
|
||||||
using vtable for trait `{$vtable_trait}` but trait `{$expected_trait}` was expected
|
using vtable for `{$vtable_dyn_type}` but `{$expected_dyn_type}` was expected
|
||||||
|
|
||||||
const_eval_lazy_lock =
|
const_eval_lazy_lock =
|
||||||
consider wrapping this expression in `std::sync::LazyLock::new(|| ...)`
|
consider wrapping this expression in `std::sync::LazyLock::new(|| ...)`
|
||||||
@ -459,7 +459,7 @@ const_eval_validation_invalid_fn_ptr = {$front_matter}: encountered {$value}, bu
|
|||||||
const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object
|
const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object
|
||||||
const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
|
const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
|
||||||
const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
|
const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
|
||||||
const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wide pointer vtable: expected `{$ref_trait}`, but encountered `{$vtable_trait}`
|
const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wide pointer vtable: expected `{$expected_dyn_type}`, but encountered `{$vtable_dyn_type}`
|
||||||
const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory
|
const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory
|
||||||
const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!`
|
const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!`
|
||||||
const_eval_validation_null_box = {$front_matter}: encountered a null box
|
const_eval_validation_null_box = {$front_matter}: encountered a null box
|
||||||
|
@ -522,12 +522,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
|
|||||||
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
|
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
|
||||||
diag.arg("pointer", ptr);
|
diag.arg("pointer", ptr);
|
||||||
}
|
}
|
||||||
InvalidVTableTrait { expected_trait, vtable_trait } => {
|
InvalidVTableTrait { expected_dyn_type, vtable_dyn_type } => {
|
||||||
diag.arg("expected_trait", expected_trait.to_string());
|
diag.arg("expected_dyn_type", expected_dyn_type.to_string());
|
||||||
diag.arg(
|
diag.arg("vtable_dyn_type", vtable_dyn_type.to_string());
|
||||||
"vtable_trait",
|
|
||||||
vtable_trait.map(|t| t.to_string()).unwrap_or_else(|| format!("<trivial>")),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
PointerUseAfterFree(alloc_id, msg) => {
|
PointerUseAfterFree(alloc_id, msg) => {
|
||||||
diag.arg("alloc_id", alloc_id)
|
diag.arg("alloc_id", alloc_id)
|
||||||
@ -777,12 +774,9 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
|
|||||||
DanglingPtrNoProvenance { pointer, .. } => {
|
DanglingPtrNoProvenance { pointer, .. } => {
|
||||||
err.arg("pointer", pointer);
|
err.arg("pointer", pointer);
|
||||||
}
|
}
|
||||||
InvalidMetaWrongTrait { expected_trait: ref_trait, vtable_trait } => {
|
InvalidMetaWrongTrait { vtable_dyn_type, expected_dyn_type } => {
|
||||||
err.arg("ref_trait", ref_trait.to_string());
|
err.arg("vtable_dyn_type", vtable_dyn_type.to_string());
|
||||||
err.arg(
|
err.arg("expected_dyn_type", expected_dyn_type.to_string());
|
||||||
"vtable_trait",
|
|
||||||
vtable_trait.map(|t| t.to_string()).unwrap_or_else(|| format!("<trivial>")),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
NullPtr { .. }
|
NullPtr { .. }
|
||||||
| ConstRefToMutable
|
| ConstRefToMutable
|
||||||
|
@ -128,7 +128,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||||||
CastKind::DynStar => {
|
CastKind::DynStar => {
|
||||||
if let ty::Dynamic(data, _, ty::DynStar) = cast_ty.kind() {
|
if let ty::Dynamic(data, _, ty::DynStar) = cast_ty.kind() {
|
||||||
// Initial cast from sized to dyn trait
|
// Initial cast from sized to dyn trait
|
||||||
let vtable = self.get_vtable_ptr(src.layout.ty, data.principal())?;
|
let vtable = self.get_vtable_ptr(src.layout.ty, data)?;
|
||||||
let vtable = Scalar::from_maybe_pointer(vtable, self);
|
let vtable = Scalar::from_maybe_pointer(vtable, self);
|
||||||
let data = self.read_immediate(src)?.to_scalar();
|
let data = self.read_immediate(src)?.to_scalar();
|
||||||
let _assert_pointer_like = data.to_pointer(self)?;
|
let _assert_pointer_like = data.to_pointer(self)?;
|
||||||
@ -446,12 +446,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the destination trait vtable and return that.
|
// Get the destination trait vtable and return that.
|
||||||
let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
|
let new_vptr = self.get_vtable_ptr(ty, data_b)?;
|
||||||
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
|
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
|
||||||
}
|
}
|
||||||
(_, &ty::Dynamic(data, _, ty::Dyn)) => {
|
(_, &ty::Dynamic(data, _, ty::Dyn)) => {
|
||||||
// Initial cast from sized to dyn trait
|
// Initial cast from sized to dyn trait
|
||||||
let vtable = self.get_vtable_ptr(src_pointee_ty, data.principal())?;
|
let vtable = self.get_vtable_ptr(src_pointee_ty, data)?;
|
||||||
let ptr = self.read_pointer(src)?;
|
let ptr = self.read_pointer(src)?;
|
||||||
let val = Immediate::new_dyn_trait(ptr, vtable, &*self.tcx);
|
let val = Immediate::new_dyn_trait(ptr, vtable, &*self.tcx);
|
||||||
self.write_immediate(val, dest)
|
self.write_immediate(val, dest)
|
||||||
|
@ -943,12 +943,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||||||
if offset.bytes() != 0 {
|
if offset.bytes() != 0 {
|
||||||
throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset)))
|
throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset)))
|
||||||
}
|
}
|
||||||
let Some(GlobalAlloc::VTable(ty, vtable_trait)) = self.tcx.try_get_global_alloc(alloc_id)
|
let Some(GlobalAlloc::VTable(ty, vtable_dyn_type)) =
|
||||||
|
self.tcx.try_get_global_alloc(alloc_id)
|
||||||
else {
|
else {
|
||||||
throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset)))
|
throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset)))
|
||||||
};
|
};
|
||||||
if let Some(expected_trait) = expected_trait {
|
if let Some(expected_dyn_type) = expected_trait {
|
||||||
self.check_vtable_for_type(vtable_trait, expected_trait)?;
|
self.check_vtable_for_type(vtable_dyn_type, expected_dyn_type)?;
|
||||||
}
|
}
|
||||||
Ok(ty)
|
Ok(ty)
|
||||||
}
|
}
|
||||||
@ -1113,11 +1114,8 @@ impl<'a, 'tcx, M: Machine<'tcx>> std::fmt::Debug for DumpAllocs<'a, 'tcx, M> {
|
|||||||
Some(GlobalAlloc::Function { instance, .. }) => {
|
Some(GlobalAlloc::Function { instance, .. }) => {
|
||||||
write!(fmt, " (fn: {instance})")?;
|
write!(fmt, " (fn: {instance})")?;
|
||||||
}
|
}
|
||||||
Some(GlobalAlloc::VTable(ty, Some(trait_ref))) => {
|
Some(GlobalAlloc::VTable(ty, dyn_ty)) => {
|
||||||
write!(fmt, " (vtable: impl {trait_ref} for {ty})")?;
|
write!(fmt, " (vtable: impl {dyn_ty} for {ty})")?;
|
||||||
}
|
|
||||||
Some(GlobalAlloc::VTable(ty, None)) => {
|
|
||||||
write!(fmt, " (vtable: impl <auto trait> for {ty})")?;
|
|
||||||
}
|
}
|
||||||
Some(GlobalAlloc::Static(did)) => {
|
Some(GlobalAlloc::Static(did)) => {
|
||||||
write!(fmt, " (static: {})", self.ecx.tcx.def_path_str(did))?;
|
write!(fmt, " (static: {})", self.ecx.tcx.def_path_str(did))?;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use rustc_middle::mir::interpret::{InterpResult, Pointer};
|
use rustc_middle::mir::interpret::{InterpResult, Pointer};
|
||||||
use rustc_middle::ty::layout::LayoutOf;
|
use rustc_middle::ty::layout::LayoutOf;
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt, VtblEntry};
|
use rustc_middle::ty::{self, ExistentialPredicateStableCmpExt, Ty, TyCtxt, VtblEntry};
|
||||||
use rustc_target::abi::{Align, Size};
|
use rustc_target::abi::{Align, Size};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
@ -11,26 +11,25 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||||||
/// Creates a dynamic vtable for the given type and vtable origin. This is used only for
|
/// Creates a dynamic vtable for the given type and vtable origin. This is used only for
|
||||||
/// objects.
|
/// objects.
|
||||||
///
|
///
|
||||||
/// The `trait_ref` encodes the erased self type. Hence, if we are making an object `Foo<Trait>`
|
/// The `dyn_ty` encodes the erased self type. Hence, if we are making an object
|
||||||
/// from a value of type `Foo<T>`, then `trait_ref` would map `T: Trait`. `None` here means that
|
/// `Foo<dyn Trait<Assoc = A> + Send>` from a value of type `Foo<T>`, then `dyn_ty`
|
||||||
/// this is an auto trait without any methods, so we only need the basic vtable (drop, size,
|
/// would be `Trait<Assoc = A> + Send`. If this list doesn't have a principal trait ref,
|
||||||
/// align).
|
/// we only need the basic vtable prefix (drop, size, align).
|
||||||
pub fn get_vtable_ptr(
|
pub fn get_vtable_ptr(
|
||||||
&self,
|
&self,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
dyn_ty: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||||
) -> InterpResult<'tcx, Pointer<Option<M::Provenance>>> {
|
) -> InterpResult<'tcx, Pointer<Option<M::Provenance>>> {
|
||||||
trace!("get_vtable(trait_ref={:?})", poly_trait_ref);
|
trace!("get_vtable(ty={ty:?}, dyn_ty={dyn_ty:?})");
|
||||||
|
|
||||||
let (ty, poly_trait_ref) = self.tcx.erase_regions((ty, poly_trait_ref));
|
let (ty, dyn_ty) = self.tcx.erase_regions((ty, dyn_ty));
|
||||||
|
|
||||||
// All vtables must be monomorphic, bail out otherwise.
|
// All vtables must be monomorphic, bail out otherwise.
|
||||||
ensure_monomorphic_enough(*self.tcx, ty)?;
|
ensure_monomorphic_enough(*self.tcx, ty)?;
|
||||||
ensure_monomorphic_enough(*self.tcx, poly_trait_ref)?;
|
ensure_monomorphic_enough(*self.tcx, dyn_ty)?;
|
||||||
|
|
||||||
let salt = M::get_global_alloc_salt(self, None);
|
let salt = M::get_global_alloc_salt(self, None);
|
||||||
let vtable_symbolic_allocation =
|
let vtable_symbolic_allocation = self.tcx.reserve_and_set_vtable_alloc(ty, dyn_ty, salt);
|
||||||
self.tcx.reserve_and_set_vtable_alloc(ty, poly_trait_ref, salt);
|
|
||||||
let vtable_ptr = self.global_root_pointer(Pointer::from(vtable_symbolic_allocation))?;
|
let vtable_ptr = self.global_root_pointer(Pointer::from(vtable_symbolic_allocation))?;
|
||||||
Ok(vtable_ptr.into())
|
Ok(vtable_ptr.into())
|
||||||
}
|
}
|
||||||
@ -64,17 +63,45 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||||||
/// expected trait type.
|
/// expected trait type.
|
||||||
pub(super) fn check_vtable_for_type(
|
pub(super) fn check_vtable_for_type(
|
||||||
&self,
|
&self,
|
||||||
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
vtable_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||||
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
expected_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
let eq = match (expected_trait.principal(), vtable_trait) {
|
// We check validity by comparing the lists of predicates for equality. We *could* instead
|
||||||
(Some(a), Some(b)) => self.eq_in_param_env(a, b),
|
// check that the dynamic type to which the vtable belongs satisfies all the expected
|
||||||
(None, None) => true,
|
// predicates, but that would likely be a lot slower and seems unnecessarily permissive.
|
||||||
_ => false,
|
|
||||||
};
|
// FIXME: we are skipping auto traits for now, but might revisit this in the future.
|
||||||
if !eq {
|
let mut sorted_vtable: Vec<_> = vtable_dyn_type.without_auto_traits().collect();
|
||||||
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
|
let mut sorted_expected: Vec<_> = expected_dyn_type.without_auto_traits().collect();
|
||||||
|
// `skip_binder` here is okay because `stable_cmp` doesn't look at binders
|
||||||
|
sorted_vtable.sort_by(|a, b| a.skip_binder().stable_cmp(*self.tcx, &b.skip_binder()));
|
||||||
|
sorted_vtable.dedup();
|
||||||
|
sorted_expected.sort_by(|a, b| a.skip_binder().stable_cmp(*self.tcx, &b.skip_binder()));
|
||||||
|
sorted_expected.dedup();
|
||||||
|
|
||||||
|
if sorted_vtable.len() != sorted_expected.len() {
|
||||||
|
throw_ub!(InvalidVTableTrait { vtable_dyn_type, expected_dyn_type });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (a_pred, b_pred) in std::iter::zip(sorted_vtable, sorted_expected) {
|
||||||
|
let is_eq = match (a_pred.skip_binder(), b_pred.skip_binder()) {
|
||||||
|
(
|
||||||
|
ty::ExistentialPredicate::Trait(a_data),
|
||||||
|
ty::ExistentialPredicate::Trait(b_data),
|
||||||
|
) => self.eq_in_param_env(a_pred.rebind(a_data), b_pred.rebind(b_data)),
|
||||||
|
|
||||||
|
(
|
||||||
|
ty::ExistentialPredicate::Projection(a_data),
|
||||||
|
ty::ExistentialPredicate::Projection(b_data),
|
||||||
|
) => self.eq_in_param_env(a_pred.rebind(a_data), b_pred.rebind(b_data)),
|
||||||
|
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if !is_eq {
|
||||||
|
throw_ub!(InvalidVTableTrait { vtable_dyn_type, expected_dyn_type });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,8 +452,8 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
|
|||||||
self.path,
|
self.path,
|
||||||
Ub(DanglingIntPointer{ .. } | InvalidVTablePointer(..)) =>
|
Ub(DanglingIntPointer{ .. } | InvalidVTablePointer(..)) =>
|
||||||
InvalidVTablePtr { value: format!("{vtable}") },
|
InvalidVTablePtr { value: format!("{vtable}") },
|
||||||
Ub(InvalidVTableTrait { expected_trait, vtable_trait }) => {
|
Ub(InvalidVTableTrait { vtable_dyn_type, expected_dyn_type }) => {
|
||||||
InvalidMetaWrongTrait { expected_trait, vtable_trait: *vtable_trait }
|
InvalidMetaWrongTrait { vtable_dyn_type, expected_dyn_type }
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1281,8 +1281,8 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
|
|||||||
self.path,
|
self.path,
|
||||||
// It's not great to catch errors here, since we can't give a very good path,
|
// It's not great to catch errors here, since we can't give a very good path,
|
||||||
// but it's better than ICEing.
|
// but it's better than ICEing.
|
||||||
Ub(InvalidVTableTrait { expected_trait, vtable_trait }) => {
|
Ub(InvalidVTableTrait { vtable_dyn_type, expected_dyn_type }) => {
|
||||||
InvalidMetaWrongTrait { expected_trait, vtable_trait: *vtable_trait }
|
InvalidMetaWrongTrait { vtable_dyn_type, expected_dyn_type: *expected_dyn_type }
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -365,8 +365,10 @@ pub enum UndefinedBehaviorInfo<'tcx> {
|
|||||||
InvalidVTablePointer(Pointer<AllocId>),
|
InvalidVTablePointer(Pointer<AllocId>),
|
||||||
/// Using a vtable for the wrong trait.
|
/// Using a vtable for the wrong trait.
|
||||||
InvalidVTableTrait {
|
InvalidVTableTrait {
|
||||||
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
/// The vtable that was actually referenced by the wide pointer metadata.
|
||||||
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
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,
|
/// Using a string that is not valid UTF-8,
|
||||||
InvalidStr(std::str::Utf8Error),
|
InvalidStr(std::str::Utf8Error),
|
||||||
@ -479,8 +481,10 @@ pub enum ValidationErrorKind<'tcx> {
|
|||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
InvalidMetaWrongTrait {
|
InvalidMetaWrongTrait {
|
||||||
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
/// The vtable that was actually referenced by the wide pointer metadata.
|
||||||
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
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 {
|
InvalidMetaSliceTooLarge {
|
||||||
ptr_kind: PointerKind,
|
ptr_kind: PointerKind,
|
||||||
|
@ -232,9 +232,8 @@ impl<'s> AllocDecodingSession<'s> {
|
|||||||
}
|
}
|
||||||
AllocDiscriminant::VTable => {
|
AllocDiscriminant::VTable => {
|
||||||
trace!("creating vtable alloc ID");
|
trace!("creating vtable alloc ID");
|
||||||
let ty = <Ty<'_> as Decodable<D>>::decode(decoder);
|
let ty = Decodable::decode(decoder);
|
||||||
let poly_trait_ref =
|
let poly_trait_ref = Decodable::decode(decoder);
|
||||||
<Option<ty::PolyExistentialTraitRef<'_>> as Decodable<D>>::decode(decoder);
|
|
||||||
trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}");
|
trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}");
|
||||||
decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref, CTFE_ALLOC_SALT)
|
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.
|
/// The alloc ID is used as a function pointer.
|
||||||
Function { instance: Instance<'tcx> },
|
Function { instance: Instance<'tcx> },
|
||||||
/// This alloc ID points to a symbolic (not-reified) vtable.
|
/// 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).
|
/// 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.
|
/// This is also used to break the cycle in recursive statics.
|
||||||
Static(DefId),
|
Static(DefId),
|
||||||
@ -293,7 +295,7 @@ impl<'tcx> GlobalAlloc<'tcx> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn unwrap_vtable(&self) -> (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>) {
|
pub fn unwrap_vtable(&self) -> (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>) {
|
||||||
match *self {
|
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),
|
_ => bug!("expected vtable, got {:?}", self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,10 +400,10 @@ impl<'tcx> TyCtxt<'tcx> {
|
|||||||
pub fn reserve_and_set_vtable_alloc(
|
pub fn reserve_and_set_vtable_alloc(
|
||||||
self,
|
self,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
|
dyn_ty: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||||
salt: usize,
|
salt: usize,
|
||||||
) -> AllocId {
|
) -> 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
|
/// 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.
|
// gracefully handle it and allow buggy rustc to be debugged via allocation printing.
|
||||||
None => write!(w, " (deallocated)")?,
|
None => write!(w, " (deallocated)")?,
|
||||||
Some(GlobalAlloc::Function { instance, .. }) => write!(w, " (fn: {instance})")?,
|
Some(GlobalAlloc::Function { instance, .. }) => write!(w, " (fn: {instance})")?,
|
||||||
Some(GlobalAlloc::VTable(ty, Some(trait_ref))) => {
|
Some(GlobalAlloc::VTable(ty, dyn_ty)) => {
|
||||||
write!(w, " (vtable: impl {trait_ref} for {ty})")?
|
write!(w, " (vtable: impl {dyn_ty} for {ty})")?
|
||||||
}
|
|
||||||
Some(GlobalAlloc::VTable(ty, None)) => {
|
|
||||||
write!(w, " (vtable: impl <auto trait> for {ty})")?
|
|
||||||
}
|
}
|
||||||
Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => {
|
Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => {
|
||||||
match tcx.eval_static_initializer(did) {
|
match tcx.eval_static_initializer(did) {
|
||||||
|
@ -1175,8 +1175,8 @@ fn collect_alloc<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIt
|
|||||||
output.push(create_fn_mono_item(tcx, instance, DUMMY_SP));
|
output.push(create_fn_mono_item(tcx, instance, DUMMY_SP));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GlobalAlloc::VTable(ty, trait_ref) => {
|
GlobalAlloc::VTable(ty, dyn_ty) => {
|
||||||
let alloc_id = tcx.vtable_allocation((ty, trait_ref));
|
let alloc_id = tcx.vtable_allocation((ty, dyn_ty.principal()));
|
||||||
collect_alloc(tcx, alloc_id, output)
|
collect_alloc(tcx, alloc_id, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,10 +318,10 @@ impl<'tcx> ReachableContext<'tcx> {
|
|||||||
));
|
));
|
||||||
self.visit(instance.args);
|
self.visit(instance.args);
|
||||||
}
|
}
|
||||||
GlobalAlloc::VTable(ty, trait_ref) => {
|
GlobalAlloc::VTable(ty, dyn_ty) => {
|
||||||
self.visit(ty);
|
self.visit(ty);
|
||||||
// Manually visit to actually see the trait's `DefId`. Type visitors won't see it
|
// Manually visit to actually see the trait's `DefId`. Type visitors won't see it
|
||||||
if let Some(trait_ref) = trait_ref {
|
if let Some(trait_ref) = dyn_ty.principal() {
|
||||||
let ExistentialTraitRef { def_id, args } = trait_ref.skip_binder();
|
let ExistentialTraitRef { def_id, args } = trait_ref.skip_binder();
|
||||||
self.visit_def_id(def_id, "", &"");
|
self.visit_def_id(def_id, "", &"");
|
||||||
self.visit(args);
|
self.visit(args);
|
||||||
|
@ -712,8 +712,9 @@ impl<'tcx> Stable<'tcx> for mir::interpret::GlobalAlloc<'tcx> {
|
|||||||
mir::interpret::GlobalAlloc::Function { instance, .. } => {
|
mir::interpret::GlobalAlloc::Function { instance, .. } => {
|
||||||
GlobalAlloc::Function(instance.stable(tables))
|
GlobalAlloc::Function(instance.stable(tables))
|
||||||
}
|
}
|
||||||
mir::interpret::GlobalAlloc::VTable(ty, trait_ref) => {
|
mir::interpret::GlobalAlloc::VTable(ty, dyn_ty) => {
|
||||||
GlobalAlloc::VTable(ty.stable(tables), trait_ref.stable(tables))
|
// FIXME: Should we record the whole vtable?
|
||||||
|
GlobalAlloc::VTable(ty.stable(tables), dyn_ty.principal().stable(tables))
|
||||||
}
|
}
|
||||||
mir::interpret::GlobalAlloc::Static(def) => {
|
mir::interpret::GlobalAlloc::Static(def) => {
|
||||||
GlobalAlloc::Static(tables.static_def(*def))
|
GlobalAlloc::Static(tables.static_def(*def))
|
||||||
|
@ -16,5 +16,5 @@ impl T1 for i32 {
|
|||||||
fn main() {
|
fn main() {
|
||||||
let r = Box::new(0) as Box<dyn T1>;
|
let r = Box::new(0) as Box<dyn T1>;
|
||||||
let r2: Box<dyn T2> = unsafe { std::mem::transmute(r) };
|
let r2: Box<dyn T2> = unsafe { std::mem::transmute(r) };
|
||||||
r2.method2(); //~ERROR: using vtable for trait `T1` but trait `T2` was expected
|
r2.method2(); //~ERROR: using vtable for `T1` but `T2` was expected
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
error: Undefined Behavior: using vtable for trait `T1` but trait `T2` was expected
|
error: Undefined Behavior: using vtable for `T1` but `T2` was expected
|
||||||
--> tests/fail/dyn-call-trait-mismatch.rs:LL:CC
|
--> tests/fail/dyn-call-trait-mismatch.rs:LL:CC
|
||||||
|
|
|
|
||||||
LL | r2.method2();
|
LL | r2.method2();
|
||||||
| ^^^^^^^^^^^^ using vtable for trait `T1` but trait `T2` was expected
|
| ^^^^^^^^^^^^ using vtable for `T1` but `T2` was expected
|
||||||
|
|
|
|
||||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||||
|
@ -63,6 +63,6 @@ fn main() {
|
|||||||
let baz: &dyn Baz = &1;
|
let baz: &dyn Baz = &1;
|
||||||
let baz_fake: *const dyn Bar = std::mem::transmute(baz);
|
let baz_fake: *const dyn Bar = std::mem::transmute(baz);
|
||||||
let _err = baz_fake as *const dyn Foo;
|
let _err = baz_fake as *const dyn Foo;
|
||||||
//~^ERROR: using vtable for trait `Baz` but trait `Bar` was expected
|
//~^ERROR: using vtable for `Baz` but `Bar` was expected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
error: Undefined Behavior: using vtable for trait `Baz` but trait `Bar` was expected
|
error: Undefined Behavior: using vtable for `Baz` but `Bar` was expected
|
||||||
--> tests/fail/dyn-upcast-trait-mismatch.rs:LL:CC
|
--> tests/fail/dyn-upcast-trait-mismatch.rs:LL:CC
|
||||||
|
|
|
|
||||||
LL | let _err = baz_fake as *const dyn Foo;
|
LL | let _err = baz_fake as *const dyn Foo;
|
||||||
| ^^^^^^^^ using vtable for trait `Baz` but trait `Bar` was expected
|
| ^^^^^^^^ using vtable for `Baz` but `Bar` was expected
|
||||||
|
|
|
|
||||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
trait Trait {
|
||||||
|
type Assoc;
|
||||||
|
fn foo(&self) -> Self::Assoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Copy> Trait for T {
|
||||||
|
type Assoc = T;
|
||||||
|
fn foo(&self) -> T { *self }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let v: Box<dyn Trait<Assoc = u8>> = Box::new(2);
|
||||||
|
let v: Box<dyn Trait<Assoc = bool>> = unsafe { std::mem::transmute(v) }; //~ERROR: wrong trait
|
||||||
|
|
||||||
|
if v.foo() {
|
||||||
|
println!("huh");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `Trait<Assoc = bool>`, but encountered `Trait<Assoc = u8>`
|
||||||
|
--> tests/fail/validity/wrong-dyn-trait-assoc-type.rs:LL:CC
|
||||||
|
|
|
||||||
|
LL | let v: Box<dyn Trait<Assoc = bool>> = unsafe { std::mem::transmute(v) };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `Trait<Assoc = bool>`, but encountered `Trait<Assoc = u8>`
|
||||||
|
|
|
||||||
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||||
|
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||||
|
= note: BACKTRACE:
|
||||||
|
= note: inside `main` at tests/fail/validity/wrong-dyn-trait-assoc-type.rs:LL:CC
|
||||||
|
|
||||||
|
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `<trivial>`
|
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `std::marker::Send`
|
||||||
--> tests/fail/validity/wrong-dyn-trait.rs:LL:CC
|
--> tests/fail/validity/wrong-dyn-trait.rs:LL:CC
|
||||||
|
|
|
|
||||||
LL | let _y: *const dyn fmt::Debug = unsafe { mem::transmute(x) };
|
LL | let _y: *const dyn fmt::Debug = unsafe { mem::transmute(x) };
|
||||||
| ^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `<trivial>`
|
| ^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `std::marker::Send`
|
||||||
|
|
|
|
||||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||||
|
Loading…
Reference in New Issue
Block a user