Skip to content

Commit 11c833e

Browse files
committed
Initial work on runtime type validity checks
1 parent cdcc53b commit 11c833e

File tree

8 files changed

+233
-2
lines changed

8 files changed

+233
-2
lines changed

compiler/rustc_codegen_ssa/src/mir/intrinsic.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
106106
| sym::needs_drop
107107
| sym::type_id
108108
| sym::type_name
109-
| sym::variant_count => {
109+
| sym::variant_count
110+
| sym::validity_invariants_of => {
110111
let value = bx
111112
.tcx()
112113
.const_eval_instance(ty::ParamEnv::reveal_all(), instance, None)

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use super::{
2424

2525
mod caller_location;
2626
mod type_name;
27+
mod validity_invariants_of;
2728

2829
fn numeric_intrinsic<Tag>(name: Symbol, bits: u128, kind: Primitive) -> Scalar<Tag> {
2930
let size = match kind {
@@ -103,6 +104,11 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>(
103104
| ty::Tuple(_)
104105
| ty::Error(_) => ConstValue::from_machine_usize(0u64, &tcx),
105106
},
107+
sym::validity_invariants_of => {
108+
ensure_monomorphic_enough(tcx, tp_ty)?;
109+
let alloc = validity_invariants_of::alloc_validity_invariants_of(tcx, tp_ty);
110+
ConstValue::Slice { data: alloc, start: 0, end: alloc.inner().len() }
111+
}
106112
other => bug!("`{}` is not a zero arg intrinsic", other),
107113
})
108114
}
@@ -162,13 +168,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
162168
| sym::needs_drop
163169
| sym::type_id
164170
| sym::type_name
171+
| sym::validity_invariants_of
165172
| sym::variant_count => {
166173
let gid = GlobalId { instance, promoted: None };
167174
let ty = match intrinsic_name {
168175
sym::pref_align_of | sym::variant_count => self.tcx.types.usize,
169176
sym::needs_drop => self.tcx.types.bool,
170177
sym::type_id => self.tcx.types.u64,
171178
sym::type_name => self.tcx.mk_static_str(),
179+
sym::validity_invariants_of => self.tcx.mk_static_bytes(),
172180
_ => bug!(),
173181
};
174182
let val =
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use rustc_middle::mir::interpret::{Allocation, ConstAllocation};
2+
use rustc_middle::mir::Mutability;
3+
use rustc_middle::ty::layout::LayoutCx;
4+
use rustc_middle::ty::{ParamEnv, ParamEnvAnd};
5+
use rustc_middle::ty::{Ty, TyCtxt};
6+
use rustc_target::abi::{
7+
Abi, Align, Endian, FieldsShape, HasDataLayout, Scalar, Size, WrappingRange,
8+
};
9+
10+
#[derive(Debug, Clone, Copy)]
11+
struct Invariant {
12+
offset: Size,
13+
size: Size,
14+
start: u128,
15+
end: u128,
16+
}
17+
18+
fn add_invariants<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, invs: &mut Vec<Invariant>, offset: Size) {
19+
let x = tcx.layout_of(ParamEnvAnd { param_env: ParamEnv::reveal_all(), value: ty });
20+
21+
if let Ok(layout) = x {
22+
if let Abi::Scalar(Scalar::Initialized { value, valid_range }) = layout.layout.abi() {
23+
let size = value.size(&tcx);
24+
let WrappingRange { start, end } = valid_range;
25+
invs.push(Invariant { offset, size, start, end })
26+
}
27+
28+
let param_env = ParamEnv::reveal_all();
29+
let unwrap = LayoutCx { tcx, param_env };
30+
31+
match layout.layout.fields() {
32+
FieldsShape::Primitive => {}
33+
FieldsShape::Union(_) => {}
34+
FieldsShape::Array { stride, count } => {
35+
// TODO: should we just bail if we're making a Too Large type?
36+
// (Like [bool; 1_000_000])
37+
for idx in 0..*count {
38+
let off = offset + *stride * idx;
39+
let f = layout.field(&unwrap, idx as usize);
40+
add_invariants(tcx, f.ty, invs, off);
41+
}
42+
}
43+
FieldsShape::Arbitrary { offsets, .. } => {
44+
for (idx, &field_offset) in offsets.iter().enumerate() {
45+
let f = layout.field(&unwrap, idx);
46+
if f.ty == ty {
47+
// Some types contain themselves as fields, such as
48+
// &mut [T]
49+
// Easy solution is to just not recurse then.
50+
} else {
51+
add_invariants(tcx, f.ty, invs, offset + field_offset);
52+
}
53+
}
54+
}
55+
}
56+
}
57+
}
58+
59+
fn extend_encoded_int(to: &mut Vec<u8>, endian: Endian, ptr_size: PointerSize, value: Size) {
60+
match (endian, ptr_size) {
61+
(Endian::Little, PointerSize::Bits16) => to.extend((value.bytes() as u16).to_le_bytes()),
62+
(Endian::Little, PointerSize::Bits32) => to.extend((value.bytes() as u32).to_le_bytes()),
63+
(Endian::Little, PointerSize::Bits64) => to.extend((value.bytes()).to_le_bytes()),
64+
(Endian::Big, PointerSize::Bits16) => to.extend((value.bytes() as u16).to_be_bytes()),
65+
(Endian::Big, PointerSize::Bits32) => to.extend((value.bytes() as u32).to_be_bytes()),
66+
(Endian::Big, PointerSize::Bits64) => to.extend((value.bytes()).to_be_bytes()),
67+
}
68+
}
69+
70+
#[derive(Clone, Copy)]
71+
enum PointerSize {
72+
Bits16,
73+
Bits32,
74+
Bits64,
75+
}
76+
77+
/// Directly returns a `ConstAllocation` containing a list of validity invariants of the given type.
78+
pub(crate) fn alloc_validity_invariants_of<'tcx>(
79+
tcx: TyCtxt<'tcx>,
80+
ty: Ty<'tcx>,
81+
) -> ConstAllocation<'tcx> {
82+
let mut invs: Vec<Invariant> = Vec::new();
83+
84+
let layout = tcx.data_layout();
85+
86+
let ptr_size = match layout.pointer_size.bits() {
87+
16 => PointerSize::Bits16,
88+
32 => PointerSize::Bits32,
89+
64 => PointerSize::Bits64,
90+
_ => {
91+
// Not sure if this can happen, but just return an empty slice?
92+
let alloc =
93+
Allocation::from_bytes(Vec::new(), Align::from_bytes(8).unwrap(), Mutability::Not);
94+
return tcx.intern_const_alloc(alloc);
95+
}
96+
};
97+
98+
add_invariants(tcx, ty, &mut invs, Size::ZERO);
99+
100+
let encode_range = match layout.endian {
101+
Endian::Little => |r: u128| r.to_le_bytes(),
102+
Endian::Big => |r: u128| r.to_be_bytes(),
103+
};
104+
105+
let mut encoded = Vec::new();
106+
107+
// TODO: this needs to match the layout of `Invariant` in core/src/intrinsics.rs
108+
// how do we ensure that?
109+
for inv in invs {
110+
extend_encoded_int(&mut encoded, layout.endian, ptr_size, inv.offset);
111+
extend_encoded_int(&mut encoded, layout.endian, ptr_size, inv.size);
112+
encoded.extend(encode_range(inv.start));
113+
encoded.extend(encode_range(inv.end));
114+
}
115+
116+
// TODO: The alignment here should be calculated from the struct definition, I guess?
117+
let alloc = Allocation::from_bytes(encoded, Align::from_bytes(8).unwrap(), Mutability::Not);
118+
tcx.intern_const_alloc(alloc)
119+
}

