Skip to main content

rapx/verify/vm/
exec.rs

1//! MIR statement and terminator executors for the symbolic VM.
2//!
3//! Each executor is a transfer function that updates `VmState` based on
4//! the semantics of a MIR construct. The VM walks retained MIR items
5//! in forward path order, calling these executors.
6
7use rustc_hir::def_id::DefId;
8use rustc_middle::{
9    mir::{
10        BasicBlock, BinOp, Local, Operand, Place, Rvalue, Statement, StatementKind, Terminator,
11        TerminatorKind, UnOp,
12    },
13    ty::{Region, Ty},
14};
15use z3::ast::{Ast, Bool, Int};
16
17use crate::{
18    compat::{FxHashMap, FxHashSet},
19    verify::{
20        contract::{ContractExpr, ContractKind, PlaceBase, Property, PropertyArg, PropertyKind},
21        def_use::PlaceKey,
22        slicer::RelevantItem,
23    },
24};
25
26use super::state::{
27    AllocId, ElementTy, ValueSource, OffsetKind, Provenance, ValueFacts, VmState,
28    VmValue,
29};
30
31use crate::verify::api_classify;
32
33impl<'z3, 'tcx> VmState<'z3, 'tcx> {
34    /// Execute all retained MIR items in path order.
35    pub(crate) fn execute_items(&mut self, items: &[RelevantItem<'tcx>]) {
36        // Initialize function parameters as fresh symbolic values.
37        // Parameters are _1.._N (excluding _0 return value).
38        self.init_parameters();
39
40        for item in items {
41            match item {
42                RelevantItem::Statement {
43                    def_id,
44                    block,
45                    statement_index,
46                } => {
47                    let body = self.tcx.optimized_mir(*def_id);
48                    let statement = &body.basic_blocks[*block].statements[*statement_index];
49                    self.exec_statement(statement);
50                }
51                RelevantItem::Terminator { def_id, block, switch_succ } => {
52                    let body = self.tcx.optimized_mir(*def_id);
53                    let terminator = body.basic_blocks[*block].terminator();
54                    self.exec_terminator(terminator, *switch_succ);
55                }
56                RelevantItem::CalleeEntry { callee, args } => {
57                    self.handle_callee_entry(*callee, args);
58                }
59                RelevantItem::CalleeExit { dest } => {
60                    self.handle_callee_exit(*dest);
61                }
62                RelevantItem::ContractFact { property } => {
63                    self.assert_contract_fact(property);
64                }
65                RelevantItem::UnknownCall => {}
66            }
67        }
68    }
69
70    /// Enter an inlined callee during path execution: save the caller context,
71    /// switch to the callee body, and bind the caller's argument locals to the
72    /// callee's parameters.
73    fn handle_callee_entry(&mut self, callee: DefId, arg_locals: &[usize]) {
74        let frame = self.save_frame();
75
76        // Collect the caller argument fields from the saved map, so the callee's
77        // parameters inherit them (e.g. NonZero's non-zero inner value, and an
78        // iterator's `ptr`/`end_or_len`). A whole-place reborrow
79        // (`_7 = &mut (*_1)`) carries its referent's fields, so resolve it too;
80        // likewise a whole-place copy temporary (`_2 = copy _1`) that the
81        // optimizer inserted between the caller's argument and the inlined
82        // callee's parameter — follow both chains so the local that actually
83        // materialized the fields is found.
84        let mut arg_fields: Vec<(usize, Vec<usize>, VmValue<'z3, 'tcx>)> = Vec::new();
85        for (i, arg) in arg_locals.iter().enumerate() {
86            let caller_local = Local::from_usize(*arg);
87            let mut source_locals: Vec<(Local, Vec<usize>)> = Vec::new();
88            let mut seen: FxHashSet<(Local, Vec<usize>)> = FxHashSet::default();
89            let mut stack = vec![(caller_local, Vec::new())];
90            while let Some((cur, prefix)) = stack.pop() {
91                if !seen.insert((cur, prefix.clone())) {
92                    continue;
93                }
94                source_locals.push((cur, prefix.clone()));
95                if let Some(r) = self.find_whole_reborrow_referent(cur) {
96                    stack.push((r, prefix.clone()));
97                }
98                if let Some((r, fields)) = self.find_field_reborrow_referent(cur) {
99                    let mut new_prefix = prefix.clone();
100                    new_prefix.extend(fields);
101                    stack.push((r, new_prefix));
102                }
103                if let Some(c) = self.find_copy_root(cur) {
104                    stack.push((c, prefix.clone()));
105                }
106            }
107            for (src, prefix) in source_locals {
108                let keys: Vec<Vec<usize>> = self
109                    .frame_field_paths(&frame, src)
110                    .into_iter()
111                    .filter(|f| {
112                        prefix.is_empty()
113                            || (f.len() >= prefix.len() && f[..prefix.len()] == prefix[..])
114                    })
115                    .collect();
116                for fields in keys {
117                    if let Some(fv) = self.frame_field_value(&frame, src, &fields).cloned() {
118                        let stripped = if prefix.is_empty() {
119                            fields
120                        } else {
121                            fields[prefix.len()..].to_vec()
122                        };
123                        arg_fields.push((i + 1, stripped, fv));
124                    }
125                }
126            }
127        }
128
129        self.current_frame.current_def_id = callee;
130
131        for (i, arg) in arg_locals.iter().enumerate() {
132            if let Some(v) = self.frame_local_value(&frame, Local::from_usize(*arg)).cloned() {
133                self.set_local(Local::from_usize(i + 1), v);
134            }
135        }
136
137        for (callee_param, fields, fv) in arg_fields {
138            self.set_field_value(Local::from_usize(callee_param), fields, fv);
139        }
140
141        self.caller_frames.push(frame);
142    }
143
144    /// Exit an inlined callee: capture the callee's return value, restore the
145    /// caller context, and write the return value to the caller's destination.
146    fn handle_callee_exit(&mut self, dest: usize) {
147        let ret = self.local_value(Local::from_usize(0)).cloned();
148        let ret_fields: Vec<(Vec<usize>, VmValue<'z3, 'tcx>)> = self
149            .field_paths(Local::from_usize(0))
150            .into_iter()
151            .filter_map(|f| {
152                self.field_value(Local::from_usize(0), &f)
153                    .cloned()
154                    .map(|v| (f, v))
155            })
156            .collect();
157        if let Some(frame) = self.caller_frames.pop() {
158            self.restore_frame(frame);
159        }
160        if let Some(mut v) = ret {
161            let dest_ty = self.body().local_decls[Local::from_usize(dest)].ty;
162            v.ty = dest_ty;
163            // Infer facts: a non-null provenance with offset 0 means the
164            // return value is valid and initialized.
165            let at_base = v
166                .provenance
167                .as_ref()
168                .is_some_and(|p| p.offset.as_u64() == Some(0));
169            if at_base {
170                v.facts.non_null = true;
171                self.mark_initialized(&mut v);
172            }
173            self.set_local(Local::from_usize(dest), v);
174            // The callee returned a fully-constructed value, so the caller's
175            // destination stack slot is initialized.
176            if let Some(dest_alloc_id) = self.current_frame.local_alloc.get(&Local::from_usize(dest)).copied()
177            {
178                self.content_mut(dest_alloc_id).facts.initialized = true;
179            }
180        }
181        for (fields, fv) in ret_fields {
182            self.set_field_value(Local::from_usize(dest), fields, fv);
183        }
184    }
185
186    // ── Initialization ──────────────────────────────────────────
187
188    fn init_parameters(&mut self) {
189        let arg_count = self.body().arg_count;
190        let local_count = self.body().local_decls.len();
191
192        // Pre-allocate ALL locals and set initial values
193        for local_idx in 1..local_count {
194            let local = Local::from_usize(local_idx);
195            if self.current_frame.local_alloc.contains_key(&local) {
196                continue;
197            }
198            let decl = &self.body().local_decls[local];
199            let ty = decl.ty;
200
201            self.ensure_local_allocation(local);
202
203            let mut invariants = ValueFacts::default();
204            if local_idx <= arg_count {
205                // ── Box / Vec parameter: heap-allocated pointee ──
206                if let rustc_middle::ty::TyKind::Adt(adt_def, _) = ty.kind() {
207                    let is_vec = api_classify::is_std_vec(adt_def.did());
208                    if api_classify::is_std_box(adt_def.did())
209                        || is_vec
210                        || api_classify::is_std_cstring(adt_def.did())
211                    {
212                        let heap_ty = if let rustc_middle::ty::TyKind::Adt(_, substs) = ty.kind() {
213                            if let Some(first) = substs.first() {
214                                first.as_type()
215                            } else {
216                                None
217                            }
218                        } else {
219                            None
220                        };
221                        let heap_ty = heap_ty.unwrap_or(ty);
222                        let heap_size = self.size_of_ty(heap_ty);
223                        let heap_align = self.align_sym(heap_ty);
224                        let heap_size_term = Int::from_u64(self.z3_ctx, heap_size.max(1));
225                        // Vec/CString can hold many elements — use an external
226                        // allocation so Allocated checks can pass for arbitrary
227                        // capacity queries.
228                        let (heap_alloc_id, heap_base) = if is_vec {
229                            let max_size = Int::from_u64(self.z3_ctx, i64::MAX as u64);
230                            let (id, base) =
231                                self.allocate_external(max_size, heap_align.clone(), Some(heap_ty));
232                            (id, base)
233                        } else {
234                            self.allocate(heap_size_term, heap_align, Some(heap_ty))
235                        };
236                        invariants.non_null = true;
237                        invariants.init = true;
238                        self.content_mut(heap_alloc_id).facts.initialized = true;
239                        // Also expose the container's owning pointer field (Box's
240                        // inner `Unique<T>.pointer` → `NonNull<T>`, Vec's
241                        // `buf.ptr.pointer`) so that inlined bodies like
242                        // `Box::into_non_null_with_allocator` — which reads
243                        // `(_1.0).0` and transmutes it to `NonNull<T>` — inherit
244                        // the heap pointer's non-null/aligned/allocated facts.
245                        let owner_path = self
246                            .container_ptr_field(ty)
247                            .map(|(p, _)| p)
248                            .expect("container parameter has no owning pointer field");
249                        self.set_field_value(
250                            local,
251                            owner_path,
252                            VmValue {
253                                z3_term: heap_base.clone(),
254                                ty,
255                                provenance: Some(Provenance {
256                                    alloc_id: heap_alloc_id,
257                                    offset: Int::from_u64(self.z3_ctx, 0),
258                                    offset_kind: None,
259                                }),
260                                facts: ValueFacts {
261                                    non_null: true,
262                                    init: true,
263                                    ..Default::default()
264                                },
265                                source: ValueSource::None,
266                            },
267                        );
268                        // For Vec: assert `0 <= len <= cap` and
269                        // `cap * elem_size <= isize::MAX` as path conditions
270                        // (the backing allocation's slice length tracks `len`).
271                        if is_vec {
272                            let cap = self.fresh_int(&format!("vec_cap_{}", local_idx));
273                            let len = self.fresh_int(&format!("vec_len_{}", local_idx));
274                            self.materialize_vec_len_cap(cap, len, heap_size);
275                        }
276                        self.set_local(
277                            local,
278                            VmValue {
279                                z3_term: heap_base,
280                                ty,
281                                provenance: Some(Provenance {
282                                    alloc_id: heap_alloc_id,
283                                    offset: Int::from_u64(self.z3_ctx, 0),
284                                    offset_kind: None,
285                                }),
286                                facts: invariants,
287                                source: ValueSource::None,
288                            },
289                        );
290                        continue;
291                    }
292                }
293                // ── Struct/tuple/enum parameter (non-Box/Vec ADT) ──
294                // Decompose into per-field symbolic values for field-level checking.
295                if let rustc_middle::ty::TyKind::Adt(adt_def, _) = ty.kind() {
296                    if adt_def.is_enum() {
297                        let term = self.fresh_int(&format!("param_{}", local_idx));
298                        self.set_local(
299                            local,
300                            VmValue {
301                                z3_term: term,
302                                ty,
303                                provenance: None,
304                                facts: invariants,
305                                source: ValueSource::None,
306                            },
307                        );
308                        continue;
309                    }
310                    let mut elem_alloc: FxHashMap<Ty<'tcx>, (AllocId, Int<'z3>)> =
311                        FxHashMap::default();
312                    self.decompose_adt_fields(local, vec![], ty, local_idx, &mut elem_alloc, 0);
313                    // A single-raw-pointer wrapper (e.g. `NonNull<T>`) *is* its
314                    // pointer, so carry the field's provenance onto the whole local —
315                    // otherwise alias/ownership reasoning can't trace a deref of
316                    // `self.pointer` back to "owned" (the local's provenance would
317                    // be `None`).
318                    if crate::helpers::mir_utils::is_raw_ptr_wrapper(self.tcx, adt_def.did()) {
319                        if let Some(f0) = self.field_value(local, &[0]).cloned() {
320                            let prov = f0.provenance.clone();
321                            self.set_local(
322                                local,
323                                VmValue {
324                                    z3_term: f0.z3_term,
325                                    ty,
326                                    provenance: prov,
327                                    facts: ValueFacts {
328                                        init: true,
329                                        ..Default::default()
330                                    },
331                                    source: ValueSource::None,
332                                },
333                            );
334                            continue;
335                        }
336                    }
337                    let term = self.fresh_int(&format!("param_{}", local_idx));
338                    self.set_local(
339                        local,
340                        VmValue {
341                            z3_term: term,
342                            ty,
343                            provenance: None,
344                            facts: ValueFacts {
345                                init: true,
346                                ..Default::default()
347                            },
348                            source: ValueSource::None,
349                        },
350                    );
351                    continue;
352                }
353                // ── Reference parameter (&T, &mut T) ──
354                // Create a symbolic allocation for the pointee and attach
355                // provenance so that pointer-deriving operations (as_ptr,
356                // add, etc.) propagate correctly.
357                if let rustc_middle::ty::TyKind::Ref(..) = ty.kind() {
358                    // Prefer the precise pointee type from the function
359                    // signature (late-bound-liberated) so reference-field regions
360                    // are `'a` rather than MIR's erased `ReErased`.
361                    let precise_pointee = (local_idx <= arg_count)
362                        .then(|| {
363                            crate::verify::vm::region::fn_arg_ty(
364                                self.tcx,
365                                self.current_frame.current_def_id,
366                                local_idx - 1,
367                            )
368                        })
369                        .flatten()
370                        .and_then(|precise_ty| match precise_ty.kind() {
371                            rustc_middle::ty::TyKind::Ref(_, inner, _) => Some(*inner),
372                            _ => None,
373                        });
374                    let pointee_ty = precise_pointee.unwrap_or_else(|| {
375                        if let rustc_middle::ty::TyKind::Ref(_, inner_ty, _) = ty.kind() {
376                            *inner_ty
377                        } else {
378                            ty
379                        }
380                    });
381                    // `&MaybeUninit<T>` / `&[MaybeUninit<T>]` carry no validity
382                    // invariant — the content need not be initialized — so do not
383                    // claim `Init` for them (the content property reduces to
384                    // `Typed`).
385                    let pointee_is_maybe_uninit = api_classify::is_maybe_uninit_ty(pointee_ty);
386
387                    invariants.non_null = true;
388                    if !pointee_is_maybe_uninit {
389                        invariants.init = true;
390                    }
391                    // A reference always points within a live allocation, so it
392                    // carries the pointer-validity facts (`NonNull`, `Allocated`,
393                    // `InBound`) explicitly — the content property `Init` must
394                    // not be the only source of pointer validity (see
395                    // std-subsumption.rs).
396                    invariants.in_bounds = true;
397
398                    if let rustc_middle::ty::TyKind::Slice(elem_ty) = pointee_ty.kind() {
399                        let elem_size = self.size_of_ty(*elem_ty);
400                        let len = self.fresh_int(&format!("slice_len_{}", local_idx));
401                        let zero = Int::from_u64(self.z3_ctx, 0);
402                        self.constraints.assertions.push(len.ge(&zero));
403                        let isize_max = Int::from_i64(self.z3_ctx, i64::MAX);
404                        let elem_sz = if elem_size > 0 {
405                            elem_size
406                        } else {
407                            crate::helpers::mir_utils::size_of_generic_param(
408                                self.tcx,
409                                self.current_frame.current_def_id,
410                                *elem_ty,
411                            )
412                            .max(1)
413                        };
414                        let elem_sz_term = Int::from_u64(self.z3_ctx, elem_sz);
415                        self.constraints.assertions
416                            .push(Int::mul(self.z3_ctx, &[&len, &elem_sz_term]).le(&isize_max));
417                        // The data allocation's byte size uses the shared
418                        // symbolic `sizeof_T` so `InBound` can cancel the factor
419                        // (`len·S / S == len`); `allocate_slice` computes
420                        // `size = len * sizeof_T` and materializes `len` together
421                        // so the two can never diverge. The alignment is the
422                        // element type's alignment — symbolic (`align_T`) for a
423                        // generic element type, with the layout constraint
424                        // `sizeof_T % align_T == 0` established by `align_sym`.
425                        let elem_align = self.align_sym(*elem_ty);
426                        let elem_size_sym = self.size_sym(*elem_ty);
427                        let (data_alloc_id, data_base) =
428                            self.allocate_slice(len, elem_size_sym, elem_align, Some(*elem_ty));
429                        if !pointee_is_maybe_uninit {
430                            self.content_mut(data_alloc_id).facts.initialized = true;
431                        }
432                        // Record placeholder per-byte symbols for the first few
433                        // elements so byte-level checkers (`ValidCStr` interior-NUL,
434                        // `ValidString` UTF-8) can reason over symbolic slice bytes
435                        // (the length is symbolic, so only a bounded prefix is
436                        // materialized — mirrors the array parameter handling).
437                        let step = (elem_size.max(1)) as usize;
438                        let m = 16usize;
439                        for i in 0..m {
440                            let off = i * step;
441                            let elem_term =
442                                self.fresh_int(&format!("slice_{}_idx_{}", local_idx, i));
443                            self.record_byte_value(data_alloc_id, off, elem_term);
444                        }
445                        self.set_local(
446                            local,
447                            VmValue {
448                                z3_term: data_base,
449                                ty,
450                                provenance: Some(Provenance {
451                                    alloc_id: data_alloc_id,
452                                    offset: Int::from_u64(self.z3_ctx, 0),
453                                    offset_kind: None,
454                                }),
455                                facts: invariants,
456                                source: ValueSource::None,
457                            },
458                        );
459                        continue;
460                    }
461
462                    // Non-slice reference: allocate pointee.  A generic `T`
463                    // yields `sizeof_T`; a struct with a generic field is summed
464                    // (`struct_size_sym`) so a field reference can be discharged.
465                    let pointee_align = self.align_sym(pointee_ty);
466                    let pointee_size_term = self
467                        .struct_size_sym(pointee_ty)
468                        .unwrap_or_else(|| self.size_sym(pointee_ty));
469                    let (pointee_alloc_id, pointee_base) =
470                        self.allocate(pointee_size_term, pointee_align, Some(pointee_ty));
471                    if !pointee_is_maybe_uninit {
472                        self.content_mut(pointee_alloc_id).facts.initialized = true;
473                    }
474                    self.set_local(
475                        local,
476                        VmValue {
477                            z3_term: pointee_base,
478                            ty,
479                            provenance: Some(Provenance {
480                                alloc_id: pointee_alloc_id,
481                                offset: Int::from_u64(self.z3_ctx, 0),
482                                offset_kind: None,
483                            }),
484                            facts: invariants,
485                            source: ValueSource::None,
486                        },
487                    );
488
489                    // Decompose struct fields for pointer-field access.
490                    // E.g. &RawBuf → (*self).ptr should yield a valid raw ptr.
491                    if let rustc_middle::ty::TyKind::Adt(adt_def, substs) = pointee_ty.kind() {
492                        if !adt_def.is_enum() {
493                            let variant = adt_def.non_enum_variant();
494                            // Track the first data allocation per element type.
495                            // Subsequent RawPtr / NonNull fields with the same
496                            // pointee type reuse the allocation with per-field
497                            // symbolic offsets, preserving the field relationships
498                            // (e.g. ptr=start, end_or_len=start+len).
499                            let mut elem_alloc: FxHashMap<Ty<'tcx>, (AllocId, Int<'z3>)> =
500                                FxHashMap::default();
501                            for (idx, field_def) in variant.fields.iter().enumerate() {
502                                let field_ty: Ty<'tcx> = crate::helpers::mir_utils::field_ty(
503                                    self.tcx, field_def, substs,
504                                );
505                                if let rustc_middle::ty::TyKind::RawPtr(inner, _) = field_ty.kind()
506                                {
507                                    self.init_ptr_field(
508                                        local,
509                                        vec![idx],
510                                        field_ty,
511                                        *inner,
512                                        local_idx,
513                                        idx,
514                                        &mut elem_alloc,
515                                        true,
516                                        "field_nn",
517                                    );
518                                } else if let Some(pointee) = self.find_nn_pointee(field_ty) {
519                                    // Field contains NonNull<T> (possibly wrapped in Option):
520                                    // create/reuse an external allocation for the pointee.
521                                    self.init_ptr_field(
522                                        local,
523                                        vec![idx],
524                                        field_ty,
525                                        pointee,
526                                        local_idx,
527                                        idx,
528                                        &mut elem_alloc,
529                                        false,
530                                        "ref_field",
531                                    );
532                                } else if let rustc_middle::ty::TyKind::Ref(region, pointee, _) =
533                                    field_ty.kind()
534                                {
535                                    // Field contains a reference (&T, &mut T, &[T], etc.).
536                                    // Give it provenance so that as_ptr() / as_mut_ptr()
537                                    // on the field propagates the allocation info.
538                                    let elem_ty = match pointee.kind() {
539                                        rustc_middle::ty::TyKind::Slice(e) => *e,
540                                        _ => *pointee,
541                                    };
542                                    self.materialize_external_field(
543                                        local, idx, field_ty, elem_ty, Some(*region),
544                                    );
545                                } else if let rustc_middle::ty::TyKind::Slice(elem_ty) =
546                                    field_ty.kind()
547                                {
548                                    // DST slice field (e.g. `CStr { inner: [u8] }`):
549                                    // model it as an external allocation so its length
550                                    // stays symbolic instead of defaulting to a single
551                                    // element (which would make `inner.len()` == 1).
552                                    self.materialize_external_field(
553                                        local, idx, field_ty, *elem_ty, None,
554                                    );
555                                } else if let rustc_middle::ty::TyKind::Adt(adt, substs) =
556                                    field_ty.kind()
557                                {
558                                    // A heap-backed smart-pointer field (`Box<[T]>`,
559                                    // `Vec<T>`) inside a referenced struct: materialize
560                                    // the pointee allocation so field access (e.g.
561                                    // `self.buckets.iter()`) resolves to the *data*
562                                    // elements, not the whole struct.
563                                    if api_classify::is_std_box(adt.did())
564                                        || api_classify::is_std_vec(adt.did())
565                                    {
566                                        if let Some(pointee) =
567                                            substs.first().and_then(|s| s.as_type())
568                                        {
569                                            let elem_ty = match pointee.kind() {
570                                                rustc_middle::ty::TyKind::Slice(e) => *e,
571                                                _ => pointee,
572                                            };
573                                            self.materialize_external_field(
574                                                local, idx, field_ty, elem_ty, None,
575                                            );
576                                        }
577                                    }
578                                } else if matches!(
579                                    field_ty.kind(),
580                                    rustc_middle::ty::TyKind::Uint(_)
581                                        | rustc_middle::ty::TyKind::Int(_)
582                                        | rustc_middle::ty::TyKind::Float(_)
583                                        | rustc_middle::ty::TyKind::Bool
584                                        | rustc_middle::ty::TyKind::Char
585                                ) {
586                                    // Scalar field (e.g. `size: usize`) inside a
587                                    // referenced struct. Materialize a fresh
588                                    // symbolic value so that field reads return
589                                    // the correct term instead of the whole
590                                    // struct term. Non-scalar, non-pointer ADT
591                                    // fields (Box/Vec/etc.) are left unset so
592                                    // they keep their pre-existing heap modeling.
593                                    let field_term =
594                                        self.fresh_int(&format!("ref_field_{}_{}", local_idx, idx));
595                                    self.set_field_value(
596                                        local,
597                                        vec![idx],
598                                        VmValue {
599                                            z3_term: field_term,
600                                            ty: field_ty,
601                                            provenance: None,
602                                            facts: ValueFacts {
603                                                init: true,
604                                                ..Default::default()
605                                            },
606                                            source: ValueSource::None,
607                                        },
608                                    );
609                                }
610                            }
611                        }
612                    }
613                    continue;
614                }
615                // ── Scalar parameter ──
616                let is_scalar = matches!(
617                    ty.kind(),
618                    rustc_middle::ty::TyKind::Uint(_)
619                        | rustc_middle::ty::TyKind::Int(_)
620                        | rustc_middle::ty::TyKind::Bool
621                        | rustc_middle::ty::TyKind::Char
622                );
623                if is_scalar {
624                    let val = self.fresh_int(&format!("arg_{}", local_idx));
625                    self.set_local(
626                        local,
627                        VmValue {
628                            z3_term: val,
629                            ty,
630                            provenance: None,
631                            facts: invariants,
632                            source: ValueSource::None,
633                        },
634                    );
635                    continue;
636                }
637                // ── Raw pointer parameter (*const T, *mut T) ──
638                // Create a symbolic external allocation for provenance
639                // tracking. No invariants are set — callers must provide
640                // contracts (NonNull, ValidPtr, etc.) via assert_contract_fact
641                // to make property checks pass.
642                if let rustc_middle::ty::TyKind::RawPtr(pointee, _mutbl) = ty.kind() {
643                    let max_size = Int::from_u64(self.z3_ctx, i64::MAX as u64);
644                    let pointee_align = self.align_sym(*pointee);
645                    let (alloc_id, base) =
646                        self.allocate_external(max_size, pointee_align, Some(*pointee));
647                    self.set_local(
648                        local,
649                        VmValue {
650                            z3_term: base,
651                            ty,
652                            provenance: Some(Provenance {
653                                alloc_id,
654                                offset: Int::from_u64(self.z3_ctx, 0),
655                                offset_kind: None,
656                            }),
657                            facts: invariants,
658                            source: ValueSource::None,
659                        },
660                    );
661                    continue;
662                }
663                // ── Array parameter ([usize; N], etc.) ──
664                // Give every array parameter a real allocation with provenance so
665                // that downstream call effects (e.g. ChecksIndexBoundsDisjoint)
666                // can record the alloc_id and property checker can match it later.
667                if let rustc_middle::ty::TyKind::Array(elem_ty, const_len) = ty.kind() {
668                    let n: Option<usize> =
669                        crate::helpers::mir_utils::eval_array_len(self.tcx, const_len)
670                            .map(|v| v as usize);
671                    let elem_size = self.size_of_ty(*elem_ty);
672                    let step = (elem_size.max(1)) as usize;
673                    let align = self.align_sym(*elem_ty);
674                    // Symbolic-aware element size: a generic `T` gets `sizeof_T`
675                    // (≥ 1) so the allocation is `N·sizeof_T` bytes, not `N`.
676                    let elem_sym = self.size_sym(*elem_ty);
677                    // Materialized element count (the array length `N`).
678                    let n_term = match n {
679                        Some(v) => Int::from_u64(self.z3_ctx, v as u64),
680                        None => {
681                            let const_text =
682                                format!("Ty({:?}, {:?})", self.tcx.types.usize, const_len);
683                            let name =
684                                format!("const_{}", const_text.replace([':', '#', ' '], "_"));
685                            Int::new_const(self.z3_ctx, name.as_str())
686                        }
687                    };
688                    let (alloc_id, base) = if let Some(n) = n {
689                        let total =
690                            Int::mul(self.z3_ctx, &[&Int::from_u64(self.z3_ctx, n as u64), &elem_sym]);
691                        self.allocate(total, align.clone(), Some(*elem_ty))
692                    } else {
693                        // Generic N: unbounded external allocation
694                        let max_size = Int::from_u64(self.z3_ctx, i64::MAX as u64);
695                        self.allocate_external(max_size, align, Some(*elem_ty))
696                    };
697                    self.alloc_mut(alloc_id).set_slice_len(n_term);
698                    self.content_mut(alloc_id).facts.initialized = true;
699                    self.current_frame.local_alloc.insert(local, alloc_id);
700                    if let Some(n) = n {
701                        for i in 0..n {
702                            let off = i * step;
703                            let elem_term =
704                                self.fresh_int(&format!("array_{}_idx_{}", local_idx, i));
705                            self.record_byte_value(alloc_id, off, elem_term);
706                        }
707                    } else {
708                        // Generic N: create placeholder byte tracking so that
709                        // downstream Index projection ITE chains and
710                        // assert_in_bound_for_each can add constraints.
711                        let m = 16usize;
712                        for i in 0..m {
713                            let off = i * step;
714                            let elem_term =
715                                self.fresh_int(&format!("array_{}_idx_{}", local_idx, i));
716                            self.record_byte_value(alloc_id, off, elem_term);
717                        }
718                    }
719                    self.set_local(
720                        local,
721                        VmValue {
722                            z3_term: base,
723                            ty,
724                            provenance: Some(Provenance {
725                                alloc_id,
726                                offset: Int::from_u64(self.z3_ctx, 0),
727                                offset_kind: None,
728                            }),
729                            facts: ValueFacts {
730                                init: true,
731                                ..invariants
732                            },
733                            source: ValueSource::None,
734                        },
735                    );
736                    continue;
737                }
738                // ── Struct / other parameter ──
739                let term = self.fresh_int(&format!("param_{}", local_idx));
740                self.set_local(
741                    local,
742                    VmValue {
743                        z3_term: term,
744                        ty,
745                        provenance: None,
746                        facts: invariants,
747                        source: ValueSource::None,
748                    },
749                );
750                continue;
751            }
752            // ── Non-parameter local: fallback value (overwritten by actual
753            // assignments). For reference/raw-pointer locals the value *is* the
754            // stack address; for scalar locals use a fresh symbolic value so a
755            // stale stack address never leaks into scalar arithmetic (e.g. the
756            // `offset <= len` bound check in memchr-style loops).
757            let term = match ty.kind() {
758                rustc_middle::ty::TyKind::Ref(..) | rustc_middle::ty::TyKind::RawPtr(..) => {
759                    self.local_address(local)
760                }
761                _ => self.fresh_int(&format!("local_{}", local_idx)),
762            };
763            self.set_local(
764                local,
765                VmValue {
766                    z3_term: term,
767                    ty,
768                    provenance: None,
769                    facts: invariants,
770                    source: ValueSource::None,
771                },
772            );
773        }
774
775        // Entry-block provenance propagation: scan the first basic block
776        // for simple assignments that propagate parameter values.  This
777        // helps when the backward slicer omits same-block definitions
778        // (e.g. `_tmp = _1 as *const T`).  Limiting to the entry block
779        // ensures only unconditionally-executed assignments are covered.
780        if let Some(entry_bb) = self.body().basic_blocks.iter().next() {
781            for stmt in &entry_bb.statements {
782                if let StatementKind::Assign(assign) = &stmt.kind {
783                    let (dest, rvalue) = &**assign;
784                    let dest_local = dest.local;
785                    let src = match rvalue {
786                        #[cfg(rapx_rvalue_use_with_retag)]
787                        Rvalue::Use(operand, _) => Some(operand),
788                        #[cfg(not(rapx_rvalue_use_with_retag))]
789                        Rvalue::Use(operand) => Some(operand),
790                        Rvalue::Cast(_, operand, _) => Some(operand),
791                        _ => None,
792                    }
793                    .and_then(|operand| match operand {
794                        Operand::Copy(place) | Operand::Move(place)
795                            if place.projection.is_empty() =>
796                        {
797                            Some(place.local)
798                        }
799                        _ => None,
800                    });
801                    if let Some(src_local) = src {
802                        if let Some(src_val) = self.local_value(src_local) {
803                            let has_better_prov = src_val.is_pointer()
804                                && src_val.facts.non_null
805                                && self.local_value(dest_local).is_none_or(|d| {
806                                    d.provenance.is_none() || !d.facts.non_null
807                                });
808                            if has_better_prov {
809                                self.set_local(
810                                    dest_local,
811                                    VmValue {
812                                        z3_term: src_val.z3_term.clone(),
813                                        ty: dest.ty(self.body(), self.tcx).ty,
814                                        provenance: src_val.provenance.clone(),
815                                        facts: src_val.facts.clone(),
816                                        source: ValueSource::None,
817                                    },
818                                );
819                            }
820                        }
821                    }
822                }
823            }
824        }
825
826        // Pre-warm a shared symbolic `sizeof_T` / `align_T` for every generic type
827        // parameter of the caller.  Warming every declared type parameter (not
828        // just those in the signature) makes the read-only `*_sym_read` fallback
829        // dead.
830        let mut warmed: FxHashSet<Ty<'tcx>> = FxHashSet::default();
831        for param in self.tcx.generics_of(self.current_frame.current_def_id).own_params.iter() {
832            if let rustc_middle::ty::GenericParamDefKind::Type { .. } = param.kind {
833                warmed.insert(rustc_middle::ty::Ty::new_param(
834                    self.tcx,
835                    param.index,
836                    param.name,
837                ));
838            }
839        }
840        for ty in warmed {
841            self.size_sym(ty);
842            self.align_sym(ty);
843        }
844    }
845
846    /// Materialize a struct field holding a reference (`&T` / `&[T]`), a DST
847    /// slice (`[T]`), or a heap-backed smart pointer (`Box`/`Vec`) as an external
848    /// allocation, so field access (e.g. `self.buckets.iter()`, `as_ptr()`)
849    /// resolves to the *data* rather than the whole struct. The caller
850    /// pre-computes `elem_ty` (the pointee / slice element). `alive_region`
851    /// carries the lifetime of a reference field (`&'a T`), marking its
852    /// referent alive for `'a` (a reference guarantees its referent is alive);
853    /// a raw slice / `Box` / `Vec` field carries no such guarantee and passes
854    /// `None`.
855    fn materialize_external_field(
856        &mut self,
857        local: Local,
858        idx: usize,
859        field_ty: Ty<'tcx>,
860        elem_ty: Ty<'tcx>,
861        alive_region: Option<Region<'tcx>>,
862    ) {
863        let align = self.align_sym(elem_ty);
864        let max_size = Int::from_u64(self.z3_ctx, i64::MAX as u64);
865        let (alloc_id, base) = self.allocate_external(max_size, align, Some(elem_ty));
866        self.content_mut(alloc_id).facts.initialized = true;
867        if let Some(region) = alive_region {
868            self.alloc_mut(alloc_id).facts.liveness = Some(region);
869        }
870        self.set_field_value(
871            local,
872            vec![idx],
873            VmValue {
874                z3_term: base,
875                ty: field_ty,
876                provenance: Some(Provenance {
877                    alloc_id,
878                    offset: Int::from_u64(self.z3_ctx, 0),
879                    offset_kind: None,
880                }),
881                facts: ValueFacts {
882                    non_null: true,
883                    init: true,
884                    ..Default::default()
885                },
886                source: ValueSource::None,
887            },
888        );
889    }
890
891    /// Initialize one pointer-like field (raw pointer or `NonNull<T>`) of a
892    /// decomposed struct/ref parameter. The first field with a given pointee
893    /// type creates a shared external allocation; later fields with the same
894    /// pointee reuse it with a symbolic offset, preserving relationships like
895    /// `ptr = start, end_or_len = start + len`.
896    #[allow(clippy::too_many_arguments)]
897    fn init_ptr_field(
898        &mut self,
899        local: Local,
900        path: Vec<usize>,
901        field_ty: Ty<'tcx>,
902        pointee: Ty<'tcx>,
903        local_idx: usize,
904        idx: usize,
905        elem_alloc: &mut FxHashMap<Ty<'tcx>, (AllocId, Int<'z3>)>,
906        is_raw_ptr: bool,
907        nn_fresh_prefix: &str,
908    ) {
909        // A `NonNull<T>` guarantees its inner pointer is aligned to `T`; a raw
910        // pointer carries no such guarantee.
911        let invariants = if is_raw_ptr {
912            // A raw pointer carries no non-null / init / align guarantee; those
913            // facts must come from the struct's own `#[rapx::invariant]`s.
914            ValueFacts::default()
915        } else {
916            ValueFacts {
917                init: true,
918                align_n: Some(self.align_sym(pointee)),
919                ..Default::default()
920            }
921        };
922        // Only raw-pointer fields (e.g. `Iter::end_or_len = ptr + len·sizeof_T`)
923        // reuse the shared per-pointee-type allocation. A `NonNull<T>` field
924        // names a *distinct* heap object (e.g. each `NodeRef.node` points at its
925        // own leaf), so it must get its own allocation rather than a symbolic
926        // offset into a sibling's — otherwise `left_child.node` and
927        // `right_child.node` would alias the same `LeafNode`, losing the per-node
928        // `Init`/`Allocated` provenance.
929        if is_raw_ptr && let Some(&(existing_alloc, ref base)) = elem_alloc.get(&pointee) {
930            // Byte-accurate element size (`sizeof_T` for a generic `T`) so the
931            // field's offset is a multiple of `align_T` (via the layout
932            // constraint `sizeof_T % align_T == 0` established by `align_sym`).
933            // A raw pointer field that reuses the aligned base allocation is
934            // therefore itself `align_T`-aligned (e.g. `Iter::end_or_len`,
935            // `ptr + len·sizeof_T`).
936            let elem_align = self.align_sym(pointee);
937            let elem_size = self.size_sym(pointee);
938            let len_term = self.fresh_int(&format!("field_len_{}_{}", local_idx, idx));
939            self.constraints.assertions
940                .push(len_term.ge(&Int::from_u64(self.z3_ctx, 0)));
941            let prost_offset = Int::mul(self.z3_ctx, &[&len_term, &elem_size]);
942            let field_term = Int::add(self.z3_ctx, &[base, &prost_offset]);
943            let mut invariants = invariants;
944            if is_raw_ptr {
945                invariants.align_n = Some(elem_align);
946            }
947            self.set_field_value(
948                local,
949                path,
950                VmValue {
951                    z3_term: field_term,
952                    ty: field_ty,
953                    provenance: Some(Provenance {
954                        alloc_id: existing_alloc,
955                        offset: prost_offset.clone(),
956                        offset_kind: Some(OffsetKind::Element(len_term.clone())),
957                    }),
958                    facts: invariants,
959                    source: ValueSource::None,
960                },
961            );
962        } else {
963            // A raw pointer carries no alignment or size guarantee for its
964            // target, so the external allocation is created with alignment 1 and
965            // a *symbolic* (unknown) size.  A `NonNull`/`Box` pointee, by
966            // contrast, is genuinely aligned and keeps the concrete `i64::MAX`
967            // "unbounded" size.
968            let field_align = if is_raw_ptr {
969                Int::from_u64(self.z3_ctx, 1)
970            } else {
971                self.align_sym(pointee)
972            };
973            let max_size = if is_raw_ptr {
974                let s = self.fresh_int("raw_target_size");
975                self.constraints.assertions.push(s.ge(&Int::from_u64(self.z3_ctx, 0)));
976                s
977            } else {
978                Int::from_u64(self.z3_ctx, i64::MAX as u64)
979            };
980            let (field_alloc_id, field_base) =
981                self.allocate_external(max_size, field_align, Some(pointee));
982            elem_alloc.insert(pointee, (field_alloc_id, field_base.clone()));
983            // Decompose the pointee's own fields into per-allocation tracking
984            // so `(*ptr).field` derefs resolve to the field value (not the raw
985            // pointer term). This is what lets `&*NonNull<LeafNode>` expose
986            // `LeafNode.len`.
987            self.decompose_pointee_fields(
988                field_alloc_id,
989                Vec::new(),
990                pointee,
991                pointee,
992                local_idx,
993                0,
994                0,
995            );
996            // A `NonNull`/`Box` pointee is a valid value of `pointee`, so its
997            // struct invariants (`ValidNum(len <= CAPACITY)` on `LeafNode`)
998            // hold for the freshly-decomposed allocation.  Raw pointers carry no
999            // such guarantee and are skipped.
1000            if !is_raw_ptr {
1001                self.assert_alloc_pointee_invariants(field_alloc_id, pointee);
1002            }
1003            let field_term = if is_raw_ptr {
1004                field_base
1005            } else {
1006                self.fresh_int(&format!("{}_{}_{}", nn_fresh_prefix, local_idx, idx))
1007            };
1008            self.set_field_value(
1009                local,
1010                path,
1011                VmValue {
1012                    z3_term: field_term,
1013                    ty: field_ty,
1014                    provenance: Some(Provenance {
1015                        alloc_id: field_alloc_id,
1016                        offset: Int::from_u64(self.z3_ctx, 0),
1017                        offset_kind: None,
1018                    }),
1019                    facts: invariants,
1020                    source: ValueSource::None,
1021                },
1022            );
1023        }
1024    }
1025
1026    /// Recursively decompose a (possibly nested) struct parameter into per-field
1027    /// symbolic values.  Nested ADT fields (e.g. `Handle { node: NodeRef { node:
1028    /// NonNull<LeafNode>, .. }, .. }`) are descended into so their `NonNull` /
1029    /// raw-pointer leaves get external-allocation provenance — otherwise a
1030    /// `NonNull` buried two levels deep loses its provenance and downstream
1031    /// `Allocated`/`Init` checks (e.g. `descend`'s `edges.get_unchecked`) fail.
1032    fn decompose_adt_fields(
1033        &mut self,
1034        local: Local,
1035        prefix: Vec<usize>,
1036        ty: Ty<'tcx>,
1037        local_idx: usize,
1038        elem_alloc: &mut FxHashMap<Ty<'tcx>, (AllocId, Int<'z3>)>,
1039        depth: usize,
1040    ) {
1041        if depth > 4 {
1042            return;
1043        }
1044        let rustc_middle::ty::TyKind::Adt(adt_def, substs) = ty.kind() else {
1045            return;
1046        };
1047        if adt_def.is_enum() {
1048            return;
1049        }
1050        let variant = adt_def.non_enum_variant();
1051        for (idx, field_def) in variant.fields.iter().enumerate() {
1052            let field_ty: Ty<'tcx> =
1053                crate::helpers::mir_utils::field_ty(self.tcx, field_def, substs);
1054            let mut path = prefix.clone();
1055            path.push(idx);
1056            if let rustc_middle::ty::TyKind::RawPtr(inner, _) = field_ty.kind() {
1057                self.init_ptr_field(
1058                    local, path, field_ty, *inner, local_idx, idx, elem_alloc, true, "field_nn",
1059                );
1060                continue;
1061            }
1062            if let Some(pointee) = self.find_nn_pointee(field_ty) {
1063                self.init_ptr_field(
1064                    local, path, field_ty, pointee, local_idx, idx, elem_alloc, false, "field_nn",
1065                );
1066                continue;
1067            }
1068            if let rustc_middle::ty::TyKind::Adt(inner_adt, _) = field_ty.kind() {
1069                if !inner_adt.is_enum() {
1070                    self.decompose_adt_fields(
1071                        local, path, field_ty, local_idx, elem_alloc, depth + 1,
1072                    );
1073                    continue;
1074                }
1075            }
1076            let field_term = self.fresh_int(&format!("field_{}_{}", local_idx, idx));
1077            self.set_field_value(
1078                local,
1079                path,
1080                VmValue {
1081                    z3_term: field_term,
1082                    ty: field_ty,
1083                    provenance: None,
1084                    facts: ValueFacts {
1085                        init: true,
1086                        ..Default::default()
1087                    },
1088                    source: ValueSource::None,
1089                },
1090            );
1091        }
1092    }
1093
1094    /// Read a scalar field's value back out of the byte layer over
1095    /// `[offset, offset + size)` (little-endian).  Returns `None` when the byte
1096    /// layer does not cover the whole field (a hole, or no byte written), so
1097    /// the caller falls back to a fresh symbol.
1098    ///
1099    /// This is the byte→field direction of the cast cross-view materialization:
1100    /// when bytes are written first (`*buf = 3`) and the buffer is then
1101    /// reinterpreted as a `repr(C)` struct, the field value is those bytes, not
1102    /// an unrelated fresh symbol.
1103    fn field_term_from_bytes(
1104        &self,
1105        alloc_id: AllocId,
1106        offset: usize,
1107        size: usize,
1108    ) -> Option<Int<'z3>> {
1109        if size == 0 {
1110            return Some(Int::from_u64(self.z3_ctx, 0));
1111        }
1112        let mut term = Int::from_u64(self.z3_ctx, 0);
1113        for j in 0..size {
1114            let off = offset + j;
1115            if !self.is_byte_init(alloc_id, off) {
1116                return None;
1117            }
1118            let b = self.byte_read(alloc_id, &Int::from_u64(self.z3_ctx, off as u64));
1119            let weight = Int::from_u64(self.z3_ctx, 1u64 << (j * 8));
1120            term = Int::add(self.z3_ctx, &[&term, &Int::mul(self.z3_ctx, &[&b, &weight])]);
1121        }
1122        Some(term)
1123    }
1124
1125    /// Recursively decompose a pointee ADT's fields into per-allocation field
1126    /// tracking, mirroring [`decompose_adt_fields`](Self::decompose_adt_fields)
1127    /// but keyed by allocation instead of local. This is what lets a
1128    /// `&*NonNull<LeafNode>` dereference resolve `(*leaf).len` to the actual
1129    /// `len` field value rather than the raw pointer term.
1130    ///
1131    /// `byte_offset` is the running byte offset of the field being decomposed,
1132    /// used for the byte→field cross-view materialization (see
1133    /// [`Self::field_term_from_bytes`]).
1134    fn decompose_pointee_fields(
1135        &mut self,
1136        alloc_id: AllocId,
1137        prefix: Vec<usize>,
1138        ty: Ty<'tcx>,
1139        root_ty: Ty<'tcx>,
1140        local_idx: usize,
1141        depth: usize,
1142        byte_offset: usize,
1143    ) {
1144        use rustc_middle::ty::TyKind;
1145        if depth > 4 {
1146            return;
1147        }
1148        let TyKind::Adt(adt_def, substs) = ty.kind() else {
1149            return;
1150        };
1151        if adt_def.is_enum() {
1152            return;
1153        }
1154        let variant = adt_def.non_enum_variant();
1155        for (idx, field_def) in variant.fields.iter().enumerate() {
1156            let field_ty = crate::helpers::mir_utils::field_ty(self.tcx, field_def, substs);
1157            let field_off = byte_offset + self.field_offset_in_bytes(ty, idx) as usize;
1158            let mut path = prefix.clone();
1159            path.push(idx);
1160            if let Some(pointee) = self.find_nn_pointee(field_ty) {
1161                let field_align = self.align_sym(pointee);
1162                let max_size = Int::from_u64(self.z3_ctx, i64::MAX as u64);
1163                let (fa, _fb) = self.allocate_external(max_size, field_align, Some(pointee));
1164                self.content_mut(fa).facts.initialized = true;
1165                let term = self.fresh_int(&format!("pointee_nn_{}_{}", local_idx, idx));
1166                self.units[alloc_id.0].content.values.insert(
1167                    (root_ty, path.clone()),
1168                    VmValue {
1169                        z3_term: term,
1170                        ty: field_ty,
1171                        provenance: Some(Provenance {
1172                            alloc_id: fa,
1173                            offset: Int::from_u64(self.z3_ctx, 0),
1174                            offset_kind: None,
1175                        }),
1176                        facts: ValueFacts {
1177                            init: true,
1178                            ..Default::default()
1179                        },
1180                        source: ValueSource::None,
1181                    },
1182                );
1183                self.decompose_pointee_fields(
1184                    fa,
1185                    Vec::new(),
1186                    pointee,
1187                    pointee,
1188                    local_idx,
1189                    depth + 1,
1190                    0,
1191                );
1192            } else if let TyKind::Adt(_, _) = field_ty.kind() {
1193                self.decompose_pointee_fields(
1194                    alloc_id,
1195                    path,
1196                    field_ty,
1197                    root_ty,
1198                    local_idx,
1199                    depth + 1,
1200                    field_off,
1201                );
1202            } else if matches!(
1203                field_ty.kind(),
1204                TyKind::Uint(_) | TyKind::Int(_) | TyKind::Float(_) | TyKind::Bool | TyKind::Char
1205            ) {
1206                // Cross-view materialization: if the backing bytes were already
1207                // written (e.g. `*buf = 3` before `buf as *mut Header`), read
1208                // the field's value back out of the byte layer instead of a
1209                // fresh symbol, so a byte→struct reinterpret round-trips.
1210                let field_size = self.size_of_ty(field_ty) as usize;
1211                let field_term = self
1212                    .field_term_from_bytes(alloc_id, field_off, field_size)
1213                    .unwrap_or_else(|| self.fresh_int(&format!("pointee_field_{}_{}", local_idx, idx)));
1214                self.units[alloc_id.0].content.values.insert(
1215                    (root_ty, path.clone()),
1216                    VmValue {
1217                        z3_term: field_term,
1218                        ty: field_ty,
1219                        provenance: None,
1220                        facts: ValueFacts {
1221                            init: true,
1222                            ..Default::default()
1223                        },
1224                        source: ValueSource::None,
1225                    },
1226                );
1227            } else if let TyKind::Array(elem_ty, const_len) = field_ty.kind() {
1228                // Array field: allocate its contents so `.len()` / `as_slice()`
1229                // resolve to the concrete array length.
1230                let n = crate::helpers::mir_utils::eval_array_len(self.tcx, const_len).unwrap_or(0)
1231                    as u64;
1232                let arr_align = self.align_sym(*elem_ty);
1233                let arr_elem_size = self.size_sym(*elem_ty);
1234                // Materialize the array length (via `allocate_slice`) so `len()`
1235                // reads the constant `n` directly rather than `size / elem_size`,
1236                // which is ill-defined when the element type is a generic ZST
1237                // (`elem_size = 0`).
1238                let (fa, fb) = self.allocate_slice(
1239                    Int::from_u64(self.z3_ctx, n),
1240                    arr_elem_size,
1241                    arr_align,
1242                    Some(*elem_ty),
1243                );
1244                self.content_mut(fa).facts.initialized = true;
1245                self.units[alloc_id.0].content.values.insert(
1246                    (root_ty, path.clone()),
1247                    VmValue {
1248                        z3_term: fb,
1249                        ty: field_ty,
1250                        provenance: Some(Provenance {
1251                            alloc_id: fa,
1252                            offset: Int::from_u64(self.z3_ctx, 0),
1253                            offset_kind: None,
1254                        }),
1255                        facts: ValueFacts {
1256                            init: true,
1257                            ..Default::default()
1258                        },
1259                        source: ValueSource::None,
1260                    },
1261                );
1262            }
1263        }
1264    }
1265
1266    // ── Statement executors ──────────────────────────────────────
1267
1268    pub(crate) fn exec_statement(&mut self, statement: &Statement<'tcx>) {
1269        match &statement.kind {
1270            StatementKind::Assign(assign) => {
1271                let (place, rvalue) = &**assign;
1272                self.exec_assign(place, rvalue);
1273            }
1274            StatementKind::StorageLive(local) => {
1275                self.exec_storage_live(*local);
1276            }
1277            StatementKind::StorageDead(local) => {
1278                self.exec_storage_dead(*local);
1279            }
1280            StatementKind::FakeRead(..)
1281            | StatementKind::SetDiscriminant { .. }
1282            | StatementKind::AscribeUserType(..)
1283            | StatementKind::Coverage(..)
1284            | StatementKind::PlaceMention(..)
1285            | StatementKind::Intrinsic(..)
1286            | StatementKind::ConstEvalCounter
1287            | StatementKind::Nop => {}
1288            #[cfg(not(rapx_ge_99))]
1289            StatementKind::Retag(..) => {}
1290            _ => {}
1291        }
1292    }
1293
1294    fn exec_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>) {
1295        let value = self.eval_rvalue(place, rvalue);
1296
1297        let has_deref = place
1298            .projection
1299            .iter()
1300            .any(|p| matches!(p.kind(), rustc_middle::mir::ProjectionElem::Deref));
1301
1302        if !place.projection.is_empty() {
1303            self.record_projected_store(place, &value);
1304            self.record_indexed_store_for_vm(place, &value);
1305        }
1306
1307        if place.projection.is_empty() {
1308            let mut value = value;
1309            value.facts.init = true;
1310            self.set_local(place.local, value);
1311            // On a whole-place move (`_3 = move _4`), the destination is a fresh
1312            // container slot: re-point its whole-value provenance from the source's
1313            // stack slot to its own, so `container_data_alloc` later reads the
1314            // destination's owning field (copied below) rather than the moved-out
1315            // source's (which is invalidated after the propagation).
1316            let moved_from = match rvalue {
1317                #[cfg(rapx_rvalue_use_with_retag)]
1318                Rvalue::Use(operand, _) => match operand {
1319                    Operand::Move(p) if p.projection.is_empty() => Some(p.local),
1320                    _ => None,
1321                },
1322                #[cfg(not(rapx_rvalue_use_with_retag))]
1323                Rvalue::Use(operand) => match operand {
1324                    Operand::Move(p) if p.projection.is_empty() => Some(p.local),
1325                    _ => None,
1326                },
1327                _ => None,
1328            };
1329            if let Some(src) = moved_from {
1330                if let Some(&src_alloc) = self.current_frame.local_alloc.get(&src) {
1331                    if let Some(mut wv) = self.local_value(place.local).cloned() {
1332                        if wv.provenance.as_ref().is_some_and(|p| p.alloc_id == src_alloc) {
1333                            let dest_alloc = self.current_frame.local_alloc[&place.local];
1334                            wv.provenance = Some(Provenance {
1335                                alloc_id: dest_alloc,
1336                                offset: Int::from_u64(self.z3_ctx, 0),
1337                                offset_kind: None,
1338                            });
1339                            self.set_local(place.local, wv);
1340                        }
1341                    }
1342                }
1343            }
1344            // Propagate field values for aggregate copies (e.g. `_4 = copy _1`)
1345            // so downstream field accesses (NonZero::get -> self.0) resolve to
1346            // the same symbolic field terms.  A projected source (`_11 = move
1347            // (_1.2)`) shifts the field path by its `Field` projection prefix,
1348            // so `(_1.2).1` becomes `_11.1` — this keeps the `NonNull` node
1349            // field's provenance alive across `NodeRef` moves.
1350            let src_place: Option<&Place<'tcx>> = match rvalue {
1351                #[cfg(rapx_rvalue_use_with_retag)]
1352                Rvalue::Use(operand, _) => match operand {
1353                    Operand::Copy(p) | Operand::Move(p) => Some(p),
1354                    _ => None,
1355                },
1356                #[cfg(not(rapx_rvalue_use_with_retag))]
1357                Rvalue::Use(operand) => match operand {
1358                    Operand::Copy(p) | Operand::Move(p) => Some(p),
1359                    _ => None,
1360                },
1361                Rvalue::CopyForDeref(p) => Some(p),
1362                _ => None,
1363            };
1364            if let Some(sp) = src_place {
1365                let field_prefix: Vec<usize> = sp
1366                    .projection
1367                    .iter()
1368                    .filter_map(|p| match p.kind() {
1369                        rustc_middle::mir::ProjectionElem::Field(fi, _) => Some(fi.as_usize()),
1370                        _ => None,
1371                    })
1372                    .collect();
1373                // Also propagate for a leading `Deref` (`_3 = copy (*_1)`): the
1374                // source is the pointee of a reference, whose per-field values
1375                // are keyed by the reference local itself, so copying the pointee
1376                // value into a fresh local must carry those field values along
1377                // (otherwise `into_leaf(self)`'s `self.node` provenance is lost).
1378                let only_field_deref = sp.projection.iter().all(|p| {
1379                    matches!(
1380                        p.kind(),
1381                        rustc_middle::mir::ProjectionElem::Field(..)
1382                            | rustc_middle::mir::ProjectionElem::Deref
1383                    )
1384                });
1385                if only_field_deref {
1386                    let keys: Vec<Vec<usize>> = self.field_paths(sp.local);
1387                    for k in keys {
1388                        let rest = if field_prefix.is_empty() {
1389                            Some(k.clone())
1390                        } else if k.len() > field_prefix.len()
1391                            && k[..field_prefix.len()] == field_prefix[..]
1392                        {
1393                            Some(k[field_prefix.len()..].to_vec())
1394                        } else {
1395                            None
1396                        };
1397                        if let Some(rest) = rest {
1398                            if let Some(fv) = self.field_value(sp.local, &k).cloned() {
1399                                self.set_field_value(place.local, rest, fv);
1400                            }
1401                        }
1402                    }
1403                }
1404            }
1405            // Invalidate the moved-out source's owner-field provenance *after*
1406            // the field propagation, so a later `Owning` check does not treat the
1407            // source as a second owner while the destination kept the pointer.
1408            if let Some(src) = moved_from {
1409                self.invalidate_owner_field(src);
1410            }
1411        } else if !has_deref {
1412            // Field projection (no Deref): update the base local's field values.
1413            let field_indices: Vec<usize> = place
1414                .projection
1415                .iter()
1416                .filter_map(|p| match p.kind() {
1417                    rustc_middle::mir::ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
1418                    _ => None,
1419                })
1420                .collect();
1421            if !field_indices.is_empty() {
1422                // Track cumulative ptr offset for Iter/IterMut before moving value.
1423                let track_iter = field_indices == [0];
1424                let mut write_value = value;
1425                write_value.facts.init = true;
1426                self.set_field_value(place.local, field_indices, write_value);
1427                if track_iter {
1428                    self.track_iter_ptr_update(place.local);
1429                }
1430            }
1431        } else {
1432            // Deref projection (`(*ptr).field = val`): resolve the referent
1433            // local and write the field there, so `&mut self` setters
1434            // (`set_len`, `clear`, …) actually update the referent's
1435            // materialized fields instead of being silently dropped.
1436            //
1437            // A pure `*ptr = val` (no trailing Field) still must NOT overwrite
1438            // the pointer local — writing through a pointer should not reassign
1439            // the pointer variable.
1440            let mut proj = place.projection.iter();
1441            if matches!(
1442                proj.next().map(|p| p.kind()),
1443                Some(rustc_middle::mir::ProjectionElem::Deref)
1444            ) {
1445                let field_indices: Vec<usize> = proj
1446                    .filter_map(|p| match p.kind() {
1447                        rustc_middle::mir::ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
1448                        _ => None,
1449                    })
1450                    .collect();
1451                if !field_indices.is_empty() {
1452                    // `&mut self` (and other reference parameters) materialize
1453                    // their pointee's scalar fields keyed by the *reference*
1454                    // local itself, so a `(*self).field = val` write must land
1455                    // in the reference local's own field values directly.  (This
1456                    // is what makes a struct-invariant re-proof see
1457                    // `self.len += 1`.)
1458                    if self.field_value(place.local, &field_indices).is_some() {
1459                        let is_iter_field = field_indices == [0];
1460                        let mut write_value = value;
1461                        write_value.facts.init = true;
1462                        self.set_field_value(place.local, field_indices, write_value);
1463                        if is_iter_field {
1464                            self.track_iter_ptr_update(place.local);
1465                        }
1466                        return;
1467                    }
1468                    // Otherwise resolve the dereferenced pointer (a
1469                    // reference/reborrow temp) back to the local it points at,
1470                    // matching its address term against the known local
1471                    // addresses.
1472                    let pointed = self.local_value(place.local).cloned();
1473                    if let Some(pointed) = pointed {
1474                        if let Some(referent) = self.find_local_by_address(&pointed.z3_term) {
1475                            let mut write_value = value;
1476                            write_value.facts.init = true;
1477                            self.set_field_value(referent, field_indices, write_value);
1478                        } else if let Some(arg_idx) = place.local.as_usize().checked_sub(1) {
1479                            // Inline frame: the caller's address map is saved
1480                            // away, so resolve through the precomputed
1481                            // `&mut self` referent and defer the write until the
1482                            // caller's frame is restored.
1483                            if let Some(referent) =
1484                                self.inline.arg_referents.get(arg_idx).copied().flatten()
1485                            {
1486                                self.inline.deferred_field_writes
1487                                    .push((referent, field_indices, value));
1488                            }
1489                        }
1490                    }
1491                }
1492            }
1493        }
1494        // For deref projections (`*ptr = val`): do NOT overwrite the base local.
1495        // Writing through a pointer should not reassign the pointer variable.
1496    }
1497
1498    /// Record byte-level values when assigning to a place with projections.
1499    /// This handles patterns like `buf[i] = 0u8` (nul-store) and `arr[i] = val`.
1500    fn record_projected_store(&mut self, place: &Place<'tcx>, value: &VmValue<'z3, 'tcx>) {
1501        // Prefer the value's provenance (pointee alloc) over slots
1502        // (reference alloc) for ref/ptr parameters.
1503        let Some(alloc_id) = self
1504            .local_value(place.local)
1505            .and_then(|v| v.provenance_alloc_id())
1506            .or_else(|| self.current_frame.local_alloc.get(&place.local).copied())
1507        else {
1508            return;
1509        };
1510
1511        let value_ty = value.ty;
1512        let value_size = self.size_of_ty(value_ty) as usize;
1513
1514        let mut byte_offset: usize = 0;
1515        let mut concrete = true;
1516
1517        let base_ty = self.body().local_decls[place.local].ty;
1518        let mut cur_ty = base_ty;
1519
1520        for proj in place.projection.iter() {
1521            match proj.kind() {
1522                rustc_middle::mir::ProjectionElem::Field(field_idx, _) => {
1523                    let off = self.field_offset_in_bytes(cur_ty, field_idx.as_usize()) as usize;
1524                    byte_offset += off;
1525                    if let rustc_middle::ty::TyKind::Adt(adt_def, substs) = cur_ty.kind() {
1526                        if !adt_def.is_enum() {
1527                            let variant = adt_def.non_enum_variant();
1528                            if let Some(field_def) = variant.fields.get(field_idx) {
1529                                cur_ty = crate::helpers::mir_utils::field_ty(
1530                                    self.tcx, field_def, substs,
1531                                );
1532                            }
1533                        }
1534                    }
1535                }
1536                rustc_middle::mir::ProjectionElem::Deref => {
1537                    if let rustc_middle::ty::TyKind::Ref(_, inner, _) = cur_ty.kind() {
1538                        cur_ty = *inner;
1539                    }
1540                }
1541                rustc_middle::mir::ProjectionElem::Index(_local) => {
1542                    concrete = false;
1543                    break;
1544                }
1545                rustc_middle::mir::ProjectionElem::Subslice {
1546                    from,
1547                    to: _,
1548                    from_end: _,
1549                } => {
1550                    byte_offset += from as usize;
1551                }
1552                _ => {}
1553            }
1554        }
1555
1556        if concrete && value_size > 0 {
1557            self.content_mut(alloc_id).facts.initialized = true;
1558
1559            let is_u8_write = matches!(
1560                value_ty.kind(),
1561                rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8)
1562            );
1563
1564            if is_u8_write {
1565                self.record_byte_value(alloc_id, byte_offset, value.z3_term.clone());
1566            }
1567        }
1568    }
1569
1570    /// Track byte-level values for index-based stores (e.g. `buf[i] = 0u8`)
1571    /// that `record_projected_store` skips due to Index projections.
1572    fn record_indexed_store_for_vm(&mut self, place: &Place<'tcx>, value: &VmValue<'z3, 'tcx>) {
1573        let is_u8 = matches!(
1574            value.ty.kind(),
1575            rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8)
1576        );
1577        if !is_u8 {
1578            return;
1579        }
1580        let has_index_with_concrete = place.projection.iter().any(|p| {
1581            if let rustc_middle::mir::ProjectionElem::Index(local) = p {
1582                self.local_value(local)
1583                    .and_then(|v| v.z3_term.simplify().as_u64())
1584                    .is_some()
1585            } else {
1586                false
1587            }
1588        });
1589        if !has_index_with_concrete {
1590            return;
1591        }
1592        if let Some(addr) = self.address_of_place(place) {
1593            if let Some(ref prov) = addr.provenance {
1594                let alloc_id = prov.alloc_id;
1595                let byte_offset = prov.offset.as_u64().map(|v| v as usize).unwrap_or(0);
1596                self.content_mut(alloc_id).facts.initialized = true;
1597                self.record_byte_value(alloc_id, byte_offset, value.z3_term.clone());
1598            }
1599        }
1600    }
1601
1602    /// Inject layout constraints (>= 1) for generic AlignOf/SizeOf constants.
1603    fn inject_layout_constraints(&mut self, operand: &Operand<'tcx>, val: &VmValue<'z3, 'tcx>) {
1604        if let Operand::Constant(constant) = operand {
1605            let text = format!("{:?}", constant.const_);
1606            if crate::helpers::mir_utils::const_int_from_debug(&text).is_none() {
1607                let is_align_or_size = text.starts_with("AlignOf(") || text.starts_with("SizeOf(");
1608                if is_align_or_size {
1609                    let one = Int::from_u64(self.z3_ctx, 1);
1610                    self.constraints.assertions.push(val.z3_term.ge(&one));
1611                }
1612            }
1613        }
1614    }
1615
1616    /// HACK: `slice::align_to_offsets` computes its element split via
1617    /// `const { gcd(size_of::<T>(), size_of::<U>()) }`. The recursive `gcd`
1618    /// const fn cannot be inlined, so the VM would otherwise model the result as
1619    /// a fresh, unconstrained constant and lose the one fact that makes the
1620    /// proof go through: the gcd *divides* both sizes. That divisibility is
1621    /// what turns `us = sizeof_T / gcd` / `ts = sizeof_U / gcd` into exact
1622    /// divisions, giving `us * sizeof_U == ts * sizeof_T` (both the lcm) and
1623    /// hence `us_len * sizeof_U <= len * sizeof_T`. Detect the `gcd` const
1624    /// block and re-establish `gcd`'s key consequences as path conditions. This
1625    /// is a targeted workaround for a missing general property of recursive
1626    /// const fns, not an `align_to`-specific effect.
1627    fn try_emit_gcd_divisibility(&mut self, operand: &Operand<'tcx>, val: &VmValue<'z3, 'tcx>) {
1628        let Operand::Constant(constant) = operand else {
1629            return;
1630        };
1631        let rustc_middle::mir::Const::Unevaluated(uneval, _) = constant.const_ else {
1632            return;
1633        };
1634        // Cheap gate: only a *promoted* const block can be a `gcd` const.
1635        let def_name = self.tcx.def_path_str(uneval.def);
1636        if !def_name.contains("::{constant") {
1637            return;
1638        }
1639        let body = self.tcx.mir_for_ctfe(uneval.def);
1640        let is_gcd = body.basic_blocks.iter().any(|bb| {
1641            if let rustc_middle::mir::TerminatorKind::Call { func, .. } = &bb.terminator().kind {
1642                if let Some(did) = crate::helpers::mir_utils::dep_callee_def_id(func) {
1643                    return self.tcx.def_path_str(did).ends_with("::gcd");
1644                }
1645            }
1646            false
1647        });
1648        if !is_gcd || uneval.args.len() < 2 {
1649            return;
1650        }
1651        let a = self.size_sym(uneval.args.type_at(0));
1652        let b = self.size_sym(uneval.args.type_at(1));
1653        let g = &val.z3_term;
1654        let zero = Int::from_u64(self.z3_ctx, 0);
1655        // `g = gcd(a, b)`:
1656        // 1. g divides both a and b.
1657        self.constraints.assertions.push(a.rem(g)._eq(&zero));
1658        self.constraints.assertions.push(b.rem(g)._eq(&zero));
1659        // 2. the lcm identity `(a / g) * b == (b / g) * a`. Emitting it directly
1660        //    (rather than letting Z3 derive it from the divisibility, which its
1661        //    incomplete nonlinear-integer solver cannot do reliably) is what
1662        //    makes `us * sizeof_U == ts * sizeof_T` — and hence
1663        //    `us_len * sizeof_U <= len * sizeof_T` — provable.
1664        let a_div_g = a.div(g);
1665        let b_div_g = b.div(g);
1666        let lhs = Int::mul(self.z3_ctx, &[&a_div_g, &b]);
1667        let rhs = Int::mul(self.z3_ctx, &[&b_div_g, &a]);
1668        self.constraints.assertions.push(lhs._eq(&rhs));
1669    }
1670
1671    /// The provenance allocation's alignment, when it is non-trivial (≠ 1).
1672    fn alloc_align_of(&self, val: &VmValue<'z3, 'tcx>) -> Option<Int<'z3>> {
1673        val.provenance
1674            .as_ref()
1675            .map(|p| self.alloc(p.alloc_id).align.clone())
1676            .filter(|a| a.simplify().as_u64() != Some(1))
1677    }
1678
1679    /// Evaluate an Rvalue into a VmValue.
1680    fn eval_rvalue(
1681        &mut self,
1682        dest_place: &Place<'tcx>,
1683        rvalue: &Rvalue<'tcx>,
1684    ) -> VmValue<'z3, 'tcx> {
1685        let dest_ty = dest_place.ty(self.body(), self.tcx).ty;
1686
1687        match rvalue {
1688            #[cfg(rapx_rvalue_use_with_retag)]
1689            Rvalue::Use(operand, _retag) => {
1690                let mut val = self.value_of_operand(operand);
1691                self.try_materialize_const_bytes(&mut val, operand);
1692                self.inject_layout_constraints(operand, &val);
1693                self.try_emit_gcd_divisibility(operand, &val);
1694                val
1695            }
1696            #[cfg(not(rapx_rvalue_use_with_retag))]
1697            Rvalue::Use(operand) => {
1698                let mut val = self.value_of_operand(operand);
1699                self.try_materialize_const_bytes(&mut val, operand);
1700                self.inject_layout_constraints(operand, &val);
1701                self.try_emit_gcd_divisibility(operand, &val);
1702                val
1703            }
1704            Rvalue::Ref(_, _borrow_kind, place) => {
1705                if let Some(addr) = self.address_of_place(place) {
1706                    let alloc_align = self.alloc_align_of(&addr);
1707                    // Inherit in_bounds. For &[T] created via Deref of a
1708                    // fat raw ptr (inlined from_raw_parts), set in_bounds
1709                    // like ReturnFreshAllocation does in builtin_models.
1710                    let has_deref = place
1711                        .projection
1712                        .iter()
1713                        .any(|p| matches!(p.kind(), rustc_middle::mir::ProjectionElem::Deref));
1714                    let src_ty = self.body().local_decls[place.local].ty;
1715                    let is_from_raw_parts_like =
1716                        matches!(src_ty.kind(), rustc_middle::ty::TyKind::RawPtr(_, _));
1717                    let is_slice_ref =
1718                        if let rustc_middle::ty::TyKind::Ref(_, inner, _) = dest_ty.kind() {
1719                            matches!(inner.kind(), rustc_middle::ty::TyKind::Slice(_))
1720                        } else {
1721                            false
1722                        };
1723                    let src_in_bounds = if is_slice_ref && is_from_raw_parts_like && has_deref {
1724                        addr.is_pointer()
1725                    } else {
1726                        self.local_value(place.local)
1727                            .is_some_and(|v| v.facts.in_bounds)
1728                    };
1729                    // An empty slice (`&[]` from `align_to`'s `offset > len` /
1730                    // ZST branch) is built as `&*dangling`: the dangling raw
1731                    // pointer (`NonNull::dangling`) is aligned to the *element*
1732                    // type, but its provenance here may reuse `self`'s allocation
1733                    // (whose align is that of `T`).  Record the element type's
1734                    // alignment so `check_align` can discharge it.
1735                    let slice_elem_align = if is_slice_ref && is_from_raw_parts_like && has_deref {
1736                        if let rustc_middle::ty::TyKind::Ref(_, inner, _) = dest_ty.kind() {
1737                            if let rustc_middle::ty::TyKind::Slice(elem) = inner.kind() {
1738                                let a = self.align_sym(*elem);
1739                                if a.simplify().as_u64() != Some(1) {
1740                                    Some(a)
1741                                } else {
1742                                    None
1743                                }
1744                            } else {
1745                                None
1746                            }
1747                        } else {
1748                            None
1749                        }
1750                    } else {
1751                        None
1752                    };
1753                    let val = VmValue {
1754                        z3_term: addr.z3_term,
1755                        ty: dest_ty,
1756                        provenance: addr.provenance,
1757                        facts: ValueFacts {
1758                            non_null: true,
1759                            init: true,
1760                            in_bounds: src_in_bounds,
1761                            align_n: slice_elem_align.or(alloc_align),
1762                        },
1763                        source: ValueSource::None,
1764                    };
1765                    self.propagate_byte_values_to_ref(place, &val);
1766                    self.propagate_field_values_to_ref(place, dest_place.local);
1767                    // Expose the freshly-created reference's provenance before
1768                    // asserting the pointee struct's `#[rapx::invariant]`s: the
1769                    // invariant predicates (`ValidNum(len <= CAPACITY)` on
1770                    // `&LeafNode`) resolve their field places through the
1771                    // pointee allocation, which needs `dest_place`'s provenance
1772                    // to be live (`exec_assign` only stores `val` afterwards).
1773                    self.set_local(dest_place.local, val.clone());
1774                    self.assert_pointee_struct_invariants(dest_ty, dest_place.local);
1775                    val
1776                } else {
1777                    let term = self.fresh_int("ref_addr");
1778                    VmValue {
1779                        z3_term: term,
1780                        ty: dest_ty,
1781                        provenance: None,
1782                        facts: ValueFacts {
1783                            non_null: true,
1784                            init: true,
1785                            ..Default::default()
1786                        },
1787                        source: ValueSource::None,
1788                    }
1789                }
1790            }
1791            Rvalue::RawPtr(_, place) => {
1792                if let Some(addr) = self.address_of_place(place) {
1793                    let alloc_align = self.alloc_align_of(&addr);
1794                    let source_in_bounds = self
1795                        .local_value(place.local)
1796                        .is_some_and(|v| v.facts.in_bounds);
1797                    VmValue {
1798                        z3_term: addr.z3_term,
1799                        ty: dest_ty,
1800                        provenance: addr.provenance,
1801                        facts: ValueFacts {
1802                            non_null: true,
1803                            in_bounds: source_in_bounds,
1804                            align_n: alloc_align,
1805                            ..Default::default()
1806                        },
1807                        source: ValueSource::None,
1808                    }
1809                } else {
1810                    let term = self.fresh_int("rawptr_addr");
1811                    VmValue {
1812                        z3_term: term,
1813                        ty: dest_ty,
1814                        provenance: None,
1815                        facts: ValueFacts {
1816                            non_null: true,
1817                            ..Default::default()
1818                        },
1819                        source: ValueSource::None,
1820                    }
1821                }
1822            }
1823            Rvalue::BinaryOp(op, pair) => {
1824                let (lhs_op, rhs_op) = &**pair;
1825                let lhs = self.value_of_operand(lhs_op);
1826                let rhs = self.value_of_operand(rhs_op);
1827                let term = self.eval_binary_op(*op, &lhs.z3_term, &rhs.z3_term);
1828                let provenance = self.provenance_for_binary_op(*op, &lhs, &rhs);
1829                let invariants = self.facts_for_binary_op(*op, &lhs, &rhs, &provenance);
1830                let lhs_pk = crate::helpers::mir_utils::operand_place(lhs_op);
1831                let rhs_pk = crate::helpers::mir_utils::operand_place(rhs_op);
1832                // Carry the direct boolean condition alongside the ite-encoded
1833                // result so `switchInt`/`Assert` can record a precise path
1834                // condition (e.g. `offset <= len - 16`) instead of
1835                // `ite(cond, 1, 0) != 0`, which the SMT solver often fails to
1836                // unfold.
1837                let cmp_cond = self
1838                    .iter_ptr_comparison(*op, &lhs, &rhs)
1839                    .or_else(|| match *op {
1840                        BinOp::Le => Some(lhs.z3_term.le(&rhs.z3_term)),
1841                        BinOp::Lt => Some(lhs.z3_term.lt(&rhs.z3_term)),
1842                        BinOp::Ge => Some(lhs.z3_term.ge(&rhs.z3_term)),
1843                        BinOp::Gt => Some(lhs.z3_term.gt(&rhs.z3_term)),
1844                        BinOp::Eq => Some(lhs.z3_term._eq(&rhs.z3_term)),
1845                        BinOp::Ne => Some(lhs.z3_term._eq(&rhs.z3_term).not()),
1846                        _ => None,
1847                    });
1848                // Add Euclidean division identity for Div and Rem:
1849                //   lhs == (lhs/rhs)*rhs + lhs%rhs  ∧  lhs%rhs >= 0
1850                // Also add (lhs/rhs)*rhs <= lhs directly for Div for robustness.
1851                // This lets later checks prove (x/N)*N <= x and x%N >= 0.
1852                // IMPORTANT: use `term` (returned by eval_binary_op) as the
1853                // quotient, NOT a separate `lhs.div(&rhs)` call, so that the
1854                // axiom constrains the SAME Z3 term used in subsequent ops.
1855                if matches!(*op, BinOp::Div | BinOp::Rem) {
1856                    let quot = if matches!(*op, BinOp::Div) {
1857                        &term
1858                    } else {
1859                        &lhs.z3_term.div(&rhs.z3_term)
1860                    };
1861                    let rem = lhs.z3_term.rem(&rhs.z3_term);
1862                    let mul_term = Int::mul(self.z3_ctx, &[quot, &rhs.z3_term]);
1863                    let sum_term = Int::add(self.z3_ctx, &[&mul_term, &rem]);
1864                    self.constraints.assertions.push(lhs.z3_term._eq(&sum_term));
1865                    let zero = Int::from_u64(self.z3_ctx, 0);
1866                    self.constraints.assertions.push(rem.ge(&zero));
1867                    // Remainder and quotient bounds help prove length constraints
1868                    // involving % and / in the SMT solver.
1869                    if rhs.z3_term.as_u64().is_none_or(|r| r >= 1) {
1870                        self.constraints.assertions.push(rem.lt(&rhs.z3_term));
1871                    }
1872                    self.constraints.assertions.push(rem.le(&lhs.z3_term));
1873                    self.constraints.assertions.push(quot.ge(&zero));
1874                    // Direct inequality: (lhs/rhs)*rhs <= lhs
1875                    self.constraints.assertions.push(mul_term.le(&lhs.z3_term));
1876                    // Quotient strict bound: for rhs >= 2 and lhs >= 2,
1877                    // quot + 1 <= lhs (hence quot < lhs). E.g. X/2 < X for X>1.
1878                    if rhs.z3_term.as_u64().is_some_and(|r| r >= 2) {
1879                        let one = Int::from_u64(self.z3_ctx, 1);
1880                        let qp1 = Int::add(self.z3_ctx, &[quot, &one]);
1881                        // qp1 <= lhs is equivalent to quot < lhs for integers
1882                        self.constraints.assertions.push(qp1.le(&lhs.z3_term));
1883                    } else {
1884                        // For rhs >= 1: quot <= lhs
1885                        if rhs.z3_term.as_u64().is_some_and(|r| r >= 1) {
1886                            self.constraints.assertions.push(quot.le(&lhs.z3_term));
1887                        }
1888                    }
1889                }
1890                // For tuple-returning binary ops (AddWithOverflow, MulWithOverflow),
1891                // populate the field values so that .0 (result) and .1 (overflow
1892                // flag) are properly tracked. Without this, field access falls
1893                // through to cloning the base term, mixing the arithmetic result
1894                // with the boolean overflow flag and corrupting path conditions.
1895                if let rustc_middle::ty::TyKind::Tuple(fields) = dest_ty.kind() {
1896                    if fields.len() == 2 {
1897                        let result_val = VmValue {
1898                            z3_term: term.clone(),
1899                            ty: fields[0],
1900                            provenance: provenance.clone(),
1901                            facts: invariants.clone(),
1902                            source: ValueSource::None,
1903                        };
1904                        self.set_field_value(dest_place.local, vec![0], result_val);
1905                        let overflow_term = self.fresh_int("overflow_flag");
1906                        let overflow_val = VmValue::new(overflow_term, fields[1]);
1907                        self.set_field_value(dest_place.local, vec![1], overflow_val);
1908                    }
1909                }
1910                VmValue {
1911                    z3_term: term,
1912                    ty: dest_ty,
1913                    provenance,
1914                    facts: invariants,
1915                    source: match cmp_cond {
1916                        Some(cond) => ValueSource::Comparison {
1917                            lhs: lhs_pk,
1918                            rhs: rhs_pk,
1919                            op: *op,
1920                            cond,
1921                        },
1922                        None => ValueSource::BinaryOp {
1923                            lhs: lhs_pk,
1924                            rhs: rhs_pk,
1925                            op: *op,
1926                        },
1927                    },
1928                }
1929            }
1930            Rvalue::UnaryOp(op, operand) => {
1931                let val = self.value_of_operand(operand);
1932                let is_bool = matches!(val.ty.kind(), rustc_middle::ty::TyKind::Bool);
1933                let term = if matches!(op, UnOp::PtrMetadata) {
1934                    // `PtrMetadata` on a `&[T]` gives the slice length, which is
1935                    // the allocation size divided by the element size. Reuse the
1936                    // same symbolic term as the allocation size so downstream
1937                    // InBound checks (`offset <= len`) agree with the `len` used
1938                    // in loop guards (`offset <= len - 16`).
1939                    self.slice_len_from_value(&val)
1940                        .unwrap_or_else(|| self.fresh_int("ptr_metadata"))
1941                } else {
1942                    self.eval_unary_op(*op, &val.z3_term, is_bool)
1943                };
1944                VmValue {
1945                    z3_term: term,
1946                    ty: dest_ty,
1947                    provenance: val.provenance,
1948                    facts: val.facts,
1949                    source: ValueSource::None,
1950                }
1951            }
1952            Rvalue::Cast(_kind, operand, cast_ty) => {
1953                let src_val = self.value_of_operand(operand);
1954                let src_ty = src_val.ty;
1955                // A raw-pointer cast to a *different* ADT pointee reinterprets the
1956                // allocation's fields (e.g. `NonNull<LeafNode>::as_ptr() as *mut
1957                // InternalNode` in `NodeRef::as_internal_ptr`).  Lazily materialize
1958                // the target ADT's field view (keyed by type) so a later
1959                // `(*cast_ptr).field` resolves to the right field instead of the
1960                // original view's field at the same index.
1961                if let rustc_middle::ty::TyKind::RawPtr(target_ty, _) = cast_ty.kind() {
1962                    if let rustc_middle::ty::TyKind::Adt(adt_def, _) = target_ty.kind() {
1963                        if adt_def.is_struct() {
1964                            if let Some(alloc_id) = src_val.provenance_alloc_id() {
1965                                self.decompose_pointee_fields(
1966                                    alloc_id,
1967                                    Vec::new(),
1968                                    *target_ty,
1969                                    *target_ty,
1970                                    0,
1971                                    0,
1972                                    0,
1973                                );
1974                            }
1975                        }
1976                    }
1977                }
1978                // Transmute-like casts of single-field newtypes (e.g.
1979                // NonZero::get's `_0 = copy _1 as T`) yield the underlying
1980                // field value, not the wrapper's own term.
1981                let term = crate::helpers::mir_utils::extract_local(operand)
1982                    .and_then(|l| self.field_value(l, &[0]).map(|v| v.z3_term.clone()))
1983                    .unwrap_or(src_val.z3_term);
1984                // A pointer→integer cast (`ptr as usize`) yields the (always
1985                // non-negative) address. Record this as a *path condition* so
1986                // downstream pointer arithmetic (e.g. `align_up` in a free-list
1987                // allocator) can discharge `NonNull` on the derived pointer —
1988                // `check_non_null`'s SMT query only sees path conditions, not
1989                // the `term >= 0` constraint `assert_value_constraints` adds.
1990                let src_is_ptr = matches!(src_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..)
1991                    | rustc_middle::ty::TyKind::Ref(..));
1992                let dest_is_int = matches!(
1993                    cast_ty.kind(),
1994                    rustc_middle::ty::TyKind::Uint(_) | rustc_middle::ty::TyKind::Int(_)
1995                );
1996                if src_is_ptr && dest_is_int {
1997                    let zero = Int::from_u64(self.z3_ctx, 0);
1998                    self.constraints.assertions.push(term.ge(&zero));
1999                    if src_val.facts.non_null {
2000                        self.constraints.assertions.push(term._eq(&zero).not());
2001                    }
2002                }
2003                VmValue {
2004                    z3_term: term,
2005                    ty: *cast_ty,
2006                    provenance: src_val.provenance,
2007                    facts: ValueFacts {
2008                        non_null: src_val.facts.non_null,
2009                        init: src_val.facts.init,
2010                        in_bounds: src_val.facts.in_bounds,
2011                        align_n: if crate::helpers::mir_utils::pointee_ty(src_ty)
2012                            .is_some_and(|t| t.is_unit())
2013                        {
2014                            None
2015                        } else {
2016                            src_val.facts.align_n
2017                        },
2018                    },
2019                    source: src_val.source.field_offset_only(),
2020                }
2021            }
2022            Rvalue::Aggregate(_kind, operands) => {
2023                // For an enum aggregate, remember whether this is the
2024                // data-carrying variant of `Option`/`Result` (`Some`/`Ok`), so
2025                // the nested-field flattening below only fires on paths that
2026                // actually carry a `Self` value.
2027                let data_variant = match &**_kind {
2028                    rustc_middle::mir::AggregateKind::Adt(did, variant_idx, ..) => {
2029                        if self.tcx.is_diagnostic_item(rustc_span::sym::Result, *did) {
2030                            Some(variant_idx.as_usize() == 0)
2031                        } else if self.tcx.is_diagnostic_item(rustc_span::sym::Option, *did) {
2032                            Some(variant_idx.as_usize() == 1)
2033                        } else {
2034                            None
2035                        }
2036                    }
2037                    _ => None,
2038                };
2039                // `NonNull::new_unchecked(ptr)` / `NonNull::from(&T)` construct a
2040                // repr(transparent) single-field newtype whose value *is* the
2041                // underlying pointer.  Model the wrapper as the pointer field
2042                // itself (term + provenance + invariants) so downstream checks
2043                // like `NonNull(node)` / `Align(node, T)` / `Allocated(node, ..)`
2044                // can discharge against the real pointer instead of a fresh
2045                // unconstrained `aggregate` symbol.
2046                if operands.len() == 1 && self.find_nn_pointee(dest_ty).is_some() {
2047                    let field_val = self.value_of_operand(operands.iter().next().unwrap());
2048                    let dest_local = dest_place.local;
2049                    self.set_field_value(dest_local, vec![0], field_val.clone());
2050                    if let Some(alloc_id) = self.current_frame.local_alloc.get(&dest_local).copied() {
2051                        self.content_mut(alloc_id).facts.initialized = true;
2052                    }
2053                    return VmValue {
2054                        z3_term: field_val.z3_term,
2055                        ty: dest_ty,
2056                        provenance: field_val.provenance,
2057                        facts: field_val.facts,
2058                        source: ValueSource::None,
2059                    };
2060                }
2061                let term = self.fresh_int("aggregate");
2062                let dest_local = dest_place.local;
2063                // Prefer the pointee allocation (through a `*ptr` deref) over the
2064                // base local's own stack slot, so byte values land on the real
2065                // buffer (e.g. `((*_8).1).0 = [a, b, 0]` writes into the box).
2066                let dest_alloc_id = self
2067                    .local_value(dest_local)
2068                    .and_then(|v| v.provenance_alloc_id())
2069                    .or_else(|| self.current_frame.local_alloc.get(&dest_local).copied());
2070                let is_byte_array = crate::helpers::mir_utils::is_u8_array_or_slice(dest_ty);
2071                let field_types: Vec<_> = self.aggregate_field_tys(dest_ty);
2072                let mut byte_offset = 0usize;
2073                for (i, operand) in operands.iter().enumerate() {
2074                    let mut field_val = self.value_of_operand(operand);
2075                    if let Some(field_ty) = field_types.get(i) {
2076                        let src_is_ref =
2077                            matches!(field_val.ty.kind(), rustc_middle::ty::TyKind::Ref(..));
2078                        let dst_is_raw =
2079                            matches!(field_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..));
2080                        if dst_is_raw && (src_is_ref || field_val.facts.non_null) {
2081                            field_val.facts.in_bounds = true;
2082                            field_val.ty = *field_ty;
2083                        }
2084                    }
2085                    let field_sz = field_types
2086                        .get(i)
2087                        .copied()
2088                        .map(|ty| self.size_of_ty(ty) as usize)
2089                        .unwrap_or(1);
2090                    let field_term = field_val.z3_term.clone();
2091                    self.set_field_value(dest_local, vec![i], field_val);
2092                    // Flatten a nested aggregate: if the operand is a local whose
2093                    // own fields are tracked (e.g. `_0 = Result::Ok(_24)` where
2094                    // `_24 = RawVecInner { ptr: _25, .. }`), expose the nested
2095                    // fields under the destination's field path so a contract
2096                    // place like `Return.Field(0).Field(0)` (the `Ok` variant's
2097                    // data, then the struct field) can resolve to `_25`.
2098                    // Only flatten the data-carrying variant (`Ok`/`Some`); on
2099                    // `Err`/`None` paths there is no `Self` and the nested place
2100                    // should resolve to `Unknown` instead.
2101                    if data_variant != Some(false) {
2102                        if let Some(op_place) = operand.place() {
2103                            if op_place.projection.is_empty() {
2104                                let nested: Vec<(Vec<usize>, VmValue<'z3, 'tcx>)> = self
2105                                    .field_paths(op_place.local)
2106                                    .into_iter()
2107                                    .filter_map(|p| {
2108                                        self.field_value(op_place.local, &p)
2109                                            .cloned()
2110                                            .map(|v| (p, v))
2111                                    })
2112                                    .collect();
2113                                for (nested_path, nested_val) in nested {
2114                                    let mut full = vec![i];
2115                                    full.extend_from_slice(&nested_path);
2116                                    self.set_field_value(dest_local, full, nested_val);
2117                                }
2118                            }
2119                        }
2120                    }
2121                    if let Some(alloc_id) = dest_alloc_id {
2122                        self.content_mut(alloc_id).facts.initialized = true;
2123                        if is_byte_array && field_sz == 1 {
2124                            self.record_byte_value(alloc_id, byte_offset, field_term.clone());
2125                        }
2126                        // Record known_nul / known_non_nul from constant operands
2127                        if let Some(int_val) = crate::helpers::mir_utils::operand_const_u64(operand)
2128                        {
2129                            if field_sz == 1 {
2130                                if int_val == 0 {
2131                                    if !is_byte_array {
2132                                        self.record_byte_value(
2133                                            alloc_id,
2134                                            byte_offset,
2135                                            Int::from_u64(self.z3_ctx, 0),
2136                                        );
2137                                    }
2138                                } else {
2139                                    if !is_byte_array {
2140                                        self.record_byte_value(
2141                                            alloc_id,
2142                                            byte_offset,
2143                                            Int::from_u64(self.z3_ctx, int_val),
2144                                        );
2145                                    }
2146                                }
2147                            }
2148                            // For multi-byte fields: track each constituent byte
2149                            for b in 0..field_sz.min(8) {
2150                                let byte_off = byte_offset + b;
2151                                let byte_val = (int_val >> (b * 8)) & 0xFF;
2152                                self.record_byte_value(
2153                                    alloc_id,
2154                                    byte_off,
2155                                    Int::from_u64(self.z3_ctx, byte_val),
2156                                );
2157                            }
2158                        }
2159                    }
2160                    byte_offset += field_sz;
2161                }
2162                // Fat-pointer construction (inlined `from_raw_parts` /
2163                // `slice_from_raw_parts_mut`): the result's address and
2164                // provenance are those of the data pointer (field 0), so
2165                // downstream `Allocated`/`Owning` checks on the slice resolve
2166                // against the real buffer instead of a fresh `aggregate` symbol.
2167                let is_slice_ptr = matches!(dest_ty.kind(),
2168                    rustc_middle::ty::TyKind::RawPtr(inner, _) | rustc_middle::ty::TyKind::Ref(_, inner, _)
2169                        if matches!(inner.kind(), rustc_middle::ty::TyKind::Slice(_)));
2170                let (result_term, result_prov) = if is_slice_ptr {
2171                    match self.field_value(dest_local, &[0]).cloned() {
2172                        Some(data) => (data.z3_term.clone(), data.provenance.clone()),
2173                        None => (term.clone(), None),
2174                    }
2175                } else {
2176                    // `Box<T>` (an aggregate `Box(Unique<T>, A)`) takes its
2177                    // provenance from the inner `Unique<T>.pointer` (`NonNull<T>`
2178                    // at path `[0, 0]`), which the field-flattening above just
2179                    // recorded.  Without this, `Box::assume_init`'s rebuild
2180                    // (`Box(Unique::new_unchecked(raw), alloc)`) drops the heap
2181                    // provenance and a later `Box::as_ptr` field read fails
2182                    // `NonNull` (rustc 1.95 lowers `as_ptr` to that field read).
2183                    let box_prov = if let rustc_middle::ty::TyKind::Adt(adt, _) = dest_ty.kind() {
2184                        if api_classify::is_std_box(adt.did()) {
2185                            self.container_ptr_field(dest_ty)
2186                                .and_then(|(path, _)| self.field_value(dest_local, &path))
2187                                .and_then(|v| v.provenance.clone())
2188                        } else {
2189                            None
2190                        }
2191                    } else {
2192                        None
2193                    };
2194                    (term.clone(), box_prov)
2195                };
2196                VmValue {
2197                    z3_term: result_term,
2198                    ty: dest_ty,
2199                    provenance: result_prov,
2200                    facts: ValueFacts::default(),
2201                    source: ValueSource::None,
2202                }
2203            }
2204            Rvalue::Discriminant(place) => {
2205                // If the ADT's variant is known symbolically (e.g. `Iterator::next`
2206                // returns `Some` iff the iterator was non-empty), reuse that term
2207                // so `switchInt(discriminant)` branches stay tied to the real
2208                // condition instead of a fresh unconstrained symbol.
2209                let place_val = self
2210                    .value_of_place(place)
2211                    .or_else(|| self.local_value(place.local).cloned());
2212                let term = place_val
2213                    .as_ref()
2214                    .and_then(|v| v.discriminant().cloned())
2215                    .unwrap_or_else(|| self.fresh_int("discriminant"));
2216                if place_val
2217                    .as_ref()
2218                    .map(|v| v.discriminant().is_some())
2219                    .unwrap_or(false)
2220                {
2221                    self.path_facts.saw_next_discriminant = true;
2222                }
2223                // For Ordering (repr i8, values: Less=-1 Equal=0 Greater=1),
2224                // the discriminant index equals the repr value + 1.
2225                // Connect the fresh discriminant term to the ADT value so
2226                // that SwitchInt constraints propagate to the stored value.
2227                if let Some(ref pv) = place_val {
2228                    if let rustc_middle::ty::TyKind::Adt(adt_def, _) = pv.ty.kind() {
2229                        if api_classify::is_std_ordering(adt_def.did()) && adt_def.is_enum() {
2230                            let one = Int::from_u64(self.z3_ctx, 1);
2231                            let discr_minus_one = Int::sub(self.z3_ctx, &[&term, &one]);
2232                            self.constraints.assertions.push(pv.z3_term._eq(&discr_minus_one));
2233                            // Also bound the discriminant to {0, 1, 2}
2234                            let zero = Int::from_u64(self.z3_ctx, 0);
2235                            let two = Int::from_u64(self.z3_ctx, 2);
2236                            self.constraints.assertions.push(term.ge(&zero));
2237                            self.constraints.assertions.push(term.le(&two));
2238                        }
2239                    }
2240                }
2241                VmValue::new(term, dest_ty)
2242            }
2243            #[cfg(not(rapx_ge_99))]
2244            Rvalue::ShallowInitBox(operand, _ty) => {
2245                let val = self.value_of_operand(operand);
2246                VmValue {
2247                    z3_term: val.z3_term,
2248                    ty: dest_ty,
2249                    provenance: val.provenance,
2250                    facts: val.facts,
2251                    source: ValueSource::None,
2252                }
2253            }
2254            Rvalue::CopyForDeref(place) => {
2255                if let Some(val) = self.value_of_place(place) {
2256                    val
2257                } else {
2258                    let term = self.fresh_int("copy_for_deref");
2259                    VmValue::new(term, dest_ty)
2260                }
2261            }
2262            Rvalue::Repeat(..) => {
2263                let term = self.fresh_int("repeat");
2264                VmValue::new(term, dest_ty)
2265            }
2266            Rvalue::ThreadLocalRef(_) => {
2267                let term = self.fresh_int("thread_local");
2268                VmValue::new(term, dest_ty)
2269            }
2270            #[cfg(not(rapx_ge_95))]
2271            Rvalue::NullaryOp(_op) => {
2272                let term = self.fresh_int("nullary");
2273                let op_debug = format!("{:?}", _op);
2274                let is_align_of = op_debug.contains("AlignOf") || op_debug.contains("min_align_of");
2275                let is_size_of = op_debug.contains("SizeOf");
2276                if is_align_of || is_size_of {
2277                    let one = Int::from_u64(self.z3_ctx, 1);
2278                    self.constraints.assertions.push(term.ge(&one));
2279                }
2280                VmValue::new(term, dest_ty)
2281            }
2282            Rvalue::WrapUnsafeBinder(_operand, _ty) => {
2283                let term = self.fresh_int("wrap_unsafe_binder");
2284                VmValue::new(term, dest_ty)
2285            }
2286            #[cfg(rapx_rvalue_has_reborrow)]
2287            Rvalue::Reborrow(_ty, _mutability, _place) => {
2288                let term = self.fresh_int("reborrow");
2289                VmValue {
2290                    z3_term: term,
2291                    ty: dest_ty,
2292                    provenance: None,
2293                    facts: ValueFacts {
2294                        non_null: true,
2295                        ..Default::default()
2296                    },
2297                    source: ValueSource::None,
2298                }
2299            }
2300        }
2301    }
2302
2303    // ── Arithmetic ────────────────────────────────────────────────
2304
2305    /// Encode a boolean condition as the integer `1`/`0`.
2306    fn bool_as_int(&self, cond: &Bool<'z3>) -> Int<'z3> {
2307        cond.ite(&Int::from_u64(self.z3_ctx, 1), &Int::from_u64(self.z3_ctx, 0))
2308    }
2309
2310    /// Negate a Z3 integer (`0 - val`).
2311    fn negate(&self, val: &Int<'z3>) -> Int<'z3> {
2312        let zero = Int::from_u64(self.z3_ctx, 0);
2313        Int::sub(self.z3_ctx, &[&zero, val])
2314    }
2315
2316    fn eval_binary_op(&mut self, op: BinOp, lhs: &Int<'z3>, rhs: &Int<'z3>) -> Int<'z3> {
2317        match op {
2318            BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked => {
2319                Int::add(self.z3_ctx, &[lhs, rhs])
2320            }
2321            BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked => {
2322                Int::sub(self.z3_ctx, &[lhs, rhs])
2323            }
2324            BinOp::Mul | BinOp::MulWithOverflow | BinOp::MulUnchecked => {
2325                // `us_len = (len / ts) * us`: a non-exact division result times
2326                // an exact gcd quotient.  Model the product as a fresh symbol
2327                // and emit its byte bound `us_len * sizeof_U <= len * sizeof_T`
2328                // directly, so the later `from_raw_parts_mut` InBound check
2329                // (`us_len * sizeof_U`) stays degree-2 rather than the
2330                // degree-3 `div * us * sizeof_U` that Z3's NIA cannot rewrite.
2331                if let Some((div_lhs, div_rhs)) = self.constraints.term_caches.div_roots.get(lhs)
2332                {
2333                    if let Some(us_dividend) = self.constraints.term_caches.exact_div_roots.get(rhs)
2334                    {
2335                        if let Some(ts_dividend) = self.constraints.term_caches.exact_div_roots.get(div_rhs)
2336                        {
2337                            let us_len = self.fresh_int("us_len");
2338                            self.constraints
2339                                .assertions
2340                                .push(us_len._eq(&Int::mul(self.z3_ctx, &[lhs, rhs])));
2341                            let byte_len = Int::mul(self.z3_ctx, &[&us_len, ts_dividend]);
2342                            let byte_bound = Int::mul(self.z3_ctx, &[div_lhs, us_dividend]);
2343                            self.constraints.assertions.push(byte_len.le(&byte_bound));
2344                            return us_len;
2345                        }
2346                    }
2347                }
2348                Int::mul(self.z3_ctx, &[lhs, rhs])
2349            }
2350            BinOp::Div => {
2351                // An *exact* division (`lhs % rhs == 0` known) is represented as
2352                // a fresh variable rather than the `lhs / rhs` term. The caller
2353                // still emits the Euclidean identity `lhs == quot*rhs + rem`
2354                // (with `rem == 0` from the divisibility), so the fresh variable
2355                // satisfies `quot*rhs == lhs` linearly. This avoids the *nested*
2356                // division (`x / (b/gcd)`) that Z3's nonlinear solver can't
2357                // handle, leaving only degree-2 products that `nlsat` handles
2358                // far more reliably. General (any exact division), not a
2359                // per-function effect.
2360                let zero = Int::from_u64(self.z3_ctx, 0);
2361                let exact = self
2362                    .constraints
2363                    .assertions
2364                    .iter()
2365                    .any(|c| *c == lhs.rem(rhs)._eq(&zero));
2366                if exact {
2367                    let q = self.fresh_int("exact_div");
2368                    self.constraints.term_caches.exact_div_roots.insert(q.clone(), lhs.clone());
2369                    q
2370                } else {
2371                    // A *non-exact* division is likewise a fresh variable (the
2372                    // caller's Euclidean identity `lhs == q*rhs + rem` links it
2373                    // back), so a later `(len/ts) * us` product stays a degree-2
2374                    // product of two symbols instead of a `div` term that Z3's
2375                    // nonlinear solver cannot combine with a multiplier.
2376                    let q = self.fresh_int("div");
2377                    self.constraints.term_caches.div_roots.insert(q.clone(), (lhs.clone(), rhs.clone()));
2378                    q
2379                }
2380            }
2381            BinOp::Rem => lhs.rem(rhs),
2382            BinOp::Eq => self.bool_as_int(&lhs._eq(rhs)),
2383            BinOp::Ne => self.bool_as_int(&lhs._eq(rhs).not()),
2384            BinOp::Lt => self.bool_as_int(&lhs.lt(rhs)),
2385            BinOp::Le => self.bool_as_int(&lhs.le(rhs)),
2386            BinOp::Gt => self.bool_as_int(&lhs.gt(rhs)),
2387            BinOp::Ge => self.bool_as_int(&lhs.ge(rhs)),
2388            BinOp::Offset => Int::add(self.z3_ctx, &[lhs, rhs]),
2389            BinOp::BitAnd => {
2390                let result = self.fresh_int("binop");
2391                // BitAnd only clears bits, so it never increases a non-negative
2392                // value: result <= lhs.
2393                self.constraints.assertions.push(result.le(lhs));
2394                // When the mask (rhs) is a non-negative constant, the result is
2395                // also bounded by it: `x & c <= c` (e.g. `rhs & 31 <= 31`).
2396                // This lets `(rhs & (BITS - 1)) < BITS` be discharged. The
2397                // mask may be a folded expression (`SubWithOverflow(BITS, 1)`),
2398                // so `simplify()` is used to recover its constant value.
2399                if rhs.simplify().as_u64().is_some() {
2400                    self.constraints.assertions.push(result.le(rhs));
2401                }
2402                if self.constraints.term_caches.not_mask_terms.contains(rhs) {
2403                    // rhs is a two's-complement mask `!(align-1) == -align`,
2404                    // so `align = -rhs`. The result of `x & !(align-1)` is
2405                    // `x` rounded down to a multiple of `align` (i.e. align_up
2406                    // of the pre-incremented value).
2407                    let zero = Int::from_u64(self.z3_ctx, 0);
2408                    let align = Int::sub(self.z3_ctx, &[&zero, rhs]);
2409                    self.constraints.assertions.push(result.rem(&align)._eq(&zero));
2410                    let one = Int::from_u64(self.z3_ctx, 1);
2411                    let addr = Int::add(self.z3_ctx, &[lhs, rhs, &one]);
2412                    self.constraints.assertions.push(result.ge(&addr));
2413                }
2414                result
2415            }
2416            BinOp::BitOr => {
2417                let result = self.fresh_int("binop");
2418                let zero = Int::from_u64(self.z3_ctx, 0);
2419                // Bitwise OR only sets bits, so the result is non-zero whenever
2420                // either operand is non-zero.  Emit an implication (rather than
2421                // `result >= lhs`, which is only valid for non-negative values)
2422                // so `NonZero` bit-or methods discharge their `!= 0` obligation
2423                // for both signed and unsigned instantiations.
2424                self.constraints.assertions
2425                    .push(lhs._eq(&zero).not().implies(&result._eq(&zero).not()));
2426                self.constraints.assertions
2427                    .push(rhs._eq(&zero).not().implies(&result._eq(&zero).not()));
2428                result
2429            }
2430            _ => self.fresh_int("binop"),
2431        }
2432    }
2433
2434    fn eval_unary_op(&mut self, op: UnOp, val: &Int<'z3>, is_bool: bool) -> Int<'z3> {
2435        match op {
2436            UnOp::Not => {
2437                if is_bool {
2438                    let zero = Int::from_u64(self.z3_ctx, 0);
2439                    let one = Int::from_u64(self.z3_ctx, 1);
2440                    val._eq(&zero).ite(&one, &zero)
2441                } else {
2442                    // Two's-complement bitwise NOT: !x == -x - 1.
2443                    let one = Int::from_u64(self.z3_ctx, 1);
2444                    let result = Int::sub(self.z3_ctx, &[&self.negate(val), &one]);
2445                    self.constraints.term_caches.not_mask_terms.insert(result.clone());
2446                    result
2447                }
2448            }
2449            UnOp::Neg => self.negate(val),
2450            UnOp::PtrMetadata => self.fresh_int("ptr_metadata"),
2451        }
2452    }
2453
2454    /// The slice/array element count for an allocation: the materialized
2455    /// `slice_len`, or `size / elem_size` as a fallback for allocations created
2456    /// before materialization (e.g. some call effects).  Uses the symbolic
2457    /// element size (`size_sym_read`) so `len = (len·S) / S` cancels to `len`
2458    /// for a generic element type — mirroring `set_len_from_alloc`.  Returns
2459    /// `None` when the element type is unknown.
2460    pub(crate) fn slice_len_of_alloc(&self, alloc_id: AllocId) -> Option<Int<'z3>> {
2461        let alloc = self.alloc(alloc_id);
2462        if let Some(len) = alloc.slice_len() {
2463            return Some(len.clone());
2464        }
2465        let elem_ty = alloc.element_ty.as_ty()?;
2466        let elem_term = self.size_sym_read(elem_ty);
2467        if elem_term.simplify().as_u64() == Some(1) {
2468            return Some(alloc.size.clone());
2469        }
2470        Some(alloc.size.div(&elem_term))
2471    }
2472
2473    /// The slice length of a `&[T]` / `&mut [T]` value, resolved through its
2474    /// provenance allocation (see [`Self::slice_len_of_alloc`]).
2475    pub(crate) fn slice_len_from_value(&self, val: &VmValue<'z3, 'tcx>) -> Option<Int<'z3>> {
2476        self.slice_len_of_alloc(val.provenance_alloc_id()?)
2477    }
2478
2479    /// Resolve `x.len()` for a pointer whose pointee ADT carries a `len` field
2480    /// (e.g. `NodeRef<LeafNode>`: `len()` reads `(*ptr).len`).  Returns the
2481    /// pointee's `len` field term, or `None` when the pointee has no such field.
2482    pub(crate) fn try_adt_len_field(&self, val: &VmValue<'z3, 'tcx>) -> Option<Int<'z3>> {
2483        let alloc_id = val.provenance_alloc_id()?;
2484        let elem_ty = self.alloc(alloc_id).element_ty.as_ty()?;
2485        self.try_adt_len_field_at(alloc_id, elem_ty, elem_ty, &[])
2486    }
2487
2488    /// Resolve a `len` field on `ty`, recursing into ADT sub-fields when there is
2489    /// no direct `len` (e.g. `String { vec: Vec { ptr, len, cap } }` resolves
2490    /// `String.len()` to `vec.len`).  `root_ty` stays fixed as the allocation's
2491    /// element type, which is how `decompose_pointee_fields` keys `MemoryContent::values`.
2492    fn try_adt_len_field_at(
2493        &self,
2494        alloc_id: AllocId,
2495        ty: Ty<'tcx>,
2496        root_ty: Ty<'tcx>,
2497        prefix: &[usize],
2498    ) -> Option<Int<'z3>> {
2499        let rustc_middle::ty::TyKind::Adt(adt_def, substs) = ty.kind() else {
2500            return None;
2501        };
2502        if !adt_def.is_struct() {
2503            return None;
2504        }
2505        let variant = adt_def.non_enum_variant();
2506        // Direct `len` field.
2507        if let Some(len_idx) = variant
2508            .fields
2509            .iter()
2510            .position(|f| f.ident(self.tcx).name.to_string() == "len")
2511        {
2512            let mut path = prefix.to_vec();
2513            path.push(len_idx);
2514            return self.units[alloc_id.0].content.values
2515                .get(&(root_ty, path))
2516                .map(|v| v.z3_term.clone());
2517        }
2518        // Recurse into ADT sub-fields (e.g. `String.vec.len`).
2519        for (idx, field_def) in variant.fields.iter().enumerate() {
2520            let field_ty = crate::helpers::mir_utils::field_ty(self.tcx, field_def, substs);
2521            if matches!(field_ty.kind(), rustc_middle::ty::TyKind::Adt(_, _)) {
2522                let mut path = prefix.to_vec();
2523                path.push(idx);
2524                if let Some(len) = self.try_adt_len_field_at(alloc_id, field_ty, root_ty, &path) {
2525                    return Some(len);
2526                }
2527            }
2528        }
2529        None
2530    }
2531
2532    /// Resolve `len()` for `core::ops::IndexRange` (a private `{ start, end }`
2533    /// struct): `len() = end - start`.  The `len` field lookup above misses it
2534    /// because `IndexRange` has no `len` field — its `len()` computes the
2535    /// difference of its two private fields.
2536    pub(crate) fn try_index_range_len(&self, val: &VmValue<'z3, 'tcx>) -> Option<Int<'z3>> {
2537        let rustc_middle::ty::TyKind::Adt(adt_def, _) = val.ty.kind() else {
2538            return None;
2539        };
2540        let name = self.tcx.def_path_str(adt_def.did());
2541        if !(name.ends_with("::IndexRange") || name == "IndexRange") {
2542            return None;
2543        }
2544        let alloc_id = val.provenance_alloc_id()?;
2545        let view_ty = crate::helpers::mir_utils::pointee_ty(val.ty).unwrap_or(val.ty);
2546        let start = self
2547            .units[alloc_id.0].content.values
2548            .get(&(view_ty, vec![0]))?
2549            .z3_term
2550            .clone();
2551        let end = self
2552            .units[alloc_id.0].content.values
2553            .get(&(view_ty, vec![1]))?
2554            .z3_term
2555            .clone();
2556        Some(Int::sub(self.z3_ctx, &[&end, &start]))
2557    }
2558
2559    /// Resolve `len()` of a slice/ADT value: the pointee ADT's `len` field, then
2560    /// the materialized slice length, then `size / elem_size`.  Shared by the
2561    /// exec- and checker-side `Len` evaluators so the fallback chain is defined
2562    /// once.
2563    pub(crate) fn len_from_value(&self, val: &VmValue<'z3, 'tcx>) -> Option<Int<'z3>> {
2564        if let Some(len) = self.try_adt_len_field(val) {
2565            return Some(len);
2566        }
2567        if let Some(len) = self.try_index_range_len(val) {
2568            return Some(len);
2569        }
2570        self.slice_len_from_value(val)
2571    }
2572
2573    /// Resolve `x.len()` for a struct `x` (e.g. `NodeRef`) whose `len()` method
2574    /// reads `(*x.field).len` through a `NonNull`/raw-pointer field.  Follows
2575    /// that field to its pointee allocation and reads the pointee's `len` field.
2576    pub(crate) fn try_struct_nn_len_field(
2577        &self,
2578        local: Local,
2579        field_path: &[usize],
2580        ty: Ty<'tcx>,
2581    ) -> Option<Int<'z3>> {
2582        let rustc_middle::ty::TyKind::Adt(adt_def, substs) = ty.kind() else {
2583            return None;
2584        };
2585        if !adt_def.is_struct() {
2586            return None;
2587        }
2588        let variant = adt_def.non_enum_variant();
2589        let mut found: Option<(usize, Ty<'tcx>)> = None;
2590        for (idx, field_def) in variant.fields.iter().enumerate() {
2591            let fty = crate::helpers::mir_utils::field_ty(self.tcx, field_def, substs);
2592            if let Some(pointee) = self.find_nn_pointee(fty) {
2593                found = Some((idx, pointee));
2594                break;
2595            }
2596        }
2597        let (nn_idx, pointee) = found?;
2598        let mut path = field_path.to_vec();
2599        path.push(nn_idx);
2600        let nn_val = self.field_value(local, &path)?;
2601        let alloc_id = nn_val.provenance_alloc_id()?;
2602        let rustc_middle::ty::TyKind::Adt(pointee_adt, _) = pointee.kind() else {
2603            return None;
2604        };
2605        let pvariant = pointee_adt.non_enum_variant();
2606        let len_idx = pvariant
2607            .fields
2608            .iter()
2609            .position(|f| f.ident(self.tcx).name.to_string() == "len")?;
2610        self.units[alloc_id.0].content.values
2611            .get(&(pointee, vec![len_idx]))
2612            .map(|v| v.z3_term.clone())
2613    }
2614
2615    /// Resolve the type of a place (`local` + field path) by walking the ADT
2616    /// field definitions.
2617    pub(crate) fn field_type_at(&self, local: Local, field_path: &[usize]) -> Option<Ty<'tcx>> {
2618        let mut ty = self.body().local_decls[local].ty;
2619        for &idx in field_path {
2620            let rustc_middle::ty::TyKind::Adt(adt_def, substs) = ty.kind() else {
2621                return None;
2622            };
2623            let variant = adt_def.non_enum_variant();
2624            let field_def = variant.fields.get(rustc_abi::FieldIdx::from_usize(idx))?;
2625            ty = crate::helpers::mir_utils::field_ty(self.tcx, field_def, substs);
2626        }
2627        Some(ty)
2628    }
2629
2630    /// Compute provenance for a binary operation on pointer values.
2631    /// Propagates provenance with adjusted offset for pointer arithmetic
2632    /// (`ptr + offset`, `ptr - offset`, `Offset`).
2633    fn provenance_for_binary_op(
2634        &self,
2635        op: BinOp,
2636        lhs: &VmValue<'z3, 'tcx>,
2637        rhs: &VmValue<'z3, 'tcx>,
2638    ) -> Option<Provenance<'z3>> {
2639        match op {
2640            BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked | BinOp::Offset => {
2641                // ptr + scalar → propagate with adjusted offset
2642                if rhs.is_pointer() {
2643                    return None;
2644                }
2645                lhs.provenance.as_ref().map(|prov| Provenance {
2646                    alloc_id: prov.alloc_id,
2647                    offset: Int::add(self.z3_ctx, &[&prov.offset, &rhs.z3_term]),
2648                    offset_kind: None,
2649                })
2650            }
2651            BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked => {
2652                if rhs.is_pointer() {
2653                    // ptr - ptr → integer (difference), no provenance
2654                    return None;
2655                }
2656                lhs.provenance.as_ref().map(|prov| Provenance {
2657                    alloc_id: prov.alloc_id,
2658                    offset: Int::sub(self.z3_ctx, &[&prov.offset, &rhs.z3_term]),
2659                    offset_kind: None,
2660                })
2661            }
2662            BinOp::BitAnd => {
2663                if rhs.is_pointer() {
2664                    return None;
2665                }
2666                lhs.provenance.as_ref().map(|prov| Provenance {
2667                    alloc_id: prov.alloc_id,
2668                    // Alignment rounding changes the intra-allocation offset
2669                    // unpredictably; use a fresh symbolic offset constrained
2670                    // by the BitAnd path conditions emitted in eval_binary_op.
2671                    offset: self.fresh_int("align_offset"),
2672                    offset_kind: None,
2673                })
2674            }
2675            BinOp::BitXor | BinOp::Shr | BinOp::ShrUnchecked => {
2676                if rhs.is_pointer() {
2677                    return None;
2678                }
2679                lhs.provenance.clone()
2680            }
2681            BinOp::BitOr | BinOp::Shl | BinOp::ShlUnchecked => {
2682                if rhs.is_pointer() {
2683                    return None;
2684                }
2685                lhs.provenance.as_ref().map(|prov| Provenance {
2686                    alloc_id: prov.alloc_id,
2687                    offset: Int::add(self.z3_ctx, &[&prov.offset, &rhs.z3_term]),
2688                    offset_kind: None,
2689                })
2690            }
2691            BinOp::Mul | BinOp::MulWithOverflow | BinOp::MulUnchecked => {
2692                if rhs.is_pointer() {
2693                    return None;
2694                }
2695                lhs.provenance.as_ref().map(|prov| Provenance {
2696                    alloc_id: prov.alloc_id,
2697                    offset: Int::mul(self.z3_ctx, &[&prov.offset, &rhs.z3_term]),
2698                    offset_kind: None,
2699                })
2700            }
2701            BinOp::Div | BinOp::Rem => lhs.provenance.clone(),
2702            _ => None,
2703        }
2704    }
2705
2706    /// Compute facts for a binary operation.
2707    /// Propagates non_null from pointer arithmetic and align_n from compatible ops.
2708    fn facts_for_binary_op(
2709        &self,
2710        op: BinOp,
2711        lhs: &VmValue<'z3, 'tcx>,
2712        rhs: &VmValue<'z3, 'tcx>,
2713        provenance: &Option<Provenance<'z3>>,
2714    ) -> ValueFacts<'z3> {
2715        // A bitwise AND may clear the low bits entirely (`ptr & mask == 0` for a
2716        // small/aligned-to-zero pointer), so the result is not guaranteed
2717        // non-null even when the lhs pointer is.  Other pointer arithmetic
2718        // (`add`/`sub`/`offset`) preserves non-nullness under the VM's
2719        // no-wrap assumption.
2720        let non_null =
2721            !matches!(op, BinOp::BitAnd) && provenance.is_some() && lhs.facts.non_null;
2722
2723        let align_n = match op {
2724            BinOp::Add
2725            | BinOp::AddWithOverflow
2726            | BinOp::AddUnchecked
2727            | BinOp::Sub
2728            | BinOp::SubWithOverflow
2729            | BinOp::SubUnchecked
2730            | BinOp::Offset => {
2731                // If both LHS and RHS are known to be n-aligned, sum/diff is n-aligned
2732                match (&lhs.facts.align_n, &rhs.facts.align_n) {
2733                    (Some(a), Some(b)) if a == b => Some(a.clone()),
2734                    // LHS has alignment, RHS is a constant multiple of it
2735                    (Some(a), None) => match a.simplify().as_u64() {
2736                        Some(au) => {
2737                            let c = rhs.z3_term.as_u64().unwrap_or(1);
2738                            if c.is_multiple_of(au) { Some(a.clone()) } else { None }
2739                        }
2740                        // Symbolic alignment: only a zero RHS is a guaranteed
2741                        // multiple of `align_T`.
2742                        None => {
2743                            if rhs.z3_term.as_u64() == Some(0) {
2744                                Some(a.clone())
2745                            } else {
2746                                None
2747                            }
2748                        }
2749                    },
2750                    // LHS has alignment, RHS is the result of Mul by constant factor
2751                    (Some(a), _) if self.rhs_is_aligned_multiple(rhs, a) => Some(a.clone()),
2752                    _ => None,
2753                }
2754            }
2755            BinOp::Mul | BinOp::MulWithOverflow | BinOp::MulUnchecked => match rhs.z3_term.as_u64() {
2756                Some(c) => pow2_factor(c).map(|p| Int::from_u64(self.z3_ctx, p)),
2757                None => lhs
2758                    .z3_term
2759                    .as_u64()
2760                    .and_then(pow2_factor)
2761                    .map(|p| Int::from_u64(self.z3_ctx, p)),
2762            },
2763            _ => lhs.facts.align_n.clone(),
2764        };
2765
2766        ValueFacts {
2767            non_null,
2768            align_n,
2769            ..Default::default()
2770        }
2771    }
2772
2773    /// Check if a value is known to be a multiple of `align` (e.g. the result
2774    /// of a Mul by a constant factor of `align`).
2775    fn rhs_is_aligned_multiple(&self, val: &VmValue<'z3, 'tcx>, align: &Int<'z3>) -> bool {
2776        // If both the value's align_n and `align` are concrete, compare directly.
2777        if let Some(au) = align.simplify().as_u64() {
2778            if let Some(a) = val
2779                .facts
2780                .align_n
2781                .as_ref()
2782                .and_then(|a| a.simplify().as_u64())
2783            {
2784                if a >= au && a % au == 0 {
2785                    return true;
2786                }
2787            }
2788            // If the value is a constant, check directly
2789            if let Some(c) = val.z3_term.as_u64() {
2790                if c % au == 0 {
2791                    return true;
2792                }
2793            }
2794        }
2795        false
2796    }
2797
2798    // ── Storage ──────────────────────────────────────────────────
2799
2800    fn exec_storage_live(&mut self, local: Local) {
2801        self.ensure_local_allocation(local);
2802        let alloc_id = self.current_frame.local_alloc[&local];
2803        self.alloc_mut(alloc_id).facts.dead = false;
2804    }
2805
2806    fn exec_storage_dead(&mut self, local: Local) {
2807        if let Some(alloc_id) = self.current_frame.local_alloc.get(&local).copied() {
2808            self.alloc_mut(alloc_id).facts.dead = true;
2809        }
2810    }
2811
2812    pub(crate) fn exec_drop(&mut self, place: &Place<'tcx>) {
2813        if let Some(alloc_id) = self.current_frame.local_alloc.get(&place.local).copied() {
2814            self.alloc_mut(alloc_id).facts.dead = true;
2815            // Cascade to the container's heap data allocation (the owning field's
2816            // provenance), so dropping a locally-created Vec/String/CString kills
2817            // its buffer. A *parameter*'s data buffer is owned by the caller (it
2818            // is an external allocation), so dropping the parameter here does not
2819            // free it.
2820            let is_param = place.local.as_usize() <= self.body().arg_count;
2821            if !is_param {
2822                let ty = self.body().local_decls[place.local].ty;
2823                if let Some(data_id) = self.container_data_alloc(alloc_id, ty) {
2824                    self.alloc_mut(data_id).facts.dead = true;
2825                }
2826            }
2827        }
2828    }
2829
2830    // ── Terminator executors ─────────────────────────────────────
2831
2832    fn exec_terminator(
2833        &mut self,
2834        terminator: &Terminator<'tcx>,
2835        switch_succ: Option<BasicBlock>,
2836    ) {
2837        match &terminator.kind {
2838            TerminatorKind::Call {
2839                func,
2840                args,
2841                destination,
2842                ..
2843            } => {
2844                let caller_id = self.current_frame.current_def_id;
2845                self.exec_call(func, args, destination.local, caller_id);
2846            }
2847            TerminatorKind::SwitchInt { discr, targets } => {
2848                self.exec_switchint(discr, targets, switch_succ);
2849            }
2850            TerminatorKind::Assert { cond, expected, .. } => {
2851                self.exec_assert(cond, *expected);
2852            }
2853            TerminatorKind::Goto { .. }
2854            | TerminatorKind::Return
2855            | TerminatorKind::Unreachable
2856            | TerminatorKind::UnwindResume
2857            | TerminatorKind::UnwindTerminate(_)
2858            | TerminatorKind::Yield { .. }
2859            | TerminatorKind::CoroutineDrop
2860            | TerminatorKind::FalseEdge { .. }
2861            | TerminatorKind::FalseUnwind { .. }
2862            | TerminatorKind::InlineAsm { .. }
2863            | TerminatorKind::TailCall { .. } => {}
2864            TerminatorKind::Drop { place, .. } => {
2865                self.exec_drop(place);
2866            }
2867        }
2868    }
2869
2870    /// Execute a SwitchInt terminator.
2871    ///
2872    /// Uses the successor resolved by the slicer (`switch_succ`) to determine
2873    /// which branch is taken, then adds a path condition asserting the
2874    /// discriminant equals that value.
2875    fn exec_switchint(
2876        &mut self,
2877        discr: &Operand<'tcx>,
2878        targets: &rustc_middle::mir::SwitchTargets,
2879        switch_succ: Option<BasicBlock>,
2880    ) {
2881        let discr_val = self.value_of_operand(discr);
2882
2883        // If the discriminator is a comparison result, record the direct
2884        // boolean condition alongside the ite-encoded `discr == value` fact,
2885        // so the SMT solver can reason about `offset <= len` directly.
2886        let cmp_cond = discr_val.bool_cond().cloned();
2887
2888        // Determine which target block is taken along the path.
2889        if let Some(chosen) = switch_succ {
2890            for (value, target) in targets.iter() {
2891                if target == chosen {
2892                    let val_term = Int::from_u64(self.z3_ctx, value as u64);
2893                    self.constraints.assertions.push(discr_val.z3_term._eq(&val_term));
2894                    if let Some(ref cond) = cmp_cond {
2895                        if value != 0 {
2896                            self.constraints.assertions.push(cond.clone());
2897                        } else {
2898                            self.constraints.assertions.push(cond.not());
2899                        }
2900                    }
2901                    if value != 0 {
2902                        self.infer_switch_guard(discr);
2903                    }
2904                    return;
2905                }
2906            }
2907            // Otherwise branch: the discrim is NOT any of the explicit values.
2908            if targets.otherwise() == chosen {
2909                // Negate every explicit target value.
2910                for (value, _) in targets.iter() {
2911                    let val_term = Int::from_u64(self.z3_ctx, value as u64);
2912                    self.constraints.assertions
2913                        .push(discr_val.z3_term._eq(&val_term).not());
2914                }
2915                if let Some(ref cond) = cmp_cond {
2916                    // For a boolean discriminator, `otherwise` means the
2917                    // comparison result is *not* any explicit value:
2918                    //   - targets include 0 → `discr != 0` → comparison true;
2919                    //   - targets include 1 → `discr != 1` → comparison false.
2920                    if targets.iter().any(|(v, _)| v == 0) {
2921                        self.constraints.assertions.push(cond.clone());
2922                    } else if targets.iter().any(|(v, _)| v == 1) {
2923                        self.constraints.assertions.push(cond.not());
2924                    }
2925                }
2926            }
2927        }
2928    }
2929
2930    /// Execute an Assert terminator.
2931    fn exec_assert(&mut self, cond: &Operand<'tcx>, expected: bool) {
2932        let cond_val = self.value_of_operand(cond);
2933        // If the asserted operand is a comparison result, record the direct
2934        // boolean condition (`idx < len`) alongside the ite-encoded fact, so
2935        // the SMT solver can unfold it (mirrors `exec_switchint`).
2936        let cmp_cond = cond_val.bool_cond().cloned();
2937        if expected {
2938            let zero = Int::from_u64(self.z3_ctx, 0);
2939            self.constraints.assertions.push(cond_val.z3_term._eq(&zero).not());
2940            if let Some(c) = &cmp_cond {
2941                self.constraints.assertions.push(c.clone());
2942            }
2943        } else {
2944            let zero = Int::from_u64(self.z3_ctx, 0);
2945            self.constraints.assertions.push(cond_val.z3_term._eq(&zero));
2946            if let Some(c) = &cmp_cond {
2947                self.constraints.assertions.push(c.not());
2948            }
2949        }
2950
2951        // Guard inference: trace the assert condition back to find non_null sources
2952        self.infer_guard_non_null(cond, expected);
2953        // Infer alignment from == 0 guards on Rem expressions
2954        self.infer_guard_align(cond, expected);
2955    }
2956
2957    /// The `(lhs, rhs, op)` of the binary-op/comparison source recorded on the
2958    /// value bound to `pk`'s local, if any.
2959    fn op_source_of(
2960        &self,
2961        pk: &PlaceKey,
2962    ) -> Option<(Option<PlaceKey>, Option<PlaceKey>, rustc_middle::mir::BinOp)> {
2963        let local = pk.local()?;
2964        let val = self.local_value(local)?;
2965        val.source
2966            .operands()
2967            .map(|(l, r, o)| (l.clone(), r.clone(), o))
2968    }
2969
2970    /// Infer alignment constraints from guards of the form `(x % n) == 0`.
2971    pub(crate) fn infer_guard_align(&mut self, cond: &Operand<'tcx>, expected: bool) {
2972        if !expected {
2973            return;
2974        }
2975        let place = match cond {
2976            Operand::Copy(p) | Operand::Move(p) => p,
2977            _ => return,
2978        };
2979        let cond_pk = PlaceKey::from_mir_place(place);
2980
2981        // Check if cond is a Ne/Eq comparison of (x % n) or (x & (align-1)) against 0
2982        if let Some((lhs_pk, rhs_pk, _)) = self.op_source_of(&cond_pk) {
2983            // The lhs is (x % n) / (x & (align-1)), rhs is constant 0
2984            let inner_pk = match (&lhs_pk, &rhs_pk) {
2985                (Some(pk), None) => pk.clone(),
2986                (None, Some(pk)) => pk.clone(),
2987                _ => return,
2988            };
2989            if let Some((div_lhs, div_rhs, inner_op)) = self.op_source_of(&inner_pk) {
2990                match inner_op {
2991                    // `x % n == 0`: div_rhs is the concrete divisor constant.
2992                    rustc_middle::mir::BinOp::Rem => {
2993                        if let Some(divisor) = resolve_u64_from_place_key(&div_rhs, self) {
2994                            if divisor > 0 {
2995                                self.mark_align_n(&div_lhs, Int::from_u64(self.z3_ctx, divisor));
2996                            }
2997                        }
2998                    }
2999                    // `x & (align-1) == 0`: the mask is `align-1` (symbolic for a
3000                    // generic `T`), so `align = mask + 1`.
3001                    rustc_middle::mir::BinOp::BitAnd => {
3002                        if let Some(rhs_local) = div_rhs.as_ref().and_then(|pk| pk.local()) {
3003                            if let Some(rhs_val) = self.local_value(rhs_local) {
3004                                let one = Int::from_u64(self.z3_ctx, 1);
3005                                let align = Int::add(self.z3_ctx, &[&rhs_val.z3_term, &one]);
3006                                self.mark_align_n(&div_lhs, align);
3007                            }
3008                        }
3009                    }
3010                    _ => {}
3011                }
3012            }
3013        }
3014    }
3015
3016    fn mark_align_n(&mut self, src_pk: &Option<PlaceKey>, align: Int<'z3>) {
3017        if let Some(src_pk) = src_pk {
3018            if let Some(local) = src_pk.local() {
3019                if let Some(mut val) = self.local_value(local).cloned() {
3020                    val.facts.align_n = Some(align);
3021                    self.set_local(local, val);
3022                }
3023            }
3024        }
3025    }
3026
3027    /// Infer non_null invariants from branch guards.
3028    pub(crate) fn infer_guard_non_null(&mut self, cond: &Operand<'tcx>, expected: bool) {
3029        if !expected {
3030            return;
3031        }
3032        let place = match cond {
3033            Operand::Copy(p) | Operand::Move(p) => p,
3034            _ => return,
3035        };
3036        let cond_pk = PlaceKey::from_mir_place(place);
3037
3038        // Check if cond was defined by BinaryOp(Ne, (ptr, 0)) or similar:
3039        // mark the non-constant side as non-null.  Only `Ne` guards imply
3040        // non-nullness; an `Eq` guard (`assert (addr & mask) == 0`, the
3041        // alignment check) means the value *is* zero, not non-null.
3042        if let Some((lhs_pk, rhs_pk, op)) = self.op_source_of(&cond_pk) {
3043            if op != rustc_middle::mir::BinOp::Ne {
3044                return;
3045            }
3046            if rhs_pk.is_none() {
3047                self.mark_guard_pointer(&lhs_pk, &None);
3048            }
3049            if lhs_pk.is_none() {
3050                self.mark_guard_pointer(&rhs_pk, &None);
3051            }
3052        }
3053    }
3054
3055    /// Infer non_null from SwitchInt discriminant.
3056    fn infer_switch_guard(&mut self, discr: &Operand<'tcx>) {
3057        let place = match discr {
3058            Operand::Copy(p) | Operand::Move(p) => p,
3059            _ => return,
3060        };
3061        let pk = PlaceKey::from_mir_place(place);
3062        if let Some((lhs_pk, rhs_pk, _)) = self.op_source_of(&pk) {
3063            self.mark_guard_pointer(&lhs_pk, &rhs_pk);
3064        }
3065    }
3066
3067    fn mark_guard_pointer(&mut self, lhs: &Option<PlaceKey>, rhs: &Option<PlaceKey>) {
3068        for pk in [lhs, rhs].into_iter().flatten() {
3069            if let Some(local) = pk.local() {
3070                if let Some(mut val) = self.local_value(local).cloned() {
3071                    val.facts.non_null = true;
3072                    self.set_local(local, val);
3073                }
3074            }
3075        }
3076    }
3077
3078    /// Assert a contract fact as VM state invariants.
3079    fn assert_contract_fact(&mut self, property: &Property<'tcx>) {
3080        // A precondition with a hazard component records that the caller
3081        // accepts that hazard (e.g. `any(Trait(T, Copy), Alias(self, ret))` on
3082        // `NonNull::read`).  Inlined read/copy intrinsics whose result
3083        // structurally aliases the source are then treated as the accepted
3084        // hazard rather than a hard failure.
3085        if contains_hazard(property) {
3086            self.path_facts.alias_hazard_accepted = true;
3087        }
3088        match property {
3089            Property::Atom(atom) => {
3090                if atom.contract_kind == ContractKind::Hazard {
3091                    return;
3092                }
3093                // Assert the atom's own effect, then each of its transitive
3094                // consequences exactly once (`subsumption_closure` is
3095                // deduplicated).
3096                self.assert_atom_direct(property);
3097                for sub in crate::verify::contract::compound::subsumption_closure(atom) {
3098                    self.assert_atom_direct(&Property::Atom(sub));
3099                }
3100            }
3101            Property::And(and) => {
3102                for conj in &and.conjuncts {
3103                    self.assert_contract_fact(conj);
3104                }
3105            }
3106            Property::Or(or) => {
3107                // Two guard patterns are materialized by asserting the
3108                // *non-guard* disjuncts (the guard is vacuous for the case the
3109                // deref actually runs in):
3110                //   `any(Null(p), (atoms…))`  — nullable pointer.
3111                //   `Size(T, 0) || Deref`     — ZST vs non-ZST (`ValidPtr`).
3112                // A general `Or` without such a guard disjunct is left to the
3113                // checker (only one disjunct holds, so no fact is sound to
3114                // assert unconditionally).
3115                let is_guard = |d: &Box<Property<'tcx>>| {
3116                    matches!(d.as_ref(), Property::Atom(a) if a.kind == PropertyKind::Null || a.kind == PropertyKind::Size)
3117                };
3118                if !or.disjuncts.iter().any(&is_guard) {
3119                    return;
3120                }
3121                for disj in &or.disjuncts {
3122                    if is_guard(disj) {
3123                        continue;
3124                    }
3125                    self.assert_contract_fact(disj);
3126                }
3127            }
3128        }
3129    }
3130
3131    /// Apply a single atom's direct effect (its `match kind` arm), without
3132    /// recursing into its subsumption consequences.
3133    fn assert_atom_direct(&mut self, property: &Property<'tcx>) {
3134        let Property::Atom(atom) = property else {
3135            return;
3136        };
3137        let kind = atom.kind;
3138        match kind {
3139            PropertyKind::NonNull => {
3140                if let Some(val) = self.contract_target_value(property) {
3141                    self.set_non_null_for_value(property, val);
3142                }
3143            }
3144            PropertyKind::Align => {
3145                if let Some(val) = self.contract_target_value(property) {
3146                    self.set_align_for_value(property, val);
3147                }
3148                self.record_for_each_align(property);
3149            }
3150            PropertyKind::Init => {
3151                if let Some(val) = self.contract_target_value(property) {
3152                    self.set_init_for_value(property, val);
3153                }
3154            }
3155            PropertyKind::Owning => {
3156                if let Some(val) = self.contract_target_value(property) {
3157                    self.set_owning_for_value(val);
3158                }
3159                self.record_for_each_owning(property);
3160            }
3161            PropertyKind::Alive => {
3162                if let Some(id) = self.contract_alloc_id_field_aware(property) {
3163                    // The region is bound at parse time (`bind_alive_regions`):
3164                    // struct invariants against the struct, function `requires`
3165                    // against the function.
3166                    if let Some(PropertyArg::Region(region)) = property.args().get(1) {
3167                        self.alloc_mut(id).facts.liveness = Some(*region);
3168                    }
3169                }
3170            }
3171            PropertyKind::InBound => {
3172                if let Some(val) = self.contract_target_value(property) {
3173                    self.set_in_bounds_for_value(property, val);
3174                }
3175                if let Some(fe_place) = property.for_each() {
3176                    self.assert_in_bound_for_each(property, fe_place);
3177                    self.path_facts.has_checked_bounds = true;
3178                } else {
3179                    self.assert_in_bound_single(property);
3180                }
3181                // The pointer-and-count form `InBound(p, T, n)` also guarantees
3182                // the allocation backs `n` elements — materialize that size so a
3183                // downstream `ptr.add(n)`/`ptr.sub(n)` can discharge `InBound`
3184                // against a concrete (or `i64::MAX`) size rather than the coarse
3185                // `in_bounds` flag (which only covers `count == 1`).
3186                if property.args().len() >= 3 {
3187                    self.assert_allocated_fact(property);
3188                }
3189            }
3190            PropertyKind::Allocated => {
3191                self.assert_allocated_fact(property);
3192                self.record_for_each_allocated(property);
3193            }
3194            PropertyKind::Typed => {
3195                if let Some(val) = self.contract_target_value(property) {
3196                    if let Some(alloc_id) = val.provenance_alloc_id() {
3197                        if let Some(expected_ty) = property.args().get(1).and_then(|a| {
3198                            if let PropertyArg::Ty(ty) = a {
3199                                Some(*ty)
3200                            } else {
3201                                None
3202                            }
3203                        }) {
3204                            // A `Typed(container.iter(), T)` *for_each* invariant
3205                            // declares that the container's pointer elements all
3206                            // point at valid `T`s. Record that target type so a
3207                            // single pointer loaded from the container can later
3208                            // discharge `Typed(ptr, T)` soundly (the fact comes
3209                            // from the invariant, not from the pointer type).
3210                            if property.for_each().is_some() {
3211                                self.alloc_mut(alloc_id).facts.for_each.target_ty = Some(expected_ty);
3212                            }
3213                            // Only record the type invariant when the allocation
3214                            // has no element type yet.  `Init ⇒ Typed` (and other
3215                            // typed preconditions) may re-assert `Typed` with a
3216                            // *re-numbered* instantiation of the same `T` (e.g.
3217                            // `T/#1` vs the `T/#0` used to size the allocation),
3218                            // which must not clobber the original element type —
3219                            // doing so makes `len()` fall back to the byte size.
3220                            if self.alloc(alloc_id).element_ty.is_generic() {
3221                                self.alloc_mut(alloc_id).element_ty = ElementTy::Typed(expected_ty);
3222                            }
3223                        }
3224                    }
3225                }
3226            }
3227            PropertyKind::SplitTransmute => {
3228                self.path_facts.split_transmute_asserted = true;
3229            }
3230            PropertyKind::ValidCStr => {
3231                // A `ValidCStr(p, n)` fact guarantees `p` points to a live,
3232                // initialized, null-terminated byte buffer. Mark the target
3233                // allocation so the checker can treat it (and any of its
3234                // sub-slices) as a valid C string, and so pointer reads /
3235                // `from_raw_parts` over it see a live, initialized allocation.
3236                //
3237                // For a `&CStr`-style target the field projection (`inner`)
3238                // may not be materialised for a DST, so fall back to the base
3239                // local's own provenance (a `&CStr` reference points directly
3240                // at the byte buffer it owns).
3241                let id = self.contract_alloc_id_field_aware(property).or_else(|| {
3242                    let local = self.contract_target_local(property)?;
3243                    self.local_value(local)?.provenance_alloc_id()
3244                });
3245                if let Some(id) = id {
3246                    self.alloc_mut(id).facts.dead = false;
3247                    self.alloc_mut(id).facts.liveness = Some(self.tcx.lifetimes.re_static);
3248                    self.content_mut(id).facts.initialized = true;
3249                    self.content_mut(id).facts.cstr_trusted = true;
3250                    // `ValidCStr(p, n)` carries the byte length of the
3251                    // nul-terminated buffer.  Assert the allocation covers `n`
3252                    // bytes so downstream `from_raw_parts(p, n)` / InBound
3253                    // obligations can be discharged from the exact length
3254                    // (rather than a conservative `1` placeholder), and
3255                    // materialize the terminal NUL as a byte-level fact
3256                    // (`byte[n] == 0`).  A `Const` length (`from_ptr`'s `1`
3257                    // placeholder) is ignored — the true length is
3258                    // `strlen(ptr) + 1`.
3259                    if let Some(n) = property
3260                        .args()
3261                        .get(1)
3262                        .and_then(|a| match a {
3263                            PropertyArg::Expr(ContractExpr::Const(_)) => None,
3264                            a => self.resolve_contract_count(a),
3265                        })
3266                    {
3267                        self.constraints.assertions.push(self.alloc(id).size.ge(&n));
3268                        let zero = Int::from_u64(self.z3_ctx, 0);
3269                        self.byte_write(id, &n, &zero);
3270                    }
3271                }
3272            }
3273            PropertyKind::ValidString => {
3274                // A `ValidString` fact guarantees the target's bytes form a
3275                // valid UTF-8 sequence.  Mark the backing allocation so the
3276                // checker can trust it instead of re-running the byte-level
3277                // DFA.  Unlike `ValidCStr`, UTF-8 validity is a content
3278                // property, so only content facts are set.
3279                let id = self.contract_alloc_id_field_aware(property).or_else(|| {
3280                    // Iterator form (`ValidString(bytes)` with `bytes: &mut I`):
3281                    // trace the iterator to its backing byte buffer.
3282                    let local = self.contract_target_local(property)?;
3283                    let val = self.local_value(local)?.clone();
3284                    let iter_local = self.find_local_by_address(&val.z3_term)?;
3285                    self.iter_utf8_buffer(iter_local).map(|(id, _)| id)
3286                });
3287                if let Some(id) = id {
3288                    self.content_mut(id).facts.initialized = true;
3289                    self.content_mut(id).facts.utf8_trusted = true;
3290                }
3291            }
3292            PropertyKind::ValidNum => {
3293                if let Some(PropertyArg::Predicates(predicates)) = property.args().first() {
3294                    for pred in predicates {
3295                        if let Some(condition) = self.eval_predicate_as_bool(pred) {
3296                            self.constraints.assertions.push(condition);
3297                            // For !self.is_empty() → self.len() != 0 on
3298                            // Iter/IterMut: also assert len >= 1 to help
3299                            // Z3 with integer division reasoning.
3300                            if let Some(len_term) = self.try_simple_iter_len_from_pred(pred) {
3301                                let one = Int::from_u64(self.z3_ctx, 1);
3302                                self.constraints.assertions.push(len_term.ge(&one));
3303                            }
3304                        }
3305                    }
3306                }
3307            }
3308            _ => {}
3309        }
3310    }
3311
3312    /// Get the local referenced by a contract property's target.
3313    fn contract_target_local(&self, property: &Property<'tcx>) -> Option<Local> {
3314        let cp = property.target_place()?;
3315        Some(cp.base.to_local())
3316    }
3317
3318    /// Materialize a fresh external allocation for an `Allocated` contract
3319    /// fact, returning a value carrying the allocation's provenance.
3320    /// Mark the target pointer's existing allocation as live (and record its
3321    /// element type for downstream `Typed` checks).  Returns `true` when the
3322    /// pointer already carried an allocation, so a fresh external allocation
3323    /// should *not* be materialized (which would loosen bound checks).
3324    fn mark_alloc_live_keep(&mut self, val: &VmValue<'z3, 'tcx>, elem_ty: Ty<'tcx>) -> bool {
3325        let Some(alloc_id) = val.provenance_alloc_id() else {
3326            return false;
3327        };
3328        self.alloc_mut(alloc_id).facts.dead = false;
3329        // An `Allocated(p, T, n)` contract on a raw-pointer parameter means the
3330        // caller guarantees the memory is allocated and outlives the call, so
3331        // it is alive for the function's execution region.
3332        if self.alloc(alloc_id).is_external() {
3333            self.alloc_mut(alloc_id).facts.liveness = Some(self.tcx.lifetimes.re_static);
3334        }
3335        if self.alloc(alloc_id).element_ty.is_generic() {
3336            self.alloc_mut(alloc_id).element_ty = ElementTy::Typed(elem_ty);
3337        }
3338        true
3339    }
3340
3341    fn materialize_external_alloc(
3342        &mut self,
3343        elem_ty: Ty<'tcx>,
3344        count_term: Option<Int<'z3>>,
3345        val_ty: Ty<'tcx>,
3346        huge: bool,
3347    ) -> VmValue<'z3, 'tcx> {
3348        let elem_sz_raw = self.size_of_ty(elem_ty);
3349        let heap_align = self.align_sym(elem_ty);
3350        let heap_align_n = if heap_align.simplify().as_u64() != Some(1) {
3351            Some(heap_align.clone())
3352        } else {
3353            None
3354        };
3355        let (heap_id, heap_base) = if huge || elem_sz_raw == 0 {
3356            // Struct-field targets (and generic element types): use an
3357            // unbounded external allocation so `Allocated`/`InBound` checks
3358            // auto-pass regardless of the symbolic element size.
3359            let max_size = Int::from_u64(self.z3_ctx, i64::MAX as u64);
3360            self.allocate_external(max_size, heap_align.clone(), Some(elem_ty))
3361        } else {
3362            let elem_sz = Int::from_u64(self.z3_ctx, elem_sz_raw);
3363            let count = count_term.unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
3364            let total = Int::mul(self.z3_ctx, &[&count, &elem_sz]);
3365            self.allocate_external(total, heap_align, Some(elem_ty))
3366        };
3367        self.content_mut(heap_id).facts.initialized = true;
3368        VmValue {
3369            z3_term: heap_base,
3370            ty: val_ty,
3371            provenance: Some(Provenance {
3372                alloc_id: heap_id,
3373                offset: Int::from_u64(self.z3_ctx, 0),
3374                offset_kind: None,
3375            }),
3376            facts: ValueFacts {
3377                non_null: true,
3378                init: true,
3379                in_bounds: true,
3380                align_n: heap_align_n,
3381            },
3382            source: ValueSource::None,
3383        }
3384    }
3385
3386    /// Assert an `Allocated(p, T, n)` contract fact by materializing a fresh
3387    /// external allocation for the pointer-typed target.
3388    ///
3389    /// - For a whole pointer parameter (`src`), the allocation is sized
3390    ///   `n * sizeof(T)` so downstream pointer arithmetic stays in bounds.
3391    /// - For a plain pointer *field* (e.g. `RawVecInner::ptr`), the allocation
3392    ///   is written back to the field via `set_contract_target_value`, and is
3393    ///   unbounded so field-subrange `InBound` checks auto-pass.
3394    /// - For `ForEach`/`Downcast` targets (e.g. `buckets.iter()`), the
3395    ///   container itself is not a pointer — keep the legacy whole-local
3396    ///   behaviour.
3397    fn assert_allocated_fact(&mut self, property: &Property<'tcx>) {
3398        let elem_ty = property.args().get(1).and_then(|a| {
3399            if let PropertyArg::Ty(ty) = a {
3400                Some(*ty)
3401            } else {
3402                None
3403            }
3404        });
3405        let count_term = property
3406            .args()
3407            .get(2)
3408            .and_then(|a| self.resolve_contract_count(a));
3409        let Some(elem_ty) = elem_ty else { return };
3410
3411        let has_nonfield = property
3412            .target_place()
3413            .map(|cp| {
3414                cp.projections.iter().any(|p| {
3415                    !matches!(p, crate::verify::contract::ContractProjection::Field { .. })
3416                })
3417            })
3418            .unwrap_or(false);
3419
3420        if has_nonfield {
3421            // ForEach/Downcast target: legacy whole-local, exact size.
3422            let Some(local) = self.contract_target_local(property) else {
3423                return;
3424            };
3425            let Some(val) = self.local_value(local).cloned() else {
3426                return;
3427            };
3428            if let Some(alloc_id) = val.provenance_alloc_id() {
3429                self.alloc_mut(alloc_id).facts.dead = false;
3430            }
3431            let v = self.materialize_external_alloc(elem_ty, count_term, val.ty, false);
3432            self.set_local(local, v);
3433        } else {
3434            let Some((local, field_path)) = self.contract_field_path(property) else {
3435                return;
3436            };
3437            if field_path.is_empty() {
3438                // Whole pointer parameter: exact size.
3439                let Some(val) = self.local_value(local).cloned() else {
3440                    return;
3441                };
3442                if self.mark_alloc_live_keep(&val, elem_ty) {
3443                    return;
3444                }
3445                let v = self.materialize_external_alloc(elem_ty, count_term, val.ty, false);
3446                self.set_local(local, v);
3447            } else {
3448                // Field target. Only a *direct* pointer field (`NonNull<T>` /
3449                // `*mut T` / `*const T`) to a *simple* element type (primitive or
3450                // generic param, e.g. `NonNull<u8>`) carries the allocation
3451                // itself without a nested field decomposition; a wrapped field
3452                // (`Option<NonNull>`, `Box`) or a pointer-to-ADT (`NonNull<LeafNode>`,
3453                // whose fields were decomposed by param init) is handled via the
3454                // legacy whole-local path to avoid losing those relationships.
3455                let is_direct_simple_ptr = self.field_value(local, &field_path)
3456                    .map(|val| {
3457                        (matches!(val.ty.kind(), rustc_middle::ty::TyKind::RawPtr(..))
3458                            || matches!(val.ty.kind(), rustc_middle::ty::TyKind::Adt(adt, _)
3459                                if api_classify::is_std_nonnull(adt.did())
3460                                    || crate::helpers::mir_utils::is_raw_ptr_wrapper(self.tcx, adt.did())))
3461                            && (elem_ty.is_primitive()
3462                                || matches!(elem_ty.kind(), rustc_middle::ty::TyKind::Param(_)))
3463                    })
3464                    .unwrap_or(false);
3465                if is_direct_simple_ptr {
3466                    let Some(val) = self.field_value(local, &field_path).cloned() else {
3467                        return;
3468                    };
3469                    if let Some(alloc_id) = val.provenance_alloc_id() {
3470                        self.alloc_mut(alloc_id).facts.dead = false;
3471                        if self.alloc(alloc_id).size.as_u64().is_some() {
3472                            return;
3473                        }
3474                    }
3475                    let mut v = self.materialize_external_alloc(elem_ty, count_term, val.ty, true);
3476                    // The size materialization must not clobber a *stronger*
3477                    // alignment fact already on the field (e.g. `Align(ptr,
3478                    // usize)` set `align_n = 8`, but `Allocated(ptr, u8, n)`
3479                    // re-materializes with `u8`'s align of 1 → `align_n = None`).
3480                    if v.facts.align_n.is_none() {
3481                        v.facts.align_n = val.facts.align_n.clone();
3482                    }
3483                    self.set_field_value(local, field_path, v);
3484                } else {
3485                    // Wrapped field / pointer-to-ADT: materialize the *field*
3486                    // target (not the whole local) so a downstream `Allocated`
3487                    // on the field sees the freshly-sized allocation.
3488                    let Some(val) = self.field_value(local, &field_path).cloned() else {
3489                        return;
3490                    };
3491                    if let Some(alloc_id) = val.provenance_alloc_id() {
3492                        self.alloc_mut(alloc_id).facts.dead = false;
3493                        if self.alloc(alloc_id).size.as_u64().is_some() {
3494                            return;
3495                        }
3496                    }
3497                    let mut v = self.materialize_external_alloc(elem_ty, count_term, val.ty, false);
3498                    if v.facts.align_n.is_none() {
3499                        v.facts.align_n = val.facts.align_n.clone();
3500                    }
3501                    self.set_field_value(local, field_path, v);
3502                }
3503            }
3504        }
3505    }
3506
3507    /// Resolve a contract place to `(local, field_path)`. Field projections
3508    /// are accumulated into `field_path`; `Downcast`/`ForEach` terminate
3509    /// the path (they unwrap the value in place).
3510    fn contract_field_path(&self, property: &Property<'tcx>) -> Option<(Local, Vec<usize>)> {
3511        let cp = property.target_place()?;
3512        let local = cp.base.to_local();
3513        let mut path = Vec::new();
3514        for proj in &cp.projections {
3515            match proj {
3516                crate::verify::contract::ContractProjection::Field { index, .. } => {
3517                    path.push(*index);
3518                }
3519                _ => break,
3520            }
3521        }
3522        Some((local, path))
3523    }
3524
3525    /// Get the VmValue for a contract property's target, following field
3526    /// projections so that `Align(self.heap, T)` resolves to the `heap` field
3527    /// value rather than the whole `self` reference.
3528    fn contract_target_value(&mut self, property: &Property<'tcx>) -> Option<VmValue<'z3, 'tcx>> {
3529        let (local, path) = self.contract_field_path(property)?;
3530        if path.is_empty() {
3531            self.local_value(local).cloned()
3532        } else {
3533            self.field_value(local, &path).cloned()
3534        }
3535    }
3536
3537    /// Write a contract target value back to its (possibly field) location.
3538    fn set_contract_target_value(&mut self, property: &Property<'tcx>, val: VmValue<'z3, 'tcx>) {
3539        if let Some((local, path)) = self.contract_field_path(property) {
3540            if path.is_empty() {
3541                self.set_local(local, val);
3542            } else {
3543                self.set_field_value(local, path, val);
3544            }
3545        }
3546    }
3547
3548    /// Resolve the alloc_id for a contract property target, following
3549    /// field projections to locate the actual field value's provenance.
3550    fn contract_alloc_id_field_aware(&mut self, property: &Property<'tcx>) -> Option<AllocId> {
3551        let cp = match property.args().first()? {
3552            PropertyArg::Expr(ContractExpr::Place(cp)) => cp.clone(),
3553            _ => return None,
3554        };
3555        let local = cp.base.to_local();
3556        let mut field_path: Vec<usize> = Vec::new();
3557        for proj in &cp.projections {
3558            match proj {
3559                crate::verify::contract::ContractProjection::Field { index, .. } => {
3560                    field_path.push(*index);
3561                }
3562                _ => return None,
3563            }
3564        }
3565        if field_path.is_empty() {
3566            self.local_value(local)?.provenance_alloc_id()
3567        } else {
3568            self.field_value(local, &field_path)?.provenance_alloc_id()
3569        }
3570    }
3571
3572    /// Resolve a contract count argument to a Z3 term by looking up
3573    /// the corresponding function parameter in the VM state.
3574    fn resolve_contract_count(&self, arg: &PropertyArg<'tcx>) -> Option<Int<'z3>> {
3575        match arg {
3576            PropertyArg::Expr(ContractExpr::Const(n)) => Some(Int::from_u64(self.z3_ctx, *n as u64)),
3577            // Delegate field-projected places and arithmetic (e.g. `cap * elem_size`)
3578            // to the general simple evaluator.
3579            PropertyArg::Expr(expr) => self.eval_contract_expr_simple(expr),
3580            _ => None,
3581        }
3582    }
3583
3584    /// Evaluate a numeric predicate to a Z3 Bool for path-condition assertion.
3585    fn relop_to_bool(
3586        &self,
3587        op: crate::verify::contract::RelOp,
3588        lhs: &Int<'z3>,
3589        rhs: &Int<'z3>,
3590    ) -> Bool<'z3> {
3591        use crate::verify::contract::RelOp;
3592        match op {
3593            RelOp::Eq => lhs._eq(rhs),
3594            RelOp::Ne => lhs._eq(rhs).not(),
3595            RelOp::Le => lhs.le(rhs),
3596            RelOp::Lt => lhs.lt(rhs),
3597            RelOp::Ge => lhs.ge(rhs),
3598            RelOp::Gt => lhs.gt(rhs),
3599        }
3600    }
3601
3602    fn eval_predicate_as_bool(
3603        &self,
3604        pred: &crate::verify::contract::NumericPredicate<'tcx>,
3605    ) -> Option<Bool<'z3>> {
3606        use crate::verify::contract::ContractExpr;
3607        let lhs = self.eval_contract_expr_simple(&pred.lhs)?;
3608        let rhs = match &pred.rhs {
3609            ContractExpr::Const(v) => Int::from_u64(self.z3_ctx, *v as u64),
3610            _ => self.eval_contract_expr_simple(&pred.rhs)?,
3611        };
3612        Some(self.relop_to_bool(pred.op, &lhs, &rhs))
3613    }
3614
3615    /// Evaluate a numeric binary operation over two symbolic terms.
3616    fn eval_numeric_binary(
3617        &self,
3618        l: &Int<'z3>,
3619        r: &Int<'z3>,
3620        op: crate::verify::contract::NumericBinOp,
3621    ) -> Option<Int<'z3>> {
3622        use crate::verify::contract::NumericBinOp;
3623        Some(match op {
3624            NumericBinOp::Add => Int::add(self.z3_ctx, &[l, r]),
3625            NumericBinOp::Sub => Int::sub(self.z3_ctx, &[l, r]),
3626            NumericBinOp::Mul => Int::mul(self.z3_ctx, &[l, r]),
3627            NumericBinOp::Div => l.div(r),
3628            NumericBinOp::Rem => l.rem(r),
3629            _ => return None,
3630        })
3631    }
3632
3633    fn eval_contract_expr_simple(
3634        &self,
3635        expr: &crate::verify::contract::ContractExpr<'tcx>,
3636    ) -> Option<Int<'z3>> {
3637        use crate::verify::contract::ContractExpr;
3638        match expr {
3639            ContractExpr::SizeOf(ty) => {
3640                // Symbolic-aware: a generic `T` yields the shared `sizeof_T`
3641                // (≥ 1) instead of the concrete `1` placeholder, so bounds like
3642                // `size_of(T) * len <= isize::MAX` match the data allocation's
3643                // `len·sizeof_T` byte size.  Concrete types still return their
3644                // real size.
3645                Some(self.size_sym_read(*ty))
3646            }
3647            ContractExpr::Place(cp) => {
3648                let local = cp.base.to_local();
3649                let mut path: Vec<usize> = Vec::new();
3650                for proj in &cp.projections {
3651                    match proj {
3652                        crate::verify::contract::ContractProjection::Field { index, .. } => {
3653                            path.push(*index);
3654                        }
3655                        // Downcast / ForEach are not scalar numeric values.
3656                        _ => return None,
3657                    }
3658                }
3659                if path.is_empty() {
3660                    self.local_value(local).map(|v| v.z3_term.clone())
3661                } else {
3662                    self.field_value(local, &path)
3663                        .map(|v| v.z3_term.clone())
3664                        .or_else(|| {
3665                            // Deref+Field: the base local is a reference whose pointee
3666                            // fields live in the per-allocation map (e.g. the
3667                            // `ValidNum(len <= CAPACITY)` invariant on `&LeafNode`
3668                            // reads `(*leaf).len` through `MemoryContent::values`).
3669                            let base_val = self.local_value(local)?;
3670                            let alloc_id = base_val.provenance_alloc_id()?;
3671                            let view_ty = crate::helpers::mir_utils::pointee_ty(base_val.ty)
3672                                .unwrap_or(base_val.ty);
3673                            self.units[alloc_id.0].content.values
3674                                .get(&(view_ty, path.clone()))
3675                                .map(|v| v.z3_term.clone())
3676                        })
3677                }
3678            }
3679            ContractExpr::Len(inner) => {
3680                // Try field-based len for Iter/IterMut first.
3681                if let Some(val) = self.eval_contract_expr_simple_value(inner) {
3682                    if let Some(term) = self.try_simple_iter_len(&val) {
3683                        return Some(term);
3684                    }
3685                }
3686                // A struct (e.g. `NodeRef`) whose `len()` reads `(*x.field).len`
3687                // through a `NonNull` field.
3688                if let ContractExpr::Place(cp) = &**inner {
3689                    if let Some(path) = cp.plain_field_path() {
3690                        let local = cp.base.to_local();
3691                        if let Some(ty) = self.field_type_at(local, &path) {
3692                            if let Some(term) = self.try_struct_nn_len_field(local, &path, ty) {
3693                                return Some(term);
3694                            }
3695                        }
3696                    }
3697                }
3698                let val = self.eval_contract_expr_simple_value(inner)?;
3699                self.len_from_value(&val)
3700            }
3701            ContractExpr::Binary { op, lhs, rhs } => {
3702                let l = self.eval_contract_expr_simple(lhs)?;
3703                let r = self.eval_contract_expr_simple(rhs)?;
3704                self.eval_numeric_binary(&l, &r, *op)
3705            }
3706            ContractExpr::Const(n) => Some(Int::from_u64(self.z3_ctx, *n as u64)),
3707            _ => None,
3708        }
3709    }
3710
3711    fn eval_contract_expr_simple_value(
3712        &self,
3713        expr: &crate::verify::contract::ContractExpr<'tcx>,
3714    ) -> Option<VmValue<'z3, 'tcx>> {
3715        match expr {
3716            ContractExpr::Place(cp) => match cp.base {
3717                PlaceBase::Local(n) => self.local_value(Local::from_usize(n)).cloned(),
3718                _ => None,
3719            },
3720            _ => None,
3721        }
3722    }
3723
3724    /// Try field-based len for Iter/IterMut references (same logic as
3725    /// `interpreter_iter_len` in call.rs). Used by `eval_contract_expr_simple`
3726    /// so that ContractFact assertions use the same symbolic term as the
3727    /// VM execution path.
3728    fn is_iter_ref(&self, val: &VmValue<'z3, 'tcx>) -> bool {
3729        use rustc_middle::ty::TyKind;
3730        match val.ty.kind() {
3731            TyKind::Ref(_, pointee, _) => match pointee.kind() {
3732                TyKind::Adt(adt_def, _) => api_classify::is_std_iter_or_itermut(adt_def.did()),
3733                _ => false,
3734            },
3735            _ => false,
3736        }
3737    }
3738
3739    /// When `lhs op rhs` compares an iterator's `ptr` and `end_or_len` pointers
3740    /// (the inlined form of `is_empty`: `ptr == end`), express the comparison
3741    /// element-wise as `iter_ptr_offset == base_len`. The operands may be plain
3742    /// temporaries (from the `as_ptr`/cast/field-read lowering), so the iterator
3743    /// is located via the shared buffer allocation (the `end` field's provenance
3744    /// alloc id). Returns `None` when this is not an iterator emptiness
3745    /// comparison.
3746    fn iter_ptr_comparison(
3747        &self,
3748        op: rustc_middle::mir::BinOp,
3749        lhs: &VmValue<'z3, 'tcx>,
3750        rhs: &VmValue<'z3, 'tcx>,
3751    ) -> Option<z3::ast::Bool<'z3>> {
3752        if !matches!(op, rustc_middle::mir::BinOp::Eq | rustc_middle::mir::BinOp::Ne) {
3753            return None;
3754        }
3755        let lp = lhs.provenance.as_ref()?;
3756        let rp = rhs.provenance.as_ref()?;
3757        if lp.alloc_id != rp.alloc_id {
3758            return None;
3759        }
3760        let (offset, base_len) = self.constraints.term_caches.iter_ptr_offset.get(&lp.alloc_id)?;
3761        let base_len = base_len.as_ref()?;
3762        let eq = offset._eq(base_len);
3763        Some(if matches!(op, rustc_middle::mir::BinOp::Eq) {
3764            eq
3765        } else {
3766            eq.not()
3767        })
3768    }
3769
3770    fn try_simple_iter_len(&self, arg_val: &VmValue<'z3, 'tcx>) -> Option<Int<'z3>> {
3771        if !self.is_iter_ref(arg_val) {
3772            return None;
3773        }
3774        let local = Local::from_usize(1);
3775        let ptr = self.field_value(local, &[0])?;
3776        let end = self.field_value(local, &[1])?;
3777        self.iter_len_from_ptrs(ptr, end)
3778    }
3779
3780    /// For a predicate of the form `self.len() != 0` (i.e. `!self.is_empty()`),
3781    /// if the self is an Iter/IterMut reference, return the field-based len term
3782    /// so that a `len >= 1` constraint can be added.
3783    fn try_simple_iter_len_from_pred(
3784        &self,
3785        pred: &crate::verify::contract::NumericPredicate<'tcx>,
3786    ) -> Option<Int<'z3>> {
3787        use crate::verify::contract::{ContractExpr, RelOp};
3788        if !matches!(pred.op, RelOp::Ne) {
3789            return None;
3790        }
3791        if !matches!(&pred.rhs, ContractExpr::Const(0)) {
3792            return None;
3793        }
3794        let ContractExpr::Len(inner) = &pred.lhs else {
3795            return None;
3796        };
3797        let val = self.eval_contract_expr_simple_value(inner)?;
3798        self.try_simple_iter_len(&val)
3799    }
3800
3801    /// If `local` is a reference to Iter/IterMut and field 0 (ptr)
3802    /// is updated, increment the cumulative ptr offset so that
3803    /// `interpreter_iter_len` can express `len = initial_len - offset`
3804    /// instead of nested `(end - (ptr + sz + sz + ...)) / sz`.
3805    fn track_iter_ptr_update(&mut self, local: Local) {
3806        let local_val = match self.local_value(local) {
3807            Some(v) => v,
3808            None => return,
3809        };
3810        if !self.is_iter_ref(local_val) {
3811            return;
3812        }
3813        let Some(buffer) = self.iter_buffer(local) else {
3814            return;
3815        };
3816        let one = Int::from_u64(self.z3_ctx, 1);
3817        let (new_offset, base_len) = match self.constraints.term_caches.iter_ptr_offset.get(&buffer) {
3818            Some((prev, base)) => (Int::add(self.z3_ctx, &[prev, &one]), base.clone()),
3819            None => {
3820                let base = self
3821                    .field_value(local, &[1])
3822                    .and_then(|end| end.provenance.as_ref())
3823                    .and_then(|ep| match &ep.offset_kind {
3824                        Some(OffsetKind::Element(e)) => Some(e.clone()),
3825                        _ => None,
3826                    });
3827                (one.clone(), base)
3828            }
3829        };
3830        // Mirror `try_iter_next`: the tracked offset must never exceed the
3831        // iterator's length (the inlined `post_inc_start` body itself only
3832        // mutates `ptr` and does not assert `offset <= len`).
3833        if let Some(e) = &base_len {
3834            self.constraints.assertions.push(new_offset.le(e));
3835        }
3836        self.constraints.term_caches.iter_ptr_offset.insert(buffer, (new_offset, base_len));
3837    }
3838
3839    /// Set non_null invariant on the target value.
3840    fn set_non_null_for_value(&mut self, property: &Property<'tcx>, mut val: VmValue<'z3, 'tcx>) {
3841        val.facts.non_null = true;
3842        self.set_contract_target_value(property, val);
3843    }
3844
3845    fn set_in_bounds_for_value(&mut self, property: &Property<'tcx>, mut val: VmValue<'z3, 'tcx>) {
3846        val.facts.in_bounds = true;
3847        self.set_contract_target_value(property, val);
3848    }
3849
3850    fn assert_in_bound_for_each(
3851        &mut self,
3852        property: &Property<'tcx>,
3853        fe_place: &crate::verify::contract::ContractPlace<'tcx>,
3854    ) {
3855        let Some(fe_local) = fe_place.base.try_to_local() else {
3856            return;
3857        };
3858        let fe_val = match self.local_value(fe_local).cloned() {
3859            Some(v) => v,
3860            None => return,
3861        };
3862        let fe_alloc_id = match fe_val.provenance_alloc_id() {
3863            Some(id) => id,
3864            None => return,
3865        };
3866        let byte_vals: Vec<(usize, Int<'z3>)> = self.alloc_byte_values(fe_alloc_id);
3867        if byte_vals.is_empty() {
3868            return;
3869        }
3870        let slice_local = match property.args().first() {
3871            Some(PropertyArg::Expr(ContractExpr::IndexAccess { slice, .. })) => {
3872                match slice.as_ref() {
3873                    ContractExpr::Place(cp) => cp.base.try_to_local(),
3874                    _ => None,
3875                }
3876            }
3877            Some(PropertyArg::Expr(ContractExpr::Place(cp))) => cp.base.try_to_local(),
3878            _ => None,
3879        };
3880        let slice_val = slice_local.and_then(|loc| self.local_value(loc));
3881        let slice_alloc_id = slice_val.and_then(|sl_val| sl_val.provenance_alloc_id());
3882        let data_size = slice_alloc_id.map(|da_id| self.alloc(da_id).size.clone());
3883        let elem_sz = slice_alloc_id
3884            .and_then(|da_id| self.alloc(da_id).element_ty.as_ty())
3885            .map(|ty| self.size_of_ty(ty))
3886            .unwrap_or(1)
3887            .max(1);
3888        let Some(data_size) = data_size else { return };
3889        let elem_sz_term = Int::from_u64(self.z3_ctx, elem_sz);
3890        // Prefer the materialized slice length; fall back to `size / elem_size`.
3891        let len = slice_val
3892            .and_then(|sl_val| self.slice_len_from_value(sl_val))
3893            .unwrap_or_else(|| data_size.div(&elem_sz_term));
3894        let zero = Int::from_u64(self.z3_ctx, 0);
3895        for (_, term) in &byte_vals {
3896            self.constraints.assertions.push(term.ge(&zero));
3897            self.constraints.assertions.push(term.lt(&len));
3898        }
3899    }
3900
3901    /// Record the numeric `index < len` bound for a single-index
3902    /// `InBound(slice, index)`, so a derived index (e.g. `index - 1` guarded by
3903    /// `index >= 1`) can be discharged by SMT rather than only via the coarse
3904    /// `in_bounds` flag. Range indices (`start..end`) are left to the checker's
3905    /// `extract_range_end` (`end <= len`).
3906    fn assert_in_bound_single(&mut self, property: &Property<'tcx>) {
3907        let Some(PropertyArg::Expr(ContractExpr::IndexAccess { slice, index })) =
3908            property.args().first()
3909        else {
3910            return;
3911        };
3912        if let ContractExpr::Place(index_place) = index.as_ref() {
3913            let index_local = index_place.base.to_local();
3914            if let rustc_middle::ty::TyKind::Adt(adt_def, _) =
3915                self.body().local_decls[index_local].ty.kind()
3916            {
3917                if crate::helpers::mir_utils::is_range_type(self.tcx, adt_def.did()) {
3918                    return;
3919                }
3920            }
3921        }
3922        let slice_local = match slice.as_ref() {
3923            ContractExpr::Place(cp) => cp.base.try_to_local(),
3924            _ => None,
3925        };
3926        let Some(slice_local) = slice_local else {
3927            return;
3928        };
3929        let Some(da_id) = self
3930            .local_value(slice_local)
3931            .and_then(|v| v.provenance_alloc_id())
3932        else {
3933            return;
3934        };
3935        // Prefer the materialized slice length; fall back to `size / elem_size`
3936        // for allocations that never got a materialized `slice_len`.
3937        let len = self
3938            .slice_len_of_alloc(da_id)
3939            .unwrap_or_else(|| self.alloc(da_id).size.clone());
3940        let Some(index_term) = self.eval_contract_expr_simple(index) else {
3941            return;
3942        };
3943        self.constraints.assertions.push(index_term.lt(&len));
3944    }
3945
3946    /// When a `&T`/`&mut T` reference is created (e.g. via `&*NonNull<T>`), assume
3947    /// `T`'s `#[rapx::invariant]`s on the new reference local, rebinding the
3948    /// invariant's `self` place to that reference. This is what lets a struct's
3949    /// invariants flow through pointer dereferences into the caller's state.
3950    fn assert_pointee_struct_invariants(&mut self, dest_ty: Ty<'tcx>, dest_local: Local) {
3951        let pointee = match dest_ty.kind() {
3952            rustc_middle::ty::TyKind::Ref(_, inner, _) => *inner,
3953            _ => return,
3954        };
3955        let adt_def = match pointee.kind() {
3956            rustc_middle::ty::TyKind::Adt(adt_def, _) => *adt_def,
3957            _ => return,
3958        };
3959        let invariants = crate::verify::target::get_struct_invariants_from_annotation(
3960            self.tcx,
3961            adt_def.did(),
3962            adt_def.did(),
3963        );
3964        for inv in &invariants {
3965            let mut rebound = inv.clone();
3966            rebind_property_place(&mut rebound, dest_local);
3967            self.assert_contract_fact(&rebound);
3968        }
3969    }
3970
3971    /// Assert a pointee ADT's `#[rapx::invariant]` struct invariants directly
3972    /// against its materialized per-allocation field values.  This is the
3973    /// pointer-field analogue of [`assert_pointee_struct_invariants`](Self::
3974    /// assert_pointee_struct_invariants): that helper covers `&T` references,
3975    /// while this one covers `NonNull<T>` / `Box<T>` pointer *fields* whose
3976    /// pointee is decomposed by `init_ptr_field` (e.g. `NodeRef.node:
3977    /// NonNull<LeafNode>` must satisfy `LeafNode`'s `len <= CAPACITY`).
3978    fn assert_alloc_pointee_invariants(&mut self, alloc_id: AllocId, pointee_ty: Ty<'tcx>) {
3979        let rustc_middle::ty::TyKind::Adt(adt_def, _) = pointee_ty.kind() else {
3980            return;
3981        };
3982        let invariants = crate::verify::target::get_struct_invariants_from_annotation(
3983            self.tcx,
3984            adt_def.did(),
3985            adt_def.did(),
3986        );
3987        for inv in &invariants {
3988            let Property::Atom(atom) = inv else {
3989                continue;
3990            };
3991            if atom.kind != PropertyKind::ValidNum {
3992                continue;
3993            }
3994            let Some(PropertyArg::Predicates(preds)) = atom.args.first() else {
3995                continue;
3996            };
3997            for pred in preds {
3998                if let Some(cond) = self.eval_pointee_predicate_as_bool(alloc_id, pointee_ty, pred)
3999                {
4000                    self.constraints.assertions.push(cond);
4001                }
4002            }
4003        }
4004    }
4005
4006    /// Evaluate a `ValidNum` predicate against a pointee allocation (not a MIR
4007    /// local): field places resolve through `MemoryContent::values` keyed by the
4008    /// pointee type.
4009    fn eval_pointee_predicate_as_bool(
4010        &self,
4011        alloc_id: AllocId,
4012        view_ty: Ty<'tcx>,
4013        pred: &crate::verify::contract::NumericPredicate<'tcx>,
4014    ) -> Option<Bool<'z3>> {
4015        let lhs = self.eval_pointee_expr(alloc_id, view_ty, &pred.lhs)?;
4016        let rhs = self.eval_pointee_expr(alloc_id, view_ty, &pred.rhs)?;
4017        Some(self.relop_to_bool(pred.op, &lhs, &rhs))
4018    }
4019
4020    /// Evaluate a numeric `ContractExpr` against a pointee allocation.
4021    fn eval_pointee_expr(
4022        &self,
4023        alloc_id: AllocId,
4024        view_ty: Ty<'tcx>,
4025        expr: &ContractExpr<'tcx>,
4026    ) -> Option<Int<'z3>> {
4027        match expr {
4028            ContractExpr::Const(v) => Some(Int::from_u64(self.z3_ctx, *v as u64)),
4029            ContractExpr::SizeOf(ty) => Some(self.size_sym_read(*ty)),
4030            ContractExpr::AlignOf(ty) => Some(self.align_sym_read(*ty)),
4031            ContractExpr::Place(cp) => {
4032                let path = cp.plain_field_path()?;
4033                self.units[alloc_id.0].content.values
4034                    .get(&(view_ty, path))
4035                    .map(|v| v.z3_term.clone())
4036            }
4037            ContractExpr::Binary { op, lhs, rhs } => {
4038                let l = self.eval_pointee_expr(alloc_id, view_ty, lhs)?;
4039                let r = self.eval_pointee_expr(alloc_id, view_ty, rhs)?;
4040                self.eval_numeric_binary(&l, &r, *op)
4041            }
4042            ContractExpr::Len(inner) => {
4043                let val = self.eval_pointee_expr_value(alloc_id, view_ty, inner)?;
4044                self.slice_len_from_value(&val)
4045            }
4046            _ => None,
4047        }
4048    }
4049
4050    /// Resolve a `Place` (or `Len` inner) against a pointee allocation, returning
4051    /// the materialized `VmValue` so slice-length fallbacks can read provenance.
4052    fn eval_pointee_expr_value(
4053        &self,
4054        alloc_id: AllocId,
4055        view_ty: Ty<'tcx>,
4056        expr: &ContractExpr<'tcx>,
4057    ) -> Option<VmValue<'z3, 'tcx>> {
4058        let ContractExpr::Place(cp) = expr else {
4059            return None;
4060        };
4061        let path = cp.plain_field_path()?;
4062        self.units[alloc_id.0].content.values
4063            .get(&(view_ty, path))
4064            .cloned()
4065    }
4066
4067    /// Set align invariant on the target value.
4068    fn set_align_for_value(&mut self, property: &Property<'tcx>, mut val: VmValue<'z3, 'tcx>) {
4069        if let Some(PropertyArg::Ty(ty)) = property.args().get(1) {
4070            let align = self.align_sym(*ty);
4071            let align_u64 = align.simplify().as_u64();
4072            if align_u64 != Some(1) {
4073                val.facts.align_n = Some(align.clone());
4074                // For a *concrete* alignment, also record `term % align == 0` as a
4075                // path condition.  `align_n` is a value invariant that pointer
4076                // arithmetic (`ptr.add(n)`) drops, but the alignment fact itself
4077                // persists: a downstream `Align(p, T)` on `p = ptr.add(n)` can then
4078                // combine `term % align == 0` with the `n % align == 0` mask fact to
4079                // prove `p % align == 0`.  (A symbolic `align_T` is skipped — the
4080                // non-linear `% align_T` is not decidable, so `align_n` alone is
4081                // used for that case.)
4082                if align_u64.is_some() {
4083                    self.constraints.assertions.push(
4084                        val.z3_term
4085                            .rem(&align)
4086                            ._eq(&Int::from_u64(self.z3_ctx, 0)),
4087                    );
4088                }
4089            }
4090        }
4091        self.set_contract_target_value(property, val);
4092    }
4093
4094    /// Set init invariant on the target value and its allocation.
4095    fn set_init_for_value(&mut self, property: &Property<'tcx>, val: VmValue<'z3, 'tcx>) {
4096        // `Init(p, MaybeUninit<T>, n)` reduces to `Typed(p, MaybeUninit<T>)`: the
4097        // content carries no validity invariant, so there is nothing to mark
4098        // initialized — the `Init ⇒ Typed` subsumption records the element type.
4099        let is_maybe_uninit = property
4100            .args()
4101            .get(1)
4102            .and_then(|a| {
4103                if let PropertyArg::Ty(ty) = a {
4104                    Some(api_classify::is_maybe_uninit_ty(*ty))
4105                } else {
4106                    None
4107                }
4108            })
4109            .unwrap_or(false);
4110        if is_maybe_uninit {
4111            return;
4112        }
4113        if let Some(prov) = &val.provenance {
4114            self.content_mut(prov.alloc_id).facts.initialized = true;
4115        }
4116        if let Some((local, path)) = self.contract_field_path(property) {
4117            let existing = if path.is_empty() {
4118                self.local_value(local).cloned()
4119            } else {
4120                self.field_value(local, &path).cloned()
4121            };
4122            if let Some(mut existing) = existing {
4123                self.mark_initialized(&mut existing);
4124                if path.is_empty() {
4125                    self.set_local(local, existing);
4126                } else {
4127                    self.set_field_value(local, path, existing);
4128                }
4129            }
4130        }
4131    }
4132
4133    /// Set owning invariant on the target value.
4134    fn set_owning_for_value(&mut self, val: VmValue<'z3, 'tcx>) {
4135        if let Some(prov) = &val.provenance {
4136            self.content_mut(prov.alloc_id).facts.initialized = true;
4137        }
4138    }
4139
4140    /// Record the `Align(container.iter(), T)` for_each fact: every element
4141    /// pointer is aligned to `align_of(T)`.  Anchored to the container
4142    /// allocation so a pointer loaded from it can discharge `Align(cur, T)`.
4143    fn record_for_each_align(&mut self, property: &Property<'tcx>) {
4144        if property.for_each().is_none() {
4145            return;
4146        }
4147        let Some(ty) = property.args().get(1).and_then(|a| match a {
4148            PropertyArg::Ty(ty) => Some(*ty),
4149            _ => None,
4150        }) else {
4151            return;
4152        };
4153        let Some(alloc_id) = self.contract_target_value(property).and_then(|v| v.provenance_alloc_id())
4154        else {
4155            return;
4156        };
4157        self.alloc_mut(alloc_id).facts.for_each.aligned_ty = Some(ty);
4158    }
4159
4160    /// Record the `Allocated(container.iter(), T, n)` for_each fact: every
4161    /// element pointer backs `>= n` `T` elements (`n` may be symbolic).
4162    fn record_for_each_allocated(&mut self, property: &Property<'tcx>) {
4163        if property.for_each().is_none() {
4164            return;
4165        }
4166        let Some(ty) = property.args().get(1).and_then(|a| match a {
4167            PropertyArg::Ty(ty) => Some(*ty),
4168            _ => None,
4169        }) else {
4170            return;
4171        };
4172        let count = property
4173            .args()
4174            .get(2)
4175            .and_then(|a| self.resolve_contract_count(a))
4176            .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
4177        let Some(alloc_id) = self.contract_target_value(property).and_then(|v| v.provenance_alloc_id())
4178        else {
4179            return;
4180        };
4181        self.alloc_mut(alloc_id).facts.for_each.allocated = Some((ty, count));
4182    }
4183
4184    /// Record the `Owning(container.iter())` for_each fact: every element
4185    /// pointer is the sole owner of its pointee.  Anchored to the container
4186    /// allocation so a pointer loaded from it can discharge `Owning(cur)`.
4187    fn record_for_each_owning(&mut self, property: &Property<'tcx>) {
4188        if property.for_each().is_none() {
4189            return;
4190        }
4191        let Some(alloc_id) = self.contract_target_value(property).and_then(|v| v.provenance_alloc_id())
4192        else {
4193            return;
4194        };
4195        self.alloc_mut(alloc_id).facts.for_each.owning = true;
4196    }
4197
4198    /// Extract the pointee type if `ty` is a `#[repr(transparent)]`
4199    /// single-field raw-pointer wrapper (`NonNull<P>`) or wrapped in
4200    /// `Option<NonNull<P>>`. Returns `Some(P)`.
4201    ///
4202    /// Detected structurally (via `#[repr(transparent)]` + a raw-pointer field)
4203    /// rather than by name, so re-implemented std types in the challenge
4204    /// suites are modelled identically to their std counterparts.
4205    fn find_nn_pointee(&self, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
4206        use rustc_middle::ty::TyKind;
4207        match ty.kind() {
4208            TyKind::Adt(adt_def, substs) => {
4209                if let Some(pointee) = self.transparent_ptr_pointee(adt_def, substs) {
4210                    return Some(pointee);
4211                }
4212                if self
4213                    .tcx
4214                    .is_diagnostic_item(rustc_span::sym::Option, adt_def.did())
4215                {
4216                    if let Some(inner) = substs.first().and_then(|s| s.as_type()) {
4217                        if let TyKind::Adt(ia, is_) = inner.kind() {
4218                            return self.transparent_ptr_pointee(ia, is_);
4219                        }
4220                    }
4221                }
4222                None
4223            }
4224            _ => None,
4225        }
4226    }
4227
4228    /// Pointee type of a `#[repr(transparent)]` single-field raw-pointer
4229    /// wrapper such as `NonNull<T>` (`struct NonNull<T> { pointer: *const T }`).
4230    fn transparent_ptr_pointee(
4231        &self,
4232        adt_def: &rustc_middle::ty::AdtDef,
4233        substs: rustc_middle::ty::GenericArgsRef<'tcx>,
4234    ) -> Option<Ty<'tcx>> {
4235        if !adt_def.repr().transparent() {
4236            return None;
4237        }
4238        let field = adt_def.non_enum_variant().fields.iter().next()?;
4239        let field_ty = crate::helpers::mir_utils::field_ty(self.tcx, field, substs);
4240        match field_ty.kind() {
4241            rustc_middle::ty::TyKind::RawPtr(pointee, _) => Some(*pointee),
4242            // NonNull's field is a pattern type `*const T is !null` on newer
4243            // toolchains; unwrap it to the underlying raw pointer.
4244            rustc_middle::ty::TyKind::Pat(inner, _) => match inner.kind() {
4245                rustc_middle::ty::TyKind::RawPtr(pointee, _) => Some(*pointee),
4246                _ => None,
4247            },
4248            _ => None,
4249        }
4250    }
4251
4252    pub(crate) fn container_ptr_field(&self, ty: Ty<'tcx>) -> Option<(Vec<usize>, Ty<'tcx>)> {
4253        self.container_ptr_field_inner(ty, Vec::new(), 0)
4254    }
4255
4256    fn container_ptr_field_inner(
4257        &self,
4258        ty: Ty<'tcx>,
4259        prefix: Vec<usize>,
4260        depth: usize,
4261    ) -> Option<(Vec<usize>, Ty<'tcx>)> {
4262        use rustc_middle::ty::TyKind;
4263        if depth > 4 {
4264            return None;
4265        }
4266        match ty.kind() {
4267            TyKind::Adt(adt, substs) => {
4268                if adt.is_enum() {
4269                    return None;
4270                }
4271                for (idx, field_def) in adt.non_enum_variant().fields.iter().enumerate() {
4272                    let field_ty =
4273                        crate::helpers::mir_utils::field_ty(self.tcx, field_def, substs);
4274                    let mut path = prefix.clone();
4275                    path.push(idx);
4276                    if let TyKind::RawPtr(inner, _) = field_ty.kind() {
4277                        return Some((path, *inner));
4278                    }
4279                    if let Some(pointee) = self.find_nn_pointee(field_ty) {
4280                        return Some((path, pointee));
4281                    }
4282                    if let Some(found) =
4283                        self.container_ptr_field_inner(field_ty, path, depth + 1)
4284                    {
4285                        return Some(found);
4286                    }
4287                }
4288                None
4289            }
4290            _ => None,
4291        }
4292    }
4293
4294    pub(crate) fn container_data_alloc(
4295        &self,
4296        header: AllocId,
4297        ty: Ty<'tcx>,
4298    ) -> Option<AllocId> {
4299        use rustc_middle::ty::TyKind;
4300        let mut view_ty = ty;
4301        loop {
4302            match view_ty.kind() {
4303                TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) => view_ty = *inner,
4304                _ => break,
4305            }
4306        }
4307        if !matches!(view_ty.kind(), TyKind::Adt(..)) {
4308            return None;
4309        }
4310        let (path, _) = self.container_ptr_field(view_ty)?;
4311        self.load_value(header, view_ty, &path)?.provenance_alloc_id()
4312    }
4313
4314    /// The data allocation behind a container header or a slice view's header.
4315    /// Tries the type-driven owning field first, then falls back to scanning the
4316    /// header's materialized fields for the first owning pointer (covers slice
4317    /// views whose header `AllocId` no longer carries a type key).
4318    pub(crate) fn data_alloc_of(&self, header: AllocId, ty: Ty<'tcx>) -> Option<AllocId> {
4319        self.container_data_alloc(header, ty).or_else(|| {
4320            // The header → data fallback is only for local (non-external) slice
4321            // views. An external parameter's header carries an `i64::MAX`
4322            // "unbounded" size that already discharges `Allocated`, so do not
4323            // redirect it to the (symbolic-sized) pointee.
4324            if self.alloc(header).is_external() {
4325                None
4326            } else {
4327                self.header_data_alloc(header)
4328            }
4329        })
4330    }
4331
4332    /// Scan a header allocation's materialized fields for the owning pointer
4333    /// (its provenance names the data allocation). This is the header → data
4334    /// fallback for slice views (whose provenance names the container header,
4335    /// not the data), replacing the old `slice_data` edge.
4336    ///
4337    /// Returns the provenance only when the header has *exactly one* owning
4338    /// pointer field. A multi-owning-pointer header (e.g. a linked list's
4339    /// `head`/`tail`) is ambiguous, so fall back to `None` rather than guess.
4340    fn header_data_alloc(&self, header: AllocId) -> Option<AllocId> {
4341        let mut result = None;
4342        for ((_, p), v) in self.units[header.0].content.values.iter() {
4343            if p.is_empty() {
4344                continue;
4345            }
4346            let Some(prov) = v.provenance_alloc_id() else {
4347                continue;
4348            };
4349            if result.is_some() {
4350                return None;
4351            }
4352            result = Some(prov);
4353        }
4354        result
4355    }
4356
4357    #[allow(clippy::too_many_arguments)]
4358    pub(crate) fn set_container_data_field(
4359        &mut self,
4360        header: AllocId,
4361        ty: Ty<'tcx>,
4362        data_alloc: AllocId,
4363        base: Int<'z3>,
4364        elem_ty: Ty<'tcx>,
4365    ) -> bool {
4366        use rustc_middle::ty::TyKind;
4367        let mut view_ty = ty;
4368        loop {
4369            match view_ty.kind() {
4370                TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) => view_ty = *inner,
4371                _ => break,
4372            }
4373        }
4374        if !matches!(view_ty.kind(), TyKind::Adt(..)) {
4375            return false;
4376        }
4377        let Some((path, _)) = self.container_ptr_field(view_ty) else {
4378            return false;
4379        };
4380        let ptr_field = VmValue {
4381            z3_term: base,
4382            ty: elem_ty,
4383            provenance: Some(Provenance {
4384                alloc_id: data_alloc,
4385                offset: Int::from_u64(self.z3_ctx, 0),
4386                offset_kind: None,
4387            }),
4388            facts: ValueFacts {
4389                non_null: true,
4390                init: true,
4391                in_bounds: true,
4392                ..ValueFacts::default()
4393            },
4394            source: ValueSource::None,
4395        };
4396        self.store_value(header, view_ty, path, ptr_field);
4397        true
4398    }
4399
4400    /// If `operand` is a constant reference to a byte array (e.g. `b"hello\0"`),
4401    /// extract the raw bytes and create a tracked allocation. Updates `val`
4402    /// in-place with the proper provenance and invariants.
4403    pub(crate) fn try_materialize_const_bytes(
4404        &mut self,
4405        val: &mut VmValue<'z3, 'tcx>,
4406        operand: &Operand<'tcx>,
4407    ) {
4408        // Use the operand's type (before any pointer cast) to check for byte arrays.
4409        let operand_val = self.value_of_operand(operand);
4410        let op_ty = operand_val.ty;
4411        let (pointee_ty, _is_ref) = match op_ty.kind() {
4412            rustc_middle::ty::TyKind::Ref(_, inner_ty, _) => (*inner_ty, true),
4413            rustc_middle::ty::TyKind::RawPtr(inner_ty, _) => (*inner_ty, false),
4414            _ => {
4415                // Fallback: use val's type
4416                let val_ty = val.ty;
4417                match val_ty.kind() {
4418                    rustc_middle::ty::TyKind::Ref(_, inner_ty, _) => (*inner_ty, true),
4419                    rustc_middle::ty::TyKind::RawPtr(inner_ty, _) => (*inner_ty, false),
4420                    _ => return,
4421                }
4422            }
4423        };
4424        match pointee_ty.kind() {
4425            rustc_middle::ty::TyKind::Array(elem_ty, _)
4426            | rustc_middle::ty::TyKind::Slice(elem_ty) => {
4427                let is_byte = matches!(
4428                    elem_ty.kind(),
4429                    rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8)
4430                        | rustc_middle::ty::TyKind::Int(rustc_middle::ty::IntTy::I8)
4431                );
4432                if is_byte {
4433                    let bytes_opt =
4434                        crate::helpers::mir_utils::const_operand_bytes(self.tcx, operand)
4435                            .or_else(|| self.trace_to_const_bytes(operand));
4436                    if let Some(bytes) = bytes_opt {
4437                        let size = z3::ast::Int::from_u64(self.z3_ctx, bytes.len() as u64);
4438                        let align = self.align_sym(pointee_ty);
4439                        let (alloc_id, base) = self.allocate(size, align, Some(pointee_ty));
4440                        self.content_mut(alloc_id).facts.initialized = true;
4441                        // A const/static byte materialization lives for the
4442                        // whole program (`'static`), so it is always alive.
4443                        self.alloc_mut(alloc_id).facts.liveness =
4444                            Some(self.tcx.lifetimes.re_static);
4445                        for (i, &b) in bytes.iter().enumerate() {
4446                            self.record_byte_value(
4447                                alloc_id,
4448                                i,
4449                                z3::ast::Int::from_u64(self.z3_ctx, b as u64),
4450                            );
4451                        }
4452                        val.z3_term = base;
4453                        val.provenance = Some(super::state::Provenance {
4454                            alloc_id,
4455                            offset: z3::ast::Int::from_u64(self.z3_ctx, 0),
4456                            offset_kind: None,
4457                        });
4458                        val.facts = ValueFacts {
4459                            non_null: true,
4460                            init: true,
4461                            in_bounds: false,
4462                            align_n: None,
4463                        };
4464                    }
4465                }
4466            }
4467            _ => {}
4468        }
4469    }
4470
4471    pub(crate) fn trace_to_const_bytes(&self, operand: &Operand<'tcx>) -> Option<Vec<u8>> {
4472        let place = match operand {
4473            Operand::Copy(p) | Operand::Move(p) => p,
4474            _ => return None,
4475        };
4476        let base_local = if place.projection.is_empty()
4477            || (place.projection.len() == 1
4478                && matches!(
4479                    place.projection.first().map(|p| p.kind()),
4480                    Some(rustc_middle::mir::ProjectionElem::Deref)
4481                ))
4482        {
4483            place.local
4484        } else {
4485            return None;
4486        };
4487        for block in self.body().basic_blocks.iter() {
4488            for stmt in &block.statements {
4489                if let StatementKind::Assign(assign) = &stmt.kind {
4490                    let (dest, rvalue) = &**assign;
4491                    if dest.local != base_local || !dest.projection.is_empty() {
4492                        continue;
4493                    }
4494                    match rvalue {
4495                        #[cfg(rapx_rvalue_use_with_retag)]
4496                        Rvalue::Use(op, _) => {
4497                            return crate::helpers::mir_utils::const_operand_bytes(self.tcx, op)
4498                                .or_else(|| self.trace_to_const_bytes(op));
4499                        }
4500                        #[cfg(not(rapx_rvalue_use_with_retag))]
4501                        Rvalue::Use(op) => {
4502                            return crate::helpers::mir_utils::const_operand_bytes(self.tcx, op)
4503                                .or_else(|| self.trace_to_const_bytes(op));
4504                        }
4505                        Rvalue::Ref(_, _, p) => {
4506                            let op = Operand::Copy(*p);
4507                            return self.trace_to_const_bytes(&op);
4508                        }
4509                        _ => return None,
4510                    }
4511                }
4512            }
4513        }
4514        None
4515    }
4516
4517    /// Propagate byte values from a source place's allocation to the
4518    /// provenance allocation of a reference. This ensures that when we
4519    /// create `&bytes` from an aggregate, the byte-level tracking follows.
4520    /// Propagate a source place's per-field values to a reference destination,
4521    /// shifting the field path by the source place's `Field` projection prefix.
4522    /// E.g. for `_3 = &(_1.0)` where `_1` is a `Handle { node: NodeRef { node:
4523    /// NonNull<..>, .. }, .. }`, the nested `NonNull`'s field value stored at
4524    /// path `[0, 1]` becomes available at `_3`'s path `[1]`, so an inlined
4525    /// callee that dereferences `_3` and reads its `node` field sees the
4526    /// provenance of the underlying allocation.
4527    fn propagate_field_values_to_ref(&mut self, source_place: &Place<'tcx>, dest: Local) {
4528        // Support both `&(local.field...)` (Field projection prefix) and
4529        // `&(*local)` (reborrow of a reference, pure Deref).  In the latter
4530        // case the reference's own per-field values already describe the
4531        // pointee, so they are copied unchanged.
4532        let only_field_deref = source_place.projection.iter().all(|p| {
4533            matches!(
4534                p.kind(),
4535                rustc_middle::mir::ProjectionElem::Field(..)
4536                    | rustc_middle::mir::ProjectionElem::Deref
4537            )
4538        });
4539        if !only_field_deref {
4540            return;
4541        }
4542        let field_prefix: Vec<usize> = source_place
4543            .projection
4544            .iter()
4545            .filter_map(|p| match p.kind() {
4546                rustc_middle::mir::ProjectionElem::Field(fi, _) => Some(fi.as_usize()),
4547                _ => None,
4548            })
4549            .collect();
4550        // A bare `&local` (no projection) exposes the pointee's whole field
4551        // map. Only propagate fields that carry provenance (pointer leaves);
4552        // plain scalar fields (e.g. array elements) must not leak into the
4553        // reference or they can corrupt downstream InBound reasoning.
4554        let empty_proj = source_place.projection.is_empty();
4555        let keys: Vec<Vec<usize>> = self.field_paths(source_place.local);
4556        for path in keys {
4557            let matches_prefix = field_prefix.is_empty()
4558                || (path.len() >= field_prefix.len()
4559                    && path[..field_prefix.len()] == field_prefix[..]);
4560            if matches_prefix {
4561                let rest = if field_prefix.is_empty() {
4562                    path.clone()
4563                } else {
4564                    path[field_prefix.len()..].to_vec()
4565                };
4566                // `rest == []` means the pointee *is* `source_place` itself
4567                // (e.g. `&mut self.v`).  Do not mirror it into the reference's
4568                // `path == []`: that slot now holds the reference's whole value
4569                // (see M2), so writing the pointee there would clobber it.  The
4570                // pointee value stays on the *source* field and is recovered by
4571                // `ReturnDerefArg`'s field-map fallback instead.
4572                if rest.is_empty() {
4573                    continue;
4574                }
4575                if let Some(v) = self.field_value(source_place.local, &path).cloned() {
4576                    if empty_proj && v.provenance.is_none() {
4577                        continue;
4578                    }
4579                    self.set_field_value(dest, rest, v);
4580                }
4581            }
4582        }
4583    }
4584
4585    fn propagate_byte_values_to_ref(
4586        &mut self,
4587        source_place: &Place<'tcx>,
4588        ref_val: &VmValue<'z3, 'tcx>,
4589    ) {
4590        let Some(src_alloc_id) = self.current_frame.local_alloc.get(&source_place.local).copied() else {
4591            return;
4592        };
4593        let Some(ref_alloc_id) = ref_val.provenance_alloc_id() else {
4594            return;
4595        };
4596        if src_alloc_id == ref_alloc_id {
4597            return; // same allocation, bytes already there
4598        }
4599        // Copy per-byte tracking from source alloc to ref's alloc (offset 0:
4600        // the reference points at the source place's start).
4601        self.copy_byte_tracking(src_alloc_id, 0, ref_alloc_id);
4602    }
4603
4604    /// Return the per-field types for an aggregate's operands.
4605    fn aggregate_field_tys(&self, ty: Ty<'tcx>) -> Vec<Ty<'tcx>> {
4606        match ty.kind() {
4607            rustc_middle::ty::TyKind::Array(elem_ty, _len) => {
4608                // We don't need the exact count — just the element type for size
4609                vec![*elem_ty]
4610            }
4611            rustc_middle::ty::TyKind::Tuple(elems) => elems.iter().collect(),
4612            rustc_middle::ty::TyKind::Adt(adt_def, substs) => {
4613                if adt_def.is_enum() {
4614                    return vec![];
4615                }
4616                let variant = adt_def.non_enum_variant();
4617                variant
4618                    .fields
4619                    .iter()
4620                    .map(|f| crate::helpers::mir_utils::field_ty(self.tcx, f, substs))
4621                    .collect()
4622            }
4623            _ => vec![],
4624        }
4625    }
4626}
4627
4628/// Whether any atom in this (possibly compound) property is a hazard
4629/// (`ContractKind::Hazard`), which the caller explicitly opts into.
4630fn contains_hazard<'tcx>(property: &Property<'tcx>) -> bool {
4631    if property.contract_kind() == ContractKind::Hazard {
4632        return true;
4633    }
4634    match property {
4635        Property::And(and) => and.conjuncts.iter().any(|p| contains_hazard(p)),
4636        Property::Or(or) => or.disjuncts.iter().any(|p| contains_hazard(p)),
4637        Property::Atom(_) => false,
4638    }
4639}
4640
4641/// Try to resolve a u64 constant from a PlaceKey's source in the VM state.
4642fn resolve_u64_from_place_key<'z3, 'tcx>(
4643    pk: &Option<PlaceKey>,
4644    state: &VmState<'z3, 'tcx>,
4645) -> Option<u64> {
4646    let pk = pk.as_ref()?;
4647    let local = pk.local()?;
4648    let val = state.local_value(local)?;
4649    val.z3_term.as_u64()
4650}
4651
4652/// Largest power-of-two factor of a non-negative constant (the alignment
4653/// implied by multiplying by `c`): `c` itself if it is a power of two,
4654/// otherwise `2^trailing_zeros(c)`.
4655fn pow2_factor(c: u64) -> Option<u64> {
4656    if c > 0 && c.is_power_of_two() {
4657        Some(c)
4658    } else if c > 0 {
4659        let factor = 1u64 << c.trailing_zeros();
4660        if factor > 1 { Some(factor) } else { None }
4661    } else {
4662        None
4663    }
4664}
4665
4666/// Rebind every `self` place in a struct invariant to the given MIR local, so
4667/// an invariant parsed against the struct's own `self` can be asserted on a
4668/// freshly-created reference (`&*NonNull<T>` → `&T`).
4669fn rebind_property_place<'tcx>(property: &mut Property<'tcx>, local: Local) {
4670    match property {
4671        Property::Atom(atom) => {
4672            for arg in &mut atom.args {
4673                match arg {
4674                    PropertyArg::Expr(expr) => rebind_expr_place(expr, local),
4675                    PropertyArg::Predicates(preds) => {
4676                        for pred in preds {
4677                            rebind_expr_place(&mut pred.lhs, local);
4678                            rebind_expr_place(&mut pred.rhs, local);
4679                        }
4680                    }
4681                    _ => {}
4682                }
4683            }
4684        }
4685        Property::And(and) => {
4686            for c in &mut and.conjuncts {
4687                rebind_property_place(c, local);
4688            }
4689        }
4690        Property::Or(or) => {
4691            for d in &mut or.disjuncts {
4692                rebind_property_place(d, local);
4693            }
4694        }
4695    }
4696}
4697
4698fn rebind_expr_place<'tcx>(expr: &mut ContractExpr<'tcx>, local: Local) {
4699    match expr {
4700        ContractExpr::Place(cp) => {
4701            cp.base = PlaceBase::Local(local.as_usize());
4702        }
4703        ContractExpr::Len(inner) => rebind_expr_place(inner, local),
4704        ContractExpr::IndexAccess { slice, index } => {
4705            rebind_expr_place(slice, local);
4706            rebind_expr_place(index, local);
4707        }
4708        ContractExpr::Binary { lhs, rhs, .. } => {
4709            rebind_expr_place(lhs, local);
4710            rebind_expr_place(rhs, local);
4711        }
4712        ContractExpr::Unary { expr: inner, .. } => rebind_expr_place(inner, local),
4713        ContractExpr::If {
4714            cond,
4715            then_expr,
4716            else_expr,
4717        } => {
4718            rebind_expr_place(&mut cond.lhs, local);
4719            rebind_expr_place(&mut cond.rhs, local);
4720            rebind_expr_place(then_expr, local);
4721            rebind_expr_place(else_expr, local);
4722        }
4723        _ => {}
4724    }
4725}