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_middle::{
8    mir::{
9        BasicBlock, BinOp, Local, Operand, Place, Rvalue,
10        Statement, StatementKind, Terminator, TerminatorKind, UnOp,
11    },
12    ty::Ty,
13};
14#[cfg(not(rapx_has_skip_norm_wip))]
15use crate::compat::SkipNormWip;
16use rustc_hir::def_id::DefId;
17use z3::ast::{Ast, Bool, Int};
18
19use crate::{
20    compat::{FxHashMap, FxHashSet},
21    verify::{
22        contract::{ContractExpr, ContractKind, PlaceBase, Property, PropertyArg, PropertyKind},
23        def_use::PlaceKey,
24        path_extractor::{Path, PathStep},
25        slicer::RelevantItem,
26    },
27};
28
29use super::state::{AllocId, InlineFrame, Provenance, VmState, VmValue, ValueInvariants};
30
31use crate::helpers::api_classify;
32
33impl<'ctx, 'tcx> VmState<'ctx, 'tcx> {
34    /// Execute all retained MIR items in path order.
35    pub fn execute_items(
36        &mut self,
37        items: &[RelevantItem<'tcx>],
38    ) {
39        // Initialize function parameters as fresh symbolic values.
40        // Parameters are _1.._N (excluding _0 return value).
41        self.init_parameters();
42
43        for item in items {
44            match item {
45                RelevantItem::CalleeEntry { callee, args } => {
46                    self.handle_callee_entry(*callee, args);
47                }
48                RelevantItem::Statement {
49                    block,
50                    statement_index,
51                } => {
52                    let statement =
53                        &self.body.basic_blocks[*block].statements[*statement_index];
54                    self.exec_statement(*block, *statement_index, statement);
55                }
56                RelevantItem::Terminator { block } => {
57                    let occ = self
58                        .block_occurrences
59                        .get(block)
60                        .map(|c| c + 1)
61                        .unwrap_or(1);
62                    self.block_occurrences.insert(*block, occ);
63                    let terminator = self.body.basic_blocks[*block].terminator();
64                    self.exec_terminator(*block, terminator, occ);
65                }
66                RelevantItem::ContractFact { property } => {
67                    self.assert_contract_fact(property);
68                }
69                RelevantItem::Forget => {
70                    self.notes.push("forget: unsupported call".to_string());
71                }
72                RelevantItem::CalleeExit { dest } => {
73                    self.handle_callee_exit(*dest);
74                }
75            }
76        }
77    }
78
79    /// Enter a callee's function context during sliced inline execution.
80    /// Saves the caller's locals state, pushes the callee body onto the
81    /// context stack, and binds caller args to callee parameters.
82    fn handle_callee_entry(
83        &mut self,
84        callee_def_id: DefId,
85        arg_locals: &[Local],
86    ) {
87        let callee_body = self.tcx.optimized_mir(callee_def_id);
88
89        // Save caller's locals
90        let saved_locals = std::mem::take(&mut self.locals);
91
92        // Clone the arg values we need before pushing context
93        let arg_vals: Vec<Option<VmValue<'ctx, 'tcx>>> = arg_locals.iter()
94            .map(|&local| saved_locals.get(&local).cloned())
95            .collect();
96
97        // Push callee context
98        self.inline_frames.push(InlineFrame {
99            body: self.body,
100            def_id: self.caller_def_id,
101            saved_locals,
102        });
103        self.body = callee_body;
104        self.caller_def_id = callee_def_id;
105
106        // Bind args from saved caller state
107        for (i, arg_val) in arg_vals.into_iter().enumerate() {
108            let callee_local = Local::from_usize(i + 1);
109            if let Some(val) = arg_val {
110                self.ensure_local_allocation(callee_local);
111                self.set_local(callee_local, val);
112            }
113        }
114        // Propagate field_values from caller arg locals to callee param
115        // locals, so that inlined callee body can access struct fields
116        // (e.g. Iter::ptr / end_or_len for len/is_empty computations).
117        for (i, caller_arg) in arg_locals.iter().enumerate() {
118            let callee_param = Local::from_usize(i + 1);
119            let caller_field_keys: Vec<Vec<usize>> = self.field_values.keys()
120                .filter(|(l, _)| *l == *caller_arg)
121                .map(|(_, f)| f.clone())
122                .collect();
123            for fields in caller_field_keys {
124                if let Some(fv) = self.field_value(*caller_arg, &fields).cloned() {
125                    self.set_field_value(callee_param, fields, fv);
126                }
127            }
128        }
129    }
130
131    /// Exit a callee's function context. Captures the return value from
132    /// callee's local_0, restores the caller's locals and body, and writes
133    /// the return value to the caller's dest local.
134    fn handle_callee_exit(
135        &mut self,
136        dest: Local,
137    ) {
138        let return_val = self.locals.get(&Local::from_usize(0)).cloned();
139
140        // Capture the callee's return-place field values (e.g. a tuple return
141        // `(prefix, mid, suffix)` whose slice fields carry provenance). They
142        // live in the shared `field_values` map keyed by local `_0`, so they
143        // must be re-keyed onto the caller's destination local, otherwise the
144        // caller's `(tuple.0)` / `(tuple.2)` projections lose provenance and
145        // the downstream `.len()` / alignment reasoning collapses.
146        let return_fields: Vec<(Vec<usize>, VmValue<'ctx, 'tcx>)> = self
147            .field_values
148            .keys()
149            .filter(|(l, _)| *l == Local::from_usize(0))
150            .cloned()
151            .collect::<Vec<_>>()
152            .into_iter()
153            .filter_map(|(_, fields)| {
154                self.field_value(Local::from_usize(0), &fields)
155                    .cloned()
156                    .map(|v| (fields, v))
157            })
158            .collect();
159
160        // Check if the callee was post_inc_start / pre_dec_end
161        // on an Iter/IterMut. If so, track the ptr offset change.
162        if let Some(frame) = self.inline_frames.last() {
163            let name = self.tcx.def_path_str(frame.def_id);
164            if api_classify::is_iter_ptr_adj(&name) {
165                self.track_iter_ptr_after_inline();
166            }
167        }
168
169        // Restore caller context
170        if let Some(frame) = self.inline_frames.pop() {
171            self.body = frame.body;
172            self.caller_def_id = frame.def_id;
173            self.locals = frame.saved_locals;
174        }
175
176        // Write return value
177        if let Some(mut val) = return_val {
178            let dest_ty = self.body.local_decls[dest].ty;
179            val.ty = dest_ty;
180            if let Some(ref prov) = val.provenance {
181                if prov.offset.as_u64() == Some(0) {
182                    val.invariants.non_null = true;
183                    val.invariants.init = true;
184                    val.invariants.aligned = true;
185                    self.alloc_mut(prov.alloc_id).initialized = true;
186                }
187            }
188            self.set_local(dest, val);
189        }
190
191        // The callee returned a fully-constructed value, so the caller's
192        // destination stack slot is initialized.  Matters for ADT returns
193        // (struct/enum) whose aggregate value carries no provenance: a later
194        // `&raw const (*&field)` + `ptr::read` must discharge `Init` against
195        // the field.
196        if let Some(dest_alloc_id) = self.local_alloc_ids.get(&dest).copied() {
197            self.alloc_mut(dest_alloc_id).initialized = true;
198        }
199
200        // Re-key the callee's return field values onto the caller's dest local.
201        for (fields, field_val) in return_fields {
202            self.set_field_value(dest, fields, field_val);
203        }
204    }
205
206    // ── Initialization ──────────────────────────────────────────
207
208    fn init_parameters(&mut self) {
209        let arg_count = self.body.arg_count;
210        let local_count = self.body.local_decls.len();
211
212        // Pre-allocate ALL locals and set initial values
213        for local_idx in 1..local_count {
214            let local = Local::from_usize(local_idx);
215            if self.locals.contains_key(&local) {
216                continue;
217            }
218            let decl = &self.body.local_decls[local];
219            let ty = decl.ty;
220
221            self.ensure_local_allocation(local);
222
223            let mut invariants = ValueInvariants::default();
224            if local_idx <= arg_count {
225                // ── Box / Vec parameter: heap-allocated pointee ──
226                if let rustc_middle::ty::TyKind::Adt(adt_def, _) = ty.kind() {
227                    let def_path = self.tcx.def_path_str(adt_def.did());
228                    let is_vec = api_classify::is_std_vec(&def_path);
229                    if api_classify::is_std_box(&def_path)
230                        || is_vec
231                        || api_classify::is_std_cstring(&def_path)
232                    {
233                        let heap_ty = if let rustc_middle::ty::TyKind::Adt(_, substs) = ty.kind() {
234                            if let Some(first) = substs.first() {
235                                first.as_type()
236                            } else {
237                                None
238                            }
239                        } else {
240                            None
241                        };
242                        let heap_ty = heap_ty.unwrap_or(ty);
243                        let heap_size = self.size_of_ty(heap_ty) as u64;
244                        let heap_align = self.align_of_ty(heap_ty);
245                        let heap_size_term = Int::from_u64(self.ctx, heap_size.max(1));
246                        // Vec/CString can hold many elements — use an external
247                        // allocation so Allocated checks can pass for arbitrary
248                        // capacity queries.
249                        let (heap_alloc_id, heap_base) = if is_vec {
250                            let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
251                            let (id, base) = self.allocate_external(max_size, heap_align, Some(heap_ty));
252                            (id, base)
253                        } else {
254                            self.allocate(heap_size_term, heap_align, Some(heap_ty))
255                        };
256                        invariants.non_null = true;
257                        invariants.init = true;
258                        invariants.aligned = true;
259                        self.alloc_mut(heap_alloc_id).initialized = true;
260                        // Also expose the box's inner `Unique<T>.pointer` field
261                        // (a `NonNull<T>` at path [0, 0]) so that inlined bodies
262                        // like `Box::into_non_null_with_allocator` — which reads
263                        // `(_1.0).0` and transmutes it to `NonNull<T>` — inherit
264                        // the heap pointer's non-null/aligned/allocated facts.
265                        self.set_field_value(local, vec![0, 0], VmValue {
266                            term: heap_base.clone(),
267                            ty,
268                            provenance: Some(Provenance {
269                                alloc_id: heap_alloc_id,
270                                offset: Int::from_u64(self.ctx, 0),
271                                is_field_offset: false,
272                            }),
273                            invariants: ValueInvariants {
274                                non_null: true,
275                                init: true,
276                                aligned: true,
277                                ..Default::default()
278                            },
279                        });
280                        self.set_local(local, VmValue {
281                            term: heap_base,
282                            ty,
283                            provenance: Some(Provenance {
284                                alloc_id: heap_alloc_id,
285                                offset: Int::from_u64(self.ctx, 0),
286                                is_field_offset: false,
287                            }),
288                            invariants,
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, substs) = ty.kind() {
296                    if adt_def.is_enum() {
297                        let term = self.fresh_int(&format!("param_{}", local_idx));
298                        self.set_local(local, VmValue {
299                            term, ty, provenance: None, invariants,
300                        });
301                        continue;
302                    }
303                    let variant = adt_def.non_enum_variant();
304                    let mut elem_alloc: FxHashMap<Ty<'tcx>, (AllocId, Int<'ctx>)> =
305                        FxHashMap::default();
306                    for (idx, field_def) in variant.fields.iter().enumerate() {
307                        let field_ty: Ty<'tcx> = field_def.ty(self.tcx, substs).skip_norm_wip();
308                        if let rustc_middle::ty::TyKind::RawPtr(inner, _) = field_ty.kind() {
309                            self.init_ptr_field(local, vec![idx], field_ty, *inner, local_idx, idx, &mut elem_alloc, true, "field_nn");
310                        } else if let Some(pointee) = self.find_nn_pointee(field_ty) {
311                            self.init_ptr_field(local, vec![idx], field_ty, pointee, local_idx, idx, &mut elem_alloc, false, "field_nn");
312                        } else if let rustc_middle::ty::TyKind::Adt(inner_adt, _) = field_ty.kind() {
313                            if !inner_adt.is_enum() {
314                                self.decompose_adt_fields(local, vec![idx], field_ty, local_idx, &mut elem_alloc, 1);
315                            } else {
316                                let field_term = self.fresh_int(&format!("field_{}_{}", local_idx, idx));
317                                self.set_field_value(local, vec![idx], VmValue {
318                                    term: field_term, ty: field_ty, provenance: None,
319                                    invariants: ValueInvariants { init: true, ..Default::default() },
320                                });
321                            }
322                        } else {
323                            let field_term = self.fresh_int(
324                                &format!("field_{}_{}", local_idx, idx)
325                            );
326                            self.set_field_value(local, vec![idx], VmValue {
327                                term: field_term,
328                                ty: field_ty,
329                                provenance: None,
330                                invariants: ValueInvariants { init: true, ..Default::default() },
331                            });
332                        }
333                    }
334                    let term = self.fresh_int(&format!("param_{}", local_idx));
335                    self.set_local(local, VmValue {
336                        term,
337                        ty,
338                        provenance: None,
339                        invariants: ValueInvariants { init: true, ..Default::default() },
340                    });
341                    continue;
342                }
343                // ── Reference parameter (&T, &mut T) ──
344                // Create a symbolic allocation for the pointee and attach
345                // provenance so that pointer-deriving operations (as_ptr,
346                // add, etc.) propagate correctly.
347                if let rustc_middle::ty::TyKind::Ref(..) = ty.kind() {
348                    invariants.non_null = true;
349                    invariants.init = true;
350                    invariants.aligned = true;
351
352                    let pointee_ty = if let rustc_middle::ty::TyKind::Ref(_, inner_ty, _) = ty.kind() {
353                        *inner_ty
354                    } else {
355                        ty
356                    };
357
358                    if let rustc_middle::ty::TyKind::Slice(elem_ty) = pointee_ty.kind() {
359                        let elem_size = self.size_of_ty(*elem_ty) as u64;
360                        let len = self.fresh_int(&format!("slice_len_{}", local_idx));
361                        let zero = Int::from_u64(self.ctx, 0);
362                        self.path_conditions.push(len.ge(&zero));
363                        let isize_max = Int::from_i64(self.ctx, i64::MAX);
364                        let elem_sz = if elem_size > 0 {
365                            elem_size
366                        } else {
367                            crate::helpers::mir_utils::size_of_generic_param(self.tcx, self.caller_def_id, *elem_ty).max(1)
368                        };
369                        let elem_sz_term = Int::from_u64(self.ctx, elem_sz);
370                        self.path_conditions.push(
371                            Int::mul(self.ctx, &[&len, &elem_sz_term]).le(&isize_max));
372                        let data_size = Int::mul(self.ctx, &[
373                            &len,
374                            &Int::from_u64(self.ctx, elem_size.max(1)),
375                        ]);
376                        let (data_alloc_id, data_base) = self.allocate(
377                            data_size,
378                            self.align_of_ty(*elem_ty),
379                            Some(*elem_ty),
380                        );
381                        if let Some(ref_alloc_id) = self.alloc_for_local(local) {
382                            self.alloc_mut(ref_alloc_id).slice_data = Some(data_alloc_id);
383                        }
384                        self.alloc_mut(data_alloc_id).initialized = true;
385                        self.set_local(local, VmValue {
386                            term: data_base,
387                            ty,
388                            provenance: Some(Provenance {
389                                alloc_id: data_alloc_id,
390                                offset: Int::from_u64(self.ctx, 0),
391                                is_field_offset: false,
392                            }),
393                            invariants,
394                        });
395                        continue;
396                    }
397
398                    // Non-slice reference: allocate pointee
399                    let pointee_size = self.size_of_ty(pointee_ty) as u64;
400                    let pointee_align = self.align_of_ty(pointee_ty);
401                    let pointee_size_term = Int::from_u64(self.ctx, pointee_size.max(1));
402                    let (pointee_alloc_id, pointee_base) = self.allocate(
403                        pointee_size_term,
404                        pointee_align,
405                        Some(pointee_ty),
406                    );
407                    self.alloc_mut(pointee_alloc_id).initialized = true;
408                    self.set_local(local, VmValue {
409                        term: pointee_base,
410                        ty,
411                        provenance: Some(Provenance {
412                            alloc_id: pointee_alloc_id,
413                            offset: Int::from_u64(self.ctx, 0),
414                            is_field_offset: false,
415                        }),
416                        invariants,
417                    });
418
419                    // Decompose struct fields for pointer-field access.
420                    // E.g. &RawBuf → (*self).ptr should yield a valid raw ptr.
421                    if let rustc_middle::ty::TyKind::Adt(adt_def, substs) = pointee_ty.kind() {
422                        if !adt_def.is_enum() {
423                            let variant = adt_def.non_enum_variant();
424                            // Track the first data allocation per element type.
425                            // Subsequent RawPtr / NonNull fields with the same
426                            // pointee type reuse the allocation with per-field
427                            // symbolic offsets, preserving the field relationships
428                            // (e.g. ptr=start, end_or_len=start+len).
429                            let mut elem_alloc: FxHashMap<Ty<'tcx>, (AllocId, Int<'ctx>)> =
430                                FxHashMap::default();
431                            for (idx, field_def) in variant.fields.iter().enumerate() {
432                                let field_ty: Ty<'tcx> = field_def.ty(self.tcx, substs).skip_norm_wip();
433                                if let rustc_middle::ty::TyKind::RawPtr(inner, _) = field_ty.kind() {
434                                    self.init_ptr_field(local, vec![idx], field_ty, *inner, local_idx, idx, &mut elem_alloc, true, "field_nn");
435                                } else if let Some(pointee) = self.find_nn_pointee(field_ty) {
436                                    // Field contains NonNull<T> (possibly wrapped in Option):
437                                    // create/reuse an external allocation for the pointee.
438                                    self.init_ptr_field(local, vec![idx], field_ty, pointee, local_idx, idx, &mut elem_alloc, false, "ref_field");
439                                } else if let rustc_middle::ty::TyKind::Ref(_, pointee, _) = field_ty.kind() {
440                                    // Field contains a reference (&T, &mut T, &[T], etc.).
441                                    // Give it provenance so that as_ptr() / as_mut_ptr()
442                                    // on the field propagates the allocation info.
443                                    if let rustc_middle::ty::TyKind::Slice(elem_ty) = pointee.kind() {
444                                        let elem_align = 1u64.max(self.align_of_ty(*elem_ty));
445                                        let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
446                                        let (data_alloc_id, data_base) = self.allocate_external(
447                                            max_size, elem_align, Some(*elem_ty),
448                                        );
449                                        self.alloc_mut(data_alloc_id).initialized = true;
450                                        self.alloc_mut(data_alloc_id).alive_assumed = true;
451                                        self.set_field_value(local, vec![idx], VmValue {
452                                            term: data_base,
453                                            ty: field_ty,
454                                            provenance: Some(Provenance {
455                                                alloc_id: data_alloc_id,
456                                                offset: Int::from_u64(self.ctx, 0),
457                                                is_field_offset: false,
458                                            }),
459                                            invariants: ValueInvariants {
460                                                non_null: true, init: true, ..Default::default()
461                                            },
462                                        });
463                                    } else {
464                                        let pointee_align = 1u64.max(self.align_of_ty(*pointee));
465                                        let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
466                                        let (field_alloc_id, field_base) = self.allocate_external(
467                                            max_size, pointee_align, Some(*pointee),
468                                        );
469                                        self.alloc_mut(field_alloc_id).initialized = true;
470                                        self.alloc_mut(field_alloc_id).alive_assumed = true;
471                                        self.set_field_value(local, vec![idx], VmValue {
472                                            term: field_base,
473                                            ty: field_ty,
474                                            provenance: Some(Provenance {
475                                                alloc_id: field_alloc_id,
476                                                offset: Int::from_u64(self.ctx, 0),
477                                                is_field_offset: false,
478                                            }),
479                                            invariants: ValueInvariants {
480                                                non_null: true, init: true, ..Default::default()
481                                            },
482                                        });
483                                    }
484                                } else if matches!(
485                                    field_ty.kind(),
486                                    rustc_middle::ty::TyKind::Uint(_)
487                                        | rustc_middle::ty::TyKind::Int(_)
488                                        | rustc_middle::ty::TyKind::Float(_)
489                                        | rustc_middle::ty::TyKind::Bool
490                                        | rustc_middle::ty::TyKind::Char
491                                ) {
492                                    // Scalar field (e.g. `size: usize`) inside a
493                                    // referenced struct. Materialize a fresh
494                                    // symbolic value so that field reads return
495                                    // the correct term instead of the whole
496                                    // struct term. Non-scalar, non-pointer ADT
497                                    // fields (Box/Vec/etc.) are left unset so
498                                    // they keep their pre-existing heap modeling.
499                                    let field_term = self.fresh_int(
500                                        &format!("ref_field_{}_{}", local_idx, idx)
501                                    );
502                                    self.set_field_value(local, vec![idx], VmValue {
503                                        term: field_term,
504                                        ty: field_ty,
505                                        provenance: None,
506                                        invariants: ValueInvariants { init: true, ..Default::default() },
507                                    });
508                                }
509                            }
510                        }
511                    }
512                    continue;
513                }
514                // ── Scalar parameter ──
515                let is_scalar = matches!(
516                    ty.kind(),
517                    rustc_middle::ty::TyKind::Uint(_)
518                        | rustc_middle::ty::TyKind::Int(_)
519                        | rustc_middle::ty::TyKind::Bool
520                        | rustc_middle::ty::TyKind::Char
521                );
522                if is_scalar {
523                    let val = self.fresh_int(&format!("arg_{}", local_idx));
524                    self.set_local(local, VmValue {
525                        term: val,
526                        ty,
527                        provenance: None,
528                        invariants,
529                    });
530                    continue;
531                }
532                // ── Raw pointer parameter (*const T, *mut T) ──
533                // Create a symbolic external allocation for provenance
534                // tracking. No invariants are set — callers must provide
535                // contracts (NonNull, ValidPtr, etc.) via assert_contract_fact
536                // to make property checks pass.
537                if let rustc_middle::ty::TyKind::RawPtr(pointee, _mutbl) = ty.kind() {
538                    let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
539                    let pointee_align = self.align_of_ty(*pointee);
540                    let (alloc_id, base) = self.allocate_external(max_size, pointee_align, Some(*pointee));
541                    self.set_local(local, VmValue {
542                        term: base,
543                        ty,
544                        provenance: Some(Provenance {
545                            alloc_id,
546                            offset: Int::from_u64(self.ctx, 0),
547                            is_field_offset: false,
548                        }),
549                        invariants,
550                    });
551                    continue;
552                }
553                // ── Array parameter ([usize; N], etc.) ──
554                // Give every array parameter a real allocation with provenance so
555                // that downstream call effects (e.g. ChecksIndexBoundsDisjoint)
556                // can record the alloc_id and property checker can match it later.
557                if let rustc_middle::ty::TyKind::Array(elem_ty, const_len) = ty.kind() {
558                    let n: Option<usize> = const_len
559                        .try_to_target_usize(self.tcx)
560                        .map(|v| v as usize);
561                    let elem_size = self.size_of_ty(*elem_ty) as u64;
562                    let step = (elem_size.max(1)) as usize;
563                    let align = self.align_of_ty(*elem_ty);
564                    let (alloc_id, base) = if let Some(n) = n {
565                        let total = Int::from_u64(self.ctx, (step as u64).saturating_mul(n as u64));
566                        self.allocate(total, align, Some(*elem_ty))
567                    } else {
568                        // Generic N: unbounded external allocation
569                        let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
570                        self.allocate_external(max_size, align, Some(*elem_ty))
571                    };
572                    self.alloc_mut(alloc_id).initialized = true;
573                    self.local_alloc_ids.insert(local, alloc_id);
574                    if let Some(n) = n {
575                        for i in 0..n {
576                            let off = i * step;
577                            let elem_term = self.fresh_int(&format!(
578                                "array_{}_idx_{}",
579                                local_idx, i
580                            ));
581                            self.record_byte_value(alloc_id, off, elem_term);
582                        }
583                    } else {
584                        // Generic N: create placeholder byte tracking so that
585                        // downstream Index projection ITE chains and
586                        // assert_in_bound_for_each can add constraints.
587                        let m = 16usize;
588                        for i in 0..m {
589                            let off = i * step;
590                            let elem_term = self.fresh_int(&format!(
591                                "array_{}_idx_{}",
592                                local_idx, i
593                            ));
594                            self.record_byte_value(alloc_id, off, elem_term);
595                        }
596                    }
597                    self.set_local(
598                        local,
599                        VmValue {
600                            term: base,
601                            ty,
602                            provenance: Some(Provenance {
603                                alloc_id,
604                                offset: Int::from_u64(self.ctx, 0),
605                                is_field_offset: false,
606                            }),
607                            invariants: ValueInvariants {
608                                init: true,
609                                ..invariants
610                            },
611                        },
612                    );
613                    continue;
614                }
615                // ── Struct / other parameter ──
616                let term = self.fresh_int(&format!("param_{}", local_idx));
617                self.set_local(local, VmValue {
618                    term,
619                    ty,
620                    provenance: None,
621                    invariants,
622                });
623                continue;
624            }
625            // ── Non-parameter local: fallback value (overwritten by actual
626            // assignments). For reference/raw-pointer locals the value *is* the
627            // stack address; for scalar locals use a fresh symbolic value so a
628            // stale stack address never leaks into scalar arithmetic (e.g. the
629            // `offset <= len` bound check in memchr-style loops).
630            let term = match ty.kind() {
631                rustc_middle::ty::TyKind::Ref(..) | rustc_middle::ty::TyKind::RawPtr(..) => {
632                    self.local_address(local)
633                }
634                _ => self.fresh_int(&format!("local_{}", local_idx)),
635            };
636            self.set_local(local, VmValue {
637                term,
638                ty,
639                provenance: None,
640                invariants,
641            });
642        }
643
644        // Entry-block provenance propagation: scan the first basic block
645        // for simple assignments that propagate parameter values.  This
646        // helps when the backward slicer omits same-block definitions
647        // (e.g. `_tmp = _1 as *const T`).  Limiting to the entry block
648        // ensures only unconditionally-executed assignments are covered.
649        if let Some(entry_bb) = self.body.basic_blocks.iter().next() {
650            for stmt in &entry_bb.statements {
651                if let StatementKind::Assign(assign) = &stmt.kind {
652                    let (dest, rvalue) = &**assign;
653                    let dest_local = dest.local;
654                    let src = match rvalue {
655                        #[cfg(rapx_rvalue_use_with_retag)]
656                        Rvalue::Use(operand, _) => Some(operand),
657                        #[cfg(not(rapx_rvalue_use_with_retag))]
658                        Rvalue::Use(operand) => Some(operand),
659                        Rvalue::Cast(_, operand, _) => Some(operand),
660                        _ => None,
661                    }.and_then(|operand| {
662                        match operand {
663                            Operand::Copy(place) | Operand::Move(place) if place.projection.is_empty() => {
664                                Some(place.local)
665                            }
666                            _ => None,
667                        }
668                    });
669                    if let Some(src_local) = src {
670                        if let Some(src_val) = self.locals.get(&src_local) {
671                            let has_better_prov = src_val.provenance.is_some()
672                                && src_val.invariants.non_null
673                                && self.locals.get(&dest_local).map_or(true, |d| {
674                                    d.provenance.is_none() || !d.invariants.non_null
675                                });
676                            if has_better_prov {
677                                self.set_local(dest_local, VmValue {
678                                    term: src_val.term.clone(),
679                                    ty: dest.ty(self.body, self.tcx).ty,
680                                    provenance: src_val.provenance.clone(),
681                                    invariants: src_val.invariants,
682                                });
683                            }
684                        }
685                    }
686                }
687            }
688        }
689    }
690
691    /// Initialize one pointer-like field (raw pointer or `NonNull<T>`) of a
692    /// decomposed struct/ref parameter. The first field with a given pointee
693    /// type creates a shared external allocation; later fields with the same
694    /// pointee reuse it with a symbolic offset, preserving relationships like
695    /// `ptr = start, end_or_len = start + len`.
696    #[allow(clippy::too_many_arguments)]
697    fn init_ptr_field(
698        &mut self,
699        local: Local,
700        path: Vec<usize>,
701        field_ty: Ty<'tcx>,
702        pointee: Ty<'tcx>,
703        local_idx: usize,
704        idx: usize,
705        elem_alloc: &mut FxHashMap<Ty<'tcx>, (AllocId, Int<'ctx>)>,
706        is_raw_ptr: bool,
707        nn_fresh_prefix: &str,
708    ) {
709        let invariants = if is_raw_ptr {
710            ValueInvariants { non_null: true, init: true, ..Default::default() }
711        } else {
712            ValueInvariants { init: true, ..Default::default() }
713        };
714        if let Some(&(existing_alloc, ref base)) = elem_alloc.get(&pointee) {
715            let elem_size = self.size_of_ty(pointee).max(1) as u64;
716            let len_term = self.fresh_int(&format!("field_len_{}_{}", local_idx, idx));
717            self.path_conditions.push(len_term.ge(&Int::from_u64(self.ctx, 0)));
718            let prost_offset = Int::mul(self.ctx, &[&len_term, &Int::from_u64(self.ctx, elem_size)]);
719            let field_term = Int::add(self.ctx, &[base, &prost_offset]);
720            self.set_field_value(local, path.clone(), VmValue {
721                term: field_term,
722                ty: field_ty,
723                provenance: Some(Provenance {
724                    alloc_id: existing_alloc,
725                    offset: prost_offset.clone(),
726                    is_field_offset: false,
727                }),
728                invariants,
729            });
730        } else {
731            let field_align = 1u64.max(self.align_of_ty(pointee));
732            let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
733            let (field_alloc_id, field_base) =
734                self.allocate_external(max_size, field_align, Some(pointee));
735            self.alloc_mut(field_alloc_id).initialized = true;
736            elem_alloc.insert(pointee, (field_alloc_id, field_base.clone()));
737            let field_term = if is_raw_ptr {
738                field_base
739            } else {
740                self.fresh_int(&format!("{}_{}_{}", nn_fresh_prefix, local_idx, idx))
741            };
742            self.set_field_value(local, path.clone(), VmValue {
743                term: field_term,
744                ty: field_ty,
745                provenance: Some(Provenance {
746                    alloc_id: field_alloc_id,
747                    offset: Int::from_u64(self.ctx, 0),
748                    is_field_offset: false,
749                }),
750                invariants,
751            });
752        }
753    }
754
755    /// Recursively decompose a (possibly nested) struct parameter into per-field
756    /// symbolic values.  Nested ADT fields (e.g. `Handle { node: NodeRef { node:
757    /// NonNull<LeafNode>, .. }, .. }`) are descended into so their `NonNull` /
758    /// raw-pointer leaves get external-allocation provenance — otherwise a
759    /// `NonNull` buried two levels deep loses its provenance and downstream
760    /// `Allocated`/`Init` checks (e.g. `descend`'s `edges.get_unchecked`) fail.
761    fn decompose_adt_fields(
762        &mut self,
763        local: Local,
764        prefix: Vec<usize>,
765        ty: Ty<'tcx>,
766        local_idx: usize,
767        elem_alloc: &mut FxHashMap<Ty<'tcx>, (AllocId, Int<'ctx>)>,
768        depth: usize,
769    ) {
770        if depth > 4 {
771            return;
772        }
773        let rustc_middle::ty::TyKind::Adt(adt_def, substs) = ty.kind() else { return };
774        if adt_def.is_enum() {
775            return;
776        }
777        let variant = adt_def.non_enum_variant();
778        for (idx, field_def) in variant.fields.iter().enumerate() {
779            let field_ty: Ty<'tcx> = field_def.ty(self.tcx, substs).skip_norm_wip();
780            let mut path = prefix.clone();
781            path.push(idx);
782            if let rustc_middle::ty::TyKind::RawPtr(inner, _) = field_ty.kind() {
783                self.init_ptr_field(local, path, field_ty, *inner, local_idx, idx, elem_alloc, true, "field_nn");
784            } else if let Some(pointee) = self.find_nn_pointee(field_ty) {
785                self.init_ptr_field(local, path, field_ty, pointee, local_idx, idx, elem_alloc, false, "field_nn");
786            } else if matches!(field_ty.kind(), rustc_middle::ty::TyKind::Adt(_, _)) {
787                self.decompose_adt_fields(local, path, field_ty, local_idx, elem_alloc, depth + 1);
788            } else {
789                let field_term = self.fresh_int(&format!("field_{}_{}", local_idx, idx));
790                self.set_field_value(local, path.clone(), VmValue {
791                    term: field_term,
792                    ty: field_ty,
793                    provenance: None,
794                    invariants: ValueInvariants { init: true, ..Default::default() },
795                });
796            }
797        }
798    }
799
800    /// Replay same-block assignment chains that the backward slicer may omit.
801    /// Walks backwards through the CFG from the checkpoint block, propagating
802    /// provenance and invariants through Use/Cast/RawPtr/CopyForDeref chains.
803    /// Uses the current path to avoid cross-branch contamination.
804    pub(crate) fn propagate_from_checkpoint(&mut self, checkpoint_block: BasicBlock) {
805        let path_blocks: FxHashSet<BasicBlock> = self.path.as_ref()
806            .map(|p| {
807                let mut blocks: FxHashSet<BasicBlock> = p.steps.iter()
808                    .filter_map(|s| match s {
809                        crate::verify::path_extractor::PathStep::Block(b) => Some(*b),
810                        _ => None,
811                    })
812                    .collect();
813                blocks.insert(checkpoint_block);
814                blocks
815            })
816            .unwrap_or_default();
817
818        if path_blocks.is_empty() {
819            self.propagate_pass(checkpoint_block, None, false);
820            return;
821        }
822
823        // Detect SCC (loop) paths: if any block appears more than once
824        // in the block steps, the path is unrolled and path-filtering
825        // may exclude needed blocks.
826        let block_steps: Vec<BasicBlock> = self.path.as_ref()
827            .map(|p| p.steps.iter()
828                .filter_map(|s| match s {
829                    crate::verify::path_extractor::PathStep::Block(b) => Some(*b),
830                    _ => None,
831                })
832                .collect())
833            .unwrap_or_default();
834        let has_duplicates = {
835            let mut seen = FxHashSet::default();
836            block_steps.iter().any(|b| !seen.insert(*b))
837        };
838
839        if has_duplicates {
840            self.propagate_pass(checkpoint_block, Some(&path_blocks), false);
841            return;
842        }
843
844        self.propagate_pass(checkpoint_block, Some(&path_blocks), false);
845        self.propagate_pass(checkpoint_block, Some(&path_blocks), true);
846    }
847
848    fn propagate_pass(&mut self, checkpoint_block: BasicBlock,
849        path_blocks: Option<&FxHashSet<BasicBlock>>, use_only: bool) {
850        // Walk backwards through all reachable predecessors to fill in
851        // provenance chains the slicer may have omitted (e.g. `_tmp = self.ptr`).
852        let mut visited = FxHashSet::default();
853        let mut worklist: Vec<BasicBlock> = vec![checkpoint_block];
854        let mut max_depth = 32usize;
855        while let Some(block) = worklist.pop() {
856            if max_depth == 0 { break; }
857            max_depth -= 1;
858            if !visited.insert(block) { continue; }
859            if let Some(blocks) = path_blocks {
860                if !blocks.contains(&block) {
861                    continue;
862                }
863            }
864            for pred in self.body.basic_blocks.predecessors()[block].to_vec() {
865                if path_blocks.map_or(true, |b| b.contains(&pred)) {
866                    worklist.push(pred);
867                }
868            }
869            for stmt in &self.body.basic_blocks[block].statements {
870                if let StatementKind::Assign(assign) = &stmt.kind {
871                    let (dest, rvalue) = &**assign;
872                    if dest.projection.is_empty() {
873                        if use_only && !Self::is_propagate_use_kind(rvalue) {
874                            continue;
875                        }
876                        self.propagate_single_assign(dest.local, rvalue);
877                    }
878                }
879            }
880            // In use_only pass, skip terminator handling
881            if use_only {
882                continue;
883            }
884            // Also try to materialize constant bytes from call terminators
885            // (e.g. as_ptr() on a constant byte array). The backward slicer
886            // may prune these calls, so we fill them in here.
887            let terminator = self.body.basic_blocks[block].terminator();
888            if let TerminatorKind::Call { destination, args, func, .. } = &terminator.kind {
889                let dest = destination.local;
890                // Check if the destination needs provenance (as_ptr/as_mut_ptr fallback).
891                let needs_fallback = match self.locals.get(&dest) {
892                    Some(dv) => dv.provenance.is_none(),
893                    None => true,
894                };
895                let mut fallback_applied = false;
896                // Try constant byte materialization first
897                if let Some(mut dv) = self.locals.get(&dest).cloned() {
898                    let mut found = false;
899                    for arg in args {
900                        self.try_materialize_const_bytes(&mut dv, &arg.node);
901                        if dv.provenance.is_some() {
902                            self.set_local(dest, dv);
903                            found = true;
904                            break;
905                        }
906                    }
907                    if !found && needs_fallback {
908                        if let Some(first) = args.first() {
909                            fallback_applied = self.try_as_ptr_fallback(dest, func, self.value_of_operand(&first.node), &first.node);
910                        }
911                    }
912                }
913                if !fallback_applied && needs_fallback && self.locals.get(&dest).is_none() {
914                    if let Some(first) = args.first() {
915                        self.try_as_ptr_fallback(dest, func, self.value_of_operand(&first.node), &first.node);
916                    }
917                }
918                // For comparison calls (e.g. <[u8]>::eq), propagate
919                // constant bytes from a literal operand to the tracked
920                // operand's allocation so ValidCStr checks succeed.
921                if self.locals.contains_key(&dest) {
922                    let cmpr_name = crate::helpers::mir_utils::call_name(self.tcx, func);
923                    if api_classify::is_eq_or_partial_eq(&cmpr_name) {
924                        self.propagate_const_bytes_to_tracked(args);
925                    }
926                }
927            }
928        }
929    }
930
931    /// Check if an rvalue kind should be re-propagated in the use-only pass
932    /// (Use/Cast/CopyForDeref — forward-propagate existing provenance).
933    fn is_propagate_use_kind(rvalue: &Rvalue<'tcx>) -> bool {
934        matches!(rvalue,
935            Rvalue::Use(..) | Rvalue::Cast(..) | Rvalue::CopyForDeref(..))
936    }
937
938    /// Propagate a single MIR assignment to fill in provenance for previously
939    /// uninitialised locals.
940    fn propagate_single_assign(&mut self, dest_local: Local, rvalue: &Rvalue<'tcx>) {
941        // Don't overwrite a value that was already set by forward execution.
942        if self.locals.contains_key(&dest_local) {
943            return;
944        }
945
946        let src_local = match rvalue {
947            #[cfg(rapx_rvalue_use_with_retag)]
948            Rvalue::Use(operand, _) => crate::helpers::mir_utils::extract_local(operand),
949            #[cfg(not(rapx_rvalue_use_with_retag))]
950            Rvalue::Use(operand) => crate::helpers::mir_utils::extract_local(operand),
951            Rvalue::Cast(_, operand, _) => crate::helpers::mir_utils::extract_local(operand),
952            Rvalue::CopyForDeref(place) if place.projection.is_empty() => Some(place.local),
953            _ => None,
954        };
955
956        if let Some(src) = src_local {
957            if let Some(src_val) = self.locals.get(&src).cloned() {
958                let dest_ty = self.body.local_decls[dest_local].ty;
959                let is_cast = matches!(rvalue, Rvalue::Cast(..));
960                let is_ptr_arith = matches!(
961                    rvalue,
962                    Rvalue::BinaryOp(BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked
963                        | BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked
964                        | BinOp::Offset, _)
965                );
966                self.set_local(dest_local, VmValue {
967                    term: src_val.term,
968                    ty: dest_ty,
969                    provenance: src_val.provenance,
970                    invariants: ValueInvariants {
971                        aligned: src_val.invariants.aligned,
972                        in_bounds: src_val.invariants.in_bounds,
973                        align_n: if is_cast || is_ptr_arith { src_val.invariants.align_n } else { None },
974                        ..src_val.invariants
975                    },
976                });
977            }
978            return;
979        }
980
981        // Handle projected-places: Use/CopyForDeref of a place with projections
982        // (e.g. `_2 = (*_1).0` or `_2 = _1.ptr`).  Trace through field and deref
983        // projections to find the ultimate source local and its provenance.
984        let src_place = match rvalue {
985            #[cfg(rapx_rvalue_use_with_retag)]
986            Rvalue::Use(Operand::Copy(p) | Operand::Move(p), _) => Some(p),
987            #[cfg(not(rapx_rvalue_use_with_retag))]
988            Rvalue::Use(Operand::Copy(p) | Operand::Move(p)) => Some(p),
989            Rvalue::CopyForDeref(p) => Some(p),
990            _ => None,
991        };
992        if let Some(place) = src_place {
993            if !place.projection.is_empty() {
994                if let Some(val) = self.value_of_place(place) {
995                    let dest_ty = self.body.local_decls[dest_local].ty;
996                    self.set_local(dest_local, VmValue {
997                        term: val.term,
998                        ty: dest_ty,
999                        provenance: val.provenance,
1000                        invariants: val.invariants,
1001                    });
1002                }
1003            }
1004            return;
1005        }
1006
1007        // Ref: &place → propagate address + provenance
1008        if let Rvalue::Ref(_, _, place) = rvalue {
1009            if let Some(addr) = self.address_of_place(place) {
1010                let dest_ty = self.body.local_decls[dest_local].ty;
1011                let alloc_align = addr.provenance.as_ref()
1012                    .map(|p| self.alloc(p.alloc_id).align)
1013                    .filter(|&a| a > 1);
1014                let has_deref = place.projection.iter().any(|p| {
1015                    matches!(p.kind(), rustc_middle::mir::ProjectionElem::Deref)
1016                });
1017                let src_ty = self.body.local_decls[place.local].ty;
1018                let is_from_raw_parts_like = matches!(src_ty.kind(),
1019                    rustc_middle::ty::TyKind::RawPtr(_, _));
1020                let is_slice_ref = if let rustc_middle::ty::TyKind::Ref(_, inner, _) = dest_ty.kind() {
1021                    matches!(inner.kind(), rustc_middle::ty::TyKind::Slice(_))
1022                } else {
1023                    false
1024                };
1025                let src_in_bounds = if is_slice_ref && is_from_raw_parts_like && has_deref {
1026                    addr.provenance.is_some()
1027                } else {
1028                    self.locals.get(&place.local)
1029                        .map_or(false, |v| v.invariants.in_bounds)
1030                };
1031                self.set_local(dest_local, VmValue {
1032                    term: addr.term,
1033                    ty: dest_ty,
1034                    provenance: addr.provenance,
1035                    invariants: ValueInvariants {
1036                        non_null: true, aligned: true, init: true,
1037                        in_bounds: src_in_bounds,
1038                        align_n: alloc_align,
1039                        is_field_offset: false,
1040                    },
1041                });
1042            }
1043            return;
1044        }
1045
1046        // RawPtr: &raw place → propagate address + provenance
1047        if let Rvalue::RawPtr(_, place) = rvalue {
1048            if let Some(addr) = self.address_of_place(place) {
1049                let dest_ty = self.body.local_decls[dest_local].ty;
1050                let alloc_align = addr.provenance.as_ref()
1051                    .map(|p| self.alloc(p.alloc_id).align)
1052                    .filter(|&a| a > 1);
1053                let src_in_bounds = self.locals.get(&place.local)
1054                    .map_or(false, |v| v.invariants.in_bounds);
1055                self.set_local(dest_local, VmValue {
1056                    term: addr.term,
1057                    ty: dest_ty,
1058                    provenance: addr.provenance,
1059                    invariants: ValueInvariants {
1060                        non_null: true, in_bounds: src_in_bounds, align_n: alloc_align, ..Default::default()
1061                    },
1062                });
1063            }
1064            return;
1065        }
1066
1067        // BinaryOp Add/Sub/Offset: lhs provenance → dest
1068        if let Rvalue::BinaryOp(op, pair) = rvalue {
1069            let (lhs_op, rhs_op) = &**pair;
1070            if matches!(op,
1071                BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked
1072                | BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked
1073                | BinOp::Offset)
1074            {
1075            let lhs = crate::helpers::mir_utils::extract_local(lhs_op);
1076            let rhs = crate::helpers::mir_utils::extract_local(rhs_op);
1077            if let Some(src) = lhs {
1078                if let Some(src_val) = self.locals.get(&src).cloned() {
1079                    let rhs_val = rhs.and_then(|r| self.locals.get(&r))
1080                        .map(|v| VmValue { term: v.term.clone(), ty: v.ty, provenance: None, invariants: ValueInvariants::default() })
1081                        .unwrap_or(VmValue { term: Int::from_u64(self.ctx, 0), ty: self.body.local_decls[dest_local].ty, provenance: None, invariants: ValueInvariants::default() });
1082                    let prov = self.provenance_for_binary_op(*op, &src_val, &rhs_val);
1083                    let dest_ty = self.body.local_decls[dest_local].ty;
1084                    self.set_local(dest_local, VmValue {
1085                        term: src_val.term,
1086                        ty: dest_ty,
1087                        provenance: prov,
1088                        invariants: ValueInvariants {
1089                                aligned: src_val.invariants.aligned,
1090                                in_bounds: false,
1091                                align_n: src_val.invariants.align_n,
1092                                ..src_val.invariants
1093                            },
1094                        });
1095                    }
1096                }
1097            }
1098        }
1099    }
1100
1101    // ── Statement executors ──────────────────────────────────────
1102
1103    pub(crate) fn exec_statement(
1104        &mut self,
1105        block: BasicBlock,
1106        statement_index: usize,
1107        statement: &Statement<'tcx>,
1108    ) {
1109        match &statement.kind {
1110            StatementKind::Assign(assign) => {
1111                let (place, rvalue) = &**assign;
1112                self.exec_assign(place, rvalue);
1113            }
1114            StatementKind::StorageLive(local) => {
1115                self.exec_storage_live(*local);
1116            }
1117            StatementKind::StorageDead(local) => {
1118                self.exec_storage_dead(*local);
1119            }
1120            StatementKind::FakeRead(..)
1121            | StatementKind::SetDiscriminant { .. }
1122            | StatementKind::AscribeUserType(..)
1123            | StatementKind::Coverage(..)
1124            | StatementKind::PlaceMention(..)
1125            | StatementKind::Intrinsic(..)
1126            | StatementKind::ConstEvalCounter
1127            | StatementKind::Nop => {}
1128            #[cfg(not(rapx_ge_99))]
1129            StatementKind::Retag(..) => {}
1130            _ => {
1131                self.notes.push(format!(
1132                    "unsupported statement at bb{}#{}",
1133                    block.as_usize(),
1134                    statement_index
1135                ));
1136            }
1137        }
1138    }
1139
1140    fn exec_assign(
1141        &mut self,
1142        place: &Place<'tcx>,
1143        rvalue: &Rvalue<'tcx>,
1144    ) {
1145        let value = self.eval_rvalue(place, rvalue);
1146
1147        let has_deref = place.projection.iter().any(|p| {
1148            matches!(p.kind(), rustc_middle::mir::ProjectionElem::Deref)
1149        });
1150
1151        if !place.projection.is_empty() {
1152            self.record_projected_store(place, &value);
1153            self.record_indexed_store_for_vm(place, &value);
1154        }
1155
1156        self.record_definition();
1157
1158        if place.projection.is_empty() {
1159            let mut value = value;
1160            value.invariants.init = true;
1161            self.set_local(place.local, value);
1162            // Propagate field values for aggregate copies (e.g. `_4 = copy _1`)
1163            // so downstream field accesses (NonZero::get -> self.0) resolve to
1164            // the same symbolic field terms.  A projected source (`_11 = move
1165            // (_1.2)`) shifts the field path by its `Field` projection prefix,
1166            // so `(_1.2).1` becomes `_11.1` — this keeps the `NonNull` node
1167            // field's provenance alive across `NodeRef` moves.
1168            let src_place: Option<&Place<'tcx>> = match rvalue {
1169                #[cfg(rapx_rvalue_use_with_retag)]
1170                Rvalue::Use(operand, _) => match operand {
1171                    Operand::Copy(p) | Operand::Move(p) => Some(p),
1172                    _ => None,
1173                },
1174                #[cfg(not(rapx_rvalue_use_with_retag))]
1175                Rvalue::Use(operand) => match operand {
1176                    Operand::Copy(p) | Operand::Move(p) => Some(p),
1177                    _ => None,
1178                },
1179                Rvalue::CopyForDeref(p) => Some(p),
1180                _ => None,
1181            };
1182            if let Some(sp) = src_place {
1183                let field_prefix: Vec<usize> = sp.projection.iter()
1184                    .filter_map(|p| match p.kind() {
1185                        rustc_middle::mir::ProjectionElem::Field(fi, _) => Some(fi.as_usize()),
1186                        _ => None,
1187                    })
1188                    .collect();
1189                let only_field = sp.projection.iter().all(|p| {
1190                    matches!(p.kind(), rustc_middle::mir::ProjectionElem::Field(..))
1191                });
1192                if only_field {
1193                    let keys: Vec<Vec<usize>> = self.field_values.keys()
1194                        .filter(|(l, _)| *l == sp.local)
1195                        .map(|(_, f)| f.clone())
1196                        .collect();
1197                    for k in keys {
1198                        let rest = if field_prefix.is_empty() {
1199                            Some(k.clone())
1200                        } else if k.len() > field_prefix.len()
1201                            && k[..field_prefix.len()] == field_prefix[..]
1202                        {
1203                            Some(k[field_prefix.len()..].to_vec())
1204                        } else {
1205                            None
1206                        };
1207                        if let Some(rest) = rest {
1208                            if let Some(fv) = self.field_value(sp.local, &k).cloned() {
1209                                self.set_field_value(place.local, rest, fv);
1210                            }
1211                        }
1212                    }
1213                }
1214            }
1215        } else if !has_deref {
1216            // Field projection (no Deref): update field_values for the base local.
1217            let field_indices: Vec<usize> = place.projection.iter()
1218                .filter_map(|p| match p.kind() {
1219                    rustc_middle::mir::ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
1220                    _ => None,
1221                })
1222                .collect();
1223            if !field_indices.is_empty() {
1224                // Track cumulative ptr offset for Iter/IterMut before moving value.
1225                let track_iter = field_indices == [0];
1226                let mut write_value = value;
1227                write_value.invariants.init = true;
1228                self.set_field_value(place.local, field_indices, write_value);
1229                if track_iter {
1230                    self.track_iter_ptr_update(place.local);
1231                }
1232            }
1233        }
1234        // For deref projections (`*ptr = val`): do NOT overwrite the base local.
1235        // Writing through a pointer should not reassign the pointer variable.
1236
1237    }
1238
1239    /// Record byte-level values when assigning to a place with projections.
1240    /// This handles patterns like `buf[i] = 0u8` (nul-store) and `arr[i] = val`.
1241    fn record_projected_store(
1242        &mut self,
1243        place: &Place<'tcx>,
1244        value: &VmValue<'ctx, 'tcx>,
1245    ) {
1246        // Prefer the value's provenance (pointee alloc) over local_alloc_ids
1247        // (reference alloc) for ref/ptr parameters.
1248        let Some(alloc_id) = self.locals.get(&place.local)
1249            .and_then(|v| v.provenance_alloc_id())
1250            .or_else(|| self.local_alloc_ids.get(&place.local).copied())
1251        else {
1252            return;
1253        };
1254
1255        let value_ty = value.ty;
1256        let value_size = self.size_of_ty(value_ty) as usize;
1257
1258        let mut byte_offset: usize = 0;
1259        let mut concrete = true;
1260
1261        let base_ty = self.body.local_decls[place.local].ty;
1262        let mut cur_ty = base_ty;
1263
1264        for proj in place.projection.iter() {
1265            match proj.kind() {
1266                rustc_middle::mir::ProjectionElem::Field(field_idx, _) => {
1267                    let off = self.field_offset_in_bytes(cur_ty, field_idx.as_usize()) as usize;
1268                    byte_offset += off;
1269                    if let rustc_middle::ty::TyKind::Adt(adt_def, substs) = cur_ty.kind() {
1270                        if !adt_def.is_enum() {
1271                            let variant = adt_def.non_enum_variant();
1272                            if let Some(field_def) = variant.fields.get(field_idx) {
1273                                let unnorm = field_def.ty(self.tcx, substs);
1274                                cur_ty = unnorm.skip_norm_wip();
1275                            }
1276                        }
1277                    }
1278                }
1279                rustc_middle::mir::ProjectionElem::Deref => {
1280                    if let rustc_middle::ty::TyKind::Ref(_, inner, _) = cur_ty.kind() {
1281                        cur_ty = *inner;
1282                    }
1283                }
1284                rustc_middle::mir::ProjectionElem::Index(_local) => {
1285                    concrete = false;
1286                    break;
1287                }
1288                rustc_middle::mir::ProjectionElem::Subslice { from, to: _, from_end: _ } => {
1289                    byte_offset += from as usize;
1290                }
1291                _ => {}
1292            }
1293        }
1294
1295        if concrete && value_size > 0 {
1296            self.alloc_mut(alloc_id).initialized = true;
1297
1298            let is_u8_write = matches!(value_ty.kind(),
1299                rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8));
1300
1301            if is_u8_write {
1302                self.record_byte_value(alloc_id, byte_offset, value.term.clone());
1303                if let Some(term_val) = value.term.as_u64() {
1304                    if term_val == 0 {
1305                        self.mark_byte_nul(alloc_id, byte_offset);
1306                    } else {
1307                        self.mark_byte_non_nul(alloc_id, byte_offset);
1308                    }
1309                }
1310            }
1311        }
1312    }
1313
1314    /// Track byte-level values for index-based stores (e.g. `buf[i] = 0u8`)
1315    /// that `record_projected_store` skips due to Index projections.
1316    fn record_indexed_store_for_vm(
1317        &mut self,
1318        place: &Place<'tcx>,
1319        value: &VmValue<'ctx, 'tcx>,
1320    ) {
1321        let is_u8 = matches!(value.ty.kind(),
1322            rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8));
1323        if !is_u8 {
1324            return;
1325        }
1326        let has_index_with_concrete = place.projection.iter().any(|p| {
1327            if let rustc_middle::mir::ProjectionElem::Index(local) = p {
1328                self.locals.get(&local)
1329                    .and_then(|v| v.term.simplify().as_u64())
1330                    .is_some()
1331            } else {
1332                false
1333            }
1334        });
1335        if !has_index_with_concrete {
1336            return;
1337        }
1338        if let Some(addr) = self.address_of_place(place) {
1339            if let Some(ref prov) = addr.provenance {
1340                let alloc_id = prov.alloc_id;
1341                let byte_offset = prov.offset.as_u64().map(|v| v as usize).unwrap_or(0);
1342                self.alloc_mut(alloc_id).initialized = true;
1343                self.record_byte_value(alloc_id, byte_offset, value.term.clone());
1344                if let Some(term_val) = value.term.as_u64() {
1345                    if term_val == 0 {
1346                        self.mark_byte_nul(alloc_id, byte_offset);
1347                    } else {
1348                        self.mark_byte_non_nul(alloc_id, byte_offset);
1349                    }
1350                }
1351            }
1352        }
1353    }
1354
1355    /// Inject layout constraints (>= 1) for generic AlignOf/SizeOf constants.
1356    fn inject_layout_constraints(&mut self, operand: &Operand<'tcx>, val: &VmValue<'ctx, 'tcx>) {
1357        if let Operand::Constant(constant) = operand {
1358            let text = format!("{:?}", constant.const_);
1359            if crate::helpers::mir_utils::const_int_from_debug(&text).is_none() {
1360                let is_align_or_size = text.starts_with("AlignOf(") || text.starts_with("SizeOf(");
1361                if is_align_or_size {
1362                    let one = Int::from_u64(self.ctx, 1);
1363                    self.path_conditions.push(val.term.ge(&one));
1364                }
1365            }
1366        }
1367    }
1368
1369    /// Evaluate an Rvalue into a VmValue.
1370    fn eval_rvalue(
1371        &mut self,
1372        dest_place: &Place<'tcx>,
1373        rvalue: &Rvalue<'tcx>,
1374    ) -> VmValue<'ctx, 'tcx> {
1375        let dest_ty = dest_place.ty(self.body, self.tcx).ty;
1376
1377        match rvalue {
1378            #[cfg(rapx_rvalue_use_with_retag)]
1379            Rvalue::Use(operand, _retag) => {
1380                let mut val = self.value_of_operand(operand);
1381                self.try_materialize_const_bytes(&mut val, operand);
1382                self.inject_layout_constraints(operand, &val);
1383                val
1384            }
1385            #[cfg(not(rapx_rvalue_use_with_retag))]
1386            Rvalue::Use(operand) => {
1387                let mut val = self.value_of_operand(operand);
1388                self.try_materialize_const_bytes(&mut val, operand);
1389                self.inject_layout_constraints(operand, &val);
1390                val
1391            }
1392            Rvalue::Ref(_, _borrow_kind, place) => {
1393                if let Some(addr) = self.address_of_place(place) {
1394                    let alloc_align = addr.provenance.as_ref()
1395                        .map(|p| self.alloc(p.alloc_id).align)
1396                        .filter(|&a| a > 1);
1397                    // Inherit in_bounds. For &[T] created via Deref of a
1398                    // fat raw ptr (inlined from_raw_parts), set in_bounds
1399                    // like ReturnFreshAllocation does in fn_simulator.
1400                    let has_deref = place.projection.iter().any(|p| {
1401                        matches!(p.kind(), rustc_middle::mir::ProjectionElem::Deref)
1402                    });
1403                    let src_ty = self.body.local_decls[place.local].ty;
1404                    let is_from_raw_parts_like = matches!(src_ty.kind(),
1405                        rustc_middle::ty::TyKind::RawPtr(_, _));
1406                    let is_slice_ref = if let rustc_middle::ty::TyKind::Ref(_, inner, _) = dest_ty.kind() {
1407                        matches!(inner.kind(), rustc_middle::ty::TyKind::Slice(_))
1408                    } else {
1409                        false
1410                    };
1411                    let src_in_bounds = if is_slice_ref && is_from_raw_parts_like && has_deref {
1412                        addr.provenance.is_some()
1413                    } else {
1414                        self.locals.get(&place.local)
1415                            .map_or(false, |v| v.invariants.in_bounds)
1416                    };
1417                    let val = VmValue {
1418                        term: addr.term,
1419                        ty: dest_ty,
1420                        provenance: addr.provenance,
1421                        invariants: ValueInvariants {
1422                            non_null: true,
1423                            aligned: self.check_place_alignment(place),
1424                            init: true,
1425                            in_bounds: src_in_bounds,
1426                            align_n: alloc_align,
1427                            is_field_offset: false,
1428                        },
1429                    };
1430                    self.propagate_byte_values_to_ref(place, &val);
1431                    self.propagate_field_values_to_ref(place, dest_place.local);
1432                    val
1433                } else {
1434                    let term = self.fresh_int("ref_addr");
1435                    VmValue {
1436                        term,
1437                        ty: dest_ty,
1438                        provenance: None,
1439                        invariants: ValueInvariants {
1440                            non_null: true,
1441                            init: true,
1442                            ..Default::default()
1443                        },
1444                    }
1445                }
1446            }
1447            Rvalue::RawPtr(_, place) => {
1448                if let Some(addr) = self.address_of_place(place) {
1449                    let alloc_align = addr.provenance.as_ref()
1450                        .map(|p| self.alloc(p.alloc_id).align)
1451                        .filter(|&a| a > 1);
1452                    let source_in_bounds = self.locals.get(&place.local)
1453                        .map_or(false, |v| v.invariants.in_bounds);
1454                    let val = VmValue {
1455                        term: addr.term,
1456                        ty: dest_ty,
1457                        provenance: addr.provenance,
1458                        invariants: ValueInvariants {
1459                            non_null: true,
1460                            in_bounds: source_in_bounds,
1461                            align_n: alloc_align,
1462                            ..Default::default()
1463                        },
1464                    };
1465                    val
1466                } else {
1467                    let term = self.fresh_int("rawptr_addr");
1468                    VmValue {
1469                        term,
1470                        ty: dest_ty,
1471                        provenance: None,
1472                        invariants: ValueInvariants {
1473                            non_null: true,
1474                            ..Default::default()
1475                        },
1476                    }
1477                }
1478            }
1479            Rvalue::BinaryOp(op, pair) => {
1480                let (lhs_op, rhs_op) = &**pair;
1481                let lhs = self.value_of_operand(lhs_op);
1482                let rhs = self.value_of_operand(rhs_op);
1483                let term = self.eval_binary_op(*op, &lhs.term, &rhs.term);
1484                let provenance = self.provenance_for_binary_op(*op, &lhs, &rhs);
1485                let invariants = self.invariants_for_binary_op(*op, &lhs, &rhs, &provenance);
1486                let dest_pk = PlaceKey::from_mir_place(dest_place);
1487                let lhs_pk = crate::helpers::mir_utils::operand_place(lhs_op);
1488                let rhs_pk = crate::helpers::mir_utils::operand_place(rhs_op);
1489                self.binary_op_sources.insert(dest_pk.clone(), (lhs_pk, rhs_pk));
1490                // Store the direct boolean condition for comparison results so
1491                // exec_switchint can record a precise path condition (e.g.
1492                // `offset <= len - 16`) instead of `ite(cond, 1, 0) != 0`, which
1493                // the SMT solver often fails to unfold.
1494                let cmp_cond = match *op {
1495                    BinOp::Le => Some(lhs.term.le(&rhs.term)),
1496                    BinOp::Lt => Some(lhs.term.lt(&rhs.term)),
1497                    BinOp::Ge => Some(lhs.term.ge(&rhs.term)),
1498                    BinOp::Gt => Some(lhs.term.gt(&rhs.term)),
1499                    BinOp::Eq => Some(lhs.term._eq(&rhs.term)),
1500                    BinOp::Ne => Some(lhs.term._eq(&rhs.term).not()),
1501                    _ => None,
1502                };
1503                if let Some(cond) = cmp_cond {
1504                    self.comparison_conds.insert(dest_pk.clone(), cond);
1505                }
1506                // Add Euclidean division identity for Div and Rem:
1507                //   lhs == (lhs/rhs)*rhs + lhs%rhs  ∧  lhs%rhs >= 0
1508                // Also add (lhs/rhs)*rhs <= lhs directly for Div for robustness.
1509                // This lets later checks prove (x/N)*N <= x and x%N >= 0.
1510                // IMPORTANT: use `term` (returned by eval_binary_op) as the
1511                // quotient, NOT a separate `lhs.div(&rhs)` call, so that the
1512                // axiom constrains the SAME Z3 term used in subsequent ops.
1513                if matches!(*op, BinOp::Div | BinOp::Rem) {
1514                    let quot = if matches!(*op, BinOp::Div) { &term } else { &lhs.term.div(&rhs.term) };
1515                    let rem = lhs.term.rem(&rhs.term);
1516                    let mul_term = Int::mul(self.ctx, &[quot, &rhs.term]);
1517                    let sum_term = Int::add(self.ctx, &[&mul_term, &rem]);
1518                    self.path_conditions.push(lhs.term._eq(&sum_term));
1519                    let zero = Int::from_u64(self.ctx, 0);
1520                    self.path_conditions.push(rem.ge(&zero));
1521                    // Remainder and quotient bounds help prove length constraints
1522                    // involving % and / in the SMT solver.
1523                    if rhs.term.as_u64().map_or(true, |r| r >= 1) {
1524                        self.path_conditions.push(rem.lt(&rhs.term));
1525                    }
1526                    self.path_conditions.push(rem.le(&lhs.term));
1527                    self.path_conditions.push(quot.ge(&zero));
1528                    // Direct inequality: (lhs/rhs)*rhs <= lhs
1529                    self.path_conditions.push(mul_term.le(&lhs.term));
1530                    // Quotient strict bound: for rhs >= 2 and lhs >= 2,
1531                    // quot + 1 <= lhs (hence quot < lhs). E.g. X/2 < X for X>1.
1532                    if rhs.term.as_u64().map_or(false, |r| r >= 2) {
1533                        let one = Int::from_u64(self.ctx, 1);
1534                        let qp1 = Int::add(self.ctx, &[quot, &one]);
1535                        // qp1 <= lhs is equivalent to quot < lhs for integers
1536                        self.path_conditions.push(qp1.le(&lhs.term));
1537                    } else {
1538                        // For rhs >= 1: quot <= lhs
1539                        if rhs.term.as_u64().map_or(false, |r| r >= 1) {
1540                            self.path_conditions.push(quot.le(&lhs.term));
1541                        }
1542                    }
1543                }
1544                // For tuple-returning binary ops (AddWithOverflow, MulWithOverflow),
1545                // populate field_values so that .0 (result) and .1 (overflow flag)
1546                // are properly tracked. Without this, field access falls through
1547                // to cloning the base term, mixing the arithmetic result with the
1548                // boolean overflow flag and corrupting path conditions.
1549                if let rustc_middle::ty::TyKind::Tuple(fields) = dest_ty.kind() {
1550                    if fields.len() == 2 {
1551                        let result_val = VmValue {
1552                            term: term.clone(),
1553                            ty: fields[0],
1554                            provenance: provenance.clone(),
1555                            invariants,
1556                        };
1557                        self.set_field_value(dest_place.local, vec![0], result_val);
1558                        let overflow_term = self.fresh_int("overflow_flag");
1559                        let overflow_val = VmValue::new(overflow_term, fields[1]);
1560                        self.set_field_value(dest_place.local, vec![1], overflow_val);
1561                    }
1562                }
1563                VmValue {
1564                    term,
1565                    ty: dest_ty,
1566                    provenance,
1567                    invariants,
1568                }
1569            }
1570            Rvalue::UnaryOp(op, operand) => {
1571                let val = self.value_of_operand(operand);
1572                let is_bool = matches!(val.ty.kind(), rustc_middle::ty::TyKind::Bool);
1573                let term = if matches!(op, UnOp::PtrMetadata) {
1574                    // `PtrMetadata` on a `&[T]` gives the slice length, which is
1575                    // the allocation size divided by the element size. Reuse the
1576                    // same symbolic term as the allocation size so downstream
1577                    // InBound checks (`offset <= len`) agree with the `len` used
1578                    // in loop guards (`offset <= len - 16`).
1579                    self.slice_len_from_value(&val)
1580                        .unwrap_or_else(|| self.fresh_int("ptr_metadata"))
1581                } else {
1582                    self.eval_unary_op(*op, &val.term, is_bool)
1583                };
1584                VmValue {
1585                    term,
1586                    ty: dest_ty,
1587                    provenance: val.provenance,
1588                    invariants: val.invariants,
1589                }
1590            }
1591            Rvalue::Cast(_kind, operand, cast_ty) => {
1592                let src_val = self.value_of_operand(operand);
1593                let src_ty = src_val.ty;
1594                let is_src_ref = matches!(src_ty.kind(),
1595                    rustc_middle::ty::TyKind::Ref(..));
1596                let dest_is_ptr = matches!(cast_ty.kind(),
1597                    rustc_middle::ty::TyKind::RawPtr(..));
1598                let aligned = if dest_is_ptr && is_src_ref {
1599                    true
1600                } else {
1601                    src_val.invariants.aligned
1602                };
1603                // Transmute-like casts of single-field newtypes (e.g.
1604                // NonZero::get's `_0 = copy _1 as T`) yield the underlying
1605                // field value, not the wrapper's own term.
1606                let term = crate::helpers::mir_utils::extract_local(operand)
1607                    .and_then(|l| self.field_value(l, &[0]).map(|v| v.term.clone()))
1608                    .unwrap_or(src_val.term);
1609                VmValue {
1610                    term,
1611                    ty: *cast_ty,
1612                    provenance: src_val.provenance,
1613                    invariants: ValueInvariants {
1614                        non_null: src_val.invariants.non_null,
1615                        init: src_val.invariants.init,
1616                        aligned,
1617                        in_bounds: src_val.invariants.in_bounds,
1618                        align_n: src_val.invariants.align_n,
1619                        is_field_offset: false,
1620                    },
1621                }
1622            }
1623            Rvalue::Aggregate(_kind, operands) => {
1624                // For an enum aggregate, remember whether this is the
1625                // data-carrying variant of `Option`/`Result` (`Some`/`Ok`), so
1626                // the nested-field flattening below only fires on paths that
1627                // actually carry a `Self` value.
1628                let data_variant = match &**_kind {
1629                    rustc_middle::mir::AggregateKind::Adt(did, variant_idx, ..) => {
1630                        if self.tcx.is_diagnostic_item(rustc_span::sym::Result, *did) {
1631                            Some(variant_idx.as_usize() == 0)
1632                        } else if self.tcx.is_diagnostic_item(rustc_span::sym::Option, *did) {
1633                            Some(variant_idx.as_usize() == 1)
1634                        } else {
1635                            None
1636                        }
1637                    }
1638                    _ => None,
1639                };
1640                // `NonNull::new_unchecked(ptr)` / `NonNull::from(&T)` construct a
1641                // repr(transparent) single-field newtype whose value *is* the
1642                // underlying pointer.  Model the wrapper as the pointer field
1643                // itself (term + provenance + invariants) so downstream checks
1644                // like `NonNull(node)` / `Align(node, T)` / `Allocated(node, ..)`
1645                // can discharge against the real pointer instead of a fresh
1646                // unconstrained `aggregate` symbol.
1647                if operands.len() == 1 && self.find_nn_pointee(dest_ty).is_some() {
1648                    let field_val = self.value_of_operand(operands.iter().next().unwrap());
1649                    let dest_local = dest_place.local;
1650                    self.set_field_value(dest_local, vec![0], field_val.clone());
1651                    if let Some(alloc_id) = self.local_alloc_ids.get(&dest_local).copied() {
1652                        self.alloc_mut(alloc_id).initialized = true;
1653                    }
1654                    return VmValue {
1655                        term: field_val.term,
1656                        ty: dest_ty,
1657                        provenance: field_val.provenance,
1658                        invariants: field_val.invariants,
1659                    };
1660                }
1661                let term = self.fresh_int("aggregate");
1662                let dest_local = dest_place.local;
1663                let dest_alloc_id = self.local_alloc_ids.get(&dest_local).copied();
1664                let is_byte_array = crate::helpers::mir_utils::is_u8_array_or_slice(dest_ty);
1665                let field_types: Vec<_> = self.aggregate_field_tys(dest_ty);
1666                let mut byte_offset = 0usize;
1667                for (i, operand) in operands.iter().enumerate() {
1668                    let mut field_val = self.value_of_operand(operand);
1669                    if let Some(field_ty) = field_types.get(i) {
1670                        let src_is_ref = matches!(field_val.ty.kind(), rustc_middle::ty::TyKind::Ref(..));
1671                        let dst_is_raw = matches!(field_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..));
1672                        if src_is_ref && dst_is_raw {
1673                            field_val.invariants.in_bounds = true;
1674                            field_val.ty = *field_ty;
1675                        } else if dst_is_raw && field_val.invariants.non_null {
1676                            field_val.invariants.in_bounds = true;
1677                            field_val.ty = *field_ty;
1678                        }
1679                    }
1680                    let field_sz = field_types.get(i).copied()
1681                        .map(|ty| self.size_of_ty(ty) as usize)
1682                        .unwrap_or(1);
1683                    let field_term = field_val.term.clone();
1684                    self.set_field_value(dest_local, vec![i], field_val);
1685                    // Flatten a nested aggregate: if the operand is a local whose
1686                    // own fields are tracked (e.g. `_0 = Result::Ok(_24)` where
1687                    // `_24 = RawVecInner { ptr: _25, .. }`), expose the nested
1688                    // fields under the destination's field path so a contract
1689                    // place like `Return.Field(0).Field(0)` (the `Ok` variant's
1690                    // data, then the struct field) can resolve to `_25`.
1691                    // Only flatten the data-carrying variant (`Ok`/`Some`); on
1692                    // `Err`/`None` paths there is no `Self` and the nested place
1693                    // should resolve to `Unknown` instead.
1694                    if data_variant != Some(false) {
1695                        if let Some(op_place) = operand.place() {
1696                            if op_place.projection.is_empty() {
1697                                let nested: Vec<(Vec<usize>, VmValue<'ctx, 'tcx>)> = self
1698                                    .field_values
1699                                    .iter()
1700                                    .filter(|((l, _), _)| *l == op_place.local)
1701                                    .map(|((_, p), v)| (p.clone(), v.clone()))
1702                                    .collect();
1703                                for (nested_path, nested_val) in nested {
1704                                    let mut full = vec![i];
1705                                    full.extend_from_slice(&nested_path);
1706                                    self.set_field_value(dest_local, full, nested_val);
1707                                }
1708                            }
1709                        }
1710                    }
1711                    if let Some(alloc_id) = dest_alloc_id {
1712                        self.alloc_mut(alloc_id).initialized = true;
1713                        if is_byte_array && field_sz == 1 {
1714                            self.record_byte_value(alloc_id, byte_offset, field_term.clone());
1715                        }
1716                        // Record known_nul / known_non_nul from constant operands
1717                        if let Some(int_val) = crate::helpers::mir_utils::extract_operand_const(operand) {
1718                            if field_sz == 1 {
1719                                if int_val == 0 {
1720                                    self.mark_byte_nul(alloc_id, byte_offset);
1721                                    if !is_byte_array {
1722                                        self.record_byte_value(alloc_id, byte_offset,
1723                                            Int::from_u64(self.ctx, 0));
1724                                    }
1725                                } else {
1726                                    self.mark_byte_non_nul(alloc_id, byte_offset);
1727                                    if !is_byte_array {
1728                                        self.record_byte_value(alloc_id, byte_offset,
1729                                            Int::from_u64(self.ctx, int_val));
1730                                    }
1731                                }
1732                            }
1733                            // For multi-byte fields: track each constituent byte
1734                            for b in 0..field_sz.min(8) {
1735                                let byte_off = byte_offset + b;
1736                                let byte_val = (int_val >> (b * 8)) & 0xFF;
1737                                if byte_val == 0 {
1738                                    self.mark_byte_nul(alloc_id, byte_off);
1739                                } else {
1740                                    self.mark_byte_non_nul(alloc_id, byte_off);
1741                                }
1742                                self.record_byte_value(alloc_id, byte_off,
1743                                    Int::from_u64(self.ctx, byte_val));
1744                            }
1745                        }
1746                    }
1747                    byte_offset += field_sz;
1748                }
1749                // Fat-pointer construction (inlined `from_raw_parts` /
1750                // `slice_from_raw_parts_mut`): the result's address and
1751                // provenance are those of the data pointer (field 0), so
1752                // downstream `Allocated`/`Owning` checks on the slice resolve
1753                // against the real buffer instead of a fresh `aggregate` symbol.
1754                let is_slice_ptr = matches!(dest_ty.kind(),
1755                    rustc_middle::ty::TyKind::RawPtr(inner, _) | rustc_middle::ty::TyKind::Ref(_, inner, _)
1756                        if matches!(inner.kind(), rustc_middle::ty::TyKind::Slice(_)));
1757                let (result_term, result_prov) = if is_slice_ptr {
1758                    match self.field_value(dest_local, &[0]).cloned() {
1759                        Some(data) => (data.term.clone(), data.provenance.clone()),
1760                        None => (term.clone(), None),
1761                    }
1762                } else {
1763                    (term.clone(), None)
1764                };
1765                VmValue {
1766                    term: result_term,
1767                    ty: dest_ty,
1768                    provenance: result_prov,
1769                    invariants: ValueInvariants::default(),
1770                }
1771            }
1772            Rvalue::Discriminant(place) => {
1773                // If the ADT's variant is known symbolically (e.g. `Iterator::next`
1774                // returns `Some` iff the iterator was non-empty), reuse that term
1775                // so `switchInt(discriminant)` branches stay tied to the real
1776                // condition instead of a fresh unconstrained symbol.
1777                let term = self.discriminant_terms.get(&place.local)
1778                    .cloned()
1779                    .unwrap_or_else(|| self.fresh_int("discriminant"));
1780                if self.discriminant_terms.contains_key(&place.local) {
1781                    self.contract_flags.saw_next_discriminant = true;
1782                }
1783                // For Ordering (repr i8, values: Less=-1 Equal=0 Greater=1),
1784                // the discriminant index equals the repr value + 1.
1785                // Connect the fresh discriminant term to the ADT value so
1786                // that SwitchInt constraints propagate to the stored value.
1787                let place_val = self.value_of_place(place)
1788                    .or_else(|| self.local_value(place.local).cloned());
1789                if let Some(ref pv) = place_val {
1790                    if let rustc_middle::ty::TyKind::Adt(adt_def, _) = pv.ty.kind() {
1791                        let def_path = self.tcx.def_path_str(adt_def.did());
1792                        if api_classify::is_std_ordering(&def_path) && adt_def.is_enum() {
1793                            let one = Int::from_u64(self.ctx, 1);
1794                            let discr_minus_one = Int::sub(self.ctx, &[&term, &one]);
1795                            self.path_conditions.push(pv.term._eq(&discr_minus_one));
1796                            // Also bound the discriminant to {0, 1, 2}
1797                            let zero = Int::from_u64(self.ctx, 0);
1798                            let two = Int::from_u64(self.ctx, 2);
1799                            self.path_conditions.push(term.ge(&zero));
1800                            self.path_conditions.push(term.le(&two));
1801                        }
1802                    }
1803                }
1804                VmValue {
1805                    term,
1806                    ty: dest_ty,
1807                    provenance: None,
1808                    invariants: ValueInvariants::default(),
1809                }
1810            }
1811            #[cfg(not(rapx_ge_99))]
1812            Rvalue::ShallowInitBox(operand, _ty) => {
1813                let val = self.value_of_operand(operand);
1814                VmValue {
1815                    term: val.term,
1816                    ty: dest_ty,
1817                    provenance: val.provenance,
1818                    invariants: val.invariants,
1819                }
1820            }
1821            Rvalue::CopyForDeref(place) => {
1822                if let Some(val) = self.value_of_place(place) {
1823                    val
1824                } else {
1825                    let term = self.fresh_int("copy_for_deref");
1826                    VmValue {
1827                        term,
1828                        ty: dest_ty,
1829                        provenance: None,
1830                        invariants: ValueInvariants::default(),
1831                    }
1832                }
1833            }
1834            Rvalue::Repeat(operand, _count) => {
1835                let _val = self.value_of_operand(operand);
1836                let term = self.fresh_int("repeat");
1837                VmValue {
1838                    term,
1839                    ty: dest_ty,
1840                    provenance: None,
1841                    invariants: ValueInvariants::default(),
1842                }
1843            }
1844            Rvalue::ThreadLocalRef(_) => {
1845                let term = self.fresh_int("thread_local");
1846                VmValue {
1847                    term,
1848                    ty: dest_ty,
1849                    provenance: None,
1850                    invariants: ValueInvariants::default(),
1851                }
1852            }
1853            #[cfg(not(rapx_ge_99))]
1854            Rvalue::NullaryOp(_op) => {
1855                let term = self.fresh_int("nullary");
1856                let op_debug = format!("{:?}", _op);
1857                let is_align_of = op_debug.contains("AlignOf") || op_debug.contains("min_align_of");
1858                let is_size_of = op_debug.contains("SizeOf");
1859                if is_align_of || is_size_of {
1860                    let one = Int::from_u64(self.ctx, 1);
1861                    self.path_conditions.push(term.ge(&one));
1862                }
1863                VmValue {
1864                    term,
1865                    ty: dest_ty,
1866                    provenance: None,
1867                    invariants: ValueInvariants::default(),
1868                }
1869            }
1870            Rvalue::WrapUnsafeBinder(_operand, _ty) => {
1871                let term = self.fresh_int("wrap_unsafe_binder");
1872                VmValue {
1873                    term,
1874                    ty: dest_ty,
1875                    provenance: None,
1876                    invariants: ValueInvariants::default(),
1877                }
1878            }
1879            #[cfg(rapx_rvalue_has_reborrow)]
1880            Rvalue::Reborrow(_ty, _mutability, _place) => {
1881                let term = self.fresh_int("reborrow");
1882                VmValue {
1883                    term,
1884                    ty: dest_ty,
1885                    provenance: None,
1886                    invariants: ValueInvariants { non_null: true, ..Default::default() },
1887                }
1888            }
1889        }
1890    }
1891
1892    // ── Arithmetic ────────────────────────────────────────────────
1893
1894    fn eval_binary_op(&mut self, op: BinOp, lhs: &Int<'ctx>, rhs: &Int<'ctx>) -> Int<'ctx> {
1895        match op {
1896            BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked => {
1897                Int::add(self.ctx, &[lhs, rhs])
1898            }
1899            BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked => {
1900                Int::sub(self.ctx, &[lhs, rhs])
1901            }
1902            BinOp::Mul | BinOp::MulWithOverflow | BinOp::MulUnchecked => {
1903                Int::mul(self.ctx, &[lhs, rhs])
1904            }
1905            BinOp::Div => lhs.div(rhs),
1906            BinOp::Rem => lhs.rem(rhs),
1907            BinOp::Eq => {
1908                let cond = lhs._eq(rhs);
1909                cond.ite(&Int::from_u64(self.ctx, 1), &Int::from_u64(self.ctx, 0))
1910            }
1911            BinOp::Ne => {
1912                let cond = lhs._eq(rhs).not();
1913                cond.ite(&Int::from_u64(self.ctx, 1), &Int::from_u64(self.ctx, 0))
1914            }
1915            BinOp::Lt => {
1916                let cond = lhs.lt(rhs);
1917                cond.ite(&Int::from_u64(self.ctx, 1), &Int::from_u64(self.ctx, 0))
1918            }
1919            BinOp::Le => {
1920                let cond = lhs.le(rhs);
1921                cond.ite(&Int::from_u64(self.ctx, 1), &Int::from_u64(self.ctx, 0))
1922            }
1923            BinOp::Gt => {
1924                let cond = lhs.gt(rhs);
1925                cond.ite(&Int::from_u64(self.ctx, 1), &Int::from_u64(self.ctx, 0))
1926            }
1927            BinOp::Ge => {
1928                let cond = lhs.ge(rhs);
1929                cond.ite(&Int::from_u64(self.ctx, 1), &Int::from_u64(self.ctx, 0))
1930            }
1931            BinOp::Offset => Int::add(self.ctx, &[lhs, rhs]),
1932            BinOp::BitAnd => {
1933                let result = self.fresh_int("binop");
1934                // BitAnd only clears bits, so it never increases a non-negative
1935                // value: result <= lhs.
1936                self.path_conditions.push(result.le(lhs));
1937                // When the mask (rhs) is a non-negative constant, the result is
1938                // also bounded by it: `x & c <= c` (e.g. `rhs & 31 <= 31`).
1939                // This lets `(rhs & (BITS - 1)) < BITS` be discharged. The
1940                // mask may be a folded expression (`SubWithOverflow(BITS, 1)`),
1941                // so `simplify()` is used to recover its constant value.
1942                if rhs.simplify().as_u64().is_some() {
1943                    self.path_conditions.push(result.le(rhs));
1944                }
1945                if self.not_mask_terms.contains(rhs) {
1946                    // rhs is a two's-complement mask `!(align-1) == -align`,
1947                    // so `align = -rhs`. The result of `x & !(align-1)` is
1948                    // `x` rounded down to a multiple of `align` (i.e. align_up
1949                    // of the pre-incremented value).
1950                    let zero = Int::from_u64(self.ctx, 0);
1951                    let align = Int::sub(self.ctx, &[&zero, rhs]);
1952                    self.path_conditions.push(result.rem(&align)._eq(&zero));
1953                    let one = Int::from_u64(self.ctx, 1);
1954                    let addr = Int::add(self.ctx, &[lhs, rhs, &one]);
1955                    self.path_conditions.push(result.ge(&addr));
1956                }
1957                result
1958            }
1959            BinOp::BitOr => {
1960                let result = self.fresh_int("binop");
1961                let zero = Int::from_u64(self.ctx, 0);
1962                // Bitwise OR only sets bits, so the result is non-zero whenever
1963                // either operand is non-zero.  Emit an implication (rather than
1964                // `result >= lhs`, which is only valid for non-negative values)
1965                // so `NonZero` bit-or methods discharge their `!= 0` obligation
1966                // for both signed and unsigned instantiations.
1967                self.path_conditions
1968                    .push(lhs._eq(&zero).not().implies(&result._eq(&zero).not()));
1969                self.path_conditions
1970                    .push(rhs._eq(&zero).not().implies(&result._eq(&zero).not()));
1971                result
1972            }
1973            _ => self.fresh_int("binop"),
1974        }
1975    }
1976
1977    fn eval_unary_op(&mut self, op: UnOp, val: &Int<'ctx>, is_bool: bool) -> Int<'ctx> {
1978        match op {
1979            UnOp::Not => {
1980                if is_bool {
1981                    let zero = Int::from_u64(self.ctx, 0);
1982                    let one = Int::from_u64(self.ctx, 1);
1983                    val._eq(&zero).ite(&one, &zero)
1984                } else {
1985                    // Two's-complement bitwise NOT: !x == -x - 1.
1986                    let zero = Int::from_u64(self.ctx, 0);
1987                    let one = Int::from_u64(self.ctx, 1);
1988                    let neg = Int::sub(self.ctx, &[&zero, val]);
1989                    let result = Int::sub(self.ctx, &[&neg, &one]);
1990                    self.not_mask_terms.insert(result.clone());
1991                    result
1992                }
1993            }
1994            UnOp::Neg => {
1995                let zero = Int::from_u64(self.ctx, 0);
1996                Int::sub(self.ctx, &[&zero, val])
1997            }
1998            UnOp::PtrMetadata => self.fresh_int("ptr_metadata"),
1999        }
2000    }
2001
2002    /// Compute the slice length for a `&[T]` / `&mut [T]` value: the allocation
2003    /// size divided by the element size. Reuses the allocation's size term so
2004    /// it agrees with InBound/`alloc.size` checks.
2005    fn slice_len_from_value(&self, val: &VmValue<'ctx, 'tcx>) -> Option<Int<'ctx>> {
2006        let alloc_id = val.provenance_alloc_id()?;
2007        let alloc = self.alloc(alloc_id);
2008        let elem_ty = alloc.element_ty?;
2009        let elem_size = self.size_of_ty(elem_ty).max(1) as u64;
2010        if elem_size == 1 {
2011            return Some(alloc.size.clone());
2012        }
2013        let elem_term = Int::from_u64(self.ctx, elem_size);
2014        Some(alloc.size.div(&elem_term))
2015    }
2016
2017    /// Compute provenance for a binary operation on pointer values.
2018    /// Propagates provenance with adjusted offset for pointer arithmetic
2019    /// (`ptr + offset`, `ptr - offset`, `Offset`).
2020    fn provenance_for_binary_op(
2021        &self,
2022        op: BinOp,
2023        lhs: &VmValue<'ctx, 'tcx>,
2024        rhs: &VmValue<'ctx, 'tcx>,
2025    ) -> Option<Provenance<'ctx>> {
2026        match op {
2027            BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked
2028            | BinOp::Offset => {
2029                // ptr + scalar → propagate with adjusted offset
2030                if rhs.provenance.is_some() {
2031                    return None;
2032                }
2033                lhs.provenance.as_ref().map(|prov| Provenance {
2034                    alloc_id: prov.alloc_id,
2035                    offset: Int::add(self.ctx, &[&prov.offset, &rhs.term]),
2036                    is_field_offset: false,
2037                })
2038            }
2039            BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked => {
2040                if rhs.provenance.is_some() {
2041                    // ptr - ptr → integer (difference), no provenance
2042                    return None;
2043                }
2044                lhs.provenance.as_ref().map(|prov| Provenance {
2045                    alloc_id: prov.alloc_id,
2046                    offset: Int::sub(self.ctx, &[&prov.offset, &rhs.term]),
2047                    is_field_offset: false,
2048                })
2049            }
2050            BinOp::BitAnd => {
2051                if rhs.provenance.is_some() {
2052                    return None;
2053                }
2054                lhs.provenance.as_ref().map(|prov| Provenance {
2055                    alloc_id: prov.alloc_id,
2056                    // Alignment rounding changes the intra-allocation offset
2057                    // unpredictably; use a fresh symbolic offset constrained
2058                    // by the BitAnd path conditions emitted in eval_binary_op.
2059                    offset: self.fresh_int("align_offset"),
2060                    is_field_offset: false,
2061                })
2062            }
2063            BinOp::BitXor | BinOp::Shr | BinOp::ShrUnchecked => {
2064                if rhs.provenance.is_some() {
2065                    return None;
2066                }
2067                lhs.provenance.clone()
2068            }
2069            BinOp::BitOr | BinOp::Shl | BinOp::ShlUnchecked => {
2070                if rhs.provenance.is_some() {
2071                    return None;
2072                }
2073                lhs.provenance.as_ref().map(|prov| Provenance {
2074                    alloc_id: prov.alloc_id,
2075                    offset: Int::add(self.ctx, &[&prov.offset, &rhs.term]),
2076                    is_field_offset: false,
2077                })
2078            }
2079            BinOp::Mul | BinOp::MulWithOverflow | BinOp::MulUnchecked => {
2080                if rhs.provenance.is_some() {
2081                    return None;
2082                }
2083                lhs.provenance.as_ref().map(|prov| Provenance {
2084                    alloc_id: prov.alloc_id,
2085                    offset: Int::mul(self.ctx, &[&prov.offset, &rhs.term]),
2086                    is_field_offset: false,
2087                })
2088            }
2089            BinOp::Div | BinOp::Rem => lhs.provenance.clone(),
2090            _ => None,
2091        }
2092    }
2093
2094    /// Compute invariants for a binary operation.
2095    /// Propagates non_null from pointer arithmetic and align_n from compatible ops.
2096    fn invariants_for_binary_op(
2097        &self,
2098        op: BinOp,
2099        lhs: &VmValue<'ctx, 'tcx>,
2100        rhs: &VmValue<'ctx, 'tcx>,
2101        provenance: &Option<Provenance<'ctx>>,
2102    ) -> ValueInvariants {
2103        let non_null = provenance.is_some() && lhs.invariants.non_null;
2104
2105        let align_n = match op {
2106            BinOp::Add | BinOp::AddWithOverflow | BinOp::AddUnchecked
2107            | BinOp::Sub | BinOp::SubWithOverflow | BinOp::SubUnchecked
2108            | BinOp::Offset => {
2109                // If both LHS and RHS are known to be n-aligned, sum/diff is n-aligned
2110                match (lhs.invariants.align_n, rhs.invariants.align_n) {
2111                    (Some(a), Some(b)) if a == b => Some(a),
2112                    // LHS has alignment, RHS is a constant multiple of it
2113                    (Some(a), None) => {
2114                        let c = rhs.term.as_u64().unwrap_or(1);
2115                        if c % a == 0 { Some(a) } else { None }
2116                    }
2117                    // LHS has alignment, RHS is the result of Mul by constant factor
2118                    (Some(a), _) if self.rhs_is_aligned_multiple(rhs, a) => Some(a),
2119                    _ => None,
2120                }
2121            }
2122            BinOp::Mul | BinOp::MulWithOverflow | BinOp::MulUnchecked => {
2123                if let Some(c) = rhs.term.as_u64() {
2124                    if c > 0 && c.is_power_of_two() {
2125                        Some(c)
2126                    } else if c > 0 {
2127                        let factor = 1u64 << c.trailing_zeros();
2128                        if factor > 1 { Some(factor) } else { None }
2129                    } else {
2130                        None
2131                    }
2132                } else if let Some(c) = lhs.term.as_u64() {
2133                    if c > 0 && c.is_power_of_two() {
2134                        Some(c)
2135                    } else if c > 0 {
2136                        let factor = 1u64 << c.trailing_zeros();
2137                        if factor > 1 { Some(factor) } else { None }
2138                    } else {
2139                        None
2140                    }
2141                } else {
2142                    None
2143                }
2144            }
2145            _ => lhs.invariants.align_n,
2146        };
2147
2148        ValueInvariants { non_null, align_n, ..Default::default() }
2149    }
2150
2151    /// Check if a value is known to be a multiple of `align` (e.g. the result
2152    /// of a Mul by a constant factor of `align`).
2153    fn rhs_is_aligned_multiple(&self, val: &VmValue<'ctx, 'tcx>, align: u64) -> bool {
2154        // If the value itself has align_n >= align, it's a multiple
2155        if let Some(a) = val.invariants.align_n {
2156            if a >= align && a % align == 0 { return true; }
2157        }
2158        // If the value is a constant, check directly
2159        if let Some(c) = val.term.as_u64() {
2160            if c % align == 0 { return true; }
2161        }
2162        false
2163    }
2164
2165    // ── Storage ──────────────────────────────────────────────────
2166
2167    fn exec_storage_live(&mut self, local: Local) {
2168        self.local_address(local);
2169        if let Some(alloc_id) = self.local_alloc_ids.get(&local).copied() {
2170            self.alloc_mut(alloc_id).dead = false;
2171        }
2172    }
2173
2174    fn exec_storage_dead(&mut self, local: Local) {
2175        if let Some(alloc_id) = self.local_alloc_ids.get(&local).copied() {
2176            self.alloc_mut(alloc_id).dead = true;
2177        }
2178    }
2179
2180    pub(crate) fn exec_drop(&mut self, place: &Place<'tcx>) {
2181        if let Some(alloc_id) = self.local_alloc_ids.get(&place.local).copied() {
2182            self.alloc_mut(alloc_id).dead = true;
2183            // Cascade to heap data allocations (see exec_storage_dead).
2184            let mut worklist: Vec<AllocId> = vec![alloc_id];
2185            while let Some(id) = worklist.pop() {
2186                if let Some(data_id) = self.alloc(id).slice_data {
2187                    self.alloc_mut(data_id).dead = true;
2188                    worklist.push(data_id);
2189                }
2190            }
2191        }
2192        self.notes.push(format!("drop: {:?}", place));
2193    }
2194
2195    // ── Terminator executors ─────────────────────────────────────
2196
2197    fn exec_terminator(
2198        &mut self,
2199        block: BasicBlock,
2200        terminator: &Terminator<'tcx>,
2201        occurrence: usize,
2202    ) {
2203        match &terminator.kind {
2204            TerminatorKind::Call {
2205                func,
2206                args,
2207                destination,
2208                target,
2209                ..
2210            } => {
2211                let caller_id = self.caller_def_id;
2212                self.exec_call(
2213                    func,
2214                    args,
2215                    destination.local,
2216                    *target,
2217                    None,
2218                    caller_id,
2219                );
2220            }
2221            TerminatorKind::SwitchInt { discr, targets } => {
2222                self.exec_switchint(block, discr, targets, occurrence);
2223            }
2224            TerminatorKind::Assert { cond, expected, .. } => {
2225                self.exec_assert(cond, *expected, block, occurrence);
2226            }
2227            TerminatorKind::Goto { .. }
2228            | TerminatorKind::Return
2229            | TerminatorKind::Unreachable
2230            | TerminatorKind::UnwindResume
2231            | TerminatorKind::UnwindTerminate(_)
2232            | TerminatorKind::Yield { .. }
2233            | TerminatorKind::CoroutineDrop
2234            | TerminatorKind::FalseEdge { .. }
2235            | TerminatorKind::FalseUnwind { .. }
2236            | TerminatorKind::InlineAsm { .. }
2237            | TerminatorKind::TailCall { .. } => {}
2238            TerminatorKind::Drop { place, .. } => {
2239                self.exec_drop(place);
2240            }
2241        }
2242    }
2243
2244    /// Execute a SwitchInt terminator.
2245    ///
2246    /// Uses the path to determine which branch is taken, then adds
2247    /// a path condition asserting the discriminant equals that value.
2248    fn exec_switchint(
2249        &mut self,
2250        block: BasicBlock,
2251        discr: &Operand<'tcx>,
2252        targets: &rustc_middle::mir::SwitchTargets,
2253        occurrence: usize,
2254    ) {
2255        let discr_val = self.value_of_operand(discr);
2256
2257        // If the discriminator is a comparison result, record the direct
2258        // boolean condition alongside the ite-encoded `discr == value` fact,
2259        // so the SMT solver can reason about `offset <= len` directly.
2260        let cmp_cond = discr.place().and_then(|p| {
2261            let pk = PlaceKey::from_mir_place(&p);
2262            self.comparison_conds.get(&pk).cloned()
2263        });
2264
2265        // Determine which target block is taken along the path.
2266        if let Some(ref path) = self.path {
2267            if let Some(chosen) = chosen_successor(path, block, occurrence) {
2268                for (value, target) in targets.iter() {
2269                    if target == chosen {
2270                        let val_term = Int::from_u64(self.ctx, value as u64);
2271                        self.path_conditions.push(discr_val.term._eq(&val_term));
2272                        if let Some(ref cond) = cmp_cond {
2273                            if value != 0 {
2274                                self.path_conditions.push(cond.clone());
2275                            } else {
2276                                self.path_conditions.push(cond.not());
2277                            }
2278                        }
2279                        if value != 0 {
2280                            self.infer_switch_guard(discr);
2281                        } else {
2282                            // For !is_empty() on Iter/IterMut (false branch),
2283                            // also assert self.len() >= 1 to help Z3.
2284                            self.inject_is_empty_len(discr);
2285                        }
2286                        return;
2287                    }
2288                }
2289                // Otherwise branch: the discrim is NOT any of the explicit values.
2290                if targets.otherwise() == chosen {
2291                    // Negate every explicit target value.
2292                    for (value, _) in targets.iter() {
2293                        let val_term = Int::from_u64(self.ctx, value as u64);
2294                        self.path_conditions.push(discr_val.term._eq(&val_term).not());
2295                    }
2296                    if let Some(ref cond) = cmp_cond {
2297                        // For a boolean discriminator, `otherwise` means
2298                        // `discr != 0`, i.e. the comparison is true.
2299                        if targets.iter().any(|(v, _)| v == 0) {
2300                            self.path_conditions.push(cond.clone());
2301                        }
2302                    }
2303                    return;
2304                }
2305            }
2306        }
2307
2308        // Conservative fallback: note it but don't add path condition
2309        self.notes.push(format!(
2310            "SwitchInt at bb{} occ{}: discr is symbolic, branch unknown",
2311            block.as_usize(),
2312            occurrence
2313        ));
2314    }
2315
2316    /// Execute an Assert terminator.
2317    fn exec_assert(
2318        &mut self,
2319        cond: &Operand<'tcx>,
2320        expected: bool,
2321        _block: BasicBlock,
2322        _occurrence: usize,
2323    ) {
2324        let cond_val = self.value_of_operand(cond);
2325        if expected {
2326            let zero = Int::from_u64(self.ctx, 0);
2327            self.path_conditions
2328                .push(cond_val.term._eq(&zero).not());
2329        } else {
2330            let zero = Int::from_u64(self.ctx, 0);
2331            self.path_conditions.push(cond_val.term._eq(&zero));
2332        }
2333
2334        // Guard inference: trace the assert condition back to find non_null sources
2335        self.infer_guard_non_null(cond, expected);
2336        // Infer alignment from == 0 guards on Rem expressions
2337        self.infer_guard_align(cond, expected);
2338    }
2339
2340    /// Infer alignment constraints from guards of the form `(x % n) == 0`.
2341    pub(crate) fn infer_guard_align(&mut self, cond: &Operand<'tcx>, expected: bool) {
2342        if !expected { return; }
2343        let place = match cond {
2344            Operand::Copy(p) | Operand::Move(p) => p,
2345            _ => return,
2346        };
2347        let cond_pk = PlaceKey::from_mir_place(place);
2348
2349        // Check if cond is a Ne/Eq comparison of (x % n) against 0
2350        if let Some((lhs_pk, rhs_pk)) = self.binary_op_sources.get(&cond_pk).cloned() {
2351            // The lhs is (x % n), rhs is constant 0
2352            let rem_pk = match (&lhs_pk, &rhs_pk) {
2353                (Some(pk), None) => pk.clone(),
2354                (None, Some(pk)) => pk.clone(),
2355                _ => return,
2356            };
2357            // Trace the Rem operand
2358            if let Some((div_lhs, div_rhs)) = self.binary_op_sources.get(&rem_pk).cloned() {
2359                // div_rhs is the divisor constant
2360                if let Some(divisor) = resolve_u64_from_place_key(&div_rhs, self) {
2361                    if divisor > 0 {
2362                        // Mark div_lhs as having align_n = divisor
2363                        if let Some(src_pk) = &div_lhs {
2364                            if let Some(local) = src_pk.local() {
2365                                if let Some(mut val) = self.locals.get(&local).cloned() {
2366                                    val.invariants.align_n = Some(divisor);
2367                                    self.set_local(local, val);
2368                                }
2369                            }
2370                        }
2371                    }
2372                }
2373            }
2374        }
2375    }
2376
2377    /// Infer non_null invariants from branch guards.
2378    pub(crate) fn infer_guard_non_null(&mut self, cond: &Operand<'tcx>, expected: bool) {
2379        if !expected { return; }
2380        let place = match cond {
2381            Operand::Copy(p) | Operand::Move(p) => p,
2382            _ => return,
2383        };
2384        let cond_pk = PlaceKey::from_mir_place(place);
2385
2386        // Check if cond was defined by BinaryOp(Ne, (ptr, 0)) or similar
2387        if let Some((lhs_pk, rhs_pk)) = self.binary_op_sources.get(&cond_pk).cloned() {
2388            // lhs is pointer, rhs is None (constant zero) → mark lhs as non_null
2389            if rhs_pk.is_none() {
2390                if let Some(ptr_pk) = &lhs_pk {
2391                    if let Some(local) = ptr_pk.local() {
2392                        if let Some(mut val) = self.locals.get(&local).cloned() {
2393                            val.invariants.non_null = true;
2394                            self.set_local(local, val);
2395                        }
2396                    }
2397                }
2398            }
2399            // rhs is pointer, lhs is None (constant zero) → mark rhs as non_null
2400            if lhs_pk.is_none() {
2401                if let Some(ptr_pk) = &rhs_pk {
2402                    if let Some(local) = ptr_pk.local() {
2403                        if let Some(mut val) = self.locals.get(&local).cloned() {
2404                            val.invariants.non_null = true;
2405                            self.set_local(local, val);
2406                        }
2407                    }
2408                }
2409            }
2410        }
2411    }
2412
2413    /// Infer non_null from SwitchInt discriminant.
2414    fn infer_switch_guard(&mut self, discr: &Operand<'tcx>) {
2415        let place = match discr {
2416            Operand::Copy(p) | Operand::Move(p) => p,
2417            _ => return,
2418        };
2419        let pk = PlaceKey::from_mir_place(place);
2420        if let Some((lhs_pk, rhs_pk)) = self.binary_op_sources.get(&pk).cloned() {
2421            self.mark_guard_pointer(&lhs_pk, &rhs_pk);
2422        }
2423    }
2424
2425    fn mark_guard_pointer(&mut self, lhs: &Option<PlaceKey>, rhs: &Option<PlaceKey>) {
2426        for ptr_pk in [lhs, rhs] {
2427            if let Some(pk) = ptr_pk {
2428                if let Some(local) = pk.local() {
2429                    if let Some(mut val) = self.locals.get(&local).cloned() {
2430                        val.invariants.non_null = true;
2431                        self.set_local(local, val);
2432                    }
2433                }
2434            }
2435        }
2436    }
2437
2438    /// Check if a MIR place's type alignment is statically known.
2439    fn check_place_alignment(&self, place: &Place<'tcx>) -> bool {
2440        let ty = place.ty(self.body, self.tcx).ty;
2441        self.align_of_ty(ty) > 0
2442    }
2443
2444    /// Assert a contract fact as VM state invariants.
2445    fn assert_contract_fact(&mut self, property: &Property<'tcx>) {
2446        // A disjunctive precondition (`any(...)`) with a hazard disjunct records
2447        // that the caller accepts that hazard (e.g. `any(Trait(T, Copy),
2448        // Alias(self, ret))` on `NonNull::read`).  Inlined read/copy intrinsics
2449        // whose result structurally aliases the source are then treated as the
2450        // accepted hazard rather than a hard failure.
2451        if let Property::Or(or) = property {
2452            for group in &or.groups {
2453                if group.iter().any(|p| p.contract_kind() == ContractKind::Hazard) {
2454                    self.contract_flags.alias_hazard_accepted = true;
2455                }
2456            }
2457        }
2458        let Property::Leaf(leaf) = property else {
2459            return;
2460        };
2461        if leaf.contract_kind == ContractKind::Hazard {
2462            self.contract_flags.alias_hazard_accepted = true;
2463            return;
2464        }
2465        let kind = leaf.kind;
2466        match kind {
2467            PropertyKind::NonNull => {
2468                if let Some(val) = self.contract_target_value(property) {
2469                    self.set_non_null_for_value(property, val);
2470                }
2471            }
2472            PropertyKind::Align => {
2473                if let Some(val) = self.contract_target_value(property) {
2474                    self.set_align_for_value(property, val);
2475                }
2476            }
2477            PropertyKind::Init => {
2478                if let Some(val) = self.contract_target_value(property) {
2479                    self.set_init_for_value(property, val);
2480                }
2481            }
2482            PropertyKind::Owning => {
2483                if let Some(val) = self.contract_target_value(property) {
2484                    self.set_owning_for_value(val);
2485                }
2486            }
2487            PropertyKind::Alive => {
2488                if let Some(id) = self.contract_alloc_id_field_aware(property) {
2489                    self.alloc_mut(id).alive_assumed = true;
2490                }
2491            }
2492            PropertyKind::InBound => {
2493                if let Some(val) = self.contract_target_value(property) {
2494                    self.set_in_bounds_for_value(property, val);
2495                }
2496                if let Some(fe_place) = property.for_each() {
2497                    self.assert_in_bound_for_each(property, fe_place);
2498                    self.contract_flags.has_checked_bounds = true;
2499                }
2500            }
2501            PropertyKind::Allocated => {
2502                self.assert_allocated_fact(property);
2503            }
2504            PropertyKind::Typed => {
2505                if let Some(val) = self.contract_target_value(property) {
2506                    if let Some(alloc_id) = val.provenance_alloc_id() {
2507                        if let Some(expected_ty) = property.args().get(1)
2508                            .and_then(|a| if let PropertyArg::Ty(ty) = a { Some(*ty) } else { None })
2509                        {
2510                            self.alloc_mut(alloc_id).element_ty = Some(expected_ty);
2511                        }
2512                    }
2513                }
2514            }
2515            PropertyKind::SplitTransmute => {
2516                self.contract_flags.split_transmute_asserted = true;
2517            }
2518            PropertyKind::ValidCStr => {
2519                // A `ValidCStr(p, n)` fact guarantees `p` points to a live,
2520                // initialized, null-terminated byte buffer. Mark the target
2521                // allocation so the checker can treat it (and any of its
2522                // sub-slices) as a valid C string, and so pointer reads /
2523                // `from_raw_parts` over it see a live, initialized allocation.
2524                //
2525                // For a `&CStr`-style target the field projection (`inner`)
2526                // may not be materialised for a DST, so fall back to the base
2527                // local's own provenance (a `&CStr` reference points directly
2528                // at the byte buffer it owns).
2529                let id = self
2530                    .contract_alloc_id_field_aware(property)
2531                    .or_else(|| {
2532                        let local = self.contract_target_local(property)?;
2533                        self.locals.get(&local)?.provenance_alloc_id()
2534                    });
2535                if let Some(id) = id {
2536                    self.alloc_mut(id).dead = false;
2537                    self.alloc_mut(id).alive_assumed = true;
2538                    self.alloc_mut(id).initialized = true;
2539                    self.alloc_mut(id).nul_terminated = true;
2540                    // `ValidCStr(p, n)` carries the byte length of the
2541                    // nul-terminated buffer.  Assert the allocation covers `n`
2542                    // bytes so downstream `from_raw_parts(p, n)` / InBound
2543                    // obligations can be discharged from the exact length
2544                    // (rather than a conservative `1` placeholder).
2545                    if let Some(n) = property
2546                        .args()
2547                        .get(1)
2548                        .and_then(|a| self.resolve_contract_count(a))
2549                    {
2550                        self.path_conditions.push(self.alloc(id).size.ge(&n));
2551                    }
2552                }
2553            }
2554            PropertyKind::ValidNum => {
2555                if let Some(PropertyArg::Predicates(predicates)) = property.args().first() {
2556                    for pred in predicates {
2557                        if let Some(condition) = self.eval_predicate_as_bool(pred) {
2558                            self.path_conditions.push(condition);
2559                            // For !self.is_empty() → self.len() != 0 on
2560                            // Iter/IterMut: also assert len >= 1 to help
2561                            // Z3 with integer division reasoning.
2562                            if let Some(len_term) = self.try_simple_iter_len_from_pred(pred) {
2563                                let one = Int::from_u64(self.ctx, 1);
2564                                self.path_conditions.push(len_term.ge(&one));
2565                            }
2566                        }
2567                    }
2568                }
2569            }
2570            _ => {
2571                self.notes.push(format!(
2572                    "contract fact {:?} not directly asserted",
2573                    kind
2574                ));
2575            }
2576        }
2577    }
2578
2579    /// Get the local referenced by a contract property's target.
2580    fn contract_target_local(&self, property: &Property<'tcx>) -> Option<Local> {
2581        let cp = match property.args().first()? {
2582            PropertyArg::Expr(ContractExpr::Place(cp)) => cp,
2583            PropertyArg::Expr(ContractExpr::IndexAccess { slice, .. }) => {
2584                match slice.as_ref() {
2585                    ContractExpr::Place(cp) => cp,
2586                    _ => return None,
2587                }
2588            }
2589            _ => return None,
2590        };
2591        match cp.base {
2592            PlaceBase::Local(n) => Some(Local::from_usize(n)),
2593            PlaceBase::Arg(n) => {
2594                Some(Local::from_usize(n + 1))
2595            }
2596            PlaceBase::Return => Some(Local::from_usize(0)),
2597        }
2598    }
2599
2600    /// Materialize a fresh external allocation for an `Allocated` contract
2601    /// fact, returning a value carrying the allocation's provenance.
2602    fn materialize_external_alloc(
2603        &mut self,
2604        elem_ty: Ty<'tcx>,
2605        count_term: Option<Int<'ctx>>,
2606        val_ty: Ty<'tcx>,
2607        huge: bool,
2608    ) -> VmValue<'ctx, 'tcx> {
2609        let elem_sz_raw = self.size_of_ty(elem_ty);
2610        let heap_align = self.align_of_ty(elem_ty).max(1);
2611        let (heap_id, heap_base) = if huge || elem_sz_raw == 0 {
2612            // Struct-field targets (and generic element types): use an
2613            // unbounded external allocation so `Allocated`/`InBound` checks
2614            // auto-pass regardless of the symbolic element size.
2615            let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
2616            self.allocate_external(max_size, heap_align, Some(elem_ty))
2617        } else {
2618            let elem_sz = Int::from_u64(self.ctx, elem_sz_raw as u64);
2619            let count = count_term.unwrap_or_else(|| Int::from_u64(self.ctx, 1));
2620            let total = Int::mul(self.ctx, &[&count, &elem_sz]);
2621            self.allocate_external(total, heap_align, Some(elem_ty))
2622        };
2623        self.alloc_mut(heap_id).initialized = true;
2624        VmValue {
2625            term: heap_base,
2626            ty: val_ty,
2627            provenance: Some(Provenance {
2628                alloc_id: heap_id,
2629                offset: Int::from_u64(self.ctx, 0),
2630                is_field_offset: false,
2631            }),
2632            invariants: ValueInvariants {
2633                non_null: true,
2634                init: true,
2635                in_bounds: true,
2636                aligned: true,
2637                align_n: if heap_align > 1 { Some(heap_align) } else { None },
2638                is_field_offset: false,
2639            },
2640        }
2641    }
2642
2643    /// Assert an `Allocated(p, T, n)` contract fact by materializing a fresh
2644    /// external allocation for the pointer-typed target.
2645    ///
2646    /// - For a whole pointer parameter (`src`), the allocation is sized
2647    ///   `n * sizeof(T)` so downstream pointer arithmetic stays in bounds.
2648    /// - For a plain pointer *field* (e.g. `RawVecInner::ptr`), the allocation
2649    ///   is written back to the field via `set_contract_target_value`, and is
2650    ///   unbounded so field-subrange `InBound` checks auto-pass.
2651    /// - For `IterElements`/`Downcast` targets (e.g. `buckets.iter()`), the
2652    ///   container itself is not a pointer — keep the legacy whole-local
2653    ///   behaviour.
2654    fn assert_allocated_fact(&mut self, property: &Property<'tcx>) {
2655        let elem_ty = property.args().get(1)
2656            .and_then(|a| if let PropertyArg::Ty(ty) = a { Some(*ty) } else { None });
2657        let count_term = property.args().get(2).and_then(|a| self.resolve_contract_count(a));
2658        let Some(elem_ty) = elem_ty else { return };
2659
2660        let has_nonfield = property.args().first()
2661            .and_then(|a| match a {
2662                PropertyArg::Expr(ContractExpr::Place(cp)) => Some(cp),
2663                PropertyArg::Expr(ContractExpr::IndexAccess { slice, .. }) => {
2664                    match slice.as_ref() {
2665                        ContractExpr::Place(cp) => Some(cp),
2666                        _ => None,
2667                    }
2668                }
2669                _ => None,
2670            })
2671            .map(|cp| {
2672                cp.projections.iter().any(|p| {
2673                    !matches!(p, crate::verify::contract::ContractProjection::Field { .. })
2674                })
2675            })
2676            .unwrap_or(false);
2677
2678        if has_nonfield {
2679            // IterElements/Downcast target: legacy whole-local, exact size.
2680            let Some(local) = self.contract_target_local(property) else { return };
2681            let Some(val) = self.locals.get(&local).cloned() else { return };
2682            if let Some(alloc_id) = val.provenance_alloc_id() {
2683                self.alloc_mut(alloc_id).dead = false;
2684            }
2685            let v = self.materialize_external_alloc(elem_ty, count_term, val.ty, false);
2686            self.set_local(local, v);
2687        } else {
2688            let Some((local, field_path)) = self.contract_field_path(property) else { return };
2689            if field_path.is_empty() {
2690                // Whole pointer parameter: exact size.
2691                let Some(val) = self.locals.get(&local).cloned() else { return };
2692                if let Some(alloc_id) = val.provenance_alloc_id() {
2693                    self.alloc_mut(alloc_id).dead = false;
2694                }
2695                let v = self.materialize_external_alloc(elem_ty, count_term, val.ty, false);
2696                self.set_local(local, v);
2697            } else {
2698                // Field target. Only a *direct* pointer field (`NonNull<T>` /
2699                // `*mut T` / `*const T`) to a *simple* element type (primitive or
2700                // generic param, e.g. `NonNull<u8>`) carries the allocation
2701                // itself without a nested field decomposition; a wrapped field
2702                // (`Option<NonNull>`, `Box`) or a pointer-to-ADT (`NonNull<LeafNode>`,
2703                // whose fields were decomposed by param init) is handled via the
2704                // legacy whole-local path to avoid losing those relationships.
2705                let is_direct_simple_ptr = self.field_value(local, &field_path)
2706                    .map(|val| {
2707                        (matches!(val.ty.kind(), rustc_middle::ty::TyKind::RawPtr(..))
2708                            || matches!(val.ty.kind(), rustc_middle::ty::TyKind::Adt(adt, _)
2709                                if api_classify::is_std_nonnull(&self.tcx.def_path_str(adt.did()))))
2710                            && (elem_ty.is_primitive()
2711                                || matches!(elem_ty.kind(), rustc_middle::ty::TyKind::Param(_)))
2712                    })
2713                    .unwrap_or(false);
2714                if is_direct_simple_ptr {
2715                    let Some(val) = self.field_value(local, &field_path).cloned() else { return };
2716                    if let Some(alloc_id) = val.provenance_alloc_id() {
2717                        self.alloc_mut(alloc_id).dead = false;
2718                    }
2719                    let v = self.materialize_external_alloc(elem_ty, count_term, val.ty, true);
2720                    self.set_field_value(local, field_path, v);
2721                } else {
2722                    let Some(val) = self.locals.get(&local).cloned() else { return };
2723                    if let Some(alloc_id) = val.provenance_alloc_id() {
2724                        self.alloc_mut(alloc_id).dead = false;
2725                    }
2726                    let v = self.materialize_external_alloc(elem_ty, count_term, val.ty, false);
2727                    self.set_local(local, v);
2728                }
2729            }
2730        }
2731    }
2732
2733    /// Resolve a contract place to `(local, field_path)`. Field projections
2734    /// are accumulated into `field_path`; `Downcast`/`IterElements` terminate
2735    /// the path (they unwrap the value in place).
2736    fn contract_field_path(&self, property: &Property<'tcx>) -> Option<(Local, Vec<usize>)> {
2737        let cp = match property.args().first()? {
2738            PropertyArg::Expr(ContractExpr::Place(cp)) => cp,
2739            PropertyArg::Expr(ContractExpr::IndexAccess { slice, .. }) => {
2740                match slice.as_ref() {
2741                    ContractExpr::Place(cp) => cp,
2742                    _ => return None,
2743                }
2744            }
2745            _ => return None,
2746        };
2747        let local = match cp.base {
2748            PlaceBase::Local(n) => Local::from_usize(n),
2749            PlaceBase::Arg(n) => Local::from_usize(n + 1),
2750            PlaceBase::Return => Local::from_usize(0),
2751        };
2752        let mut path = Vec::new();
2753        for proj in &cp.projections {
2754            match proj {
2755                crate::verify::contract::ContractProjection::Field { index, .. } => {
2756                    path.push(*index);
2757                }
2758                _ => break,
2759            }
2760        }
2761        Some((local, path))
2762    }
2763
2764    /// Get the VmValue for a contract property's target, following field
2765    /// projections so that `Align(self.heap, T)` resolves to the `heap` field
2766    /// value rather than the whole `self` reference.
2767    fn contract_target_value(&mut self, property: &Property<'tcx>) -> Option<VmValue<'ctx, 'tcx>> {
2768        let (local, path) = self.contract_field_path(property)?;
2769        if path.is_empty() {
2770            self.locals.get(&local).cloned()
2771        } else {
2772            self.field_value(local, &path).cloned()
2773        }
2774    }
2775
2776    /// Write a contract target value back to its (possibly field) location.
2777    fn set_contract_target_value(&mut self, property: &Property<'tcx>, val: VmValue<'ctx, 'tcx>) {
2778        if let Some((local, path)) = self.contract_field_path(property) {
2779            if path.is_empty() {
2780                self.set_local(local, val);
2781            } else {
2782                self.set_field_value(local, path, val);
2783            }
2784        }
2785    }
2786
2787    /// Resolve the alloc_id for a contract property target, following
2788    /// field projections to locate the actual field value's provenance.
2789    fn contract_alloc_id_field_aware(&mut self, property: &Property<'tcx>) -> Option<AllocId> {
2790        let cp = match property.args().first()? {
2791            PropertyArg::Expr(ContractExpr::Place(cp)) => cp.clone(),
2792            _ => return None,
2793        };
2794        let local = match cp.base {
2795            PlaceBase::Local(n) => Local::from_usize(n),
2796            PlaceBase::Arg(n) => Local::from_usize(n + 1),
2797            PlaceBase::Return => Local::from_usize(0),
2798        };
2799        let mut field_path: Vec<usize> = Vec::new();
2800        for proj in &cp.projections {
2801            match proj {
2802                crate::verify::contract::ContractProjection::Field { index, .. } => {
2803                    field_path.push(*index);
2804                }
2805                _ => return None,
2806            }
2807        }
2808        if field_path.is_empty() {
2809            self.locals.get(&local)?.provenance_alloc_id()
2810        } else {
2811            self.field_value(local, &field_path)?.provenance_alloc_id()
2812        }
2813    }
2814
2815    /// Resolve a contract count argument to a Z3 term by looking up
2816    /// the corresponding function parameter in the VM state.
2817    fn resolve_contract_count(&self, arg: &PropertyArg<'tcx>) -> Option<Int<'ctx>> {
2818        match arg {
2819            PropertyArg::Expr(ContractExpr::Const(n)) => {
2820                Some(Int::from_u64(self.ctx, *n as u64))
2821            }
2822            // Delegate field-projected places and arithmetic (e.g. `cap * elem_size`)
2823            // to the general simple evaluator.
2824            PropertyArg::Expr(expr) => self.eval_contract_expr_simple(expr),
2825            _ => None,
2826        }
2827    }
2828
2829    /// Evaluate a numeric predicate to a Z3 Bool for path-condition assertion.
2830    fn eval_predicate_as_bool(&self, pred: &crate::verify::contract::NumericPredicate<'tcx>) -> Option<Bool<'ctx>> {
2831        use crate::verify::contract::{ContractExpr, RelOp};
2832        let lhs = self.eval_contract_expr_simple(&pred.lhs)?;
2833        let rhs = match &pred.rhs {
2834            ContractExpr::Const(v) => Int::from_u64(self.ctx, *v as u64),
2835            _ => self.eval_contract_expr_simple(&pred.rhs)?,
2836        };
2837        Some(match pred.op {
2838            RelOp::Eq => lhs._eq(&rhs),
2839            RelOp::Ne => lhs._eq(&rhs).not(),
2840            RelOp::Le => lhs.le(&rhs),
2841            RelOp::Lt => lhs.lt(&rhs),
2842            RelOp::Ge => lhs.ge(&rhs),
2843            RelOp::Gt => lhs.gt(&rhs),
2844        })
2845    }
2846
2847    fn eval_contract_expr_simple(&self, expr: &crate::verify::contract::ContractExpr<'tcx>) -> Option<Int<'ctx>> {
2848        use crate::verify::contract::{ContractExpr, NumericOp, PlaceBase};
2849        match expr {
2850            ContractExpr::SizeOf(ty) => {
2851                let size = self.size_of_ty(*ty).max(1);
2852                Some(Int::from_u64(self.ctx, size as u64))
2853            }
2854            ContractExpr::Place(cp) => {
2855                let local = match cp.base {
2856                    PlaceBase::Local(n) => Local::from_usize(n),
2857                    PlaceBase::Arg(n) => Local::from_usize(n + 1),
2858                    PlaceBase::Return => Local::from_usize(0),
2859                };
2860                let mut path: Vec<usize> = Vec::new();
2861                for proj in &cp.projections {
2862                    match proj {
2863                        crate::verify::contract::ContractProjection::Field { index, .. } => {
2864                            path.push(*index);
2865                        }
2866                        // Downcast / IterElements are not scalar numeric values.
2867                        _ => return None,
2868                    }
2869                }
2870                if path.is_empty() {
2871                    self.local_value(local).map(|v| v.term.clone())
2872                } else {
2873                    self.field_value(local, &path).map(|v| v.term.clone())
2874                }
2875            }
2876            ContractExpr::Len(inner) => {
2877                // Try field-based len for Iter/IterMut first.
2878                if let Some(val) = self.eval_contract_expr_simple_value(inner) {
2879                    if let Some(term) = self.try_simple_iter_len(&val) {
2880                        return Some(term);
2881                    }
2882                }
2883                let val = self.eval_contract_expr_simple_value(inner)?;
2884                let alloc_id = val.provenance_alloc_id()?;
2885                let alloc = self.alloc(alloc_id);
2886                let elem_ty = alloc.element_ty?;
2887                let elem_size = self.size_of_ty(elem_ty).max(1) as u64;
2888                if elem_size == 1 {
2889                    return Some(alloc.size.clone());
2890                }
2891                let elem_term = Int::from_u64(self.ctx, elem_size);
2892                Some(alloc.size.div(&elem_term))
2893            }
2894            ContractExpr::Binary { op: NumericOp::Mul, lhs, rhs } => {
2895                let l = self.eval_contract_expr_simple(lhs)?;
2896                let r = self.eval_contract_expr_simple(rhs)?;
2897                Some(Int::mul(self.ctx, &[&l, &r]))
2898            }
2899            ContractExpr::Binary { op: NumericOp::Add, lhs, rhs } => {
2900                let l = self.eval_contract_expr_simple(lhs)?;
2901                let r = self.eval_contract_expr_simple(rhs)?;
2902                Some(Int::add(self.ctx, &[&l, &r]))
2903            }
2904            ContractExpr::Binary { op: NumericOp::Sub, lhs, rhs } => {
2905                let l = self.eval_contract_expr_simple(lhs)?;
2906                let r = self.eval_contract_expr_simple(rhs)?;
2907                Some(Int::sub(self.ctx, &[&l, &r]))
2908            }
2909            ContractExpr::Binary { op: NumericOp::Div, lhs, rhs } => {
2910                let l = self.eval_contract_expr_simple(lhs)?;
2911                let r = self.eval_contract_expr_simple(rhs)?;
2912                Some(l.div(&r))
2913            }
2914            ContractExpr::Const(n) => Some(Int::from_u64(self.ctx, *n as u64)),
2915            _ => None,
2916        }
2917    }
2918
2919    fn eval_contract_expr_simple_value(&self, expr: &crate::verify::contract::ContractExpr<'tcx>) -> Option<VmValue<'ctx, 'tcx>> {
2920        match expr {
2921            ContractExpr::Place(cp) => {
2922                match cp.base {
2923                    PlaceBase::Local(n) => {
2924                        self.local_value(Local::from_usize(n)).cloned()
2925                    }
2926                    _ => None,
2927                }
2928            }
2929            _ => None,
2930        }
2931    }
2932
2933    /// Try field-based len for Iter/IterMut references (same logic as
2934    /// `interpreter_iter_len` in call.rs). Used by `eval_contract_expr_simple`
2935    /// so that ContractFact assertions use the same symbolic term as the
2936    /// VM execution path.
2937    fn try_simple_iter_len(&self, arg_val: &VmValue<'ctx, 'tcx>) -> Option<Int<'ctx>> {
2938        use rustc_middle::ty::TyKind;
2939        let is_iter = match arg_val.ty.kind() {
2940            TyKind::Ref(_, pointee, _) => match pointee.kind() {
2941                TyKind::Adt(adt_def, _) => {
2942                    let name = self.tcx.def_path_str(adt_def.did());
2943                    api_classify::is_std_iter_or_itermut(&name)
2944                }
2945                _ => false,
2946            },
2947            _ => false,
2948        };
2949        if !is_iter { return None; }
2950        let local = Local::from_usize(1);
2951        let ptr = self.field_value(local, &[0])?;
2952        let end = self.field_value(local, &[1])?;
2953        let pp = ptr.provenance.as_ref()?;
2954        let ep = end.provenance.as_ref()?;
2955        if pp.alloc_id != ep.alloc_id { return None; }
2956        let diff = Int::sub(self.ctx, &[&ep.offset, &pp.offset]);
2957        let sz = Int::from_u64(self.ctx, self.iter_elem_size(ptr));
2958        Some(diff.div(&sz))
2959    }
2960
2961    /// For a predicate of the form `self.len() != 0` (i.e. `!self.is_empty()`),
2962    /// if the self is an Iter/IterMut reference, return the field-based len term
2963    /// so that a `len >= 1` constraint can be added.
2964    fn try_simple_iter_len_from_pred(
2965        &self,
2966        pred: &crate::verify::contract::NumericPredicate<'tcx>,
2967    ) -> Option<Int<'ctx>> {
2968        use crate::verify::contract::{ContractExpr, RelOp};
2969        if !matches!(pred.op, RelOp::Ne) { return None; }
2970        if !matches!(&pred.rhs, ContractExpr::Const(0)) { return None; }
2971        let ContractExpr::Len(inner) = &pred.lhs else { return None; };
2972        let val = self.eval_contract_expr_simple_value(inner)?;
2973        self.try_simple_iter_len(&val)
2974    }
2975
2976    /// If `discr` is a local that was set by `iterpreter_iter_is_empty`
2977    /// for an Iter/IterMut struct, push `len >= 1` as a path condition.
2978    fn inject_is_empty_len(&mut self, discr: &Operand<'tcx>) {
2979        let place = match discr {
2980            Operand::Copy(p) | Operand::Move(p) => p,
2981            _ => return,
2982        };
2983        if let Some(len_expr) = self.is_empty_len.get(&place.local) {
2984            let one = Int::from_u64(self.ctx, 1);
2985            self.path_conditions.push(len_expr.ge(&one));
2986        }
2987    }
2988
2989    /// If `local` is a reference to Iter/IterMut and field 0 (ptr)
2990    /// is updated, increment the cumulative ptr offset so that
2991    /// `interpreter_iter_len` can express `len = initial_len - offset`
2992    /// instead of nested `(end - (ptr + sz + sz + ...)) / sz`.
2993    fn track_iter_ptr_update(
2994        &mut self,
2995        local: Local,
2996    ) {
2997        let local_val = match self.locals.get(&local) {
2998            Some(v) => v,
2999            None => return,
3000        };
3001        let is_iter = match local_val.ty.kind() {
3002            rustc_middle::ty::TyKind::Ref(_, pointee, _) => match pointee.kind() {
3003                rustc_middle::ty::TyKind::Adt(adt_def, _) => {
3004                    let name = self.tcx.def_path_str(adt_def.did());
3005                    api_classify::is_std_iter_or_itermut(&name)
3006                }
3007                _ => false,
3008            },
3009            _ => false,
3010        };
3011        if !is_iter { return; }
3012        let one = Int::from_u64(self.ctx, 1);
3013        let new_offset = match self.iter_ptr_offset.get(&local) {
3014            Some(prev) => Int::add(self.ctx, &[prev, &one]),
3015            None => one,
3016        };
3017        self.iter_ptr_offset.insert(local, new_offset);
3018    }
3019
3020    /// After inlining post_inc_start/pre_dec_end for Iter/IterMut,
3021    /// increment the tracked ptr offset so that `interpreter_iter_len`
3022    /// can compute `base_len - offset` compactly.
3023    fn track_iter_ptr_after_inline(&mut self) {
3024        let mut to_update: Vec<Local> = Vec::new();
3025        for (&local, val) in self.locals.iter() {
3026            let is_iter = match val.ty.kind() {
3027                rustc_middle::ty::TyKind::Ref(_, pointee, _) => match pointee.kind() {
3028                    rustc_middle::ty::TyKind::Adt(adt_def, _) => {
3029                        let name = self.tcx.def_path_str(adt_def.did());
3030                        name.ends_with("::Iter") || name == "Iter"
3031                            || name.ends_with("::IterMut") || name == "IterMut"
3032                    }
3033                    _ => false,
3034                },
3035                _ => false,
3036            };
3037            if is_iter {
3038                to_update.push(local);
3039            }
3040        }
3041        let one = Int::from_u64(self.ctx, 1);
3042        for local in to_update {
3043            let new_offset = match self.iter_ptr_offset.get(&local) {
3044                Some(prev) => Int::add(self.ctx, &[prev, &one]),
3045                None => one.clone(),
3046            };
3047            self.iter_ptr_offset.insert(local, new_offset);
3048        }
3049    }
3050
3051    /// Set non_null invariant on the target value.
3052    fn set_non_null_for_value(&mut self, property: &Property<'tcx>, mut val: VmValue<'ctx, 'tcx>) {
3053        val.invariants.non_null = true;
3054        self.set_contract_target_value(property, val);
3055    }
3056
3057    fn set_in_bounds_for_value(&mut self, property: &Property<'tcx>, mut val: VmValue<'ctx, 'tcx>) {
3058        val.invariants.in_bounds = true;
3059        self.set_contract_target_value(property, val);
3060    }
3061
3062    fn assert_in_bound_for_each(&mut self, property: &Property<'tcx>, fe_place: &crate::verify::contract::ContractPlace<'tcx>) {
3063        let fe_local = match fe_place.base {
3064            PlaceBase::Arg(n) => Local::from_usize(n + 1),
3065            PlaceBase::Local(n) => Local::from_usize(n),
3066            _ => return,
3067        };
3068        let fe_val = match self.locals.get(&fe_local).cloned() {
3069            Some(v) => v,
3070            None => return,
3071        };
3072        let fe_alloc_id = match fe_val.provenance_alloc_id() {
3073            Some(id) => id,
3074            None => return,
3075        };
3076        let byte_vals: Vec<(usize, Int<'ctx>)> = self
3077            .alloc_byte_values(fe_alloc_id)
3078            .into_iter()
3079            .map(|(off, term)| (off, term.clone()))
3080            .collect();
3081        if byte_vals.is_empty() {
3082            return;
3083        }
3084        let slice_local = match property.args().first() {
3085            Some(PropertyArg::Expr(ContractExpr::IndexAccess { slice, .. })) => {
3086                match slice.as_ref() {
3087                    ContractExpr::Place(cp) => match cp.base {
3088                        PlaceBase::Arg(n) => Some(Local::from_usize(n + 1)),
3089                        PlaceBase::Local(n) => Some(Local::from_usize(n)),
3090                        _ => None,
3091                    },
3092                    _ => None,
3093                }
3094            }
3095            Some(PropertyArg::Expr(ContractExpr::Place(cp))) => match cp.base {
3096                PlaceBase::Arg(n) => Some(Local::from_usize(n + 1)),
3097                PlaceBase::Local(n) => Some(Local::from_usize(n)),
3098                _ => None,
3099            },
3100            _ => None,
3101        };
3102        let data_size = slice_local
3103            .and_then(|loc| self.locals.get(&loc))
3104            .and_then(|sl_val| sl_val.provenance_alloc_id())
3105            .map(|da_id| self.alloc(da_id).size.clone());
3106        let elem_sz = slice_local
3107            .and_then(|loc| self.locals.get(&loc))
3108            .and_then(|sl_val| sl_val.provenance_alloc_id())
3109            .and_then(|da_id| self.alloc(da_id).element_ty)
3110            .map(|ty| self.size_of_ty(ty) as u64)
3111            .unwrap_or(1)
3112            .max(1);
3113        let Some(data_size) = data_size else { return };
3114        let elem_sz_term = Int::from_u64(self.ctx, elem_sz);
3115        let len = data_size.div(&elem_sz_term);
3116        let zero = Int::from_u64(self.ctx, 0);
3117        for (_, term) in &byte_vals {
3118            self.path_conditions.push(term.ge(&zero));
3119            self.path_conditions.push(term.lt(&len));
3120        }
3121    }
3122
3123    /// Set align invariant on the target value.
3124    fn set_align_for_value(&mut self, property: &Property<'tcx>, mut val: VmValue<'ctx, 'tcx>) {
3125        val.invariants.aligned = true;
3126        if let Some(PropertyArg::Ty(ty)) = property.args().get(1) {
3127            let align = self.align_of_ty(*ty);
3128            if align > 1 {
3129                val.invariants.align_n = Some(align);
3130            }
3131        }
3132        self.set_contract_target_value(property, val);
3133    }
3134
3135    /// Set init invariant on the target value and its allocation.
3136    fn set_init_for_value(&mut self, property: &Property<'tcx>, val: VmValue<'ctx, 'tcx>) {
3137        if let Some(prov) = &val.provenance {
3138            self.alloc_mut(prov.alloc_id).initialized = true;
3139        }
3140        if let Some((local, path)) = self.contract_field_path(property) {
3141            let existing = if path.is_empty() {
3142                self.locals.get(&local).cloned()
3143            } else {
3144                self.field_value(local, &path).cloned()
3145            };
3146            if let Some(mut existing) = existing {
3147                existing.invariants.init = true;
3148                if let Some(prov) = &existing.provenance {
3149                    self.alloc_mut(prov.alloc_id).initialized = true;
3150                }
3151                if path.is_empty() {
3152                    self.set_local(local, existing);
3153                } else {
3154                    self.set_field_value(local, path, existing);
3155                }
3156            }
3157        }
3158    }
3159
3160    /// Set owning invariant on the target value.
3161    fn set_owning_for_value(&mut self, val: VmValue<'ctx, 'tcx>) {
3162        if let Some(prov) = &val.provenance {
3163            self.alloc_mut(prov.alloc_id).initialized = true;
3164        }
3165    }
3166
3167    /// Extract the pointee type if `ty` is `NonNull<P>` or wrapped in
3168    /// `Option<NonNull<P>>`. Returns `Some(P)`.
3169    fn find_nn_pointee(&self, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
3170        use rustc_middle::ty::TyKind;
3171        match ty.kind() {
3172            TyKind::Adt(adt_def, substs) => {
3173                let def_path = self.tcx.def_path_str(adt_def.did());
3174                let is_nn = api_classify::is_std_nonnull(&def_path);
3175                if is_nn {
3176                    substs.first().and_then(|s| s.as_type())
3177                } else if api_classify::is_std_option(&def_path)
3178                {
3179                    if let Some(inner) = substs.first().and_then(|s| s.as_type()) {
3180                        match inner.kind() {
3181                            TyKind::Adt(ia, is_) => {
3182                                let ip = self.tcx.def_path_str(ia.did());
3183                                let is_nn_inner = api_classify::is_std_nonnull(&ip);
3184                                if is_nn_inner {
3185                                    is_.first().and_then(|s| s.as_type())
3186                                } else {
3187                                    None
3188                                }
3189                            }
3190                            _ => None,
3191                        }
3192                    } else {
3193                        None
3194                    }
3195                } else {
3196                    None
3197                }
3198            }
3199            _ => None,
3200        }
3201    }
3202
3203    /// Try to propagate provenance from pointer-extracting calls
3204    /// (e.g. as_ptr, as_mut_ptr). Returns true if applied.
3205    /// Try to propagate provenance from pointer-extracting calls
3206    /// (e.g. as_ptr, as_mut_ptr). Returns true if applied.
3207    fn try_as_ptr_fallback(
3208        &mut self,
3209        dest: Local,
3210        func: &Operand<'tcx>,
3211        first_arg_val: VmValue<'ctx, 'tcx>,
3212        first_arg_op: &Operand<'tcx>,
3213    ) -> bool {
3214        let name = crate::helpers::mir_utils::call_name(self.tcx, func);
3215        if !api_classify::is_as_ptr(&name) {
3216            return false;
3217        }
3218        let dest_ty = self.body.local_decls[dest].ty;
3219        let prov = first_arg_val.provenance.clone().or_else(|| {
3220            if let Operand::Move(place) | Operand::Copy(place) = first_arg_op {
3221                self.local_alloc_ids.get(&place.local).map(|&id| {
3222                    Provenance { alloc_id: id, offset: Int::from_u64(self.ctx, 0), is_field_offset: false }
3223                })
3224            } else {
3225                None
3226            }
3227        });
3228        if let Some(ref prov) = prov {
3229            self.alloc_mut(prov.alloc_id).initialized = true;
3230            self.set_local(dest, VmValue {
3231                term: first_arg_val.term.clone(),
3232                ty: dest_ty,
3233                provenance: Some(prov.clone()),
3234                invariants: ValueInvariants {
3235                    non_null: true, aligned: true, init: true,
3236                    in_bounds: first_arg_val.invariants.in_bounds,
3237                    align_n: first_arg_val.invariants.align_n,
3238                    is_field_offset: false,
3239                },
3240            });
3241            return true;
3242        }
3243        false
3244    }
3245
3246    /// If `operand` is a constant reference to a byte array (e.g. `b"hello\0"`),
3247    /// extract the raw bytes and create a tracked allocation. Updates `val`
3248    /// in-place with the proper provenance and invariants.
3249    pub(crate) fn try_materialize_const_bytes(
3250        &mut self,
3251        val: &mut VmValue<'ctx, 'tcx>,
3252        operand: &Operand<'tcx>,
3253    ) {
3254        // Use the operand's type (before any pointer cast) to check for byte arrays.
3255        let operand_val = self.value_of_operand(operand);
3256        let op_ty = operand_val.ty;
3257        let (pointee_ty, _is_ref) = match op_ty.kind() {
3258            rustc_middle::ty::TyKind::Ref(_, inner_ty, _) => (*inner_ty, true),
3259            rustc_middle::ty::TyKind::RawPtr(inner_ty, _) => (*inner_ty, false),
3260            _ => {
3261                // Fallback: use val's type
3262                let val_ty = val.ty;
3263                match val_ty.kind() {
3264                    rustc_middle::ty::TyKind::Ref(_, inner_ty, _) => (*inner_ty, true),
3265                    rustc_middle::ty::TyKind::RawPtr(inner_ty, _) => (*inner_ty, false),
3266                    _ => return,
3267                }
3268            }
3269        };
3270        match pointee_ty.kind() {
3271            rustc_middle::ty::TyKind::Array(elem_ty, _)
3272            | rustc_middle::ty::TyKind::Slice(elem_ty) => {
3273                let is_byte = match elem_ty.kind() {
3274                    rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8) => true,
3275                    rustc_middle::ty::TyKind::Int(rustc_middle::ty::IntTy::I8) => true,
3276                    _ => false,
3277                };
3278                if is_byte {
3279                    let bytes_opt = crate::helpers::mir_utils::extract_const_bytes_from_operand(self.tcx, operand)
3280                        .or_else(|| self.trace_to_const_bytes(operand));
3281                    if let Some(bytes) = bytes_opt {
3282                        let size = z3::ast::Int::from_u64(self.ctx, bytes.len() as u64);
3283                        let (alloc_id, base) = self.allocate(
3284                            size,
3285                            self.align_of_ty(pointee_ty),
3286                            Some(pointee_ty),
3287                        );
3288                        self.alloc_mut(alloc_id).initialized = true;
3289                        for (i, &b) in bytes.iter().enumerate() {
3290                            self.record_byte_value(alloc_id, i,
3291                                z3::ast::Int::from_u64(self.ctx, b as u64));
3292                            if b == 0 {
3293                                self.mark_byte_nul(alloc_id, i);
3294                            } else {
3295                                self.mark_byte_non_nul(alloc_id, i);
3296                            }
3297                        }
3298                        val.term = base;
3299                        val.provenance = Some(super::state::Provenance {
3300                            alloc_id,
3301                            offset: z3::ast::Int::from_u64(self.ctx, 0),
3302                            is_field_offset: false,
3303                        });
3304                        val.invariants = ValueInvariants {
3305                            non_null: true, init: true, aligned: true, in_bounds: false,
3306                            align_n: None,
3307                            is_field_offset: false,
3308                        };
3309                    }
3310                }
3311            }
3312            _ => {}
3313        }
3314    }
3315
3316    pub(crate) fn trace_to_const_bytes(&self, operand: &Operand<'tcx>) -> Option<Vec<u8>> {
3317        let place = match operand {
3318            Operand::Copy(p) | Operand::Move(p) => p,
3319            _ => return None,
3320        };
3321        let base_local = if place.projection.len() == 1
3322            && matches!(place.projection.first().map(|p| p.kind()),
3323                Some(rustc_middle::mir::ProjectionElem::Deref))
3324        {
3325            place.local
3326        } else if place.projection.is_empty() {
3327            place.local
3328        } else {
3329            return None;
3330        };
3331        for block in self.body.basic_blocks.iter() {
3332            for stmt in &block.statements {
3333                if let StatementKind::Assign(assign) = &stmt.kind {
3334                    let (dest, rvalue) = &**assign;
3335                    if dest.local != base_local || !dest.projection.is_empty() {
3336                        continue;
3337                    }
3338                    match rvalue {
3339                        #[cfg(rapx_rvalue_use_with_retag)]
3340                        Rvalue::Use(op, _) => {
3341                            return crate::helpers::mir_utils::extract_const_bytes_from_operand(self.tcx, op)
3342                                .or_else(|| self.trace_to_const_bytes(op));
3343                        }
3344                        #[cfg(not(rapx_rvalue_use_with_retag))]
3345                        Rvalue::Use(op) => {
3346                            return crate::helpers::mir_utils::extract_const_bytes_from_operand(self.tcx, op)
3347                                .or_else(|| self.trace_to_const_bytes(op));
3348                        }
3349                        Rvalue::Ref(_, _, p) => {
3350                            let op = Operand::Copy(*p);
3351                            return self.trace_to_const_bytes(&op);
3352                        }
3353                        _ => return None,
3354                    }
3355                }
3356            }
3357        }
3358        None
3359    }
3360
3361    /// Propagate byte values from a source place's allocation to the
3362    /// provenance allocation of a reference. This ensures that when we
3363    /// create `&bytes` from an aggregate, the byte-level tracking follows.
3364    /// Propagate a source place's per-field values to a reference destination,
3365    /// shifting the field path by the source place's `Field` projection prefix.
3366    /// E.g. for `_3 = &(_1.0)` where `_1` is a `Handle { node: NodeRef { node:
3367    /// NonNull<..>, .. }, .. }`, the nested `NonNull`'s field value stored at
3368    /// path `[0, 1]` becomes available at `_3`'s path `[1]`, so an inlined
3369    /// callee that dereferences `_3` and reads its `node` field sees the
3370    /// provenance of the underlying allocation.
3371    fn propagate_field_values_to_ref(&mut self, source_place: &Place<'tcx>, dest: Local) {
3372        // Support both `&(local.field...)` (Field projection prefix) and
3373        // `&(*local)` (reborrow of a reference, pure Deref).  In the latter
3374        // case the reference's own per-field values already describe the
3375        // pointee, so they are copied unchanged.
3376        let only_field_deref = source_place.projection.iter().all(|p| {
3377            matches!(p.kind(), rustc_middle::mir::ProjectionElem::Field(..)
3378                | rustc_middle::mir::ProjectionElem::Deref)
3379        });
3380        if !only_field_deref || source_place.projection.is_empty() {
3381            return;
3382        }
3383        let field_prefix: Vec<usize> = source_place
3384            .projection
3385            .iter()
3386            .filter_map(|p| match p.kind() {
3387                rustc_middle::mir::ProjectionElem::Field(fi, _) => Some(fi.as_usize()),
3388                _ => None,
3389            })
3390            .collect();
3391        let keys: Vec<Vec<usize>> = self
3392            .field_values
3393            .keys()
3394            .filter(|(l, _)| *l == source_place.local)
3395            .map(|(_, p)| p.clone())
3396            .collect();
3397        for path in keys {
3398            let matches_prefix = field_prefix.is_empty()
3399                || (path.len() > field_prefix.len() && path[..field_prefix.len()] == field_prefix[..]);
3400            if matches_prefix {
3401                let rest = if field_prefix.is_empty() {
3402                    path.clone()
3403                } else {
3404                    path[field_prefix.len()..].to_vec()
3405                };
3406                if let Some(v) = self.field_values.get(&(source_place.local, path.clone())).cloned() {
3407                    self.set_field_value(dest, rest, v);
3408                }
3409            }
3410        }
3411    }
3412
3413    fn propagate_byte_values_to_ref(
3414        &mut self,
3415        source_place: &Place<'tcx>,
3416        ref_val: &VmValue<'ctx, 'tcx>,
3417    ) {
3418        let Some(src_alloc_id) = self.local_alloc_ids.get(&source_place.local).copied() else {
3419            return;
3420        };
3421        let Some(ref_alloc_id) = ref_val.provenance_alloc_id() else {
3422            return;
3423        };
3424        if src_alloc_id == ref_alloc_id {
3425            return; // same allocation, bytes already there
3426        }
3427        // Copy per-byte tracking from source alloc to ref's alloc.
3428        self.copy_byte_tracking(src_alloc_id, ref_alloc_id);
3429    }
3430
3431    /// Return the per-field types for an aggregate's operands.
3432    fn aggregate_field_tys(&self, ty: Ty<'tcx>) -> Vec<Ty<'tcx>> {
3433        match ty.kind() {
3434            rustc_middle::ty::TyKind::Array(elem_ty, _len) => {
3435                // We don't need the exact count — just the element type for size
3436                vec![*elem_ty]
3437            }
3438            rustc_middle::ty::TyKind::Tuple(elems) => {
3439                elems.iter().collect()
3440            }
3441            rustc_middle::ty::TyKind::Adt(adt_def, substs) => {
3442                if adt_def.is_enum() { return vec![]; }
3443                let variant = adt_def.non_enum_variant();
3444                variant.fields.iter()
3445                    .map(|f| {
3446                        let unnorm = f.ty(self.tcx, substs);
3447                        unnorm.skip_norm_wip()
3448                    })
3449                    .collect()
3450            }
3451            _ => vec![],
3452        }
3453    }
3454}
3455
3456/// Return the next MIR block after `block` in a finite verification path.
3457fn chosen_successor(path: &Path, block: BasicBlock, occurrence: usize) -> Option<BasicBlock> {
3458    let mut count = 0;
3459    let mut previous = None;
3460    for step in path.steps.iter() {
3461        match step {
3462            PathStep::Block(current) => {
3463                if previous == Some(block) {
3464                    count += 1;
3465                    if count == occurrence {
3466                        return Some(*current);
3467                    }
3468                }
3469                previous = Some(*current);
3470            }
3471            PathStep::Checkpoint(_) => return None,
3472        }
3473    }
3474    None
3475}
3476
3477/// Try to resolve a u64 constant from a PlaceKey's source in the VM state.
3478fn resolve_u64_from_place_key<'ctx, 'tcx>(
3479    pk: &Option<PlaceKey>,
3480    state: &VmState<'ctx, 'tcx>,
3481) -> Option<u64> {
3482    let pk = pk.as_ref()?;
3483    let local = pk.local()?;
3484    let val = state.local_value(local)?;
3485    val.term.as_u64()
3486}