Skip to content

Commit 9c515b8

Browse files
committed
Make TypeId robust against compile-time introspection
1 parent 0d60777 commit 9c515b8

File tree

14 files changed

+133
-52
lines changed

14 files changed

+133
-52
lines changed

compiler/rustc_codegen_llvm/src/common.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,19 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
342342
Scalar::from_u128(type_id),
343343
)
344344
.unwrap();
345+
alloc.mutability = Mutability::Not;
345346
let init = const_alloc_to_llvm(self, &alloc, /*static*/ false);
346347
self.static_addr_of_impl(init, alloc.align, None)
347348
}
349+
GlobalAlloc::PartialHash(ty) => {
350+
assert!(matches!(layout.primitive(), Pointer(_)));
351+
let bytes = self.tcx.type_id_hash(ty).truncate().as_u64().to_be_bytes();
352+
let bits = self.tcx.data_layout.pointer_size.bits();
353+
let mask = u64::MAX >> (64 - bits);
354+
// It doesn't matter which bits we pick as long as the scheme is the same with the same compiler.
355+
let llval = self.const_usize(u64::from_be_bytes(bytes) & mask);
356+
return unsafe { llvm::LLVMConstIntToPtr(llval, llty) };
357+
}
348358
};
349359
let base_addr_space = global_alloc.address_space(self);
350360
let llval = unsafe {

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use std::assert_matches::assert_matches;
66

77
use rustc_abi::Size;
88
use rustc_apfloat::ieee::{Double, Half, Quad, Single};
9+
use rustc_ast::Mutability;
910
use rustc_hir::def_id::DefId;
11+
use rustc_middle::mir::interpret::{AllocId, AllocInit, CtfeProvenance, alloc_range};
1012
use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic};
1113
use rustc_middle::ty::layout::{LayoutOf as _, TyAndLayout, ValidityRequirement};
1214
use rustc_middle::ty::{GenericArgsRef, Ty, TyCtxt};
@@ -30,6 +32,31 @@ pub(crate) fn alloc_type_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ConstAll
3032
tcx.mk_const_alloc(alloc)
3133
}
3234

