Skip to main content

rapx/verify/vm/
call.rs

1//! Call handling for the symbolic VM.
2//!
3//! Bridges the existing call summary infrastructure (`call_summary`)
4//! with the new symbolic VM state. The `exec_call` method is called
5//! from `exec.rs` when a `Call` terminator is encountered.
6//!
7//! When the callee has MIR available, the VM recursively inlines the
8//! callee's body to achieve context-sensitive precision, unless a
9//! fn_simulator summary provides more precise hand-crafted invariants.
10//! Otherwise it falls back to the summary-based approach.
11
12use rustc_hir::def_id::DefId;
13use rustc_middle::mir::{BasicBlock, Local, Operand, TerminatorKind};
14use rustc_middle::ty::{Ty, TyKind};
15use z3::ast::{Ast, Bool, Int};
16
17use crate::compat::{FxHashSet, Spanned};
18use crate::verify::call_summary::{self, CallEffect};
19use crate::verify::def_use::{PlaceBaseKey, PlaceKey};
20use crate::helpers::mir_utils::operand_place;
21use crate::helpers::api_classify;
22
23use super::state::{AllocId, Provenance, VmState, VmValue, ValueInvariants};
24
25/// Classification of a call site for dispatch prioritization.
26const MAX_INLINE_DEPTH: usize = 5;
27
28impl<'ctx, 'tcx> VmState<'ctx, 'tcx> {
29    /// Execute a call terminator.
30    ///
31    /// Dispatch priority: hand-specialized handlers first, then fn_simulator
32    /// summaries (whose hand-crafted invariants are more precise than inline),
33    /// then inline execution of the callee's MIR (including dependency
34    /// crates), then interprocedural/effect summaries, and finally an
35    /// unconstrained "unsupported call" result.
36    pub fn exec_call(
37        &mut self,
38        func: &Operand<'tcx>,
39        args: &[Spanned<Operand<'tcx>>],
40        destination: Local,
41        _target: Option<BasicBlock>,
42        _cleanup: Option<BasicBlock>,
43        caller_def_id: DefId,
44    ) {
45        let arg_values: Vec<VmValue<'ctx, 'tcx>> = args
46            .iter()
47            .map(|arg| self.value_of_operand(&arg.node))
48            .collect();
49
50        let name = crate::helpers::mir_utils::call_name(self.tcx, func);
51        let callee = crate::helpers::mir_utils::dep_callee_def_id(func);
52        let caller_arg_locals: Vec<Option<Local>> = args.iter()
53            .map(|a| a.node.place().map(|p| p.local))
54            .collect();
55
56        // ── select_unpredictable: result ∈ {x, y} ─────────────────────
57        if self.try_select_unpredictable(&name, &arg_values, args, destination) {
58            return;
59        }
60
61        // Slice range indexing: `<[T]>::index(range)` / `::index_mut(range)`
62        // returns a sub-slice whose length is the range's extent.
63        if self.try_slice_index(&name, &arg_values, args, destination) {
64            return;
65        }
66
67        // Iter::len() / Iter::is_empty(): compute from struct fields.
68        if self.try_iter_len_is_empty(&name, &arg_values, args, destination) {
69            return;
70        }
71
72        // Iter::next() / IterMut::next(): advance ptr by 1 and return old.
73        if self.try_iter_next(&name, &arg_values, args, destination) {
74            return;
75        }
76
77        // post_inc_start / pre_dec_end on Iter/IterMut: apply the ptr/end
78        // update as a side effect, then fall through to normal handling.
79        // These callees have SwitchInt (ZST branch) exceeding inline limits,
80        // so the ptr update would otherwise be lost.
81        if let Some(c) = callee {
82            if self.tcx.is_mir_available(c) {
83                let cname = self.tcx.def_path_str(c);
84                if (api_classify::is_iter_ptr_adj(&cname))
85                    && arg_values.len() >= 2
86                {
87                    self.apply_iter_ptr_update(c, &cname, &arg_values, &caller_arg_locals);
88                    // Continue to normal handling (return value is () , ignored).
89                }
90            }
91        }
92
93        // Try inline for callees with available MIR, unless fn_simulator
94        // has a precise summary (memory allocation, intrinsics, known ptr
95        // arithmetic, etc.). The summary path handles these with
96        // hand-crafted invariants that are more precise than BFS inline.
97        if let Some(c) = callee {
98            if self.tcx.is_mir_available(c) {
99                let has_fn_sim = crate::verify::call_summary::fn_simulator::lookup_effect(
100                    self.tcx, caller_def_id, Some(c), &name, func, destination,
101                ).is_some();
102                if !has_fn_sim {
103                    if self.exec_inline_call(c, &arg_values, &caller_arg_locals, destination) {
104                        self.materialize_const_bytes_after_call(args, destination);
105                        return;
106                    }
107                }
108            }
109        }
110
111        let summary = call_summary::effect_summary(
112            self.tcx,
113            caller_def_id,
114            func,
115            destination,
116        );
117
118        self.last_call_name = summary.name.clone();
119
120        if !summary.unsupported {
121            for effect in &summary.effects {
122                self.apply_call_effect(effect, &arg_values, &caller_arg_locals, destination);
123            }
124        } else {
125            self.notes.push(format!("unsupported call: {}", summary.name));
126            let dest_ty = self.body.local_decls[destination].ty;
127            let term = self.fresh_int(&format!("callret_{}", destination.as_usize()));
128            if let TyKind::Adt(adt_def, _) = dest_ty.kind() {
129                let path = self.tcx.def_path_str(adt_def.did());
130                if api_classify::is_std_ordering(&path) {
131                    let minus_one = Int::from_i64(self.ctx, -1);
132                    let one = Int::from_i64(self.ctx, 1);
133                    self.path_conditions.push(term.ge(&minus_one));
134                    self.path_conditions.push(term.le(&one));
135                }
136            }
137            // bool return (bool, Result::ok/err, etc.) — constrain to {0, 1}
138            if dest_ty.is_bool() {
139                let zero = Int::from_u64(self.ctx, 0);
140                let one = Int::from_u64(self.ctx, 1);
141                self.path_conditions.push(term.ge(&zero));
142                self.path_conditions.push(term.le(&one));
143            }
144            self.set_local(
145                destination,
146                VmValue {
147                    term,
148                    ty: dest_ty,
149                    provenance: None,
150                    invariants: ValueInvariants::default(),
151                },
152            );
153            return;
154        }
155
156        self.materialize_const_bytes_after_call(args, destination);
157    }
158
159    /// `select_unpredictable`: result ∈ {x, y}.
160    fn try_select_unpredictable(
161        &mut self,
162        name: &str,
163        arg_values: &[VmValue<'ctx, 'tcx>],
164        args: &[Spanned<Operand<'tcx>>],
165        destination: Local,
166    ) -> bool {
167        if !api_classify::is_select_unpredictable(name) || arg_values.len() < 3 {
168            return false;
169        }
170        let term = self.fresh_int(&format!("selunpred_{}", destination.as_usize()));
171        let dest_ty = self.body.local_decls[destination].ty;
172        let eq1 = term._eq(&arg_values[1].term);
173        let eq2 = term._eq(&arg_values[2].term);
174        self.path_conditions.push(Bool::or(self.ctx, &[&eq1, &eq2]));
175        let prov = arg_values[1].provenance.clone()
176            .or_else(|| arg_values[2].provenance.clone());
177        // Track operand chain for inject_div_axioms_for_term so that
178        // division axioms reachable through select_unpredictable
179        // can be found even across Use / Cast chains.
180        let dest_pk = PlaceKey { base: PlaceBaseKey::Local(destination.as_usize()), fields: vec![] };
181        let lhs_pk = args.get(1).and_then(|a| operand_place(&a.node));
182        let rhs_pk = args.get(2).and_then(|a| operand_place(&a.node));
183        self.other_op_sources.insert(dest_pk, (lhs_pk, rhs_pk));
184        self.set_local(destination, VmValue {
185            term,
186            ty: dest_ty,
187            provenance: prov,
188            invariants: ValueInvariants::default(),
189        });
190        true
191    }
192
193    /// Slice range indexing `<[T]>::index(range)` / `::index_mut(range)`:
194    /// returns a sub-slice whose length is the range's extent. Model it as a
195    /// sub-allocation of the array so downstream `into_iter`/`next()` see the
196    /// correct element count (empty for `..0`). Single-element indexing
197    /// (`index(usize)`) has a non-slice destination and keeps the plain
198    /// alias behaviour from the summary table.
199    fn try_slice_index(
200        &mut self,
201        name: &str,
202        arg_values: &[VmValue<'ctx, 'tcx>],
203        args: &[Spanned<Operand<'tcx>>],
204        destination: Local,
205    ) -> bool {
206        let is_index = name.ends_with("::Index::index") || name.ends_with("::IndexMut::index_mut");
207        if !is_index || arg_values.len() < 2 {
208            return false;
209        }
210        let dest_ty = self.body.local_decls[destination].ty;
211        let is_slice = matches!(dest_ty.kind(), TyKind::Ref(_, inner, _)
212            if matches!(inner.kind(), TyKind::Slice(_)));
213        if !is_slice {
214            return false;
215        }
216        let Some(prov) = arg_values[0].provenance.clone() else {
217            return false;
218        };
219        let array_term = arg_values[0].term.clone();
220        let (elem_ty, elem_size) = match arg_values[0].ty.kind() {
221            TyKind::Ref(_, inner, _) => match inner.kind() {
222                TyKind::Array(e, _) | TyKind::Slice(e) => {
223                    (*e, self.size_of_ty(*e).max(1) as u64)
224                }
225                _ => (arg_values[0].ty, 1),
226            },
227            _ => (arg_values[0].ty, 1),
228        };
229        let elem_align = self.align_of_ty(elem_ty).max(1);
230        // The range argument is an aggregate whose field layout determines the
231        // slice extent (start element offset and element count):
232        //   RangeTo { end }        -> start = 0, len = end
233        //   RangeFrom { start }    -> start,     len = total - start
234        //   Range { start, end }   -> start,     len = end - start
235        //   RangeInclusive { .. }  -> start,     len = end - start + 1
236        //   otherwise              -> start = 0, len = total
237        let range_local = args.get(1).and_then(|a| match &a.node {
238            Operand::Copy(p) | Operand::Move(p) => Some(p.local),
239            _ => None,
240        });
241        let range_field = |idx: usize| -> Option<Int<'ctx>> {
242            range_local.and_then(|l| self.field_value(l, &[idx]).map(|v| v.term.clone()))
243        };
244        let zero = Int::from_u64(self.ctx, 0);
245        let one = Int::from_u64(self.ctx, 1);
246        let total_len = self.alloc(prov.alloc_id).size.clone()
247            .div(&Int::from_u64(self.ctx, elem_size));
248        let range_ty_path = arg_values.get(1).and_then(|v| match v.ty.kind() {
249            TyKind::Adt(adt_def, _) => Some(self.tcx.def_path_str(adt_def.did())),
250            _ => None,
251        });
252        let (start, len) = match range_ty_path.as_deref() {
253            Some(p) if p.ends_with("::RangeTo") => {
254                (zero.clone(), range_field(0).unwrap_or_else(|| total_len.clone()))
255            }
256            Some(p) if p.ends_with("::RangeFrom") => {
257                let s = range_field(0).unwrap_or_else(|| zero.clone());
258                (s.clone(), Int::sub(self.ctx, &[&total_len, &s]))
259            }
260            Some(p) if p.ends_with("::Range") || p.ends_with("::RangeInclusive") => {
261                let inclusive = p.ends_with("::RangeInclusive");
262                let s = range_field(0).unwrap_or_else(|| zero.clone());
263                let e = range_field(1).unwrap_or_else(|| total_len.clone());
264                let l = Int::sub(self.ctx, &[&e, &s]);
265                (s.clone(), if inclusive { Int::add(self.ctx, &[&l, &one]) } else { l })
266            }
267            _ => (zero.clone(), total_len.clone()),
268        };
269        let elem_size_term = Int::from_u64(self.ctx, elem_size);
270        let start_bytes = Int::mul(self.ctx, &[&start, &elem_size_term]);
271        let size_bytes = Int::mul(self.ctx, &[&len, &elem_size_term]);
272        let dest_term = Int::add(self.ctx, &[&array_term, &start_bytes]);
273        let (alloc_id, _) = self.allocate(size_bytes, elem_align, Some(elem_ty));
274        self.alloc_mut(alloc_id).parent = Some(prov.alloc_id);
275        self.set_local(destination, VmValue {
276            term: dest_term,
277            ty: dest_ty,
278            provenance: Some(Provenance {
279                alloc_id,
280                offset: Int::from_u64(self.ctx, 0),
281                is_field_offset: false,
282            }),
283            invariants: ValueInvariants {
284                non_null: true, aligned: true, init: true, in_bounds: true,
285                ..Default::default()
286            },
287        });
288        true
289    }
290
291    /// `Iter::len()` / `Iter::is_empty()`: compute from struct fields
292    /// (ptr + end_or_len share the same allocation with per-field offsets).
293    /// The generic fn_simulator would return sizeof(Iter)/sizeof(T), which is
294    /// wrong for generic T.
295    fn try_iter_len_is_empty(
296        &mut self,
297        name: &str,
298        arg_values: &[VmValue<'ctx, 'tcx>],
299        args: &[Spanned<Operand<'tcx>>],
300        destination: Local,
301    ) -> bool {
302        if !((name.contains("::Iter<") || name.contains("::IterMut<")
303            || name.ends_with("::Iter::len") || name.ends_with("::IterMut::len")
304            || name.ends_with("::Iter::is_empty") || name.ends_with("::IterMut::is_empty"))
305            && (name.ends_with("::len") || name.ends_with("::is_empty"))
306            && arg_values.len() >= 1)
307        {
308            return false;
309        }
310        let receiver_local = args.first().and_then(|a| a.node.place()).map(|p| p.local);
311        let Some(local) = receiver_local else { return false; };
312        // len() = (end_or_len - ptr) / sizeof(T)   (non-ZST)
313        // is_empty() = ptr == end_or_len           (non-ZST)
314        let (Some(ptr), Some(end)) = (self.field_value(local, &[0]), self.field_value(local, &[1])) else {
315            return false;
316        };
317        let (Some(pp), Some(ep)) = (&ptr.provenance, &end.provenance) else {
318            return false;
319        };
320        if pp.alloc_id != ep.alloc_id {
321            return false;
322        }
323        let dest_ty = self.body.local_decls[destination].ty;
324        if name.ends_with("::len") {
325            let diff = Int::sub(self.ctx, &[&ep.offset, &pp.offset]);
326            let sz = Int::from_u64(self.ctx, self.iter_elem_size(ptr));
327            let val = VmValue::new(diff.div(&sz), dest_ty);
328            self.set_local(destination, val);
329        } else {
330            // is_empty(): ptr == end_or_len  (non-ZST branch)
331            let eq = pp.offset._eq(&ep.offset);
332            let zero = Int::from_u64(self.ctx, 0);
333            let one = Int::from_u64(self.ctx, 1);
334            let val = VmValue {
335                term: eq.ite(&one, &zero),
336                ty: dest_ty,
337                provenance: None,
338                invariants: ValueInvariants::default(),
339            };
340            self.set_local(destination, val);
341        }
342        true
343    }
344
345    /// `Iter::next()` / `IterMut::next()`: advance ptr by 1 and return old.
346    /// The MIR calls the `Iterator::next` trait method, so also match the
347    /// trait path (`std::iter::Iterator::next`) in addition to the concrete
348    /// `Iter`/`IterMut` method names.
349    fn try_iter_next(
350        &mut self,
351        name: &str,
352        arg_values: &[VmValue<'ctx, 'tcx>],
353        _args: &[Spanned<Operand<'tcx>>],
354        destination: Local,
355    ) -> bool {
356        let is_next = name.contains("::next")
357            && (name.starts_with("Iter::") || name.starts_with("IterMut::")
358                || name.contains("::Iter::") || name.contains("::IterMut::")
359                || name.contains("::Iter<") || name.contains("::IterMut<")
360                || name.contains("::Iterator::next"));
361        if !is_next || arg_values.len() < 1 {
362            return false;
363        }
364        let self_val = &arg_values[0];
365        let Some(local) = self.find_iter_self_local(self_val) else {
366            return false;
367        };
368        let (Some(ptr), Some(end)) = (self.field_value(local, &[0]), self.field_value(local, &[1])) else {
369            return false;
370        };
371        let (Some(pp), Some(ep)) = (&ptr.provenance, &end.provenance) else {
372            return false;
373        };
374        if pp.alloc_id != ep.alloc_id {
375            return false;
376        }
377        let dest_ty = self.body.local_decls[destination].ty;
378        // Compute is_empty from fields/tracked offset (same as is_empty()).
379        let sz = Int::from_u64(self.ctx, self.iter_elem_size(ptr));
380        let ep_offset = ep.offset.clone();
381        let remaining = if let Some(off) = self.iter_ptr_offset.get(&local) {
382            let base_len = ep_offset.div(&sz);
383            let zero = Int::from_u64(self.ctx, 0);
384            off.gt(&base_len).ite(&zero, &Int::sub(self.ctx, &[&base_len, off]))
385        } else {
386            let diff = Int::sub(self.ctx, &[&ep_offset, &pp.offset]);
387            diff.div(&sz)
388        };
389        let is_empty = remaining._eq(&Int::from_u64(self.ctx, 0));
390        // The returned element is the *current* position: the tracked element
391        // index (iter_ptr_offset) scaled by the element stride, or the base
392        // ptr offset on the first call.
393        let zero = Int::from_u64(self.ctx, 0);
394        let cur_off = match self.iter_ptr_offset.get(&local) {
395            Some(prev) => Int::mul(self.ctx, &[prev, &sz]),
396            None => pp.offset.clone(),
397        };
398        let old_ptr_val = VmValue {
399            term: cur_off.clone(),
400            ty: ptr.ty,
401            provenance: Some(Provenance {
402                alloc_id: pp.alloc_id,
403                offset: cur_off,
404                is_field_offset: false,
405            }),
406            invariants: ValueInvariants { non_null: true, init: true, ..Default::default() },
407        };
408        // Advance ptr when not empty
409        let one_term = Int::from_u64(self.ctx, 1);
410        let new_offset = match self.iter_ptr_offset.get(&local) {
411            Some(prev) => Int::add(self.ctx, &[prev, &one_term]),
412            None => one_term.clone(),
413        };
414        // Assert !is_empty as path condition (remaining > 0)
415        self.path_conditions.push(remaining.gt(&zero));
416        // Push: base_len >= tracked_offset
417        let base_len = ep_offset.div(&sz);
418        self.path_conditions.push(new_offset.le(&base_len));
419        self.iter_ptr_offset.insert(local, new_offset);
420        // Return None or old ptr
421        let result_val = VmValue {
422            term: is_empty.ite(&zero, &old_ptr_val.term),
423            ty: dest_ty,
424            provenance: if is_empty.as_bool().unwrap_or(false) { None } else { old_ptr_val.provenance.clone() },
425            invariants: ValueInvariants::default(),
426        };
427        self.set_local(destination, result_val);
428        // Tie the Option's discriminant to the emptiness condition so
429        // `switchInt(discriminant(_n))` only takes the `Some` branch when the
430        // iterator was non-empty (and the `None` branch when empty).
431        let discr_term = is_empty.ite(&zero, &one_term);
432        self.discriminant_terms.insert(destination, discr_term);
433        true
434    }
435
436    fn materialize_const_bytes_after_call(
437        &mut self,
438        args: &[Spanned<Operand<'tcx>>],
439        destination: Local,
440    ) {
441        if let Some(mut dv) = self.locals.get(&destination).cloned() {
442            let dest_ty = dv.ty;
443            let pointee_is_byte_like = match dest_ty.kind() {
444                rustc_middle::ty::TyKind::RawPtr(inner, _)
445                | rustc_middle::ty::TyKind::Ref(_, inner, _) => {
446                    match inner.kind() {
447                        rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8)
448                        | rustc_middle::ty::TyKind::Int(rustc_middle::ty::IntTy::I8) => true,
449                        rustc_middle::ty::TyKind::Array(elem_ty, _)
450                        | rustc_middle::ty::TyKind::Slice(elem_ty) => {
451                            matches!(elem_ty.kind(), rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8))
452                        }
453                        _ => false,
454                    }
455                }
456                _ => false,
457            };
458            if pointee_is_byte_like {
459                for arg in args {
460                    self.try_materialize_const_bytes(&mut dv, &arg.node);
461                    if dv.provenance.is_some() {
462                        self.set_local(destination, dv);
463                        break;
464                    }
465                }
466            }
467        }
468    }
469
470    /// Recursively execute a callee's MIR body inline.
471    ///
472    /// Binds the caller's argument values to the callee's parameters,
473    /// executes the callee's MIR, and writes the return value to
474    /// the caller's destination local. Returns `false` if inline
475    /// is not possible (e.g., recursion limit reached, callee has
476    /// branches, or the callee is too large).
477    fn exec_inline_call(
478        &mut self,
479        callee_def_id: DefId,
480        arg_values: &[VmValue<'ctx, 'tcx>],
481        caller_arg_locals: &[Option<Local>],
482        dest: Local,
483    ) -> bool {
484        if self.inline_depth >= MAX_INLINE_DEPTH {
485            return false;
486        }
487        self.inline_depth += 1;
488
489        // Only inline small, branch-free functions. `inline_execute_body`
490        // follows every `SwitchInt` target without forking state, so a real
491        // branch (e.g. a `match` that returns different pointers per arm)
492        // would have its arms merged and lose precision — which silently marks
493        // unsound callers sound. Keep rejecting `SwitchInt` bodies; branch-free
494        // bodies that merely exceed a small block count are still safe to
495        // inline, so the cap must cover the Box construction helpers used by
496        // constructors (`from_new_internal` is 9 blocks) so the fresh heap
497        // allocation's provenance reaches the returned `NonNull`.
498        let callee_body = self.tcx.optimized_mir(callee_def_id);
499        let n_return = callee_body
500            .basic_blocks
501            .iter()
502            .filter(|bb| matches!(bb.terminator().kind, rustc_middle::mir::TerminatorKind::Return))
503            .count();
504        // Reject a *semantic* branch (a `SwitchInt` reachable on the normal
505        // path): `inline_execute_body` merges its arms and loses precision.
506        // A `SwitchInt` that only appears in a cleanup block (the drop-flag
507        // dispatch) is dead on the normal path and is safe to ignore.
508        // Likewise, a `debug_assert!`/`assert!`-style `SwitchInt` whose every
509        // non-otherwise target leads to `panic`/`unreachable` is dead on the
510        // normal path — inlining it and taking only the `otherwise` edge keeps
511        // the field-level provenance of wrapper casts (`cast_to_internal_unchecked`).
512        let has_switch = callee_body.basic_blocks.iter_enumerated().any(|(idx, bb)| {
513            !bb.is_cleanup
514                && matches!(bb.terminator().kind, rustc_middle::mir::TerminatorKind::SwitchInt { .. })
515                && !Self::switch_is_debug_assert(self.tcx, &callee_body, idx)
516        });
517        if arg_values.len() > 4 || callee_body.basic_blocks.len() > 16 || n_return > 1 || has_switch {
518            self.inline_depth -= 1;
519            return false;
520        }
521
522        // ── Save caller context ──
523        let saved_body = self.body;
524        let saved_caller = self.caller_def_id;
525        let saved_locals = std::mem::take(&mut self.locals);
526        let saved_field_values = std::mem::take(&mut self.field_values);
527        let saved_local_addresses = std::mem::take(&mut self.local_addresses);
528        let saved_local_alloc_ids = std::mem::take(&mut self.local_alloc_ids);
529        let saved_binary_op_sources = std::mem::take(&mut self.binary_op_sources);
530        let saved_other_op_sources = std::mem::take(&mut self.other_op_sources);
531        let saved_iter_ptr_offset = std::mem::take(&mut self.iter_ptr_offset);
532        let saved_discriminant_terms = std::mem::take(&mut self.discriminant_terms);
533
534        // ── Switch to callee context ──
535        self.body = callee_body;
536        self.caller_def_id = callee_def_id;
537
538        // Bind args to callee locals (local_1..local_N are function params)
539        for (i, arg_val) in arg_values.iter().enumerate() {
540            let callee_local = Local::from_usize(i + 1);
541            self.ensure_local_allocation(callee_local);
542            self.set_local(callee_local, arg_val.clone());
543        }
544
545        // Propagate field_values from caller arg locals into the callee
546        // context so that inline body can access struct fields (e.g.
547        // Iter::ptr / end_or_len for len/is_empty computations).
548        for (i, caller_arg_opt) in caller_arg_locals.iter().enumerate() {
549            let callee_param = Local::from_usize(i + 1);
550            let Some(caller_arg) = caller_arg_opt else { continue; };
551            if *caller_arg == callee_param {
552                continue; // same local; field_values already present
553            }
554            let caller_field_keys: Vec<Vec<usize>> = saved_field_values.keys()
555                .filter(|(l, _)| *l == *caller_arg)
556                .map(|(_, f)| f.clone())
557                .collect();
558            for fields in caller_field_keys {
559                if let Some(fv) = saved_field_values.get(&(*caller_arg, fields.clone())).cloned() {
560                    self.set_field_value(callee_param, fields, fv);
561                }
562            }
563        }
564
565        // ── BFS execution of callee MIR ──
566        self.inline_execute_body();
567
568        // ── Capture return value and its per-field values ──
569        let return_val = self.locals.get(&Local::from_usize(0)).cloned();
570        let return_fields: Vec<(Vec<usize>, VmValue<'ctx, 'tcx>)> = self
571            .field_values
572            .iter()
573            .filter(|((l, _), _)| *l == Local::from_usize(0))
574            .map(|((_, path), val)| (path.clone(), val.clone()))
575            .collect();
576
577        // ── Restore caller context ──
578        self.body = saved_body;
579        self.caller_def_id = saved_caller;
580        self.locals = saved_locals;
581        self.field_values = saved_field_values;
582        self.local_addresses = saved_local_addresses;
583        self.local_alloc_ids = saved_local_alloc_ids;
584        self.binary_op_sources = saved_binary_op_sources;
585        self.other_op_sources = saved_other_op_sources;
586        self.iter_ptr_offset = saved_iter_ptr_offset;
587        self.discriminant_terms = saved_discriminant_terms;
588
589        // ── Write return value to caller destination ──
590        let dest_ty = self.body.local_decls[dest].ty;
591        match return_val {
592            Some(mut val) => {
593                val.ty = dest_ty;
594                // Infer invariants: a non-null provenance with offset=0
595                // means the return value is valid and initialized.
596                if let Some(ref prov) = val.provenance {
597                    if prov.offset.as_u64() == Some(0) {
598                        val.invariants.non_null = true;
599                        val.invariants.init = true;
600                        val.invariants.aligned = true;
601                        self.alloc_mut(prov.alloc_id).initialized = true;
602                    }
603                }
604                self.set_local(dest, val);
605                // Propagate the callee's per-field return values (e.g. a
606                // tuple `(NonNull<T>, A)`'s field 0) to the caller's
607                // destination so subsequent field projections resolve.
608                for (path, fv) in return_fields {
609                    self.set_field_value(dest, path, fv);
610                }
611                // The callee returned a fully-constructed value, so the
612                // caller's destination stack slot is initialized.  This matters
613                // for ADT returns (struct/enum) whose aggregate value carries
614                // no provenance: a later `&raw const (*&field)` + `ptr::read`
615                // must be able to discharge `Init` against the field.
616                if let Some(dest_alloc_id) = self.local_alloc_ids.get(&dest).copied() {
617                    self.alloc_mut(dest_alloc_id).initialized = true;
618                }
619            }
620            None => {
621                self.inline_depth -= 1;
622                return false;
623            }
624        }
625
626        self.inline_depth -= 1;
627        true
628    }
629
630    /// Whether a `SwitchInt`'s non-`otherwise` targets all lead straight to
631    /// `panic`/`unreachable` (a `debug_assert!`/`assert!` dispatch).  Such a
632    /// switch is dead on the normal path and can be inlined by following only
633    /// the `otherwise` edge.
634    fn switch_targets_unreachable(
635        tcx: rustc_middle::ty::TyCtxt<'tcx>,
636        body: &rustc_middle::mir::Body<'tcx>,
637        targets: &rustc_middle::mir::SwitchTargets,
638    ) -> bool {
639        targets.iter().all(|(_, target)| {
640            let mut cur = target;
641            let mut seen = FxHashSet::default();
642            loop {
643                if !seen.insert(cur) {
644                    return false;
645                }
646                let bb = &body.basic_blocks[cur];
647                let term = bb.terminator();
648                match &term.kind {
649                    rustc_middle::mir::TerminatorKind::Unreachable => return true,
650                    rustc_middle::mir::TerminatorKind::Call { func, .. } => {
651                        let name = crate::helpers::mir_utils::call_name(tcx, func);
652                        return name.contains("panic") || name.contains("unreachable") || name.contains("abort");
653                    }
654                    rustc_middle::mir::TerminatorKind::Goto { target: next } => {
655                        cur = *next;
656                    }
657                    // A bare `return` with no statements is a drop-flag skip
658                    // (dead on the normal path); a `return` preceded by real
659                    // statements computes a different value, so it is a semantic
660                    // branch and must not be ignored.
661                    rustc_middle::mir::TerminatorKind::Return => return bb.statements.is_empty(),
662                    _ => return false,
663                }
664            }
665        })
666    }
667
668    /// Whether a block's `SwitchInt` is a `debug_assert!`-style dispatch (all
669    /// non-`otherwise` targets are `panic`/`unreachable`).
670    fn switch_is_debug_assert(
671        tcx: rustc_middle::ty::TyCtxt<'tcx>,
672        body: &rustc_middle::mir::Body<'tcx>,
673        bb: BasicBlock,
674    ) -> bool {
675        let rustc_middle::mir::TerminatorKind::SwitchInt { discr, targets } =
676            &body.basic_blocks[bb].terminator().kind
677        else {
678            return false;
679        };
680        // A constant discriminant (e.g. `_3 = const true` for a no-drop flag)
681        // folds to a single live edge; the other edges are dead and can be
682        // ignored when inlining.  This includes a `move _3` whose `_3` is
683        // assigned a constant earlier in the body.
684        let discr_is_const = match discr {
685            rustc_middle::mir::Operand::Constant(_) => true,
686            rustc_middle::mir::Operand::Copy(p) | rustc_middle::mir::Operand::Move(p) => {
687                body.basic_blocks.iter().any(|bbd| {
688                    bbd.statements.iter().any(|stmt| {
689                        let rustc_middle::mir::StatementKind::Assign(assign) = &stmt.kind else {
690                            return false;
691                        };
692                        let (dest, rvalue) = &**assign;
693                        let is_const = match rvalue {
694                            #[cfg(rapx_rvalue_use_with_retag)]
695                            rustc_middle::mir::Rvalue::Use(rustc_middle::mir::Operand::Constant(_), _) => true,
696                            #[cfg(not(rapx_rvalue_use_with_retag))]
697                            rustc_middle::mir::Rvalue::Use(rustc_middle::mir::Operand::Constant(_)) => true,
698                            _ => false,
699                        };
700                        dest == p && is_const
701                    })
702                })
703            }
704            #[allow(unreachable_patterns)]
705            _ => false,
706        };
707        if discr_is_const {
708            return true;
709        }
710        Self::switch_targets_unreachable(tcx, body, targets)
711    }
712
713    /// BFS-execute the callee's MIR body.
714    fn inline_execute_body(&mut self) {
715        let mut visited = FxHashSet::default();
716        let mut queue: Vec<BasicBlock> = Vec::new();
717        queue.push(BasicBlock::from_usize(0));
718
719        while let Some(block) = queue.pop() {
720            if !visited.insert(block) {
721                continue;
722            }
723
724            let bb_data = &self.body.basic_blocks[block];
725
726            // Execute statements
727            for (si, stmt) in bb_data.statements.iter().enumerate() {
728                self.exec_statement(block, si, stmt);
729            }
730
731            // Process terminator
732            let terminator = bb_data.terminator();
733
734            match &terminator.kind {
735                TerminatorKind::Goto { target } => {
736                    queue.push(*target);
737                }
738                TerminatorKind::Return => {
739                    // Return value captured in local_0
740                }
741                TerminatorKind::Assert { cond, expected, target, .. } => {
742                    let cond_val = self.value_of_operand(cond);
743                    if *expected {
744                        let zero = Int::from_u64(self.ctx, 0);
745                        self.path_conditions.push(cond_val.term._eq(&zero).not());
746                    } else {
747                        let zero = Int::from_u64(self.ctx, 0);
748                        self.path_conditions.push(cond_val.term._eq(&zero));
749                    }
750                    // Guard inference for inline callee
751                    self.infer_guard_non_null(cond, *expected);
752                    self.infer_guard_align(cond, *expected);
753                    queue.push(*target);
754                }
755                TerminatorKind::SwitchInt { discr, targets } => {
756                    // A constant discriminant folds to a single live edge.
757                    if let rustc_middle::mir::Operand::Constant(c) = discr {
758                        let text = format!("{:?}", c.const_);
759                        if let Some(v) = crate::helpers::mir_utils::const_int_from_debug(&text) {
760                            let t = targets.iter().find(|(val, _)| *val == v as u128)
761                                .map(|(_, t)| t)
762                                .unwrap_or_else(|| targets.otherwise());
763                            queue.push(t);
764                            continue;
765                        }
766                    }
767                    // A `debug_assert!`/`assert!` switch or a drop-flag dispatch
768                    // has its non-otherwise edges dead on the normal path, so
769                    // follow only `otherwise`.
770                    let trivial = Self::switch_targets_unreachable(self.tcx, &self.body, targets);
771                    if trivial {
772                        queue.push(targets.otherwise());
773                        continue;
774                    }
775                    // Conservative: add path conditions for all branches,
776                    // but since we don't fork state, we follow all targets.
777                    // This loses precision for overwritten locals but is sound.
778                    for (value, target) in targets.iter() {
779                        let discr_val = self.value_of_operand(discr);
780                        let val_term = Int::from_u64(self.ctx, value as u64);
781                        self.path_conditions.push(discr_val.term._eq(&val_term));
782                        queue.push(target);
783                    }
784                    let otherwise = targets.otherwise();
785                    queue.push(otherwise);
786                }
787                TerminatorKind::Call {
788                    func,
789                    args,
790                    destination,
791                    target,
792                    ..
793                } => {
794                    self.exec_call(
795                        func,
796                        args,
797                        destination.local,
798                        *target,
799                        None,
800                        self.caller_def_id,
801                    );
802                    if let Some(t) = target {
803                        queue.push(*t);
804                    }
805                }
806                TerminatorKind::Drop { place, target, .. } => {
807                    self.exec_drop(place);
808                    queue.push(*target);
809                }
810                TerminatorKind::Unreachable
811                | TerminatorKind::UnwindResume
812                | TerminatorKind::UnwindTerminate(_)
813                | TerminatorKind::Yield { .. }
814                | TerminatorKind::CoroutineDrop
815                | TerminatorKind::FalseEdge { .. }
816                | TerminatorKind::FalseUnwind { .. }
817                | TerminatorKind::InlineAsm { .. }
818                | TerminatorKind::TailCall { .. } => {
819                    // Dead-end or unsupported — stop traversal at this block.
820                }
821            }
822        }
823    }
824
825    /// Apply a single call effect to the VM state.
826    fn apply_call_effect(
827        &mut self,
828        effect: &CallEffect,
829        args: &[VmValue<'ctx, 'tcx>],
830        caller_arg_locals: &[Option<Local>],
831        dest: Local,
832    ) {
833        match effect {
834            CallEffect::ReturnAliasArg { arg } => {
835                if let Some(arg_val) = args.get(*arg) {
836                    let mut val = arg_val.clone();
837                    val.ty = self.body.local_decls[dest].ty;
838                    val.invariants.non_null = true;
839                    val.invariants.aligned = true;
840                    val.invariants.init = true;
841                    self.set_local(dest, val);
842                }
843            }
844            CallEffect::ReturnTransparentDeref { arg, peel } => {
845                if let Some(arg_val) = args.get(*arg) {
846                    let mut val = arg_val.clone();
847                    val.ty = self.body.local_decls[dest].ty;
848                    val.invariants.non_null = true;
849                    val.invariants.aligned = true;
850                    val.invariants.init = true;
851                    self.set_local(dest, val);
852                    // Peel `peel` leading field-0 hops off the argument's
853                    // pointee field values (ManuallyDrop.value → MaybeDangling.0)
854                    // and expose them as the deref result's pointee fields.
855                    if let Some(arg_local) = caller_arg_locals.get(*arg).copied().flatten() {
856                        let keys: Vec<Vec<usize>> = self
857                            .field_values
858                            .keys()
859                            .filter(|(l, _)| *l == arg_local)
860                            .map(|(_, p)| p.clone())
861                            .collect();
862                        for path in keys {
863                            if path.len() > *peel
864                                && path[..*peel].iter().all(|&f| f == 0)
865                            {
866                                if let Some(v) =
867                                    self.field_values.get(&(arg_local, path.clone())).cloned()
868                                {
869                                    self.set_field_value(dest, path[*peel..].to_vec(), v);
870                                }
871                            }
872                        }
873                    }
874                }
875            }
876            CallEffect::ReturnTupleFieldLength { field: _field, from_arg: _from_arg } => {
877                if args.len() < 2 {
878                    return;
879                }
880                let self_val = &args[0]; // &[T]
881                let mid_val = &args[1];  // usize
882
883                let dest_ty = self.body.local_decls[dest].ty;
884                if let TyKind::Tuple(elem_tys) = dest_ty.kind() {
885                    // Look up the source allocation from self's provenance.
886                    let src_alloc_id = self_val.provenance.as_ref().map(|p| p.alloc_id);
887                    let _src_offset = self_val.provenance.as_ref()
888                        .map(|p| p.offset.clone())
889                        .unwrap_or_else(|| Int::from_u64(self.ctx, 0));
890
891                    let (elem_ty, elem_sz, alloc_size) = src_alloc_id
892                        .map(|id| self.alloc(id))
893                        .map(|a| {
894                            let ty = a.element_ty;
895                            let sz = self.size_of_ty(ty.unwrap_or(self_val.ty)).max(1) as u64;
896                            (ty, sz, a.size.clone())
897                        })
898                        .unwrap_or((None, 1, Int::from_u64(self.ctx, 1)));
899
900                    let elem_sz_term = Int::from_u64(self.ctx, elem_sz);
901                    let total_len = alloc_size.div(&elem_sz_term); // self.len()
902
903                    let zero = Int::from_u64(self.ctx, 0);
904                    self.path_conditions.push(mid_val.term.ge(&zero));
905                    self.path_conditions.push(mid_val.term.le(&total_len));
906
907                    // mid (field 0 length)
908                    let mid = mid_val.term.clone();
909                    // self.len() - mid (field 1 length)
910                    let rest_len = Int::sub(self.ctx, &[&total_len, &mid]);
911
912                    // mid byte offset for field 1 pointer
913                    let mid_bytes = Int::mul(self.ctx, &[&mid, &elem_sz_term]);
914                    let ptr1 = Int::add(self.ctx, &[&self_val.term, &mid_bytes]);
915
916                    for f in 0..elem_tys.len() {
917                        let field_ty = elem_tys[f];
918                        let (field_len, field_ptr) = if f == 0 {
919                            (mid.clone(), self_val.term.clone())
920                        } else {
921                            (rest_len.clone(), ptr1.clone())
922                        };
923                        let field_size = Int::mul(self.ctx, &[&field_len, &elem_sz_term]);
924                        let field_alloc_align = self_val.provenance.as_ref()
925                            .map(|p| self.alloc(p.alloc_id).align)
926                            .unwrap_or(1);
927
928                        let (alloc_id, _base) = self.allocate(
929                            field_size.clone(), field_alloc_align, elem_ty,
930                        );
931                        let src_bytes = Int::mul(self.ctx, &[&total_len, &elem_sz_term]);
932                        if f == 0 {
933                            self.path_conditions.push(field_size._eq(&mid_bytes));
934                        } else {
935                            let remaining = Int::sub(self.ctx, &[&src_bytes, &mid_bytes]);
936                            self.path_conditions.push(field_size._eq(&remaining));
937                        }
938                        self.alloc_mut(alloc_id).initialized = true;
939                        if let Some(ref source_prov) = self_val.provenance {
940                            self.alloc_mut(alloc_id).parent = Some(source_prov.alloc_id);
941                        }
942                        if let Some(ref_dest_alloc_id) = self.local_alloc_ids.get(&dest).copied() {
943                            self.alloc_mut(ref_dest_alloc_id).slice_data = Some(alloc_id);
944                        }
945
946                        let field_offset = Int::from_u64(self.ctx, 0);
947
948                        let field_prov = Provenance {
949                            alloc_id,
950                            offset: field_offset,
951                            is_field_offset: false,
952                        };
953
954                        let field_val = VmValue {
955                            term: field_ptr,
956                            ty: field_ty,
957                            provenance: Some(field_prov),
958                            invariants: ValueInvariants {
959                                init: true,
960                                non_null: true,
961                                aligned: true,
962                                in_bounds: true,
963                                align_n: Some(field_alloc_align),
964                                is_field_offset: false,
965                            },
966                        };
967                        self.set_field_value(dest, vec![f], field_val);
968                    }
969                }
970            }
971            CallEffect::ReturnIter { receiver_arg } => {
972                let Some(self_val) = args.get(*receiver_arg).cloned() else { return };
973                let Some(src_prov) = self_val.provenance.clone() else { return };
974                // `array[..i]` may be a `from_raw_parts` sub-allocation of the
975                // array's backing storage. Follow the sub-allocation chain to the
976                // root so the iterator's `ptr`/`end_or_len` fields point at live,
977                // init-tracked storage (the array itself), not the transient
978                // slice allocation.
979                let root_alloc_id = {
980                    let mut id = src_prov.alloc_id;
981                    while let Some(parent) = self.alloc(id).parent {
982                        id = parent;
983                    }
984                    id
985                };
986                let slice_len = self.alloc(src_prov.alloc_id).size.clone();
987
988                // The Iter/IterMut struct has `ptr` (field 0) and `end_or_len`
989                // (field 1), both raw pointers into the source slice allocation.
990                // Derive the pointee type so `next()` can compute the stride.
991                let field_ty = match self_val.ty.kind() {
992                    TyKind::Ref(_, inner, _) => match inner.kind() {
993                        TyKind::Slice(t) => *t,
994                        _ => self_val.ty,
995                    },
996                    _ => self_val.ty,
997                };
998
999                let start_off = Int::from_u64(self.ctx, 0);
1000                let end_term = Int::add(self.ctx, &[&self_val.term, &slice_len]);
1001
1002                let start_val = VmValue {
1003                    term: self_val.term.clone(),
1004                    ty: field_ty,
1005                    provenance: Some(Provenance {
1006                        alloc_id: root_alloc_id,
1007                        offset: start_off,
1008                        is_field_offset: false,
1009                    }),
1010                    invariants: ValueInvariants { init: true, non_null: true, ..Default::default() },
1011                };
1012                let end_val = VmValue {
1013                    term: end_term,
1014                    ty: field_ty,
1015                    provenance: Some(Provenance {
1016                        alloc_id: root_alloc_id,
1017                        offset: slice_len,
1018                        is_field_offset: false,
1019                    }),
1020                    invariants: ValueInvariants { init: true, non_null: true, ..Default::default() },
1021                };
1022                self.set_field_value(dest, vec![0], start_val);
1023                self.set_field_value(dest, vec![1], end_val);
1024            }
1025            CallEffect::ReturnAlignTo { receiver_arg } => {
1026                let Some(self_val) = args.get(*receiver_arg).cloned() else { return };
1027                let dest_ty = self.body.local_decls[dest].ty;
1028                let TyKind::Tuple(elem_tys) = dest_ty.kind() else { return };
1029                if elem_tys.len() < 3 { return; }
1030
1031                // Body element type U is the pointee of field 1 (`&[U]`).
1032                let body_elem_ty = match elem_tys[1].kind() {
1033                    TyKind::Ref(_, inner, _) => match inner.kind() {
1034                        TyKind::Slice(u) => *u,
1035                        _ => return,
1036                    },
1037                    _ => return,
1038                };
1039                let size_u = self.size_of_ty(body_elem_ty).max(1) as u64;
1040                let align_u = self.align_of_ty(body_elem_ty).max(1);
1041
1042                let Some(src_prov) = self_val.provenance.clone() else { return };
1043                let alloc = self.alloc(src_prov.alloc_id);
1044                let (elem_ty, elem_sz, len_bytes) = {
1045                    let ty = alloc.element_ty;
1046                    let sz = self.size_of_ty(ty.unwrap_or(self_val.ty)).max(1) as u64;
1047                    (ty, sz, alloc.size.clone())
1048                };
1049
1050                let elem_sz_term = Int::from_u64(self.ctx, elem_sz);
1051                let size_u_term = Int::from_u64(self.ctx, size_u);
1052                let align_u_term = Int::from_u64(self.ctx, align_u);
1053
1054                // Fresh aligned offset: (ptr + offset) % align_u == 0 and
1055                // 0 <= offset < align_u.
1056                let offset = self.fresh_int(&format!("align_to_offset_{}", dest.as_usize()));
1057                let zero = Int::from_u64(self.ctx, 0);
1058                let ptr_plus_offset = Int::add(self.ctx, &[&self_val.term, &offset]);
1059                self.path_conditions.push(ptr_plus_offset.rem(&align_u_term)._eq(&zero));
1060                self.path_conditions.push(offset.ge(&zero));
1061                self.path_conditions.push(offset.lt(&align_u_term));
1062
1063                // body = len_bytes - offset bytes split into size_u chunks; the
1064                // remainder is the suffix. Record the Euclidean identity so that
1065                // `len - offset - suffix = body_len * size_u` (a multiple of
1066                // align_u) is derivable downstream.
1067                let body_bytes = Int::sub(self.ctx, &[&len_bytes, &offset]);
1068                let body_len = body_bytes.div(&size_u_term);
1069                let suffix_bytes = body_bytes.rem(&size_u_term);
1070                let mul_term = Int::mul(self.ctx, &[&body_len, &size_u_term]);
1071                let sum_term = Int::add(self.ctx, &[&mul_term, &suffix_bytes]);
1072                self.path_conditions.push(body_bytes._eq(&sum_term));
1073                self.path_conditions.push(suffix_bytes.ge(&zero));
1074                self.path_conditions.push(suffix_bytes.lt(&size_u_term));
1075
1076                // Field lengths in elements.
1077                let prefix_len = offset.div(&elem_sz_term);
1078                let suffix_len = suffix_bytes.div(&elem_sz_term);
1079
1080                let body_byte_len = Int::mul(self.ctx, &[&body_len, &size_u_term]);
1081                let suffix_ptr = Int::add(self.ctx, &[&ptr_plus_offset, &body_byte_len]);
1082
1083                let base_align = self.alloc(src_prov.alloc_id).align;
1084
1085                let fields: Vec<(Int<'ctx>, Int<'ctx>, Ty<'tcx>, u64, u64)> = vec![
1086                    (prefix_len, self_val.term.clone(), elem_tys[0], elem_sz, base_align),
1087                    (body_len, ptr_plus_offset, elem_tys[1], size_u, align_u),
1088                    (suffix_len, suffix_ptr, elem_tys[2], elem_sz, base_align),
1089                ];
1090
1091                for (f, (f_len, f_ptr, f_ty, f_elem_sz, f_align)) in fields.into_iter().enumerate() {
1092                    let f_size = Int::mul(self.ctx, &[&f_len, &Int::from_u64(self.ctx, f_elem_sz)]);
1093                    let f_elem_ty = if f == 1 { Some(body_elem_ty) } else { elem_ty };
1094                    let (alloc_id, _) = self.allocate(f_size.clone(), f_align, f_elem_ty);
1095                    self.alloc_mut(alloc_id).initialized = true;
1096                    self.alloc_mut(alloc_id).parent = Some(src_prov.alloc_id);
1097                    if let Some(ref_dest_alloc_id) = self.local_alloc_ids.get(&dest).copied() {
1098                        self.alloc_mut(ref_dest_alloc_id).slice_data = Some(alloc_id);
1099                    }
1100                    let field_val = VmValue {
1101                        term: f_ptr,
1102                        ty: f_ty,
1103                        provenance: Some(Provenance {
1104                            alloc_id,
1105                            offset: Int::from_u64(self.ctx, 0),
1106                            is_field_offset: false,
1107                        }),
1108                        invariants: ValueInvariants {
1109                            init: true, non_null: true, aligned: true, in_bounds: true,
1110                            align_n: if f_align > 1 { Some(f_align) } else { None },
1111                            is_field_offset: false,
1112                        },
1113                    };
1114                    self.set_field_value(dest, vec![f], field_val);
1115                }
1116            }
1117            CallEffect::ReturnPointerFromArg { arg } => {
1118                if let Some(arg_val) = args.get(*arg) {
1119                    let mut val = arg_val.clone();
1120                    let dest_ty = self.body.local_decls[dest].ty;
1121                    val.ty = dest_ty;
1122                    val.invariants.non_null = true;
1123                    val.invariants.aligned = true;
1124                    // Pointer-returning APIs expose the backing allocation;
1125                    // mark it init-accessible for raw pointer types.
1126                    if matches!(dest_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..)) {
1127                        val.invariants.init = true;
1128                    }
1129                    // For locally-created Vec: redirect as_ptr() from the
1130                    // struct allocation to the heap data allocation.
1131                    let is_vec = api_classify::is_vec_or_cstring_call(&self.last_call_name);
1132                    if is_vec {
1133                        if let Some(ref prov) = val.provenance {
1134                            if let Some(data_alloc) = self.alloc(prov.alloc_id).slice_data {
1135                                if let Some(data_base) = self.allocation_base(data_alloc).cloned() {
1136                                    val.term = data_base;
1137                                    val.provenance = Some(Provenance {
1138                                        alloc_id: data_alloc,
1139                                        offset: Int::from_u64(self.ctx, 0),
1140                                        is_field_offset: false,
1141                                    });
1142                                }
1143                            }
1144                        }
1145                    }
1146                    self.set_local(dest, val);
1147                }
1148            }
1149            CallEffect::ReturnPointerAdd { base_arg, offset_arg, stride } => {
1150                if let (Some(base), Some(offset)) = (args.get(*base_arg), args.get(*offset_arg)) {
1151                    let stride_bytes = stride.unwrap_or(1);
1152                    let adjusted_offset = if stride_bytes == 1 {
1153                        Int::add(self.ctx, &[&offset.term])
1154                    } else {
1155                        let stride_term = Int::from_u64(self.ctx, stride_bytes);
1156                        Int::mul(self.ctx, &[&offset.term, &stride_term])
1157                    };
1158                    let new_term = Int::add(self.ctx, &[&base.term, &adjusted_offset]);
1159                    // A field offset (`offset_of!`) added to a container base
1160                    // keeps the pointer within the container allocation.
1161                    let is_field_offset = offset.invariants.is_field_offset
1162                        && base
1163                            .provenance
1164                            .as_ref()
1165                            .is_some_and(|p| p.offset.as_u64() == Some(0));
1166                    let adjusted_provenance = base.provenance.as_ref().map(|prov| {
1167                        Provenance {
1168                            alloc_id: prov.alloc_id,
1169                            offset: Int::add(self.ctx, &[&prov.offset, &adjusted_offset]),
1170                            is_field_offset,
1171                        }
1172                    });
1173                    // Preserve alignment if the added offset is compatible
1174                    let align_n = self.compute_pointer_add_align(base, offset, stride_bytes);
1175                    let val = VmValue {
1176                        term: new_term,
1177                        ty: self.body.local_decls[dest].ty,
1178                        provenance: adjusted_provenance,
1179                        invariants: ValueInvariants {
1180                            non_null: base.invariants.non_null,
1181                            aligned: align_n.is_some() && base.invariants.aligned,
1182                            in_bounds: base.invariants.in_bounds,
1183                            align_n,
1184                            init: base.invariants.init,
1185                            is_field_offset: false,
1186                        },
1187                    };
1188                    self.set_local(dest, val);
1189                }
1190            }
1191            CallEffect::ReturnPointerSub { base_arg, offset_arg, stride } => {
1192                if let (Some(base), Some(offset)) = (args.get(*base_arg), args.get(*offset_arg)) {
1193                    let stride_bytes = stride.unwrap_or(1);
1194                    let stride_term = Int::from_u64(self.ctx, stride_bytes);
1195                    let scaled = Int::mul(self.ctx, &[&offset.term, &stride_term]);
1196                    let new_term = Int::sub(self.ctx, &[&base.term, &scaled]);
1197                    let adjusted_provenance = base.provenance.as_ref().map(|prov| {
1198                        Provenance {
1199                            alloc_id: prov.alloc_id,
1200                            offset: Int::sub(self.ctx, &[&prov.offset, &scaled]),
1201                            is_field_offset: false,
1202                        }
1203                    });
1204                    let align_n = self.compute_pointer_add_align(base, offset, stride_bytes);
1205                    let val = VmValue {
1206                        term: new_term,
1207                        ty: self.body.local_decls[dest].ty,
1208                        provenance: adjusted_provenance,
1209                        invariants: ValueInvariants {
1210                            non_null: base.invariants.non_null,
1211                            aligned: align_n.is_some() && base.invariants.aligned,
1212                            in_bounds: base.invariants.in_bounds,
1213                            align_n,
1214                            init: base.invariants.init,
1215                            is_field_offset: false,
1216                        },
1217                    };
1218                    self.set_local(dest, val);
1219                }
1220            }
1221            CallEffect::CleanSliceDataLinks { arg } => {
1222                if let Some(arg_val) = args.get(*arg) {
1223                    if let Some(ref prov) = arg_val.provenance {
1224                        self.alloc_mut(prov.alloc_id).slice_data = None;
1225                    }
1226                }
1227            }
1228            CallEffect::ReturnNonZero => {
1229                let zero = Int::from_u64(self.ctx, 0);
1230                if let Some(mut existing) = self.locals.get(&dest).cloned() {
1231                    existing.invariants.non_null = true;
1232                    // Record the non-zero fact as a path condition so that a
1233                    // downstream `ValidNum(result != 0)` obligation (e.g.
1234                    // `NonZero::new_unchecked` after a bit-preserving operation)
1235                    // discharges against it.
1236                    self.path_conditions.push(existing.term._eq(&zero).not());
1237                    self.set_local(dest, existing);
1238                } else {
1239                    let dest_ty = self.body.local_decls[dest].ty;
1240                    let term = self.fresh_int(&format!("ret_nz_{}", dest.as_usize()));
1241                    self.path_conditions.push(term._eq(&zero).not());
1242                    self.set_local(dest, VmValue {
1243                        term, ty: dest_ty, provenance: None,
1244                        invariants: ValueInvariants { non_null: true, ..Default::default() },
1245                    });
1246                }
1247            }
1248            CallEffect::ReturnTupleFieldNonZero { field } => {
1249                let dest_ty = self.body.local_decls[dest].ty;
1250                if let TyKind::Tuple(elem_tys) = dest_ty.kind() {
1251                    if let Some(field_ty) = elem_tys.get(*field) {
1252                        let zero = Int::from_u64(self.ctx, 0);
1253                        let term = self
1254                            .fresh_int(&format!("ret_tup_nz_{}_{}", dest.as_usize(), field));
1255                        self.path_conditions.push(term._eq(&zero).not());
1256                        self.set_field_value(dest, vec![*field], VmValue {
1257                            term,
1258                            ty: *field_ty,
1259                            provenance: None,
1260                            invariants: ValueInvariants { non_null: true, init: true, ..Default::default() },
1261                        });
1262                    }
1263                }
1264            }
1265            CallEffect::ReturnAligned { align: _, ty_name: _ } => {
1266                if let Some(mut existing) = self.locals.get(&dest).cloned() {
1267                    existing.invariants.aligned = true;
1268                    existing.invariants.non_null = true;
1269                    self.set_local(dest, existing);
1270                } else {
1271                    let dest_ty = self.body.local_decls[dest].ty;
1272                    let term = self.fresh_int(&format!("ret_align_{}", dest.as_usize()));
1273                    self.set_local(dest, VmValue {
1274                        term, ty: dest_ty, provenance: None,
1275                        invariants: ValueInvariants { aligned: true, non_null: true, ..Default::default() },
1276                    });
1277                }
1278            }
1279            CallEffect::ReturnLengthOfArg { arg } => {
1280                if let Some(arg_val) = args.get(*arg) {
1281                    // For Iter / IterMut, compute len from struct fields
1282                    // (ptr + end_or_len with shared allocation) instead of
1283                    // the generic sizeof(Iter)/sizeof(T) heuristic.
1284                    if self.interpreter_iter_len(arg_val, dest) {
1285                        return;
1286                    }
1287                    let effective_alloc_id = arg_val.provenance_alloc_id()
1288                        .and_then(|pid| self.alloc(pid).slice_data)
1289                        .or_else(|| arg_val.provenance_alloc_id());
1290
1291                    if let Some(alloc_id) = effective_alloc_id {
1292                        let dest_ty = self.body.local_decls[dest].ty;
1293                        // If the allocation has an element type, divide the
1294                        // byte-aligned size by the element size to return the
1295                        // number of elements (e.g. slice length).
1296                        if let Some(elem_ty) = self.alloc(alloc_id).element_ty {
1297                            let elem_size = self.size_of_ty(elem_ty) as u64;
1298                            if elem_size > 1 {
1299                                if let Some(size) = self.allocation_size(alloc_id) {
1300                                    let div = Int::from_u64(self.ctx, elem_size);
1301                                    let val = VmValue::new(size.div(&div), dest_ty);
1302                                    self.set_local(dest, val);
1303                                    return;
1304                                }
1305                            } else if let Some(size) = self.allocation_size(alloc_id) {
1306                                let val = VmValue::new(size.clone(), dest_ty);
1307                                self.set_local(dest, val);
1308                                return;
1309                            }
1310                        } else if let Some(size) = self.allocation_size(alloc_id) {
1311                            let val = VmValue::new(size.clone(), dest_ty);
1312                            self.set_local(dest, val);
1313                            return;
1314                        }
1315                    }
1316                }
1317                let dest_ty = self.body.local_decls[dest].ty;
1318                let term = self.fresh_int(&format!("len_{}", dest.as_usize()));
1319                let val = VmValue {
1320                    term,
1321                    ty: dest_ty,
1322                    provenance: None,
1323                    invariants: ValueInvariants::default(),
1324                };
1325                self.set_local(dest, val);
1326            }
1327            CallEffect::ReturnIsEmptyOfArg { arg } => {
1328                if let Some(arg_val) = args.get(*arg) {
1329                    if self.interpreter_iter_is_empty(arg_val, dest) {
1330                        return;
1331                    }
1332                    let effective_alloc_id = arg_val.provenance_alloc_id()
1333                        .and_then(|pid| self.alloc(pid).slice_data)
1334                        .or_else(|| arg_val.provenance_alloc_id());
1335                    if let Some(alloc_id) = effective_alloc_id {
1336                        if let Some(len_term) = self.allocation_size(alloc_id).cloned() {
1337                            let zero = Int::from_u64(self.ctx, 0);
1338                            let one = Int::from_u64(self.ctx, 1);
1339                            let dest_ty = self.body.local_decls[dest].ty;
1340                            let cond = len_term._eq(&zero);
1341                            let val = VmValue {
1342                                term: cond.ite(&one, &zero),
1343                                ty: dest_ty,
1344                                provenance: None,
1345                                invariants: ValueInvariants::default(),
1346                            };
1347                            self.set_local(dest, val);
1348                            return;
1349                        }
1350                    }
1351                }
1352                let dest_ty = self.body.local_decls[dest].ty;
1353                let one = Int::from_u64(self.ctx, 1);
1354                let zero = Int::from_u64(self.ctx, 0);
1355                let fresh = self.fresh_int(&format!("empty_{}", dest.as_usize()));
1356                let val = VmValue {
1357                    term: fresh.le(&zero).ite(&one, &zero),
1358                    ty: dest_ty,
1359                    provenance: None,
1360                    invariants: ValueInvariants::default(),
1361                };
1362                self.set_local(dest, val);
1363            }
1364            CallEffect::ReturnOffsetFromUnsigned { self_arg, origin_arg } => {
1365                if let (Some(self_val), Some(origin_val)) = (args.get(*self_arg), args.get(*origin_arg)) {
1366                    let dest_ty = self.body.local_decls[dest].ty;
1367                    if let (Some(self_prov), Some(origin_prov)) = (&self_val.provenance, &origin_val.provenance) {
1368                        // Both pointers share provenance: the element-distance
1369                        // is (self_offset - origin_offset) / elem_size.
1370                        let elem_ty = match self_val.ty.kind() {
1371                            TyKind::Adt(_, substs) => substs.first().and_then(|s| s.as_type()),
1372                            _ => None,
1373                        };
1374                        let elem_size = elem_ty.map(|t| self.size_of_ty(t).max(1)).unwrap_or(1) as u64;
1375                        let diff = Int::sub(self.ctx, &[&self_prov.offset, &origin_prov.offset]);
1376                        let sz = Int::from_u64(self.ctx, elem_size);
1377                        let val = VmValue::new(diff.div(&sz), dest_ty);
1378                        self.set_local(dest, val);
1379                        return;
1380                    }
1381                    // Fallback: fresh symbolic length.
1382                    let term = self.fresh_int(&format!("offset_{}", dest.as_usize()));
1383                    let val = VmValue {
1384                        term,
1385                        ty: dest_ty,
1386                        provenance: None,
1387                        invariants: ValueInvariants::default(),
1388                    };
1389                    self.set_local(dest, val);
1390                    return;
1391                }
1392                let dest_ty = self.body.local_decls[dest].ty;
1393                let term = self.fresh_int(&format!("offset_{}", dest.as_usize()));
1394                let val = VmValue {
1395                    term,
1396                    ty: dest_ty,
1397                    provenance: None,
1398                    invariants: ValueInvariants::default(),
1399                };
1400                self.set_local(dest, val);
1401            }
1402            CallEffect::ReturnConst { value, label: _ } => {
1403                let dest_ty = self.body.local_decls[dest].ty;
1404                let term = Int::from_u64(self.ctx, *value);
1405                let val = VmValue {
1406                    term,
1407                    ty: dest_ty,
1408                    provenance: None,
1409                    invariants: ValueInvariants::default(),
1410                };
1411                self.set_local(dest, val);
1412            }
1413            CallEffect::ReturnAlignOffset { ptr_arg, align_arg } => {
1414                let dest_ty = self.body.local_decls[dest].ty;
1415                let offset = self.fresh_int(&format!("align_offset_{}", dest.as_usize()));
1416                if let (Some(ptr_val), Some(align_val)) = (args.get(*ptr_arg), args.get(*align_arg)) {
1417                    // `ptr.align_offset(align)` guarantees `(ptr + offset) % align == 0`
1418                    // with `0 <= offset < align` on the success path. Record both so a
1419                    // downstream `*(ptr.add(offset) as *const U)` can discharge `Align`.
1420                    let zero = Int::from_u64(self.ctx, 0);
1421                    let ptr_plus_off = Int::add(self.ctx, &[&ptr_val.term, &offset]);
1422                    self.path_conditions
1423                        .push(ptr_plus_off.rem(&align_val.term)._eq(&zero));
1424                    self.path_conditions.push(offset.ge(&zero));
1425                    self.path_conditions.push(offset.lt(&align_val.term));
1426                }
1427                let val = VmValue {
1428                    term: offset,
1429                    ty: dest_ty,
1430                    provenance: None,
1431                    invariants: ValueInvariants::default(),
1432                };
1433                self.set_local(dest, val);
1434            }
1435            CallEffect::ReturnMin { lhs_arg, rhs_arg } => {
1436                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1437                    let dest_ty = self.body.local_decls[dest].ty;
1438                    // Build the min as a first-class `ite(lhs <= rhs, lhs, rhs)`
1439                    // term rather than a fresh variable plus disjunction facts.
1440                    // A fresh variable breaks downstream alignment/bounds
1441                    // reasoning: e.g. `ptr.align_offset(8)` guarantees
1442                    // `(ptr + offset) % 8 == 0`, but `offset.min(len)` would
1443                    // then become an unrelated symbol and the `Align`/`InBound`
1444                    // checks on `*(ptr.add(offset) as *const usize)` could no
1445                    // longer discharge.  With an `ite`, the path conditions
1446                    // (`offset < 8`, `len >= 16`) let the solver reduce
1447                    // `ite(offset <= len, offset, len)` back to `offset`.
1448                    let term = lhs.term.le(&rhs.term).ite(&lhs.term, &rhs.term);
1449                    let val = VmValue {
1450                        term,
1451                        ty: dest_ty,
1452                        provenance: None,
1453                        invariants: ValueInvariants::default(),
1454                    };
1455                    self.set_local(dest, val);
1456                }
1457            }
1458            CallEffect::ReturnMax { lhs_arg, rhs_arg } => {
1459                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1460                    let dest_ty = self.body.local_decls[dest].ty;
1461                    let term = lhs.term.ge(&rhs.term).ite(&lhs.term, &rhs.term);
1462                    let val = VmValue {
1463                        term,
1464                        ty: dest_ty,
1465                        provenance: None,
1466                        invariants: ValueInvariants::default(),
1467                    };
1468                    self.set_local(dest, val);
1469                }
1470            }
1471            CallEffect::ReturnClamp { value_arg, min_arg, max_arg } => {
1472                if let (Some(v), Some(mn), Some(mx)) =
1473                    (args.get(*value_arg), args.get(*min_arg), args.get(*max_arg))
1474                {
1475                    let dest_ty = self.body.local_decls[dest].ty;
1476                    // clamp(v, mn, mx) = max(mn, min(v, mx))
1477                    let upper = v.term.gt(&mx.term).ite(&mx.term, &v.term);
1478                    let term = v.term.lt(&mn.term).ite(&mn.term, &upper);
1479                    let val = VmValue {
1480                        term,
1481                        ty: dest_ty,
1482                        provenance: None,
1483                        invariants: ValueInvariants::default(),
1484                    };
1485                    self.set_local(dest, val);
1486                }
1487            }
1488            CallEffect::ReturnAbs { arg } => {
1489                if let Some(a) = args.get(*arg) {
1490                    let dest_ty = self.body.local_decls[dest].ty;
1491                    let zero = Int::from_u64(self.ctx, 0);
1492                    let neg = Int::sub(self.ctx, &[&zero, &a.term]);
1493                    let term = a.term.ge(&zero).ite(&a.term, &neg);
1494                    let val = VmValue {
1495                        term,
1496                        ty: dest_ty,
1497                        provenance: None,
1498                        invariants: ValueInvariants::default(),
1499                    };
1500                    self.set_local(dest, val);
1501                }
1502            }
1503            CallEffect::ReturnNeg { arg } => {
1504                if let Some(a) = args.get(*arg) {
1505                    let dest_ty = self.body.local_decls[dest].ty;
1506                    let zero = Int::from_u64(self.ctx, 0);
1507                    let term = Int::sub(self.ctx, &[&zero, &a.term]);
1508                    let val = VmValue {
1509                        term,
1510                        ty: dest_ty,
1511                        provenance: None,
1512                        invariants: ValueInvariants::default(),
1513                    };
1514                    self.set_local(dest, val);
1515                }
1516            }
1517            CallEffect::ReturnAdd { lhs_arg, rhs_arg } => {
1518                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1519                    let dest_ty = self.body.local_decls[dest].ty;
1520                    let term = Int::add(self.ctx, &[&lhs.term, &rhs.term]);
1521                    let val = VmValue {
1522                        term,
1523                        ty: dest_ty,
1524                        provenance: None,
1525                        invariants: ValueInvariants::default(),
1526                    };
1527                    self.set_local(dest, val);
1528                }
1529            }
1530            CallEffect::ReturnSub { lhs_arg, rhs_arg } => {
1531                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1532                    let dest_ty = self.body.local_decls[dest].ty;
1533                    let term = Int::sub(self.ctx, &[&lhs.term, &rhs.term]);
1534                    let val = VmValue {
1535                        term,
1536                        ty: dest_ty,
1537                        provenance: None,
1538                        invariants: ValueInvariants::default(),
1539                    };
1540                    self.set_local(dest, val);
1541                }
1542            }
1543            CallEffect::ReturnMul { lhs_arg, rhs_arg } => {
1544                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1545                    let dest_ty = self.body.local_decls[dest].ty;
1546                    let term = Int::mul(self.ctx, &[&lhs.term, &rhs.term]);
1547                    let val = VmValue {
1548                        term,
1549                        ty: dest_ty,
1550                        provenance: None,
1551                        invariants: ValueInvariants::default(),
1552                    };
1553                    self.set_local(dest, val);
1554                }
1555            }
1556            CallEffect::ReturnOptionSomeAdd { lhs_arg, rhs_arg } => {
1557                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1558                    // `checked_add` returns `Option<T>`; its `Some` payload is
1559                    // `lhs + rhs`. Store the payload term under field 0 so the
1560                    // `if let Some(payload)` projection resolves to it. The
1561                    // discriminant is left unconstrained, so both `Some`/`None`
1562                    // branches remain reachable.
1563                    let term = Int::add(self.ctx, &[&lhs.term, &rhs.term]);
1564                    self.set_field_value(dest, vec![0], VmValue {
1565                        term,
1566                        ty: lhs.ty,
1567                        provenance: None,
1568                        invariants: ValueInvariants::default(),
1569                    });
1570                }
1571            }
1572            CallEffect::ReturnOptionSomeMul { lhs_arg, rhs_arg } => {
1573                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1574                    let term = Int::mul(self.ctx, &[&lhs.term, &rhs.term]);
1575                    self.set_field_value(dest, vec![0], VmValue {
1576                        term,
1577                        ty: lhs.ty,
1578                        provenance: None,
1579                        invariants: ValueInvariants::default(),
1580                    });
1581                }
1582            }
1583            CallEffect::ReturnOptionSomeScanIndex { self_arg } => {
1584                // `Iterator::position`/`find` return `Option<usize>` whose `Some`
1585                // payload is a scan index into the iterator, so `0 <= i < self.len()`.
1586                // The receiver is `&mut iter` (a reference to the Iter/IterMut
1587                // struct), so resolve the reference to the iterator local it
1588                // points at (via its provenance = the iterator's stack alloc).
1589                // The iterator carries `ptr` (field 0) and `end_or_len`
1590                // (field 1); `len = end_or_len - ptr`.
1591                if let Some(iter_ref) = caller_arg_locals.get(*self_arg).copied().flatten() {
1592                    let iter_local = self
1593                        .locals
1594                        .get(&iter_ref)
1595                        .and_then(|v| v.provenance_alloc_id())
1596                        .and_then(|alloc| {
1597                            self.local_alloc_ids
1598                                .iter()
1599                                .find(|(_, a)| **a == alloc)
1600                                .map(|(l, _)| *l)
1601                        });
1602                    let ptr_term = iter_local.and_then(|l| {
1603                        self.field_value(l, &[0]).map(|v| v.term.clone())
1604                    });
1605                    let end_term = iter_local.and_then(|l| {
1606                        self.field_value(l, &[1]).map(|v| v.term.clone())
1607                    });
1608                    if let (Some(ptr), Some(end)) = (ptr_term, end_term) {
1609                        let len = Int::sub(self.ctx, &[&end, &ptr]);
1610                        let payload = self.fresh_int(&format!("scan_idx_{}", dest.as_usize()));
1611                        self.path_conditions.push(payload.lt(&len));
1612                        let dest_ty = self.body.local_decls[dest].ty;
1613                        let payload_ty = match dest_ty.kind() {
1614                            TyKind::Adt(adt, substs) if adt.is_enum() => substs.type_at(0),
1615                            _ => dest_ty,
1616                        };
1617                        self.set_field_value(dest, vec![0], VmValue {
1618                            term: payload,
1619                            ty: payload_ty,
1620                            provenance: None,
1621                            invariants: ValueInvariants::default(),
1622                        });
1623                    }
1624                }
1625            }
1626            CallEffect::ReturnScanLength { ptr_arg: _ } => {
1627                // `strlen(ptr)` returns the byte length before the NUL
1628                // terminator. The `ValidCStr` invariant guarantees the NUL is
1629                // within `isize::MAX` bytes, so `len < isize::MAX`, and
1630                // `len + 1` (the length with the terminator) fits in
1631                // `isize::MAX` — discharging `from_raw_parts`'s
1632                // `ValidNum(size_of(T)*(len+1) <= isize::MAX)`.
1633                let len = self.fresh_int(&format!("strlen_{}", dest.as_usize()));
1634                let max = Int::from_i64(self.ctx, i64::MAX);
1635                self.path_conditions.push(len.lt(&max));
1636                let dest_ty = self.body.local_decls[dest].ty;
1637                self.set_local(dest, VmValue {
1638                    term: len,
1639                    ty: dest_ty,
1640                    provenance: None,
1641                    invariants: ValueInvariants::default(),
1642                });
1643            }
1644            CallEffect::ReturnNonZeroIff { arg } => {
1645                if let Some(a) = args.get(*arg) {
1646                    let dest_ty = self.body.local_decls[dest].ty;
1647                    let zero = Int::from_u64(self.ctx, 0);
1648                    let term = self.fresh_int(&format!("ret_nz_iff_{}", dest.as_usize()));
1649                    // `result == 0` iff `arg == 0`, i.e. non-zero is preserved
1650                    // exactly (bit-preserving ops map 0 -> 0, non-zero -> non-zero).
1651                    self.path_conditions
1652                        .push(term._eq(&zero)._eq(&a.term._eq(&zero)));
1653                    self.set_local(dest, VmValue {
1654                        term,
1655                        ty: dest_ty,
1656                        provenance: None,
1657                        invariants: ValueInvariants::default(),
1658                    });
1659                }
1660            }
1661            CallEffect::ReturnOptionSomeNonZeroIff { arg } => {
1662                if let Some(a) = args.get(*arg) {
1663                    let zero = Int::from_u64(self.ctx, 0);
1664                    let term = self.fresh_int(&format!("ret_opt_nz_iff_{}", dest.as_usize()));
1665                    self.path_conditions
1666                        .push(term._eq(&zero)._eq(&a.term._eq(&zero)));
1667                    self.set_field_value(dest, vec![0], VmValue {
1668                        term,
1669                        ty: a.ty,
1670                        provenance: None,
1671                        invariants: ValueInvariants::default(),
1672                    });
1673                }
1674            }
1675            CallEffect::WriteMemory { pointer_arg } => {
1676                if let Some(arg_val) = args.get(*pointer_arg) {
1677                    if let Some(prov) = &arg_val.provenance {
1678                        // For locally-created Vec-like types: create a heap data
1679                        // allocation on first mutation. (Param Vecs already have
1680                        // an external allocation set by init_parameters.)
1681                        let is_vec = crate::helpers::api_classify::is_vec_push(&self.last_call_name);
1682                        let is_external = self.alloc(prov.alloc_id).is_external;
1683                        if is_vec && !is_external {
1684                            let elem_ty = match arg_val.ty.kind() {
1685                                TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) => crate::helpers::mir_utils::vec_elem_ty(self.tcx, *inner),
1686                                _ => crate::helpers::mir_utils::vec_elem_ty(self.tcx, arg_val.ty),
1687                            };
1688                            let heap_align = elem_ty.map(|ty| self.align_of_ty(ty)).unwrap_or(1).max(1);
1689                            if let Some(old_data) = self.alloc(prov.alloc_id).slice_data {
1690                                // Subsequent mutation: invalidate old heap data.
1691                                self.alloc_mut(old_data).dead = true;
1692                                let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
1693                                let (data_alloc, _) = self.allocate_external(max_size, heap_align, elem_ty);
1694                                self.alloc_mut(prov.alloc_id).slice_data = Some(data_alloc);
1695                            } else {
1696                                // First mutation: create heap data allocation.
1697                                let max_size = Int::from_u64(self.ctx, i64::MAX as u64);
1698                                let (data_alloc, _) = self.allocate_external(max_size, heap_align, elem_ty);
1699                                self.alloc_mut(prov.alloc_id).slice_data = Some(data_alloc);
1700                            }
1701                        }
1702                        // When offset is concrete, only mark the bytes actually
1703                        // written. For symbolic offsets, mark entire allocation.
1704                        let off_u64 = prov.offset.as_u64()
1705                            .or_else(|| prov.offset.simplify().as_u64());
1706                        if let Some(off) = off_u64 {
1707                            if off == 0 {
1708                                self.alloc_mut(prov.alloc_id).initialized = true;
1709                            }
1710                            let elem_size = match arg_val.ty.kind() {
1711                                rustc_middle::ty::TyKind::Ref(_, inner, _) => self.size_of_ty(*inner) as usize,
1712                                _ => 0,
1713                            };
1714                            let write_size = if elem_size > 0 { elem_size } else {
1715                                self.allocation_size(prov.alloc_id).and_then(|s| s.as_u64()).unwrap_or(0) as usize
1716                            };
1717                            let end = (off as usize + write_size).min(4096);
1718                            for byte_off in (off as usize)..end {
1719                                self.mark_byte_init(prov.alloc_id, byte_off);
1720                            }
1721                        } else {
1722                            // Symbolic write offset: the exact written element
1723                            // can't be tracked per-byte. For concrete allocation
1724                            // sizes, mark every byte (as before). For unknown /
1725                            // zero sizes — generic element types such as
1726                            // `MaybeUninit<T>` inside `[MaybeUninit<T>; N]` —
1727                            // mark the whole allocation initialized so a later
1728                            // `assume_init_read`/`assume_init_drop` can discharge
1729                            // `Init` on those (fully initialized) elements.
1730                            let size_val = self.allocation_size(prov.alloc_id)
1731                                .and_then(|s| s.as_u64());
1732                            match size_val {
1733                                Some(sz) if sz > 0 => {
1734                                    for off in 0..(sz as usize).min(1024) {
1735                                        self.mark_byte_init(prov.alloc_id, off);
1736                                    }
1737                                }
1738                                _ => {
1739                                    self.alloc_mut(prov.alloc_id).initialized = true;
1740                                }
1741                            }
1742                        }
1743                    }
1744                }
1745            }
1746            CallEffect::ReadMemory { arg: _ } => {
1747                let dest_ty = self.body.local_decls[dest].ty;
1748                let term = self.fresh_int(&format!("read_{}", dest.as_usize()));
1749                let val = VmValue {
1750                    term,
1751                    ty: dest_ty,
1752                    provenance: None,
1753                    invariants: ValueInvariants::default(),
1754                };
1755                self.set_local(dest, val);
1756            }
1757            CallEffect::ReturnFreshAllocation { pointer_arg, size_arg, elem_size } => {
1758                if let (Some(ptr_val), Some(size_val)) = (args.get(*pointer_arg), args.get(*size_arg)) {
1759                    let elem_sz = Int::from_u64(self.ctx, *elem_size);
1760                    let total = Int::mul(self.ctx, &[&size_val.term, &elem_sz]);
1761                    let dest_ty = self.body.local_decls[dest].ty;
1762                    // For generic types (elem_size == 0), use external alloc
1763                    // so Allocated/InBound checks auto-pass.
1764                    let (alloc_id, base) = if *elem_size == 0 {
1765                        let max = Int::from_u64(self.ctx, i64::MAX as u64);
1766                        self.allocate_external(max, 1, None)
1767                    } else {
1768                        self.allocate(total, *elem_size, None)
1769                    };
1770                    let prov = Provenance {
1771                        alloc_id,
1772                        offset: Int::from_u64(self.ctx, 0),
1773                        is_field_offset: false,
1774                    };
1775                    // If return is a reference, register slice/pointee data
1776                    if let Some(ref dest_alloc_id) = self.local_alloc_ids.get(&dest).copied() {
1777                        self.alloc_mut(*dest_alloc_id).slice_data = Some(alloc_id);
1778                    }
1779                    // Propagate init status and byte-level tracking from the source pointer
1780                    // For fresh allocations, the init status is inherited from the source.
1781                    let is_external = self.alloc(alloc_id).is_external;
1782                    if is_external {
1783                        self.alloc_mut(alloc_id).initialized = true;
1784                    }
1785                    if let Some(ref source_prov) = ptr_val.provenance {
1786                        if !self.alloc(source_prov.alloc_id).dead {
1787                            self.alloc_mut(alloc_id).initialized = true;
1788                            self.alloc_mut(alloc_id).parent = Some(source_prov.alloc_id);
1789                        }
1790                        // Copy byte-level tracking (value, init, NUL knowledge).
1791                        self.copy_byte_tracking(source_prov.alloc_id, alloc_id);
1792                    }
1793                    let result_align_n = ptr_val.invariants.align_n.or_else(|| {
1794                        ptr_val.provenance.as_ref()
1795                            .map(|p| self.alloc(p.alloc_id).align)
1796                    });
1797                    self.set_local(dest, VmValue {
1798                        term: base,
1799                        ty: dest_ty,
1800                        provenance: Some(prov),
1801                        invariants: ValueInvariants {
1802                            non_null: true, init: true, in_bounds: true, aligned: true,
1803                            align_n: result_align_n,
1804                            ..ValueInvariants::default()
1805                        },
1806                    });
1807                }
1808            }
1809            CallEffect::ReturnNewAllocation { size_arg, elem_size } => {
1810                if let Some(size_val) = args.get(*size_arg) {
1811                    let elem_sz = Int::from_u64(self.ctx, *elem_size);
1812                    let total = Int::mul(self.ctx, &[&size_val.term, &elem_sz]);
1813                    let dest_ty = self.body.local_decls[dest].ty;
1814                    let elem_ty = crate::helpers::mir_utils::vec_elem_ty(self.tcx, dest_ty);
1815                    let heap_align = elem_ty.map(|ty| self.align_of_ty(ty)).unwrap_or(1).max(1);
1816                    let (alloc_id, base) = self.allocate_external(total, heap_align, elem_ty);
1817                    let dest_alloc_id = self.local_alloc_ids.get(&dest).copied();
1818                    if let Some(dest_alloc_id) = dest_alloc_id {
1819                        self.alloc_mut(dest_alloc_id).slice_data = Some(alloc_id);
1820                    }
1821                    self.alloc_mut(alloc_id).initialized = true;
1822                    self.set_local(dest, VmValue {
1823                        term: base,
1824                        ty: dest_ty,
1825                        provenance: dest_alloc_id.map(|stack_id| Provenance {
1826                            alloc_id: stack_id,
1827                            offset: Int::from_u64(self.ctx, 0),
1828                            is_field_offset: false,
1829                        }),
1830                        invariants: ValueInvariants {
1831                            non_null: true,
1832                            init: true,
1833                            in_bounds: true,
1834                            aligned: true,
1835                            ..ValueInvariants::default()
1836                        },
1837                    });
1838                }
1839            }
1840            CallEffect::ReturnNewAllocationFromBox { box_arg: _ } => {
1841                // Box→Vec conversion (into_vec, box_assume_init_into_vec_unsafe).
1842                self.ensure_local_allocation(dest);
1843                let dest_ty = self.body.local_decls[dest].ty;
1844                let elem_ty = crate::helpers::mir_utils::vec_elem_ty(self.tcx, dest_ty);
1845                let heap_align = elem_ty.map(|ty| self.align_of_ty(ty)).unwrap_or(1).max(1);
1846                let max = Int::from_u64(self.ctx, i64::MAX as u64);
1847                let (alloc_id, base) = self.allocate_external(max, heap_align, elem_ty);
1848                let dest_alloc_id = self.local_alloc_ids.get(&dest).copied();
1849                if let Some(ref dest_alloc_id) = dest_alloc_id {
1850                    self.alloc_mut(*dest_alloc_id).slice_data = Some(alloc_id);
1851                }
1852                self.alloc_mut(alloc_id).initialized = true;
1853                self.set_local(dest, VmValue {
1854                    term: base,
1855                    ty: dest_ty,
1856                    provenance: dest_alloc_id.map(|stack_id| Provenance {
1857                        alloc_id: stack_id,
1858                        offset: Int::from_u64(self.ctx, 0),
1859                        is_field_offset: false,
1860                    }),
1861                    invariants: ValueInvariants {
1862                        non_null: true,
1863                        init: true,
1864                        in_bounds: true,
1865                        aligned: true,
1866                        ..ValueInvariants::default()
1867                    },
1868                });
1869            }
1870            CallEffect::ReturnBoxFromVec { arg } => {
1871                if let Some(vec_val) = args.get(*arg) {
1872                    if let Some(ref prov) = vec_val.provenance {
1873                        if let Some(heap_alloc_id) = self.alloc(prov.alloc_id).slice_data {
1874                            if let Some(heap_base) = self.allocation_base(heap_alloc_id).cloned() {
1875                                let dest_ty = self.body.local_decls[dest].ty;
1876                                self.set_local(dest, VmValue {
1877                                    term: heap_base,
1878                                    ty: dest_ty,
1879                                    provenance: Some(Provenance {
1880                                        alloc_id: heap_alloc_id,
1881                                        offset: Int::from_u64(self.ctx, 0),
1882                                        is_field_offset: false,
1883                                    }),
1884                                    invariants: ValueInvariants {
1885                                        non_null: true,
1886                                        init: true,
1887                                        in_bounds: true,
1888                                        aligned: true,
1889                                        ..ValueInvariants::default()
1890                                    },
1891                                });
1892                            }
1893                        }
1894                    }
1895                }
1896            }
1897            CallEffect::OwnsInitMemory { arg } => {
1898                if let Some(arg_val) = args.get(*arg) {
1899                    if let Some(prov) = &arg_val.provenance {
1900                        self.alloc_mut(prov.alloc_id).initialized = true;
1901                    }
1902                    let mut val = arg_val.clone();
1903                    val.ty = self.body.local_decls[dest].ty;
1904                    val.invariants.init = true;
1905                    val.invariants.non_null = true;
1906                    self.set_local(dest, val);
1907                }
1908            }
1909            CallEffect::ReturnAllocBuffer => {
1910                // Model `Allocator::allocate(self, layout)`'s `Ok` variant as a
1911                // fresh *external* allocation: the exact byte count is
1912                // `layout.size()`, a symbolic value, so mark the allocation
1913                // unbounded (`is_external`) so `NonNull`/`Allocated` checks
1914                // auto-pass. The `Result` downcast `((_res as Ok).0)` copies
1915                // this provenance into the extracted `NonNull<[u8]>`.
1916                let dest_ty = self.body.local_decls[dest].ty;
1917                let max = Int::from_u64(self.ctx, i64::MAX as u64);
1918                let (alloc_id, base) = self.allocate_external(max, 1, None);
1919                self.alloc_mut(alloc_id).initialized = true;
1920                self.set_local(dest, VmValue {
1921                    term: base,
1922                    ty: dest_ty,
1923                    provenance: Some(Provenance {
1924                        alloc_id,
1925                        offset: Int::from_u64(self.ctx, 0),
1926                        is_field_offset: false,
1927                    }),
1928                    invariants: ValueInvariants {
1929                        non_null: true,
1930                        init: true,
1931                        in_bounds: true,
1932                        aligned: true,
1933                        ..ValueInvariants::default()
1934                    },
1935                });
1936            }
1937            CallEffect::ReturnPowerOfTwo => {
1938                // `Layout::align()` returns the layout's alignment, which is a
1939                // non-zero power of two. `Layout::align` inlines to
1940                // `self.align.as_usize()`, whose transmute-based body drops the
1941                // `NonZero` provenance; re-establish the non-zero fact (and the
1942                // power-of-two fact) with a fresh symbol so downstream
1943                // `from_size_align_unchecked` can discharge `align != 0` (its
1944                // `(align & (align - 1)) == 0` check is otherwise vacuously
1945                // proved, since contract-level `BitAnd` is unsupported).
1946                let dest_ty = self.body.local_decls[dest].ty;
1947                let term = self.fresh_int(&format!("layout_align_{}", dest.as_usize()));
1948                let zero = Int::from_u64(self.ctx, 0);
1949                self.path_conditions.push(term.gt(&zero));
1950                self.set_local(dest, VmValue {
1951                    term,
1952                    ty: dest_ty,
1953                    provenance: None,
1954                    invariants: ValueInvariants::default(),
1955                });
1956            }
1957            CallEffect::ChecksIndexBoundsDisjoint { indices_arg, len_arg } => {
1958                let indices = args.get(*indices_arg);
1959                let len_val = args.get(*len_arg);
1960                if let (Some(indices_val), Some(len_val)) = (indices, len_val) {
1961                    let arr_ty = match indices_val.ty.kind() {
1962                        rustc_middle::ty::TyKind::Ref(_, inner, _) => *inner,
1963                        _ => indices_val.ty,
1964                    };
1965                    if let rustc_middle::ty::TyKind::Array(_elem_ty, _const_len) = arr_ty.kind() {
1966                        let alloc_id = indices_val.provenance_alloc_id()
1967                            .or_else(|| {
1968                                // Slicer may have dropped the &indices
1969                                // assignment, losing provenance.  Fall back
1970                                 let fallback = self.locals.values().find_map(|v| {
1971                                    if v.ty == arr_ty { v.provenance_alloc_id() }
1972                                    else { None }
1973                                });
1974                                fallback
1975                            });
1976                        if let Some(alloc_id) = alloc_id {
1977                            self.contract_flags.has_checked_bounds = true;
1978                            let zero = Int::from_u64(self.ctx, 0);
1979                            let mut byte_offsets: Vec<(usize, Int)> = self
1980                                .alloc_byte_values(alloc_id)
1981                                .into_iter()
1982                                .map(|(off, term)| (off, term.clone()))
1983                                .collect();
1984                            byte_offsets.sort_by_key(|(off, _)| *off);
1985                            for (_, term) in &byte_offsets {
1986                                self.path_conditions.push(term.ge(&zero));
1987                                self.path_conditions.push(term.lt(&len_val.term));
1988                            }
1989                            for i in 0..byte_offsets.len() {
1990                                for j in (i + 1)..byte_offsets.len() {
1991                                    let ti = &byte_offsets[i].1;
1992                                    let tj = &byte_offsets[j].1;
1993                                    self.path_conditions.push(ti._eq(tj).not());
1994                                }
1995                            }
1996                        }
1997                    }
1998                }
1999                let dest_ty = self.body.local_decls[dest].ty;
2000                let term = self.fresh_int(&format!("ck_ok_{}", dest.as_usize()));
2001                self.set_local( dest, VmValue { term, ty: dest_ty, provenance: None, invariants: ValueInvariants::default() });
2002            }
2003        }
2004    }
2005
2006    /// Compute the preserved alignment when doing `base + offset * stride`.
2007    /// Pointer arithmetic only ever *preserves* the base's alignment; it never
2008    /// creates it. When the base's alignment is unknown, we cannot conclude
2009    /// anything about the result (a `wrapping_add` over misaligned storage does
2010    /// not become aligned just because the stride is a power of two).
2011    fn compute_pointer_add_align(
2012        &self,
2013        base: &VmValue<'ctx, 'tcx>,
2014        _offset: &VmValue<'ctx, 'tcx>,
2015        stride_bytes: u64,
2016    ) -> Option<u64> {
2017        let base_align = base.invariants.align_n;
2018        let Some(n) = base_align else { return None };
2019        if stride_bytes > 0 && stride_bytes % n == 0 {
2020            return Some(n);
2021        }
2022        None
2023    }
2024
2025    pub(crate) fn propagate_const_bytes_to_tracked(
2026        &mut self,
2027        args: &[Spanned<Operand<'tcx>>],
2028    ) {
2029        let mut const_bytes: Option<(Vec<u8>, usize)> = None;
2030        let mut tracked_alloc: Option<AllocId> = None;
2031        let mut tracked_offset: usize = 0;
2032
2033        for (i, arg) in args.iter().enumerate() {
2034            let arg_val = self.value_of_operand(&arg.node);
2035            if const_bytes.is_none() {
2036                let bytes_opt = crate::helpers::mir_utils::extract_const_bytes_from_operand(
2037                    self.tcx,
2038                    &arg.node,
2039                ).or_else(|| self.trace_to_const_bytes(&arg.node));
2040                if let Some(bytes) = bytes_opt {
2041                    const_bytes = Some((bytes, i));
2042                }
2043            }
2044            if tracked_alloc.is_none() {
2045                if let Some(alloc_id) = arg_val.provenance_alloc_id() {
2046                    tracked_alloc = Some(alloc_id);
2047                    if let Some(ref prov) = arg_val.provenance {
2048                        tracked_offset = prov.offset.as_u64().map(|v| v as usize).unwrap_or(0);
2049                    }
2050                }
2051            }
2052        }
2053
2054        if let (Some((bytes, _)), Some(alloc_id)) = (const_bytes, tracked_alloc) {
2055            for (j, &b) in bytes.iter().enumerate() {
2056                let off = tracked_offset + j;
2057                self.record_byte_value(
2058                    alloc_id,
2059                    off,
2060                    Int::from_u64(self.ctx, b as u64),
2061                );
2062                if b == 0 {
2063                    self.mark_byte_nul(alloc_id, off);
2064                } else {
2065                    self.mark_byte_non_nul(alloc_id, off);
2066                }
2067            }
2068            self.alloc_mut(alloc_id).initialized = true;
2069        }
2070    }
2071
2072    /// Element size (bytes) of the type iterated by an Iter/IterMut pointer.
2073    pub(crate) fn iter_elem_size(&self, ptr: &VmValue<'ctx, 'tcx>) -> u64 {
2074        let elem_ty = match ptr.ty.kind() {
2075            TyKind::Adt(_, substs) => substs.first().and_then(|s| s.as_type()),
2076            _ => None,
2077        };
2078        elem_ty.map(|t| self.size_of_ty(t).max(1)).unwrap_or(1) as u64
2079    }
2080
2081    /// Remaining element count of the Iter/IterMut backed by `local`
2082    /// (fields `[0]` = ptr, `[1]` = end_or_len).  When a tracked pointer
2083    /// offset exists (`iter_ptr_offset`), prefers the compact
2084    /// `base_len - offset` form; otherwise falls back to
2085    /// `(end.offset - ptr.offset) / elem_size`.
2086    fn iter_remaining_len(&self, local: Local) -> Option<Int<'ctx>> {
2087        let ptr = self.field_value(local, &[0])?;
2088        let end = self.field_value(local, &[1])?;
2089        let pp = ptr.provenance.as_ref()?;
2090        let ep = end.provenance.as_ref()?;
2091        if pp.alloc_id != ep.alloc_id {
2092            return None;
2093        }
2094        let sz = Int::from_u64(self.ctx, self.iter_elem_size(&ptr));
2095        if let Some(offset) = self.iter_ptr_offset.get(&local) {
2096            let base_len = ep.offset.div(&sz);
2097            let zero = Int::from_u64(self.ctx, 0);
2098            Some(offset.gt(&base_len).ite(&zero, &Int::sub(self.ctx, &[&base_len, offset])))
2099        } else {
2100            Some(Int::sub(self.ctx, &[&ep.offset, &pp.offset]).div(&sz))
2101        }
2102    }
2103
2104    /// For Iter/IterMut types, compute len from struct fields directly
2105    /// instead of the generic allocation-size heuristic. Returns true
2106    /// if handled (value set to dest).
2107    fn interpreter_iter_len(&mut self, arg_val: &VmValue<'ctx, 'tcx>, dest: Local) -> bool {
2108        let Some(l) = self.find_iter_self_local(arg_val) else {
2109            return false;
2110        };
2111        let Some(len_term) = self.iter_remaining_len(l) else {
2112            return false;
2113        };
2114        let dest_ty = self.body.local_decls[dest].ty;
2115        self.set_local(dest, VmValue::new(len_term, dest_ty));
2116        true
2117    }
2118
2119    /// For Iter/IterMut types, compute is_empty from struct fields. Returns
2120    /// true if handled (value set to dest).
2121    fn interpreter_iter_is_empty(&mut self, arg_val: &VmValue<'ctx, 'tcx>, dest: Local) -> bool {
2122        let Some(l) = self.find_iter_self_local(arg_val) else {
2123            return false;
2124        };
2125        let Some(remaining) = self.iter_remaining_len(l) else {
2126            return false;
2127        };
2128        let dest_ty = self.body.local_decls[dest].ty;
2129        let zero = Int::from_u64(self.ctx, 0);
2130        let one = Int::from_u64(self.ctx, 1);
2131        let val = VmValue {
2132            term: remaining._eq(&zero).ite(&one, &zero),
2133            ty: dest_ty,
2134            provenance: None,
2135            invariants: ValueInvariants::default(),
2136        };
2137        self.is_empty_len.insert(dest, remaining);
2138        self.set_local(dest, val);
2139        true
2140    }
2141
2142    /// Apply the side effect of post_inc_start / pre_dec_end on Iter/IterMut.
2143    /// Only updates the tracked offset (not field values), so that the
2144    /// precondition check (which runs before the call executes) sees the
2145    /// pre-update state, while subsequent len()/is_empty() calls use
2146    /// `base_len - offset` via interpreter_iter_len.
2147    fn apply_iter_ptr_update(
2148        &mut self,
2149        _callee: DefId,
2150        cname: &str,
2151        arg_values: &[VmValue<'ctx, 'tcx>],
2152        _caller_arg_locals: &[Option<Local>],
2153    ) {
2154        let is_inc = api_classify::is_post_inc_start(&cname);
2155        if !is_inc { return; }  // pre_dec_end not yet supported
2156        let self_val = &arg_values[0];
2157        let some_local = self.find_iter_self_local(self_val);
2158        let Some(local) = some_local else { return };
2159        let offset_term = arg_values.get(1).map(|v| v.term.clone())
2160            .unwrap_or_else(|| Int::from_u64(self.ctx, 1));
2161        let new_offset = match self.iter_ptr_offset.get(&local) {
2162            Some(prev) => Int::add(self.ctx, &[prev, &offset_term]),
2163            None => offset_term,
2164        };
2165        self.iter_ptr_offset.insert(local, new_offset);
2166    }
2167
2168    /// If arg_val is a reference to an Iter or IterMut struct, return the
2169    /// local index of the referent (so field values can be looked up).
2170    /// Since len()/is_empty() always take &self, local 1 is the receiver.
2171    fn find_iter_self_local(&self, arg_val: &VmValue<'ctx, 'tcx>) -> Option<Local> {
2172        match arg_val.ty.kind() {
2173            TyKind::Ref(_, pointee, _) => match pointee.kind() {
2174                TyKind::Adt(adt_def, _) => {
2175                    let name = self.tcx.def_path_str(adt_def.did());
2176                    if api_classify::is_std_iter_or_itermut(&name) {
2177                        // Find the local holding the iterator by matching the
2178                        // reference's address term against known local addresses
2179                        // (`&mut _iter` has term `addr__iter`).  A hardcoded
2180                        // `Local(1)` only holds for inlined `next` bodies where
2181                        // the iterator is the first argument; direct trait
2182                        // `Iterator::next` calls keep the iterator at an
2183                        // arbitrary local.
2184                        for (local, addr) in &self.local_addresses {
2185                            if addr == &arg_val.term {
2186                                return Some(*local);
2187                            }
2188                        }
2189                        // Fallback for inlined `next` bodies (iter bound to arg 1).
2190                        return Some(Local::from_usize(1));
2191                    }
2192                    None
2193                }
2194                _ => None,
2195            },
2196            _ => None,
2197        }
2198    }
2199}