Rollup merge of #143975 - RalfJung:type-id-eq, r=oli-obk

type_id_eq: check that the hash fully matches the type

The previous logic wouldn't always detect when the hash mismatches the provenance. Fix that by adding a new helper, `read_type_id`, that reads a single type ID while fully checking it for validity and consistency.

r? ``@oli-obk``
This commit is contained in:
Matthias Krüger
2025-07-17 10:41:48 +02:00
committed by GitHub
6 changed files with 66 additions and 58 deletions

View File

@@ -4,8 +4,9 @@
use std::assert_matches::assert_matches;
use rustc_abi::{FieldIdx, Size};
use rustc_abi::{FieldIdx, HasDataLayout, Size};
use rustc_apfloat::ieee::{Double, Half, Quad, Single};
use rustc_middle::mir::interpret::{read_target_uint, write_target_uint};
use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic};
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{Ty, TyCtxt};
@@ -30,7 +31,7 @@ pub(crate) fn alloc_type_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ConstAll
}
impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
/// Generates a value of `TypeId` for `ty` in-place.
pub(crate) fn write_type_id(
fn write_type_id(
&mut self,
ty: Ty<'tcx>,
dest: &PlaceTy<'tcx, M::Provenance>,
@@ -48,8 +49,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// Here we rely on `TypeId` being a newtype around an array of pointers, so we
// first project to its only field and then the array elements.
let alloc_id = tcx.reserve_and_set_type_id_alloc(ty);
let first = self.project_field(dest, FieldIdx::ZERO)?;
let mut elem_iter = self.project_array_fields(&first)?;
let arr = self.project_field(dest, FieldIdx::ZERO)?;
let mut elem_iter = self.project_array_fields(&arr)?;
while let Some((_, elem)) = elem_iter.next(self)? {
// Decorate this part of the hash with provenance; leave the integer part unchanged.
let hash_fragment = self.read_scalar(&elem)?.to_target_usize(&tcx)?;
@@ -61,6 +62,52 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
interp_ok(())
}
/// Read a value of type `TypeId`, returning the type it represents.
pub(crate) fn read_type_id(
&self,
op: &OpTy<'tcx, M::Provenance>,
) -> InterpResult<'tcx, Ty<'tcx>> {
// `TypeId` is a newtype around an array of pointers. All pointers must have the same
// provenance, and that provenance represents the type.
let ptr_size = self.pointer_size().bytes_usize();
let arr = self.project_field(op, FieldIdx::ZERO)?;
let mut ty_and_hash = None;
let mut elem_iter = self.project_array_fields(&arr)?;
while let Some((idx, elem)) = elem_iter.next(self)? {
let elem = self.read_pointer(&elem)?;
let (elem_ty, elem_hash) = self.get_ptr_type_id(elem)?;
// If this is the first element, remember the type and its hash.
// If this is not the first element, ensure it is consistent with the previous ones.
let full_hash = match ty_and_hash {
None => {
let hash = self.tcx.type_id_hash(elem_ty).as_u128();
let mut hash_bytes = [0u8; 16];
write_target_uint(self.data_layout().endian, &mut hash_bytes, hash).unwrap();
ty_and_hash = Some((elem_ty, hash_bytes));
hash_bytes
}
Some((ty, hash_bytes)) => {
if ty != elem_ty {
throw_ub_format!(
"invalid `TypeId` value: not all bytes carry the same type id metadata"
);
}
hash_bytes
}
};
// Ensure the elem_hash matches the corresponding part of the full hash.
let hash_frag = &full_hash[(idx as usize) * ptr_size..][..ptr_size];
if read_target_uint(self.data_layout().endian, hash_frag).unwrap() != elem_hash.into() {
throw_ub_format!(
"invalid `TypeId` value: the hash does not match the type id metadata"
);
}
}
interp_ok(ty_and_hash.unwrap().0)
}
/// Returns `true` if emulation happened.
/// Here we implement the intrinsics that are common to all Miri instances; individual machines can add their own
/// intrinsic handling.
@@ -97,47 +144,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self.write_type_id(tp_ty, dest)?;
}
sym::type_id_eq => {
// Both operands are `TypeId`, which is a newtype around an array of pointers.
// Project until we have the array elements.
let a_fields = self.project_field(&args[0], FieldIdx::ZERO)?;
let b_fields = self.project_field(&args[1], FieldIdx::ZERO)?;
let mut a_fields = self.project_array_fields(&a_fields)?;
let mut b_fields = self.project_array_fields(&b_fields)?;
let mut provenance_a = None;
let mut provenance_b = None;
let mut provenance_matches = true;
while let Some((i, a)) = a_fields.next(self)? {
let (_, b) = b_fields.next(self)?.unwrap();
let a = self.deref_pointer(&a)?;
let (a, offset_a) = self.get_ptr_type_id(a.ptr())?;
let b = self.deref_pointer(&b)?;
let (b, offset_b) = self.get_ptr_type_id(b.ptr())?;
if *provenance_a.get_or_insert(a) != a {
throw_ub_format!(
"type_id_eq: the first TypeId argument is invalid, the provenance of chunk {i} does not match the first chunk's"
)
}
if *provenance_b.get_or_insert(b) != b {
throw_ub_format!(
"type_id_eq: the second TypeId argument is invalid, the provenance of chunk {i} does not match the first chunk's"
)
}
provenance_matches &= a == b;
if offset_a != offset_b && provenance_matches {
throw_ub_format!(
"type_id_eq: one of the TypeId arguments is invalid, chunk {i} of the hash does not match the type it represents"
)
}
}
self.write_scalar(Scalar::from_bool(provenance_matches), dest)?;
let a_ty = self.read_type_id(&args[0])?;
let b_ty = self.read_type_id(&args[1])?;
self.write_scalar(Scalar::from_bool(a_ty == b_ty), dest)?;
}
sym::variant_count => {
let tp_ty = instance.args.type_at(0);

View File

@@ -997,12 +997,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
pub fn get_ptr_type_id(
&self,
ptr: Pointer<Option<M::Provenance>>,
) -> InterpResult<'tcx, (Ty<'tcx>, Size)> {
) -> InterpResult<'tcx, (Ty<'tcx>, u64)> {
let (alloc_id, offset, _meta) = self.ptr_get_alloc_id(ptr, 0)?;
let GlobalAlloc::TypeId { ty } = self.tcx.global_alloc(alloc_id) else {
throw_ub_format!("type_id_eq: `TypeId` provenance is not a type id")
throw_ub_format!("invalid `TypeId` value: not all bytes carry type id metadata")
};
interp_ok((ty, offset))
interp_ok((ty, offset.bytes()))
}
pub fn get_ptr_fn(