1
Fork 0

Rollup merge of #124220 - RalfJung:interpret-wrong-vtable, r=oli-obk

Miri: detect wrong vtables in wide pointers

Fixes https://github.com/rust-lang/miri/issues/3497.
Needed to catch the UB that https://github.com/rust-lang/rust/pull/123572 will start exploiting.

r? `@oli-obk`
This commit is contained in:
Matthias Krüger 2024-04-23 06:24:57 +02:00 committed by GitHub
commit 8039488e59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 265 additions and 85 deletions

View file

@ -82,12 +82,6 @@ const_eval_double_storage_live =
const_eval_dyn_call_not_a_method = const_eval_dyn_call_not_a_method =
`dyn` call trying to call something that is not a method `dyn` call trying to call something that is not a method
const_eval_dyn_call_vtable_mismatch =
`dyn` call on a pointer whose vtable does not match its type
const_eval_dyn_star_call_vtable_mismatch =
`dyn*` call on a pointer whose vtable does not match its type
const_eval_error = {$error_kind -> const_eval_error = {$error_kind ->
[static] could not evaluate static initializer [static] could not evaluate static initializer
[const] evaluation of constant value failed [const] evaluation of constant value failed
@ -192,6 +186,8 @@ const_eval_invalid_uninit_bytes_unknown =
const_eval_invalid_vtable_pointer = 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 =
using vtable for trait `{$vtable_trait}` but trait `{$expected_trait}` was expected
const_eval_live_drop = const_eval_live_drop =
destructor of `{$dropped_ty}` cannot be evaluated at compile-time destructor of `{$dropped_ty}` cannot be evaluated at compile-time
@ -401,9 +397,6 @@ const_eval_unterminated_c_string =
const_eval_unwind_past_top = const_eval_unwind_past_top =
unwinding past the topmost frame of the stack unwinding past the topmost frame of the stack
const_eval_upcast_mismatch =
upcast on a pointer whose vtable does not match its type
## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`. ## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`.
## (We'd love to sort this differently to make that more clear but tidy won't let us...) ## (We'd love to sort this differently to make that more clear but tidy won't let us...)
const_eval_validation_box_to_static = {$front_matter}: encountered a box pointing to a static variable in a constant const_eval_validation_box_to_static = {$front_matter}: encountered a box pointing to a static variable in a constant
@ -450,6 +443,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_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

View file

@ -498,6 +498,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
InvalidTag(_) => const_eval_invalid_tag, InvalidTag(_) => const_eval_invalid_tag,
InvalidFunctionPointer(_) => const_eval_invalid_function_pointer, InvalidFunctionPointer(_) => const_eval_invalid_function_pointer,
InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer, InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer,
InvalidVTableTrait { .. } => const_eval_invalid_vtable_trait,
InvalidStr(_) => const_eval_invalid_str, InvalidStr(_) => const_eval_invalid_str,
InvalidUninitBytes(None) => const_eval_invalid_uninit_bytes_unknown, InvalidUninitBytes(None) => const_eval_invalid_uninit_bytes_unknown,
InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes, InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes,
@ -537,6 +538,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
| DeadLocal | DeadLocal
| UninhabitedEnumVariantWritten(_) | UninhabitedEnumVariantWritten(_)
| UninhabitedEnumVariantRead(_) => {} | UninhabitedEnumVariantRead(_) => {}
BoundsCheckFailed { len, index } => { BoundsCheckFailed { len, index } => {
diag.arg("len", len); diag.arg("len", len);
diag.arg("index", index); diag.arg("index", index);
@ -544,6 +546,13 @@ 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 } => {
diag.arg("expected_trait", expected_trait.to_string());
diag.arg(
"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)
.arg("bad_pointer_message", bad_pointer_message(msg, dcx)); .arg("bad_pointer_message", bad_pointer_message(msg, dcx));
@ -634,6 +643,7 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
UninhabitedEnumVariant => const_eval_validation_uninhabited_enum_variant, UninhabitedEnumVariant => const_eval_validation_uninhabited_enum_variant,
Uninit { .. } => const_eval_validation_uninit, Uninit { .. } => const_eval_validation_uninit,
InvalidVTablePtr { .. } => const_eval_validation_invalid_vtable_ptr, InvalidVTablePtr { .. } => const_eval_validation_invalid_vtable_ptr,
InvalidMetaWrongTrait { .. } => const_eval_validation_invalid_vtable_trait,
InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => { InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => {
const_eval_validation_invalid_box_slice_meta const_eval_validation_invalid_box_slice_meta
} }
@ -773,6 +783,13 @@ 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 } => {
err.arg("ref_trait", ref_trait.to_string());
err.arg(
"vtable_trait",
vtable_trait.map(|t| t.to_string()).unwrap_or_else(|| format!("<trivial>")),
);
}
NullPtr { .. } NullPtr { .. }
| PtrToStatic { .. } | PtrToStatic { .. }
| ConstRefToMutable | ConstRefToMutable

View file

@ -393,6 +393,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let val = self.read_immediate(src)?; let val = self.read_immediate(src)?;
if data_a.principal() == data_b.principal() { if data_a.principal() == data_b.principal() {
// A NOP cast that doesn't actually change anything, should be allowed even with mismatching vtables. // A NOP cast that doesn't actually change anything, should be allowed even with mismatching vtables.
// (But currently mismatching vtables violate the validity invariant so UB is triggered anyway.)
return self.write_immediate(*val, dest); return self.write_immediate(*val, dest);
} }
let (old_data, old_vptr) = val.to_scalar_pair(); let (old_data, old_vptr) = val.to_scalar_pair();
@ -400,7 +401,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let old_vptr = old_vptr.to_pointer(self)?; let old_vptr = old_vptr.to_pointer(self)?;
let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?; let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?;
if old_trait != data_a.principal() { if old_trait != data_a.principal() {
throw_ub_custom!(fluent::const_eval_upcast_mismatch); throw_ub!(InvalidVTableTrait {
expected_trait: data_a,
vtable_trait: old_trait,
});
} }
let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?; let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
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)

View file

@ -1020,16 +1020,20 @@ where
pub(super) fn unpack_dyn_trait( pub(super) fn unpack_dyn_trait(
&self, &self,
mplace: &MPlaceTy<'tcx, M::Provenance>, mplace: &MPlaceTy<'tcx, M::Provenance>,
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, Pointer<Option<M::Provenance>>)> { ) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, Pointer<Option<M::Provenance>>)> {
assert!( assert!(
matches!(mplace.layout.ty.kind(), ty::Dynamic(_, _, ty::Dyn)), matches!(mplace.layout.ty.kind(), ty::Dynamic(_, _, ty::Dyn)),
"`unpack_dyn_trait` only makes sense on `dyn*` types" "`unpack_dyn_trait` only makes sense on `dyn*` types"
); );
let vtable = mplace.meta().unwrap_meta().to_pointer(self)?; let vtable = mplace.meta().unwrap_meta().to_pointer(self)?;
let (ty, _) = self.get_ptr_vtable(vtable)?; let (ty, vtable_trait) = self.get_ptr_vtable(vtable)?;
let layout = self.layout_of(ty)?; if expected_trait.principal() != vtable_trait {
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
}
// This is a kind of transmute, from a place with unsized type and metadata to // This is a kind of transmute, from a place with unsized type and metadata to
// a place with sized type and no metadata. // a place with sized type and no metadata.
let layout = self.layout_of(ty)?;
let mplace = let mplace =
MPlaceTy { mplace: MemPlace { meta: MemPlaceMeta::None, ..mplace.mplace }, layout }; MPlaceTy { mplace: MemPlace { meta: MemPlaceMeta::None, ..mplace.mplace }, layout };
Ok((mplace, vtable)) Ok((mplace, vtable))
@ -1040,6 +1044,7 @@ where
pub(super) fn unpack_dyn_star<P: Projectable<'tcx, M::Provenance>>( pub(super) fn unpack_dyn_star<P: Projectable<'tcx, M::Provenance>>(
&self, &self,
val: &P, val: &P,
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> InterpResult<'tcx, (P, Pointer<Option<M::Provenance>>)> { ) -> InterpResult<'tcx, (P, Pointer<Option<M::Provenance>>)> {
assert!( assert!(
matches!(val.layout().ty.kind(), ty::Dynamic(_, _, ty::DynStar)), matches!(val.layout().ty.kind(), ty::Dynamic(_, _, ty::DynStar)),
@ -1048,10 +1053,12 @@ where
let data = self.project_field(val, 0)?; let data = self.project_field(val, 0)?;
let vtable = self.project_field(val, 1)?; let vtable = self.project_field(val, 1)?;
let vtable = self.read_pointer(&vtable.to_op(self)?)?; let vtable = self.read_pointer(&vtable.to_op(self)?)?;
let (ty, _) = self.get_ptr_vtable(vtable)?; let (ty, vtable_trait) = self.get_ptr_vtable(vtable)?;
if expected_trait.principal() != vtable_trait {
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
}
// `data` is already the right thing but has the wrong type. So we transmute it.
let layout = self.layout_of(ty)?; let layout = self.layout_of(ty)?;
// `data` is already the right thing but has the wrong type. So we transmute it, by
// projecting with offset 0.
let data = data.transmute(layout, self)?; let data = data.transmute(layout, self)?;
Ok((data, vtable)) Ok((data, vtable))
} }

View file

@ -803,11 +803,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let (vptr, dyn_ty, adjusted_receiver) = if let ty::Dynamic(data, _, ty::DynStar) = let (vptr, dyn_ty, adjusted_receiver) = if let ty::Dynamic(data, _, ty::DynStar) =
receiver_place.layout.ty.kind() receiver_place.layout.ty.kind()
{ {
let (recv, vptr) = self.unpack_dyn_star(&receiver_place)?; let (recv, vptr) = self.unpack_dyn_star(&receiver_place, data)?;
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?; let (dyn_ty, _dyn_trait) = self.get_ptr_vtable(vptr)?;
if dyn_trait != data.principal() {
throw_ub_custom!(fluent::const_eval_dyn_star_call_vtable_mismatch);
}
(vptr, dyn_ty, recv.ptr()) (vptr, dyn_ty, recv.ptr())
} else { } else {
@ -829,7 +826,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let vptr = receiver_place.meta().unwrap_meta().to_pointer(self)?; let vptr = receiver_place.meta().unwrap_meta().to_pointer(self)?;
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?; let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
if dyn_trait != data.principal() { if dyn_trait != data.principal() {
throw_ub_custom!(fluent::const_eval_dyn_call_vtable_mismatch); throw_ub!(InvalidVTableTrait {
expected_trait: data,
vtable_trait: dyn_trait,
});
} }
// It might be surprising that we use a pointer as the receiver even if this // It might be surprising that we use a pointer as the receiver even if this
@ -939,13 +939,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let place = self.force_allocation(place)?; let place = self.force_allocation(place)?;
let place = match place.layout.ty.kind() { let place = match place.layout.ty.kind() {
ty::Dynamic(_, _, ty::Dyn) => { ty::Dynamic(data, _, ty::Dyn) => {
// Dropping a trait object. Need to find actual drop fn. // Dropping a trait object. Need to find actual drop fn.
self.unpack_dyn_trait(&place)?.0 self.unpack_dyn_trait(&place, data)?.0
} }
ty::Dynamic(_, _, ty::DynStar) => { ty::Dynamic(data, _, ty::DynStar) => {
// Dropping a `dyn*`. Need to find actual drop fn. // Dropping a `dyn*`. Need to find actual drop fn.
self.unpack_dyn_star(&place)?.0 self.unpack_dyn_star(&place, data)?.0
} }
_ => { _ => {
debug_assert_eq!( debug_assert_eq!(

View file

@ -339,16 +339,22 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
) -> InterpResult<'tcx> { ) -> InterpResult<'tcx> {
let tail = self.ecx.tcx.struct_tail_erasing_lifetimes(pointee.ty, self.ecx.param_env); let tail = self.ecx.tcx.struct_tail_erasing_lifetimes(pointee.ty, self.ecx.param_env);
match tail.kind() { match tail.kind() {
ty::Dynamic(_, _, ty::Dyn) => { ty::Dynamic(data, _, ty::Dyn) => {
let vtable = meta.unwrap_meta().to_pointer(self.ecx)?; let vtable = meta.unwrap_meta().to_pointer(self.ecx)?;
// Make sure it is a genuine vtable pointer. // Make sure it is a genuine vtable pointer.
let (_ty, _trait) = try_validation!( let (_dyn_ty, dyn_trait) = try_validation!(
self.ecx.get_ptr_vtable(vtable), self.ecx.get_ptr_vtable(vtable),
self.path, self.path,
Ub(DanglingIntPointer(..) | InvalidVTablePointer(..)) => Ub(DanglingIntPointer(..) | InvalidVTablePointer(..)) =>
InvalidVTablePtr { value: format!("{vtable}") } InvalidVTablePtr { value: format!("{vtable}") }
); );
// FIXME: check if the type/trait match what ty::Dynamic says? // Make sure it is for the right trait.
if dyn_trait != data.principal() {
throw_validation_failure!(
self.path,
InvalidMetaWrongTrait { expected_trait: data, vtable_trait: dyn_trait }
);
}
} }
ty::Slice(..) | ty::Str => { ty::Slice(..) | ty::Str => {
let _len = meta.unwrap_meta().to_target_usize(self.ecx)?; let _len = meta.unwrap_meta().to_target_usize(self.ecx)?;
@ -933,7 +939,16 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
} }
} }
_ => { _ => {
self.walk_value(op)?; // default handler // default handler
try_validation!(
self.walk_value(op),
self.path,
// It's not great to catch errors here, since we can't give a very good path,
// but it's better than ICEing.
Ub(InvalidVTableTrait { expected_trait, vtable_trait }) => {
InvalidMetaWrongTrait { expected_trait, vtable_trait: *vtable_trait }
},
);
} }
} }

View file

@ -88,22 +88,22 @@ pub trait ValueVisitor<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>>: Sized {
// Special treatment for special types, where the (static) layout is not sufficient. // Special treatment for special types, where the (static) layout is not sufficient.
match *ty.kind() { match *ty.kind() {
// If it is a trait object, switch to the real type that was used to create it. // If it is a trait object, switch to the real type that was used to create it.
ty::Dynamic(_, _, ty::Dyn) => { ty::Dynamic(data, _, ty::Dyn) => {
// Dyn types. This is unsized, and the actual dynamic type of the data is given by the // Dyn types. This is unsized, and the actual dynamic type of the data is given by the
// vtable stored in the place metadata. // vtable stored in the place metadata.
// unsized values are never immediate, so we can assert_mem_place // unsized values are never immediate, so we can assert_mem_place
let op = v.to_op(self.ecx())?; let op = v.to_op(self.ecx())?;
let dest = op.assert_mem_place(); let dest = op.assert_mem_place();
let inner_mplace = self.ecx().unpack_dyn_trait(&dest)?.0; let inner_mplace = self.ecx().unpack_dyn_trait(&dest, data)?.0;
trace!("walk_value: dyn object layout: {:#?}", inner_mplace.layout); trace!("walk_value: dyn object layout: {:#?}", inner_mplace.layout);
// recurse with the inner type // recurse with the inner type
return self.visit_field(v, 0, &inner_mplace.into()); return self.visit_field(v, 0, &inner_mplace.into());
} }
ty::Dynamic(_, _, ty::DynStar) => { ty::Dynamic(data, _, ty::DynStar) => {
// DynStar types. Very different from a dyn type (but strangely part of the // DynStar types. Very different from a dyn type (but strangely part of the
// same variant in `TyKind`): These are pairs where the 2nd component is the // same variant in `TyKind`): These are pairs where the 2nd component is the
// vtable, and the first component is the data (which must be ptr-sized). // vtable, and the first component is the data (which must be ptr-sized).
let data = self.ecx().unpack_dyn_star(v)?.0; let data = self.ecx().unpack_dyn_star(v, data)?.0;
return self.visit_field(v, 0, &data); return self.visit_field(v, 0, &data);
} }
// Slices do not need special handling here: they have `Array` field // Slices do not need special handling here: they have `Array` field

View file

@ -2,7 +2,7 @@ use super::{AllocId, AllocRange, ConstAllocation, Pointer, Scalar};
use crate::error; use crate::error;
use crate::mir::{ConstAlloc, ConstValue}; use crate::mir::{ConstAlloc, ConstValue};
use crate::ty::{layout, tls, Ty, TyCtxt, ValTree}; use crate::ty::{self, layout, tls, Ty, TyCtxt, ValTree};
use rustc_ast_ir::Mutability; use rustc_ast_ir::Mutability;
use rustc_data_structures::sync::Lock; use rustc_data_structures::sync::Lock;
@ -344,6 +344,11 @@ pub enum UndefinedBehaviorInfo<'tcx> {
InvalidFunctionPointer(Pointer<AllocId>), InvalidFunctionPointer(Pointer<AllocId>),
/// Using a pointer-not-to-a-vtable as vtable pointer. /// Using a pointer-not-to-a-vtable as vtable pointer.
InvalidVTablePointer(Pointer<AllocId>), 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>>,
},
/// 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),
/// Using uninitialized data where it is not allowed. /// Using uninitialized data where it is not allowed.
@ -414,34 +419,86 @@ impl From<PointerKind> for ExpectedKind {
#[derive(Debug)] #[derive(Debug)]
pub enum ValidationErrorKind<'tcx> { pub enum ValidationErrorKind<'tcx> {
PointerAsInt { expected: ExpectedKind }, PointerAsInt {
expected: ExpectedKind,
},
PartialPointer, PartialPointer,
PtrToUninhabited { ptr_kind: PointerKind, ty: Ty<'tcx> }, PtrToUninhabited {
PtrToStatic { ptr_kind: PointerKind }, ptr_kind: PointerKind,
ty: Ty<'tcx>,
},
PtrToStatic {
ptr_kind: PointerKind,
},
ConstRefToMutable, ConstRefToMutable,
ConstRefToExtern, ConstRefToExtern,
MutableRefToImmutable, MutableRefToImmutable,
UnsafeCellInImmutable, UnsafeCellInImmutable,
NullFnPtr, NullFnPtr,
NeverVal, NeverVal,
NullablePtrOutOfRange { range: WrappingRange, max_value: u128 }, NullablePtrOutOfRange {
PtrOutOfRange { range: WrappingRange, max_value: u128 }, range: WrappingRange,
OutOfRange { value: String, range: WrappingRange, max_value: u128 }, max_value: u128,
UninhabitedVal { ty: Ty<'tcx> }, },
InvalidEnumTag { value: String }, PtrOutOfRange {
range: WrappingRange,
max_value: u128,
},
OutOfRange {
value: String,
range: WrappingRange,
max_value: u128,
},
UninhabitedVal {
ty: Ty<'tcx>,
},
InvalidEnumTag {
value: String,
},
UninhabitedEnumVariant, UninhabitedEnumVariant,
Uninit { expected: ExpectedKind }, Uninit {
InvalidVTablePtr { value: String }, expected: ExpectedKind,
InvalidMetaSliceTooLarge { ptr_kind: PointerKind }, },
InvalidMetaTooLarge { ptr_kind: PointerKind }, InvalidVTablePtr {
UnalignedPtr { ptr_kind: PointerKind, required_bytes: u64, found_bytes: u64 }, value: String,
NullPtr { ptr_kind: PointerKind }, },
DanglingPtrNoProvenance { ptr_kind: PointerKind, pointer: String }, InvalidMetaWrongTrait {
DanglingPtrOutOfBounds { ptr_kind: PointerKind }, expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
DanglingPtrUseAfterFree { ptr_kind: PointerKind }, vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
InvalidBool { value: String }, },
InvalidChar { value: String }, InvalidMetaSliceTooLarge {
InvalidFnPtr { value: String }, ptr_kind: PointerKind,
},
InvalidMetaTooLarge {
ptr_kind: PointerKind,
},
UnalignedPtr {
ptr_kind: PointerKind,
required_bytes: u64,
found_bytes: u64,
},
NullPtr {
ptr_kind: PointerKind,
},
DanglingPtrNoProvenance {
ptr_kind: PointerKind,
pointer: String,
},
DanglingPtrOutOfBounds {
ptr_kind: PointerKind,
},
DanglingPtrUseAfterFree {
ptr_kind: PointerKind,
},
InvalidBool {
value: String,
},
InvalidChar {
value: String,
},
InvalidFnPtr {
value: String,
},
} }
/// Error information for when the program did something that might (or might not) be correct /// Error information for when the program did something that might (or might not) be correct

View file

@ -1,3 +1,6 @@
// Validation stops this too early.
//@compile-flags: -Zmiri-disable-validation
trait T1 { trait T1 {
#[allow(dead_code)] #[allow(dead_code)]
fn method1(self: Box<Self>); fn method1(self: Box<Self>);
@ -13,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: call on a pointer whose vtable does not match its type r2.method2(); //~ERROR: using vtable for trait `T1` but trait `T2` was expected
} }

View file

@ -1,8 +1,8 @@
error: Undefined Behavior: `dyn` call on a pointer whose vtable does not match its type error: Undefined Behavior: using vtable for trait `T1` but trait `T2` was expected
--> $DIR/dyn-call-trait-mismatch.rs:LL:CC --> $DIR/dyn-call-trait-mismatch.rs:LL:CC
| |
LL | r2.method2(); LL | r2.method2();
| ^^^^^^^^^^^^ `dyn` call on a pointer whose vtable does not match its type | ^^^^^^^^^^^^ using vtable for trait `T1` but trait `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

View file

@ -0,0 +1,15 @@
// This upcast is currently forbidden because it involves an invalid value.
// However, if in the future we relax the validity requirements for raw pointer vtables,
// we could consider allowing this again -- the cast itself isn't doing anything wrong,
// only the transmutes needed to set up the testcase are wrong.
use std::fmt;
fn main() {
// vtable_mismatch_nop_cast
let ptr: &dyn fmt::Display = &0;
let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) }; //~ERROR: wrong trait
// Even though the vtable is for the wrong trait, this cast doesn't actually change the needed
// vtable so it should still be allowed -- if we ever allow the line above.
let _ptr2 = ptr as *const dyn fmt::Debug;
}

View file

@ -0,0 +1,15 @@
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug + std::marker::Send + std::marker::Sync`, but encountered `std::fmt::Display`
--> $DIR/dyn-upcast-nop-wrong-trait.rs:LL:CC
|
LL | let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) };
| ^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug + std::marker::Send + std::marker::Sync`, but encountered `std::fmt::Display`
|
= 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 $DIR/dyn-upcast-nop-wrong-trait.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

View file

@ -1,3 +1,6 @@
// Validation stops this too early.
//@compile-flags: -Zmiri-disable-validation
#![feature(trait_upcasting)] #![feature(trait_upcasting)]
#![allow(incomplete_features)] #![allow(incomplete_features)]
@ -57,7 +60,7 @@ impl Baz for i32 {
fn main() { fn main() {
let baz: &dyn Baz = &1; let baz: &dyn Baz = &1;
let baz_fake: &dyn Bar = unsafe { std::mem::transmute(baz) }; let baz_fake: *const dyn Bar = unsafe { std::mem::transmute(baz) };
let _err = baz_fake as &dyn Foo; let _err = baz_fake as *const dyn Foo;
//~^ERROR: upcast on a pointer whose vtable does not match its type //~^ERROR: using vtable for trait `Baz` but trait `Bar` was expected
} }

View file

@ -1,8 +1,8 @@
error: Undefined Behavior: upcast on a pointer whose vtable does not match its type error: Undefined Behavior: using vtable for trait `Baz` but trait `Bar` was expected
--> $DIR/dyn-upcast-trait-mismatch.rs:LL:CC --> $DIR/dyn-upcast-trait-mismatch.rs:LL:CC
| |
LL | let _err = baz_fake as &dyn Foo; LL | let _err = baz_fake as *const dyn Foo;
| ^^^^^^^^ upcast on a pointer whose vtable does not match its type | ^^^^^^^^ using vtable for trait `Baz` but trait `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

View file

@ -0,0 +1,12 @@
use std::mem;
// Make sure we notice the mismatch also if the difference is "only" in the generic
// parameters of the trait.
trait Trait<T> {}
impl<T> Trait<T> for T {}
fn main() {
let x: &dyn Trait<i32> = &0;
let _y: *const dyn Trait<u32> = unsafe { mem::transmute(x) }; //~ERROR: wrong trait
}

View file

@ -0,0 +1,15 @@
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `Trait<u32>`, but encountered `Trait<i32>`
--> $DIR/wrong-dyn-trait-generic.rs:LL:CC
|
LL | let _y: *const dyn Trait<u32> = unsafe { mem::transmute(x) };
| ^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `Trait<u32>`, but encountered `Trait<i32>`
|
= 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 $DIR/wrong-dyn-trait-generic.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

View file

@ -0,0 +1,6 @@
use std::{fmt, mem};
fn main() {
let x: &dyn Send = &0;
let _y: *const dyn fmt::Debug = unsafe { mem::transmute(x) }; //~ERROR: wrong trait
}

View file

@ -0,0 +1,15 @@
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `<trivial>`
--> $DIR/wrong-dyn-trait.rs:LL:CC
|
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>`
|
= 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 $DIR/wrong-dyn-trait.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

View file

@ -25,7 +25,7 @@ impl Foo<u32> for u32 {
impl Bar for () {} impl Bar for () {}
unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo<u32> + 'a)) -> u32 { unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo<u32> + 'a)) -> u32 {
let foo_e: *const dyn Foo<u16> = t as *const _; let foo_e: *const dyn Foo<u32> = t as *const _;
let r_1 = foo_e as *mut dyn Foo<u32>; let r_1 = foo_e as *mut dyn Foo<u32>;
(&*r_1).foo(0) (&*r_1).foo(0)

View file

@ -1,24 +1,26 @@
#![feature(trait_upcasting)] #![feature(trait_upcasting)]
#![allow(incomplete_features)] #![allow(incomplete_features)]
use std::fmt;
fn main() { fn main() {
basic(); basic();
diamond(); diamond();
struct_(); struct_();
replace_vptr(); replace_vptr();
vtable_mismatch_nop_cast(); vtable_nop_cast();
} }
fn vtable_mismatch_nop_cast() { fn vtable_nop_cast() {
let ptr: &dyn std::fmt::Display = &0; let ptr: &dyn fmt::Debug = &0;
// Even though the vtable is for the wrong trait, this cast doesn't actually change the needed // We transmute things around, but the principal trait does not change, so this is allowed.
// vtable so it should still be allowed. let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) };
let ptr: *const (dyn std::fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) }; // This cast is a NOP and should be allowed.
let _ptr2 = ptr as *const dyn std::fmt::Debug; let _ptr2 = ptr as *const dyn fmt::Debug;
} }
fn basic() { fn basic() {
trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync { trait Foo: PartialEq<i32> + fmt::Debug + Send + Sync {
fn a(&self) -> i32 { fn a(&self) -> i32 {
10 10
} }
@ -67,7 +69,7 @@ fn basic() {
} }
let baz: &dyn Baz = &1; let baz: &dyn Baz = &1;
let _: &dyn std::fmt::Debug = baz; let _: &dyn fmt::Debug = baz;
assert_eq!(*baz, 1); assert_eq!(*baz, 1);
assert_eq!(baz.a(), 100); assert_eq!(baz.a(), 100);
assert_eq!(baz.b(), 200); assert_eq!(baz.b(), 200);
@ -77,7 +79,7 @@ fn basic() {
assert_eq!(baz.w(), 21); assert_eq!(baz.w(), 21);
let bar: &dyn Bar = baz; let bar: &dyn Bar = baz;
let _: &dyn std::fmt::Debug = bar; let _: &dyn fmt::Debug = bar;
assert_eq!(*bar, 1); assert_eq!(*bar, 1);
assert_eq!(bar.a(), 100); assert_eq!(bar.a(), 100);
assert_eq!(bar.b(), 200); assert_eq!(bar.b(), 200);
@ -86,14 +88,14 @@ fn basic() {
assert_eq!(bar.w(), 21); assert_eq!(bar.w(), 21);
let foo: &dyn Foo = baz; let foo: &dyn Foo = baz;
let _: &dyn std::fmt::Debug = foo; let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1); assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100); assert_eq!(foo.a(), 100);
assert_eq!(foo.z(), 11); assert_eq!(foo.z(), 11);
assert_eq!(foo.y(), 12); assert_eq!(foo.y(), 12);
let foo: &dyn Foo = bar; let foo: &dyn Foo = bar;
let _: &dyn std::fmt::Debug = foo; let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1); assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100); assert_eq!(foo.a(), 100);
assert_eq!(foo.z(), 11); assert_eq!(foo.z(), 11);
@ -101,7 +103,7 @@ fn basic() {
} }
fn diamond() { fn diamond() {
trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync { trait Foo: PartialEq<i32> + fmt::Debug + Send + Sync {
fn a(&self) -> i32 { fn a(&self) -> i32 {
10 10
} }
@ -166,7 +168,7 @@ fn diamond() {
} }
let baz: &dyn Baz = &1; let baz: &dyn Baz = &1;
let _: &dyn std::fmt::Debug = baz; let _: &dyn fmt::Debug = baz;
assert_eq!(*baz, 1); assert_eq!(*baz, 1);
assert_eq!(baz.a(), 100); assert_eq!(baz.a(), 100);
assert_eq!(baz.b(), 200); assert_eq!(baz.b(), 200);
@ -178,7 +180,7 @@ fn diamond() {
assert_eq!(baz.v(), 31); assert_eq!(baz.v(), 31);
let bar1: &dyn Bar1 = baz; let bar1: &dyn Bar1 = baz;
let _: &dyn std::fmt::Debug = bar1; let _: &dyn fmt::Debug = bar1;
assert_eq!(*bar1, 1); assert_eq!(*bar1, 1);
assert_eq!(bar1.a(), 100); assert_eq!(bar1.a(), 100);
assert_eq!(bar1.b(), 200); assert_eq!(bar1.b(), 200);
@ -187,7 +189,7 @@ fn diamond() {
assert_eq!(bar1.w(), 21); assert_eq!(bar1.w(), 21);
let bar2: &dyn Bar2 = baz; let bar2: &dyn Bar2 = baz;
let _: &dyn std::fmt::Debug = bar2; let _: &dyn fmt::Debug = bar2;
assert_eq!(*bar2, 1); assert_eq!(*bar2, 1);
assert_eq!(bar2.a(), 100); assert_eq!(bar2.a(), 100);
assert_eq!(bar2.c(), 300); assert_eq!(bar2.c(), 300);
@ -196,17 +198,17 @@ fn diamond() {
assert_eq!(bar2.v(), 31); assert_eq!(bar2.v(), 31);
let foo: &dyn Foo = baz; let foo: &dyn Foo = baz;
let _: &dyn std::fmt::Debug = foo; let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1); assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100); assert_eq!(foo.a(), 100);
let foo: &dyn Foo = bar1; let foo: &dyn Foo = bar1;
let _: &dyn std::fmt::Debug = foo; let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1); assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100); assert_eq!(foo.a(), 100);
let foo: &dyn Foo = bar2; let foo: &dyn Foo = bar2;
let _: &dyn std::fmt::Debug = foo; let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1); assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100); assert_eq!(foo.a(), 100);
} }
@ -215,7 +217,7 @@ fn struct_() {
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync { trait Foo: PartialEq<i32> + fmt::Debug + Send + Sync {
fn a(&self) -> i32 { fn a(&self) -> i32 {
10 10
} }

View file

@ -17,7 +17,7 @@ impl Foo<u32> for u32 { fn foo(&self, _: u32) -> u32 { self+43 } }
impl Bar for () {} impl Bar for () {}
unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo<u32>+'a)) -> u32 { unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo<u32>+'a)) -> u32 {
let foo_e : *const dyn Foo<u16> = t as *const _; let foo_e : *const dyn Foo<u32> = t as *const _;
let r_1 = foo_e as *mut dyn Foo<u32>; let r_1 = foo_e as *mut dyn Foo<u32>;
(&*r_1).foo(0) (&*r_1).foo(0)