rapx/verify/property_checker/typed.rs
1//! Checkers for `Typed` and `Size`.
2//!
3//! `Typed` matches an allocation's `element_ty` (or a field at the provenance
4//! offset) against the expected type; `Size` checks `sized`/`unsized`/exact
5//! size assertions.
6
7use crate::helpers::mir_scan::Checkpoint;
8use crate::verify::contract::{ContractExpr, Property, PropertyArg};
9use crate::verify::report::{CheckResult, UnknownReason};
10use crate::verify::vm::state::VmState;
11#[cfg(rapx_has_attr_ir)]
12use rustc_attr_ir::LangItem;
13#[cfg(all(not(rapx_has_attr_ir), not(rapx_ge_100)))]
14use rustc_hir::LangItem;
15#[cfg(all(not(rapx_has_attr_ir), rapx_ge_100))]
16use rustc_hir::attrs::lang_items::LangItem;
17use rustc_middle::ty::{Ty, TyKind};
18use z3::ast::Ast;
19
20use super::PropertyChecker;
21
22impl PropertyChecker {
23 pub(super) fn check_typed<'z3, 'tcx>(
24 &self,
25 vm_state: &VmState<'z3, 'tcx>,
26 checkpoint: &Checkpoint<'tcx>,
27 property: &Property<'tcx>,
28 ) -> CheckResult {
29 let Some(value) = self.target_value(vm_state, checkpoint, property) else {
30 return CheckResult::Unknown(UnknownReason::Unimplemented);
31 };
32 let expected = Self::ty_arg(property, 1);
33 if let Some(expected_ty) = expected {
34 let expected_ty = self.instantiate_callsite_ty(vm_state, checkpoint, expected_ty);
35
36 let value_elem_ty = match value.ty.kind() {
37 TyKind::RawPtr(inner, _) | TyKind::Ref(_, inner, _) => *inner,
38 _ => value.ty,
39 };
40
41 // `MaybeUninit<T>` (and slices/arrays of it) carries no validity
42 // invariant: any byte pattern is a valid `MaybeUninit<T>`. A byte
43 // buffer reinterpreted as `[MaybeUninit<T>]` (e.g. the slice handed
44 // to `Box::from_raw_in` by `RawVec::into_box`) is therefore always
45 // "typed" — alignment/size are discharged by the separate
46 // `Align`/`Allocated` facts.
47 if Self::ty_is_maybe_uninit(expected_ty) {
48 return CheckResult::ProvedByRule;
49 }
50
51 // Check provenance: does the allocation's element type match the expected type?
52 if let Some(alloc_id) = value.provenance_alloc_id() {
53 let alloc = vm_state.alloc(alloc_id);
54 if let Some(mut elem_ty) = alloc.element_ty.as_ty() {
55 // Resolve generic type param to concrete callsite type.
56 elem_ty = self.resolve_ty_params(vm_state, checkpoint, elem_ty);
57 if matches!(elem_ty.kind(), TyKind::Param(_)) {
58 let resolved = self.instantiate_callsite_ty(vm_state, checkpoint, elem_ty);
59 if resolved != elem_ty {
60 elem_ty = resolved;
61 }
62 }
63 if elem_ty == expected_ty {
64 return CheckResult::ProvedByRule;
65 }
66 // An allocation of `T` elements is also "typed" when
67 // accessed through a slice/array pointer `[T]`/`[T; N]`
68 // (a slice is just N contiguous `T` elements, e.g. a `u8`
69 // buffer reinterpreted as `[u8]` by `slice_from_raw_parts`).
70 let expected_elem = match expected_ty.kind() {
71 TyKind::Slice(e) | TyKind::Array(e, _) => *e,
72 _ => expected_ty,
73 };
74 if elem_ty == expected_elem {
75 return CheckResult::ProvedByRule;
76 }
77 // MaybeUninit<T> accessed via raw pointer from as_mut_ptr:
78 // treat as T for write ops where caller will initialize it.
79 if let TyKind::Adt(adt_def, substs) = elem_ty.kind() {
80 if crate::verify::api_classify::is_maybe_uninit_type(adt_def.did())
81 && matches!(value.ty.kind(), TyKind::RawPtr(..))
82 {
83 if let Some(inner) = substs.first().and_then(|s| s.as_type()) {
84 if inner == expected_ty {
85 if crate::verify::api_classify::is_mem_copy_or_write(
86 checkpoint.callee,
87 ) {
88 return CheckResult::ProvedByRule;
89 }
90 }
91 }
92 }
93 }
94 // Struct/enum field: check if expected_ty matches a field at the provenance offset.
95 if let TyKind::Adt(adt_def, substs) = elem_ty.kind() {
96 if !adt_def.is_enum() {
97 let off_u64 = value
98 .provenance
99 .as_ref()
100 .and_then(|p| p.offset.simplify().as_u64());
101 let variant = adt_def.non_enum_variant();
102 let mut accum: u64 = 0;
103 for (i, field_def) in variant.fields.iter().enumerate() {
104 let field_off = vm_state.field_offset_in_bytes(elem_ty, i);
105 if i > 0 && field_off == 0 {
106 accum = 0;
107 }
108 let field_ty: Ty<'tcx> = crate::helpers::mir_utils::field_ty(
109 vm_state.tcx,
110 field_def,
111 substs,
112 );
113 if field_ty == expected_ty {
114 if off_u64 == Some(accum) {
115 if value.facts.init {
116 return CheckResult::ProvedByRule;
117 }
118 return CheckResult::Failed;
119 }
120 } else if off_u64 == Some(accum) {
121 // Unwrap ManuallyDrop<T> → T for unions like MaybeUninit.
122 if let TyKind::Adt(wrap_adt, wrap_substs) = field_ty.kind() {
123 if !wrap_adt.is_enum() {
124 if (vm_state.tcx.is_lang_item(
125 wrap_adt.did(),
126 LangItem::ManuallyDrop,
127 ) || vm_state
128 .tcx
129 .is_lang_item(wrap_adt.did(), LangItem::UnsafeCell))
130 && wrap_substs.first().and_then(|s| s.as_type())
131 == Some(expected_ty)
132 {
133 if vm_state.content(alloc_id).facts.initialized {
134 return CheckResult::ProvedByRule;
135 }
136 return CheckResult::Failed;
137 }
138 }
139 }
140 }
141 accum += vm_state.size_of_ty(field_ty).max(1);
142 }
143 }
144 }
145 // ForEach (`buckets.iter()`): the allocation stores pointers
146 // (`*mut T`), but the invariant applies to the pointee (`T`).
147 // Unwrap *const/*mut to match.
148 if property.for_each().is_some() {
149 if let TyKind::RawPtr(inner, _) = elem_ty.kind() {
150 if *inner == expected_ty {
151 return CheckResult::ProvedByRule;
152 }
153 }
154 }
155 // A single pointer loaded from a container whose
156 // `Typed(container.iter(), T)` invariant established the
157 // element target type (`let cur = buckets[i]`). The fact comes
158 // from the invariant, so this does not bless dangling pointers
159 // in containers that carry no such invariant.
160 if let Some(target) = vm_state.alloc(alloc_id).facts.for_each.target_ty {
161 if target == expected_ty {
162 return CheckResult::ProvedByRule;
163 }
164 }
165 // Transmute to an all-bit-valid destination type
166 // (integers, floats, raw pointers): any byte pattern is
167 // a valid value, so a reinterpretation from a
168 // differently-typed allocation is sound (e.g. memchr
169 // reads `[u8]` as `usize`). This is only sound when the
170 // pointer is also correctly aligned to the destination
171 // type: a raw `*const u8 as *const u32` cast over
172 // align-1 storage is misaligned and must stay UNSOUND.
173 if Self::all_bit_patterns_valid(expected_ty) {
174 let expected_align = vm_state.align_of_ty(expected_ty).max(1);
175 if Self::value_aligned_to(vm_state, &value, expected_align) {
176 return CheckResult::ProvedByRule;
177 }
178 }
179 // Non-ADT element type that doesn't match → Failed.
180 if !matches!(elem_ty.kind(), TyKind::Adt(..)) {
181 return CheckResult::Failed;
182 }
183 // ADT type with no matching field and no init → Failed.
184 if !value.facts.init {
185 return CheckResult::Failed;
186 }
187 }
188 }
189
190 // No provenance: fall back to init and size checks.
191 if value.facts.init {
192 if vm_state.size_of_ty(value_elem_ty) > 0
193 && vm_state.size_of_ty(expected_ty) > 0
194 && vm_state.size_of_ty(value_elem_ty) == vm_state.size_of_ty(expected_ty)
195 {
196 return CheckResult::ProvedByRule;
197 }
198 }
199
200 // For ForEach (for_each) properties, the invariant applies to
201 // individual elements loaded from a container. The VM may not track
202 // provenance through memory loads from heap allocations. When sizes
203 // match, trust the type.
204 if property.for_each().is_some() {
205 if vm_state.size_of_ty(value_elem_ty) > 0
206 && vm_state.size_of_ty(expected_ty) > 0
207 && vm_state.size_of_ty(value_elem_ty) == vm_state.size_of_ty(expected_ty)
208 {
209 return CheckResult::ProvedByRule;
210 }
211 }
212
213 // When we have provenance but the element type doesn't match and
214 // sizes match, assume the type is correct. This handles pointers
215 // loaded from container elements where individual provenance is lost.
216 let vs = vm_state.size_of_ty(value_elem_ty);
217 let es = vm_state.size_of_ty(expected_ty);
218 if let Some(alloc_id) = value.provenance_alloc_id()
219 && vs == es
220 {
221 if !vm_state.alloc(alloc_id).element_ty.is_generic() {
222 return CheckResult::ProvedByRule;
223 }
224 }
225
226 if vs > 0 && es > 0 && vs != es {
227 return CheckResult::Failed;
228 }
229 }
230 CheckResult::Unknown(UnknownReason::Unimplemented)
231 }
232
233 pub(super) fn ty_is_maybe_uninit(ty: Ty<'_>) -> bool {
234 crate::verify::api_classify::is_maybe_uninit_ty(ty)
235 }
236
237 pub(super) fn check_size<'z3, 'tcx>(
238 &self,
239 vm_state: &VmState<'z3, 'tcx>,
240 checkpoint: &Checkpoint<'tcx>,
241 property: &Property<'tcx>,
242 ) -> CheckResult {
243 let ty = match property.args().iter().find_map(|a| match a {
244 PropertyArg::Ty(t) => Some(*t),
245 _ => None,
246 }) {
247 Some(t) => t,
248 None => return CheckResult::Unknown(UnknownReason::Unimplemented),
249 };
250 // Resolve a generic `T` to the call-site concrete type (e.g. `Box<i32>`
251 // for `drop_in_place::<Box<i32>>`), so `Size(T, 0)` is decided rather
252 // than left `Unknown` and dragged through `ValidPtr`'s `Size || Deref`.
253 let resolved_ty = self.instantiate_callsite_ty(vm_state, checkpoint, ty);
254
255 match property.args().last() {
256 Some(PropertyArg::Ident(id)) if id == "sized" => {
257 // For a generic type parameter (`T: Sized`) the concrete size is
258 // unknown, but the `non-ZST` constraint is a caller obligation
259 // (mirroring the `inject_layout_constraints` convention that a
260 // generic `SizeOf(T)` term is `>= 1`). Functions that panic on
261 // ZST — `offset_from`, `size_of_val`, ... — are sound for every
262 // `T`, so treating the constraint as satisfied is safe.
263 if self.is_generic_ty(ty) {
264 return CheckResult::ProvedByRule;
265 }
266 if vm_state.size_of_ty(ty) == 0 {
267 CheckResult::Failed
268 } else {
269 CheckResult::ProvedByRule
270 }
271 }
272 Some(PropertyArg::Ident(id)) if id == "unsized" => match ty.kind() {
273 TyKind::Slice(_) | TyKind::Str | TyKind::Dynamic(..) => CheckResult::ProvedByRule,
274 _ => CheckResult::Unknown(UnknownReason::Unimplemented),
275 },
276 Some(PropertyArg::Expr(ContractExpr::Const(c))) => {
277 let ty = resolved_ty;
278 if self.is_generic_ty(ty) {
279 return CheckResult::Unknown(UnknownReason::Unimplemented);
280 }
281 if vm_state.size_of_ty(ty) as u128 == *c {
282 CheckResult::ProvedByRule
283 } else {
284 CheckResult::Failed
285 }
286 }
287 _ => CheckResult::Unknown(UnknownReason::Unimplemented),
288 }
289 }
290
291 pub(super) fn check_no_padding<'z3, 'tcx>(
292 &self,
293 vm_state: &VmState<'z3, 'tcx>,
294 checkpoint: &Checkpoint<'tcx>,
295 property: &Property<'tcx>,
296 ) -> CheckResult {
297 let Some(ty) = property.args().iter().find_map(|a| match a {
298 PropertyArg::Ty(t) => Some(*t),
299 _ => None,
300 }) else {
301 return CheckResult::Unknown(UnknownReason::Unimplemented);
302 };
303 let ty = self.instantiate_callsite_ty(vm_state, checkpoint, ty);
304 match self.type_has_no_padding(vm_state, ty) {
305 Some(true) => CheckResult::ProvedByRule,
306 Some(false) => CheckResult::Failed,
307 None => CheckResult::Unknown(UnknownReason::Unimplemented),
308 }
309 }
310
311 /// Conservative "no padding" test: `Some(true)` when the type definitely has
312 /// no padding bytes, `Some(false)` when it definitely does, `None` when it
313 /// cannot be determined (generic / enum / union / opaque).
314 fn type_has_no_padding<'tcx>(
315 &self,
316 vm_state: &VmState<'_, 'tcx>,
317 ty: Ty<'tcx>,
318 ) -> Option<bool> {
319 let tcx = vm_state.tcx;
320 if self.is_generic_ty(ty) {
321 return None;
322 }
323 match ty.kind() {
324 TyKind::Bool
325 | TyKind::Char
326 | TyKind::Int(_)
327 | TyKind::Uint(_)
328 | TyKind::Float(_)
329 | TyKind::RawPtr(..)
330 | TyKind::Ref(..)
331 | TyKind::FnPtr(..)
332 | TyKind::Never => Some(true),
333 TyKind::Array(elem, _) => self.type_has_no_padding(vm_state, *elem),
334 TyKind::Tuple(elems) => {
335 let mut sum = 0u64;
336 for elem in *elems {
337 match self.type_has_no_padding(vm_state, elem) {
338 Some(true) => sum += vm_state.size_of_ty(elem),
339 Some(false) => return Some(false),
340 None => return None,
341 }
342 }
343 Some(vm_state.size_of_ty(ty) == sum)
344 }
345 TyKind::Adt(adt_def, substs) if !adt_def.is_enum() && !adt_def.is_union() => {
346 let variant = adt_def.non_enum_variant();
347 let mut sum = 0u64;
348 for field_def in variant.fields.iter() {
349 let field_ty: Ty<'tcx> =
350 crate::helpers::mir_utils::field_ty(tcx, field_def, substs);
351 match self.type_has_no_padding(vm_state, field_ty) {
352 Some(true) => sum += vm_state.size_of_ty(field_ty),
353 Some(false) => return Some(false),
354 None => return None,
355 }
356 }
357 Some(vm_state.size_of_ty(ty) == sum)
358 }
359 _ => None,
360 }
361 }
362}