Skip to main content

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}