compiler/rustc_middle/src/ty/context.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2339,6 +2339,11 @@ impl<'tcx> TyCtxt<'tcx> {
23392339
self.mk_imm_ref(self.lifetimes.re_static, self.types.str_)
23402340
}
23412341

2342+
#[inline]
2343+
pub fn mk_static_bytes(self) -> Ty<'tcx> {
2344+
self.mk_imm_ref(self.lifetimes.re_static, self.mk_slice(self.types.u8))
2345+
}
2346+
23422347
#[inline]
23432348
pub fn mk_adt(self, def: AdtDef<'tcx>, substs: SubstsRef<'tcx>) -> Ty<'tcx> {
23442349
// Take a copy of substs so that we own the vectors inside.

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,7 @@ symbols! {
15241524
va_list,
15251525
va_start,
15261526
val,
1527+
validity_invariants_of,
15271528
values,
15281529
var,
15291530
variant_count,

compiler/rustc_typeck/src/check/intrinsic.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ pub fn intrinsic_operation_unsafety(intrinsic: Symbol) -> hir::Unsafety {
102102
| sym::type_name
103103
| sym::forget
104104
| sym::black_box
105-
| sym::variant_count => hir::Unsafety::Normal,
105+
| sym::variant_count
106+
| sym::validity_invariants_of => hir::Unsafety::Normal,
106107
_ => hir::Unsafety::Unsafe,
107108
}
108109
}
@@ -191,6 +192,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
191192
sym::needs_drop => (1, Vec::new(), tcx.types.bool),
192193

193194
sym::type_name => (1, Vec::new(), tcx.mk_static_str()),
195+
sym::validity_invariants_of => (1, Vec::new(), tcx.mk_static_bytes()),
194196
sym::type_id => (1, Vec::new(), tcx.types.u64),
195197
sym::offset | sym::arith_offset => (
196198
1,

library/core/src/intrinsics.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2138,6 +2138,30 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
21382138
}
21392139
}
21402140

