Skip to main content

rapx/verify/property_checker/
util.rs

1//! Shared helpers for the property checkers.
2//!
3//! Argument/place resolution (`target_value`, `eval_contract_expr`), the
4//! `smt_check` "negate and prove" primitive, and size/byte-width utilities used
5//! by every checker family.
6
7use crate::helpers::mir_scan::Checkpoint;
8use crate::verify::contract::{
9    ContractExpr, ContractPlace, ContractProjection, NumericBinOp, PlaceBase, Property,
10    PropertyArg, RelOp,
11};
12use crate::verify::report::{CheckResult, UnknownReason};
13use crate::verify::vm::state::{VmState, VmValue};
14use rustc_middle::mir::{Local, Operand, Rvalue, StatementKind, TerminatorKind};
15#[cfg(rapx_const_ext)]
16use rustc_middle::ty::consts::ConstExt;
17use rustc_middle::ty::{GenericArg, GenericArgKind, Ty, TyKind};
18use z3::{
19    SatResult, Solver,
20    ast::{Ast, Bool, Int},
21};
22
23use super::PropertyChecker;
24
25/// Resolve a callee `Local(n)` to the corresponding checkpoint operand, using
26/// the callee's real argument count. Returns `None` when the checkpoint has no
27/// callee or `n` is not an argument local.
28pub(super) fn local_param_operand<'a, 'z3, 'tcx>(
29    vm_state: &VmState<'z3, 'tcx>,
30    ck: &'a Checkpoint<'tcx>,
31    n: usize,
32) -> Option<&'a Operand<'tcx>> {
33    let callee = ck.callee?;
34    let idx = crate::helpers::mir_utils::callee_param_index_for_local(vm_state.tcx, callee, n)?;
35    ck.args.get(idx)
36}
37
38impl PropertyChecker {
39    pub(super) fn ty_arg<'tcx>(property: &Property<'tcx>, idx: usize) -> Option<Ty<'tcx>> {
40        property.args().get(idx).and_then(|a| match a {
41            PropertyArg::Ty(ty) => Some(*ty),
42            _ => None,
43        })
44    }
45
46    pub(super) fn target_value<'z3, 'tcx>(
47        &self,
48        vm_state: &VmState<'z3, 'tcx>,
49        checkpoint: &Checkpoint<'tcx>,
50        property: &Property<'tcx>,
51    ) -> Option<VmValue<'z3, 'tcx>> {
52        self.target_value_raw(vm_state, checkpoint, property)
53    }
54
55    /// Resolve the target place to a `VmValue`, without pointer provenance
56    /// penetration (see [`Self::resolve_pointer_provenance`]).
57    fn target_value_raw<'z3, 'tcx>(
58        &self,
59        vm_state: &VmState<'z3, 'tcx>,
60        checkpoint: &Checkpoint<'tcx>,
61        property: &Property<'tcx>,
62    ) -> Option<VmValue<'z3, 'tcx>> {
63        let cp = match property.args().first()? {
64            PropertyArg::Expr(ContractExpr::Const(n)) => {
65                let idx = usize::try_from(*n).ok()?;
66                crate::verify::contract::ContractPlace {
67                    base: PlaceBase::Arg(idx),
68                    projections: vec![],
69                }
70            }
71            PropertyArg::Predicates(_) | PropertyArg::Ty(_) | PropertyArg::Ident(_) => return None,
72            PropertyArg::Expr(ContractExpr::Place(cp)) => cp.clone(),
73            PropertyArg::Expr(ContractExpr::IndexAccess { slice, .. }) => match slice.as_ref() {
74                ContractExpr::Place(cp) => cp.clone(),
75                _ => return None,
76            },
77            _ => return None,
78        };
79        if cp.projections.is_empty() {
80            return match cp.base {
81                PlaceBase::Return => vm_state.local_value(Local::from_usize(0)).cloned(),
82                PlaceBase::Arg(n) => {
83                    let operand = checkpoint.args.get(n)?;
84                    Some(vm_state.value_of_operand(operand))
85                }
86                PlaceBase::Local(n) => vm_state.local_value(Local::from_usize(n)).cloned(),
87            };
88        }
89        let base_local = match cp.base {
90            PlaceBase::Return => Local::from_usize(0),
91            PlaceBase::Arg(n) => {
92                let operand = checkpoint.args.get(n)?;
93                match operand {
94                    Operand::Copy(place) | Operand::Move(place) => place.local,
95                    _ => return None,
96                }
97            }
98            PlaceBase::Local(n) => Local::from_usize(n),
99        };
100        let mut field_path: Vec<usize> = Vec::new();
101        let mut last_field_ty: Option<Ty<'tcx>> = None;
102
103        for proj in &cp.projections {
104            match proj {
105                ContractProjection::Field { index, ty } => {
106                    field_path.push(*index);
107                    last_field_ty = *ty;
108                }
109                ContractProjection::Downcast { variant_index } => {
110                    let base_val = vm_state
111                        .field_value(base_local, &field_path)
112                        .cloned()
113                        .or_else(|| vm_state.local_value(base_local).cloned());
114                    let Some(base_val) = base_val else {
115                        return None;
116                    };
117
118                    let enum_ty = last_field_ty.unwrap_or(base_val.ty);
119                    let inner_ty = match enum_ty.kind() {
120                        TyKind::Adt(adt_def, substs) => {
121                            if adt_def.is_enum() {
122                                let variant = &adt_def.variants()
123                                    [rustc_abi::VariantIdx::from_usize(*variant_index)];
124                                if !variant.fields.is_empty() {
125                                    Some(crate::helpers::mir_utils::field_ty(
126                                        vm_state.tcx,
127                                        &variant.fields[rustc_abi::FieldIdx::from_usize(0)],
128                                        substs,
129                                    ))
130                                } else {
131                                    None
132                                }
133                            } else {
134                                None
135                            }
136                        }
137                        _ => None,
138                    };
139                    let inner_ty = inner_ty.unwrap_or(base_val.ty);
140
141                    return Some(VmValue {
142                        z3_term: base_val.z3_term.clone(),
143                        ty: inner_ty,
144                        provenance: base_val.provenance.clone(),
145                        facts: base_val.facts,
146                        source: base_val.source.field_offset_only(),
147                    });
148                }
149                ContractProjection::ForEach => {
150                    // iter() projections: try to resolve the base field and
151                    // return the base value (iterator elements handled elsewhere).
152                    if let Some(val) = vm_state.field_value(base_local, &field_path) {
153                        return Some(val.clone());
154                    }
155                    if let Some(base_val) = vm_state.local_value(base_local) {
156                        if base_val.is_pointer() {
157                            return Some(VmValue {
158                                z3_term: base_val.z3_term.clone(),
159                                ty: base_val.ty,
160                                provenance: base_val.provenance.clone(),
161                                facts: base_val.facts.clone(),
162                                source: base_val.source.field_offset_only(),
163                            });
164                        }
165                    }
166                    return None;
167                }
168            }
169        }
170
171        // All projections were Field (or no projections)
172        if let Some(val) = vm_state.field_value(base_local, &field_path) {
173            return Some(val.clone());
174        }
175        // Fallback: if the field value is not set (e.g. constructor return
176        // value _0 whose Aggregate was not executed), resolve from MIR.
177        if !field_path.is_empty() && base_local == Local::from_usize(0) {
178            for bb in vm_state.body().basic_blocks.iter() {
179                for stmt in &bb.statements {
180                    if let rustc_middle::mir::StatementKind::Assign(assign) = &stmt.kind {
181                        let (ref place, ref rval) = **assign;
182                        if let rustc_middle::mir::Rvalue::Aggregate(_, operands) = rval {
183                            if place.local == base_local {
184                                if let Some(operand) =
185                                    operands.get(rustc_abi::FieldIdx::from_usize(field_path[0]))
186                                {
187                                    let val = vm_state.value_of_operand(operand);
188                                    if field_path.len() == 1 {
189                                        return Some(val);
190                                    }
191                                }
192                            }
193                        }
194                    }
195                }
196            }
197        }
198        if let Some(base_val) = vm_state.local_value(base_local) {
199            if let Some(ref prov) = base_val.provenance {
200                return Some(VmValue {
201                    z3_term: base_val.z3_term.clone(),
202                    ty: base_val.ty,
203                    provenance: Some(prov.clone()),
204                    facts: base_val.facts.clone(),
205                    source: base_val.source.field_offset_only(),
206                });
207            }
208        }
209        None
210    }
211
212    /// Penetrate a reference/raw-pointer target down to the owned heap behind
213    /// it.  A target like `&mut ManuallyDrop<Box<T>>` or `*mut Box<T>` carries
214    /// the *stack* provenance of the referent; the properties that matter
215    /// (`Allocated`/`Owning`/`ValidPtr`) concern the heap object inside, so
216    /// resolve through the referent local's owned heap field.
217    pub(super) fn resolve_pointer_provenance<'z3, 'tcx>(
218        &self,
219        vm_state: &VmState<'z3, 'tcx>,
220        mut value: VmValue<'z3, 'tcx>,
221    ) -> VmValue<'z3, 'tcx> {
222        if matches!(
223            value.ty.kind(),
224            rustc_middle::ty::TyKind::Ref(..) | rustc_middle::ty::TyKind::RawPtr(..)
225        ) {
226            if let Some(owner) = vm_state.find_local_by_address(&value.z3_term) {
227                if let Some(heap_field) = vm_state.owner_ptr_field(owner) {
228                    if heap_field.is_pointer() {
229                        value.z3_term = heap_field.z3_term.clone();
230                        value.provenance = heap_field.provenance.clone();
231                        value.facts = heap_field.facts.clone();
232                    }
233                }
234            }
235        }
236        value
237    }
238
239    /// Implicit vacuous truth for projected targets.
240    ///
241    /// A property over `x.unwrap_some()` / `x.iter()` talks about the contents
242    /// of an `Option`/container; when that container resolves to no allocation
243    /// (e.g. `Option::None`, an empty or unmodeled container) there is no
244    /// element to check, so the property holds vacuously.  The explicit
245    /// counterpart is the `Null(p)` guard ([`Self::is_null`]), which the user
246    /// writes via `any(Null(p), …)`.
247    pub(super) fn is_vacuously_true_for_nullable<'z3, 'tcx>(
248        &self,
249        vm_state: &VmState<'z3, 'tcx>,
250        checkpoint: &Checkpoint<'tcx>,
251        property: &Property<'tcx>,
252    ) -> bool {
253        let cp = match property.args().first() {
254            Some(PropertyArg::Expr(crate::verify::contract::ContractExpr::Place(cp))) => cp,
255            _ => return false,
256        };
257        let has_nullable_proj = cp.projections.iter().any(|p| {
258            matches!(
259                p,
260                ContractProjection::Downcast { .. } | ContractProjection::ForEach
261            )
262        });
263        if !has_nullable_proj {
264            return false;
265        }
266        match self.target_value(vm_state, checkpoint, property) {
267            Some(val) => val.provenance.is_none(),
268            None => true,
269        }
270    }
271
272    /// Whether `place` is null, in the vacuity sense of the `Null(p)` guard:
273    /// true when the value provably equals 0, or carries no provenance and is
274    /// not known non-null (e.g. an `Option::None` or an unmodeled value).  The
275    /// implicit counterpart is [`Self::is_vacuously_true_for_nullable`], which
276    /// handles `unwrap_some()` / `iter()` projections without an explicit
277    /// guard.
278    pub(super) fn is_null<'z3, 'tcx>(
279        &self,
280        vm_state: &VmState<'z3, 'tcx>,
281        checkpoint: &Checkpoint<'tcx>,
282        place: &ContractPlace<'tcx>,
283    ) -> bool {
284        use crate::verify::def_use::{PlaceBaseKey, PlaceKey};
285        let key = PlaceKey::from_contract_place(place);
286        let local = match key.base {
287            PlaceBaseKey::Local(n) => Local::from_usize(n),
288            PlaceBaseKey::Arg(n) => checkpoint
289                .args
290                .get(n)
291                .and_then(|op| match op {
292                    Operand::Copy(place) | Operand::Move(place) => Some(place.local),
293                    _ => None,
294                })
295                .unwrap_or(Local::from_usize(n + 1)),
296            PlaceBaseKey::Return => Local::from_usize(0),
297        };
298        let val = if key.fields.is_empty() {
299            vm_state.local_value(local).cloned()
300        } else {
301            vm_state.field_value(local, &key.fields).cloned()
302        };
303        match val {
304            Some(v) => {
305                // A pointer without a `non_null` fact is possibly null: either it
306                // carries no provenance, or its provenance is an external
307                // placeholder (a raw-pointer field/param), which does not imply
308                // non-nullness.
309                if !v.facts.non_null {
310                    let possibly_null = match &v.provenance {
311                        None => true,
312                        Some(prov) => vm_state.alloc(prov.alloc_id).is_external(),
313                    };
314                    if possibly_null {
315                        return true;
316                    }
317                }
318                if let Some(term_zero) = v.z3_term.simplify().as_u64() {
319                    if term_zero == 0 {
320                        return true;
321                    }
322                }
323                false
324            }
325            None => true,
326        }
327    }
328
329    pub(super) fn smt_check<'z3>(
330        &self,
331        solver: &Solver<'z3>,
332        condition: &Bool<'z3>,
333    ) -> CheckResult {
334        solver.push();
335        solver.assert(condition);
336        let r = match solver.check() {
337            SatResult::Unsat => CheckResult::ProvedBySmt,
338            SatResult::Sat => CheckResult::Failed,
339            SatResult::Unknown => CheckResult::Unknown(UnknownReason::SmtTimeout),
340        };
341        solver.pop(1);
342        r
343    }
344
345    /// Prove `goal_negated` is unsatisfiable under a case split on a *generic*
346    /// element size `S`: the ZST branch (`S = 0`) and the non-ZST branch
347    /// (`S ≥ 1`, where the `S` factor cancels).  Both branches must be UNSAT.
348    /// `on_sat` is the result when either branch is satisfiable.
349    pub(super) fn smt_check_size_split<'z3, 'tcx>(
350        vm_state: &VmState<'z3, 'tcx>,
351        elem_size: &Int<'z3>,
352        goal_negated: &Bool<'z3>,
353        on_sat: CheckResult,
354    ) -> CheckResult {
355        let solver = Solver::new(vm_state.z3_ctx);
356        let zero = Int::from_u64(vm_state.z3_ctx, 0);
357        let one = Int::from_u64(vm_state.z3_ctx, 1);
358
359        solver.push();
360        vm_state.assert_all(&solver);
361        solver.assert(&elem_size._eq(&zero));
362        solver.assert(goal_negated);
363        let r_zst = solver.check();
364        solver.pop(1);
365
366        solver.push();
367        vm_state.assert_all(&solver);
368        solver.assert(&elem_size.ge(&one));
369        solver.assert(goal_negated);
370        let r_non_zst = solver.check();
371        solver.pop(1);
372
373        match (r_zst, r_non_zst) {
374            (SatResult::Unsat, SatResult::Unsat) => CheckResult::ProvedBySmt,
375            (SatResult::Sat, _) | (_, SatResult::Sat) => on_sat,
376            _ => CheckResult::Unknown(UnknownReason::SmtTimeout),
377        }
378    }
379
380    pub(super) fn resolve_arg_term<'z3, 'tcx>(
381        &self,
382        vm_state: &VmState<'z3, 'tcx>,
383        checkpoint: &Checkpoint<'tcx>,
384        arg: &PropertyArg<'tcx>,
385    ) -> Option<Int<'z3>> {
386        match arg {
387            PropertyArg::Expr(ContractExpr::Const(n)) if *n <= u64::MAX as u128 => {
388                Some(Int::from_u64(vm_state.z3_ctx, *n as u64))
389            }
390            PropertyArg::Expr(ContractExpr::Place(cp)) => {
391                match cp.base {
392                    PlaceBase::Arg(n) => {
393                        let op = checkpoint.args.get(n)?;
394                        Some(vm_state.value_of_operand(op).z3_term)
395                    }
396                    PlaceBase::Local(n) => {
397                        // The Local(N) refers to the callee's parameter. Map to
398                        // the callsite's corresponding Arg via the callee's real
399                        // signature (falling back to the VM local when there is
400                        // no callee — e.g. a synthetic checkpoint — or `n` is a
401                        // temporary rather than a parameter).
402                        if let Some(op) = local_param_operand(vm_state, checkpoint, n) {
403                            Some(vm_state.value_of_operand(op).z3_term)
404                        } else {
405                            vm_state
406                                .local_value(Local::from_usize(n))
407                                .map(|v| v.z3_term.clone())
408                        }
409                    }
410                    PlaceBase::Return => None,
411                }
412            }
413            PropertyArg::Expr(expr) => self.eval_contract_expr(vm_state, Some(checkpoint), expr),
414            _ => None,
415        }
416    }
417
418    /// Whether the element-count argument (defaulting to `args[2]`, the
419    /// `[Target, Ty, Expr]` layout) evaluates to the constant `0`, making any
420    /// InBound/Allocated byte-range check trivially satisfied.  `count_arg`
421    /// overrides the index for two-argument forms like `Init(self, n)`.
422    pub(super) fn count_is_zero<'z3, 'tcx>(
423        &self,
424        vm_state: &VmState<'z3, 'tcx>,
425        checkpoint: &Checkpoint<'tcx>,
426        property: &Property<'tcx>,
427        count_arg: usize,
428    ) -> bool {
429        property
430            .args()
431            .get(count_arg)
432            .and_then(|a| self.resolve_arg_term(vm_state, checkpoint, a))
433            .and_then(|ct| ct.as_u64())
434            == Some(0)
435    }
436
437    pub(super) fn access_bytes<'z3, 'tcx>(
438        &self,
439        vm_state: &VmState<'z3, 'tcx>,
440        property: &Property<'tcx>,
441        ty_arg: usize,
442        count_arg: usize,
443        checkpoint: &Checkpoint<'tcx>,
444        value: &VmValue<'z3, 'tcx>,
445    ) -> Int<'z3> {
446        // Element size as a symbolic term.  A concrete contract `T` uses its
447        // constant byte size; a generic `T` falls back to the target pointer's
448        // own pointee type (which may resolve to the call-site concrete type,
449        // e.g. `from_raw_parts::<u32>`), and only then to the symbolic `sizeof_T`.
450        let elem_ty = property
451            .args()
452            .get(ty_arg)
453            .and_then(|a| {
454                if let PropertyArg::Ty(ty) = a {
455                    Some(*ty)
456                } else {
457                    None
458                }
459            })
460            .filter(|ty| vm_state.size_of_ty(*ty) > 0)
461            .or_else(|| {
462                // Two-argument form (`Init(self, n)`, no `T`): derive the
463                // element type from the target's pointee, peeling `[T]` /
464                // `[T; N]` down to `T` so `n * sizeof(elem)` is computed.
465                crate::helpers::mir_utils::pointee_ty(value.ty).map(|ty| match ty.kind() {
466                    rustc_middle::ty::TyKind::Slice(e) | rustc_middle::ty::TyKind::Array(e, _) => {
467                        *e
468                    }
469                    _ => ty,
470                })
471            });
472        let elem_size_term = elem_ty
473            .map(|ty| vm_state.size_sym_read(ty))
474            .unwrap_or_else(|| Int::from_u64(vm_state.z3_ctx, 1));
475
476        let count_term = property
477            .args()
478            .get(count_arg)
479            .and_then(|a| self.resolve_arg_term(vm_state, checkpoint, a))
480            .unwrap_or_else(|| Int::from_u64(vm_state.z3_ctx, 1));
481        // Simplify the multiplication for concrete count and elem_size
482        if let (Some(elem), Some(count)) = (
483            elem_size_term.simplify().as_u64(),
484            count_term.simplify().as_u64(),
485        ) {
486            return Int::from_u64(vm_state.z3_ctx, elem.max(1) * count.max(1));
487        }
488        Int::mul(vm_state.z3_ctx, &[&elem_size_term, &count_term])
489    }
490
491    pub(super) fn zst_guard<'z3, 'tcx>(
492        &self,
493        vm_state: &VmState<'z3, 'tcx>,
494        checkpoint: &Checkpoint<'tcx>,
495        property: &Property<'tcx>,
496    ) -> bool {
497        let required_ty = Self::ty_arg(property, 1);
498        self.is_zst_type(vm_state, checkpoint, required_ty)
499    }
500
501    pub(super) fn is_zst_type<'z3, 'tcx>(
502        &self,
503        vm_state: &VmState<'z3, 'tcx>,
504        checkpoint: &Checkpoint<'tcx>,
505        ty: Option<Ty<'tcx>>,
506    ) -> bool {
507        let ty = match ty {
508            Some(t) => t,
509            None => return false,
510        };
511        if self.is_concrete_zst(vm_state, ty) {
512            return true;
513        }
514        let resolved = self.instantiate_callsite_ty(vm_state, checkpoint, ty);
515        if resolved != ty {
516            return self.is_concrete_zst(vm_state, resolved);
517        }
518        false
519    }
520
521    pub(super) fn is_concrete_zst<'z3, 'tcx>(
522        &self,
523        vm_state: &VmState<'z3, 'tcx>,
524        ty: Ty<'tcx>,
525    ) -> bool {
526        !self.is_generic_ty(ty) && vm_state.size_of_ty(ty) == 0
527    }
528
529    pub(super) fn is_generic_ty<'tcx>(&self, ty: Ty<'tcx>) -> bool {
530        matches!(
531            ty.kind(),
532            TyKind::Param(_) | TyKind::Alias(..) | TyKind::Error(_)
533        )
534    }
535
536    pub(super) fn instantiate_callsite_ty<'z3, 'tcx>(
537        &self,
538        vm_state: &VmState<'z3, 'tcx>,
539        checkpoint: &Checkpoint<'tcx>,
540        ty: Ty<'tcx>,
541    ) -> Ty<'tcx> {
542        let TyKind::Param(param) = ty.kind() else {
543            return ty;
544        };
545        // A synthetic checkpoint (raw-ptr-deref, static-mut, type-invariant) has
546        // no callee: its property type args are already the caller's own types.
547        // Resolving them through `checkpoint.block`'s terminator would pick up an
548        // *unrelated* neighbouring call (e.g. the `same_bucket` closure call for a
549        // `&mut *ptr_write` deref), mapping `T` to the closure type `F`.
550        if checkpoint.callee.is_none() {
551            return ty;
552        };
553
554        let body = vm_state.body();
555        let terminator = body.basic_blocks[checkpoint.block].terminator();
556        let TerminatorKind::Call { func, .. } = &terminator.kind else {
557            return ty;
558        };
559        let Operand::Constant(func_constant) = func else {
560            return ty;
561        };
562        let TyKind::FnDef(_, args) = func_constant.const_.ty().kind() else {
563            return ty;
564        };
565        let Some(arg) = crate::compat::args_get(args, param.index as usize) else {
566            return ty;
567        };
568        match arg.kind() {
569            GenericArgKind::Type(actual_ty) => actual_ty,
570            _ => ty,
571        }
572    }
573
574    pub(super) fn instantiate_callsite_const<'z3, 'tcx>(
575        &self,
576        vm_state: &VmState<'z3, 'tcx>,
577        checkpoint: &Checkpoint<'tcx>,
578        index: u32,
579    ) -> Option<u128> {
580        let body = vm_state.body();
581        let terminator = body.basic_blocks[checkpoint.block].terminator();
582        let TerminatorKind::Call { func, .. } = &terminator.kind else {
583            return None;
584        };
585        let Operand::Constant(func_constant) = func else {
586            return None;
587        };
588        let TyKind::FnDef(_, args) = func_constant.const_.ty().kind() else {
589            return None;
590        };
591        let arg = crate::compat::args_get(args, index as usize)?;
592        match arg.kind() {
593            GenericArgKind::Const(actual_const) => actual_const
594                .try_to_target_usize(vm_state.tcx)
595                .map(|value| value as u128)
596                .or_else(|| {
597                    crate::helpers::mir_utils::const_int_from_debug(&format!("{actual_const:?}"))
598                        .map(|v| v as u128)
599                }),
600            _ => None,
601        }
602    }
603
604    pub(super) fn resolve_ty_params<'z3, 'tcx>(
605        &self,
606        vm_state: &VmState<'z3, 'tcx>,
607        checkpoint: &Checkpoint<'tcx>,
608        ty: Ty<'tcx>,
609    ) -> Ty<'tcx> {
610        match ty.kind() {
611            TyKind::Param(_) => self.instantiate_callsite_ty(vm_state, checkpoint, ty),
612            TyKind::Adt(adt_def, substs) => {
613                let mut changed = false;
614                let resolved_substs: Vec<_> = substs
615                    .iter()
616                    .map(|arg| match arg.kind() {
617                        GenericArgKind::Type(t) => {
618                            let resolved = self.resolve_ty_params(vm_state, checkpoint, t);
619                            if resolved != t {
620                                changed = true;
621                                GenericArg::from(resolved)
622                            } else {
623                                arg.clone()
624                            }
625                        }
626                        _ => arg.clone(),
627                    })
628                    .collect();
629                if changed {
630                    Ty::new_adt(
631                        vm_state.tcx,
632                        *adt_def,
633                        vm_state.tcx.mk_args(&resolved_substs),
634                    )
635                } else {
636                    ty
637                }
638            }
639            _ => ty,
640        }
641    }
642
643    pub(super) fn eval_contract_expr<'z3, 'tcx>(
644        &self,
645        vm_state: &VmState<'z3, 'tcx>,
646        checkpoint: Option<&Checkpoint<'tcx>>,
647        expr: &ContractExpr<'tcx>,
648    ) -> Option<Int<'z3>> {
649        match expr {
650            ContractExpr::Const(n) => Some(Int::from_u64(vm_state.z3_ctx, *n as u64)),
651            ContractExpr::SizeOf(ty) => {
652                let mut size = vm_state.size_of_ty(*ty);
653                if size == 0 && matches!(ty.kind(), rustc_middle::ty::TyKind::Param(_)) {
654                    size = crate::helpers::mir_utils::size_of_generic_param(
655                        vm_state.tcx,
656                        vm_state.current_frame.current_def_id,
657                        *ty,
658                    );
659                    if size == 0 {
660                        if let Some(ck) = checkpoint {
661                            if let Some(_callee) = ck.callee {
662                                if !self.is_caller_type_param(vm_state, *ty) {
663                                    let resolved = self.instantiate_callsite_ty(vm_state, ck, *ty);
664                                    if resolved != *ty {
665                                        size = vm_state.size_of_ty(resolved);
666                                    }
667                                }
668                            }
669                        }
670                    }
671                }
672                if size > 0 {
673                    Some(Int::from_u64(vm_state.z3_ctx, size))
674                } else {
675                    Some(Int::from_u64(vm_state.z3_ctx, 0))
676                }
677            }
678            ContractExpr::AlignOf(ty) => {
679                let align = vm_state.align_of_ty(*ty);
680                if align > 0 {
681                    Some(Int::from_u64(vm_state.z3_ctx, align.max(1)))
682                } else {
683                    Some(Int::from_u64(vm_state.z3_ctx, 0))
684                }
685            }
686            ContractExpr::Place(cp) => self.eval_contract_place(vm_state, checkpoint, cp),
687            ContractExpr::Binary { op, lhs, rhs } => {
688                let l = self.eval_contract_expr(vm_state, checkpoint, lhs)?;
689                let r = self.eval_contract_expr(vm_state, checkpoint, rhs)?;
690                match op {
691                    NumericBinOp::Add => Some(Int::add(vm_state.z3_ctx, &[&l, &r])),
692                    NumericBinOp::Sub => Some(Int::sub(vm_state.z3_ctx, &[&l, &r])),
693                    NumericBinOp::Mul => Some(Int::mul(vm_state.z3_ctx, &[&l, &r])),
694                    NumericBinOp::Div | NumericBinOp::Rem => {
695                        // Z3 division by zero yields unconstrained results,
696                        // leading to unsound proofs downstream. When the
697                        // divisor is zero (e.g. size_of::<T>() for generic
698                        // or ZST params), return zero so that subsequent
699                        // access_bytes computes 0 * elem_size == 0.
700                        if r.as_u64() == Some(0) {
701                            Some(Int::from_u64(vm_state.z3_ctx, 0))
702                        } else if matches!(op, NumericBinOp::Div) {
703                            Some(l.div(&r))
704                        } else {
705                            let q = l.div(&r);
706                            Some(Int::sub(
707                                vm_state.z3_ctx,
708                                &[&l, &Int::mul(vm_state.z3_ctx, &[&q, &r])],
709                            ))
710                        }
711                    }
712                    NumericBinOp::Min => Some(l.le(&r).ite(&l, &r)),
713                    NumericBinOp::Max => Some(l.ge(&r).ite(&l, &r)),
714                    _ => None,
715                }
716            }
717            ContractExpr::Unary { op, expr: inner } => {
718                let v = self.eval_contract_expr(vm_state, checkpoint, inner)?;
719                match op {
720                    crate::verify::contract::NumericUnaryOp::Not => {
721                        Some(v._eq(&Int::from_u64(vm_state.z3_ctx, 0)).ite(
722                            &Int::from_u64(vm_state.z3_ctx, 1),
723                            &Int::from_u64(vm_state.z3_ctx, 0),
724                        ))
725                    }
726                    crate::verify::contract::NumericUnaryOp::Neg => {
727                        let zero = Int::from_u64(vm_state.z3_ctx, 0);
728                        Some(Int::sub(vm_state.z3_ctx, &[&zero, &v]))
729                    }
730                }
731            }
732            ContractExpr::Len(inner) => {
733                if let Some(ck) = checkpoint {
734                    if let Some(term) = self.try_iter_len_from_fields(vm_state, ck, inner) {
735                        return Some(term);
736                    }
737                }
738                let val = self.eval_contract_expr_to_value(vm_state, checkpoint, inner)?;
739                // A struct (e.g. `NodeRef`) whose `len()` reads `(*x.field).len`
740                // through a `NonNull` field.
741                if let crate::verify::contract::ContractExpr::Place(cp) = &**inner {
742                    if let Some(field_path) = cp.plain_field_path() {
743                        let base_local =
744                            match cp.base {
745                                PlaceBase::Return => Some(Local::from_usize(0)),
746                                PlaceBase::Local(n) => Some(Local::from_usize(n)),
747                                PlaceBase::Arg(n) => checkpoint
748                                    .and_then(|ck| ck.args.get(n))
749                                    .and_then(|op| match op {
750                                        Operand::Copy(p) | Operand::Move(p) => Some(p.local),
751                                        _ => None,
752                                    }),
753                            };
754                        if let Some(local) = base_local {
755                            if let Some(len) =
756                                vm_state.try_struct_nn_len_field(local, &field_path, val.ty)
757                            {
758                                return Some(len);
759                            }
760                        }
761                    }
762                }
763                vm_state.len_from_value(&val)
764            }
765            ContractExpr::ConstParam { index, name: _ } => self
766                .instantiate_callsite_const(vm_state, checkpoint?, *index)
767                .and_then(|v| u64::try_from(v).ok())
768                .map(|v| Int::from_u64(vm_state.z3_ctx, v)),
769            ContractExpr::If {
770                cond,
771                then_expr,
772                else_expr,
773            } => {
774                let l = self.eval_contract_expr(vm_state, checkpoint, &cond.lhs)?;
775                let r = self.eval_contract_expr(vm_state, checkpoint, &cond.rhs)?;
776                let cond_bool = match cond.op {
777                    RelOp::Eq => l._eq(&r),
778                    RelOp::Ne => l._eq(&r).not(),
779                    RelOp::Le => l.le(&r),
780                    RelOp::Lt => l.lt(&r),
781                    RelOp::Ge => l.ge(&r),
782                    RelOp::Gt => l.gt(&r),
783                };
784                // When the condition is concretely true/false, short-circuit to
785                // the taken branch so the result is a concrete term (otherwise an
786                // `ite(true, a, b)` stays symbolic and downstream `as_u64()`
787                // checks fail, e.g. the `count == 0` fast-path in check_in_bound).
788                match cond_bool.simplify().as_bool() {
789                    Some(true) => self.eval_contract_expr(vm_state, checkpoint, then_expr),
790                    Some(false) => self.eval_contract_expr(vm_state, checkpoint, else_expr),
791                    _ => {
792                        let t = self.eval_contract_expr(vm_state, checkpoint, then_expr)?;
793                        let e = self.eval_contract_expr(vm_state, checkpoint, else_expr)?;
794                        Some(cond_bool.ite(&t, &e))
795                    }
796                }
797            }
798            _ => None,
799        }
800    }
801
802    pub(super) fn eval_contract_expr_to_value<'z3, 'tcx>(
803        &self,
804        vm_state: &VmState<'z3, 'tcx>,
805        checkpoint: Option<&Checkpoint<'tcx>>,
806        expr: &ContractExpr<'tcx>,
807    ) -> Option<VmValue<'z3, 'tcx>> {
808        match expr {
809            ContractExpr::Place(cp) => {
810                if cp.projections.is_empty() {
811                    return match cp.base {
812                        PlaceBase::Return => vm_state.local_value(Local::from_usize(0)).cloned(),
813                        PlaceBase::Arg(n) => checkpoint?
814                            .args
815                            .get(n)
816                            .map(|op| vm_state.value_of_operand(op)),
817                        PlaceBase::Local(n) => {
818                            let ck = checkpoint?;
819                            if let Some(op) = local_param_operand(vm_state, ck, n) {
820                                return Some(vm_state.value_of_operand(op));
821                            }
822                            vm_state.local_value(Local::from_usize(n)).cloned()
823                        }
824                    };
825                }
826                // Field projections: resolve the base local, then read the
827                // materialized field value (mirrors `target_value`). This is
828                // what lets `InBound(v, T, v.len())` resolve `Len(v)` for a
829                // struct field `v` (e.g. a `*mut [T]` slice field on the
830                // constructed return value).
831                let base_local = match cp.base {
832                    PlaceBase::Return => Local::from_usize(0),
833                    PlaceBase::Arg(n) => {
834                        let op = checkpoint?.args.get(n)?;
835                        match op {
836                            Operand::Copy(p) | Operand::Move(p) => p.local,
837                            _ => return None,
838                        }
839                    }
840                    PlaceBase::Local(n) => Local::from_usize(n),
841                };
842                let field_path = cp.plain_field_path()?;
843                vm_state.field_value(base_local, &field_path).cloned()
844            }
845            _ => None,
846        }
847    }
848
849    pub(super) fn eval_contract_place<'z3, 'tcx>(
850        &self,
851        vm_state: &VmState<'z3, 'tcx>,
852        checkpoint: Option<&Checkpoint<'tcx>>,
853        cp: &crate::verify::contract::ContractPlace<'tcx>,
854    ) -> Option<Int<'z3>> {
855        // Collect numeric field projections.  Any non-field projection (e.g. a
856        // `Downcast` or `ForEach`) cannot be resolved to a scalar, so the
857        // place does not evaluate.
858        let field_path = cp.plain_field_path()?;
859
860        let base_local: Option<Local> = match cp.base {
861            PlaceBase::Return => Some(Local::from_usize(0)),
862            PlaceBase::Arg(n) => {
863                if field_path.is_empty() {
864                    return checkpoint.and_then(|ck| {
865                        let op = ck.args.get(n)?;
866                        self.eval_contract_operand(vm_state, op)
867                    });
868                }
869                // A field projection of an argument: resolve the argument
870                // operand to its underlying local so the field can be read.
871                checkpoint
872                    .and_then(|ck| ck.args.get(n))
873                    .and_then(|op| match op {
874                        Operand::Copy(p) | Operand::Move(p) => Some(p.local),
875                        _ => None,
876                    })
877            }
878            PlaceBase::Local(n) => {
879                if field_path.is_empty() {
880                    if let Some(ck) = checkpoint {
881                        if let Some(op) = local_param_operand(vm_state, ck, n) {
882                            if let Some(v) = self.eval_contract_operand(vm_state, op) {
883                                return Some(v);
884                            }
885                        }
886                    }
887                }
888                Some(Local::from_usize(n))
889            }
890        };
891
892        let local = base_local?;
893        if field_path.is_empty() {
894            vm_state.local_value(local).map(|v| v.z3_term.clone())
895        } else {
896            vm_state
897                .field_value(local, &field_path)
898                .map(|v| v.z3_term.clone())
899        }
900    }
901
902    pub(super) fn eval_contract_operand<'z3, 'tcx>(
903        &self,
904        vm_state: &VmState<'z3, 'tcx>,
905        op: &Operand<'tcx>,
906    ) -> Option<Int<'z3>> {
907        match op {
908            Operand::Constant(c) => {
909                let const_text = format!("{:?}", c.const_);
910                let typing_env = rustc_middle::ty::TypingEnv::fully_monomorphized();
911                if let Ok(val) = c
912                    .const_
913                    .eval(vm_state.tcx, typing_env, rustc_span::DUMMY_SP)
914                {
915                    if let Some(scalar) = val.try_to_scalar_int() {
916                        let v = scalar.to_bits(scalar.size()) as u64;
917                        if v == 0
918                            && (const_text.contains("AlignOf")
919                                || const_text.contains("SizeOf")
920                                || const_text.contains("min_align_of")
921                                || const_text.contains("min_size_of"))
922                        {
923                            // Generic AlignOf/SizeOf may evaluate to 0 but
924                            // are always >= 1 for non-ZST types. Fall through
925                            // to the debug text path below.
926                        } else {
927                            return Some(Int::from_u64(vm_state.z3_ctx, v));
928                        }
929                    }
930                }
931                crate::helpers::mir_utils::const_int_from_debug(&const_text)
932                    .map(|v| Int::from_u64(vm_state.z3_ctx, v))
933            }
934            Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => {
935                vm_state.local_value(p.local).map(|v| v.z3_term.clone())
936            }
937            _ => None,
938        }
939    }
940
941    pub(super) fn trace_value<'z3, 'tcx>(
942        &self,
943        vm_state: &VmState<'z3, 'tcx>,
944        op: &Operand<'tcx>,
945    ) -> VmValue<'z3, 'tcx> {
946        let place = match op {
947            Operand::Copy(p) | Operand::Move(p) => p,
948            _ => return vm_state.value_of_operand(op),
949        };
950        if !place.projection.is_empty() {
951            return vm_state.value_of_operand(op);
952        }
953        let local = place.local;
954        // If this local is a parameter (arg), use it directly
955        if local.as_usize() <= vm_state.body().arg_count {
956            return vm_state.value_of_operand(op);
957        }
958        // Trace through simple Use assignments
959        for block in vm_state.body().basic_blocks.iter() {
960            for stmt in &block.statements {
961                if let StatementKind::Assign(assign) = &stmt.kind {
962                    let (dest, rvalue) = &**assign;
963                    if dest.local == local && dest.projection.is_empty() {
964                        #[cfg(rapx_rvalue_use_with_retag)]
965                        if let Rvalue::Use(src_op, _) = rvalue {
966                            return self.trace_value(vm_state, src_op);
967                        }
968                        #[cfg(not(rapx_rvalue_use_with_retag))]
969                        if let Rvalue::Use(src_op) = rvalue {
970                            return self.trace_value(vm_state, src_op);
971                        }
972                    }
973                }
974            }
975        }
976        vm_state.value_of_operand(op)
977    }
978
979    pub(super) fn alloc_elem_is_array_of<'tcx>(
980        &self,
981        alloc_elem_ty: Ty<'tcx>,
982        required_ty: Ty<'tcx>,
983    ) -> bool {
984        match alloc_elem_ty.kind() {
985            TyKind::Array(inner_ty, _) => {
986                *inner_ty == required_ty
987                    || matches!(
988                        (inner_ty.kind(), required_ty.kind()),
989                        (TyKind::Param(_), TyKind::Param(_))
990                    )
991            }
992            TyKind::Slice(inner_ty) => {
993                *inner_ty == required_ty
994                    || matches!(
995                        (inner_ty.kind(), required_ty.kind()),
996                        (TyKind::Param(_), TyKind::Param(_))
997                    )
998            }
999            _ => false,
1000        }
1001    }
1002}
1003
1004/// Unwrap `MaybeUninit<T>` to `T` (or `None` for any other type).  `MaybeUninit`
1005/// is `#[repr(transparent)]` over a union, so `MaybeUninit<T>` and `T` share
1006/// size and alignment.
1007pub(super) fn maybe_uninit_inner(ty: Ty<'_>) -> Option<Ty<'_>> {
1008    if let TyKind::Adt(adt_def, substs) = ty.kind()
1009        && crate::verify::api_classify::is_maybe_uninit_type(adt_def.did())
1010        && let Some(inner) = substs.first().and_then(|s| s.as_type())
1011    {
1012        return Some(inner);
1013    }
1014    None
1015}
1016
1017/// Peel one level of smart-pointer indirection to the pointee type:
1018/// `Box<T>`/`Vec<T>`/`NonNull<T>`/`Rc<T>`/`CString` (matched by `DefId`), plus
1019/// raw pointers and references.  Used by `check_allocated` to discharge
1020/// `Allocated(p, Box<T>, n)` after provenance resolution has already penetrated
1021/// `p` (e.g. a `&mut ManuallyDrop<Box<T>>`) down to the `T` allocation: the
1022/// box's *pointee* is what actually occupies the allocation.
1023pub(super) fn smart_pointer_pointee(ty: Ty<'_>) -> Option<Ty<'_>> {
1024    match ty.kind() {
1025        TyKind::RawPtr(e, _) | TyKind::Ref(_, e, _) => Some(*e),
1026        TyKind::Adt(adt, args) => {
1027            let did = adt.did();
1028            if crate::verify::api_classify::is_std_box(did)
1029                || crate::verify::api_classify::is_std_vec(did)
1030                || crate::verify::api_classify::is_std_nonnull(did)
1031                || crate::verify::api_classify::is_std_cstring(did)
1032                || crate::def_id::rc_types().contains(&did)
1033            {
1034                args.types().next()
1035            } else {
1036                None
1037            }
1038        }
1039        _ => None,
1040    }
1041}