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