2141+
#[repr(C)]
2142+
#[derive(Debug, Clone, Copy)]
2143+
pub struct Invariant {
2144+
offset: u64,
2145+
size: u64,
2146+
start: u128,
2147+
end: u128,
2148+
}
2149+
2150+
#[cfg(not(bootstrap))]
2151+
/// Returns a list of all validity invariants of the type.
2152+
pub const fn validity_invariants_of<T>() -> &'static [Invariant] {
2153+
extern "rust-intrinsic" {
2154+
#[rustc_const_stable(feature = "validity_invariants_of", since = "1.40.0")]
2155+
pub fn validity_invariants_of<T>() -> &'static [u8];
2156+
}
2157+
2158+
let invariants: &'static [u8] = validity_invariants_of::<T>();
2159+
let sz = invariants.len() / core::mem::size_of::<Invariant>();
2160+
2161+
// SAFETY: we know this is valid.
2162+
unsafe { core::slice::from_raw_parts(invariants.as_ptr().cast(), sz) }
2163+
}
2164+
21412165
/// Copies `count * size_of::<T>()` bytes from `src` to `dst`. The source
21422166
/// and destination may overlap.
21432167
///
@@ -2394,3 +2418,59 @@ where
23942418
{
23952419
called_in_const.call_once(arg)
23962420
}
2421+
2422+
#[cfg(bootstrap)]
2423+
pub(crate) const unsafe fn assert_validity_of<T>(_: *const T) -> bool {
2424+
true
2425+
}
2426+
2427+
#[cfg(not(bootstrap))]
2428+
/// Asserts that the value at `value` is valid at type T.
2429+
/// Best effort, and is UB if the value is invalid.
2430+
pub(crate) unsafe fn assert_validity_of<T>(value: *const T) -> bool {
2431+
#[repr(packed)]
2432+
struct Unaligned<T>(T);
2433+
2434+
// SAFETY:
2435+
unsafe {
2436+
let invariants = validity_invariants_of::<T>();
2437+
for invariant in invariants {
2438+
let off = invariant.offset as usize;
2439+
let start = invariant.start;
2440+
let end = invariant.end;
2441+
2442+
let (value, max): (u128, u128) = match invariant.size {
2443+
1 => ((*(value.cast::<u8>().add(off))).into(), u8::MAX.into()),
2444+
2 => (
2445+
(*value.cast::<u8>().add(off).cast::<Unaligned<u16>>()).0.into(),
2446+
u16::MAX.into(),
2447+
),
2448+
4 => (
2449+
(*value.cast::<u8>().add(off).cast::<Unaligned<u32>>()).0.into(),
2450+
u32::MAX.into(),
2451+
),
2452+
8 => (
2453+
(*value.cast::<u8>().add(off).cast::<Unaligned<u64>>()).0.into(),
2454+
u64::MAX.into(),
2455+
),
2456+
16 => (
2457+
(*value.cast::<u8>().add(off).cast::<Unaligned<u128>>()).0.into(),
2458+
u128::MAX.into(),
2459+
),
2460+
s => panic!("unexpected size {s}"),
2461+
};
2462+
2463+
if start > end {
2464+
if !((start..=max).contains(&value) || (0..=end).contains(&value)) {
2465+
return false;
2466+
}
2467+
} else {
2468+
if !(start..=end).contains(&value) {
2469+
return false;
2470+
}
2471+
}
2472+
}
2473+
2474+
true
2475+
}
2476+
}

library/core/src/mem/maybe_uninit.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,11 @@ impl<T> MaybeUninit<T> {
631631
// This also means that `self` must be a `value` variant.
632632
unsafe {
633633
intrinsics::assert_inhabited::<T>();
634+
635+
intrinsics::assert_unsafe_precondition!(intrinsics::assert_validity_of::<T>(
636+
&self as *const MaybeUninit<T> as *const T
637+
));
638+
634639
ManuallyDrop::into_inner(self.value)
635640
}
636641
}
@@ -795,6 +800,11 @@ impl<T> MaybeUninit<T> {
795800
// This also means that `self` must be a `value` variant.
796801
unsafe {
797802
intrinsics::assert_inhabited::<T>();
803+
804+
intrinsics::assert_unsafe_precondition!(intrinsics::assert_validity_of::<T>(
805+
self as *const MaybeUninit<T> as *const T
806+
));
807+
798808
&*self.as_ptr()
799809
}
800810
}
@@ -912,6 +922,11 @@ impl<T> MaybeUninit<T> {
912922
// This also means that `self` must be a `value` variant.
913923
unsafe {
914924
intrinsics::assert_inhabited::<T>();
925+
926+
intrinsics::assert_unsafe_precondition!(intrinsics::assert_validity_of::<T>(
927+
self as *const MaybeUninit<T> as *const T
928+
));
929+
915930
&mut *self.as_mut_ptr()
916931
}
917932
}

0 commit comments

Comments
 (0)