35+
pub(crate) fn alloc_type_id<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> AllocId {
36+
let ptr_size = tcx.data_layout.pointer_size;
37+
let align = tcx.data_layout.pointer_align;
38+
39+
let mut alloc = Allocation::new(ptr_size * 2, *align, AllocInit::Uninit, ());
40+
// Write a pointer pointing to the hash. At ctfe time this is an opaque pointer that you cannot deref
41+
let ptr = tcx.reserve_and_set_type_id_alloc(ty);
42+
let ptr = Pointer::new(CtfeProvenance::from(ptr), Size::ZERO);
43+
alloc
44+
.write_scalar(&tcx, alloc_range(Size::ZERO, ptr_size), Scalar::from_pointer(ptr, &tcx))
45+
.unwrap();
46+
47+
// Write a pointer that is not actually a pointer but will just get replaced by the first `pointer_size` bytes of the hash
48+
// in codegen. It is a pointer in CTFE so no one can access the bits.
49+
let ptr = tcx.reserve_and_set_type_id_partial_hash(ty);
50+
let ptr = Pointer::new(CtfeProvenance::from(ptr), Size::ZERO);
51+
alloc
52+
.write_scalar(&tcx, alloc_range(ptr_size, ptr_size), Scalar::from_pointer(ptr, &tcx))
53+
.unwrap();
54+
55+
alloc.mutability = Mutability::Not;
56+
57+
tcx.reserve_and_set_memory_alloc(tcx.mk_const_alloc(alloc))
58+
}
59+
3360
/// The logic for all nullary intrinsics is implemented here. These intrinsics don't get evaluated
3461
/// inside an `InterpCx` and instead have their value computed directly from rustc internal info.
3562
pub(crate) fn eval_nullary_intrinsic<'tcx>(
@@ -52,7 +79,8 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>(
5279
}
5380
sym::type_id => {
5481
ensure_monomorphic_enough(tcx, tp_ty)?;
55-
ConstValue::from_u128(tcx.type_id_hash(tp_ty).as_u128())
82+
let alloc_id = alloc_type_id(tcx, tp_ty);
83+
ConstValue::Indirect { alloc_id, offset: Size::ZERO }
5684
}
5785
sym::variant_count => match match tp_ty.kind() {
5886
// Pattern types have the same number of variants as their base type.

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
353353
kind = "typeid",
354354
)
355355
}
356+
Some(GlobalAlloc::PartialHash(..)) => {
357+
err_ub_custom!(
358+
fluent::const_eval_invalid_dealloc,
359+
alloc_id = alloc_id,
360+
kind = "partial_hash",
361+
)
362+
}
356363
Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
357364
err_ub_custom!(
358365
fluent::const_eval_invalid_dealloc,
@@ -623,7 +630,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
623630
Some(GlobalAlloc::Function { .. }) => throw_ub!(DerefFunctionPointer(id)),
624631
Some(GlobalAlloc::VTable(..)) => throw_ub!(DerefVTablePointer(id)),
625632
// TODO
626-
Some(GlobalAlloc::Type(..)) => throw_ub!(DerefTypeIdPointer(id)),
633+
//Some(GlobalAlloc::Type(..)) => throw_ub!(DerefTypeIdPointer(id)),
634+
//Some(GlobalAlloc::PartialHash(..)) => throw_ub!(DerefTypeIdPointer(id)),
635+
Some(GlobalAlloc::Type(..)) => todo!(),
636+
Some(GlobalAlloc::PartialHash(..)) => todo!(),
627637
None => throw_ub!(PointerUseAfterFree(id, CheckInAllocMsg::MemoryAccess)),
628638
Some(GlobalAlloc::Static(def_id)) => {
629639
assert!(self.tcx.is_static(def_id));
@@ -905,9 +915,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
905915
let (size, align) = global_alloc.size_and_align(*self.tcx, self.typing_env);
906916
let mutbl = global_alloc.mutability(*self.tcx, self.typing_env);
907917
let kind = match global_alloc {
908-
GlobalAlloc::Type(_) | GlobalAlloc::Static { .. } | GlobalAlloc::Memory { .. } => {
909-
AllocKind::LiveData
910-
}
918+
GlobalAlloc::Type(_)
919+
| GlobalAlloc::PartialHash(_)
920+
| GlobalAlloc::Static { .. }
921+
| GlobalAlloc::Memory { .. } => AllocKind::LiveData,
911922
GlobalAlloc::Function { .. } => bug!("We already checked function pointers above"),
912923
GlobalAlloc::VTable { .. } => AllocKind::VTable,
913924
};
@@ -1220,6 +1231,9 @@ impl<'a, 'tcx, M: Machine<'tcx>> std::fmt::Debug for DumpAllocs<'a, 'tcx, M> {
12201231
Some(GlobalAlloc::Type(ty)) => {
12211232
write!(fmt, " (typeid for {ty})")?;
12221233
}
1234+
Some(GlobalAlloc::PartialHash(ty)) => {
1235+
write!(fmt, " (partial hash of {ty})")?;
1236+
}
12231237
Some(GlobalAlloc::Static(did)) => {
12241238
write!(fmt, " (static: {})", self.ecx.tcx.def_path_str(did))?;
12251239
}

compiler/rustc_const_eval/src/interpret/validity.rs

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -571,38 +571,43 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
571571
let (size, _align) =
572572
global_alloc.size_and_align(*self.ecx.tcx, self.ecx.typing_env);
573573

574-
if let GlobalAlloc::Static(did) = global_alloc {
575-
let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else {
576-
bug!()
577-
};
578-
// Special handling for pointers to statics (irrespective of their type).
579-
assert!(!self.ecx.tcx.is_thread_local_static(did));
580-
assert!(self.ecx.tcx.is_static(did));
581-
// Mode-specific checks
582-
match ctfe_mode {
583-
CtfeValidationMode::Static { .. }
584-
| CtfeValidationMode::Promoted { .. } => {
585-
// We skip recursively checking other statics. These statics must be sound by
586-
// themselves, and the only way to get broken statics here is by using
587-
// unsafe code.
588-
// The reasons we don't check other statics is twofold. For one, in all
589-
// sound cases, the static was already validated on its own, and second, we
590-
// trigger cycle errors if we try to compute the value of the other static
591-
// and that static refers back to us (potentially through a promoted).
592-
// This could miss some UB, but that's fine.
593-
// We still walk nested allocations, as they are fundamentally part of this validation run.
594-
// This means we will also recurse into nested statics of *other*
595-
// statics, even though we do not recurse into other statics directly.
596-
// That's somewhat inconsistent but harmless.
597-
skip_recursive_check = !nested;
598-
}
599-
CtfeValidationMode::Const { .. } => {
600-
// We can't recursively validate `extern static`, so we better reject them.
601-
if self.ecx.tcx.is_foreign_item(did) {
602-
throw_validation_failure!(self.path, ConstRefToExtern);
574+
match global_alloc {
575+
GlobalAlloc::Static(did) => {
576+
let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else {
577+
bug!()
578+
};
579+
assert!(!self.ecx.tcx.is_thread_local_static(did));
580+
assert!(self.ecx.tcx.is_static(did));
581+
match ctfe_mode {
582+
CtfeValidationMode::Static { .. }
583+
| CtfeValidationMode::Promoted { .. } => {
584+
// We skip recursively checking other statics. These statics must be sound by
585+
// themselves, and the only way to get broken statics here is by using
586+
// unsafe code.
587+
// The reasons we don't check other statics is twofold. For one, in all
588+
// sound cases, the static was already validated on its own, and second, we
589+
// trigger cycle errors if we try to compute the value of the other static
590+
// and that static refers back to us (potentially through a promoted).
591+
// This could miss some UB, but that's fine.
592+
// We still walk nested allocations, as they are fundamentally part of this validation run.
593+
// This means we will also recurse into nested statics of *other*
594+
// statics, even though we do not recurse into other statics directly.
595+
// That's somewhat inconsistent but harmless.
596+
skip_recursive_check = !nested;
597+
}
598+
CtfeValidationMode::Const { .. } => {
599+
// We can't recursively validate `extern static`, so we better reject them.
600+
if self.ecx.tcx.is_foreign_item(did) {
601+
throw_validation_failure!(self.path, ConstRefToExtern);
602+
}
603603
}
604604
}
605605
}
606+
// These are controlled by rustc and not available for CTFE
607+
GlobalAlloc::Type(_) | GlobalAlloc::PartialHash(_) => {
608+
skip_recursive_check = true
609+
}
610+
_ => (),
606611
}
607612

608613
// If this allocation has size zero, there is no actual mutability here.

compiler/rustc_hir/src/lang_items.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ language_item_table! {
274274
PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1);
275275
CVoid, sym::c_void, c_void, Target::Enum, GenericRequirement::None;
276276

277+
TypeId, sym::type_id, type_id, Target::Struct, GenericRequirement::None;
278+
277279
// A number of panic-related lang items. The `panic` item corresponds to divide-by-zero and
278280
// various panic cases with `match`. The `panic_bounds_check` item is for indexing arrays.
279281
//

compiler/rustc_hir_analysis/src/check/intrinsic.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,9 @@ pub(crate) fn check_intrinsic_type(
220220
sym::needs_drop => (1, 0, vec![], tcx.types.bool),
221221

222222
sym::type_name => (1, 0, vec![], Ty::new_static_str(tcx)),
223-
sym::type_id => (1, 0, vec![], tcx.types.u128),
223+
sym::type_id => {
224+
(1, 0, vec![], tcx.type_of(tcx.lang_items().type_id().unwrap()).instantiate_identity())
225+
}
224226
sym::offset => (2, 0, vec![param(0), param(1)], param(0)),
225227
sym::arith_offset => (
226228
1,

compiler/rustc_middle/src/mir/interpret/mod.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ enum AllocDiscriminant {
104104
VTable,
105105
Static,
106106
Type,
107+
PartialHash,
107108
}
108109

109110
pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<'tcx>>(
@@ -133,6 +134,11 @@ pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<'tcx>>(
133134
AllocDiscriminant::Type.encode(encoder);
134135
ty.encode(encoder);
135136
}
137+
GlobalAlloc::PartialHash(ty) => {
138+
trace!("encoding {alloc_id:?} with {ty:#?}");
139+
AllocDiscriminant::PartialHash.encode(encoder);
140+
ty.encode(encoder);
141+
}
136142
GlobalAlloc::Static(did) => {
137143
assert!(!tcx.is_thread_local_static(did));
138144
// References to statics doesn't need to know about their allocations,
@@ -240,6 +246,12 @@ impl<'s> AllocDecodingSession<'s> {
240246
trace!("decoded typid: {ty:?}");
241247
decoder.interner().reserve_and_set_type_id_alloc(ty)
242248
}
249+
AllocDiscriminant::PartialHash => {
250+
trace!("creating typeid alloc ID");
251+
let ty = Decodable::decode(decoder);
252+
trace!("decoded typid: {ty:?}");
253+
decoder.interner().reserve_and_set_type_id_partial_hash(ty)
254+
}
243255
AllocDiscriminant::Static => {
244256
trace!("creating extern static alloc ID");
245257
let did = <DefId as Decodable<D>>::decode(decoder);
@@ -270,9 +282,10 @@ pub enum GlobalAlloc<'tcx> {
270282
Static(DefId),
271283
/// The alloc ID points to memory.
272284
Memory(ConstAllocation<'tcx>),
273-
/// A TypeId pointer. For now cannot be turned into a runtime value.
274-
/// TODO: turn into actual TypeId?
285+
/// A pointer to be stored within a TypeId pointing to the full hash.
275286
Type(Ty<'tcx>),
287+
/// A partial type hash (the first `size_of<usize>()` bytes of the hash).
288+
PartialHash(Ty<'tcx>),
276289
}
277290

278291
impl<'tcx> GlobalAlloc<'tcx> {
@@ -312,6 +325,7 @@ impl<'tcx> GlobalAlloc<'tcx> {
312325
match self {
313326
GlobalAlloc::Function { .. } => cx.data_layout().instruction_address_space,
314327
GlobalAlloc::Type(_)
328+
| GlobalAlloc::PartialHash(_)
315329
| GlobalAlloc::Static(..)
316330
| GlobalAlloc::Memory(..)
317331
| GlobalAlloc::VTable(..) => AddressSpace::DATA,
@@ -350,7 +364,10 @@ impl<'tcx> GlobalAlloc<'tcx> {
350364
}
351365
}
352366
GlobalAlloc::Memory(alloc) => alloc.inner().mutability,
353-
GlobalAlloc::Type(_) | GlobalAlloc::Function { .. } | GlobalAlloc::VTable(..) => {
367+
GlobalAlloc::PartialHash(_)
368+
| GlobalAlloc::Type(_)
369+
| GlobalAlloc::Function { .. }
370+
| GlobalAlloc::VTable(..) => {
354371
// These are immutable.
355372
Mutability::Not
356373
}
@@ -398,8 +415,8 @@ impl<'tcx> GlobalAlloc<'tcx> {
398415
// No data to be accessed here. But vtables are pointer-aligned.
399416
(Size::ZERO, tcx.data_layout.pointer_align.abi)
400417
}
401-
// TODO make this represent normal type ids somehow
402-
GlobalAlloc::Type(_) => (Size::from_bytes(16), Align::from_bytes(8).unwrap()),
418+
GlobalAlloc::Type(_) => (Size::from_bytes(16), Align::ONE),
419+
GlobalAlloc::PartialHash(_) => (Size::ZERO, Align::ONE),
403420
}
404421
}
405422
}
@@ -505,11 +522,17 @@ impl<'tcx> TyCtxt<'tcx> {
505522
self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, dyn_ty), salt)
506523
}
507524

508-
/// Generates an [AllocId] for a [core::mem::type_info::TypeId]. Will get deduplicated.
525+
/// Generates an [AllocId] for a [core::mem::type_info::TypeIdData]. Will get deduplicated.
509526
pub fn reserve_and_set_type_id_alloc(self, ty: Ty<'tcx>) -> AllocId {
510527
self.reserve_and_set_dedup(GlobalAlloc::Type(ty), 0)
511528
}
512529

530+
/// Generates an [AllocId] that will get replaced by a partial hash of a type.
531+
/// See [core::mem::type_info::TypeId] for more information.
532+
pub fn reserve_and_set_type_id_partial_hash(self, ty: Ty<'tcx>) -> AllocId {
533+
self.reserve_and_set_dedup(GlobalAlloc::PartialHash(ty), 0)
534+
}
535+
513536
/// Interns the `Allocation` and return a new `AllocId`, even if there's already an identical
514537
/// `Allocation` with a different `AllocId`.
515538
/// Statics with identical content will still point to the same `Allocation`, i.e.,

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,6 +1622,7 @@ pub fn write_allocations<'tcx>(
16221622
write!(w, " (vtable: impl {dyn_ty} for {ty})")?
16231623
}
16241624
Some(GlobalAlloc::Type(ty)) => write!(w, " (typeid for {ty})")?,
1625+
Some(GlobalAlloc::PartialHash(ty)) => write!(w, " (partial hash of {ty})")?,
16251626
Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => {
16261627
write!(w, " (static: {}", tcx.def_path_str(did))?;
16271628
if body.phase <= MirPhase::Runtime(RuntimePhase::PostCleanup)

compiler/rustc_middle/src/ty/print/pretty.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,6 +1781,7 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
17811781
Some(GlobalAlloc::Function { .. }) => p!("<function>"),
17821782
Some(GlobalAlloc::VTable(..)) => p!("<vtable>"),
17831783
Some(GlobalAlloc::Type(_)) => p!("<typeid>"),
1784+
Some(GlobalAlloc::PartialHash(_)) => p!("<hash>"),
17841785
None => p!("<dangling pointer>"),
17851786
}
17861787
return Ok(());

compiler/rustc_monomorphize/src/collector.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,7 @@ fn collect_alloc<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIt
12201220
collect_alloc(tcx, alloc_id, output)
12211221
}
12221222
GlobalAlloc::Type(_) => {}
1223+
GlobalAlloc::PartialHash(_) => {}
12231224
}
12241225
}
12251226

compiler/rustc_passes/src/reachable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ impl<'tcx> ReachableContext<'tcx> {
325325
self.visit(args);
326326
}
327327
}
328-
GlobalAlloc::Type(ty) => self.visit(ty),
328+
GlobalAlloc::PartialHash(ty) | GlobalAlloc::Type(ty) => self.visit(ty),
329329
GlobalAlloc::Memory(alloc) => self.propagate_from_alloc(alloc),
330330
}
331331
}

compiler/rustc_smir/src/rustc_smir/convert/mir.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ impl<'tcx> Stable<'tcx> for mir::interpret::GlobalAlloc<'tcx> {
753753
}
754754
mir::interpret::GlobalAlloc::Memory(alloc) => GlobalAlloc::Memory(alloc.stable(tables)),
755755
mir::interpret::GlobalAlloc::Type(_) => todo!(),
756+
mir::interpret::GlobalAlloc::PartialHash(_) => todo!(),
756757
}
757758
}
758759
}

library/core/src/any.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ impl dyn Any + Send + Sync {
707707
/// ```
708708
#[derive(Clone, Copy, Eq, PartialOrd, Ord)]
709709
#[stable(feature = "rust1", since = "1.0.0")]
710+
#[lang = "type_id"]
710711
pub struct TypeId {
711712
/// Quick accept: if pointers are the same, the ids are the same
712713
data: &'static TypeIdData,
@@ -766,15 +767,7 @@ impl TypeId {
766767
#[stable(feature = "rust1", since = "1.0.0")]
767768
#[rustc_const_unstable(feature = "const_type_id", issue = "77125")]
768769
pub const fn of<T: ?Sized + 'static>() -> TypeId {
769-
let data = &const {
770-
let t: u128 = intrinsics::type_id::<T>();
771-
TypeIdData { full_hash: t.to_ne_bytes() }
772-
};
773-
774-
TypeId {
775-
data,
776-
partial_hash: crate::ptr::without_provenance(intrinsics::type_id::<T>() as usize),
777-
}
770+
intrinsics::type_id::<T>()
778771
}
779772

780773
fn as_u128(self) -> u128 {

0 commit comments

Comments
 (0)