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//! builtin_models 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::{FxHashMap, FxHashSet, Spanned};
18use crate::limit::MAX_INLINE_DEPTH;
19use crate::verify::api_classify;
20use crate::verify::call_summary::{self, CallEffect};
21use super::state::{AllocId, ElementTy, OffsetKind, Provenance, ValueFacts, ValueSource, VmState, VmValue};
22
23impl<'z3, 'tcx> VmState<'z3, 'tcx> {
24    /// Execute a call terminator.
25    ///
26    /// Dispatch priority: hand-specialized handlers first, then builtin_models
27    /// summaries (whose hand-crafted invariants are more precise than inline),
28    /// then inline execution of the callee's MIR (including dependency
29    /// crates), then interprocedural/effect summaries, and finally an
30    /// unconstrained "unsupported call" result.
31    pub(crate) fn exec_call(
32        &mut self,
33        func: &Operand<'tcx>,
34        args: &[Spanned<Operand<'tcx>>],
35        destination: Local,
36        caller_def_id: DefId,
37    ) {
38        let arg_values: Vec<VmValue<'z3, 'tcx>> = args
39            .iter()
40            .map(|arg| self.value_of_operand(&arg.node))
41            .collect();
42
43        let name = crate::helpers::mir_utils::call_name(self.tcx, func);
44        let callee =
45            crate::helpers::mir_utils::dep_callee_resolved_def_id(self.tcx, caller_def_id, func);
46        let caller_arg_locals: Vec<Option<Local>> = args
47            .iter()
48            .map(|a| a.node.place().map(|p| p.local))
49            .collect();
50
51        // `<[u8]>::eq` comparison: the result is just a bool, but its *literal*
52        // operand's bytes are the tracked operand's content on the true path.
53        // Write them into the tracked allocation so a later `ValidCStr` can see
54        // the NUL terminator.
55        if crate::helpers::mir_utils::is_eq_call(self.tcx, func) {
56            self.propagate_const_bytes_to_tracked(args);
57        }
58
59        // Slice range indexing: `<[T]>::index(range)` / `::index_mut(range)`
60        // returns a sub-slice whose length is the range's extent.
61        if self.try_slice_index(callee, &arg_values, args, destination) {
62            return;
63        }
64
65        // Slice range `get`: `<[T]>::get(range)` returns `Option<&[T]>` whose
66        // `Some` payload is the sub-slice.
67        if self.try_slice_get(callee, &arg_values, args, destination) {
68            return;
69        }
70
71        // Iter::len() / Iter::is_empty(): compute from struct fields.
72        if self.try_iter_len_is_empty(&name, &arg_values, args, destination) {
73            return;
74        }
75
76        // Iter::next() / IterMut::next(): advance ptr by 1 and return old.
77        if self.try_iter_next(&name, &arg_values, destination) {
78            return;
79        }
80
81        // NonNull::new(ptr): the safe constructor returns Some(ptr) iff ptr is
82        // non-null. Its body branches on `ptr.is_null()`, so the branch-free
83        // inline path rejects it; model the null-check directly.
84        if self.try_nonnull_new(callee, &arg_values, destination) {
85            return;
86        }
87
88        // post_inc_start / pre_dec_end on Iter/IterMut: apply the ptr/end
89        // update as a side effect, then fall through to normal handling.
90        // These callees have SwitchInt (ZST branch) exceeding inline limits,
91        // so the ptr update would otherwise be lost.
92        if let Some(c) = callee {
93            if self.tcx.is_mir_available(c) {
94                if crate::helpers::mir_utils::is_iter_ptr_adj(self.tcx, c) && arg_values.len() >= 2
95                {
96                    self.apply_iter_ptr_update(c, &arg_values);
97                    // Continue to normal handling (return value is () , ignored).
98                }
99            }
100        }
101
102        // Try inline for callees with available MIR, unless builtin_models
103        // has a precise summary (memory allocation, intrinsics, known ptr
104        // arithmetic, etc.). The summary path handles these with
105        // hand-crafted invariants that are more precise than BFS inline.
106        if let Some(c) = callee {
107            if self.tcx.is_mir_available(c) {
108                // MIR-derived field load (`(*self).field` getter shape, e.g.
109                // `Vec::len`): recognized from the callee's MIR, not by name.
110                if let Some(effect) =
111                    crate::verify::call_summary::interprocedural::try_field_load_effect(self.tcx, c)
112                {
113                    self.apply_call_effect(&effect, &arg_values, &caller_arg_locals, destination, callee);
114                    self.materialize_const_bytes_after_call(args, destination);
115                    return;
116                }
117                if let Some(effect) =
118                    crate::verify::call_summary::interprocedural::try_ptr_field_return_effect(
119                        self.tcx, c,
120                    )
121                {
122                    self.apply_call_effect(&effect, &arg_values, &caller_arg_locals, destination, callee);
123                    self.materialize_const_bytes_after_call(args, destination);
124                    return;
125                }
126                if let Some(effect) =
127                    crate::verify::call_summary::interprocedural::try_branch_effect(self.tcx, c)
128                {
129                    self.apply_call_effect(&effect, &arg_values, &caller_arg_locals, destination, callee);
130                    self.materialize_const_bytes_after_call(args, destination);
131                    return;
132                }
133                if let Some(effect) =
134                    crate::verify::call_summary::interprocedural::try_slice_bounded_return_effect(
135                        self.tcx, c,
136                    )
137                {
138                    self.apply_call_effect(&effect, &arg_values, &caller_arg_locals, destination, callee);
139                    self.materialize_const_bytes_after_call(args, destination);
140                    return;
141                }
142                if let Some(effect) =
143                    crate::verify::call_summary::interprocedural::try_decode_length_return_effect(
144                        self.tcx, c,
145                    )
146                {
147                    self.apply_call_effect(&effect, &arg_values, &caller_arg_locals, destination, callee);
148                    self.materialize_const_bytes_after_call(args, destination);
149                    return;
150                }
151                let has_fn_sim = crate::verify::call_summary::builtin_models::lookup_effect(
152                    self.tcx,
153                    caller_def_id,
154                    callee,
155                    func,
156                    destination,
157                )
158                .is_some();
159                if !has_fn_sim {
160                    if self.exec_inline_call(c, &arg_values, &caller_arg_locals, destination) {
161                        self.materialize_const_bytes_after_call(args, destination);
162                        return;
163                    }
164                }
165            }
166        }
167
168        let mut concrete = FxHashMap::default();
169        for (i, arg) in arg_values.iter().enumerate() {
170            if let Some(v) = arg.z3_term.simplify().as_u64() {
171                concrete.insert(i, v as i128);
172            }
173        }
174        let context = call_summary::CallContext { concrete };
175
176        let summary = call_summary::effect_summary(
177            self.tcx,
178            caller_def_id,
179            func,
180            destination,
181            &context,
182        );
183
184        // A `size_of::<T>()` / `align_of::<T>()` on a *generic* `T` has no
185        // concrete layout, so `eff_layout_const` produces no effect and the
186        // result would otherwise be an unrelated fresh value.  Bind it to the
187        // shared symbolic `sizeof_T` / `align_T` so it agrees with allocation
188        // sizes and pointer strides.
189        if self.try_size_align_effect(func, destination) {
190            self.materialize_const_bytes_after_call(args, destination);
191            return;
192        }
193
194        if !summary.unsupported {
195            for effect in &summary.effects {
196                self.apply_call_effect(effect, &arg_values, &caller_arg_locals, destination, callee);
197            }
198        } else {
199            let dest_ty = self.body().local_decls[destination].ty;
200            let term = self.fresh_int(&format!("callret_{}", destination.as_usize()));
201            if let TyKind::Adt(adt_def, _) = dest_ty.kind() {
202                if api_classify::is_std_ordering(adt_def.did()) {
203                    let minus_one = Int::from_i64(self.z3_ctx, -1);
204                    let one = Int::from_i64(self.z3_ctx, 1);
205                    self.constraints.assertions.push(term.ge(&minus_one));
206                    self.constraints.assertions.push(term.le(&one));
207                }
208            }
209            // bool return (bool, Result::ok/err, etc.) — constrain to {0, 1}
210            if dest_ty.is_bool() {
211                let zero = Int::from_u64(self.z3_ctx, 0);
212                let one = Int::from_u64(self.z3_ctx, 1);
213                self.constraints.assertions.push(term.ge(&zero));
214                self.constraints.assertions.push(term.le(&one));
215            }
216            self.set_local(
217                destination,
218                VmValue::new(term, dest_ty),
219            );
220        }
221
222        self.materialize_const_bytes_after_call(args, destination);
223    }
224
225    /// Slice range indexing `<[T]>::index(range)` / `::index_mut(range)`:
226    /// returns a sub-slice whose length is the range's extent. Model it as a
227    /// sub-allocation of the array so downstream `into_iter`/`next()` see the
228    /// correct element count (empty for `..0`). Single-element indexing
229    /// (`index(usize)`) has a non-slice destination and keeps the plain
230    /// alias behaviour from the summary table.
231    fn try_slice_index(
232        &mut self,
233        callee: Option<DefId>,
234        arg_values: &[VmValue<'z3, 'tcx>],
235        args: &[Spanned<Operand<'tcx>>],
236        destination: Local,
237    ) -> bool {
238        let is_index =
239            callee.is_some_and(|c| crate::helpers::mir_utils::is_index_method(self.tcx, c));
240        if !is_index || arg_values.len() < 2 {
241            return false;
242        }
243        let dest_ty = self.body().local_decls[destination].ty;
244        let is_slice = matches!(dest_ty.kind(), TyKind::Ref(_, inner, _)
245            if matches!(inner.kind(), TyKind::Slice(_)));
246        // A range index (`s[..]` / `s[0..]` / …) yields a `&[T]` / `&mut [T]`,
247        // but in generic MIR the destination type may be left as an
248        // un-normalized `Index`/`IndexMut::Output` projection.  Fall back to the
249        // *index* argument's range kind: a range indexes a slice, a `usize`
250        // indexes a single element (which this handler does not model).
251        let range_kind = arg_values.get(1).and_then(|v| match v.ty.kind() {
252            TyKind::Adt(adt_def, _) => Some(crate::helpers::mir_utils::range_kind(
253                self.tcx,
254                adt_def.did(),
255            )),
256            _ => None,
257        });
258        if !is_slice && range_kind.is_none() {
259            return false;
260        }
261        let Some(prov) = arg_values[0].provenance.clone() else {
262            return false;
263        };
264        let array_term = arg_values[0].z3_term.clone();
265        let (elem_ty, elem_size) = match arg_values[0].ty.kind() {
266            TyKind::Ref(_, inner, _) => match inner.kind() {
267                TyKind::Array(e, _) | TyKind::Slice(e) => (*e, self.size_of_ty(*e).max(1)),
268                _ => (arg_values[0].ty, 1),
269            },
270            _ => (arg_values[0].ty, 1),
271        };
272        let elem_align = self.align_sym(elem_ty);
273        // The range argument is an aggregate whose field layout determines the
274        // slice extent (start element offset and element count):
275        //   RangeTo { end }        -> start = 0, len = end
276        //   RangeFrom { start }    -> start,     len = total - start
277        //   Range { start, end }   -> start,     len = end - start
278        //   RangeInclusive { .. }  -> start,     len = end - start + 1
279        //   otherwise              -> start = 0, len = total
280        let range_local = args.get(1).and_then(|a| match &a.node {
281            Operand::Copy(p) | Operand::Move(p) => Some(p.local),
282            _ => None,
283        });
284        let range_field = |idx: usize| -> Option<Int<'z3>> {
285            range_local.and_then(|l| self.field_value(l, &[idx]).map(|v| v.z3_term.clone()))
286        };
287        let zero = Int::from_u64(self.z3_ctx, 0);
288        let one = Int::from_u64(self.z3_ctx, 1);
289        let total_len = self
290            .alloc(prov.alloc_id)
291            .size
292            .clone()
293            .div(&Int::from_u64(self.z3_ctx, elem_size));
294        let (start, len) = match range_kind {
295            Some(crate::helpers::mir_utils::RangeKind::RangeTo) => (
296                zero.clone(),
297                range_field(0).unwrap_or_else(|| total_len.clone()),
298            ),
299            Some(crate::helpers::mir_utils::RangeKind::RangeFrom) => {
300                let s = range_field(0).unwrap_or_else(|| zero.clone());
301                (s.clone(), Int::sub(self.z3_ctx, &[&total_len, &s]))
302            }
303            Some(crate::helpers::mir_utils::RangeKind::Range) => {
304                let s = range_field(0).unwrap_or_else(|| zero.clone());
305                let e = range_field(1).unwrap_or_else(|| total_len.clone());
306                (s.clone(), Int::sub(self.z3_ctx, &[&e, &s]))
307            }
308            Some(crate::helpers::mir_utils::RangeKind::RangeInclusive) => {
309                let s = range_field(0).unwrap_or_else(|| zero.clone());
310                let e = range_field(1).unwrap_or_else(|| total_len.clone());
311                let l = Int::sub(self.z3_ctx, &[&e, &s]);
312                (s.clone(), Int::add(self.z3_ctx, &[&l, &one]))
313            }
314            _ => (zero.clone(), total_len.clone()),
315        };
316        let elem_size_term = Int::from_u64(self.z3_ctx, elem_size);
317        let start_bytes = if elem_size == 1 {
318            start.clone()
319        } else {
320            Int::mul(self.z3_ctx, &[&start, &elem_size_term])
321        };
322        let size_bytes = if elem_size == 1 {
323            len.clone()
324        } else {
325            Int::mul(self.z3_ctx, &[&len, &elem_size_term])
326        };
327        let dest_term = Int::add(self.z3_ctx, &[&array_term, &start_bytes]);
328        let (alloc_id, _) = self.allocate(size_bytes, elem_align, Some(elem_ty));
329        self.alloc_mut(alloc_id).parent = Some(prov.alloc_id);
330        self.set_local(
331            destination,
332            VmValue {
333                z3_term: dest_term,
334                ty: dest_ty,
335                provenance: Some(Provenance {
336                    alloc_id,
337                    offset: Int::from_u64(self.z3_ctx, 0),
338                    offset_kind: None,
339                }),
340                facts: ValueFacts {
341                    non_null: true,
342                    init: true,
343                    in_bounds: true,
344                    ..Default::default()
345                },
346                source: ValueSource::None,
347            },
348        );
349        true
350    }
351
352    /// Slice range `get` `<[T]>::get(range)` / `::get_mut(range)`: returns
353    /// `Option<&[T]>` whose `Some` payload is a sub-slice with the range's
354    /// extent.  Mirrors [`try_slice_index`](Self::try_slice_index), but stores
355    /// the sub-slice under field 0 (the `Some` payload) so a downstream
356    /// `slice.len()` / `memchr(x, subslice)` sees the correct element count and
357    /// provenance.
358    fn try_slice_get(
359        &mut self,
360        callee: Option<DefId>,
361        arg_values: &[VmValue<'z3, 'tcx>],
362        args: &[Spanned<Operand<'tcx>>],
363        destination: Local,
364    ) -> bool {
365        let Some(c) = callee else {
366            return false;
367        };
368        let Some(assoc) = self.tcx.opt_associated_item(c) else {
369            return false;
370        };
371        if !matches!(assoc.name().as_str(), "get" | "get_mut") || arg_values.len() < 2 {
372            return false;
373        }
374        let dest_ty = self.body().local_decls[destination].ty;
375        let TyKind::Adt(adt, substs) = dest_ty.kind() else {
376            return false;
377        };
378        if !self
379            .tcx
380            .is_diagnostic_item(rustc_span::sym::Option, adt.did())
381        {
382            return false;
383        }
384        let payload_ty = substs.type_at(0);
385        let TyKind::Ref(_, slice_ty, _) = payload_ty.kind() else {
386            return false;
387        };
388        if !matches!(slice_ty.kind(), TyKind::Slice(_)) {
389            return false;
390        }
391        let Some(prov) = arg_values[0].provenance.clone() else {
392            return false;
393        };
394        let array_term = arg_values[0].z3_term.clone();
395        let (elem_ty, elem_size) = match arg_values[0].ty.kind() {
396            TyKind::Ref(_, inner, _) => match inner.kind() {
397                TyKind::Array(e, _) | TyKind::Slice(e) => (*e, self.size_of_ty(*e).max(1)),
398                _ => (arg_values[0].ty, 1),
399            },
400            _ => (arg_values[0].ty, 1),
401        };
402        let elem_align = self.align_sym(elem_ty);
403        let range_local = args.get(1).and_then(|a| match &a.node {
404            Operand::Copy(p) | Operand::Move(p) => Some(p.local),
405            _ => None,
406        });
407        let range_field = |idx: usize| -> Option<Int<'z3>> {
408            range_local.and_then(|l| self.field_value(l, &[idx]).map(|v| v.z3_term.clone()))
409        };
410        let zero = Int::from_u64(self.z3_ctx, 0);
411        let one = Int::from_u64(self.z3_ctx, 1);
412        let total_len = self
413            .alloc(prov.alloc_id)
414            .size
415            .clone()
416            .div(&Int::from_u64(self.z3_ctx, elem_size));
417        let range_kind = arg_values.get(1).and_then(|v| match v.ty.kind() {
418            TyKind::Adt(adt_def, _) => Some(crate::helpers::mir_utils::range_kind(
419                self.tcx,
420                adt_def.did(),
421            )),
422            _ => None,
423        });
424        let (start, len) = match range_kind {
425            Some(crate::helpers::mir_utils::RangeKind::RangeTo) => (
426                zero.clone(),
427                range_field(0).unwrap_or_else(|| total_len.clone()),
428            ),
429            Some(crate::helpers::mir_utils::RangeKind::RangeFrom) => {
430                let s = range_field(0).unwrap_or_else(|| zero.clone());
431                (s.clone(), Int::sub(self.z3_ctx, &[&total_len, &s]))
432            }
433            Some(crate::helpers::mir_utils::RangeKind::Range) => {
434                let s = range_field(0).unwrap_or_else(|| zero.clone());
435                let e = range_field(1).unwrap_or_else(|| total_len.clone());
436                (s.clone(), Int::sub(self.z3_ctx, &[&e, &s]))
437            }
438            Some(crate::helpers::mir_utils::RangeKind::RangeInclusive) => {
439                let s = range_field(0).unwrap_or_else(|| zero.clone());
440                let e = range_field(1).unwrap_or_else(|| total_len.clone());
441                let l = Int::sub(self.z3_ctx, &[&e, &s]);
442                (s.clone(), Int::add(self.z3_ctx, &[&l, &one]))
443            }
444            _ => (zero.clone(), total_len.clone()),
445        };
446        let elem_size_term = Int::from_u64(self.z3_ctx, elem_size);
447        let start_bytes = if elem_size == 1 {
448            start.clone()
449        } else {
450            Int::mul(self.z3_ctx, &[&start, &elem_size_term])
451        };
452        let size_bytes = if elem_size == 1 {
453            len.clone()
454        } else {
455            Int::mul(self.z3_ctx, &[&len, &elem_size_term])
456        };
457        let dest_term = Int::add(self.z3_ctx, &[&array_term, &start_bytes]);
458        let (alloc_id, _) = self.allocate(size_bytes, elem_align, Some(elem_ty));
459        self.alloc_mut(alloc_id).parent = Some(prov.alloc_id);
460        self.set_field_value(
461            destination,
462            vec![0],
463            VmValue {
464                z3_term: dest_term,
465                ty: payload_ty,
466                provenance: Some(Provenance {
467                    alloc_id,
468                    offset: Int::from_u64(self.z3_ctx, 0),
469                    offset_kind: None,
470                }),
471                facts: ValueFacts {
472                    non_null: true,
473                    init: true,
474                    in_bounds: true,
475                    ..Default::default()
476                },
477                source: ValueSource::None,
478            },
479        );
480        true
481    }
482
483    /// `Iter::len()` / `Iter::is_empty()`: compute from struct fields
484    /// (ptr + end_or_len share the same allocation with per-field offsets).
485    /// The generic builtin_models would return sizeof(Iter)/sizeof(T), which is
486    /// wrong for generic T.
487    fn try_iter_len_is_empty(
488        &mut self,
489        name: &str,
490        arg_values: &[VmValue<'z3, 'tcx>],
491        args: &[Spanned<Operand<'tcx>>],
492        destination: Local,
493    ) -> bool {
494        if !((name.contains("::Iter<")
495            || name.contains("::IterMut<")
496            || name.ends_with("::Iter::len")
497            || name.ends_with("::IterMut::len")
498            || name.ends_with("::Iter::is_empty")
499            || name.ends_with("::IterMut::is_empty"))
500            && (name.ends_with("::len") || name.ends_with("::is_empty"))
501            && arg_values.len() >= 1)
502        {
503            return false;
504        }
505        let receiver_local = args.first().and_then(|a| a.node.place()).map(|p| p.local);
506        let Some(local) = receiver_local else {
507            return false;
508        };
509        // len() = (end_or_len - ptr) / sizeof(T)   (non-ZST)
510        // is_empty() = ptr == end_or_len           (non-ZST)
511        let (Some(ptr), Some(end)) = (self.field_value(local, &[0]), self.field_value(local, &[1]))
512        else {
513            return false;
514        };
515        let (Some(pp), Some(ep)) = (&ptr.provenance, &end.provenance) else {
516            return false;
517        };
518        if pp.alloc_id != ep.alloc_id {
519            return false;
520        }
521        let dest_ty = self.body().local_decls[destination].ty;
522        if name.ends_with("::len") {
523            let diff = Int::sub(self.z3_ctx, &[&ep.offset, &pp.offset]);
524            let sz = self.iter_elem_size(ptr);
525            let val = VmValue::new(diff.div(&sz), dest_ty);
526            self.set_local(destination, val);
527        } else {
528            // is_empty(): ptr == end_or_len  (non-ZST branch)
529            let eq = pp.offset._eq(&ep.offset);
530            let zero = Int::from_u64(self.z3_ctx, 0);
531            let one = Int::from_u64(self.z3_ctx, 1);
532            let val = VmValue {
533                z3_term: eq.ite(&one, &zero),
534                ty: dest_ty,
535                provenance: None,
536                facts: ValueFacts::default(),
537                source: ValueSource::None,
538            };
539            self.set_local(destination, val);
540        }
541        true
542    }
543
544    /// `NonNull::<T>::new(ptr) -> Option<NonNull<T>>`: the safe constructor
545    /// returns `Some` iff `ptr` is non-null. Its body branches on
546    /// `ptr.is_null()`, so `exec_inline_call` (branch-free only) cannot inline
547    /// it. Model the null-check directly from provenance, mirroring
548    /// `check_non_null`: internal provenance or a set `non_null`/`in_bounds`
549    /// invariant means the pointer is definitely non-null (`Some(ptr)`), and
550    /// otherwise the `Option` is left symbolic (it may be `None`).
551    fn try_nonnull_new(
552        &mut self,
553        callee: Option<DefId>,
554        arg_values: &[VmValue<'z3, 'tcx>],
555        destination: Local,
556    ) -> bool {
557        if !api_classify::is_nonnull_checked_new(callee) {
558            return false;
559        }
560        let Some(ptr) = arg_values.first() else {
561            return false;
562        };
563        let dest_ty = self.body().local_decls[destination].ty;
564        let definitely_non_null = ptr.facts.non_null
565            || ptr.facts.in_bounds
566            || ptr
567                .provenance
568                .as_ref()
569                .is_some_and(|p| !self.alloc(p.alloc_id).is_external());
570        if definitely_non_null {
571            // Some(NonNull(ptr)): the Option data payload is the non-null pointer.
572            let mut val = ptr.clone();
573            val.ty = dest_ty;
574            val.facts.non_null = true;
575            let zero = Int::from_u64(self.z3_ctx, 0);
576            self.constraints.assertions.push(ptr.z3_term._eq(&zero).not());
577            self.set_local(destination, val);
578        } else {
579            // ptr may be null, so the Option may be None — keep it symbolic.
580            let term = self.fresh_int(&format!("nn_new_{}", destination.as_usize()));
581            self.set_local(
582                destination,
583                VmValue::new(term, dest_ty),
584            );
585        }
586        true
587    }
588
589    /// `Iter::next()` / `IterMut::next()`: advance ptr by 1 and return old.
590    /// The MIR calls the `Iterator::next` trait method, so also match the
591    /// trait path (`std::iter::Iterator::next`) in addition to the concrete
592    /// `Iter`/`IterMut` method names.
593    fn try_iter_next(
594        &mut self,
595        name: &str,
596        arg_values: &[VmValue<'z3, 'tcx>],
597        destination: Local,
598    ) -> bool {
599        let is_next = name.ends_with("::next")
600            && (name.starts_with("Iter::")
601                || name.starts_with("IterMut::")
602                || name.contains("::Iter::")
603                || name.contains("::IterMut::")
604                || name.contains("::Iter<")
605                || name.contains("::IterMut<")
606                || name.contains("::Iterator::next"));
607        if !is_next || arg_values.len() < 1 {
608            return false;
609        }
610        let self_val = &arg_values[0];
611        let Some(local) = self.find_iter_self_local(self_val) else {
612            return false;
613        };
614        let (Some(ptr), Some(end)) = (self.field_value(local, &[0]), self.field_value(local, &[1]))
615        else {
616            return false;
617        };
618        let (Some(pp), Some(ep)) = (&ptr.provenance, &end.provenance) else {
619            return false;
620        };
621        if pp.alloc_id != ep.alloc_id {
622            return false;
623        }
624        let buffer = ep.alloc_id;
625        let ep_elem = match &ep.offset_kind {
626            Some(OffsetKind::Element(e)) => Some(e.clone()),
627            _ => None,
628        };
629        let dest_ty = self.body().local_decls[destination].ty;
630        // Compute is_empty from fields/tracked offset (same as is_empty()).
631        let sz = self.iter_elem_size(ptr);
632        let ep_offset = ep.offset.clone();
633        let remaining = if let Some((off, _)) = self.constraints.term_caches.iter_ptr_offset.get(&buffer) {
634            let base_len = ep_offset.div(&sz);
635            let zero = Int::from_u64(self.z3_ctx, 0);
636            off.gt(&base_len)
637                .ite(&zero, &Int::sub(self.z3_ctx, &[&base_len, off]))
638        } else {
639            let diff = Int::sub(self.z3_ctx, &[&ep_offset, &pp.offset]);
640            diff.div(&sz)
641        };
642        let is_empty = remaining._eq(&Int::from_u64(self.z3_ctx, 0));
643        // The returned element is the *current* position: the tracked element
644        // index (iter_ptr_offset) scaled by the element stride, or the base
645        // ptr offset on the first call.
646        let zero = Int::from_u64(self.z3_ctx, 0);
647        let cur_off = match self.constraints.term_caches.iter_ptr_offset.get(&buffer) {
648            Some((prev, _)) => Int::mul(self.z3_ctx, &[prev, &sz]),
649            None => pp.offset.clone(),
650        };
651        let old_ptr_val = VmValue {
652            z3_term: cur_off.clone(),
653            ty: ptr.ty,
654            provenance: Some(Provenance {
655                alloc_id: pp.alloc_id,
656                offset: cur_off,
657                offset_kind: None,
658            }),
659            facts: ValueFacts {
660                non_null: true,
661                init: true,
662                ..Default::default()
663            },
664            source: ValueSource::None,
665        };
666        // Advance ptr when not empty
667        let one_term = Int::from_u64(self.z3_ctx, 1);
668        let (new_offset, base_len_elem) = match self.constraints.term_caches.iter_ptr_offset.get(&buffer) {
669            Some((prev, base)) => (Int::add(self.z3_ctx, &[prev, &one_term]), base.clone()),
670            None => (one_term.clone(), ep_elem),
671        };
672        // Assert !is_empty as path condition (remaining > 0)
673        self.constraints.assertions.push(remaining.gt(&zero));
674        // Push: base_len >= tracked_offset
675        let base_len = ep_offset.div(&sz);
676        self.constraints.assertions.push(new_offset.le(&base_len));
677        self.constraints
678            .term_caches
679            .iter_ptr_offset
680            .insert(buffer, (new_offset, base_len_elem));
681        // Return None or old ptr
682        let result_val = VmValue {
683            z3_term: is_empty.ite(&zero, &old_ptr_val.z3_term),
684            ty: dest_ty,
685            provenance: if is_empty.as_bool().unwrap_or(false) {
686                None
687            } else {
688                old_ptr_val.provenance.clone()
689            },
690            facts: ValueFacts::default(),
691            // Tie the Option's discriminant to the emptiness condition so
692            // `switchInt(discriminant(_n))` only takes the `Some` branch when
693            // the iterator was non-empty (and the `None` branch when empty).
694            source: ValueSource::Discriminant(is_empty.ite(&zero, &one_term)),
695        };
696        self.set_local(destination, result_val);
697        true
698    }
699
700    fn materialize_const_bytes_after_call(
701        &mut self,
702        args: &[Spanned<Operand<'tcx>>],
703        destination: Local,
704    ) {
705        if let Some(mut dv) = self.local_value(destination).cloned() {
706            let dest_ty = dv.ty;
707            let pointee_is_byte_like = match dest_ty.kind() {
708                rustc_middle::ty::TyKind::RawPtr(inner, _)
709                | rustc_middle::ty::TyKind::Ref(_, inner, _) => match inner.kind() {
710                    rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8)
711                    | rustc_middle::ty::TyKind::Int(rustc_middle::ty::IntTy::I8) => true,
712                    rustc_middle::ty::TyKind::Array(elem_ty, _)
713                    | rustc_middle::ty::TyKind::Slice(elem_ty) => {
714                        matches!(
715                            elem_ty.kind(),
716                            rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8)
717                        )
718                    }
719                    _ => false,
720                },
721                _ => false,
722            };
723            if pointee_is_byte_like {
724                for arg in args {
725                    self.try_materialize_const_bytes(&mut dv, &arg.node);
726                    if dv.is_pointer() {
727                        self.set_local(destination, dv);
728                        break;
729                    }
730                }
731            }
732        }
733    }
734
735    /// Recursively execute a callee's MIR body inline.
736    ///
737    /// Binds the caller's argument values to the callee's parameters,
738    /// executes the callee's MIR, and writes the return value to
739    /// the caller's destination local. Returns `false` if inline
740    /// is not possible (e.g., recursion limit reached, callee has
741    /// branches, or the callee is too large).
742    fn exec_inline_call(
743        &mut self,
744        callee_def_id: DefId,
745        arg_values: &[VmValue<'z3, 'tcx>],
746        caller_arg_locals: &[Option<Local>],
747        dest: Local,
748    ) -> bool {
749        if self.inline.inline_depth >= MAX_INLINE_DEPTH {
750            return false;
751        }
752        self.inline.inline_depth += 1;
753
754        // Only inline branch-free functions. `inline_execute_body` follows
755        // every `SwitchInt` target without forking state, so a real branch
756        // (e.g. a `match` that returns different pointers per arm) would have
757        // its arms merged and lose precision — which silently marks unsound
758        // callers sound. A branch-free body of *any* size is safe to inline
759        // (block count is not a soundness gate), so the filters are the
760        // semantic branch (`has_switch`), multi-return (`n_return > 1`), and
761        // arity (`arg_values > 4`) checks. This keeps the `Box` construction
762        // helpers (`from_new_internal`, 9 blocks) reachable so the fresh heap
763        // allocation's provenance reaches the returned `NonNull`.
764        let callee_body = self.tcx.optimized_mir(callee_def_id);
765        let n_return = callee_body
766            .basic_blocks
767            .iter()
768            .filter(|bb| {
769                matches!(
770                    bb.terminator().kind,
771                    rustc_middle::mir::TerminatorKind::Return
772                )
773            })
774            .count();
775        // Reject a *semantic* branch (a `SwitchInt` reachable on the normal
776        // path): `inline_execute_body` merges its arms and loses precision.
777        // A `SwitchInt` that only appears in a cleanup block (the drop-flag
778        // dispatch) is dead on the normal path and is safe to ignore.
779        // Likewise, a `debug_assert!`/`assert!`-style `SwitchInt` whose every
780        // non-otherwise target leads to `panic`/`unreachable` is dead on the
781        // normal path — inlining it and taking only the `otherwise` edge keeps
782        // the field-level provenance of wrapper casts (`cast_to_internal_unchecked`).
783        let has_switch = callee_body.basic_blocks.iter_enumerated().any(|(idx, bb)| {
784            !bb.is_cleanup
785                && matches!(
786                    bb.terminator().kind,
787                    rustc_middle::mir::TerminatorKind::SwitchInt { .. }
788                )
789                && !crate::helpers::mir_utils::switch_is_debug_assert(self.tcx, callee_body, idx)
790        });
791        if arg_values.len() > 4 || n_return > 1 || has_switch
792        {
793            self.inline.inline_depth -= 1;
794            return false;
795        }
796
797        // ── Save caller context ──
798        // Resolve each arg's referent local (for `&self`/`&mut self` reborrow
799        // temps) *before* the caller's address map is saved away, so that
800        // `exec_assign` can resolve `(*self).field = val` writes back to the
801        // caller's referent while the callee executes.
802        let inline_arg_referents: Vec<Option<Local>> = arg_values
803            .iter()
804            .map(|v| self.find_local_by_address(&v.z3_term))
805            .collect();
806        // Whole-place reborrow referents, resolved from the *caller's* MIR
807        // before the body is switched to the callee (used to propagate the
808        // referent's struct fields into the callee for `old = self.ptr`).
809        let reborrow_referents: Vec<Option<Local>> = caller_arg_locals
810            .iter()
811            .map(|arg_opt| arg_opt.and_then(|a| self.find_whole_reborrow_referent(a)))
812            .collect();
813        let frame = self.save_frame();
814        let saved_inline_arg_referents =
815            std::mem::replace(&mut self.inline.arg_referents, inline_arg_referents);
816        let saved_deferred_field_writes = std::mem::take(&mut self.inline.deferred_field_writes);
817
818        // ── Switch to callee context ──
819        self.current_frame.current_def_id = callee_def_id;
820
821        // Bind args to callee locals (local_1..local_N are function params)
822        for (i, arg_val) in arg_values.iter().enumerate() {
823            let callee_local = Local::from_usize(i + 1);
824            self.ensure_local_allocation(callee_local);
825            self.set_local(callee_local, arg_val.clone());
826        }
827
828        // Propagate the caller arg locals' field values into the callee context
829        // so that the inline body can access struct fields (e.g. Iter::ptr /
830        // end_or_len for len/is_empty computations).
831        for (i, caller_arg_opt) in caller_arg_locals.iter().enumerate() {
832            let callee_param = Local::from_usize(i + 1);
833            let Some(caller_arg) = caller_arg_opt else {
834                continue;
835            };
836            // A whole-place reborrow (`_7 = &mut (*_1)`) shares the referent's
837            // struct fields; when the reborrow temp's own assignment was pruned,
838            // propagate the referent's fields so `self.ptr` resolves in the
839            // callee (Iter::next).  Excludes field reborrows.
840            let mut source_locals = vec![*caller_arg];
841            if let Some(r) = reborrow_referents.get(i).copied().flatten() {
842                if r != *caller_arg {
843                    source_locals.push(r);
844                }
845            }
846            for src in source_locals {
847                let caller_field_keys: Vec<Vec<usize>> = self.frame_field_paths(&frame, src);
848                for fields in caller_field_keys {
849                    if let Some(fv) = self.frame_field_value(&frame, src, &fields).cloned() {
850                        self.set_field_value(callee_param, fields, fv);
851                    }
852                }
853            }
854        }
855
856        // ── BFS execution of callee MIR ──
857        self.inline_execute_body();
858
859        // ── Capture return value and its per-field values ──
860        let return_val = self.local_value(Local::from_usize(0)).cloned();
861        crate::rap_debug!(
862            "exec_inline_call: callee={:?} return_val={:?}",
863            callee_def_id,
864            return_val
865                .as_ref()
866                .map(|v| (v.z3_term.to_string(), v.facts.non_null))
867        );
868        let return_fields: Vec<(Vec<usize>, VmValue<'z3, 'tcx>)> = self
869            .field_paths(Local::from_usize(0))
870            .into_iter()
871            .filter_map(|path| {
872                self.field_value(Local::from_usize(0), &path)
873                    .cloned()
874                    .map(|val| (path, val))
875            })
876            .collect();
877
878        // ── Restore caller context ──
879        self.restore_frame(frame);
880
881        // Apply deferred field writes (`(*self).field = val` through a
882        // `&mut self` reborrow) collected during the callee's execution, now
883        // that the caller's frame (and its address map) is live again.
884        for (local, path, value) in std::mem::take(&mut self.inline.deferred_field_writes) {
885            self.set_field_value(local, path, value);
886        }
887        self.inline.arg_referents = saved_inline_arg_referents;
888        self.inline.deferred_field_writes = saved_deferred_field_writes;
889
890        // ── Write return value to caller destination ──
891        let dest_ty = self.body().local_decls[dest].ty;
892        match return_val {
893            Some(mut val) => {
894                val.ty = dest_ty;
895                // Infer facts: a non-null provenance with offset=0
896                // means the return value is valid and initialized.
897                let at_base = val
898                    .provenance
899                    .as_ref()
900                    .is_some_and(|p| p.offset.as_u64() == Some(0));
901                if at_base {
902                    val.facts.non_null = true;
903                    self.mark_initialized(&mut val);
904                }
905                self.set_local(dest, val);
906                // Propagate the callee's per-field return values (e.g. a
907                // tuple `(NonNull<T>, A)`'s field 0) to the caller's
908                // destination so subsequent field projections resolve.
909                for (path, fv) in return_fields {
910                    self.set_field_value(dest, path, fv);
911                }
912                // The callee returned a fully-constructed value, so the
913                // caller's destination stack slot is initialized.  This matters
914                // for ADT returns (struct/enum) whose aggregate value carries
915                // no provenance: a later `&raw const (*&field)` + `ptr::read`
916                // must be able to discharge `Init` against the field.
917                if let Some(dest_alloc_id) = self.current_frame.local_alloc.get(&dest).copied() {
918                    self.content_mut(dest_alloc_id).facts.initialized = true;
919                }
920            }
921            None => {
922                self.inline.inline_depth -= 1;
923                return false;
924            }
925        }
926
927        self.inline.inline_depth -= 1;
928        true
929    }
930
931    /// Resolve a `SwitchInt` discriminant to a constant `u64`, following a
932    /// single local-assignment chain (a `cfg!`-style runtime-check flag).
933    fn switch_discr_const(
934        body: &rustc_middle::mir::Body<'tcx>,
935        discr: &Operand<'tcx>,
936    ) -> Option<u64> {
937        if let Some(v) = crate::helpers::mir_utils::operand_const_u64(discr) {
938            return Some(v);
939        }
940        let (Operand::Copy(p) | Operand::Move(p)) = discr else {
941            return None;
942        };
943        for bbd in body.basic_blocks.iter() {
944            for stmt in bbd.statements.iter() {
945                let rustc_middle::mir::StatementKind::Assign(assign) = &stmt.kind else {
946                    continue;
947                };
948                let (dest, rvalue) = &**assign;
949                if dest != p {
950                    continue;
951                }
952                return crate::helpers::mir_utils::rvalue_runtime_checks_value(rvalue);
953            }
954        }
955        None
956    }
957
958    /// BFS-execute the callee's MIR body.
959    fn inline_execute_body(&mut self) {
960        let mut visited = FxHashSet::default();
961        let mut queue: Vec<BasicBlock> = Vec::new();
962        queue.push(BasicBlock::from_usize(0));
963
964        while let Some(block) = queue.pop() {
965            if !visited.insert(block) {
966                continue;
967            }
968
969            let bb_data = &self.body().basic_blocks[block];
970
971            // Execute statements
972            for stmt in bb_data.statements.iter() {
973                self.exec_statement(stmt);
974            }
975
976            // Process terminator
977            let terminator = bb_data.terminator();
978
979            match &terminator.kind {
980                TerminatorKind::Goto { target } => {
981                    queue.push(*target);
982                }
983                TerminatorKind::Return => {
984                    // Return value captured in local_0
985                }
986                TerminatorKind::Assert {
987                    cond,
988                    expected,
989                    target,
990                    ..
991                } => {
992                    let cond_val = self.value_of_operand(cond);
993                    if *expected {
994                        let zero = Int::from_u64(self.z3_ctx, 0);
995                        self.constraints.assertions.push(cond_val.z3_term._eq(&zero).not());
996                    } else {
997                        let zero = Int::from_u64(self.z3_ctx, 0);
998                        self.constraints.assertions.push(cond_val.z3_term._eq(&zero));
999                    }
1000                    // Guard inference for inline callee
1001                    self.infer_guard_non_null(cond, *expected);
1002                    self.infer_guard_align(cond, *expected);
1003                    queue.push(*target);
1004                }
1005                TerminatorKind::SwitchInt { discr, targets } => {
1006                    // A constant discriminant folds to a single live edge.
1007                    if let Some(v) = Self::switch_discr_const(self.body(), discr) {
1008                        let t = targets
1009                            .iter()
1010                            .find(|(val, _)| *val == v as u128)
1011                            .map(|(_, t)| t)
1012                            .unwrap_or_else(|| targets.otherwise());
1013                        queue.push(t);
1014                        continue;
1015                    }
1016                    // A `debug_assert!`/`assert!` switch or a drop-flag dispatch
1017                    // has its non-otherwise edges dead on the normal path, so
1018                    // follow only `otherwise`.
1019                    let trivial = crate::helpers::mir_utils::switch_targets_unreachable(
1020                        self.tcx,
1021                        self.body(),
1022                        targets,
1023                    );
1024                    if trivial {
1025                        queue.push(targets.otherwise());
1026                        continue;
1027                    }
1028                    // Conservative: add path conditions for all branches,
1029                    // but since we don't fork state, we follow all targets.
1030                    // This loses precision for overwritten locals but is sound.
1031                    for (value, target) in targets.iter() {
1032                        let discr_val = self.value_of_operand(discr);
1033                        let val_term = Int::from_u64(self.z3_ctx, value as u64);
1034                        self.constraints.assertions.push(discr_val.z3_term._eq(&val_term));
1035                        queue.push(target);
1036                    }
1037                    let otherwise = targets.otherwise();
1038                    queue.push(otherwise);
1039                }
1040                TerminatorKind::Call {
1041                    func,
1042                    args,
1043                    destination,
1044                    target,
1045                    ..
1046                } => {
1047                    self.exec_call(
1048                        func,
1049                        args,
1050                        destination.local,
1051                        self.current_frame.current_def_id,
1052                    );
1053                    if let Some(t) = target {
1054                        queue.push(*t);
1055                    }
1056                }
1057                TerminatorKind::Drop { place, target, .. } => {
1058                    self.exec_drop(place);
1059                    queue.push(*target);
1060                }
1061                TerminatorKind::Unreachable
1062                | TerminatorKind::UnwindResume
1063                | TerminatorKind::UnwindTerminate(_)
1064                | TerminatorKind::Yield { .. }
1065                | TerminatorKind::CoroutineDrop
1066                | TerminatorKind::FalseEdge { .. }
1067                | TerminatorKind::FalseUnwind { .. }
1068                | TerminatorKind::InlineAsm { .. }
1069                | TerminatorKind::TailCall { .. } => {
1070                    // Dead-end or unsupported — stop traversal at this block.
1071                }
1072            }
1073        }
1074    }
1075
1076    /// Clone `arg_val`, retype it to `dest`'s type, mark it as a non-null,
1077    /// aligned, initialized pointer, and bind it to `dest`.
1078    fn set_dest_as_heap_ptr(&mut self, arg_val: &VmValue<'z3, 'tcx>, dest: Local) {
1079        let mut val = arg_val.clone();
1080        val.ty = self.body().local_decls[dest].ty;
1081        val.facts.non_null = true;
1082        val.facts.init = true;
1083        self.set_local(dest, val);
1084    }
1085
1086    /// For a `size_of::<T>()` / `align_of::<T>()` call whose `T` is generic (no
1087    /// concrete layout), bind the destination to the shared symbolic
1088    /// `sizeof_T` / `align_T` so it agrees with `size_sym`/`align_sym`.  Returns
1089    /// `true` when handled.  Concrete layouts are left to `eff_layout_const`.
1090    fn try_size_align_effect(&mut self, func: &Operand<'tcx>, destination: Local) -> bool {
1091        let Some(ty) = crate::helpers::mir_utils::fn_def_first_type_arg(func) else {
1092            return false;
1093        };
1094        let Some(callee) = crate::helpers::mir_utils::dep_callee_def_id(func) else {
1095            return false;
1096        };
1097        let is_size = crate::def_id::contains(
1098            &[
1099                crate::def_id::mem_size_of(),
1100                crate::def_id::intrinsics_size_of(),
1101            ],
1102            callee,
1103        );
1104        let is_align = crate::def_id::contains(
1105            &[
1106                crate::def_id::mem_align_of(),
1107                crate::def_id::intrinsics_align_of(),
1108            ],
1109            callee,
1110        );
1111        if !is_size && !is_align {
1112            return false;
1113        }
1114        // A concrete layout is already modelled as `ReturnConst` by
1115        // `eff_layout_const`; only the generic (symbolic) case needs binding here.
1116        // `type_layout` reports `(0, 0)` for a generic `T`, so a zero alignment
1117        // (not a zero *size*, which is a legal ZST) marks the unknown case.
1118        if crate::helpers::mir_utils::type_layout(self.tcx, self.current_frame.current_def_id, ty)
1119            .is_some_and(|(align, _)| align > 0)
1120        {
1121            return false;
1122        }
1123        let term = if is_size {
1124            self.size_sym(ty)
1125        } else {
1126            self.align_sym(ty)
1127        };
1128        let dest_ty = self.body().local_decls[destination].ty;
1129        self.set_local(
1130            destination,
1131            VmValue::new(term, dest_ty),
1132        );
1133        true
1134    }
1135
1136    /// Apply a binary numeric effect: compute `f(lhs.z3_term, rhs.z3_term)` and store
1137    /// it as the destination's fresh scalar value.
1138    fn apply_binary_num(
1139        &mut self,
1140        dest: Local,
1141        args: &[VmValue<'z3, 'tcx>],
1142        lhs_arg: usize,
1143        rhs_arg: usize,
1144        f: impl Fn(&Int<'z3>, &Int<'z3>) -> Int<'z3>,
1145    ) {
1146        if let (Some(lhs), Some(rhs)) = (args.get(lhs_arg), args.get(rhs_arg)) {
1147            let dest_ty = self.body().local_decls[dest].ty;
1148            self.set_local(dest, VmValue::new(f(&lhs.z3_term, &rhs.z3_term), dest_ty));
1149        }
1150    }
1151
1152    /// Apply a unary numeric effect: compute `f(a.z3_term)` and store it as the
1153    /// destination's fresh scalar value.
1154    fn apply_unary_num(
1155        &mut self,
1156        dest: Local,
1157        args: &[VmValue<'z3, 'tcx>],
1158        arg: usize,
1159        f: impl Fn(&Int<'z3>) -> Int<'z3>,
1160    ) {
1161        if let Some(a) = args.get(arg) {
1162            let dest_ty = self.body().local_decls[dest].ty;
1163            self.set_local(dest, VmValue::new(f(&a.z3_term), dest_ty));
1164        }
1165    }
1166
1167    /// Apply a single call effect to the VM state.
1168    pub(crate) fn apply_call_effect(
1169        &mut self,
1170        effect: &CallEffect,
1171        args: &[VmValue<'z3, 'tcx>],
1172        caller_arg_locals: &[Option<Local>],
1173        dest: Local,
1174        callee: Option<DefId>,
1175    ) {
1176        match effect {
1177            CallEffect::ReturnAliasArg { arg } => {
1178                if let Some(arg_val) = args.get(*arg) {
1179                    self.set_dest_as_heap_ptr(arg_val, dest);
1180                }
1181            }
1182            CallEffect::SelectUnpredictable => {
1183                if args.len() >= 3 {
1184                    let term = self.fresh_int(&format!("selunpred_{}", dest.as_usize()));
1185                    let dest_ty = self.body().local_decls[dest].ty;
1186                    let eq1 = term._eq(&args[1].z3_term);
1187                    let eq2 = term._eq(&args[2].z3_term);
1188                    self.constraints.assertions.push(Bool::or(self.z3_ctx, &[&eq1, &eq2]));
1189                    let prov = args[1]
1190                        .provenance
1191                        .clone()
1192                        .or_else(|| args[2].provenance.clone());
1193                    self.set_local(
1194                        dest,
1195                        VmValue {
1196                            z3_term: term,
1197                            ty: dest_ty,
1198                            provenance: prov,
1199                            facts: ValueFacts::default(),
1200                            source: ValueSource::None,
1201                        },
1202                    );
1203                }
1204            }
1205            CallEffect::ReturnDerefArg { arg } => {
1206                // `mem::replace(dest, src)` returns `*dest`: the pointee value,
1207                // not the `&mut` reference. Prefer the materialized pointee
1208                // (the empty-path field value, set by
1209                // `propagate_field_values_to_ref` for `&mut self.field`); then
1210                // recover the old field value from the materialized field maps;
1211                // finally, when the borrow chain was dropped by the slicer and no
1212                // field value is recoverable, model the returned slice as a fresh
1213                // external allocation so a downstream `Allocated`/`InBound` can
1214                // still match `[T]` vs `T`.
1215                let dest_ty = self.body().local_decls[dest].ty;
1216                let mut val = args.get(*arg).cloned().unwrap_or_else(|| VmValue::new(self.fresh_int("replaced"), dest_ty));
1217                // `mem::replace(&mut dest, src)` returns `*dest`: the pointee
1218                // value, not the `&mut` reference.  With M2 the reference's
1219                // `path == []` holds its *address* (whole value), so the pointee
1220                // is recovered from the materialized field maps by type +
1221                // provenance (e.g. `self.v: *mut [T]`), or — when the borrow
1222                // chain was dropped by the slicer and no field value is
1223                // recoverable — modeled as a fresh external allocation so a
1224                // downstream `Allocated`/`InBound` can still match `[T]` vs `T`.
1225                if let Some(search) = self
1226                    .units
1227                    .iter()
1228                    .flat_map(|u| u.content.values.values())
1229                    .find(|v| v.ty == dest_ty && v.is_pointer())
1230                    .cloned()
1231                {
1232                    val = search;
1233                } else if let Some(elem) = crate::helpers::mir_utils::pointee_ty(dest_ty) {
1234                    let is_slice = matches!(elem.kind(), rustc_middle::ty::TyKind::Slice(_));
1235                    if is_slice {
1236                        let elem_align = self.align_sym(elem);
1237                        let (alloc_id, base) = self.allocate_external(
1238                            Int::from_u64(self.z3_ctx, i64::MAX as u64),
1239                            elem_align,
1240                            Some(elem),
1241                        );
1242                        val = VmValue {
1243                            z3_term: base,
1244                            ty: dest_ty,
1245                            provenance: Some(Provenance {
1246                                alloc_id,
1247                                offset: Int::from_u64(self.z3_ctx, 0),
1248                                offset_kind: None,
1249                            }),
1250                            facts: ValueFacts::default(),
1251                            source: ValueSource::None,
1252                        };
1253                    }
1254                }
1255                val.ty = dest_ty;
1256                self.set_local(dest, val);
1257            }
1258            CallEffect::ReturnTransparentDeref { arg, peel } => {
1259                if let Some(arg_val) = args.get(*arg) {
1260                    self.set_dest_as_heap_ptr(arg_val, dest);
1261                    // Peel `peel` leading field-0 hops off the argument's
1262                    // pointee field values (ManuallyDrop.value → MaybeDangling.0)
1263                    // and expose them as the deref result's pointee fields.
1264                    if let Some(arg_local) = caller_arg_locals.get(*arg).copied().flatten() {
1265                        let keys: Vec<Vec<usize>> = self.field_paths(arg_local);
1266                        for path in keys {
1267                            if path.len() > *peel && path[..*peel].iter().all(|&f| f == 0) {
1268                                if let Some(v) = self.field_value(arg_local, &path).cloned() {
1269                                    self.set_field_value(dest, path[*peel..].to_vec(), v);
1270                                }
1271                            }
1272                        }
1273                    }
1274                }
1275            }
1276            CallEffect::ReturnTupleFieldLength {
1277                field: _field,
1278                from_arg: _from_arg,
1279            } => {
1280                if args.len() < 2 {
1281                    return;
1282                }
1283                let self_val = &args[0]; // &[T]
1284                let mid_val = &args[1]; // usize
1285
1286                let dest_ty = self.body().local_decls[dest].ty;
1287                if let TyKind::Tuple(elem_tys) = dest_ty.kind() {
1288                    // Look up the source allocation from self's provenance.
1289                    let src_alloc_id = self_val.provenance.as_ref().map(|p| p.alloc_id);
1290
1291                    let (elem_ty, elem_sz_term, alloc_size) = src_alloc_id
1292                        .map(|id| self.alloc(id))
1293                        .map(|a| {
1294                            let ty = a.element_ty.as_ty();
1295                            let sz_term = self.size_sym_read(ty.unwrap_or(self_val.ty));
1296                            (ty, sz_term, a.size.clone())
1297                        })
1298                        .unwrap_or_else(|| {
1299                            // Provenance lost (e.g. `mem::replace` on a raw field
1300                            // whose borrow the slicer dropped): fall back to the
1301                            // slice pointee type so `InBound`/`Allocated` can
1302                            // still match `[T]` against the element `T`.
1303                            let pointee = crate::helpers::mir_utils::pointee_ty(self_val.ty);
1304                            let sz = self.size_sym_read(pointee.unwrap_or(self_val.ty));
1305                            (pointee, sz, Int::from_u64(self.z3_ctx, 1))
1306                        });
1307
1308                    let total_len = self
1309                        .slice_len_from_value(self_val)
1310                        .unwrap_or_else(|| alloc_size.div(&elem_sz_term)); // self.len()
1311
1312                    let zero = Int::from_u64(self.z3_ctx, 0);
1313                    self.constraints.assertions.push(mid_val.z3_term.ge(&zero));
1314                    self.constraints.assertions.push(mid_val.z3_term.le(&total_len));
1315
1316                    // mid (field 0 length)
1317                    let mid = mid_val.z3_term.clone();
1318                    // self.len() - mid (field 1 length)
1319                    let rest_len = Int::sub(self.z3_ctx, &[&total_len, &mid]);
1320
1321                    // mid byte offset for field 1 pointer
1322                    let mid_bytes = Int::mul(self.z3_ctx, &[&mid, &elem_sz_term]);
1323                    let ptr1 = Int::add(self.z3_ctx, &[&self_val.z3_term, &mid_bytes]);
1324
1325                    for f in 0..elem_tys.len() {
1326                        let field_ty = elem_tys[f];
1327                        let (field_len, field_ptr) = if f == 0 {
1328                            (mid.clone(), self_val.z3_term.clone())
1329                        } else {
1330                            (rest_len.clone(), ptr1.clone())
1331                        };
1332                        let field_size = Int::mul(self.z3_ctx, &[&field_len, &elem_sz_term]);
1333                        let field_alloc_align = self_val
1334                            .provenance
1335                            .as_ref()
1336                            .map(|p| self.alloc(p.alloc_id).align.clone())
1337                            .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
1338
1339                        let (alloc_id, _base) = self.allocate_slice(
1340                            field_len.clone(),
1341                            elem_sz_term.clone(),
1342                            field_alloc_align.clone(),
1343                            elem_ty,
1344                        );
1345                        let src_bytes = Int::mul(self.z3_ctx, &[&total_len, &elem_sz_term]);
1346                        if f == 0 {
1347                            self.constraints.assertions.push(field_size._eq(&mid_bytes));
1348                        } else {
1349                            let remaining = Int::sub(self.z3_ctx, &[&src_bytes, &mid_bytes]);
1350                            self.constraints.assertions.push(field_size._eq(&remaining));
1351                        }
1352                        self.content_mut(alloc_id).facts.initialized = true;
1353                        if let Some(ref source_prov) = self_val.provenance {
1354                            self.alloc_mut(alloc_id).parent = Some(source_prov.alloc_id);
1355                        }
1356
1357                        let field_offset = Int::from_u64(self.z3_ctx, 0);
1358
1359                        let field_prov = Provenance {
1360                            alloc_id,
1361                            offset: field_offset,
1362                            offset_kind: None,
1363                        };
1364
1365                        let field_val = VmValue {
1366                            z3_term: field_ptr,
1367                            ty: field_ty,
1368                            provenance: Some(field_prov),
1369                            facts: ValueFacts {
1370                                init: true,
1371                                non_null: true,
1372                                in_bounds: true,
1373                                align_n: Some(field_alloc_align),
1374                            },
1375                            source: ValueSource::None,
1376                        };
1377                        self.set_field_value(dest, vec![f], field_val);
1378                    }
1379                }
1380            }
1381            CallEffect::ReturnIter { receiver_arg } => {
1382                let Some(self_val) = args.get(*receiver_arg).cloned() else {
1383                    return;
1384                };
1385                let Some(src_prov) = self_val.provenance.clone() else {
1386                    return;
1387                };
1388                // `array[..i]` may be a `from_raw_parts` sub-allocation of the
1389                // array's backing storage. Follow the sub-allocation chain to the
1390                // root so the iterator's `ptr`/`end_or_len` fields point at live,
1391                // init-tracked storage (the array itself), not the transient
1392                // slice allocation.
1393                let root_alloc_id = {
1394                    let mut id = src_prov.alloc_id;
1395                    while let Some(parent) = self.alloc(id).parent {
1396                        id = parent;
1397                    }
1398                    id
1399                };
1400                let slice_len = self.alloc(src_prov.alloc_id).size.clone();
1401
1402                // The Iter/IterMut struct has `ptr` (field 0) and `end_or_len`
1403                // (field 1), both raw pointers into the source slice allocation.
1404                // Derive the pointee type so `next()` can compute the stride.
1405                let field_ty = match self_val.ty.kind() {
1406                    TyKind::Ref(_, inner, _) => match inner.kind() {
1407                        TyKind::Slice(t) => *t,
1408                        _ => self_val.ty,
1409                    },
1410                    _ => self_val.ty,
1411                };
1412
1413                let start_off = Int::from_u64(self.z3_ctx, 0);
1414                let end_term = Int::add(self.z3_ctx, &[&self_val.z3_term, &slice_len]);
1415
1416                // `&[T]` / `&mut [T]` data pointers are aligned to the element
1417                // type `T`, so the iterator's `ptr` / `end_or_len` fields inherit
1418                // that alignment.  This lets the `raw-ptr-deref` `Align` check in
1419                // `Iterator::next`/`next_back` discharge against the tracked
1420                // `align_n` instead of falling back to the (unprovable) modulo.
1421                let elem_align_n = {
1422                    let a = self.align_sym(field_ty);
1423                    (a.simplify().as_u64() != Some(1)).then_some(a)
1424                };
1425
1426                let start_val = VmValue {
1427                    z3_term: self_val.z3_term.clone(),
1428                    ty: field_ty,
1429                    provenance: Some(Provenance {
1430                        alloc_id: root_alloc_id,
1431                        offset: start_off,
1432                        offset_kind: None,
1433                    }),
1434                    facts: ValueFacts {
1435                        init: true,
1436                        non_null: true,
1437                        align_n: elem_align_n.clone(),
1438                        ..Default::default()
1439                    },
1440                    source: ValueSource::None,
1441                };
1442                let end_val = VmValue {
1443                    z3_term: end_term,
1444                    ty: field_ty,
1445                    provenance: Some(Provenance {
1446                        alloc_id: root_alloc_id,
1447                        offset: slice_len,
1448                        offset_kind: None,
1449                    }),
1450                    facts: ValueFacts {
1451                        init: true,
1452                        non_null: true,
1453                        align_n: elem_align_n,
1454                        ..Default::default()
1455                    },
1456                    source: ValueSource::None,
1457                };
1458                self.set_field_value(dest, vec![0], start_val);
1459                self.set_field_value(dest, vec![1], end_val);
1460            }
1461            CallEffect::ReturnRange { bounds_arg } => {
1462                self.apply_range_effect(*bounds_arg, args, caller_arg_locals, dest);
1463            }
1464            CallEffect::ReturnAlignTo { receiver_arg } => {
1465                let Some(self_val) = args.get(*receiver_arg).cloned() else {
1466                    return;
1467                };
1468                let dest_ty = self.body().local_decls[dest].ty;
1469                let TyKind::Tuple(elem_tys) = dest_ty.kind() else {
1470                    return;
1471                };
1472                if elem_tys.len() < 3 {
1473                    return;
1474                }
1475
1476                // Body element type U is the pointee of field 1 (`&[U]`).
1477                let body_elem_ty = match elem_tys[1].kind() {
1478                    TyKind::Ref(_, inner, _) => match inner.kind() {
1479                        TyKind::Slice(u) => *u,
1480                        _ => return,
1481                    },
1482                    _ => return,
1483                };
1484                let size_u = self.size_of_ty(body_elem_ty).max(1);
1485                let align_u = self.align_sym(body_elem_ty);
1486
1487                let Some(src_prov) = self_val.provenance.clone() else {
1488                    return;
1489                };
1490                let alloc = self.alloc(src_prov.alloc_id);
1491                let (elem_ty, elem_sz, len_bytes) = {
1492                    let ty = alloc.element_ty.as_ty();
1493                    let sz = self.size_of_ty(ty.unwrap_or(self_val.ty)).max(1);
1494                    (ty, sz, alloc.size.clone())
1495                };
1496
1497                let elem_sz_term = Int::from_u64(self.z3_ctx, elem_sz);
1498                let size_u_term = Int::from_u64(self.z3_ctx, size_u);
1499
1500                // Fresh aligned offset: (ptr + offset) % align_u == 0 and
1501                // 0 <= offset < align_u.
1502                let offset = self.fresh_int(&format!("align_to_offset_{}", dest.as_usize()));
1503                let zero = Int::from_u64(self.z3_ctx, 0);
1504                let ptr_plus_offset = Int::add(self.z3_ctx, &[&self_val.z3_term, &offset]);
1505                self.constraints.assertions
1506                    .push(ptr_plus_offset.rem(&align_u)._eq(&zero));
1507                self.constraints.assertions.push(offset.ge(&zero));
1508                self.constraints.assertions.push(offset.lt(&align_u));
1509
1510                // body = len_bytes - offset bytes split into size_u chunks; the
1511                // remainder is the suffix. Record the Euclidean identity so that
1512                // `len - offset - suffix = body_len * size_u` (a multiple of
1513                // align_u) is derivable downstream.
1514                let body_bytes = Int::sub(self.z3_ctx, &[&len_bytes, &offset]);
1515                let body_len = body_bytes.div(&size_u_term);
1516                let suffix_bytes = body_bytes.rem(&size_u_term);
1517                let mul_term = Int::mul(self.z3_ctx, &[&body_len, &size_u_term]);
1518                let sum_term = Int::add(self.z3_ctx, &[&mul_term, &suffix_bytes]);
1519                self.constraints.assertions.push(body_bytes._eq(&sum_term));
1520                self.constraints.assertions.push(suffix_bytes.ge(&zero));
1521                self.constraints.assertions.push(suffix_bytes.lt(&size_u_term));
1522
1523                // Field lengths in elements.
1524                let prefix_len = offset.div(&elem_sz_term);
1525                let suffix_len = suffix_bytes.div(&elem_sz_term);
1526
1527                let body_byte_len = Int::mul(self.z3_ctx, &[&body_len, &size_u_term]);
1528                let suffix_ptr = Int::add(self.z3_ctx, &[&ptr_plus_offset, &body_byte_len]);
1529
1530                let base_align = self.alloc(src_prov.alloc_id).align.clone();
1531
1532                let fields: Vec<(Int<'z3>, Int<'z3>, Ty<'tcx>, u64, Int<'z3>)> = vec![
1533                    (
1534                        prefix_len,
1535                        self_val.z3_term.clone(),
1536                        elem_tys[0],
1537                        elem_sz,
1538                        base_align.clone(),
1539                    ),
1540                    (body_len, ptr_plus_offset, elem_tys[1], size_u, align_u),
1541                    (suffix_len, suffix_ptr, elem_tys[2], elem_sz, base_align),
1542                ];
1543
1544                for (f, (f_len, f_ptr, f_ty, f_elem_sz, f_align)) in fields.into_iter().enumerate()
1545                {
1546                    let f_elem_ty = if f == 1 { Some(body_elem_ty) } else { elem_ty };
1547                    let (alloc_id, _) = self.allocate_slice(
1548                        f_len.clone(),
1549                        Int::from_u64(self.z3_ctx, f_elem_sz),
1550                        f_align.clone(),
1551                        f_elem_ty,
1552                    );
1553                    self.content_mut(alloc_id).facts.initialized = true;
1554                    self.alloc_mut(alloc_id).parent = Some(src_prov.alloc_id);
1555                    let field_val = VmValue {
1556                        z3_term: f_ptr,
1557                        ty: f_ty,
1558                        provenance: Some(Provenance {
1559                            alloc_id,
1560                            offset: Int::from_u64(self.z3_ctx, 0),
1561                            offset_kind: None,
1562                        }),
1563                        facts: ValueFacts {
1564                            init: true,
1565                            non_null: true,
1566                            in_bounds: true,
1567                            align_n: if f_align.simplify().as_u64() != Some(1) {
1568                                Some(f_align)
1569                            } else {
1570                                None
1571                            },
1572                        },
1573                        source: ValueSource::None,
1574                    };
1575                    self.set_field_value(dest, vec![f], field_val);
1576                }
1577            }
1578            CallEffect::ReturnPointerFromArg { arg } => {
1579                if let Some(arg_val) = args.get(*arg) {
1580                    let mut val = arg_val.clone();
1581                    let dest_ty = self.body().local_decls[dest].ty;
1582                    val.ty = dest_ty;
1583                    // The returned pointer aliases `arg`, so it is non-null
1584                    // exactly when the source is. The source is non-null either
1585                    // because its value already carries the fact, or by its
1586                    // *type*: a reference (`&`/`&mut`) is never null, and
1587                    // `NonNull` is non-null by invariant.
1588                    let src_non_null = arg_val.facts.non_null
1589                        || matches!(arg_val.ty.kind(), rustc_middle::ty::TyKind::Ref(..))
1590                        || matches!(
1591                            arg_val.ty.kind(),
1592                            rustc_middle::ty::TyKind::Adt(adt, _)
1593                                if api_classify::is_std_nonnull(adt.did())
1594                        );
1595                    val.facts.non_null = src_non_null;
1596                    // Preserve the tracked alignment so `as_ptr().deref()` can
1597                    // discharge the `raw-ptr-deref` `Align` check (Iter::next).
1598                    val.facts.align_n = arg_val.facts.align_n.clone();
1599                    // Pointer-returning APIs expose the backing allocation;
1600                    // mark it init-accessible for raw pointer types.
1601                    if matches!(dest_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..)) {
1602                        val.facts.init = true;
1603                    }
1604                    // For heap-backed containers (Vec/CString/String) and slice
1605                    // views: redirect as_ptr() from the struct/slice allocation
1606                    // to the heap data allocation.
1607                    if let Some(ref prov) = val.provenance {
1608                        if let Some(data_alloc) = self.data_alloc_of(prov.alloc_id, arg_val.ty) {
1609                            val.z3_term = self.allocation_base(data_alloc).clone();
1610                            val.provenance = Some(Provenance {
1611                                alloc_id: data_alloc,
1612                                offset: Int::from_u64(self.z3_ctx, 0),
1613                                offset_kind: None,
1614                            });
1615                        }
1616                    }
1617                    if src_non_null {
1618                        let zero = Int::from_u64(self.z3_ctx, 0);
1619                        self.constraints.assertions.push(val.z3_term._eq(&zero).not());
1620                    }
1621                    self.set_local(dest, val);
1622                }
1623            }
1624            CallEffect::ReturnPointerAdd {
1625                base_arg,
1626                offset_arg,
1627                stride,
1628                dereferenceable,
1629            } => {
1630                let stride = *stride;
1631                if let (Some(base), Some(offset)) = (args.get(*base_arg), args.get(*offset_arg)) {
1632                    let stride_term = self.pointer_stride_term(dest, stride);
1633                    let adjusted_offset = if stride == Some(1) {
1634                        offset.z3_term.clone()
1635                    } else {
1636                        Int::mul(self.z3_ctx, &[&offset.z3_term, &stride_term])
1637                    };
1638                    let new_term = Int::add(self.z3_ctx, &[&base.z3_term, &adjusted_offset]);
1639                    let is_field_offset = offset.is_field_offset()
1640                        && base
1641                            .provenance
1642                            .as_ref()
1643                            .is_some_and(|p| p.offset.as_u64() == Some(0));
1644                    let element_offset = if stride == Some(1) {
1645                        None
1646                    } else {
1647                        match base.provenance.as_ref().and_then(|p| match &p.offset_kind {
1648                            Some(OffsetKind::Element(e)) => Some(e.clone()),
1649                            _ => None,
1650                        }) {
1651                            Some(e) => Some(Int::add(self.z3_ctx, &[&e, &offset.z3_term])),
1652                            None if base
1653                                .provenance
1654                                .as_ref()
1655                                .is_some_and(|p| p.offset.as_u64() == Some(0)) =>
1656                            {
1657                                Some(offset.z3_term.clone())
1658                            }
1659                            None => None,
1660                        }
1661                    };
1662                    let offset_kind = if is_field_offset {
1663                        Some(OffsetKind::Field)
1664                    } else if let Some(e) = element_offset {
1665                        Some(OffsetKind::Element(e))
1666                    } else {
1667                        Some(OffsetKind::Byte)
1668                    };
1669                    let adjusted_provenance = base.provenance.as_ref().map(|prov| Provenance {
1670                        alloc_id: prov.alloc_id,
1671                        offset: Int::add(self.z3_ctx, &[&prov.offset, &adjusted_offset]),
1672                        offset_kind,
1673                    });
1674                    let align_n = match stride {
1675                        Some(s) => self.compute_pointer_add_align(base, s),
1676                        None => base.facts.align_n.clone(),
1677                    };
1678                    let val = VmValue {
1679                        z3_term: new_term,
1680                        ty: self.body().local_decls[dest].ty,
1681                        provenance: adjusted_provenance,
1682                        facts: ValueFacts {
1683                            non_null: base.facts.non_null,
1684                            in_bounds: *dereferenceable,
1685                            align_n,
1686                            init: base.facts.init,
1687                        },
1688                        source: ValueSource::None,
1689                    };
1690                    self.set_local(dest, val);
1691                }
1692            }
1693            CallEffect::ReturnPointerSub {
1694                base_arg,
1695                offset_arg,
1696                stride,
1697            } => {
1698                let stride = *stride;
1699                if let (Some(base), Some(offset)) = (args.get(*base_arg), args.get(*offset_arg)) {
1700                    let stride_term = self.pointer_stride_term(dest, stride);
1701                    let scaled = if stride == Some(1) {
1702                        offset.z3_term.clone()
1703                    } else {
1704                        Int::mul(self.z3_ctx, &[&offset.z3_term, &stride_term])
1705                    };
1706                    let new_term = Int::sub(self.z3_ctx, &[&base.z3_term, &scaled]);
1707                    let element_offset = if stride == Some(1) {
1708                        None
1709                    } else {
1710                        match base.provenance.as_ref().and_then(|p| match &p.offset_kind {
1711                            Some(OffsetKind::Element(e)) => Some(e.clone()),
1712                            _ => None,
1713                        }) {
1714                            Some(e) => Some(Int::sub(self.z3_ctx, &[&e, &offset.z3_term])),
1715                            None => None,
1716                        }
1717                    };
1718                    let offset_kind = if let Some(e) = element_offset {
1719                        Some(OffsetKind::Element(e))
1720                    } else {
1721                        Some(OffsetKind::Byte)
1722                    };
1723                    let adjusted_provenance = base.provenance.as_ref().map(|prov| Provenance {
1724                        alloc_id: prov.alloc_id,
1725                        offset: Int::sub(self.z3_ctx, &[&prov.offset, &scaled]),
1726                        offset_kind,
1727                    });
1728                    let align_n = match stride {
1729                        Some(s) => self.compute_pointer_add_align(base, s),
1730                        None => base.facts.align_n.clone(),
1731                    };
1732                    let val = VmValue {
1733                        z3_term: new_term,
1734                        ty: self.body().local_decls[dest].ty,
1735                        provenance: adjusted_provenance,
1736                        facts: ValueFacts {
1737                            non_null: base.facts.non_null,
1738                            in_bounds: base.facts.in_bounds,
1739                            align_n,
1740                            init: base.facts.init,
1741                        },
1742                        source: ValueSource::None,
1743                    };
1744                    self.set_local(dest, val);
1745                }
1746            }
1747            CallEffect::ReturnNonZero => {
1748                let zero = Int::from_u64(self.z3_ctx, 0);
1749                if let Some(mut existing) = self.local_value(dest).cloned() {
1750                    existing.facts.non_null = true;
1751                    // Record the non-zero fact as a path condition so that a
1752                    // downstream `ValidNum(result != 0)` obligation (e.g.
1753                    // `NonZero::new_unchecked` after a bit-preserving operation)
1754                    // discharges against it.
1755                    self.constraints.assertions.push(existing.z3_term._eq(&zero).not());
1756                    self.set_local(dest, existing);
1757                } else {
1758                    let dest_ty = self.body().local_decls[dest].ty;
1759                    let term = self.fresh_int(&format!("ret_nz_{}", dest.as_usize()));
1760                    self.constraints.assertions.push(term._eq(&zero).not());
1761                    self.set_local(
1762                        dest,
1763                        VmValue {
1764                            z3_term: term,
1765                            ty: dest_ty,
1766                            provenance: None,
1767                            facts: ValueFacts {
1768                                non_null: true,
1769                                ..Default::default()
1770                            },
1771                            source: ValueSource::None,
1772                        },
1773                    );
1774                }
1775            }
1776            CallEffect::ReturnTupleFieldNonZero { field } => {
1777                let dest_ty = self.body().local_decls[dest].ty;
1778                if let TyKind::Tuple(elem_tys) = dest_ty.kind() {
1779                    if let Some(field_ty) = elem_tys.get(*field) {
1780                        let zero = Int::from_u64(self.z3_ctx, 0);
1781                        let term =
1782                            self.fresh_int(&format!("ret_tup_nz_{}_{}", dest.as_usize(), field));
1783                        self.constraints.assertions.push(term._eq(&zero).not());
1784                        self.set_field_value(
1785                            dest,
1786                            vec![*field],
1787                            VmValue {
1788                                z3_term: term,
1789                                ty: *field_ty,
1790                                provenance: None,
1791                                facts: ValueFacts {
1792                                    non_null: true,
1793                                    init: true,
1794                                    ..Default::default()
1795                                },
1796                                source: ValueSource::None,
1797                            },
1798                        );
1799                    }
1800                }
1801            }
1802            CallEffect::ReturnAligned => {
1803                if let Some(mut existing) = self.local_value(dest).cloned() {
1804                    // `as_ptr`/`as_mut_ptr`/`into_raw` expose a pointer aligned to
1805                    // the *pointee* type, so record the symbolic alignment for the
1806                    // downstream `raw-ptr-deref`/`from_raw_parts` `Align` check.
1807                    if existing.facts.align_n.is_none() {
1808                        let dest_ty = self.body().local_decls[dest].ty;
1809                        if let Some(pointee) = crate::helpers::mir_utils::pointee_ty(dest_ty) {
1810                            let a = self.align_sym(pointee);
1811                            if a.simplify().as_u64() != Some(1) {
1812                                existing.facts.align_n = Some(a);
1813                            }
1814                        }
1815                    }
1816                    self.set_local(dest, existing);
1817                } else {
1818                    let dest_ty = self.body().local_decls[dest].ty;
1819                    let term = self.fresh_int(&format!("ret_align_{}", dest.as_usize()));
1820                    self.set_local(
1821                        dest,
1822                        VmValue {
1823                            z3_term: term,
1824                            ty: dest_ty,
1825                            provenance: None,
1826                            facts: ValueFacts {
1827                                ..Default::default()
1828                            },
1829                            source: ValueSource::None,
1830                        },
1831                    );
1832                }
1833            }
1834            CallEffect::ReturnLengthOfArg { arg } => {
1835                if let Some(arg_val) = args.get(*arg) {
1836                    // For Iter / IterMut, compute len from struct fields
1837                    // (ptr + end_or_len with shared allocation) instead of
1838                    // the generic sizeof(Iter)/sizeof(T) heuristic.
1839                    if self.interpreter_iter_len(arg_val, dest) {
1840                        return;
1841                    }
1842                }
1843                // Field-read `len` (e.g. `Vec::len`) is handled by
1844                // `ReturnFieldOfArg`; here fall back to `size / elem_size`
1845                // (slices, `&str`, and legacy Vec values).
1846                if let Some(arg_val) = args.get(*arg) {
1847                    if self.set_len_from_alloc(arg_val, dest) {
1848                        return;
1849                    }
1850                }
1851                let dest_ty = self.body().local_decls[dest].ty;
1852                let term = self.fresh_int(&format!("len_{}", dest.as_usize()));
1853                let val = VmValue::new(term, dest_ty);
1854                self.set_local(dest, val);
1855            }
1856            CallEffect::ReturnFieldOfArg { arg, field } => {
1857                self.apply_field_of_arg_effect(*arg, *field, None, args, caller_arg_locals, dest);
1858            }
1859            CallEffect::ReturnFieldOfArgSub { arg, field, offset } => {
1860                self.apply_field_of_arg_effect(
1861                    *arg,
1862                    *field,
1863                    Some(*offset),
1864                    args,
1865                    caller_arg_locals,
1866                    dest,
1867                );
1868            }
1869            CallEffect::ReturnConst { value } => {
1870                let dest_ty = self.body().local_decls[dest].ty;
1871                let term = Int::from_u64(self.z3_ctx, *value);
1872                let val = VmValue::new(term, dest_ty);
1873                self.set_local(dest, val);
1874            }
1875            CallEffect::ReturnAlignOffset { ptr_arg, align_arg } => {
1876                let dest_ty = self.body().local_decls[dest].ty;
1877                let offset = self.fresh_int(&format!("align_offset_{}", dest.as_usize()));
1878                if let (Some(ptr_val), Some(align_val)) = (args.get(*ptr_arg), args.get(*align_arg))
1879                {
1880                    // `ptr.align_offset(align)` returns the offset in *elements* of
1881                    // the pointee type (not bytes), so the aligned address is
1882                    // `ptr + offset * size_of::<pointee>()`.  Record that
1883                    // `(ptr + offset*elem) % align == 0` with `0 <= offset < align`
1884                    // so a downstream `*(ptr.add(offset) as *const U)` can
1885                    // discharge `Align`.
1886                    let elem = crate::helpers::mir_utils::pointee_ty(ptr_val.ty)
1887                        .map(|pointee| self.size_sym(pointee))
1888                        .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
1889                    let byte_off = Int::mul(self.z3_ctx, &[&offset, &elem]);
1890                    let ptr_plus_off = Int::add(self.z3_ctx, &[&ptr_val.z3_term, &byte_off]);
1891                    let zero = Int::from_u64(self.z3_ctx, 0);
1892                    self.constraints.assertions
1893                        .push(ptr_plus_off.rem(&align_val.z3_term)._eq(&zero));
1894                    self.constraints.assertions.push(offset.ge(&zero));
1895                    self.constraints.assertions.push(offset.lt(&align_val.z3_term));
1896                }
1897                let val = VmValue::new(offset, dest_ty);
1898                self.set_local(dest, val);
1899            }
1900            CallEffect::ReturnMin { lhs_arg, rhs_arg } => {
1901                // Build the min as a first-class `ite(lhs <= rhs, lhs, rhs)`
1902                // term rather than a fresh variable plus disjunction facts.
1903                // A fresh variable breaks downstream alignment/bounds
1904                // reasoning: e.g. `ptr.align_offset(8)` guarantees
1905                // `(ptr + offset) % 8 == 0`, but `offset.min(len)` would
1906                // then become an unrelated symbol and the `Align`/`InBound`
1907                // checks on `*(ptr.add(offset) as *const usize)` could no
1908                // longer discharge.  With an `ite`, the path conditions
1909                // (`offset < 8`, `len >= 16`) let the solver reduce
1910                // `ite(offset <= len, offset, len)` back to `offset`.
1911                self.apply_binary_num(dest, args, *lhs_arg, *rhs_arg, |lhs, rhs| {
1912                    lhs.le(rhs).ite(lhs, rhs)
1913                });
1914            }
1915            CallEffect::ReturnMax { lhs_arg, rhs_arg } => {
1916                self.apply_binary_num(dest, args, *lhs_arg, *rhs_arg, |lhs, rhs| {
1917                    lhs.ge(rhs).ite(lhs, rhs)
1918                });
1919            }
1920            CallEffect::ReturnClamp {
1921                value_arg,
1922                min_arg,
1923                max_arg,
1924            } => {
1925                if let (Some(v), Some(mn), Some(mx)) =
1926                    (args.get(*value_arg), args.get(*min_arg), args.get(*max_arg))
1927                {
1928                    let dest_ty = self.body().local_decls[dest].ty;
1929                    // clamp(v, mn, mx) = max(mn, min(v, mx))
1930                    let upper = v.z3_term.gt(&mx.z3_term).ite(&mx.z3_term, &v.z3_term);
1931                    let term = v.z3_term.lt(&mn.z3_term).ite(&mn.z3_term, &upper);
1932                    let val = VmValue::new(term, dest_ty);
1933                    self.set_local(dest, val);
1934                }
1935            }
1936            CallEffect::ReturnAbs { arg } => {
1937                self.apply_unary_num(dest, args, *arg, |a| {
1938                    let zero = Int::from_u64(self.z3_ctx, 0);
1939                    let neg = Int::sub(self.z3_ctx, &[&zero, a]);
1940                    a.ge(&zero).ite(a, &neg)
1941                });
1942            }
1943            CallEffect::ReturnNeg { arg } => {
1944                self.apply_unary_num(dest, args, *arg, |a| {
1945                    let zero = Int::from_u64(self.z3_ctx, 0);
1946                    Int::sub(self.z3_ctx, &[&zero, a])
1947                });
1948            }
1949            CallEffect::ReturnAdd { lhs_arg, rhs_arg } => {
1950                self.apply_binary_num(dest, args, *lhs_arg, *rhs_arg, |lhs, rhs| {
1951                    Int::add(self.z3_ctx, &[lhs, rhs])
1952                });
1953            }
1954            CallEffect::ReturnMul { lhs_arg, rhs_arg } => {
1955                self.apply_binary_num(dest, args, *lhs_arg, *rhs_arg, |lhs, rhs| {
1956                    Int::mul(self.z3_ctx, &[lhs, rhs])
1957                });
1958            }
1959            CallEffect::ReturnOptionSomeAdd { lhs_arg, rhs_arg } => {
1960                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1961                    // `checked_add` returns `Option<T>`; its `Some` payload is
1962                    // `lhs + rhs`. Store the payload term under field 0 so the
1963                    // `if let Some(payload)` projection resolves to it. The
1964                    // discriminant is left unconstrained, so both `Some`/`None`
1965                    // branches remain reachable.
1966                    let term = Int::add(self.z3_ctx, &[&lhs.z3_term, &rhs.z3_term]);
1967                    self.set_field_value(
1968                        dest,
1969                        vec![0],
1970                        VmValue::new(term, lhs.ty),
1971                    );
1972                }
1973            }
1974            CallEffect::ReturnOptionSomeMul { lhs_arg, rhs_arg } => {
1975                if let (Some(lhs), Some(rhs)) = (args.get(*lhs_arg), args.get(*rhs_arg)) {
1976                    let term = Int::mul(self.z3_ctx, &[&lhs.z3_term, &rhs.z3_term]);
1977                    self.set_field_value(
1978                        dest,
1979                        vec![0],
1980                        VmValue::new(term, lhs.ty),
1981                    );
1982                }
1983            }
1984            CallEffect::ReturnOptionSomeScanIndex { self_arg } => {
1985                // `Iterator::position`/`find` return `Option<usize>` whose `Some`
1986                // payload is a scan index into the iterator, so `0 <= i < self.len()`.
1987                // The receiver is `&mut iter` (a reference to the Iter/IterMut
1988                // struct), so resolve the reference to the iterator local it
1989                // points at (via its provenance = the iterator's stack alloc).
1990                // The iterator carries `ptr` (field 0) and `end_or_len`
1991                // (field 1); `len = end_or_len - ptr`.
1992                if let Some(iter_ref) = caller_arg_locals.get(*self_arg).copied().flatten() {
1993                    let iter_local = self
1994                        .local_value(iter_ref)
1995                        .and_then(|v| v.provenance_alloc_id())
1996                        .and_then(|alloc| {
1997                            self.current_frame.local_alloc
1998                                .iter()
1999                                .find(|(_, a)| **a == alloc)
2000                                .map(|(l, _)| *l)
2001                        });
2002                    let ptr_term =
2003                        iter_local.and_then(|l| self.field_value(l, &[0]).map(|v| v.z3_term.clone()));
2004                    let end_term =
2005                        iter_local.and_then(|l| self.field_value(l, &[1]).map(|v| v.z3_term.clone()));
2006                    if let (Some(ptr), Some(end)) = (ptr_term, end_term) {
2007                        let len = Int::sub(self.z3_ctx, &[&end, &ptr]);
2008                        let payload = self.fresh_int(&format!("scan_idx_{}", dest.as_usize()));
2009                        self.constraints.assertions.push(payload.lt(&len));
2010                        let dest_ty = self.body().local_decls[dest].ty;
2011                        let payload_ty = match dest_ty.kind() {
2012                            TyKind::Adt(adt, substs) if adt.is_enum() => substs.type_at(0),
2013                            _ => dest_ty,
2014                        };
2015                        self.set_field_value(
2016                            dest,
2017                            vec![0],
2018                            VmValue::new(payload, payload_ty),
2019                        );
2020                    }
2021                }
2022            }
2023            CallEffect::ReturnBranchPayload { arg } => {
2024                // `Try::branch`: copy the `Option` arg's `Some` payload (field 0)
2025                // to the `ControlFlow` result's `Continue` payload (field 0),
2026                // preserving its provenance so a `?`-operator unwrap survives.
2027                let arg_local = caller_arg_locals.get(*arg).copied().flatten();
2028                if let Some(l) = arg_local {
2029                    if let Some(payload) = self.field_value(l, &[0]).cloned() {
2030                        self.set_field_value(dest, vec![0], payload);
2031                    }
2032                }
2033            }
2034            CallEffect::ReturnOptionSomeIndexLtArgLen { arg } => {
2035                // `memchr(x, bytes)`/`memrchr(x, bytes)`-style search returns
2036                // `Option<usize>` whose `Some(i)` payload satisfies
2037                // `0 <= i < bytes.len()`.  Store the payload under field 0 (so
2038                // `if let Some(i)` resolves to it) and record both bounds so a
2039                // caller can re-prove `finger <= finger_back` after
2040                // `finger += i + 1` (forward) or `finger_back = finger + i`
2041                // (reverse).
2042                if let Some(slice) = args.get(*arg) {
2043                    if let Some(len) = self.slice_len_from_value(slice) {
2044                        let payload = self.fresh_int(&format!("scan_idx_{}", dest.as_usize()));
2045                        self.constraints.assertions.push(payload.lt(&len));
2046                        let zero = Int::from_u64(self.z3_ctx, 0);
2047                        self.constraints.assertions.push(payload.ge(&zero));
2048                        let dest_ty = self.body().local_decls[dest].ty;
2049                        let payload_ty = match dest_ty.kind() {
2050                            TyKind::Adt(adt, substs) if adt.is_enum() => substs.type_at(0),
2051                            _ => dest_ty,
2052                        };
2053                        self.set_field_value(
2054                            dest,
2055                            vec![0],
2056                            VmValue::new(payload, payload_ty),
2057                        );
2058                    }
2059                }
2060            }
2061            CallEffect::ReturnOptionSomeTupleFieldLeArgLen { field, arg } => {
2062                // UTF-8 decoder returns `Option<(.., len, ..)>` whose length
2063                // field satisfies `len <= slice.len()`.  Store the length under
2064                // `[0, field]` (the `Some` payload tuple's field) and record
2065                // `len <= arg.len()` so a caller can re-prove
2066                // `finger <= finger_back` after `finger += len`.
2067                if let Some(slice) = args.get(*arg) {
2068                    if let Some(arg_len) = self.slice_len_from_value(slice) {
2069                        let len = self.fresh_int(&format!("decode_len_{}", dest.as_usize()));
2070                        self.constraints.assertions.push(len.le(&arg_len));
2071                        let dest_ty = self.body().local_decls[dest].ty;
2072                        let payload_ty = match dest_ty.kind() {
2073                            TyKind::Adt(adt, substs) if adt.is_enum() => substs.type_at(0),
2074                            _ => dest_ty,
2075                        };
2076                        let field_ty = match payload_ty.kind() {
2077                            TyKind::Tuple(tys) => tys.get(*field).copied().unwrap_or(payload_ty),
2078                            _ => payload_ty,
2079                        };
2080                        self.set_field_value(
2081                            dest,
2082                            vec![0, *field],
2083                            VmValue::new(len, field_ty),
2084                        );
2085                    }
2086                }
2087            }
2088            CallEffect::ReturnScanLength => {
2089                // `strlen(ptr)` returns the byte length before the NUL
2090                // terminator. The `ValidCStr` invariant guarantees the NUL is
2091                // within `isize::MAX` bytes, so `len < isize::MAX`, and
2092                // `len + 1` (the length with the terminator) fits in
2093                // `isize::MAX` — discharging `from_raw_parts`'s
2094                // `ValidNum(size_of(T)*(len+1) <= isize::MAX)`.
2095                let len = self.fresh_int(&format!("strlen_{}", dest.as_usize()));
2096                let max = Int::from_i64(self.z3_ctx, i64::MAX);
2097                self.constraints.assertions.push(len.lt(&max));
2098                let dest_ty = self.body().local_decls[dest].ty;
2099                self.set_local(
2100                    dest,
2101                    VmValue::new(len, dest_ty),
2102                );
2103            }
2104            CallEffect::ReturnNonZeroIff { arg } => {
2105                if let Some(a) = args.get(*arg) {
2106                    let dest_ty = self.body().local_decls[dest].ty;
2107                    let zero = Int::from_u64(self.z3_ctx, 0);
2108                    let term = self.fresh_int(&format!("ret_nz_iff_{}", dest.as_usize()));
2109                    // `result == 0` iff `arg == 0`, i.e. non-zero is preserved
2110                    // exactly (bit-preserving ops map 0 -> 0, non-zero -> non-zero).
2111                    self.constraints.assertions
2112                        .push(term._eq(&zero)._eq(&a.z3_term._eq(&zero)));
2113                    self.set_local(
2114                        dest,
2115                        VmValue::new(term, dest_ty),
2116                    );
2117                }
2118            }
2119            CallEffect::ReturnOptionSomeNonZeroIff { arg } => {
2120                if let Some(a) = args.get(*arg) {
2121                    let zero = Int::from_u64(self.z3_ctx, 0);
2122                    let term = self.fresh_int(&format!("ret_opt_nz_iff_{}", dest.as_usize()));
2123                    self.constraints.assertions
2124                        .push(term._eq(&zero)._eq(&a.z3_term._eq(&zero)));
2125                    self.set_field_value(
2126                        dest,
2127                        vec![0],
2128                        VmValue::new(term, a.ty),
2129                    );
2130                }
2131            }
2132            CallEffect::ReturnOptionSomeNonZero => {
2133                // `Some` payload is unconditionally non-zero (e.g.
2134                // `checked_next_power_of_two`).
2135                let zero = Int::from_u64(self.z3_ctx, 0);
2136                let term = self.fresh_int(&format!("ret_opt_nz_{}", dest.as_usize()));
2137                self.constraints.assertions.push(term._eq(&zero).not());
2138                let payload_ty = args
2139                    .first()
2140                    .map(|a| a.ty)
2141                    .unwrap_or(self.body().local_decls[dest].ty);
2142                self.set_field_value(
2143                    dest,
2144                    vec![0],
2145                    VmValue::new(term, payload_ty),
2146                );
2147            }
2148            CallEffect::WriteMemory { pointer_arg } => {
2149                if let Some(arg_val) = args.get(*pointer_arg) {
2150                    if let Some(prov) = &arg_val.provenance {
2151                        // Writing a non-`u8` value through a byte buffer reinterprets
2152                        // it (e.g. `*mut FreeBlock` cast from a `Vec<u8>` buffer):
2153                        // update the allocation's element type so a later `Typed`
2154                        // invariant matches the written type.
2155                        if let rustc_middle::ty::TyKind::RawPtr(inner, _)
2156                        | rustc_middle::ty::TyKind::Ref(_, inner, _) = arg_val.ty.kind()
2157                        {
2158                            let cur = self.alloc(prov.alloc_id).element_ty.as_ty();
2159                            let is_u8 = |t: rustc_middle::ty::Ty<'_>| {
2160                                matches!(
2161                                    t.kind(),
2162                                    rustc_middle::ty::TyKind::Uint(rustc_middle::ty::UintTy::U8)
2163                                )
2164                            };
2165                            if let Some(c) = cur {
2166                                if is_u8(c) && !is_u8(*inner) {
2167                                    self.alloc_mut(prov.alloc_id).element_ty = ElementTy::Typed(*inner);
2168                                }
2169                            }
2170                        }
2171                        // For locally-created Vec-like types: create a heap data
2172                        // allocation on first mutation. (Param Vecs already have
2173                        // an external allocation set by init_parameters.)
2174                        let is_vec = crate::verify::api_classify::is_vec_push_or_reserve(callee);
2175                        let is_external = self.alloc(prov.alloc_id).is_external();
2176                        if is_vec && !is_external {
2177                            let elem_ty = match arg_val.ty.kind() {
2178                                TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) => {
2179                                    crate::verify::call_summary::vec_elem_ty(self.tcx, *inner)
2180                                }
2181                                _ => crate::verify::call_summary::vec_elem_ty(self.tcx, arg_val.ty),
2182                            };
2183                            let heap_align = elem_ty
2184                                .map(|ty| self.align_sym(ty))
2185                                .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
2186                            if let Some(old_data) =
2187                                self.container_data_alloc(prov.alloc_id, arg_val.ty)
2188                            {
2189                                // Subsequent mutation: invalidate old heap data.
2190                                self.alloc_mut(old_data).facts.dead = true;
2191                            }
2192                            let max_size = Int::from_u64(self.z3_ctx, i64::MAX as u64);
2193                            let (data_alloc, data_base) =
2194                                self.allocate_external(max_size, heap_align, elem_ty);
2195                            let container_ty = match arg_val.ty.kind() {
2196                                TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) => *inner,
2197                                _ => arg_val.ty,
2198                            };
2199                            self.set_container_data_field(
2200                                prov.alloc_id,
2201                                arg_val.ty,
2202                                data_alloc,
2203                                data_base,
2204                                elem_ty.unwrap_or(container_ty),
2205                            );
2206                        }
2207                        // When offset is concrete, only mark the bytes actually
2208                        // written. For symbolic offsets, mark entire allocation.
2209                        let off_u64 = prov
2210                            .offset
2211                            .as_u64()
2212                            .or_else(|| prov.offset.simplify().as_u64());
2213                        if let Some(off) = off_u64 {
2214                            if off == 0 {
2215                                self.content_mut(prov.alloc_id).facts.initialized = true;
2216                            }
2217                            let elem_size = match arg_val.ty.kind() {
2218                                rustc_middle::ty::TyKind::Ref(_, inner, _) => {
2219                                    self.size_of_ty(*inner) as usize
2220                                }
2221                                _ => 0,
2222                            };
2223                            let write_size = if elem_size > 0 {
2224                                elem_size
2225                            } else {
2226                                self.allocation_size(prov.alloc_id).as_u64().unwrap_or(0) as usize
2227                            };
2228                            let end = (off as usize + write_size).min(4096);
2229                            for byte_off in (off as usize)..end {
2230                                self.mark_byte_init(prov.alloc_id, byte_off);
2231                            }
2232                        } else {
2233                            // Symbolic write offset: the exact written element
2234                            // can't be tracked per-byte. For concrete allocation
2235                            // sizes, mark every byte (as before). For unknown /
2236                            // zero sizes — generic element types such as
2237                            // `MaybeUninit<T>` inside `[MaybeUninit<T>; N]` —
2238                            // mark the whole allocation initialized so a later
2239                            // `assume_init_read`/`assume_init_drop` can discharge
2240                            // `Init` on those (fully initialized) elements.
2241                            let size_val = self.allocation_size(prov.alloc_id).as_u64();
2242                            match size_val {
2243                                Some(sz) if sz > 0 => {
2244                                    for off in 0..(sz as usize).min(1024) {
2245                                        self.mark_byte_init(prov.alloc_id, off);
2246                                    }
2247                                }
2248                                _ => {
2249                                    self.content_mut(prov.alloc_id).facts.initialized = true;
2250                                }
2251                            }
2252                        }
2253                    }
2254                }
2255            }
2256            CallEffect::ReturnFreshAllocation {
2257                pointer_arg,
2258                size_arg,
2259                elem_size,
2260            } => {
2261                if let (Some(ptr_val), Some(size_val)) =
2262                    (args.get(*pointer_arg), args.get(*size_arg))
2263                {
2264                    let dest_ty = self.body().local_decls[dest].ty;
2265                    let elem_ty = crate::verify::call_summary::from_raw_parts_elem_ty(
2266                        self.tcx,
2267                        self.current_frame.current_def_id,
2268                        Some(dest),
2269                    );
2270                    // A generic element type uses the shared symbolic `sizeof_T`
2271                    // so the fresh allocation's size stays consistent with ptr
2272                    // strides and `InBound` cancels the factor.
2273                    let elem_sz_term = if *elem_size == 0 {
2274                        self.size_sym(elem_ty.unwrap_or(dest_ty))
2275                    } else {
2276                        Int::from_u64(self.z3_ctx, *elem_size)
2277                    };
2278                    let heap_align = elem_ty
2279                        .map(|ty| self.align_sym(ty))
2280                        .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
2281                    let (alloc_id, base) = self.allocate_slice(
2282                        size_val.z3_term.clone(),
2283                        elem_sz_term.clone(),
2284                        heap_align,
2285                        elem_ty,
2286                    );
2287                    let prov = Provenance {
2288                        alloc_id,
2289                        offset: Int::from_u64(self.z3_ctx, 0),
2290                        offset_kind: None,
2291                    };
2292                    // Propagate init status and byte-level tracking from the source pointer.
2293                    if let Some(ref source_prov) = ptr_val.provenance {
2294                        if !self.alloc(source_prov.alloc_id).facts.dead {
2295                            self.content_mut(alloc_id).facts.initialized = true;
2296                            self.alloc_mut(alloc_id).parent = Some(source_prov.alloc_id);
2297                        }
2298                        // Copy byte-level tracking (value, init, NUL knowledge),
2299                        // shifting by the source pointer's byte offset so a
2300                        // non-zero-offset sub-slice (`from_raw_parts(ptr.add(k),
2301                        // n)`) inherits the right per-byte state.
2302                        let src_offset = source_prov
2303                            .offset
2304                            .simplify()
2305                            .as_u64()
2306                            .map(|v| v as usize)
2307                            .unwrap_or(0);
2308                        self.copy_byte_tracking(source_prov.alloc_id, src_offset, alloc_id);
2309                    }
2310                    let result_align_n = ptr_val.facts.align_n.clone().or_else(|| {
2311                        ptr_val
2312                            .provenance
2313                            .as_ref()
2314                            .map(|p| self.alloc(p.alloc_id).align.clone())
2315                    });
2316                    let vec_base = base.clone();
2317                    let vec_prov = prov.clone();
2318                    let vec_len = size_val.z3_term.clone();
2319                    self.set_local(
2320                        dest,
2321                        VmValue {
2322                            z3_term: base,
2323                            ty: dest_ty,
2324                            provenance: Some(prov),
2325                            facts: ValueFacts {
2326                                non_null: true,
2327                                init: true,
2328                                in_bounds: true,
2329                                align_n: result_align_n.clone(),
2330                                ..ValueFacts::default()
2331                            },
2332                            source: ValueSource::None,
2333                        },
2334                    );
2335                    // Materialize `{ptr, cap, len}` fields for a Vec destination
2336                    // (`from_raw_parts` sets cap == len).
2337                    if let rustc_middle::ty::TyKind::Adt(adt_def, _) = dest_ty.kind() {
2338                        if api_classify::is_std_vec(adt_def.did()) {
2339                            let ptr_field = VmValue {
2340                                z3_term: vec_base,
2341                                ty: ptr_val.ty,
2342                                provenance: Some(vec_prov),
2343                                facts: ValueFacts {
2344                                    non_null: true,
2345                                    init: true,
2346                                    in_bounds: true,
2347                                    align_n: result_align_n,
2348                                    ..ValueFacts::default()
2349                                },
2350                                source: ValueSource::None,
2351                            };
2352                            self.materialize_vec_fields(dest, ptr_field, vec_len.clone(), vec_len);
2353                        }
2354                    }
2355                }
2356            }
2357            CallEffect::ReturnBoxAllocation => {
2358                let dest_ty = self.body().local_decls[dest].ty;
2359                // `pointee_ty` doesn't unwrap `Box`; extract its `T` from the
2360                // first generic argument so the heap allocation is sized to the
2361                // pointee and (below) the inner `Unique<T>.pointer` field can be
2362                // exposed.
2363                let pointee = crate::helpers::mir_utils::pointee_ty(dest_ty).or_else(|| {
2364                    if let rustc_middle::ty::TyKind::Adt(adt, substs) = dest_ty.kind() {
2365                        if api_classify::is_std_box(adt.did()) {
2366                            substs.first().and_then(|s| s.as_type())
2367                        } else {
2368                            None
2369                        }
2370                    } else {
2371                        None
2372                    }
2373                });
2374                let size = pointee
2375                    .map(|ty| self.size_sym(ty))
2376                    .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
2377                let align = pointee
2378                    .map(|ty| self.align_sym(ty))
2379                    .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
2380                let (alloc_id, base) = self.allocate(size, align, pointee);
2381                self.content_mut(alloc_id).facts.initialized = true;
2382                let align_n = pointee.map(|ty| self.align_sym(ty));
2383                let heap_prov = Provenance {
2384                    alloc_id,
2385                    offset: Int::from_u64(self.z3_ctx, 0),
2386                    offset_kind: None,
2387                };
2388                // Expose `Box`'s inner `Unique<T>.pointer` (`NonNull<T>` at path
2389                // `[0, 0]`) so inlined `Box::as_ptr`/`as_mut_ptr` bodies — which
2390                // read `(_1.0).0` and cast it to `*const`/`*mut T` — inherit the
2391                // heap pointer's provenance (rustc 1.95 lowers `&raw **b` to
2392                // exactly this field read + transmute).  Record it both on the
2393                // local's stack slot (`set_field_value`, for direct `_1.0.0`
2394                // reads) and on the heap allocation (`MemoryContent::values`, for
2395                // `(*&box).0.0` deref-reads through a reborrow).
2396                let nn_field = VmValue {
2397                    z3_term: base.clone(),
2398                    ty: dest_ty,
2399                    provenance: Some(heap_prov.clone()),
2400                    facts: ValueFacts {
2401                        non_null: true,
2402                        init: true,
2403                        ..Default::default()
2404                    },
2405                    source: ValueSource::None,
2406                };
2407                let nn_path = self
2408                    .container_ptr_field(dest_ty)
2409                    .map(|(p, _)| p)
2410                    .expect("Box has no owning pointer field");
2411                self.set_field_value(dest, nn_path.clone(), nn_field.clone());
2412                self.units[alloc_id.0]
2413                    .content
2414                    .values
2415                    .insert((dest_ty, nn_path), nn_field);
2416                self.set_local(
2417                    dest,
2418                    VmValue {
2419                        z3_term: base.clone(),
2420                        ty: dest_ty,
2421                        provenance: Some(Provenance {
2422                            alloc_id,
2423                            offset: Int::from_u64(self.z3_ctx, 0),
2424                            offset_kind: None,
2425                        }),
2426                        facts: ValueFacts {
2427                            non_null: true,
2428                            init: true,
2429                            in_bounds: true,
2430                            align_n,
2431                        },
2432                        source: ValueSource::None,
2433                    },
2434                );
2435            }
2436            CallEffect::ReturnExchangeMalloc { size_arg } => {
2437                if let Some(size_val) = args.get(*size_arg) {
2438                    let dest_ty = self.body().local_decls[dest].ty;
2439                    let u8_ty = self.tcx.types.u8;
2440                    let (alloc_id, base) = self.allocate_external(
2441                        size_val.z3_term.clone(),
2442                        Int::from_u64(self.z3_ctx, 1),
2443                        Some(u8_ty),
2444                    );
2445                    self.alloc_mut(alloc_id).set_slice_len(size_val.z3_term.clone());
2446                    self.content_mut(alloc_id).facts.initialized = true;
2447                    self.set_local(
2448                        dest,
2449                        VmValue {
2450                            z3_term: base,
2451                            ty: dest_ty,
2452                            provenance: Some(Provenance {
2453                                alloc_id,
2454                                offset: Int::from_u64(self.z3_ctx, 0),
2455                                offset_kind: None,
2456                            }),
2457                            facts: ValueFacts {
2458                                non_null: true,
2459                                init: true,
2460                                in_bounds: true,
2461                                ..ValueFacts::default()
2462                            },
2463                            source: ValueSource::None,
2464                        },
2465                    );
2466                }
2467            }
2468            CallEffect::ReturnNewAllocation {
2469                size_arg,
2470                elem_size,
2471            } => {
2472                if let Some(size_val) = args.get(*size_arg) {
2473                    let dest_ty = self.body().local_decls[dest].ty;
2474                    let elem_ty = crate::verify::call_summary::vec_elem_ty(self.tcx, dest_ty);
2475                    // A generic element type (`elem_size == 0`) uses the shared
2476                    // symbolic `sizeof_T` so the allocation size stays consistent
2477                    // with pointer strides (mirrors `ReturnFreshAllocation`).
2478                    let elem_sz = if *elem_size == 0 {
2479                        self.size_sym(elem_ty.unwrap_or(dest_ty))
2480                    } else {
2481                        Int::from_u64(self.z3_ctx, *elem_size)
2482                    };
2483                    let total = Int::mul(self.z3_ctx, &[&size_val.z3_term, &elem_sz]);
2484                    let heap_align = elem_ty
2485                        .map(|ty| self.align_sym(ty))
2486                        .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
2487                    let (alloc_id, base) = self.allocate_external(total, heap_align, elem_ty);
2488                    self.alloc_mut(alloc_id).set_slice_len(size_val.z3_term.clone());
2489                    let dest_alloc_id = self.current_frame.local_alloc.get(&dest).copied();
2490                    self.content_mut(alloc_id).facts.initialized = true;
2491                    let vec_base = base.clone();
2492                    let vec_len = size_val.z3_term.clone();
2493                    self.set_local(
2494                        dest,
2495                        VmValue {
2496                            z3_term: base,
2497                            ty: dest_ty,
2498                            provenance: dest_alloc_id.map(|stack_id| Provenance {
2499                                alloc_id: stack_id,
2500                                offset: Int::from_u64(self.z3_ctx, 0),
2501                                offset_kind: None,
2502                            }),
2503                            facts: ValueFacts {
2504                                non_null: true,
2505                                init: true,
2506                                in_bounds: true,
2507                                ..ValueFacts::default()
2508                            },
2509                            source: ValueSource::None,
2510                        },
2511                    );
2512                    // `Vec::from_elem`/`from_elem`-style constructors set
2513                    // len == cap == count.
2514                    if let rustc_middle::ty::TyKind::Adt(adt_def, _) = dest_ty.kind() {
2515                        if api_classify::is_std_vec(adt_def.did()) {
2516                            let ptr_field = VmValue {
2517                                z3_term: vec_base,
2518                                ty: elem_ty.unwrap_or(dest_ty),
2519                                provenance: Some(Provenance {
2520                                    alloc_id,
2521                                    offset: Int::from_u64(self.z3_ctx, 0),
2522                                    offset_kind: None,
2523                                }),
2524                                facts: ValueFacts {
2525                                    non_null: true,
2526                                    init: true,
2527                                    in_bounds: true,
2528                                    ..ValueFacts::default()
2529                                },
2530                                source: ValueSource::None,
2531                            };
2532                            self.materialize_vec_fields(dest, ptr_field, vec_len.clone(), vec_len);
2533                        }
2534                    }
2535                }
2536            }
2537            CallEffect::ReturnNewAllocationFromCap { cap_arg, elem_size } => {
2538                if let Some(cap_val) = args.get(*cap_arg) {
2539                    let dest_ty = self.body().local_decls[dest].ty;
2540                    let elem_ty = crate::verify::call_summary::vec_elem_ty(self.tcx, dest_ty);
2541                    // A generic element type (`elem_size == 0`) uses the shared
2542                    // symbolic `sizeof_T` so the allocation size stays consistent
2543                    // with pointer strides (mirrors `ReturnFreshAllocation`).
2544                    let elem_sz = if *elem_size == 0 {
2545                        self.size_sym(elem_ty.unwrap_or(dest_ty))
2546                    } else {
2547                        Int::from_u64(self.z3_ctx, *elem_size)
2548                    };
2549                    let total = Int::mul(self.z3_ctx, &[&cap_val.z3_term, &elem_sz]);
2550                    let heap_align = elem_ty
2551                        .map(|ty| self.align_sym(ty))
2552                        .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
2553                    let (alloc_id, base) = self.allocate_external(total, heap_align, elem_ty);
2554                    let dest_alloc_id = self.current_frame.local_alloc.get(&dest).copied();
2555                    self.content_mut(alloc_id).facts.initialized = true;
2556                    let vec_base = base.clone();
2557                    let vec_cap = cap_val.z3_term.clone();
2558                    self.set_local(
2559                        dest,
2560                        VmValue {
2561                            z3_term: base,
2562                            ty: dest_ty,
2563                            provenance: dest_alloc_id.map(|stack_id| Provenance {
2564                                alloc_id: stack_id,
2565                                offset: Int::from_u64(self.z3_ctx, 0),
2566                                offset_kind: None,
2567                            }),
2568                            facts: ValueFacts {
2569                                non_null: true,
2570                                init: true,
2571                                in_bounds: true,
2572                                ..ValueFacts::default()
2573                            },
2574                            source: ValueSource::None,
2575                        },
2576                    );
2577                    // `Vec::with_capacity(n)`: len == 0, cap == n.
2578                    if let rustc_middle::ty::TyKind::Adt(adt_def, _) = dest_ty.kind() {
2579                        if api_classify::is_std_vec(adt_def.did()) {
2580                            let ptr_field = VmValue {
2581                                z3_term: vec_base,
2582                                ty: elem_ty.unwrap_or(dest_ty),
2583                                provenance: Some(Provenance {
2584                                    alloc_id,
2585                                    offset: Int::from_u64(self.z3_ctx, 0),
2586                                    offset_kind: None,
2587                                }),
2588                                facts: ValueFacts {
2589                                    non_null: true,
2590                                    init: true,
2591                                    in_bounds: true,
2592                                    ..ValueFacts::default()
2593                                },
2594                                source: ValueSource::None,
2595                            };
2596                            let zero = Int::from_u64(self.z3_ctx, 0);
2597                            self.materialize_vec_fields(dest, ptr_field, vec_cap, zero);
2598                        }
2599                    }
2600                }
2601            }
2602            CallEffect::ReturnNewAllocationFromBox => {
2603                // Box→Vec conversion (into_vec, box_assume_init_into_vec_unsafe)
2604                // and `slice::to_vec` (a fresh copy of a slice).
2605                self.ensure_local_allocation(dest);
2606                let dest_ty = self.body().local_decls[dest].ty;
2607                let elem_ty = crate::verify::call_summary::vec_elem_ty(self.tcx, dest_ty);
2608                let heap_align = elem_ty
2609                    .map(|ty| self.align_sym(ty))
2610                    .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
2611                // Use the receiver's slice length as the allocation size when
2612                // known (`to_vec`/`into_vec`), else a symbolic upper bound.
2613                let known_len = args
2614                    .first()
2615                    .and_then(|v| v.provenance.as_ref())
2616                    .and_then(|p| {
2617                        let alloc = self.alloc(p.alloc_id);
2618                        if let Some(slice_len) = alloc.slice_len().cloned() {
2619                            return Some(slice_len);
2620                        }
2621                        // A boxed array (`box [T; N]`) has a concrete size but no
2622                        // slice length; its element count is `size / elem_size`.
2623                        let elem_size = elem_ty.map(|t| self.size_of_ty(t)).unwrap_or(1);
2624                        let n = alloc.size.as_u64()?;
2625                        if elem_size > 0 {
2626                            Some(Int::from_u64(self.z3_ctx, n / elem_size))
2627                        } else {
2628                            None
2629                        }
2630                    });
2631                let size = known_len
2632                    .clone()
2633                    .unwrap_or_else(|| Int::from_u64(self.z3_ctx, i64::MAX as u64));
2634                let (alloc_id, base) = self.allocate_external(size, heap_align, elem_ty);
2635                // Copy the boxed slice's tracked byte values into the fresh Vec
2636                // buffer so byte-level checkers (`ValidCStr`/`ValidString`) can
2637                // reason over the copied contents.
2638                if let Some(box_alloc) = args.first().and_then(|v| v.provenance_alloc_id()) {
2639                    self.copy_byte_tracking(box_alloc, 0, alloc_id);
2640                }
2641                let dest_alloc_id = self.current_frame.local_alloc.get(&dest).copied();
2642                self.content_mut(alloc_id).facts.initialized = true;
2643                let vec_base = base.clone();
2644                self.set_local(
2645                    dest,
2646                    VmValue {
2647                        z3_term: base,
2648                        ty: dest_ty,
2649                        provenance: dest_alloc_id.map(|stack_id| Provenance {
2650                            alloc_id: stack_id,
2651                            offset: Int::from_u64(self.z3_ctx, 0),
2652                            offset_kind: None,
2653                        }),
2654                        facts: ValueFacts {
2655                            non_null: true,
2656                            init: true,
2657                            in_bounds: true,
2658                            ..ValueFacts::default()
2659                        },
2660                        source: ValueSource::None,
2661                    },
2662                );
2663                // `into_vec` / `box_assume_init_into_vec_unsafe`: the Vec's
2664                // length equals the source boxed slice's length (symbolic);
2665                // cap == len (no spare capacity).
2666                if let rustc_middle::ty::TyKind::Adt(adt_def, _) = dest_ty.kind() {
2667                    if api_classify::is_std_vec(adt_def.did()) {
2668                        let ptr_field = VmValue {
2669                            z3_term: vec_base,
2670                            ty: elem_ty.unwrap_or(dest_ty),
2671                            provenance: Some(Provenance {
2672                                alloc_id,
2673                                offset: Int::from_u64(self.z3_ctx, 0),
2674                                offset_kind: None,
2675                            }),
2676                            facts: ValueFacts {
2677                                non_null: true,
2678                                init: true,
2679                                in_bounds: true,
2680                                ..ValueFacts::default()
2681                            },
2682                            source: ValueSource::None,
2683                        };
2684                        let len_term = known_len
2685                            .clone()
2686                            .unwrap_or_else(|| self.fresh_int(&format!("vec_len_{}", dest.as_usize())));
2687                        self.materialize_vec_fields(dest, ptr_field, len_term.clone(), len_term);
2688                    }
2689                }
2690            }
2691            CallEffect::ReturnBoxFromVec { arg } => {
2692                if let Some(vec_val) = args.get(*arg) {
2693                    if let Some(ref prov) = vec_val.provenance {
2694                        if let Some(heap_alloc_id) =
2695                            self.container_data_alloc(prov.alloc_id, vec_val.ty)
2696                        {
2697                            let heap_base = self.allocation_base(heap_alloc_id).clone();
2698                            let dest_ty = self.body().local_decls[dest].ty;
2699                            self.set_local(
2700                                dest,
2701                                VmValue {
2702                                    z3_term: heap_base,
2703                                    ty: dest_ty,
2704                                    provenance: Some(Provenance {
2705                                        alloc_id: heap_alloc_id,
2706                                        offset: Int::from_u64(self.z3_ctx, 0),
2707                                        offset_kind: None,
2708                                    }),
2709                                    facts: ValueFacts {
2710                                        non_null: true,
2711                                        init: true,
2712                                        in_bounds: true,
2713                                        ..ValueFacts::default()
2714                                    },
2715                                    source: ValueSource::None,
2716                                },
2717                            );
2718                        }
2719                    }
2720                }
2721            }
2722            CallEffect::OwnsInitMemory { arg } => {
2723                if let Some(arg_val) = args.get(*arg) {
2724                    if let Some(prov) = &arg_val.provenance {
2725                        self.content_mut(prov.alloc_id).facts.initialized = true;
2726                    }
2727                    let mut val = arg_val.clone();
2728                    val.ty = self.body().local_decls[dest].ty;
2729                    val.facts.init = true;
2730                    val.facts.non_null = true;
2731                    // `Box::from_raw`/`from_raw_in` reconstruct a Box whose
2732                    // `Unique<T>.pointer` (`NonNull<T>` at path `[0, 0]`) must
2733                    // carry the same provenance: rustc 1.95 lowers `Box::as_ptr`
2734                    // (`&raw **b`) to a `(_1.0).0` field read + transmute, so
2735                    // without this the re-derived pointer loses provenance.
2736                    if let rustc_middle::ty::TyKind::Adt(adt, _) = val.ty.kind() {
2737                        if api_classify::is_std_box(adt.did()) {
2738                            let nn_path = self
2739                                .container_ptr_field(val.ty)
2740                                .map(|(p, _)| p)
2741                                .expect("Box has no owning pointer field");
2742                            self.set_field_value(dest, nn_path.clone(), val.clone());
2743                            if let Some(prov) = &val.provenance {
2744                                self.units[prov.alloc_id.0]
2745                                    .content
2746                                    .values
2747                                    .insert((val.ty, nn_path), val.clone());
2748                            }
2749                        }
2750                    }
2751                    self.set_local(dest, val);
2752                }
2753            }
2754            CallEffect::DropMemory { pointer_arg } => {
2755                // `ManuallyDrop::drop(slot)` / `drop_in_place(x)` frees the heap
2756                // allocation behind the argument. A reference/raw-pointer
2757                // argument carries the *stack* provenance of the referent
2758                // (penetrate to its heap field); a value argument (`Box`/`Vec`)
2759                // carries the heap provenance directly. Mark the allocation
2760                // dead; a second drop of an already-dead allocation is detected
2761                // downstream via `dead` alone.
2762                if let Some(arg_val) = args.get(*pointer_arg) {
2763                    let alloc_id = if matches!(
2764                        arg_val.ty.kind(),
2765                        rustc_middle::ty::TyKind::Ref(..)
2766                            | rustc_middle::ty::TyKind::RawPtr(..)
2767                    ) {
2768                        self.find_local_by_address(&arg_val.z3_term)
2769                            .and_then(|r| self.owner_ptr_field(r))
2770                            .and_then(|v| v.provenance_alloc_id())
2771                    } else {
2772                        arg_val.provenance_alloc_id()
2773                    };
2774                    if let Some(alloc_id) = alloc_id {
2775                        self.alloc_mut(alloc_id).facts.dead = true;
2776                    }
2777                }
2778            }
2779            CallEffect::ReturnPowerOfTwo => {
2780                // `Layout::align()` returns the layout's alignment, which is a
2781                // non-zero power of two. `Layout::align` inlines to
2782                // `self.align.as_usize()`, whose transmute-based body drops the
2783                // `NonZero` provenance; re-establish the non-zero fact (and the
2784                // power-of-two fact) with a fresh symbol so downstream
2785                // `from_size_align_unchecked` can discharge `align != 0` (its
2786                // `(align & (align - 1)) == 0` check is otherwise vacuously
2787                // proved, since contract-level `BitAnd` is unsupported).
2788                let dest_ty = self.body().local_decls[dest].ty;
2789                let term = self.fresh_int(&format!("layout_align_{}", dest.as_usize()));
2790                let zero = Int::from_u64(self.z3_ctx, 0);
2791                self.constraints.assertions.push(term.gt(&zero));
2792                self.set_local(
2793                    dest,
2794                    VmValue::new(term, dest_ty),
2795                );
2796            }
2797            CallEffect::ChecksIndexBoundsDisjoint {
2798                indices_arg,
2799                len_arg,
2800            } => {
2801                let indices = args.get(*indices_arg);
2802                let len_val = args.get(*len_arg);
2803                if let (Some(indices_val), Some(len_val)) = (indices, len_val) {
2804                    let arr_ty = match indices_val.ty.kind() {
2805                        rustc_middle::ty::TyKind::Ref(_, inner, _) => *inner,
2806                        _ => indices_val.ty,
2807                    };
2808                    if let rustc_middle::ty::TyKind::Array(_elem_ty, _const_len) = arr_ty.kind() {
2809                        let alloc_id = indices_val.provenance_alloc_id().or_else(|| {
2810                            // Slicer may have dropped the &indices
2811                            // assignment, losing provenance.  Fall back
2812                            self.all_local_values().into_iter().find_map(|(_, v)| {
2813                                if v.ty == arr_ty {
2814                                    v.provenance_alloc_id()
2815                                } else {
2816                                    None
2817                                }
2818                            })
2819                        });
2820                        if let Some(alloc_id) = alloc_id {
2821                            self.path_facts.has_checked_bounds = true;
2822                            let zero = Int::from_u64(self.z3_ctx, 0);
2823                            let byte_offsets: Vec<(usize, Int)> =
2824                                self.alloc_byte_values(alloc_id);
2825                            for (_, term) in &byte_offsets {
2826                                self.constraints.assertions.push(term.ge(&zero));
2827                                self.constraints.assertions.push(term.lt(&len_val.z3_term));
2828                            }
2829                            for i in 0..byte_offsets.len() {
2830                                for j in (i + 1)..byte_offsets.len() {
2831                                    let ti = &byte_offsets[i].1;
2832                                    let tj = &byte_offsets[j].1;
2833                                    self.constraints.assertions.push(ti._eq(tj).not());
2834                                }
2835                            }
2836                        }
2837                    }
2838                }
2839                let dest_ty = self.body().local_decls[dest].ty;
2840                let term = self.fresh_int(&format!("ck_ok_{}", dest.as_usize()));
2841                self.set_local(
2842                    dest,
2843                    VmValue::new(term, dest_ty),
2844                );
2845            }
2846        }
2847    }
2848
2849    /// Compute the preserved alignment when doing `base + offset * stride`.
2850    /// Pointer arithmetic only ever *preserves* the base's alignment; it never
2851    /// creates it. When the base's alignment is unknown, we cannot conclude
2852    /// anything about the result (a `wrapping_add` over misaligned storage does
2853    /// not become aligned just because the stride is a power of two).
2854    fn compute_pointer_add_align(
2855        &self,
2856        base: &VmValue<'z3, 'tcx>,
2857        stride_bytes: u64,
2858    ) -> Option<Int<'z3>> {
2859        let base_align = base.facts.align_n.as_ref()?;
2860        // Concrete alignment: the result stays n-aligned only if the stride is
2861        // a multiple of n.  A symbolic alignment can't be decided against a
2862        // concrete stride, so drop it here (the `check_align` SMT query
2863        // re-derives alignment from the allocation's align and the
2864        // `sizeof_T % align_T == 0` layout constraint).
2865        let Some(n) = base_align.simplify().as_u64() else {
2866            return None;
2867        };
2868        if stride_bytes > 0 && stride_bytes.is_multiple_of(n) {
2869            return Some(base_align.clone());
2870        }
2871        None
2872    }
2873
2874    /// The byte stride for a pointer add/sub: the fixed `stride`, or the pointee's
2875    /// symbolic size when the stride is element-sized (a generic `T`).
2876    fn pointer_stride_term(&mut self, dest: Local, stride: Option<u64>) -> Int<'z3> {
2877        match stride {
2878            Some(s) => Int::from_u64(self.z3_ctx, s),
2879            None => {
2880                let dest_ty = self.body().local_decls[dest].ty;
2881                let pointee = crate::helpers::mir_utils::pointee_ty(dest_ty).unwrap_or(dest_ty);
2882                self.size_sym(pointee)
2883            }
2884        }
2885    }
2886
2887    pub(crate) fn propagate_const_bytes_to_tracked(&mut self, args: &[Spanned<Operand<'tcx>>]) {
2888        let mut const_bytes: Option<(Vec<u8>, usize)> = None;
2889        let mut tracked_alloc: Option<AllocId> = None;
2890        let mut tracked_offset: usize = 0;
2891
2892        for (i, arg) in args.iter().enumerate() {
2893            let arg_val = self.value_of_operand(&arg.node);
2894            if const_bytes.is_none() {
2895                let bytes_opt = crate::helpers::mir_utils::const_operand_bytes(self.tcx, &arg.node)
2896                    .or_else(|| self.trace_to_const_bytes(&arg.node));
2897                if let Some(bytes) = bytes_opt {
2898                    const_bytes = Some((bytes, i));
2899                }
2900            }
2901            if tracked_alloc.is_none() {
2902                if let Some(alloc_id) = arg_val.provenance_alloc_id() {
2903                    tracked_alloc = Some(alloc_id);
2904                    if let Some(ref prov) = arg_val.provenance {
2905                        tracked_offset = prov.offset.as_u64().map(|v| v as usize).unwrap_or(0);
2906                    }
2907                }
2908            }
2909        }
2910
2911        if let (Some((bytes, _)), Some(alloc_id)) = (const_bytes, tracked_alloc) {
2912            for (j, &b) in bytes.iter().enumerate() {
2913                let off = tracked_offset + j;
2914                self.record_byte_value(alloc_id, off, Int::from_u64(self.z3_ctx, b as u64));
2915            }
2916            self.content_mut(alloc_id).facts.initialized = true;
2917        }
2918    }
2919
2920    /// Element size of the type iterated by an Iter/IterMut pointer, symbolic
2921    /// (`sizeof_T`) for a generic element type so `size / elem_size` cancels.
2922    pub(crate) fn iter_elem_size(&self, ptr: &VmValue<'z3, 'tcx>) -> Int<'z3> {
2923        let elem_ty = match ptr.ty.kind() {
2924            TyKind::Adt(_, substs) => substs.first().and_then(|s| s.as_type()),
2925            _ => None,
2926        };
2927        match elem_ty {
2928            Some(t) => self.size_sym_read(t),
2929            None => Int::from_u64(self.z3_ctx, 1),
2930        }
2931    }
2932
2933    /// Element count from two pointer fields sharing the same allocation:
2934    /// `(end.offset - ptr.offset) / elem_size`. When both pointers carry an
2935    /// element-structured offset (`OffsetKind::Element`, or the base), the
2936    /// count is computed element-wise (`end_elem - ptr_elem`) so the
2937    /// `(end·S - ptr·S)/S` division is avoided for a generic element size `S`.
2938    pub(crate) fn iter_len_from_ptrs(
2939        &self,
2940        ptr: &VmValue<'z3, 'tcx>,
2941        end: &VmValue<'z3, 'tcx>,
2942    ) -> Option<Int<'z3>> {
2943        let pp = ptr.provenance.as_ref()?;
2944        let ep = end.provenance.as_ref()?;
2945        if pp.alloc_id != ep.alloc_id {
2946            return None;
2947        }
2948        let elem_of = |p: &Provenance<'z3>| -> Option<Int<'z3>> {
2949            match &p.offset_kind {
2950                Some(OffsetKind::Element(e)) => Some(e.clone()),
2951                Some(OffsetKind::Field) | None => Some(Int::from_u64(self.z3_ctx, 0)),
2952                _ => None,
2953            }
2954        };
2955        if let (Some(pe), Some(ee)) = (elem_of(pp), elem_of(ep)) {
2956            return Some(Int::sub(self.z3_ctx, &[&ee, &pe]));
2957        }
2958        let sz = self.iter_elem_size(ptr);
2959        let diff = Int::sub(self.z3_ctx, &[&ep.offset, &pp.offset]);
2960        Some(diff.div(&sz))
2961    }
2962
2963    /// Remaining element count of the Iter/IterMut backed by `local`
2964    /// (fields `[0]` = ptr, `[1]` = end_or_len).  When a tracked pointer
2965    /// offset exists (`iter_ptr_offset`), prefers the compact
2966    /// `base_len - offset` form; otherwise falls back to
2967    /// `(end.offset - ptr.offset) / elem_size`.
2968    fn iter_remaining_len(&self, local: Local) -> Option<Int<'z3>> {
2969        let ptr = self.field_value(local, &[0])?;
2970        let end = self.field_value(local, &[1])?;
2971        let ep = end.provenance.as_ref()?;
2972        if ptr.provenance.as_ref().map(|p| p.alloc_id) != Some(ep.alloc_id) {
2973            return None;
2974        }
2975        let sz = self.iter_elem_size(ptr);
2976        if let Some((offset, _)) = self.constraints.term_caches.iter_ptr_offset.get(&ep.alloc_id) {
2977            let base_len = ep.offset.div(&sz);
2978            let zero = Int::from_u64(self.z3_ctx, 0);
2979            Some(
2980                offset
2981                    .gt(&base_len)
2982                    .ite(&zero, &Int::sub(self.z3_ctx, &[&base_len, offset])),
2983            )
2984        } else {
2985            self.iter_len_from_ptrs(ptr, end)
2986        }
2987    }
2988
2989    /// For Iter/IterMut types, compute len from struct fields directly
2990    /// instead of the generic allocation-size heuristic. Returns true
2991    /// if handled (value set to dest).
2992    fn interpreter_iter_len(&mut self, arg_val: &VmValue<'z3, 'tcx>, dest: Local) -> bool {
2993        let Some(l) = self.find_iter_self_local(arg_val) else {
2994            return false;
2995        };
2996        let Some(len_term) = self.iter_remaining_len(l) else {
2997            return false;
2998        };
2999        let dest_ty = self.body().local_decls[dest].ty;
3000        self.set_local(dest, VmValue::new(len_term, dest_ty));
3001        true
3002    }
3003
3004    /// Apply the side effect of post_inc_start / pre_dec_end on Iter/IterMut.
3005    /// Only updates the tracked offset (not field values), so that the
3006    /// precondition check (which runs before the call executes) sees the
3007    /// pre-update state, while subsequent len()/is_empty() calls use
3008    /// `base_len - offset` via interpreter_iter_len.
3009    fn apply_iter_ptr_update(
3010        &mut self,
3011        callee: DefId,
3012        arg_values: &[VmValue<'z3, 'tcx>],
3013    ) {
3014        let is_inc = crate::helpers::mir_utils::is_post_inc_start(self.tcx, callee);
3015        if !is_inc {
3016            return;
3017        } // pre_dec_end not yet supported
3018        let self_val = &arg_values[0];
3019        let some_local = self.find_iter_self_local(self_val);
3020        let Some(local) = some_local else { return };
3021        let Some(buffer) = self.iter_buffer(local) else { return };
3022        let offset_term = arg_values
3023            .get(1)
3024            .map(|v| v.z3_term.clone())
3025            .unwrap_or_else(|| Int::from_u64(self.z3_ctx, 1));
3026        let (new_offset, base_len) = match self.constraints.term_caches.iter_ptr_offset.get(&buffer) {
3027            Some((prev, base)) => (Int::add(self.z3_ctx, &[prev, &offset_term]), base.clone()),
3028            None => {
3029                let base = self
3030                    .field_value(local, &[1])
3031                    .and_then(|end| end.provenance.as_ref())
3032                    .and_then(|ep| match &ep.offset_kind {
3033                        Some(OffsetKind::Element(e)) => Some(e.clone()),
3034                        _ => None,
3035                    });
3036                (offset_term, base)
3037            }
3038        };
3039        self.constraints.term_caches.iter_ptr_offset.insert(buffer, (new_offset, base_len));
3040    }
3041
3042    /// Find the local whose symbolic address matches `term` (the address a
3043    /// reference value points at). Used to resolve a `&self`/`&mut self`
3044    /// receiver (often a reborrow temp) back to the referent local that carries
3045    /// the materialized field values.
3046    pub(crate) fn find_local_by_address(&self, term: &Int<'z3>) -> Option<Local> {
3047        for (local, id) in &self.current_frame.local_alloc {
3048            if self.units[id.0].allocation.base == *term {
3049                return Some(*local);
3050            }
3051        }
3052        None
3053    }
3054
3055    /// Resolve a *whole-place* reborrow (`_7 = &mut (*_1)` / `_7 = &(*_1)`,
3056    /// projection exactly `[Deref]`) back to its referent local.  Used to
3057    /// propagate the referent's materialized field values into an inlined
3058    /// callee when the reborrow temp's own forward assignment was pruned from
3059    /// the slice (so its value is still the stack-address default and
3060    /// `find_local_by_address` can only self-match).  Deliberately excludes
3061    /// field reborrows (`&mut (*_x).field`) — those address a subfield, whose
3062    /// value is tracked separately, so propagating the whole struct's fields
3063    /// would be wrong (BTreeMap's `NodeRef` handles).
3064    pub(crate) fn find_whole_reborrow_referent(&self, local: Local) -> Option<Local> {
3065        use rustc_middle::mir::{ProjectionElem, Rvalue, StatementKind};
3066        for bb in self.body().basic_blocks.iter() {
3067            for stmt in &bb.statements {
3068                if let StatementKind::Assign(assign) = &stmt.kind {
3069                    let (dest, rvalue) = &**assign;
3070                    if dest.local == local && dest.projection.is_empty() {
3071                        if let Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) = rvalue {
3072                            if place.projection.len() == 1
3073                                && matches!(place.projection[0].kind(), ProjectionElem::Deref)
3074                            {
3075                                return Some(place.local);
3076                            }
3077                        }
3078                    }
3079                }
3080            }
3081        }
3082        None
3083    }
3084
3085    /// Resolve a *field* reborrow (`_7 = &mut (*_x).field`, projection
3086    /// `[Deref, Field(..)*]`) back to its referent local plus the field path.
3087    pub(crate) fn find_field_reborrow_referent(
3088        &self,
3089        local: Local,
3090    ) -> Option<(Local, Vec<usize>)> {
3091        use rustc_middle::mir::{ProjectionElem, Rvalue, StatementKind};
3092        for bb in self.body().basic_blocks.iter() {
3093            for stmt in &bb.statements {
3094                if let StatementKind::Assign(assign) = &stmt.kind {
3095                    let (dest, rvalue) = &**assign;
3096                    if dest.local == local && dest.projection.is_empty() {
3097                        if let Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) = rvalue {
3098                            let mut proj = place.projection.iter();
3099                            if !matches!(proj.next().map(|p| p.kind()), Some(ProjectionElem::Deref))
3100                            {
3101                                continue;
3102                            }
3103                            let mut fields = Vec::new();
3104                            for p in proj {
3105                                if let ProjectionElem::Field(f, _) = p.kind() {
3106                                    fields.push(f.as_usize());
3107                                } else {
3108                                    fields.clear();
3109                                    break;
3110                                }
3111                            }
3112                            if !fields.is_empty() {
3113                                return Some((place.local, fields));
3114                            }
3115                        }
3116                    }
3117                }
3118            }
3119        }
3120        None
3121    }
3122
3123    /// Resolve a whole-place copy root: `_x = copy _y` / `_x = move _y` (no
3124    /// projection) traces `_x` back to `_y`. Used to recover a value parameter's
3125    /// materialized fields when the optimizer inserted a copy temporary between
3126    /// the caller's argument and the inlined callee's parameter (e.g.
3127    /// `get_ext`'s `_2 = copy _1` before `NonZero::get(move _2)`), so that
3128    /// `handle_callee_entry`'s field collection can follow the copy chain to the
3129    /// local that actually carries the fields.
3130    pub(crate) fn find_copy_root(&self, local: Local) -> Option<Local> {
3131        use rustc_middle::mir::{Rvalue, StatementKind};
3132        for bb in self.body().basic_blocks.iter() {
3133            for stmt in &bb.statements {
3134                if let StatementKind::Assign(assign) = &stmt.kind {
3135                    let (dest, rvalue) = &**assign;
3136                    if dest.local == local && dest.projection.is_empty() {
3137                        #[cfg(rapx_rvalue_use_with_retag)]
3138                        let op = match rvalue {
3139                            Rvalue::Use(op, _) => Some(op),
3140                            _ => None,
3141                        };
3142                        #[cfg(not(rapx_rvalue_use_with_retag))]
3143                        let op = match rvalue {
3144                            Rvalue::Use(op) => Some(op),
3145                            _ => None,
3146                        };
3147                        if let Some(Operand::Copy(p) | Operand::Move(p)) = op
3148                            && p.projection.is_empty()
3149                        {
3150                            return Some(p.local);
3151                        }
3152                    }
3153                }
3154            }
3155        }
3156        None
3157    }
3158
3159    /// If arg_val is a reference to an Iter or IterMut struct, return the
3160    /// local index of the referent (so field values can be looked up).
3161    /// Since len()/is_empty() always take &self, local 1 is the receiver.
3162    fn find_iter_self_local(&self, arg_val: &VmValue<'z3, 'tcx>) -> Option<Local> {
3163        match arg_val.ty.kind() {
3164            TyKind::Ref(_, pointee, _) => match pointee.kind() {
3165                TyKind::Adt(adt_def, _) => {
3166                    if api_classify::is_std_iter_or_itermut(adt_def.did()) {
3167                        // Find the local holding the iterator by matching the
3168                        // reference's address term against known local addresses
3169                        // (`&mut _iter` has term `addr__iter`).  A hardcoded
3170                        // `Local(1)` only holds for inlined `next` bodies where
3171                        // the iterator is the first argument; direct trait
3172                        // `Iterator::next` calls keep the iterator at an
3173                        // arbitrary local.
3174                        if let Some(local) = self.find_local_by_address(&arg_val.z3_term) {
3175                            return Some(local);
3176                        }
3177                        // Fallback for inlined `next` bodies (iter bound to arg 1).
3178                        return Some(Local::from_usize(1));
3179                    }
3180                    None
3181                }
3182                _ => None,
3183            },
3184            _ => None,
3185        }
3186    }
3187
3188    /// Derive an element count from the backing allocation (`size / elem_size`).
3189    /// Used by `ReturnLengthOfArg` and the fallback in `ReturnFieldOfArg`
3190    /// (slices, `&str`, and Vec values whose `{buf{ptr,cap}, len}` field was not
3191    /// materialized). Returns true when a value was produced.
3192    fn set_len_from_alloc(&mut self, arg_val: &VmValue<'z3, 'tcx>, dest: Local) -> bool {
3193        let effective_alloc_id = arg_val
3194            .provenance_alloc_id()
3195            .and_then(|pid| self.data_alloc_of(pid, arg_val.ty))
3196            .or_else(|| arg_val.provenance_alloc_id());
3197        let Some(alloc_id) = effective_alloc_id else {
3198            return false;
3199        };
3200        let dest_ty = self.body().local_decls[dest].ty;
3201        // Prefer the materialized slice length.
3202        if let Some(len) = self.alloc(alloc_id).slice_len().cloned() {
3203            let val = VmValue::new(len, dest_ty);
3204            self.set_local(dest, val);
3205            return true;
3206        }
3207        if let Some(elem_ty) = self.alloc(alloc_id).element_ty.as_ty() {
3208            let elem_term = self.size_sym_read(elem_ty);
3209            let size = self.allocation_size(alloc_id);
3210            if elem_term.simplify().as_u64() == Some(1) {
3211                let val = VmValue::new(size.clone(), dest_ty);
3212                self.set_local(dest, val);
3213                return true;
3214            }
3215            let val = VmValue::new(size.div(&elem_term), dest_ty);
3216            self.set_local(dest, val);
3217            return true;
3218        }
3219        let size = self.allocation_size(alloc_id);
3220        let val = VmValue::new(size.clone(), dest_ty);
3221        self.set_local(dest, val);
3222        true
3223    }
3224
3225    /// Apply a `ReturnFieldOfArg`/`ReturnFieldOfArgSub` effect: read the
3226    /// materialized field `field` of the receiver's pointee and return it,
3227    /// preserving the field's own type/provenance. For `ReturnFieldOfArgSub`,
3228    /// subtract `sub_offset` elements from the field pointer (`next_back_unchecked`
3229    /// after `pre_dec_end`).
3230    ///
3231    /// The receiver of a `&self` getter is a reborrow temp (`_t = &data`) whose
3232    /// local carries no field values, while the fields were materialized on the
3233    /// referent (`data`). Resolve the referent by matching the receiver value's
3234    /// address term against the known local addresses; fall back to the direct
3235    /// arg local.
3236    fn apply_field_of_arg_effect(
3237        &mut self,
3238        arg: usize,
3239        field: usize,
3240        sub_offset: Option<u64>,
3241        args: &[VmValue<'z3, 'tcx>],
3242        caller_arg_locals: &[Option<Local>],
3243        dest: Local,
3244    ) {
3245        // Candidate locals that may carry the materialized field, in order of
3246        // preference. A `&mut self` receiver is often a mutable reborrow
3247        // (`_t = &mut (*self)`) whose local does not carry the field values,
3248        // while the parameter and the shared reborrow (`_t = &(*self)`) do.
3249        let mut candidates: Vec<Local> = Vec::new();
3250        if let Some(l) = args
3251            .get(arg)
3252            .and_then(|v| self.find_local_by_address(&v.z3_term))
3253        {
3254            candidates.push(l);
3255        }
3256        if let Some(l) = caller_arg_locals.get(arg).copied().flatten() {
3257            candidates.push(l);
3258        }
3259        // Any local that already materializes the field (covers the receiver
3260        // parameter / shared reborrow that the mutable reborrow does not copy).
3261        for l in self.current_frame.local_alloc.keys() {
3262            if !self.field_paths(*l).is_empty() {
3263                candidates.push(*l);
3264            }
3265        }
3266        let mut found: Option<VmValue<'z3, 'tcx>> = None;
3267        for l in candidates {
3268            if let Some(fv) = self.field_value(l, &[field]) {
3269                found = Some(fv.clone());
3270                break;
3271            }
3272        }
3273        if let Some(mut v) = found {
3274            if let Some(offset) = sub_offset {
3275                // `field - offset` elements: subtract the element stride from
3276                // both the address term and the provenance offset.
3277                let stride = self.pointee_elem_size(v.ty).max(1);
3278                let scaled = Int::from_u64(self.z3_ctx, offset * stride);
3279                v.z3_term = Int::sub(self.z3_ctx, &[&v.z3_term, &scaled]);
3280                if let Some(prov) = &v.provenance {
3281                    v.provenance = Some(Provenance {
3282                        alloc_id: prov.alloc_id,
3283                        offset: Int::sub(self.z3_ctx, &[&prov.offset, &scaled]),
3284                        offset_kind: None,
3285                    });
3286                }
3287            }
3288            v.ty = self.body().local_decls[dest].ty;
3289            self.set_local(dest, v);
3290            return;
3291        }
3292        // Fallback: for an integer result (e.g. `len`/`capacity`), the
3293        // receiver is often a reborrow temp whose referent carries no field
3294        // values; reconstruct the length from the backing allocation
3295        // (`size / elem_size`), as `ReturnLengthOfArg` does.
3296        let dest_ty = self.body().local_decls[dest].ty;
3297        if matches!(dest_ty.kind(), TyKind::Uint(_) | TyKind::Int(_)) {
3298            if let Some(arg_val) = args.get(arg) {
3299                if self.set_len_from_alloc(arg_val, dest) {
3300                    return;
3301                }
3302            }
3303        }
3304        let term = self.fresh_int(&format!("field_{}", dest.as_usize()));
3305        let val = VmValue::new(term, dest_ty);
3306        self.set_local(dest, val);
3307    }
3308
3309    /// Apply a `ReturnRange` effect: model `slice::range(range, bounds)`
3310    /// returning `Range { start, end }` with `0 <= start <= end <= bounds.end`.
3311    /// The `bounds` argument is a `RangeTo<usize>` whose field 0 carries the
3312    /// slice length; the returned `Range<usize>` fields are fresh symbols bound
3313    /// by the range invariant.
3314    fn apply_range_effect(
3315        &mut self,
3316        bounds_arg: usize,
3317        args: &[VmValue<'z3, 'tcx>],
3318        caller_arg_locals: &[Option<Local>],
3319        dest: Local,
3320    ) {
3321        let dest_ty = self.body().local_decls[dest].ty;
3322        let TyKind::Adt(adt, substs) = dest_ty.kind() else {
3323            return;
3324        };
3325        let variant = adt.non_enum_variant();
3326        let field_ty = |idx: usize| -> Ty<'tcx> {
3327            variant
3328                .fields
3329                .iter()
3330                .nth(idx)
3331                .map(|f| crate::helpers::mir_utils::field_ty(self.tcx, f, substs))
3332                .unwrap_or(dest_ty)
3333        };
3334
3335        // Resolve `bounds.end` (the slice length): prefer the materialized
3336        // field 0 of the `RangeTo<usize>` argument, falling back to the
3337        // argument's own term.
3338        let mut len_term = None;
3339        if let Some(l) = caller_arg_locals.get(bounds_arg).copied().flatten() {
3340            if let Some(fv) = self.field_value(l, &[0]) {
3341                len_term = Some(fv.z3_term.clone());
3342            }
3343        }
3344        let len_term = len_term.or_else(|| args.get(bounds_arg).map(|v| v.z3_term.clone()));
3345        let Some(len_term) = len_term else {
3346            return;
3347        };
3348
3349        let start = self.fresh_int(&format!("range_start_{}", dest.as_usize()));
3350        let end = self.fresh_int(&format!("range_end_{}", dest.as_usize()));
3351        let zero = Int::from_u64(self.z3_ctx, 0);
3352        self.constraints.assertions.push(start.ge(&zero));
3353        self.constraints.assertions.push(start.le(&end));
3354        self.constraints.assertions.push(end.le(&len_term));
3355
3356        let start_val = VmValue::new(start, field_ty(0));
3357        let end_val = VmValue::new(end, field_ty(1));
3358        self.set_field_value(dest, vec![0], start_val);
3359        self.set_field_value(dest, vec![1], end_val);
3360    }
3361
3362    /// Compute the `(ptr, cap, len)` field paths of a `Vec`-shaped local, handling
3363    /// both the std `Vec<T>` layout `{ buf: RawVec { ptr, cap }, len }` and the
3364    /// flat local re-implementation `{ ptr: NonNull, len, cap }` used by the
3365    /// std-challenge suites.
3366    /// Materialize the `{ptr, cap, len}` field values of a `Vec<T>` aggregate
3367    /// at `local`. The backing-buffer pointer is written to the owning raw
3368    /// pointer field (located generically via [`Self::container_ptr_field`]);
3369    /// the `len`/`cap` are no longer materialized as fields — they are asserted
3370    /// as path conditions and tracked by the backing allocation's slice length.
3371    ///
3372    /// The symbolic invariant `0 <= len <= cap` and `cap * elem_size <=
3373    /// isize::MAX` is asserted as a path condition so downstream `len()` /
3374    /// `capacity()` / `InBound` / `ValidNum` queries agree.
3375    pub(crate) fn materialize_vec_fields(
3376        &mut self,
3377        local: Local,
3378        ptr: VmValue<'z3, 'tcx>,
3379        cap: Int<'z3>,
3380        len: Int<'z3>,
3381    ) {
3382        let elem_size = self.size_of_ty(ptr.ty).max(1);
3383        let ty = self.body().local_decls[local].ty;
3384        let (ptr_path, _) = self
3385            .container_ptr_field(ty)
3386            .expect("materialize_vec_fields: container has no owning pointer field");
3387        self.set_field_value(local, ptr_path, ptr);
3388        self.materialize_vec_len_cap(cap, len, elem_size);
3389    }
3390
3391    /// Assert the Vec length/capacity invariants `0 <= len <= cap` and
3392    /// `cap * elem_size <= isize::MAX` as path conditions.
3393    pub(crate) fn materialize_vec_len_cap(
3394        &mut self,
3395        cap: Int<'z3>,
3396        len: Int<'z3>,
3397        elem_size: u64,
3398    ) {
3399        let zero = Int::from_u64(self.z3_ctx, 0);
3400        self.constraints.assertions.push(len.ge(&zero));
3401        self.constraints.assertions.push(len.le(&cap));
3402        self.constraints.assertions.push(cap.ge(&zero));
3403        // Language invariant: a Vec's byte length fits in `isize::MAX`, so the
3404        // `from_raw_parts`/`from_raw_parts_mut` precondition
3405        // `size_of(T) * len <= isize::MAX` is provable from the materialized
3406        // fields (`len <= cap` and `cap * elem_size <= isize::MAX`).
3407        let isize_max = Int::from_u64(self.z3_ctx, isize::MAX as u64);
3408        let elem_term = Int::from_u64(self.z3_ctx, elem_size.max(1));
3409        self.constraints.assertions
3410            .push(Int::mul(self.z3_ctx, &[&cap, &elem_term]).le(&isize_max));
3411    }
3412}