1use std::collections::{HashMap, HashSet};
28
29use rustc_middle::{
30 mir::{BinOp, Local, Operand, Rvalue, TerminatorKind, UnOp},
31 ty::{ConstKind, GenericArgKind, PseudoCanonicalInput, Ty, TyCtxt, TyKind, UintTy},
32};
33use z3::{
34 Config, Context, SatResult, Solver,
35 ast::{Ast, Bool, Int},
36};
37
38use super::{
39 alias, align, alive, allocated, deref, in_bound, init, non_null, non_overlap, typed, valid_num,
40 valid_ptr,
41};
42
43use crate::verify::{
44 contract::{
45 ContractExpr, ContractPlace, ContractProjection, NumericOp, NumericPredicate, PlaceBase,
46 Property, PropertyArg, PropertyKind, RelOp,
47 },
48 def_use::{PlaceBaseKey, PlaceKey},
49 generic::GenericTypeCandidates,
50 helpers::{Checkpoint, callee_param_index_for_local},
51 path_extractor::PathStep,
52 primitive::PrimitiveCall,
53 report::CheckResult,
54 verifier::{AbstractValue, CallSummary, ForwardVisitResult, StateFact},
55};
56
57type ValueCursor = usize;
58type TraceSeen = HashSet<(PlaceKey, ValueCursor)>;
59
60#[derive(Clone, Copy)]
61struct PathCursorCutoff {
62 block: rustc_middle::mir::BasicBlock,
63 statement_index: Option<usize>,
64}
65
66pub struct SmtChecker<'tcx> {
68 pub(crate) tcx: TyCtxt<'tcx>,
69}
70
71fn ty_has_param_const(ty: Ty<'_>) -> bool {
72 for arg in ty.walk() {
73 match arg.kind() {
74 GenericArgKind::Const(c) if matches!(c.kind(), ConstKind::Param(_)) => return true,
75 GenericArgKind::Type(inner_ty) if matches!(inner_ty.kind(), TyKind::Alias(..)) => {
76 return true;
77 }
78 _ => {}
79 }
80 }
81 false
82}
83
84fn safe_type_layout<'tcx>(
85 tcx: TyCtxt<'tcx>,
86 caller: rustc_hir::def_id::DefId,
87 ty: Ty<'tcx>,
88) -> Option<(u64, u64)> {
89 if let TyKind::Ref(_, _, _) | TyKind::RawPtr(_, _) = ty.kind() {
90 let ptr_size = tcx.data_layout.pointer_size().bytes();
91 let ptr_align = tcx.data_layout.pointer_align().abi.bytes();
92 return Some((ptr_align, ptr_size));
93 }
94 if ty_has_param_const(ty) {
95 return None;
96 }
97 let typing_env = rustc_middle::ty::TypingEnv::post_analysis(tcx, caller);
98 let input = PseudoCanonicalInput {
99 typing_env,
100 value: ty,
101 };
102 match tcx.layout_of(input) {
103 Ok(layout) => Some((layout.align.abi.bytes(), layout.size.bytes())),
104 Err(_) if matches!(ty.kind(), TyKind::Param(_)) => Some((0, 0)),
105 Err(_) => None,
106 }
107}
108
109impl<'tcx> SmtChecker<'tcx> {
110 pub fn new(tcx: TyCtxt<'tcx>) -> Self {
112 Self { tcx }
113 }
114
115 pub fn check(
117 &self,
118 checkpoint: &Checkpoint<'tcx>,
119 property: &Property<'tcx>,
120 forward: &ForwardVisitResult<'tcx>,
121 ) -> SmtCheckResult {
122 match property.kind {
123 PropertyKind::Align => align::check(self, checkpoint, property, forward),
124 PropertyKind::Alias => alias::check(self, checkpoint, property, forward),
125 PropertyKind::Alive => alive::check(self, checkpoint, property, forward),
126 PropertyKind::Allocated => allocated::check(self, checkpoint, property, forward),
127 PropertyKind::Deref => deref::check(self, checkpoint, property, forward),
128 PropertyKind::NonNull => non_null::check(self, checkpoint, property, forward),
129 PropertyKind::InBound => in_bound::check(self, checkpoint, property, forward),
130 PropertyKind::Init => init::check(self, checkpoint, property, forward),
131 PropertyKind::NonOverlap => non_overlap::check(self, checkpoint, property, forward),
132 PropertyKind::Typed => typed::check(self, checkpoint, property, forward),
133 PropertyKind::ValidNum => valid_num::check(self, checkpoint, property, forward),
134 PropertyKind::ValidPtr => valid_ptr::check(self, checkpoint, property, forward),
135 PropertyKind::NonVolatile => super::non_volatile::check(self, checkpoint, property, forward),
136 _ => SmtCheckResult::unknown("no SMT lowering for this property yet"),
137 }
138 }
139
140 pub fn check_for_checkpoint(
146 &self,
147 caller: rustc_hir::def_id::DefId,
148 property: &Property<'tcx>,
149 forward: &ForwardVisitResult<'tcx>,
150 ) -> SmtCheckResult {
151 match property.kind {
152 PropertyKind::Align => align::check_for_checkpoint(self, caller, property, forward),
153 PropertyKind::Allocated => {
154 SmtCheckResult::unknown("Allocated struct invariant not implemented yet")
155 }
156 PropertyKind::NonNull => {
157 SmtCheckResult::unknown("NonNull struct invariant not implemented yet")
158 }
159 PropertyKind::InBound => {
160 in_bound::check_for_checkpoint(self, caller, property, forward)
161 }
162 PropertyKind::Init => init::check_for_checkpoint(self, caller, property, forward),
163 PropertyKind::ValidPtr => {
164 SmtCheckResult::unknown("ValidPtr struct invariant not implemented yet")
165 }
166 _ => SmtCheckResult::unknown("no struct invariant SMT lowering for this property yet"),
167 }
168 }
169
170 pub(crate) fn prove_obligation(
172 &self,
173 checkpoint: &Checkpoint<'tcx>,
174 forward: &ForwardVisitResult<'tcx>,
175 obligation: SmtObligation,
176 ) -> SmtCheckResult {
177 let has_contracts = forward
178 .facts
179 .iter()
180 .any(|f| matches!(f, StateFact::Contract(_)));
181
182 let cfg = Config::new();
186 let ctx = Context::new(&cfg);
187 let solver = Solver::new(&ctx);
188 let mut model = SmtModel::new(self.tcx, checkpoint, forward, &ctx);
189 model.assert_forward_facts(&solver);
190
191 if matches!(solver.check(), SatResult::Unsat) {
192 return SmtCheckResult::proved(
193 "path facts are infeasible; the obligation holds vacuously on this path",
194 )
195 .with_query(SmtQuery::new(
196 obligation,
197 model.assumptions().to_vec(),
198 SmtPredicate::Custom(String::from("path constraints are unsat")),
199 ));
200 }
201
202 if !forward.forgets.is_empty() && !has_contracts {
203 let reasons = forward
204 .forgets
205 .iter()
206 .map(|reason| format!("{reason:?}"))
207 .collect::<Vec<_>>()
208 .join(", ");
209 return SmtCheckResult::unknown(
210 "path has precision loss; SMT proof is not trusted without a summary",
211 )
212 .with_note(format!("precision loss: {reasons}"));
213 }
214
215 match &obligation {
222 SmtObligation::Aligned {
223 place,
224 align,
225 ty_name,
226 } => {
227 if *align > 0 && *align <= 1 {
228 return SmtCheckResult {
229 result: CheckResult::Proved,
230 query: Some(SmtQuery::new(
231 obligation.clone(),
232 model.assumptions().to_vec(),
233 SmtPredicate::Custom(format!(
234 "{} has trivial 1-byte alignment",
235 place_label(place)
236 )),
237 )),
238 notes: vec![String::from("alignment requirement is trivial")],
239 };
240 }
241
242 let target_label = place_label(place);
243 let Some(target_term) = model.term_for_place(place) else {
244 return SmtCheckResult::unknown(format!(
245 "could not build an address term for {target_label}"
246 ))
247 .with_query(SmtQuery::new(
248 obligation.clone(),
249 model.assumptions().to_vec(),
250 SmtPredicate::Not(Box::new(SmtPredicate::Divisible {
251 term: SmtTerm::Place(place.clone()),
252 modulus: *align,
253 })),
254 ));
255 };
256
257 let is_symbolic = *align == 0;
258 let align_term = if is_symbolic {
259 model.symbolic_align_term(&ty_name)
260 } else {
261 Int::from_u64(&ctx, *align)
262 };
263 let align_u64 = if is_symbolic { 0 } else { *align };
264 let zero = Int::from_u64(&ctx, 0);
265 let goal = target_term.modulo(&align_term)._eq(&zero);
266 let query = SmtQuery::new(
267 obligation.clone(),
268 model.assumptions().to_vec(),
269 SmtPredicate::Not(Box::new(SmtPredicate::Divisible {
270 term: SmtTerm::Place(place.clone()),
271 modulus: align_u64,
272 })),
273 );
274
275 solver.assert(&goal.not());
276 match solver.check() {
277 SatResult::Unsat => SmtCheckResult::proved(
278 "alignment proved; no counterexample satisfies the path facts",
279 )
280 .with_query(query),
281 SatResult::Sat => {
282 rap_debug!(" [SMT Align] {} sat: counterexample found", target_label);
283 SmtCheckResult::unknown(
284 "current path facts do not prove the required alignment",
285 )
286 .with_query(query)
287 .with_note(
288 "hint: add an offset-alignment guard or provide a pointer-add/layout summary",
289 )
290 }
291 SatResult::Unknown => {
292 rap_info!(" [SMT Align] {} unknown result", target_label);
293 SmtCheckResult::unknown("solver returned unknown").with_query(query)
294 }
295 }
296 }
297 SmtObligation::NonZero { place } => {
298 let target_label = place_label(place);
299 let Some(target_term) = model.term_for_place(place) else {
300 return SmtCheckResult::unknown(format!(
301 "could not build an address term for {target_label}"
302 ))
303 .with_query(SmtQuery::new(
304 obligation.clone(),
305 model.assumptions().to_vec(),
306 SmtPredicate::Eq(SmtTerm::Place(place.clone()), SmtTerm::Const(0)),
307 ));
308 };
309
310 let zero = Int::from_u64(&ctx, 0);
311 let query = SmtQuery::new(
312 obligation.clone(),
313 model.assumptions().to_vec(),
314 SmtPredicate::Eq(SmtTerm::Place(place.clone()), SmtTerm::Const(0)),
315 );
316
317 solver.assert(&target_term._eq(&zero));
318 match solver.check() {
319 SatResult::Unsat => SmtCheckResult::proved(
320 "non-null proved; no zero-address model satisfies the path facts",
321 )
322 .with_query(query),
323 SatResult::Sat => SmtCheckResult::unknown(
324 "current path facts do not prove the target is non-null",
325 )
326 .with_query(query)
327 .with_note("hint: add a non-null guard or provide a source/provenance summary"),
328 SatResult::Unknown => {
329 SmtCheckResult::unknown("solver returned unknown").with_query(query)
330 }
331 }
332 }
333 SmtObligation::InBounds {
334 place,
335 ty_name,
336 access_count,
337 ..
338 } => {
339 let target_label = place_label(place);
340 if model.has_index_access_assumptions {
341 return SmtCheckResult::proved(
342 "IndexAccess in-bounds proved via caller contract",
343 )
344 .with_query(SmtQuery::new(
345 obligation.clone(),
346 model.assumptions().to_vec(),
347 SmtPredicate::Custom(String::from("IndexAccess InBound by contract")),
348 ));
349 }
350 let Some(bounds) = model.pointer_bounds_for_place(place) else {
351 rap_debug!(
352 " [SMT InBound] could not recover pointer bounds for {target_label}"
353 );
354 if model.has_equivalent_contract_fact(place, PropertyKind::InBound) {
355 return SmtCheckResult::proved(
356 "in-bounds proved via caller contract on equivalent place",
357 )
358 .with_query(SmtQuery::new(
359 obligation.clone(),
360 model.assumptions().to_vec(),
361 SmtPredicate::Not(Box::new(SmtPredicate::InBounds {
362 index: SmtTerm::Value("index(?)".to_string()),
363 access_count: access_count.clone(),
364 len: SmtTerm::Value("len(?)".to_string()),
365 })),
366 ))
367 .with_note("caller contract provides InBound for raw pointer parameter");
368 }
369 return SmtCheckResult::unknown(format!(
370 "could not connect {target_label} to a slice length and pointer-add index"
371 ))
372 .with_query(SmtQuery::new(
373 obligation.clone(),
374 model.assumptions().to_vec(),
375 SmtPredicate::Not(Box::new(SmtPredicate::InBounds {
376 index: SmtTerm::Value("index(?)".to_string()),
377 access_count: access_count.clone(),
378 len: SmtTerm::Value("len(?)".to_string()),
379 })),
380 ))
381 .with_note(
382 "hint: this first InBound lowering needs slice.as_ptr(), ptr.add(index), and a matching index < slice.len() path fact",
383 );
384 };
385
386 let zero = Int::from_u64(&ctx, 0);
387 let Some(access) = model.term_for_smt_term(access_count) else {
388 rap_debug!(
389 " [SMT InBound] could not lower access-count term {}",
390 access_count.describe()
391 );
392 return SmtCheckResult::unknown(format!(
393 "could not build an access-count term for {}",
394 access_count.describe()
395 ))
396 .with_query(SmtQuery::new(
397 obligation.clone(),
398 model.assumptions().to_vec(),
399 SmtPredicate::Not(Box::new(SmtPredicate::InBounds {
400 index: bounds.index_term,
401 access_count: access_count.clone(),
402 len: bounds.len_term,
403 })),
404 ));
405 };
406 let index_non_negative = bounds.index.ge(&zero);
407 let access_non_negative = access.ge(&zero);
408 let len_non_negative = bounds.len.ge(&zero);
409 let covered_end = Int::add(&ctx, &[bounds.index.clone(), access]);
410 let within_len = covered_end.le(&bounds.len);
411 solver.assert(&index_non_negative);
412 solver.assert(&access_non_negative);
413 solver.assert(&len_non_negative);
414 model.assumptions.push(SmtPredicate::Ge(
415 bounds.index_term.clone(),
416 SmtTerm::Const(0),
417 ));
418 model
419 .assumptions
420 .push(SmtPredicate::Ge(access_count.clone(), SmtTerm::Const(0)));
421 let goal = Bool::and(
422 &ctx,
423 &[&index_non_negative, &access_non_negative, &within_len],
424 );
425 let query = SmtQuery::new(
426 obligation.clone(),
427 model.assumptions().to_vec(),
428 SmtPredicate::Not(Box::new(SmtPredicate::InBounds {
429 index: bounds.index_term,
430 access_count: access_count.clone(),
431 len: bounds.len_term,
432 })),
433 );
434
435 solver.assert(&goal.not());
436 match solver.check() {
437 SatResult::Unsat => SmtCheckResult::proved(format!(
438 "in-bounds proved for {target_label}; {} {ty_name} element(s) fit under the matched slice length",
439 access_count.describe()
440 ))
441 .with_query(query),
442 SatResult::Sat => {
443 rap_debug!(
444 " [SMT InBound] sat for {target_label}; assumptions: {:?}; negated goal: {}",
445 query.assumptions,
446 query.negated_goal.describe()
447 );
448 SmtCheckResult::unknown(
449 "current path facts do not prove the required bounds",
450 )
451 .with_query(query)
452 .with_note(
453 "hint: add an index < len guard or provide a richer object-size summary",
454 )
455 }
456 SatResult::Unknown => {
457 SmtCheckResult::unknown("solver returned unknown").with_query(query)
458 }
459 }
460 }
461 SmtObligation::PointerRangeInBounds {
462 place,
463 ty_name,
464 lower_delta,
465 upper_delta,
466 } => {
467 let target_label = place_label(place);
468 let Some(bounds) = model.pointer_bounds_for_place(place) else {
469 return SmtCheckResult::unknown(format!(
470 "could not connect {target_label} to a slice length and pointer index"
471 ))
472 .with_query(SmtQuery::new(
473 obligation.clone(),
474 model.assumptions().to_vec(),
475 pointer_range_negated_goal(
476 SmtTerm::Value("index(?)".to_string()),
477 lower_delta.clone(),
478 upper_delta.clone(),
479 SmtTerm::Value("len(?)".to_string()),
480 ),
481 ))
482 .with_note(
483 "hint: pointer arithmetic bounds need a recoverable base object and index/length facts",
484 );
485 };
486
487 let Some(lower) = model.term_for_smt_term(lower_delta) else {
488 return SmtCheckResult::unknown(format!(
489 "could not build a lower pointer-range term for {}",
490 lower_delta.describe()
491 ));
492 };
493 let Some(upper) = model.term_for_smt_term(upper_delta) else {
494 return SmtCheckResult::unknown(format!(
495 "could not build an upper pointer-range term for {}",
496 upper_delta.describe()
497 ));
498 };
499
500 model.assert_unsigned_bounds_for_term(&solver, lower_delta, &mut HashSet::new());
501 model.assert_unsigned_bounds_for_term(&solver, upper_delta, &mut HashSet::new());
502
503 let zero = Int::from_u64(&ctx, 0);
504 solver.assert(&bounds.len.ge(&zero));
510 model
511 .assumptions
512 .push(SmtPredicate::Ge(bounds.len_term.clone(), SmtTerm::Const(0)));
513
514 let lower_index = Int::add(&ctx, &[bounds.index.clone(), lower]);
515 let upper_index = Int::add(&ctx, &[bounds.index.clone(), upper]);
516 let base_non_negative = bounds.index.ge(&zero);
517 let base_within_len = bounds.index.le(&bounds.len);
518 let lower_in_object = lower_index.ge(&zero);
519 let upper_in_object = upper_index.le(&bounds.len);
520
521 let goal = Bool::and(
522 &ctx,
523 &[
524 &base_non_negative,
525 &base_within_len,
526 &lower_in_object,
527 &upper_in_object,
528 ],
529 );
530 let query = SmtQuery::new(
531 obligation.clone(),
532 model.assumptions().to_vec(),
533 pointer_range_negated_goal(
534 bounds.index_term,
535 lower_delta.clone(),
536 upper_delta.clone(),
537 bounds.len_term,
538 ),
539 );
540
541 solver.assert(&goal.not());
542 match solver.check() {
543 SatResult::Unsat => SmtCheckResult::proved(format!(
544 "pointer arithmetic range proved for {target_label}; the {ty_name} range stays inside the matched object"
545 ))
546 .with_query(query),
547 SatResult::Sat => SmtCheckResult::unknown(
548 "current path facts do not prove the pointer arithmetic stays in bounds",
549 )
550 .with_query(query)
551 .with_note("hint: add bounds guards for the pointer arithmetic count"),
552 SatResult::Unknown => {
553 SmtCheckResult::unknown("solver returned unknown").with_query(query)
554 }
555 }
556 }
557 SmtObligation::Initialized {
558 place,
559 ty_name,
560 elements,
561 elem_size,
562 array_elem_size,
563 array_len_term,
564 } => {
565 if *elem_size == Some(0) {
566 return SmtCheckResult::proved(format!(
567 "initialization proved; zero-sized type {ty_name}"
568 ));
569 }
570 if let (Some(ae), Some(alt)) = (array_elem_size, array_len_term) {
571 if *ae > 0 {
572 if let Some(len_term) = model.term_for_smt_term(alt) {
573 let zero = Int::from_u64(&ctx, 0);
574 let size_term = Int::from_u64(&ctx, *ae);
575 let total_gt_zero =
576 Bool::and(&ctx, &[&len_term.gt(&zero), &size_term.gt(&zero)]);
577 solver.push();
578 solver.assert(&total_gt_zero);
579 let check = solver.check();
580 solver.pop(1);
581 if matches!(check, SatResult::Unsat) {
582 return SmtCheckResult::proved(format!(
583 "initialization proved; array length is provably zero for {ty_name}"
584 ));
585 }
586 }
587 }
588 }
589 let target_label = place_label(place);
590 let target_terms = model.init_target_terms(place);
591 if target_terms.is_empty() {
592 return SmtCheckResult::unknown(format!(
593 "could not build an address term for {target_label}"
594 ))
595 .with_query(SmtQuery::new(
596 obligation.clone(),
597 model.assumptions().to_vec(),
598 SmtPredicate::Custom(format!(
599 "not Init({}, {ty_name}, {elements})",
600 target_label,
601 elements = elements.describe()
602 )),
603 ));
604 }
605
606 if model.has_equivalent_contract_fact(place, PropertyKind::Init) {
607 return SmtCheckResult::proved(
608 "initialized proved via caller contract on equivalent place",
609 )
610 .with_query(SmtQuery::new(
611 obligation.clone(),
612 model.assumptions().to_vec(),
613 SmtPredicate::Custom(format!(
614 "Init({}, {ty_name}, {elements})",
615 target_label,
616 elements = elements.describe()
617 )),
618 ))
619 .with_note("caller contract provides Init for raw pointer parameter");
620 }
621
622 if let Some(bounds) = model.pointer_bounds_for_place(place)
623 && model.origin_is_initialized_for_ty(&bounds.origin_key, ty_name)
624 {
625 let Some(access) = model.term_for_smt_term(elements) else {
626 return SmtCheckResult::unknown(format!(
627 "could not build an Init element-count term for {}",
628 elements.describe()
629 ));
630 };
631 model.assert_unsigned_bounds_for_term(&solver, elements, &mut HashSet::new());
632 let zero = Int::from_u64(&ctx, 0);
633 let index_non_negative = bounds.index.ge(&zero);
634 let access_non_negative = access.ge(&zero);
635 let len_non_negative = bounds.len.ge(&zero);
636 let covered_end = Int::add(&ctx, &[bounds.index.clone(), access]);
637 let within_len = covered_end.le(&bounds.len);
638 solver.assert(&len_non_negative);
639 let goal = Bool::and(
640 &ctx,
641 &[&index_non_negative, &access_non_negative, &within_len],
642 );
643 let query = SmtQuery::new(
644 obligation.clone(),
645 model.assumptions().to_vec(),
646 SmtPredicate::Custom(format!(
647 "not initialized_object_range({}, {}, {})",
648 target_label,
649 bounds.index_term.describe(),
650 elements.describe()
651 )),
652 );
653
654 solver.assert(&goal.not());
655 return match solver.check() {
656 SatResult::Unsat => SmtCheckResult::proved(format!(
657 "initialization proved; {target_label} covers {} initialized {ty_name} element(s) from its source object",
658 elements.describe()
659 ))
660 .with_query(query),
661 SatResult::Sat => SmtCheckResult::unknown(
662 "current path facts do not prove the initialized object range covers the target",
663 )
664 .with_query(query)
665 .with_note("hint: keep object length facts for the initialized source"),
666 SatResult::Unknown => {
667 SmtCheckResult::unknown("solver returned unknown").with_query(query)
668 }
669 };
670 }
671
672 let init_facts: Vec<_> = forward
673 .facts
674 .iter()
675 .filter_map(|fact| match fact {
676 StateFact::KnownInit {
677 place,
678 ty_name,
679 elements,
680 reason,
681 } => Some((place.clone(), ty_name.clone(), *elements, reason.clone())),
682 _ => None,
683 })
684 .collect();
685
686 let mut checked_any_init_fact = false;
687 for target_term in &target_terms {
688 let mut matched_elements = 0_u64;
689 let mut matched_places = HashSet::new();
690 let mut matched_notes = Vec::new();
691 let mut last_query = None;
692 let Some(required_elements) = smt_term_const_u64(elements) else {
693 continue;
694 };
695
696 for (init_place, init_ty_name, init_elements, init_reason) in &init_facts {
697 if !init_type_compatible(init_ty_name, ty_name) {
698 continue;
699 }
700 if !matched_places.insert(init_place.clone()) {
701 continue;
702 }
703 let init_terms = model.init_source_terms(init_place);
704 if init_terms.is_empty() {
705 continue;
706 }
707
708 for init_term in &init_terms {
709 let query = SmtQuery::new(
710 obligation.clone(),
711 model.assumptions().to_vec(),
712 SmtPredicate::Custom(format!(
713 "not same_addr({}, {}) for Init({}, {ty_name}, {elements})",
714 target_label,
715 place_label(init_place),
716 target_label,
717 elements = elements.describe()
718 )),
719 );
720 solver.push();
721 solver.assert(&target_term._eq(init_term).not());
722 let check = solver.check();
723 solver.pop(1);
724 if matches!(check, SatResult::Unsat) {
725 checked_any_init_fact = true;
726 matched_elements = matched_elements.saturating_add(*init_elements);
727 matched_notes.push(format!(
728 "{} element(s) from {} ({init_reason})",
729 init_elements,
730 place_label(init_place)
731 ));
732 last_query = Some(query);
733 break;
734 }
735 }
736
737 if matched_elements >= required_elements {
738 let query = last_query.unwrap_or_else(|| {
739 SmtQuery::new(
740 obligation.clone(),
741 model.assumptions().to_vec(),
742 SmtPredicate::Custom(format!(
743 "not Init({}, {ty_name}, {elements})",
744 target_label,
745 elements = elements.describe()
746 )),
747 )
748 });
749 return SmtCheckResult::proved(format!(
750 "initialization proved; {target_label} aliases {matched_elements} initialized element(s)"
751 ))
752 .with_query(query)
753 .with_note(format!(
754 "matched initialized writes: {}",
755 matched_notes.join("; ")
756 ));
757 }
758 }
759 }
760
761 let mut result = SmtCheckResult::unknown(
762 "current path facts do not prove the target memory is initialized",
763 )
764 .with_query(SmtQuery::new(
765 obligation.clone(),
766 model.assumptions().to_vec(),
767 SmtPredicate::Custom(format!(
768 "not Init({}, {ty_name}, {elements})",
769 target_label,
770 elements = elements.describe()
771 )),
772 ));
773 if checked_any_init_fact {
774 result = result.with_note(
775 "hint: a write was found, but SMT could not prove it aliases the Init target",
776 );
777 } else {
778 result = result.with_note(
779 "hint: add a preceding ptr.write summary or a verified init-range summary",
780 );
781 }
782 result
783 }
784 SmtObligation::Allocated {
785 place,
786 ty_name,
787 elements,
788 } => {
789 let target_label = place_label(place);
790
791 if let Some(bounds) = model.pointer_bounds_for_place(place) {
792 let zero = Int::from_u64(&ctx, 0);
793 let Some(access) = model.term_for_smt_term(elements) else {
794 return SmtCheckResult::unknown(format!(
795 "could not build an allocation element-count term for {}",
796 elements.describe()
797 ))
798 .with_query(SmtQuery::new(
799 obligation.clone(),
800 model.assumptions().to_vec(),
801 SmtPredicate::Custom(format!(
802 "not Allocated({}, {ty_name}, {})",
803 target_label,
804 elements.describe()
805 )),
806 ));
807 };
808 let index_non_negative = bounds.index.ge(&zero);
809 let access_non_negative = access.ge(&zero);
810 let len_non_negative = bounds.len.ge(&zero);
811 let covered_end = Int::add(&ctx, &[bounds.index.clone(), access]);
812 let within_len = covered_end.le(&bounds.len);
813 solver.assert(&index_non_negative);
814 solver.assert(&access_non_negative);
815 solver.assert(&len_non_negative);
816 model.assumptions.push(SmtPredicate::Ge(
817 bounds.index_term.clone(),
818 SmtTerm::Const(0),
819 ));
820 model
821 .assumptions
822 .push(SmtPredicate::Ge(elements.clone(), SmtTerm::Const(0)));
823 model.assumptions.push(SmtPredicate::Ge(
824 bounds.len_term.clone(),
825 SmtTerm::Const(0),
826 ));
827 let goal = Bool::and(
828 &ctx,
829 &[&index_non_negative, &access_non_negative, &within_len],
830 );
831 let query = SmtQuery::new(
832 obligation.clone(),
833 model.assumptions().to_vec(),
834 SmtPredicate::Custom(format!(
835 "not same_object_bounds({}, {}, {})",
836 target_label,
837 bounds.index_term.describe(),
838 elements.describe()
839 )),
840 );
841
842 solver.assert(&goal.not());
843 return match solver.check() {
844 SatResult::Unsat => SmtCheckResult::proved(format!(
845 "allocation proved for {target_label}; requested range stays inside the matched object"
846 ))
847 .with_query(query),
848 SatResult::Sat => SmtCheckResult::unknown(
849 "current path facts do not prove the requested range stays inside one allocation",
850 )
851 .with_query(query)
852 .with_note(
853 "hint: add an object-length guard or provide a richer allocation summary",
854 ),
855 SatResult::Unknown => {
856 SmtCheckResult::unknown("solver returned unknown").with_query(query)
857 }
858 };
859 }
860
861 let Some(target_term) = model.term_for_place(place) else {
862 return SmtCheckResult::unknown(format!(
863 "could not build an address term for {target_label}"
864 ))
865 .with_query(SmtQuery::new(
866 obligation.clone(),
867 model.assumptions().to_vec(),
868 SmtPredicate::Custom(format!(
869 "not Allocated({}, {ty_name}, {})",
870 target_label,
871 elements.describe()
872 )),
873 ));
874 };
875 let Some(required_elements) = model.term_for_smt_term(elements) else {
876 return SmtCheckResult::unknown(format!(
877 "could not build an allocation element-count term for {}",
878 elements.describe()
879 ))
880 .with_query(SmtQuery::new(
881 obligation.clone(),
882 model.assumptions().to_vec(),
883 SmtPredicate::Custom(format!(
884 "not Allocated({}, {ty_name}, {})",
885 target_label,
886 elements.describe()
887 )),
888 ));
889 };
890
891 let allocated_facts = forward
892 .facts
893 .iter()
894 .filter_map(|fact| match fact {
895 StateFact::KnownAllocated {
896 place,
897 object,
898 ty_name,
899 elements,
900 reason,
901 } => Some((
902 place.clone(),
903 object.clone(),
904 ty_name.clone(),
905 *elements,
906 reason.clone(),
907 )),
908 _ => None,
909 })
910 .collect::<Vec<_>>();
911
912 for (alloc_place, object, alloc_ty_name, alloc_elements, reason) in allocated_facts
913 {
914 if !allocated_type_compatible(&alloc_ty_name, ty_name) {
915 continue;
916 }
917 if allocation_object_invalidated(forward, &object) {
918 continue;
919 }
920 let Some(alloc_term) = model.term_for_place(&alloc_place) else {
921 continue;
922 };
923 let query = SmtQuery::new(
924 obligation.clone(),
925 model.assumptions().to_vec(),
926 SmtPredicate::Custom(format!(
927 "not same_allocated_object({}, {}) or {} > {}",
928 target_label,
929 place_label(&alloc_place),
930 elements.describe(),
931 alloc_elements
932 )),
933 );
934
935 solver.push();
936 solver.assert(&target_term._eq(&alloc_term).not());
937 let same_address = matches!(solver.check(), SatResult::Unsat);
938 solver.pop(1);
939 if !same_address {
940 continue;
941 }
942
943 solver.push();
944 solver.assert(&required_elements.gt(&Int::from_u64(&ctx, alloc_elements)));
945 let enough_elements = matches!(solver.check(), SatResult::Unsat);
946 solver.pop(1);
947 if enough_elements {
948 return SmtCheckResult::proved(format!(
949 "allocation proved; {target_label} aliases {} element(s) of {} ({reason})",
950 alloc_elements, alloc_ty_name
951 ))
952 .with_query(query);
953 }
954 }
955
956 SmtCheckResult::unknown(
957 "current path facts do not prove the target range is backed by one live allocation",
958 )
959 .with_query(SmtQuery::new(
960 obligation.clone(),
961 model.assumptions().to_vec(),
962 SmtPredicate::Custom(format!(
963 "not Allocated({}, {ty_name}, {})",
964 target_label,
965 elements.describe()
966 )),
967 ))
968 .with_note(
969 "hint: keep pointer provenance, object length, and lifetime facts for the target object",
970 )
971 }
972 SmtObligation::NonOverlapping {
973 left,
974 right,
975 left_count,
976 right_count,
977 elem_size,
978 } => {
979 if let (Some((left_object, left_offset)), Some((right_object, right_offset))) = (
980 model.pointer_object_offset_for_place(left),
981 model.pointer_object_offset_for_place(right),
982 ) && left_object == right_object
983 {
984 let Some(left_offset_term) = model.term_for_smt_term(&left_offset) else {
985 return SmtCheckResult::unknown(format!(
986 "could not lower object offset {}",
987 left_offset.describe()
988 ));
989 };
990 let Some(right_offset_term) = model.term_for_smt_term(&right_offset) else {
991 return SmtCheckResult::unknown(format!(
992 "could not lower object offset {}",
993 right_offset.describe()
994 ));
995 };
996 let Some(left_count_term) = model.term_for_smt_term(left_count) else {
997 return SmtCheckResult::unknown(format!(
998 "could not build a range-count term for {}",
999 left_count.describe()
1000 ));
1001 };
1002 let Some(right_count_term) = model.term_for_smt_term(right_count) else {
1003 return SmtCheckResult::unknown(format!(
1004 "could not build a range-count term for {}",
1005 right_count.describe()
1006 ));
1007 };
1008 let left_end = Int::add(&ctx, &[left_offset_term.clone(), left_count_term]);
1009 let right_end = Int::add(&ctx, &[right_offset_term.clone(), right_count_term]);
1010 let disjoint = Bool::or(
1011 &ctx,
1012 &[
1013 &left_end.le(&right_offset_term),
1014 &right_end.le(&left_offset_term),
1015 ],
1016 );
1017 let negated = SmtPredicate::Not(Box::new(SmtPredicate::NonOverlapping {
1018 left: left_offset,
1019 right: right_offset,
1020 left_count: left_count.clone(),
1021 right_count: right_count.clone(),
1022 elem_size: 1,
1023 }));
1024 let query =
1025 SmtQuery::new(obligation.clone(), model.assumptions().to_vec(), negated);
1026
1027 model.assert_unsigned_bounds_for_term(&solver, left_count, &mut HashSet::new());
1028 model.assert_unsigned_bounds_for_term(
1029 &solver,
1030 right_count,
1031 &mut HashSet::new(),
1032 );
1033 solver.assert(&disjoint.not());
1034 return match solver.check() {
1035 SatResult::Unsat => SmtCheckResult::proved(format!(
1036 "non-overlap proved inside allocation {}",
1037 place_label(&left_object)
1038 ))
1039 .with_query(query),
1040 SatResult::Sat => {
1041 failed_smt("the two ranges overlap within the same allocation object")
1042 .with_query(query)
1043 }
1044 SatResult::Unknown => {
1045 SmtCheckResult::unknown("solver returned unknown").with_query(query)
1046 }
1047 };
1048 }
1049
1050 let Some(left_addr) = model.term_for_place(left) else {
1051 return SmtCheckResult::unknown(format!(
1052 "could not build an address term for {}",
1053 place_label(left)
1054 ));
1055 };
1056 let Some(right_addr) = model.term_for_place(right) else {
1057 return SmtCheckResult::unknown(format!(
1058 "could not build an address term for {}",
1059 place_label(right)
1060 ));
1061 };
1062 let Some(left_count_term) = model.term_for_smt_term(left_count) else {
1063 return SmtCheckResult::unknown(format!(
1064 "could not build a range-count term for {}",
1065 left_count.describe()
1066 ));
1067 };
1068 let Some(right_count_term) = model.term_for_smt_term(right_count) else {
1069 return SmtCheckResult::unknown(format!(
1070 "could not build a range-count term for {}",
1071 right_count.describe()
1072 ));
1073 };
1074
1075 let size = Int::from_u64(&ctx, *elem_size);
1076 let left_end = Int::add(
1077 &ctx,
1078 &[
1079 left_addr.clone(),
1080 Int::mul(&ctx, &[left_count_term, size.clone()]),
1081 ],
1082 );
1083 let right_end = Int::add(
1084 &ctx,
1085 &[
1086 right_addr.clone(),
1087 Int::mul(&ctx, &[right_count_term, size]),
1088 ],
1089 );
1090 let disjoint = Bool::or(
1091 &ctx,
1092 &[&left_end.le(&right_addr), &right_end.le(&left_addr)],
1093 );
1094 let negated = SmtPredicate::Not(Box::new(SmtPredicate::NonOverlapping {
1095 left: SmtTerm::Place(left.clone()),
1096 right: SmtTerm::Place(right.clone()),
1097 left_count: left_count.clone(),
1098 right_count: right_count.clone(),
1099 elem_size: *elem_size,
1100 }));
1101 let query =
1102 SmtQuery::new(obligation.clone(), model.assumptions().to_vec(), negated);
1103
1104 model.assert_unsigned_bounds_for_term(&solver, left_count, &mut HashSet::new());
1105 model.assert_unsigned_bounds_for_term(&solver, right_count, &mut HashSet::new());
1106 solver.assert(&disjoint.not());
1107 match solver.check() {
1108 SatResult::Unsat => SmtCheckResult::proved(
1109 "non-overlap proved; the two pointer ranges cannot intersect on this path",
1110 )
1111 .with_query(query),
1112 SatResult::Sat => failed_smt(
1113 "the two pointer ranges may overlap under the current path facts",
1114 )
1115 .with_query(query),
1116 SatResult::Unknown => {
1117 SmtCheckResult::unknown("solver returned unknown").with_query(query)
1118 }
1119 }
1120 }
1121 SmtObligation::Predicate { predicates } => {
1122 if model.has_index_access_assumptions {
1125 return SmtCheckResult::proved(
1126 "IndexAccess in-bounds proved via caller contract",
1127 );
1128 }
1129 if predicates.is_empty() {
1130 return SmtCheckResult::unknown("ValidNum predicate set is empty").with_query(
1131 SmtQuery::new(
1132 obligation.clone(),
1133 model.assumptions().to_vec(),
1134 SmtPredicate::Custom(String::from("empty ValidNum predicate")),
1135 ),
1136 );
1137 }
1138 model.assert_unsigned_bounds_for_predicates(&solver, predicates);
1139
1140 let Some(goal) = model.bool_for_predicates(predicates) else {
1141 return SmtCheckResult::unknown(
1142 "ValidNum predicate could not be lowered to SMT",
1143 )
1144 .with_query(SmtQuery::new(
1145 obligation.clone(),
1146 model.assumptions().to_vec(),
1147 SmtPredicate::Not(Box::new(if predicates.len() == 1 {
1148 predicates[0].clone()
1149 } else {
1150 SmtPredicate::And(predicates.clone())
1151 })),
1152 ));
1153 };
1154 let query = SmtQuery::new(
1155 obligation.clone(),
1156 model.assumptions().to_vec(),
1157 SmtPredicate::Not(Box::new(if predicates.len() == 1 {
1158 predicates[0].clone()
1159 } else {
1160 SmtPredicate::And(predicates.clone())
1161 })),
1162 );
1163
1164 solver.assert(&goal.not());
1165 match solver.check() {
1166 SatResult::Unsat => SmtCheckResult::proved(
1167 "numeric precondition proved; no counterexample satisfies the path facts",
1168 )
1169 .with_query(query),
1170 SatResult::Sat => SmtCheckResult::unknown(
1171 "current path facts do not prove the numeric precondition",
1172 )
1173 .with_query(query)
1174 .with_note("hint: add a matching numeric guard or expose a stronger summary"),
1175 SatResult::Unknown => {
1176 SmtCheckResult::unknown("solver returned unknown").with_query(query)
1177 }
1178 }
1179 }
1180 SmtObligation::Range { .. } => SmtCheckResult::unknown(
1181 "range obligations are not implemented yet",
1182 )
1183 .with_query(SmtQuery::new(
1184 obligation.clone(),
1185 model.assumptions().to_vec(),
1186 SmtPredicate::Custom(String::from("range refutation not implemented")),
1187 )),
1188 }
1189 }
1190
1191 pub(crate) fn prove_obligation_for_checkpoint(
1196 &self,
1197 caller: rustc_hir::def_id::DefId,
1198 forward: &ForwardVisitResult<'tcx>,
1199 obligation: SmtObligation,
1200 ) -> SmtCheckResult {
1201 let dummy_checkpoint = Checkpoint {
1202 caller,
1203 callee: Some(caller),
1204 block: rustc_middle::mir::BasicBlock::from_usize(0),
1205 span: rustc_span::Span::default(),
1206 args: Vec::new(),
1207 kind: crate::helpers::mir_scan::CheckpointKind::UnsafeCall,
1208 is_ref: false,
1209 };
1210 self.prove_obligation(&dummy_checkpoint, forward, obligation)
1211 }
1212
1213 pub(crate) fn property_target(
1215 &self,
1216 checkpoint: &Checkpoint<'tcx>,
1217 property: &Property<'tcx>,
1218 ) -> Option<PlaceKey> {
1219 let arg = property.args.first()?;
1220 match arg {
1221 PropertyArg::Place(place) => self.contract_place_to_callsite_place(checkpoint, place),
1222 PropertyArg::Expr(ContractExpr::Place(place)) => {
1223 self.contract_place_to_callsite_place(checkpoint, place)
1224 }
1225 PropertyArg::Expr(ContractExpr::IndexAccess { slice, .. }) => {
1226 match slice.as_ref() {
1227 ContractExpr::Place(place) => {
1228 self.contract_place_to_callsite_place(checkpoint, place)
1229 }
1230 _ => None,
1231 }
1232 }
1233 PropertyArg::Expr(ContractExpr::Const(index)) => {
1234 let index = usize::try_from(*index).ok()?;
1235 self.callsite_arg_place(checkpoint, index)
1236 }
1237 _ => None,
1238 }
1239 }
1240
1241 pub(crate) fn property_place_arg(
1243 &self,
1244 checkpoint: &Checkpoint<'tcx>,
1245 property: &Property<'tcx>,
1246 index: usize,
1247 ) -> Option<PlaceKey> {
1248 let arg = property.args.get(index)?;
1249 match arg {
1250 PropertyArg::Place(place) => self.contract_place_to_callsite_place(checkpoint, place),
1251 PropertyArg::Expr(ContractExpr::Place(place)) => {
1252 self.contract_place_to_callsite_place(checkpoint, place)
1253 }
1254 PropertyArg::Expr(ContractExpr::Const(arg_index)) => {
1255 let arg_index = usize::try_from(*arg_index).ok()?;
1256 self.callsite_arg_place(checkpoint, arg_index)
1257 }
1258 _ => None,
1259 }
1260 }
1261
1262 pub(crate) fn property_target_direct(&self, property: &Property<'tcx>) -> Option<PlaceKey> {
1265 let arg = property.args.first()?;
1266 match arg {
1267 PropertyArg::Place(place) => Some(self.resolve_contract_place(place)),
1268 PropertyArg::Expr(ContractExpr::Place(place)) => {
1269 Some(self.resolve_contract_place(place))
1270 }
1271 _ => None,
1272 }
1273 }
1274
1275 fn resolve_contract_place(&self, place: &ContractPlace<'tcx>) -> PlaceKey {
1277 let mut key = PlaceKey::from_contract_place(place);
1278 if let PlaceBaseKey::Arg(index) = key.base {
1279 key.base = PlaceBaseKey::Local(index + 1);
1280 }
1281 key
1282 }
1283
1284 pub(crate) fn property_required_ty(
1286 &self,
1287 checkpoint: &Checkpoint<'tcx>,
1288 property: &Property<'tcx>,
1289 ) -> Option<Ty<'tcx>> {
1290 property.args.iter().find_map(|arg| {
1291 let PropertyArg::Ty(ty) = arg else {
1292 return None;
1293 };
1294 Some(self.instantiate_callsite_ty(checkpoint, *ty))
1295 })
1296 }
1297
1298 pub(crate) fn property_required_ty_direct(
1300 &self,
1301 property: &Property<'tcx>,
1302 ) -> Option<Ty<'tcx>> {
1303 property.args.iter().find_map(|arg| {
1304 let PropertyArg::Ty(ty) = arg else {
1305 return None;
1306 };
1307 Some(*ty)
1308 })
1309 }
1310
1311 pub(crate) fn property_len_expr_direct(
1316 &self,
1317 property: &Property<'tcx>,
1318 ) -> Option<ContractExpr<'tcx>> {
1319 property.args.iter().rev().find_map(|arg| {
1320 let PropertyArg::Expr(expr) = arg else {
1321 return None;
1322 };
1323 Some(expr.clone())
1324 })
1325 }
1326
1327 pub(crate) fn property_len_expr(
1334 &self,
1335 checkpoint: &Checkpoint<'tcx>,
1336 property: &Property<'tcx>,
1337 ) -> Option<ContractExpr<'tcx>> {
1338 property.args.iter().rev().find_map(|arg| {
1339 let PropertyArg::Expr(expr) = arg else {
1340 return None;
1341 };
1342 self.bind_contract_expr_to_callsite(checkpoint, expr)
1343 })
1344 }
1345
1346 pub(crate) fn contract_expr_to_smt_term(
1348 &self,
1349 caller: rustc_hir::def_id::DefId,
1350 expr: &ContractExpr<'tcx>,
1351 ) -> Option<SmtTerm> {
1352 match expr {
1353 ContractExpr::Place(place) => {
1354 Some(SmtTerm::Place(PlaceKey::from_contract_place(place)))
1355 }
1356 ContractExpr::Const(value) => u64::try_from(*value).ok().map(SmtTerm::Const),
1357 ContractExpr::ConstParam { name, .. } => {
1358 Some(SmtTerm::ConstParam(name.clone()))
1359 }
1360 ContractExpr::SizeOf(ty) => {
1361 let size = self.required_size(caller, *ty)?;
1362 Some(SmtTerm::Const(size))
1363 }
1364 ContractExpr::AlignOf(ty) => {
1365 let align = self.required_alignment(caller, *ty)?;
1366 Some(SmtTerm::Const(align))
1367 }
1368 ContractExpr::Len(expr) => Some(SmtTerm::Value(format!(
1369 "len({})",
1370 self.contract_expr_label(expr)?
1371 ))),
1372 ContractExpr::IndexAccess { .. } => None,
1373 ContractExpr::Binary { op, lhs, rhs } => {
1374 let lhs = Box::new(self.contract_expr_to_smt_term(caller, lhs)?);
1375 let rhs = Box::new(self.contract_expr_to_smt_term(caller, rhs)?);
1376 match op {
1377 NumericOp::Add => Some(SmtTerm::Add(lhs, rhs)),
1378 NumericOp::Sub => Some(SmtTerm::Sub(lhs, rhs)),
1379 NumericOp::Mul => Some(SmtTerm::Mul(lhs, rhs)),
1380 NumericOp::Div => Some(SmtTerm::Div(lhs, rhs)),
1381 NumericOp::Rem => Some(SmtTerm::Rem(lhs, rhs)),
1382 NumericOp::BitAnd | NumericOp::BitOr | NumericOp::BitXor => None,
1383 }
1384 }
1385 ContractExpr::Unary { .. } | ContractExpr::Unknown => None,
1386 }
1387 }
1388
1389 pub(crate) fn property_numeric_predicates(
1391 &self,
1392 checkpoint: &Checkpoint<'tcx>,
1393 property: &Property<'tcx>,
1394 ) -> Option<Vec<NumericPredicate<'tcx>>> {
1395 property.args.iter().find_map(|arg| {
1396 let PropertyArg::Predicates(predicates) = arg else {
1397 return None;
1398 };
1399 predicates
1400 .iter()
1401 .map(|predicate| {
1402 Some(NumericPredicate {
1403 lhs: self.bind_contract_expr_to_callsite(checkpoint, &predicate.lhs)?,
1404 op: predicate.op,
1405 rhs: self.bind_contract_expr_to_callsite(checkpoint, &predicate.rhs)?,
1406 })
1407 })
1408 .collect()
1409 })
1410 }
1411
1412 pub(crate) fn numeric_predicate_to_smt_predicate(
1414 &self,
1415 caller: rustc_hir::def_id::DefId,
1416 predicate: &NumericPredicate<'tcx>,
1417 ) -> Option<SmtPredicate> {
1418 let lhs = self.contract_expr_to_smt_term(caller, &predicate.lhs)?;
1419 let rhs = self.contract_expr_to_smt_term(caller, &predicate.rhs)?;
1420 Some(match predicate.op {
1421 RelOp::Eq => SmtPredicate::Eq(lhs, rhs),
1422 RelOp::Ne => SmtPredicate::Ne(lhs, rhs),
1423 RelOp::Lt => SmtPredicate::Lt(lhs, rhs),
1424 RelOp::Le => SmtPredicate::Le(lhs, rhs),
1425 RelOp::Gt => SmtPredicate::Gt(lhs, rhs),
1426 RelOp::Ge => SmtPredicate::Ge(lhs, rhs),
1427 })
1428 }
1429
1430 pub(crate) fn property_numeric_smt_predicates(
1432 &self,
1433 checkpoint: &Checkpoint<'tcx>,
1434 property: &Property<'tcx>,
1435 ) -> Option<Vec<SmtPredicate>> {
1436 let predicates = self.property_numeric_predicates(checkpoint, property)?;
1437 let mut lowered = Vec::new();
1438 for predicate in predicates {
1439 if let Some(expanded) = self.expand_index_access_predicate(checkpoint, &predicate)? {
1440 lowered.extend(expanded);
1441 } else {
1442 lowered
1443 .push(self.numeric_predicate_to_smt_predicate(checkpoint.caller, &predicate)?);
1444 }
1445 }
1446 Some(lowered)
1447 }
1448
1449 pub(crate) fn property_index_access_in_bound_predicates(
1451 &self,
1452 checkpoint: &Checkpoint<'tcx>,
1453 property: &Property<'tcx>,
1454 ) -> Option<Vec<SmtPredicate>> {
1455 property.args.iter().find_map(|arg| {
1456 let PropertyArg::Expr(expr) = arg else {
1457 return None;
1458 };
1459 if !matches!(expr, ContractExpr::IndexAccess { .. }) {
1460 return None;
1461 }
1462 let rebound = self.bind_contract_expr_to_callsite(checkpoint, expr)?;
1463 self.index_access_in_bound_predicates(checkpoint, &rebound)
1464 })
1465 }
1466
1467 fn expand_index_access_predicate(
1468 &self,
1469 checkpoint: &Checkpoint<'tcx>,
1470 predicate: &NumericPredicate<'tcx>,
1471 ) -> Option<Option<Vec<SmtPredicate>>> {
1472 let ContractExpr::IndexAccess { slice, index } = &predicate.lhs else {
1473 if matches!(predicate.rhs, ContractExpr::IndexAccess { .. }) {
1474 return None;
1475 }
1476 return Some(None);
1477 };
1478 if !matches!(predicate.op, RelOp::Ne) || !matches!(predicate.rhs, ContractExpr::Const(0)) {
1479 return None;
1480 }
1481
1482 self.index_access_in_bound_predicates(
1483 checkpoint,
1484 &ContractExpr::IndexAccess {
1485 slice: slice.clone(),
1486 index: index.clone(),
1487 },
1488 )
1489 .map(Some)
1490 }
1491
1492 fn index_access_in_bound_predicates(
1493 &self,
1494 checkpoint: &Checkpoint<'tcx>,
1495 expr: &ContractExpr<'tcx>,
1496 ) -> Option<Vec<SmtPredicate>> {
1497 let ContractExpr::IndexAccess { slice, index } = expr else {
1498 return None;
1499 };
1500 let len = SmtTerm::Value(format!("len({})", self.contract_expr_label(slice)?));
1501 let (lower, upper) = self.slice_index_bounds(checkpoint.caller, index, len.clone())?;
1502 Some(vec![
1503 SmtPredicate::Le(SmtTerm::Const(0), lower.clone()),
1504 SmtPredicate::Le(lower, upper.clone()),
1505 SmtPredicate::Le(upper, len),
1506 ])
1507 }
1508
1509 fn slice_index_bounds(
1510 &self,
1511 caller: rustc_hir::def_id::DefId,
1512 index: &ContractExpr<'tcx>,
1513 len: SmtTerm,
1514 ) -> Option<(SmtTerm, SmtTerm)> {
1515 let index_term = self.contract_expr_to_smt_term(caller, index)?;
1516 let Some(kind) = self.slice_index_kind(caller, index) else {
1517 return Some((
1518 index_term.clone(),
1519 SmtTerm::Add(Box::new(index_term), Box::new(SmtTerm::Const(1))),
1520 ));
1521 };
1522
1523 match kind {
1524 SliceIndexKind::Scalar => Some((
1525 index_term.clone(),
1526 SmtTerm::Add(Box::new(index_term), Box::new(SmtTerm::Const(1))),
1527 )),
1528 SliceIndexKind::Range => Some((
1529 self.contract_expr_field_term(caller, index, 0)?,
1530 self.contract_expr_field_term(caller, index, 1)?,
1531 )),
1532 SliceIndexKind::RangeFrom => Some((
1533 self.contract_expr_field_term(caller, index, 0)?,
1534 len,
1535 )),
1536 SliceIndexKind::RangeTo => Some((
1537 SmtTerm::Const(0),
1538 self.contract_expr_field_term(caller, index, 0)?,
1539 )),
1540 SliceIndexKind::RangeFull => Some((SmtTerm::Const(0), len)),
1541 SliceIndexKind::RangeInclusive => {
1542 let start = self.contract_expr_field_term(caller, index, 0)?;
1543 let end = self.contract_expr_field_term(caller, index, 1)?;
1544 Some((
1545 start,
1546 SmtTerm::Add(Box::new(end), Box::new(SmtTerm::Const(1))),
1547 ))
1548 }
1549 SliceIndexKind::RangeToInclusive => {
1550 let end = self.contract_expr_field_term(caller, index, 0)?;
1551 Some((
1552 SmtTerm::Const(0),
1553 SmtTerm::Add(Box::new(end), Box::new(SmtTerm::Const(1))),
1554 ))
1555 }
1556 }
1557 }
1558
1559 fn slice_index_kind(
1560 &self,
1561 caller: rustc_hir::def_id::DefId,
1562 index: &ContractExpr<'tcx>,
1563 ) -> Option<SliceIndexKind> {
1564 let place = self.contract_expr_place(index)?;
1565 let ty = self.place_ty_for_caller(caller, &place)?;
1566 if matches!(ty.kind(), TyKind::Uint(UintTy::Usize)) {
1567 return Some(SliceIndexKind::Scalar);
1568 }
1569
1570 let ty_name = format!("{ty:?}");
1571 if ty_name.contains("RangeToInclusive<usize>") {
1572 Some(SliceIndexKind::RangeToInclusive)
1573 } else if ty_name.contains("RangeInclusive<usize>") {
1574 Some(SliceIndexKind::RangeInclusive)
1575 } else if ty_name.contains("RangeFrom<usize>") {
1576 Some(SliceIndexKind::RangeFrom)
1577 } else if ty_name.contains("RangeTo<usize>") {
1578 Some(SliceIndexKind::RangeTo)
1579 } else if ty_name.contains("RangeFull") {
1580 Some(SliceIndexKind::RangeFull)
1581 } else if ty_name.contains("Range<usize>") || ty_name.contains("IndexRange") {
1582 Some(SliceIndexKind::Range)
1583 } else {
1584 None
1585 }
1586 }
1587
1588 fn contract_expr_field_term(
1589 &self,
1590 caller: rustc_hir::def_id::DefId,
1591 expr: &ContractExpr<'tcx>,
1592 field: usize,
1593 ) -> Option<SmtTerm> {
1594 let mut place = self.contract_expr_place(expr)?;
1595 place.fields.push(field);
1596 self.contract_expr_to_smt_term(caller, &contract_expr_from_place_key(place))
1597 }
1598
1599 fn contract_expr_place(&self, expr: &ContractExpr<'tcx>) -> Option<PlaceKey> {
1600 let ContractExpr::Place(place) = expr else {
1601 return None;
1602 };
1603 Some(PlaceKey::from_contract_place(place))
1604 }
1605
1606 fn contract_expr_label(&self, expr: &ContractExpr<'tcx>) -> Option<String> {
1607 match expr {
1608 ContractExpr::Place(place) => Some(place_label(&PlaceKey::from_contract_place(place))),
1609 ContractExpr::Const(value) => Some(value.to_string()),
1610 ContractExpr::ConstParam { name, .. } => Some(name.clone()),
1611 _ => None,
1612 }
1613 }
1614
1615 fn place_ty_for_caller(
1616 &self,
1617 caller: rustc_hir::def_id::DefId,
1618 place: &PlaceKey,
1619 ) -> Option<Ty<'tcx>> {
1620 if !place.fields.is_empty() {
1621 return None;
1622 }
1623 let local = match place.base {
1624 PlaceBaseKey::Return => Local::from_usize(0),
1625 PlaceBaseKey::Local(local) => Local::from_usize(local),
1626 PlaceBaseKey::Arg(_) => return None,
1627 };
1628 Some(self.tcx.optimized_mir(caller).local_decls[local].ty)
1629 }
1630
1631 pub(crate) fn infer_pointee_ty(
1632 &self,
1633 caller: rustc_hir::def_id::DefId,
1634 place: &PlaceKey,
1635 ) -> Option<Ty<'tcx>> {
1636 let ty = self.place_ty_for_caller(caller, place)?;
1637 infer_element_ty(ty)
1638 }
1639
1640 pub(crate) fn is_len_carrying_place_for_caller(
1642 &self,
1643 caller: rustc_hir::def_id::DefId,
1644 place: &PlaceKey,
1645 ) -> bool {
1646 self.place_ty_for_caller(caller, place)
1647 .is_some_and(is_len_carrying_ty)
1648 }
1649
1650 fn bind_contract_expr_to_callsite(
1651 &self,
1652 checkpoint: &Checkpoint<'tcx>,
1653 expr: &ContractExpr<'tcx>,
1654 ) -> Option<ContractExpr<'tcx>> {
1655 match expr {
1656 ContractExpr::Place(place) => self.contract_place_to_callsite_expr(checkpoint, place),
1657 ContractExpr::Const(value) => Some(ContractExpr::Const(*value)),
1658 ContractExpr::ConstParam { index, name } => self
1659 .instantiate_callsite_const(checkpoint, *index)
1660 .map(ContractExpr::Const)
1661 .or_else(|| {
1662 Some(ContractExpr::ConstParam {
1663 index: *index,
1664 name: name.clone(),
1665 })
1666 }),
1667 ContractExpr::SizeOf(ty) => Some(ContractExpr::SizeOf(
1668 self.instantiate_callsite_ty(checkpoint, *ty),
1669 )),
1670 ContractExpr::AlignOf(ty) => Some(ContractExpr::AlignOf(
1671 self.instantiate_callsite_ty(checkpoint, *ty),
1672 )),
1673 ContractExpr::Len(expr) => Some(ContractExpr::Len(Box::new(
1674 self.bind_contract_expr_to_callsite(checkpoint, expr)?,
1675 ))),
1676 ContractExpr::IndexAccess { slice, index } => Some(ContractExpr::IndexAccess {
1677 slice: Box::new(self.bind_contract_expr_to_callsite(checkpoint, slice)?),
1678 index: Box::new(self.bind_contract_expr_to_callsite(checkpoint, index)?),
1679 }),
1680 ContractExpr::Binary { op, lhs, rhs } => Some(ContractExpr::Binary {
1681 op: *op,
1682 lhs: Box::new(self.bind_contract_expr_to_callsite(checkpoint, lhs)?),
1683 rhs: Box::new(self.bind_contract_expr_to_callsite(checkpoint, rhs)?),
1684 }),
1685 ContractExpr::Unary { op, expr } => Some(ContractExpr::Unary {
1686 op: *op,
1687 expr: Box::new(self.bind_contract_expr_to_callsite(checkpoint, expr)?),
1688 }),
1689 ContractExpr::Unknown => Some(ContractExpr::Unknown),
1690 }
1691 }
1692
1693 fn contract_place_to_callsite_expr(
1694 &self,
1695 checkpoint: &Checkpoint<'tcx>,
1696 place: &ContractPlace<'tcx>,
1697 ) -> Option<ContractExpr<'tcx>> {
1698 let key = PlaceKey::from_contract_place(place);
1699 match place.base {
1700 PlaceBase::Arg(index) => self.callsite_arg_expr(checkpoint, index, &key.fields),
1701 PlaceBase::Local(local) => {
1702 if let Some(index) = checkpoint
1703 .callee
1704 .and_then(|callee| callee_param_index_for_local(self.tcx, callee, local))
1705 {
1706 self.callsite_arg_expr(checkpoint, index, &key.fields)
1707 } else {
1708 Some(ContractExpr::Place(place.clone()))
1709 }
1710 }
1711 PlaceBase::Return => Some(ContractExpr::Place(place.clone())),
1712 }
1713 }
1714
1715 fn callsite_arg_expr(
1716 &self,
1717 checkpoint: &Checkpoint<'tcx>,
1718 index: usize,
1719 fields: &[usize],
1720 ) -> Option<ContractExpr<'tcx>> {
1721 let operand = checkpoint.args.get(index)
1723 .or_else(|| {
1726 let body = self.tcx.optimized_mir(checkpoint.caller);
1727 let terminator = body.basic_blocks[checkpoint.block].terminator();
1728 if let TerminatorKind::Call { args, .. } = &terminator.kind {
1729 args.get(index).map(|a| &a.node)
1730 } else {
1731 None
1732 }
1733 })?;
1734 if fields.is_empty()
1735 && let Operand::Constant(constant) = operand
1736 {
1737 let const_debug = format!("{:?}", constant.const_);
1738 if let Some(value) = const_int_from_debug(&const_debug) {
1739 return Some(ContractExpr::Const(value));
1740 }
1741 if let Some(name) = const_param_name_from_debug(&const_debug) {
1742 return Some(ContractExpr::ConstParam {
1743 index: 0,
1744 name,
1745 });
1746 }
1747 rap_warn!("callsite Const debug={}", const_debug);
1748 }
1749 self.callsite_arg_place_with_fields(checkpoint, index, fields)
1750 .map(contract_expr_from_place_key)
1751 }
1752
1753 pub(crate) fn contract_place_to_callsite_place(
1755 &self,
1756 checkpoint: &Checkpoint<'tcx>,
1757 place: &ContractPlace<'tcx>,
1758 ) -> Option<PlaceKey> {
1759 match place.base {
1760 PlaceBase::Arg(index) => self.callsite_arg_place_with_fields(
1761 checkpoint,
1762 index,
1763 &PlaceKey::from_contract_place(place).fields,
1764 ),
1765 PlaceBase::Local(local) => {
1766 if let Some(index) = checkpoint
1767 .callee
1768 .and_then(|callee| callee_param_index_for_local(self.tcx, callee, local))
1769 {
1770 self.callsite_arg_place_with_fields(
1771 checkpoint,
1772 index,
1773 &PlaceKey::from_contract_place(place).fields,
1774 )
1775 } else {
1776 Some(PlaceKey::from_contract_place(place))
1777 }
1778 }
1779 PlaceBase::Return => Some(PlaceKey::from_contract_place(place)),
1780 }
1781 }
1782
1783 pub(crate) fn callsite_arg_place(
1785 &self,
1786 checkpoint: &Checkpoint<'tcx>,
1787 index: usize,
1788 ) -> Option<PlaceKey> {
1789 let operand = checkpoint.args.get(index)?;
1790 operand_place(operand)
1791 }
1792
1793 pub(crate) fn callsite_arg_smt_term(
1795 &self,
1796 checkpoint: &Checkpoint<'tcx>,
1797 index: usize,
1798 ) -> Option<SmtTerm> {
1799 let expr = self.callsite_arg_expr(checkpoint, index, &[])?;
1800 self.contract_expr_to_smt_term(checkpoint.caller, &expr)
1801 }
1802
1803 pub(crate) fn place_pointee_size(
1805 &self,
1806 caller: rustc_hir::def_id::DefId,
1807 place: &PlaceKey,
1808 ) -> Option<u64> {
1809 if !place.fields.is_empty() {
1810 return None;
1811 }
1812 let local = match place.base {
1813 PlaceBaseKey::Return => Local::from_usize(0),
1814 PlaceBaseKey::Local(local) => Local::from_usize(local),
1815 PlaceBaseKey::Arg(_) => return None,
1816 };
1817 let body = self.tcx.optimized_mir(caller);
1818 let ty = body.local_decls[local].ty;
1819 let pointee = pointee_ty(ty)?;
1820 self.type_layout(caller, pointee).map(|(_, size)| size)
1821 }
1822
1823 pub(crate) fn callsite_arg_place_with_fields(
1825 &self,
1826 checkpoint: &Checkpoint<'tcx>,
1827 index: usize,
1828 fields: &[usize],
1829 ) -> Option<PlaceKey> {
1830 let mut place = self.callsite_arg_place(checkpoint, index)?;
1831 place.fields.extend(fields.iter().copied());
1832 Some(place)
1833 }
1834
1835 pub(crate) fn instantiate_callsite_ty(
1837 &self,
1838 checkpoint: &Checkpoint<'tcx>,
1839 ty: Ty<'tcx>,
1840 ) -> Ty<'tcx> {
1841 let TyKind::Param(param) = ty.kind() else {
1842 return ty;
1843 };
1844
1845 let body = self.tcx.optimized_mir(checkpoint.caller);
1846 let terminator = body.basic_blocks[checkpoint.block].terminator();
1847 let TerminatorKind::Call { func, .. } = &terminator.kind else {
1848 return ty;
1849 };
1850 let Operand::Constant(func_constant) = func else {
1851 return ty;
1852 };
1853 let TyKind::FnDef(_, args) = func_constant.const_.ty().kind() else {
1854 return ty;
1855 };
1856 let Some(arg) = args.get(param.index as usize) else {
1857 return ty;
1858 };
1859 match arg.kind() {
1860 GenericArgKind::Type(actual_ty) => actual_ty,
1861 _ => ty,
1862 }
1863 }
1864
1865 pub(crate) fn instantiate_callsite_const(
1867 &self,
1868 checkpoint: &Checkpoint<'tcx>,
1869 index: u32,
1870 ) -> Option<u128> {
1871 let body = self.tcx.optimized_mir(checkpoint.caller);
1872 let terminator = body.basic_blocks[checkpoint.block].terminator();
1873 let TerminatorKind::Call { func, .. } = &terminator.kind else {
1874 return None;
1875 };
1876 let Operand::Constant(func_constant) = func else {
1877 return None;
1878 };
1879 let TyKind::FnDef(_, args) = func_constant.const_.ty().kind() else {
1880 return None;
1881 };
1882 let arg = args.get(index as usize)?;
1883 match arg.kind() {
1884 GenericArgKind::Const(actual_const) => actual_const
1885 .try_to_target_usize(self.tcx)
1886 .map(|value| value as u128)
1887 .or_else(|| const_int_from_debug(&format!("{actual_const:?}"))),
1888 _ => None,
1889 }
1890 }
1891
1892 pub(crate) fn type_layout(
1894 &self,
1895 caller: rustc_hir::def_id::DefId,
1896 ty: Ty<'tcx>,
1897 ) -> Option<(u64, u64)> {
1898 safe_type_layout(self.tcx, caller, ty)
1899 }
1900
1901 pub(crate) fn required_alignment(
1907 &self,
1908 caller: rustc_hir::def_id::DefId,
1909 ty: Ty<'tcx>,
1910 ) -> Option<u64> {
1911 if let Some((align, _)) = self.type_layout(caller, ty).filter(|(align, _)| *align > 0) {
1912 return Some(align);
1913 }
1914 if let Some(max_align) = self
1915 .generic_candidate_alignments(caller, ty)
1916 .and_then(|candidates| candidates.into_iter().max())
1917 {
1918 return Some(max_align);
1919 }
1920 if let TyKind::Array(elem, _) = ty.kind()
1921 && matches!(elem.kind(), TyKind::Param(_))
1922 {
1923 return Some(0);
1924 }
1925 if matches!(ty.kind(), TyKind::Param(_)) {
1926 return Some(0);
1927 }
1928 if matches!(ty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..)) {
1929 return Some(0);
1930 }
1931 None
1932 }
1933
1934 pub(crate) fn required_size(
1941 &self,
1942 caller: rustc_hir::def_id::DefId,
1943 ty: Ty<'tcx>,
1944 ) -> Option<u64> {
1945 if let TyKind::Array(elem, _) = ty.kind()
1946 && matches!(elem.kind(), TyKind::Param(_))
1947 {
1948 return Some(0);
1949 }
1950 if !matches!(ty.kind(), TyKind::Param(_)) {
1951 return self.type_layout(caller, ty).map(|(_, size)| size);
1952 }
1953 if let Some(max_size) = self
1954 .generic_candidate_sizes(caller, ty)
1955 .and_then(|candidates| candidates.into_iter().max())
1956 {
1957 return Some(max_size);
1958 }
1959 if matches!(ty.kind(), TyKind::Param(_)) {
1960 return Some(0);
1961 }
1962 None
1963 }
1964
1965 pub(crate) fn type_size_class(
1968 &self,
1969 caller: rustc_hir::def_id::DefId,
1970 ty: Ty<'tcx>,
1971 ) -> TypeSizeClass {
1972 if let TyKind::Array(elem, _) = ty.kind()
1973 && matches!(elem.kind(), TyKind::Param(_))
1974 {
1975 return TypeSizeClass::Unknown;
1976 }
1977 if !matches!(ty.kind(), TyKind::Param(_)) {
1978 return match self.type_layout(caller, ty).map(|(_, size)| size) {
1979 Some(0) => TypeSizeClass::Zero,
1980 Some(_) => TypeSizeClass::NonZero,
1981 None => TypeSizeClass::Unknown,
1982 };
1983 }
1984
1985 let Some(sizes) = self.generic_candidate_sizes(caller, ty) else {
1986 return TypeSizeClass::Unknown;
1987 };
1988 if sizes.iter().all(|size| *size == 0) {
1989 TypeSizeClass::Zero
1990 } else if sizes.iter().all(|size| *size > 0) {
1991 TypeSizeClass::NonZero
1992 } else {
1993 TypeSizeClass::Unknown
1994 }
1995 }
1996
1997 fn generic_candidate_alignments(
1998 &self,
1999 caller: rustc_hir::def_id::DefId,
2000 ty: Ty<'tcx>,
2001 ) -> Option<Vec<u64>> {
2002 let candidates = GenericTypeCandidates::for_def(self.tcx, caller);
2003 let alignments = candidates
2004 .candidates_for_ty(ty)?
2005 .iter()
2006 .filter_map(|candidate| self.type_layout(caller, *candidate).map(|(align, _)| align))
2007 .filter(|align| *align > 0)
2008 .collect::<Vec<_>>();
2009 if alignments.is_empty() {
2010 None
2011 } else {
2012 Some(alignments)
2013 }
2014 }
2015
2016 fn generic_candidate_sizes(
2017 &self,
2018 caller: rustc_hir::def_id::DefId,
2019 ty: Ty<'tcx>,
2020 ) -> Option<Vec<u64>> {
2021 let candidates = GenericTypeCandidates::for_def(self.tcx, caller);
2022 let sizes = candidates
2023 .candidates_for_ty(ty)?
2024 .iter()
2025 .filter_map(|candidate| self.type_layout(caller, *candidate).map(|(_, size)| size))
2026 .collect::<Vec<_>>();
2027 if sizes.is_empty() { None } else { Some(sizes) }
2028 }
2029}
2030
2031#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2033pub(crate) enum TypeSizeClass {
2034 Zero,
2035 NonZero,
2036 Unknown,
2037}
2038
2039#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2040enum SliceIndexKind {
2041 Scalar,
2042 Range,
2043 RangeFrom,
2044 RangeTo,
2045 RangeFull,
2046 RangeInclusive,
2047 RangeToInclusive,
2048}
2049
2050#[derive(Clone, Debug)]
2052pub enum SmtObligation {
2053 Aligned {
2055 place: PlaceKey,
2056 align: u64,
2057 ty_name: String,
2058 },
2059 NonZero { place: PlaceKey },
2061 Range {
2063 value: PlaceKey,
2064 lower: i128,
2065 upper: Option<i128>,
2066 },
2067 InBounds {
2069 place: PlaceKey,
2070 ty_name: String,
2071 elem_size: u64,
2072 access_count: SmtTerm,
2073 },
2074 PointerRangeInBounds {
2077 place: PlaceKey,
2078 ty_name: String,
2079 lower_delta: SmtTerm,
2080 upper_delta: SmtTerm,
2081 },
2082 Initialized {
2084 place: PlaceKey,
2085 ty_name: String,
2086 elements: SmtTerm,
2087 elem_size: Option<u64>,
2089 array_elem_size: Option<u64>,
2091 array_len_term: Option<SmtTerm>,
2093 },
2094 Allocated {
2096 place: PlaceKey,
2097 ty_name: String,
2098 elements: SmtTerm,
2099 },
2100 NonOverlapping {
2102 left: PlaceKey,
2103 right: PlaceKey,
2104 left_count: SmtTerm,
2105 right_count: SmtTerm,
2106 elem_size: u64,
2107 },
2108 Predicate { predicates: Vec<SmtPredicate> },
2110}
2111
2112impl SmtObligation {
2113 pub fn describe(&self) -> String {
2115 match self {
2116 SmtObligation::Aligned {
2117 place,
2118 align,
2119 ty_name,
2120 } => {
2121 format!(
2122 "Align({}, {}, {}-byte boundary)",
2123 place_label(place),
2124 ty_name,
2125 align
2126 )
2127 }
2128 SmtObligation::NonZero { place } => format!("NonZero({})", place_label(place)),
2129 SmtObligation::Range {
2130 value,
2131 lower,
2132 upper,
2133 } => match upper {
2134 Some(upper) => format!("Range({}, {lower}..{upper})", place_label(value)),
2135 None => format!("Range({}, {lower}..)", place_label(value)),
2136 },
2137 SmtObligation::InBounds {
2138 place,
2139 ty_name,
2140 elem_size,
2141 access_count,
2142 } => format!(
2143 "InBound({}, {}, {} element(s), {} byte(s) each)",
2144 place_label(place),
2145 ty_name,
2146 access_count.describe(),
2147 elem_size
2148 ),
2149 SmtObligation::PointerRangeInBounds {
2150 place,
2151 ty_name,
2152 lower_delta,
2153 upper_delta,
2154 } => format!(
2155 "PointerRangeInBound({}, {}, lower={}, upper={})",
2156 place_label(place),
2157 ty_name,
2158 lower_delta.describe(),
2159 upper_delta.describe()
2160 ),
2161 SmtObligation::Initialized {
2162 place,
2163 ty_name,
2164 elements,
2165 ..
2166 } => format!(
2167 "Init({}, {}, {} element(s))",
2168 place_label(place),
2169 ty_name,
2170 elements.describe()
2171 ),
2172 SmtObligation::Allocated {
2173 place,
2174 ty_name,
2175 elements,
2176 } => format!(
2177 "Allocated({}, {}, {} element(s))",
2178 place_label(place),
2179 ty_name,
2180 elements.describe()
2181 ),
2182 SmtObligation::NonOverlapping {
2183 left,
2184 right,
2185 left_count,
2186 right_count,
2187 elem_size,
2188 } => format!(
2189 "NonOverlap({}, {}, left={} element(s), right={} element(s), elem_size={})",
2190 place_label(left),
2191 place_label(right),
2192 left_count.describe(),
2193 right_count.describe(),
2194 elem_size
2195 ),
2196 SmtObligation::Predicate { predicates } => {
2197 let rendered = predicates
2198 .iter()
2199 .map(SmtPredicate::describe)
2200 .collect::<Vec<_>>()
2201 .join(" && ");
2202 format!("ValidNum({rendered})")
2203 }
2204 }
2205 }
2206}
2207
2208#[derive(Clone, Debug)]
2210pub enum SmtTerm {
2211 Place(PlaceKey),
2212 Value(String),
2213 Const(u64),
2214 ConstParam(String),
2217 Add(Box<SmtTerm>, Box<SmtTerm>),
2218 Sub(Box<SmtTerm>, Box<SmtTerm>),
2219 Mul(Box<SmtTerm>, Box<SmtTerm>),
2220 Div(Box<SmtTerm>, Box<SmtTerm>),
2221 Rem(Box<SmtTerm>, Box<SmtTerm>),
2222}
2223
2224impl SmtTerm {
2225 pub fn describe(&self) -> String {
2227 match self {
2228 SmtTerm::Place(place) => place_label(place),
2229 SmtTerm::Value(value) => value.clone(),
2230 SmtTerm::ConstParam(value) => value.clone(),
2231 SmtTerm::Const(value) => value.to_string(),
2232 SmtTerm::Add(lhs, rhs) => format!("({} + {})", lhs.describe(), rhs.describe()),
2233 SmtTerm::Sub(lhs, rhs) => format!("({} - {})", lhs.describe(), rhs.describe()),
2234 SmtTerm::Mul(lhs, rhs) => format!("({} * {})", lhs.describe(), rhs.describe()),
2235 SmtTerm::Div(lhs, rhs) => format!("({} / {})", lhs.describe(), rhs.describe()),
2236 SmtTerm::Rem(lhs, rhs) => format!("({} % {})", lhs.describe(), rhs.describe()),
2237 }
2238 }
2239}
2240
2241#[derive(Clone, Debug)]
2243pub enum SmtPredicate {
2244 Eq(SmtTerm, SmtTerm),
2245 Ne(SmtTerm, SmtTerm),
2246 Le(SmtTerm, SmtTerm),
2247 Lt(SmtTerm, SmtTerm),
2248 Ge(SmtTerm, SmtTerm),
2249 Gt(SmtTerm, SmtTerm),
2250 And(Vec<SmtPredicate>),
2251 Divisible {
2252 term: SmtTerm,
2253 modulus: u64,
2254 },
2255 InBounds {
2256 index: SmtTerm,
2257 access_count: SmtTerm,
2258 len: SmtTerm,
2259 },
2260 NonOverlapping {
2261 left: SmtTerm,
2262 right: SmtTerm,
2263 left_count: SmtTerm,
2264 right_count: SmtTerm,
2265 elem_size: u64,
2266 },
2267 Not(Box<SmtPredicate>),
2268 Custom(String),
2269}
2270
2271impl SmtPredicate {
2272 pub fn describe(&self) -> String {
2274 match self {
2275 SmtPredicate::Eq(lhs, rhs) => format!("{} == {}", lhs.describe(), rhs.describe()),
2276 SmtPredicate::Ne(lhs, rhs) => format!("{} != {}", lhs.describe(), rhs.describe()),
2277 SmtPredicate::Le(lhs, rhs) => format!("{} <= {}", lhs.describe(), rhs.describe()),
2278 SmtPredicate::Lt(lhs, rhs) => format!("{} < {}", lhs.describe(), rhs.describe()),
2279 SmtPredicate::Ge(lhs, rhs) => format!("{} >= {}", lhs.describe(), rhs.describe()),
2280 SmtPredicate::Gt(lhs, rhs) => format!("{} > {}", lhs.describe(), rhs.describe()),
2281 SmtPredicate::And(predicates) => predicates
2282 .iter()
2283 .map(SmtPredicate::describe)
2284 .collect::<Vec<_>>()
2285 .join(" && "),
2286 SmtPredicate::Divisible { term, modulus } => {
2287 format!("{} % {modulus} == 0", term.describe())
2288 }
2289 SmtPredicate::InBounds {
2290 index,
2291 access_count,
2292 len,
2293 } => format!(
2294 "0 <= {} && {} + {} <= {}",
2295 index.describe(),
2296 index.describe(),
2297 access_count.describe(),
2298 len.describe()
2299 ),
2300 SmtPredicate::NonOverlapping {
2301 left,
2302 right,
2303 left_count,
2304 right_count,
2305 elem_size,
2306 } => format!(
2307 "{} + {} * {} <= {} || {} + {} * {} <= {}",
2308 left.describe(),
2309 left_count.describe(),
2310 elem_size,
2311 right.describe(),
2312 right.describe(),
2313 right_count.describe(),
2314 elem_size,
2315 left.describe()
2316 ),
2317 SmtPredicate::Not(predicate) => format!("not({})", predicate.describe()),
2318 SmtPredicate::Custom(text) => text.clone(),
2319 }
2320 }
2321}
2322
2323#[derive(Clone, Debug)]
2325pub struct SmtQuery {
2326 pub obligation: SmtObligation,
2328 pub assumptions: Vec<SmtPredicate>,
2330 pub negated_goal: SmtPredicate,
2332}
2333
2334impl SmtQuery {
2335 pub fn new(
2337 obligation: SmtObligation,
2338 assumptions: Vec<SmtPredicate>,
2339 negated_goal: SmtPredicate,
2340 ) -> Self {
2341 Self {
2342 obligation,
2343 assumptions,
2344 negated_goal,
2345 }
2346 }
2347}
2348
2349#[derive(Clone, Debug)]
2351pub struct SmtCheckResult {
2352 pub result: CheckResult,
2354 pub query: Option<SmtQuery>,
2356 pub notes: Vec<String>,
2358}
2359
2360impl SmtCheckResult {
2361 pub fn proved(note: impl Into<String>) -> Self {
2363 Self {
2364 result: CheckResult::Proved,
2365 query: None,
2366 notes: vec![note.into()],
2367 }
2368 }
2369
2370 pub fn unknown(note: impl Into<String>) -> Self {
2372 Self {
2373 result: CheckResult::Unknown,
2374 query: None,
2375 notes: vec![note.into()],
2376 }
2377 }
2378
2379 pub fn with_query(mut self, query: SmtQuery) -> Self {
2381 self.query = Some(query);
2382 self
2383 }
2384
2385 pub fn with_note(mut self, note: impl Into<String>) -> Self {
2387 self.notes.push(note.into());
2388 self
2389 }
2390
2391 pub fn describe(&self) -> String {
2393 let mut lines = vec![format!(" smt check: {:?}", self.result)];
2394 if let Some(query) = &self.query {
2395 lines.push(format!(" |_ goal: {}", query.obligation.describe()));
2396 if !query.assumptions.is_empty() {
2397 lines.push(" |_ known facts:".to_string());
2398 for assumption in &query.assumptions {
2399 lines.push(format!(" | |_ {}", assumption.describe()));
2400 }
2401 }
2402 lines.push(format!(
2403 " |_ checked: {}",
2404 query.negated_goal.describe()
2405 ));
2406 }
2407 if let Some((first, rest)) = self.notes.split_first() {
2408 lines.push(format!(" |_ verdict: {first}"));
2409 for note in rest {
2410 if let Some(hint) = note.strip_prefix("hint: ") {
2411 lines.push(format!(" |_ hint: {hint}"));
2412 } else {
2413 lines.push(format!(" |_ detail: {note}"));
2414 }
2415 }
2416 }
2417 lines.join("\n")
2418 }
2419}
2420
2421fn failed_smt(note: impl Into<String>) -> SmtCheckResult {
2422 SmtCheckResult {
2423 result: CheckResult::Failed,
2424 query: None,
2425 notes: vec![note.into()],
2426 }
2427}
2428
2429fn smt_term_const_u64(term: &SmtTerm) -> Option<u64> {
2430 match term {
2431 SmtTerm::Const(value) => Some(*value),
2432 _ => None,
2433 }
2434}
2435
2436fn pointer_range_negated_goal(
2437 index: SmtTerm,
2438 lower_delta: SmtTerm,
2439 upper_delta: SmtTerm,
2440 len: SmtTerm,
2441) -> SmtPredicate {
2442 let lower_index = SmtTerm::Add(Box::new(index.clone()), Box::new(lower_delta));
2443 let upper_index = SmtTerm::Add(Box::new(index.clone()), Box::new(upper_delta));
2444 SmtPredicate::Not(Box::new(SmtPredicate::And(vec![
2445 SmtPredicate::Ge(index.clone(), SmtTerm::Const(0)),
2446 SmtPredicate::Le(index, len.clone()),
2447 SmtPredicate::Ge(lower_index, SmtTerm::Const(0)),
2448 SmtPredicate::Le(upper_index, len),
2449 ])))
2450}
2451
2452pub(crate) struct SmtModel<'a, 'ctx, 'tcx> {
2454 tcx: TyCtxt<'tcx>,
2455 checkpoint: &'a Checkpoint<'tcx>,
2456 forward: &'a ForwardVisitResult<'tcx>,
2457 ctx: &'ctx Context,
2458 place_terms: HashMap<PlaceKey, Int<'ctx>>,
2459 local_terms: HashMap<usize, Int<'ctx>>,
2463 symbolic_align_terms: HashMap<String, Int<'ctx>>,
2464 symbolic_len_terms: HashMap<String, Int<'ctx>>,
2465 const_terms: HashMap<String, Int<'ctx>>,
2466 assumptions: Vec<SmtPredicate>,
2467 has_index_access_assumptions: bool,
2469}
2470
2471impl<'a, 'ctx, 'tcx> SmtModel<'a, 'ctx, 'tcx> {
2472 pub(crate) fn new(
2474 tcx: TyCtxt<'tcx>,
2475 checkpoint: &'a Checkpoint<'tcx>,
2476 forward: &'a ForwardVisitResult<'tcx>,
2477 ctx: &'ctx Context,
2478 ) -> Self {
2479 Self {
2480 tcx,
2481 checkpoint,
2482 forward,
2483 ctx,
2484 place_terms: HashMap::new(),
2485 local_terms: HashMap::new(),
2486 symbolic_align_terms: HashMap::new(),
2487 symbolic_len_terms: HashMap::new(),
2488 const_terms: HashMap::new(),
2489 assumptions: Vec::new(),
2490 has_index_access_assumptions: false,
2491 }
2492 }
2493
2494 pub(crate) fn symbolic_align_term(&mut self, ty_name: &str) -> Int<'ctx> {
2496 let ty_name = normalize_init_ty_name(ty_name);
2497 if let Some(term) = self.symbolic_align_terms.get(&ty_name) {
2498 return term.clone();
2499 }
2500 let term = Int::new_const(self.ctx, format!("align_{ty_name}"));
2501 self.symbolic_align_terms
2502 .insert(ty_name.to_string(), term.clone());
2503 term
2504 }
2505
2506 fn symbolic_len_term(&mut self, len_key: &str) -> Int<'ctx> {
2507 let name = sanitize_smt_name(len_key);
2508 if let Some(term) = self.symbolic_len_terms.get(&name) {
2509 return term.clone();
2510 }
2511 let term = Int::new_const(self.ctx, name.as_str());
2512 self.symbolic_len_terms.insert(name, term.clone());
2513 term
2514 }
2515
2516 fn has_equivalent_contract_fact(&mut self, place: &PlaceKey, _kind: PropertyKind) -> bool {
2522 let Some(target_term) = self.term_for_place(place) else {
2523 return false;
2524 };
2525 for fact in &self.forward.facts {
2526 let StateFact::Contract(property) = fact else {
2527 continue;
2528 };
2529 let is_target_kind =
2530 matches!(property.kind, PropertyKind::InBound | PropertyKind::Init);
2531 if !is_target_kind {
2532 continue;
2533 }
2534 let Some(contract_target) = property.args.first().and_then(|arg| {
2535 if let PropertyArg::Place(contract_place) = arg {
2536 let mut key = PlaceKey::from_contract_place(contract_place);
2537 if let PlaceBaseKey::Arg(index) = key.base {
2538 key.base = PlaceBaseKey::Local(index + 1);
2539 }
2540 Some(key)
2541 } else {
2542 None
2543 }
2544 }) else {
2545 continue;
2546 };
2547 let Some(contract_term) = self.term_for_place(&contract_target) else {
2548 continue;
2549 };
2550 if target_term.eq(&contract_term) {
2551 return true;
2552 }
2553 }
2554 false
2555 }
2556
2557 fn contract_predicate_to_smt(
2558 &mut self,
2559 predicate: &NumericPredicate<'tcx>,
2560 ) -> Option<SmtPredicate> {
2561 let lhs = self.smt_term_from_contract_expr(&predicate.lhs)?;
2562 let rhs = self.smt_term_from_contract_expr(&predicate.rhs)?;
2563 Some(match predicate.op {
2564 RelOp::Eq => SmtPredicate::Eq(lhs, rhs),
2565 RelOp::Ne => SmtPredicate::Ne(lhs, rhs),
2566 RelOp::Lt => SmtPredicate::Lt(lhs, rhs),
2567 RelOp::Le => SmtPredicate::Le(lhs, rhs),
2568 RelOp::Gt => SmtPredicate::Gt(lhs, rhs),
2569 RelOp::Ge => SmtPredicate::Ge(lhs, rhs),
2570 })
2571 }
2572
2573 fn smt_term_from_contract_expr(
2574 &mut self,
2575 expr: &ContractExpr<'tcx>,
2576 ) -> Option<SmtTerm> {
2577 match expr {
2578 ContractExpr::Place(place) => {
2579 let mut key = PlaceKey::from_contract_place(place);
2580 if let PlaceBaseKey::Arg(index) = key.base {
2581 key.base = PlaceBaseKey::Local(index + 1);
2582 }
2583 Some(SmtTerm::Place(key))
2584 }
2585 ContractExpr::Const(value) => {
2586 u64::try_from(*value).ok().map(SmtTerm::Const)
2587 }
2588 ContractExpr::ConstParam { name, .. } => {
2589 Some(SmtTerm::ConstParam(name.clone()))
2590 }
2591 ContractExpr::Len(inner) => {
2592 let origin = match inner.as_ref() {
2593 ContractExpr::Place(cp) => {
2594 let mut key = PlaceKey::from_contract_place(cp);
2595 if let PlaceBaseKey::Arg(index) = key.base {
2596 key.base = PlaceBaseKey::Local(index + 1);
2597 }
2598 let val = AbstractValue::Place(key.clone());
2599 self.origin_key_for_value(&val, &mut TraceSeen::new())
2600 }
2601 _ => None,
2602 };
2603
2604 let origin_str = origin.clone().unwrap_or_default();
2607 let mut first_len_dest: Option<PlaceKey> = None;
2608 for fact in &self.forward.facts {
2609 let StateFact::Call(call) = fact else { continue };
2610 let is_len_call = call.func.ends_with("::len")
2611 || call.func.contains("::len(");
2612 if !is_len_call {
2613 continue;
2614 }
2615 for effect in &call.effects {
2616 let crate::verify::call_summary::CallEffect::ReturnLengthOfArg {
2617 arg,
2618 } = effect
2619 else {
2620 continue;
2621 };
2622 let dest = PlaceKey {
2623 base: PlaceBaseKey::Local(call.destination.as_usize()),
2624 fields: Vec::new(),
2625 };
2626 if first_len_dest.is_none() {
2628 first_len_dest = Some(dest.clone());
2629 }
2630 let Some(arg_value) = call.args.get(*arg) else {
2631 continue;
2632 };
2633 let arg_origin = self.origin_key_for_value(
2634 arg_value,
2635 &mut TraceSeen::new(),
2636 );
2637 let matches = arg_origin.as_deref() == Some(&origin_str)
2638 || arg_origin
2639 .as_deref()
2640 .is_some_and(|ao| ao.starts_with(&origin_str));
2641 if matches {
2642 return Some(SmtTerm::Place(dest));
2643 }
2644 }
2645 }
2646 if let Some(dest) = first_len_dest {
2648 return Some(SmtTerm::Place(dest));
2649 }
2650
2651 Some(SmtTerm::Value(format!("len({})", origin_str)))
2652 }
2653 ContractExpr::Binary { op, lhs, rhs } => {
2654 let lhs = Box::new(self.smt_term_from_contract_expr(lhs)?);
2655 let rhs = Box::new(self.smt_term_from_contract_expr(rhs)?);
2656 Some(match op {
2657 NumericOp::Add => SmtTerm::Add(lhs, rhs),
2658 NumericOp::Sub => SmtTerm::Sub(lhs, rhs),
2659 NumericOp::Mul => SmtTerm::Mul(lhs, rhs),
2660 NumericOp::Div => SmtTerm::Div(lhs, rhs),
2661 NumericOp::Rem => SmtTerm::Rem(lhs, rhs),
2662 _ => return None,
2663 })
2664 }
2665 _ => None,
2666 }
2667 }
2668
2669 fn value_to_int(&mut self, value: &AbstractValue<'tcx>) -> Option<Int<'ctx>> {
2670 match value {
2671 AbstractValue::ConstInt(v) => u64::try_from(*v).ok().map(|v| Int::from_u64(self.ctx, v)),
2672 AbstractValue::Place(place) => self.term_for_place(place),
2673 AbstractValue::ConstParam(name) => Some(Int::new_const(self.ctx, format!("const_{name}").as_str())),
2674 AbstractValue::Const(name) => Some(Int::new_const(self.ctx, sanitize_smt_name(name).as_str())),
2675 _ => None,
2676 }
2677 }
2678
2679 pub(crate) fn assert_forward_facts(&mut self, solver: &Solver<'ctx>) {
2681 for fact in &self.forward.facts {
2682 match fact {
2683 StateFact::PointsTo { pointer, source } => {
2684 self.assert_place_non_zero(
2685 solver,
2686 pointer,
2687 "created from a reference/raw pointer",
2688 );
2689 let ptr_pointee_str = self.place_ty(pointer)
2696 .and_then(|ty| pointee_ty_str(ty))
2697 .map(|s| normalize_init_ty_name(&s));
2698 let src_pointee_str = self.place_ty(source)
2699 .and_then(|ty| pointee_ty_str(ty))
2700 .map(|s| normalize_init_ty_name(&s));
2701 if ptr_pointee_str == src_pointee_str {
2702 self.assert_place_alignment(solver, pointer);
2703 }
2704 self.assert_place_alignment(solver, source);
2705 self.assert_length_alias(solver, pointer, source);
2706 }
2707 StateFact::Call(call) => {
2708 if is_as_ptr_call(&call.func) {
2709 let place = PlaceKey {
2710 base: PlaceBaseKey::Local(call.destination.as_usize()),
2711 fields: Vec::new(),
2712 };
2713 self.assert_place_non_zero(solver, &place, "returned by as_ptr");
2714 self.assert_place_alignment(solver, &place);
2715 }
2716 self.record_call_effect_assumptions(call);
2717 if call.func.contains("exact_div") {
2722 let dest = PlaceKey {
2723 base: PlaceBaseKey::Local(call.destination.as_usize()),
2724 fields: Vec::new(),
2725 };
2726 if let Some(dest_term) = self.term_for_place(&dest)
2727 && let Some(arg0) = call.args.get(0)
2728 {
2729 let cursor = self.call_definition_cursor(call);
2730 if let Some(arg0_term) = self.term_for_value_at(
2731 arg0,
2732 cursor,
2733 &mut TraceSeen::new(),
2734 ) {
2735 solver.assert(&dest_term.le(&arg0_term));
2736 self.place_terms.insert(dest.clone(), dest_term);
2740 self.assumptions.push(SmtPredicate::Le(
2741 SmtTerm::Place(dest),
2742 SmtTerm::Value(value_label(arg0)),
2743 ));
2744 }
2745 }
2746 }
2747 }
2748 StateFact::KnownNonZero { place, reason } => {
2749 self.assert_place_non_zero(solver, place, reason);
2750 }
2751 StateFact::KnownAligned {
2752 place,
2753 align,
2754 ty_name,
2755 reason,
2756 } => {
2757 self.assert_known_alignment(solver, place, *align, ty_name, reason);
2758 }
2759 StateFact::KnownInit {
2760 place,
2761 ty_name,
2762 elements,
2763 reason,
2764 } => {
2765 self.assumptions.push(SmtPredicate::Custom(format!(
2766 "{} initialized for {ty_name}, {elements} element(s) ({reason})",
2767 place_label(place)
2768 )));
2769 }
2770 StateFact::KnownAllocated {
2771 place,
2772 object,
2773 ty_name,
2774 elements,
2775 reason,
2776 } => {
2777 self.assumptions.push(SmtPredicate::Custom(format!(
2778 "{} allocated in {} for {ty_name}, {elements} element(s) ({reason})",
2779 place_label(place),
2780 place_label(object)
2781 )));
2782 }
2783 StateFact::KnownConst {
2784 place,
2785 value,
2786 reason,
2787 } => {
2788 self.assert_known_const(solver, place, *value, reason);
2789 }
2790 StateFact::BranchEq { value, equals, cmp_op, cmp_lhs, cmp_rhs } => {
2791 if let Some(term) = self.term_for_value(value, &mut HashSet::new()) {
2792 let expected = Int::from_u64(self.ctx, *equals as u64);
2793 solver.assert(&term._eq(&expected));
2794 self.assumptions.push(SmtPredicate::Eq(
2795 SmtTerm::Value(value_label(value)),
2796 SmtTerm::Const(*equals as u64),
2797 ));
2798 }
2799 if let (Some(op), Some(lhs), Some(rhs)) = (cmp_op, cmp_lhs, cmp_rhs) {
2800 if let (Some(lhs_t), Some(rhs_t)) = (
2801 self.value_to_int(lhs), self.value_to_int(rhs),
2802 ) {
2803 let holds = *equals == 1;
2804 match (op, holds) {
2805 (BinOp::Eq, true) | (BinOp::Ne, false) => solver.assert(&lhs_t._eq(&rhs_t)),
2806 (BinOp::Ne, true) | (BinOp::Eq, false) => solver.assert(&lhs_t._eq(&rhs_t).not()),
2807 (BinOp::Lt, true) | (BinOp::Ge, false) => solver.assert(&lhs_t.lt(&rhs_t)),
2808 (BinOp::Le, true) | (BinOp::Gt, false) => solver.assert(&lhs_t.le(&rhs_t)),
2809 (BinOp::Gt, true) | (BinOp::Le, false) => solver.assert(&lhs_t.gt(&rhs_t)),
2810 (BinOp::Ge, true) | (BinOp::Lt, false) => solver.assert(&lhs_t.ge(&rhs_t)),
2811 _ => {}
2812 }
2813 }
2814 }
2815 }
2816 StateFact::Cast { target, source, .. } => {
2817 self.assumptions.push(SmtPredicate::Eq(
2818 SmtTerm::Place(target.clone()),
2819 SmtTerm::Value(value_label(source)),
2820 ));
2821 if let AbstractValue::Place(source_place) = source {
2822 if self
2823 .place_ty(source_place)
2824 .is_some_and(|ty| pointee_ty(ty).is_some())
2825 {
2826 self.assert_place_alignment(solver, source_place);
2827 }
2828 }
2829 if let Some(term) = self.term_for_value(source, &mut HashSet::new()) {
2830 self.place_terms.insert(target.clone(), term);
2831 }
2832 }
2833 StateFact::Binary {
2834 target,
2835 op,
2836 lhs,
2837 rhs,
2838 } => {
2839 self.assumptions.push(SmtPredicate::Eq(
2840 SmtTerm::Place(target.clone()),
2841 SmtTerm::Value(format!(
2842 "({} {} {})",
2843 value_label(lhs),
2844 binop_label(*op),
2845 value_label(rhs)
2846 )),
2847 ));
2848 }
2849 StateFact::Contract(property) => match property.kind {
2850 PropertyKind::Align => {
2851 let Some(target) = (|| {
2852 let arg = property.args.first()?;
2853 let PropertyArg::Place(place) = arg else {
2854 return None;
2855 };
2856 let mut key = PlaceKey::from_contract_place(place);
2857 if let PlaceBaseKey::Arg(index) = key.base {
2858 key.base = PlaceBaseKey::Local(index + 1);
2859 }
2860 Some(key)
2861 })() else {
2862 continue;
2863 };
2864 let Some(required_ty) = property.args.iter().find_map(|arg| {
2865 if let PropertyArg::Ty(ty) = arg {
2866 Some(*ty)
2867 } else {
2868 None
2869 }
2870 }) else {
2871 continue;
2872 };
2873 let Some((align, _)) = self.type_layout(required_ty) else {
2874 continue;
2875 };
2876 if align == 0 {
2877 let ty_name = format!("{required_ty:?}");
2878 if let Some(term) = self.term_for_place(&target) {
2879 let align_term = self.symbolic_align_term(&ty_name);
2880 let zero = Int::from_u64(self.ctx, 0);
2881 solver.assert(&term.modulo(&align_term)._eq(&zero));
2882 self.assumptions.push(SmtPredicate::Custom(format!(
2883 "{} aligned for {ty_name} (symbolic, struct-invariant)",
2884 place_label(&target)
2885 )));
2886 }
2887 } else {
2888 self.assert_known_alignment(
2889 solver,
2890 &target,
2891 align,
2892 &format!("{required_ty:?}"),
2893 "struct-invariant",
2894 );
2895 }
2896 }
2897 PropertyKind::NonNull => {
2898 let Some(target) = (|| {
2899 let arg = property.args.first()?;
2900 let PropertyArg::Place(place) = arg else {
2901 return None;
2902 };
2903 let mut key = PlaceKey::from_contract_place(place);
2904 if let PlaceBaseKey::Arg(index) = key.base {
2905 key.base = PlaceBaseKey::Local(index + 1);
2906 }
2907 Some(key)
2908 })() else {
2909 continue;
2910 };
2911 self.assert_place_non_zero(solver, &target, "caller-contract");
2912 }
2913 PropertyKind::InBound => {
2914 if let Some(PropertyArg::Expr(ContractExpr::IndexAccess {
2915 slice,
2916 index,
2917 })) = property.args.first()
2918 {
2919 let slice_key = match slice.as_ref() {
2920 ContractExpr::Place(place) => {
2921 let mut key = PlaceKey::from_contract_place(place);
2922 if let PlaceBaseKey::Arg(ix) = key.base {
2923 key.base = PlaceBaseKey::Local(ix + 1);
2924 }
2925 key
2926 }
2927 _ => {
2928 continue;
2929 }
2930 };
2931 let slice_label = place_label(&slice_key);
2932 let len =
2933 SmtTerm::Value(format!("len({slice_label})"));
2934
2935 if let ContractExpr::Place(place) = index.as_ref() {
2936 let mut index_key =
2937 PlaceKey::from_contract_place(place);
2938 if let PlaceBaseKey::Arg(ix) = index_key.base {
2939 index_key.base = PlaceBaseKey::Local(ix + 1);
2940 }
2941 if let Some(ty) = self.place_ty(&index_key)
2942 && let TyKind::Array(_, len_const) = ty.kind()
2943 {
2944 if let Some(array_len) =
2945 len_const.try_to_target_usize(self.tcx)
2946 {
2947 for j in 0..array_len {
2948 let elem_key = PlaceKey {
2949 base: index_key.base.clone(),
2950 fields: vec![j as usize],
2951 };
2952 let index_term =
2953 SmtTerm::Place(elem_key);
2954 let lower = index_term.clone();
2955 let upper = SmtTerm::Add(
2956 Box::new(index_term),
2957 Box::new(SmtTerm::Const(1)),
2958 );
2959 let preds = vec![
2960 SmtPredicate::Le(
2961 SmtTerm::Const(0),
2962 lower.clone(),
2963 ),
2964 SmtPredicate::Le(
2965 lower.clone(),
2966 upper.clone(),
2967 ),
2968 SmtPredicate::Le(
2969 upper,
2970 len.clone(),
2971 ),
2972 ];
2973 for pred in &preds {
2974 if let Some(bool_term) =
2975 self.bool_for_predicate(pred)
2976 {
2977 solver.assert(&bool_term);
2978 }
2979 self.assumptions
2980 .push(pred.clone());
2981 }
2982 }
2983 self.has_index_access_assumptions =
2984 true;
2985 continue;
2986 }
2987 }
2988 for term in self.const_terms.values() {
2992 solver.assert(&term.ge(&Int::from_u64(self.ctx, 0)));
2993 }
2994 }
2995
2996 let index_term = match index.as_ref() {
2997 ContractExpr::Place(place) => {
2998 let mut key =
2999 PlaceKey::from_contract_place(place);
3000 if let PlaceBaseKey::Arg(ix) = key.base {
3001 key.base = PlaceBaseKey::Local(ix + 1);
3002 }
3003 Some(SmtTerm::Place(key))
3004 }
3005 ContractExpr::Const(value) => {
3006 u64::try_from(*value).ok().map(SmtTerm::Const)
3007 }
3008 _ => None,
3009 };
3010 let Some(index_term) = index_term else {
3011 continue;
3012 };
3013 let lower = index_term.clone();
3014 let upper = SmtTerm::Add(
3015 Box::new(index_term),
3016 Box::new(SmtTerm::Const(1)),
3017 );
3018 let preds = vec![
3019 SmtPredicate::Le(
3020 SmtTerm::Const(0),
3021 lower.clone(),
3022 ),
3023 SmtPredicate::Le(
3024 lower.clone(),
3025 upper.clone(),
3026 ),
3027 SmtPredicate::Le(upper, len),
3028 ];
3029 for pred in &preds {
3030 if let Some(bool_term) =
3031 self.bool_for_predicate(pred)
3032 {
3033 solver.assert(&bool_term);
3034 }
3035 self.assumptions.push(pred.clone());
3036 }
3037 self.has_index_access_assumptions = true;
3038 continue;
3039 }
3040 if property.args.len() == 3 {
3043 let slice_place = match property.args.get(0) {
3044 Some(PropertyArg::Place(place)) => place,
3045 _ => {
3046 continue;
3047 }
3048 };
3049 let index_expr = match property.args.get(2) {
3050 Some(PropertyArg::Expr(expr)) => expr,
3051 _ => {
3052 continue;
3053 }
3054 };
3055 let slice_label = {
3056 let mut key = PlaceKey::from_contract_place(slice_place);
3057 if let PlaceBaseKey::Arg(ix) = key.base {
3058 key.base = PlaceBaseKey::Local(ix + 1);
3059 }
3060 place_label(&key)
3061 };
3062 let len = SmtTerm::Value(format!("len({slice_label})"));
3063 let index_term = match index_expr {
3064 ContractExpr::Place(place) => {
3065 let mut key = PlaceKey::from_contract_place(place);
3066 if let PlaceBaseKey::Arg(ix) = key.base {
3067 key.base = PlaceBaseKey::Local(ix + 1);
3068 }
3069 Some(SmtTerm::Place(key))
3070 }
3071 ContractExpr::Const(value) => {
3072 u64::try_from(*value).ok().map(SmtTerm::Const)
3073 }
3074 _ => None,
3075 };
3076 let Some(index_term) = index_term else {
3077 continue;
3078 };
3079 let lower = index_term.clone();
3080 let upper = SmtTerm::Add(
3081 Box::new(index_term),
3082 Box::new(SmtTerm::Const(1)),
3083 );
3084 let preds = vec![
3085 SmtPredicate::Le(SmtTerm::Const(0), lower.clone()),
3086 SmtPredicate::Le(lower.clone(), upper.clone()),
3087 SmtPredicate::Le(upper, len),
3088 ];
3089 for pred in &preds {
3090 if let Some(bool_term) = self.bool_for_predicate(pred) {
3091 solver.assert(&bool_term);
3092 }
3093 self.assumptions.push(pred.clone());
3094 }
3095 self.has_index_access_assumptions = true;
3096 continue;
3097 }
3098 let Some(target) = (|| {
3099 let arg = property.args.first()?;
3100 let PropertyArg::Place(place) = arg else {
3101 return None;
3102 };
3103 let mut key = PlaceKey::from_contract_place(place);
3104 if let PlaceBaseKey::Arg(index) = key.base {
3105 key.base = PlaceBaseKey::Local(index + 1);
3106 }
3107 Some(key)
3108 })() else {
3109 continue;
3110 };
3111 let Some(required_ty) = property.args.iter().find_map(|arg| {
3112 if let PropertyArg::Ty(ty) = arg {
3113 Some(*ty)
3114 } else {
3115 None
3116 }
3117 }) else {
3118 continue;
3119 };
3120 let Some((_, elem_size)) = self.type_layout(required_ty) else {
3121 continue;
3122 };
3123 let access_count = property
3124 .args
3125 .iter()
3126 .rev()
3127 .find_map(|arg| {
3128 let PropertyArg::Expr(ContractExpr::Const(value)) = arg else {
3129 return None;
3130 };
3131 u64::try_from(*value).ok()
3132 })
3133 .unwrap_or(0);
3134 self.assumptions.push(SmtPredicate::InBounds {
3135 index: SmtTerm::Const(0),
3136 access_count: SmtTerm::Const(access_count),
3137 len: SmtTerm::Value(format!("precond_len_{}", place_label(&target))),
3138 });
3139 if elem_size > 0 && access_count > 0 {
3140 self.assumptions.push(SmtPredicate::Custom(format!(
3141 "InBound({}, T, {access_count}) holds (caller-contract, elem_size={elem_size})",
3142 place_label(&target)
3143 )));
3144 } else {
3145 self.assumptions.push(SmtPredicate::Custom(format!(
3146 "InBound({}, T, {access_count}) holds (caller-contract, symbolic)",
3147 place_label(&target)
3148 )));
3149 }
3150 }
3151 PropertyKind::Init => {
3152 let Some(target) = (|| {
3153 let arg = property.args.first()?;
3154 let PropertyArg::Place(place) = arg else {
3155 return None;
3156 };
3157 let mut key = PlaceKey::from_contract_place(place);
3158 if let PlaceBaseKey::Arg(index) = key.base {
3159 key.base = PlaceBaseKey::Local(index + 1);
3160 }
3161 Some(key)
3162 })() else {
3163 continue;
3164 };
3165 let Some(required_ty) = property.args.iter().find_map(|arg| {
3166 if let PropertyArg::Ty(ty) = arg {
3167 Some(*ty)
3168 } else {
3169 None
3170 }
3171 }) else {
3172 continue;
3173 };
3174 let elements = property
3175 .args
3176 .iter()
3177 .rev()
3178 .find_map(|arg| {
3179 let PropertyArg::Expr(ContractExpr::Const(value)) = arg else {
3180 return None;
3181 };
3182 u64::try_from(*value).ok()
3183 })
3184 .unwrap_or(0);
3185 self.assumptions.push(SmtPredicate::Custom(format!(
3186 "{} initialized for {:?}, {elements} element(s) (caller-contract)",
3187 place_label(&target),
3188 required_ty
3189 )));
3190 }
3191 PropertyKind::ValidNum => {
3192 if let Some(PropertyArg::Predicates(predicates)) =
3193 property.args.first()
3194 {
3195 for predicate in predicates {
3196 if let Some(smt_pred) = self.contract_predicate_to_smt(predicate) {
3197 if let Some(z3_bool) = self.bool_for_predicate(&smt_pred) {
3198 solver.assert(&z3_bool);
3199 }
3200 self.assumptions.push(smt_pred);
3201 }
3202 }
3203 }
3204 }
3205 _ => {}
3206 },
3207 StateFact::PathCondition(_)
3208 | StateFact::Drop(_)
3209 | StateFact::LocalDead(_)
3210 | StateFact::CallEffect(_) => {}
3211 }
3212 }
3213
3214 let body = self.tcx.optimized_mir(self.checkpoint.caller);
3221 for arg in 1..=body.arg_count {
3222 let place = PlaceKey {
3223 base: PlaceBaseKey::Local(arg),
3224 fields: Vec::new(),
3225 };
3226 let Some(ty) = self.place_ty(&place) else { continue };
3227 if !is_unsigned_integral_ty(ty) { continue }
3228 let Some(term) = self.term_for_place(&place) else { continue };
3229 let zero = Int::from_u64(self.ctx, 0);
3230 solver.assert(&term.ge(&zero));
3231 self.assumptions.push(SmtPredicate::Ge(
3232 SmtTerm::Place(place),
3233 SmtTerm::Const(0),
3234 ));
3235 }
3236 }
3237
3238 pub(crate) fn assumptions(&self) -> &[SmtPredicate] {
3240 &self.assumptions
3241 }
3242
3243 fn latest_cursor(&self) -> ValueCursor {
3244 self.forward.value_definitions.len()
3245 }
3246
3247 fn call_definition_cursor(&self, call: &CallSummary<'tcx>) -> ValueCursor {
3248 self.forward
3249 .value_definitions
3250 .iter()
3251 .find_map(|definition| {
3252 if definition.local != call.destination {
3253 return None;
3254 }
3255 let AbstractValue::CallResult(recorded) = &definition.value else {
3256 return None;
3257 };
3258 if recorded.func == call.func && recorded.arg_count == call.arg_count {
3259 Some(definition.ordinal)
3260 } else {
3261 None
3262 }
3263 })
3264 .unwrap_or_else(|| self.latest_cursor())
3265 }
3266
3267 pub(crate) fn pointer_bounds_for_place(
3274 &mut self,
3275 place: &PlaceKey,
3276 ) -> Option<PointerBounds<'ctx>> {
3277 if let Some(call) = self.pointer_add_call_for_place(place) {
3278 let (base_arg, offset_arg) = call.effects.iter().find_map(|effect| {
3279 match effect {
3280 crate::verify::call_summary::CallEffect::ReturnPointerAdd {
3281 base_arg,
3282 offset_arg,
3283 ..
3284 }
3285 | crate::verify::call_summary::CallEffect::ReturnPointerSub {
3286 base_arg,
3287 offset_arg,
3288 ..
3289 } => Some((*base_arg, *offset_arg)),
3290 _ => None,
3291 }
3292 })?;
3293 let base = call.args.get(base_arg)?;
3294 let index = call.args.get(offset_arg)?;
3295 let call_cursor = self.call_definition_cursor(&call);
3296 let base_origin =
3297 self.origin_key_for_value_before(base, call_cursor, &mut TraceSeen::new())?;
3298
3299 let index_term = self.term_for_value_at(index, call_cursor, &mut TraceSeen::new())?;
3300 let (len_term_int, len_term) = self.bounds_len_for_origin(&base_origin, Some(index))?;
3301
3302 let result_index_smt: SmtTerm;
3305 let result_index_val: Int<'ctx>;
3306 if let AbstractValue::Place(base_place) = base {
3307 let (base_smt, base_val) = self.field_projection_index(
3308 base_place, &base_origin, &len_term,
3309 );
3310 if !matches!(&base_smt, SmtTerm::Const(0)) {
3311 let is_sub = call_has_pointer_sub_effect(&call);
3313 let (adjusted_val, adjusted_smt) = if is_sub {
3314 let v = Int::sub(&self.ctx, &[base_val, index_term]);
3315 let s = SmtTerm::Sub(Box::new(base_smt), Box::new(SmtTerm::Value(value_label(index))));
3316 (v, s)
3317 } else {
3318 let v = Int::add(&self.ctx, &[base_val, index_term]);
3319 let s = SmtTerm::Add(Box::new(base_smt), Box::new(SmtTerm::Value(value_label(index))));
3320 (v, s)
3321 };
3322 result_index_val = adjusted_val;
3323 result_index_smt = adjusted_smt;
3324 } else {
3325 result_index_val = index_term;
3326 result_index_smt = SmtTerm::Value(value_label(index));
3327 }
3328 } else {
3329 result_index_val = index_term;
3330 result_index_smt = SmtTerm::Value(value_label(index));
3331 }
3332
3333 return Some(PointerBounds {
3334 index: result_index_val,
3335 len: len_term_int,
3336 index_term: result_index_smt,
3337 len_term,
3338 origin_key: base_origin,
3339 });
3340 }
3341
3342 let value = self
3343 .resolved_value_for_place(place, &mut TraceSeen::new())
3344 .unwrap_or_else(|| AbstractValue::Place(place.clone()));
3345 let base_origin =
3346 self.origin_key_for_value_before(&value, self.latest_cursor(), &mut TraceSeen::new())?;
3347 let zero = AbstractValue::ConstInt(0);
3348 let (mut len_term_int, mut len_term) = self.bounds_len_for_origin(&base_origin, Some(&zero))?;
3349
3350 if place.fields.is_empty()
3351 && let Some(local) = place.local()
3352 && let Some(definition) = self.forward.latest_value_definition_before(local, self.latest_cursor())
3353 {
3354 if let AbstractValue::Cast(inner, cast_ty) = &definition.value {
3355 let caller = self.checkpoint.caller;
3356 if let Some(dst_pt) = pointee_ty(*cast_ty) {
3357 let dst_size = safe_type_layout(self.tcx, caller, dst_pt).map(|(_, s)| s);
3358 let src_ty = match &**inner {
3359 AbstractValue::Place(inner_place)
3360 | AbstractValue::RawPtr(inner_place)
3361 | AbstractValue::Ref(inner_place) => {
3362 inner_place.local().and_then(|local| {
3363 let body = self.tcx.optimized_mir(caller);
3364 let ty = body.local_decls[local].ty;
3365 pointee_ty(ty)
3366 })
3367 }
3368 _ => None,
3369 };
3370 let src_size = src_ty.and_then(|pt| safe_type_layout(self.tcx, caller, pt).map(|(_, s)| s));
3371 let ratio_smt = match (src_size, dst_size) {
3372 (Some(src), Some(dst)) if src > dst && src % dst == 0 => Some(SmtTerm::Const(src / dst)),
3373 _ => {
3374 if let (Some(src_pty), Some(dst_pty)) = (src_ty, Some(dst_pt)) {
3375 pointee_stride_from_types(self.tcx, src_pty, dst_pty)
3376 } else {
3377 None
3378 }
3379 }
3380 };
3381 if let Some(ratio_smt) = ratio_smt {
3382 let ratio_int = self.term_for_smt_term(&ratio_smt)?;
3383 len_term_int = Int::mul(self.ctx, &[len_term_int, ratio_int]);
3384 len_term = SmtTerm::Mul(Box::new(len_term), Box::new(ratio_smt));
3385 }
3386 }
3387 }
3388 }
3389
3390 let (index_term, index_val) = self.field_projection_index(place, &base_origin, &len_term);
3393
3394 Some(PointerBounds {
3395 index: index_val,
3396 len: len_term_int,
3397 index_term,
3398 len_term,
3399 origin_key: base_origin,
3400 })
3401 }
3402
3403 fn field_projection_index(
3408 &mut self,
3409 place: &PlaceKey,
3410 origin_key: &str,
3411 len_term: &SmtTerm,
3412 ) -> (SmtTerm, Int<'ctx>) {
3413 let default_index = SmtTerm::Const(0);
3414 let default_val = Int::from_u64(self.ctx, 0);
3415
3416 let mut cur_place = place.clone();
3417 loop {
3418 if !cur_place.fields.is_empty() {
3419 let field_idx = cur_place.fields.as_slice();
3420 let mut base = cur_place.clone();
3421 base.fields.clear();
3422 if let Some(local) = base.local() {
3423 if let Some(definition) = self.forward.latest_value_definition_before(
3424 local,
3425 self.latest_cursor(),
3426 ) {
3427 let is_range = match &definition.value {
3428 AbstractValue::CallResult(call) => {
3429 let prim = PrimitiveCall::classify(&call.func);
3430 prim == Some(PrimitiveCall::AsPtrRange)
3431 || prim == Some(PrimitiveCall::AsMutPtrRange)
3432 }
3433 AbstractValue::Aggregate(_, _) => true,
3434 _ => false,
3435 };
3436 if is_range {
3437 if field_idx == [0] {
3438 return (SmtTerm::Const(0), Int::from_u64(self.ctx, 0));
3439 }
3440 if field_idx == [1] {
3441 let len_key = format!("len({})", origin_key);
3442 let len_val = self.symbolic_len_term(&len_key);
3443 return (SmtTerm::Value(len_key), len_val);
3444 }
3445 }
3446 for fact in &self.forward.facts {
3448 if let StateFact::Cast { target, source, .. } = fact {
3449 if *target == cur_place {
3450 if let AbstractValue::Place(p) = source {
3451 cur_place = p.clone();
3452 if cur_place.fields.as_slice() == [1] {
3454 let mut inner_base = cur_place.clone();
3455 inner_base.fields.clear();
3456 if let Some(inner_local) = inner_base.local() {
3457 if let Some(inner_def) = self.forward.latest_value_definition_before(inner_local, self.latest_cursor()) {
3458 if let AbstractValue::CallResult(inner_call) = &inner_def.value {
3459 let inner_prim = PrimitiveCall::classify(&inner_call.func);
3460 if inner_prim == Some(PrimitiveCall::AsPtrRange) || inner_prim == Some(PrimitiveCall::AsMutPtrRange) {
3461 let len_key = format!("len({})", origin_key);
3462 let len_val = self.symbolic_len_term(&len_key);
3463 return (SmtTerm::Value(len_key), len_val);
3464 }
3465 }
3466 }
3467 }
3468 }
3469 continue;
3470 }
3471 }
3472 }
3473 }
3474 }
3475 }
3476 return (default_index, default_val);
3477 }
3478
3479 if let Some(local) = cur_place.local() {
3480 if let Some(definition) =
3481 self.forward.latest_value_definition_before(local, self.latest_cursor())
3482 {
3483 match &definition.value {
3484 AbstractValue::Place(p) => {
3485 cur_place = p.clone();
3486 continue;
3487 }
3488 _ => {}
3489 }
3490 }
3491 }
3492 return (default_index, default_val);
3493 }
3494 }
3495
3496 fn compute_pointer_arith_index(
3499 &mut self,
3500 base: &AbstractValue<'tcx>,
3501 base_origin: &str,
3502 len_term: &SmtTerm,
3503 call: &CallSummary<'tcx>,
3504 call_cursor: ValueCursor,
3505 ) -> (SmtTerm, Int<'ctx>) {
3506 let base_place = match base {
3508 AbstractValue::Place(p) => p.clone(),
3509 _ => {
3510 if let Some(index) = call.effects.iter().find_map(|effect| match effect {
3512 crate::verify::call_summary::CallEffect::ReturnPointerAdd { offset_arg, .. }
3513 | crate::verify::call_summary::CallEffect::ReturnPointerSub { offset_arg, .. } => {
3514 call.args.get(*offset_arg)
3515 }
3516 _ => None,
3517 }) {
3518 let count_smt = SmtTerm::Value(value_label(index));
3519 let count_val = self.term_for_value_at(index, call_cursor, &mut TraceSeen::new())
3520 .unwrap_or(Int::from_u64(self.ctx, 0));
3521 return (count_smt, count_val);
3522 }
3523 return (SmtTerm::Const(0), Int::from_u64(self.ctx, 0));
3524 }
3525 };
3526
3527 let (base_idx_smt, _base_idx_val) = self.field_projection_index(
3529 &base_place, base_origin, len_term,
3530 );
3531
3532 let count = call.effects.iter().find_map(|effect| match effect {
3534 crate::verify::call_summary::CallEffect::ReturnPointerAdd { offset_arg, .. }
3535 | crate::verify::call_summary::CallEffect::ReturnPointerSub { offset_arg, .. } => {
3536 call.args.get(*offset_arg)
3537 }
3538 _ => None,
3539 });
3540
3541 let count_smt = if let Some(c) = count {
3542 SmtTerm::Value(value_label(c))
3543 } else {
3544 SmtTerm::Const(0)
3545 };
3546
3547 let is_sub = call_has_pointer_sub_effect(call);
3548 let result_smt = if is_sub {
3549 SmtTerm::Sub(Box::new(base_idx_smt), Box::new(count_smt))
3550 } else {
3551 SmtTerm::Add(Box::new(base_idx_smt), Box::new(count_smt))
3552 };
3553
3554 let result_val = self.term_for_smt_term(&result_smt)
3555 .unwrap_or(Int::from_u64(self.ctx, 0));
3556
3557 (result_smt, result_val)
3558 }
3559
3560 fn pointer_object_offset_for_place(&self, place: &PlaceKey) -> Option<(PlaceKey, SmtTerm)> {
3565 self.pointer_object_offset_for_place_before(
3566 place,
3567 self.latest_cursor(),
3568 &mut TraceSeen::new(),
3569 )
3570 }
3571
3572 fn pointer_object_offset_for_place_before(
3573 &self,
3574 place: &PlaceKey,
3575 cursor: ValueCursor,
3576 seen: &mut TraceSeen,
3577 ) -> Option<(PlaceKey, SmtTerm)> {
3578 if let Some(object) = self.allocated_object_for_place(place) {
3579 return Some((object, SmtTerm::Const(0)));
3580 }
3581 let seen_key = (place.clone(), cursor);
3582 if !seen.insert(seen_key) {
3583 return None;
3584 }
3585 let local = place.local()?;
3586 let definition = self.forward.latest_value_definition_before(local, cursor)?;
3587 self.pointer_object_offset_for_value(&definition.value, definition.ordinal, seen)
3588 }
3589
3590 fn pointer_object_offset_for_value(
3591 &self,
3592 value: &AbstractValue<'tcx>,
3593 cursor: ValueCursor,
3594 seen: &mut TraceSeen,
3595 ) -> Option<(PlaceKey, SmtTerm)> {
3596 match value {
3597 AbstractValue::Place(place) => {
3598 self.pointer_object_offset_for_place_before(place, cursor, seen)
3599 }
3600 AbstractValue::Cast(inner, _) => {
3601 self.pointer_object_offset_for_value(inner, cursor, seen)
3602 }
3603 AbstractValue::CallResult(call) => {
3604 if call_has_pointer_add_effect(call) || is_pointer_add_call(&call.func) {
3605 let (base_arg, offset_arg) = call
3606 .effects
3607 .iter()
3608 .find_map(|effect| {
3609 let crate::verify::call_summary::CallEffect::ReturnPointerAdd {
3610 base_arg,
3611 offset_arg,
3612 ..
3613 } = effect
3614 else {
3615 return None;
3616 };
3617 Some((*base_arg, *offset_arg))
3618 })
3619 .unwrap_or((0, 1));
3620 let call_cursor = self.call_definition_cursor(call);
3621 let (object, base_offset) = self.pointer_object_offset_for_value(
3622 call.args.get(base_arg)?,
3623 call_cursor,
3624 seen,
3625 )?;
3626 let offset = smt_term_for_value(call.args.get(offset_arg)?)?;
3627 return Some((
3628 object,
3629 SmtTerm::Add(Box::new(base_offset), Box::new(offset)),
3630 ));
3631 }
3632 if call_has_pointer_sub_effect(call) || is_pointer_sub_call(&call.func) {
3633 let (base_arg, offset_arg) = call
3634 .effects
3635 .iter()
3636 .find_map(|effect| {
3637 let crate::verify::call_summary::CallEffect::ReturnPointerSub {
3638 base_arg,
3639 offset_arg,
3640 ..
3641 } = effect
3642 else {
3643 return None;
3644 };
3645 Some((*base_arg, *offset_arg))
3646 })
3647 .unwrap_or((0, 1));
3648 let call_cursor = self.call_definition_cursor(call);
3649 let (object, base_offset) = self.pointer_object_offset_for_value(
3650 call.args.get(base_arg)?,
3651 call_cursor,
3652 seen,
3653 )?;
3654 let offset = smt_term_for_value(call.args.get(offset_arg)?)?;
3655 return Some((
3656 object,
3657 SmtTerm::Sub(Box::new(base_offset), Box::new(offset)),
3658 ));
3659 }
3660 let destination = PlaceKey {
3661 base: PlaceBaseKey::Local(call.destination.as_usize()),
3662 fields: Vec::new(),
3663 };
3664 self.allocated_object_for_place(&destination)
3665 .map(|object| (object, SmtTerm::Const(0)))
3666 }
3667 _ => None,
3668 }
3669 }
3670
3671 fn allocated_object_for_place(&self, place: &PlaceKey) -> Option<PlaceKey> {
3672 if let Some(object) = self.forward.facts.iter().find_map(|fact| match fact {
3674 StateFact::KnownAllocated {
3675 place: allocated_place,
3676 object,
3677 ..
3678 } if allocated_place == place => Some(object.clone()),
3679 _ => None,
3680 }) {
3681 return Some(object);
3682 }
3683 let base = if place.fields.is_empty() {
3686 place.clone()
3687 } else {
3688 PlaceKey { base: place.base.clone(), fields: Vec::new() }
3689 };
3690 if let Some(source) = self.forward.facts.iter().find_map(|fact| match fact {
3691 StateFact::PointsTo { pointer, source } if *pointer == base => Some(source.clone()),
3692 _ => None,
3693 }) {
3694 return Some(source);
3695 }
3696 None
3697 }
3698
3699 pub(crate) fn assert_place_non_zero(
3701 &mut self,
3702 solver: &Solver<'ctx>,
3703 place: &PlaceKey,
3704 reason: &str,
3705 ) {
3706 if let Some(term) = self.term_for_place(place) {
3707 let zero = Int::from_u64(self.ctx, 0);
3708 solver.assert(&term._eq(&zero).not());
3709 self.assumptions.push(SmtPredicate::Custom(format!(
3710 "{} != 0 ({reason})",
3711 place_label(place)
3712 )));
3713 }
3714 }
3715
3716 fn assert_place_alignment(&mut self, solver: &Solver<'ctx>, place: &PlaceKey) {
3718 let Some(ty) = self.place_ty(place) else {
3719 return;
3720 };
3721 let Some(align_ty) = pointee_ty(ty).or(Some(ty)) else {
3722 return;
3723 };
3724 let Some(align) = self.guaranteed_alignment(align_ty) else {
3725 return;
3726 };
3727 if align > 0 && align <= 1 {
3728 return;
3729 }
3730 if let Some(term) = self.term_for_place(place) {
3731 let zero = Int::from_u64(self.ctx, 0);
3732 let align_term = if align == 0 {
3733 self.symbolic_align_term(&format!("{align_ty:?}"))
3734 } else {
3735 Int::from_u64(self.ctx, align)
3736 };
3737 solver.assert(&term.modulo(&align_term)._eq(&zero));
3738 self.assumptions.push(SmtPredicate::Custom(format!(
3739 "{} aligned for {align_ty:?} ({} bytes)",
3740 place_label(place),
3741 if align == 0 { "symbolic".to_string() } else { align.to_string() }
3742 )));
3743 }
3744 }
3745
3746 fn assert_known_alignment(
3748 &mut self,
3749 solver: &Solver<'ctx>,
3750 place: &PlaceKey,
3751 align: u64,
3752 ty_name: &str,
3753 reason: &str,
3754 ) {
3755 if align == 0 {
3756 if let Some(term) = self.term_for_place(place) {
3757 let align_term = self.symbolic_align_term(ty_name);
3758 let zero = Int::from_u64(self.ctx, 0);
3759 solver.assert(&term.modulo(&align_term)._eq(&zero));
3760 self.assumptions.push(SmtPredicate::Custom(format!(
3761 "{} aligned for {ty_name} (symbolic, {reason})",
3762 place_label(place)
3763 )));
3764 }
3765 return;
3766 }
3767 if align <= 1 {
3768 return;
3769 }
3770 if let Some(term) = self.term_for_place(place) {
3771 let align_term = Int::from_u64(self.ctx, align);
3772 let k = Int::new_const(self.ctx, format!("{}_ka_k", place_label(place)));
3773 solver.assert(&term._eq(&Int::mul(self.ctx, &[k, align_term.clone()])));
3774 let zero = Int::from_u64(self.ctx, 0);
3775 solver.assert(&term.modulo(&align_term)._eq(&zero));
3776 self.assumptions.push(SmtPredicate::Custom(format!(
3777 "{} aligned for {ty_name} ({align} bytes, {reason})",
3778 place_label(place)
3779 )));
3780 }
3781 }
3782
3783 fn assert_known_const(
3785 &mut self,
3786 solver: &Solver<'ctx>,
3787 place: &PlaceKey,
3788 value: u64,
3789 reason: &str,
3790 ) {
3791 if let Some(term) = self.term_for_place(place) {
3792 let value_term = Int::from_u64(self.ctx, value);
3793 solver.assert(&term._eq(&value_term));
3794 self.assumptions.push(SmtPredicate::Custom(format!(
3795 "{} == {value} ({reason})",
3796 place_label(place)
3797 )));
3798 }
3799 }
3800
3801 fn assert_length_alias(&mut self, solver: &Solver<'ctx>, left: &PlaceKey, right: &PlaceKey) {
3803 if !self.is_len_carrying_place(left) || !self.is_len_carrying_place(right) {
3804 return;
3805 }
3806 let left_label = place_label(left);
3807 let right_label = place_label(right);
3808 let lhs = self.symbolic_len_term(&format!("len({left_label})"));
3809 let rhs = self.symbolic_len_term(&format!("len({right_label})"));
3810 solver.assert(&lhs._eq(&rhs));
3811 self.assumptions.push(SmtPredicate::Eq(
3812 SmtTerm::Value(format!("len({left_label})")),
3813 SmtTerm::Value(format!("len({right_label})")),
3814 ));
3815 }
3816
3817 fn is_len_carrying_place(&self, place: &PlaceKey) -> bool {
3818 self.place_ty(place).is_some_and(is_len_carrying_ty)
3819 }
3820
3821 fn record_call_effect_assumptions(&mut self, call: &CallSummary<'tcx>) {
3823 let destination = PlaceKey {
3824 base: PlaceBaseKey::Local(call.destination.as_usize()),
3825 fields: Vec::new(),
3826 };
3827 let cursor = self.call_definition_cursor(call);
3828 for effect in &call.effects {
3829 match effect {
3830 crate::verify::call_summary::CallEffect::ReturnPointerAdd {
3831 base_arg,
3832 offset_arg,
3833 stride,
3834 } => {
3835 let base_term = call
3836 .args
3837 .get(*base_arg)
3838 .and_then(|v| self.term_for_value_at(v, cursor, &mut TraceSeen::new()));
3839 let offset_term = call
3840 .args
3841 .get(*offset_arg)
3842 .and_then(|v| self.term_for_value_at(v, cursor, &mut TraceSeen::new()));
3843 if let (Some(base), Some(offset)) = (base_term, offset_term) {
3844 let stride = Int::from_u64(self.ctx, stride.unwrap_or(1));
3845 let term =
3846 Int::add(self.ctx, &[base, Int::mul(self.ctx, &[offset, stride])]);
3847 self.place_terms.insert(destination.clone(), term);
3848 }
3849 }
3850 crate::verify::call_summary::CallEffect::ReturnPointerSub {
3851 base_arg,
3852 offset_arg,
3853 stride,
3854 } => {
3855 let base_term = call
3856 .args
3857 .get(*base_arg)
3858 .and_then(|v| self.term_for_value_at(v, cursor, &mut TraceSeen::new()));
3859 let offset_term = call
3860 .args
3861 .get(*offset_arg)
3862 .and_then(|v| self.term_for_value_at(v, cursor, &mut TraceSeen::new()));
3863 if let (Some(base), Some(offset)) = (base_term, offset_term) {
3864 let stride = Int::from_u64(self.ctx, stride.unwrap_or(1));
3865 let term =
3866 Int::sub(self.ctx, &[base, Int::mul(self.ctx, &[offset, stride])]);
3867 self.place_terms.insert(destination.clone(), term);
3868 }
3869 }
3870 crate::verify::call_summary::CallEffect::ReturnLengthOfArg { arg } => {
3871 let raw_source = call
3872 .args
3873 .get(*arg)
3874 .and_then(|value| {
3875 self.origin_key_for_value_before(value, cursor, &mut TraceSeen::new())
3876 })
3877 .or_else(|| call.args.get(*arg).map(value_label))
3878 .unwrap_or_else(|| format!("arg{arg}"));
3879 let source = raw_source.split('.').next().unwrap_or(&raw_source);
3882 let len_key = format!("len({source})");
3883 let len_term = self.symbolic_len_term(&len_key);
3884 self.place_terms.insert(destination.clone(), len_term);
3885 self.assumptions.push(SmtPredicate::Eq(
3886 SmtTerm::Place(destination.clone()),
3887 SmtTerm::Value(len_key),
3888 ));
3889 }
3890 crate::verify::call_summary::CallEffect::ReturnPointerFromArg { arg }
3891 | crate::verify::call_summary::CallEffect::ReturnAliasArg { arg } => {
3892 let source_value = call.args.get(*arg);
3893 if let Some(term) = source_value.and_then(|value| {
3894 self.term_for_value_at(value, cursor, &mut TraceSeen::new())
3895 }) {
3896 self.place_terms.insert(destination.clone(), term);
3897 }
3898 let source = source_value
3899 .map(value_label)
3900 .unwrap_or_else(|| format!("arg{arg}"));
3901 self.assumptions.push(SmtPredicate::Eq(
3902 SmtTerm::Place(destination.clone()),
3903 SmtTerm::Value(source),
3904 ));
3905 }
3906 crate::verify::call_summary::CallEffect::ReturnConst { .. } => {}
3907 crate::verify::call_summary::CallEffect::ReturnTupleFieldLength {
3908 field,
3909 from_arg,
3910 } => {
3911 if *field == 0 {
3912 let cursor = self.call_definition_cursor(call);
3913 if let Some(mid_term) = call
3914 .args
3915 .get(*from_arg)
3916 .and_then(|v| {
3917 self.term_for_value_at(v, cursor, &mut TraceSeen::new())
3918 })
3919 {
3920 let label = value_label(
3921 call.args.get(*from_arg)
3922 .unwrap_or(&AbstractValue::Unknown(String::new())),
3923 );
3924 let dest_key = PlaceKey {
3925 base: PlaceBaseKey::Local(call.destination.as_usize()),
3926 fields: vec![0],
3927 };
3928 self.place_terms.insert(dest_key.clone(), mid_term);
3929 self.assumptions.push(SmtPredicate::Eq(
3930 SmtTerm::Place(dest_key),
3931 SmtTerm::Value(label),
3932 ));
3933 }
3934 }
3935 }
3936 crate::verify::call_summary::CallEffect::ReturnNonZero
3937 | crate::verify::call_summary::CallEffect::ReturnAligned { .. }
3938 | crate::verify::call_summary::CallEffect::ReadMemory { .. }
3939 | crate::verify::call_summary::CallEffect::WriteMemory { .. }
3940 | crate::verify::call_summary::CallEffect::ForgetArgFacts { .. } => {}
3941 }
3942 }
3943 }
3944
3945 pub(crate) fn term_for_place(&mut self, place: &PlaceKey) -> Option<Int<'ctx>> {
3947 self.term_for_place_before(place, self.latest_cursor(), &mut TraceSeen::new())
3948 }
3949
3950 fn term_for_place_before(
3952 &mut self,
3953 place: &PlaceKey,
3954 cursor: ValueCursor,
3955 seen: &mut TraceSeen,
3956 ) -> Option<Int<'ctx>> {
3957 let seen_key = (place.clone(), cursor);
3958 if !seen.insert(seen_key) {
3959 return None;
3960 }
3961
3962 if !place.fields.is_empty() {
3963 if let Some(term) = self.projected_term_for_place(place, cursor, seen) {
3964 return Some(term);
3965 }
3966 if let Some(term) = self.place_terms.get(place) {
3967 return Some(term.clone());
3968 }
3969 let term = Int::new_const(self.ctx, place_name(place));
3970 self.place_terms.insert(place.clone(), term.clone());
3971 return Some(term);
3972 }
3973
3974 if let Some(local) = place.local()
3975 && let Some(definition) = self.forward.latest_value_definition_before(local, cursor)
3976 {
3977 if let Some(term) = self.term_for_value_at(&definition.value, definition.ordinal, seen)
3978 {
3979 return Some(term);
3980 }
3981 }
3982
3983 if let Some(value) = self.path_value_definition_before(place, cursor)
3984 && let Some(term) = self.term_for_value_at(&value, cursor, seen)
3985 {
3986 return Some(term);
3987 }
3988
3989 if let Some(term) = self.place_terms.get(place) {
3990 return Some(term.clone());
3991 }
3992
3993 if place.fields.is_empty()
3996 && let Some(local) = place.local()
3997 {
3998 let id = local.as_usize();
3999 if let Some(term) = self.local_terms.get(&id) {
4000 let term = term.clone();
4001 self.place_terms.insert(place.clone(), term.clone());
4002 return Some(term);
4003 }
4004 let term = Int::new_const(self.ctx, place_name(place));
4005 self.local_terms.insert(id, term.clone());
4006 self.place_terms.insert(place.clone(), term.clone());
4007 return Some(term);
4008 }
4009
4010 let term = Int::new_const(self.ctx, place_name(place));
4011 self.place_terms.insert(place.clone(), term.clone());
4012 Some(term)
4013 }
4014
4015 fn projected_term_for_place(
4022 &mut self,
4023 place: &PlaceKey,
4024 cursor: ValueCursor,
4025 seen: &mut TraceSeen,
4026 ) -> Option<Int<'ctx>> {
4027 let mut base = place.clone();
4028 base.fields.clear();
4029 let local = base.local()?;
4030 let definition = self.forward.latest_value_definition_before(local, cursor)?;
4031 let value = &definition.value;
4032
4033 if let AbstractValue::CallResult(call) = value {
4037 let prim = PrimitiveCall::classify(&call.func);
4038 if prim == Some(PrimitiveCall::AsPtrRange)
4039 || prim == Some(PrimitiveCall::AsMutPtrRange)
4040 {
4041 let arg_index = if place.fields.as_slice() == [0] {
4042 0 } else if place.fields.as_slice() == [1] {
4044 0 } else {
4046 return None;
4047 };
4048 let call_cursor = self.call_definition_cursor(call);
4049 return call
4050 .args
4051 .get(arg_index)
4052 .and_then(|arg| self.term_for_value_at(arg, call_cursor, seen));
4053 }
4054 }
4055
4056 if place.fields.as_slice() != [0] {
4057 return None;
4058 }
4059 let AbstractValue::Binary(op, lhs, rhs) = value else {
4060 return None;
4061 };
4062 if !matches!(
4063 op,
4064 BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow
4065 ) {
4066 return None;
4067 }
4068 if matches!(op, BinOp::MulWithOverflow) {
4069 if let Some(len_origin) = ptr_metadata_origin(lhs, self) {
4070 let len_key = format!("len({len_origin})");
4071 let len_term = self.symbolic_len_term(&len_key);
4072 let rhs_term = self.term_for_value_at(rhs, definition.ordinal, seen)?;
4073 return Some(Int::mul(self.ctx, &[len_term, rhs_term]));
4074 }
4075 }
4076 let lhs = self.term_for_value_at(lhs, definition.ordinal, seen)?;
4077 let rhs = self.term_for_value_at(rhs, definition.ordinal, seen)?;
4078 self.term_for_binary(*op, &lhs, &rhs)
4079 }
4080
4081 fn term_for_value(
4083 &mut self,
4084 value: &AbstractValue<'tcx>,
4085 seen: &mut TraceSeen,
4086 ) -> Option<Int<'ctx>> {
4087 self.term_for_value_at(value, self.latest_cursor(), seen)
4088 }
4089
4090 fn term_for_pointer_arith_call(
4092 &mut self,
4093 call: &CallSummary<'tcx>,
4094 cursor: ValueCursor,
4095 seen: &mut TraceSeen,
4096 ) -> Option<Int<'ctx>> {
4097 let effect = call.effects.iter().find_map(|effect| match effect {
4098 crate::verify::call_summary::CallEffect::ReturnPointerAdd {
4099 base_arg,
4100 offset_arg,
4101 stride,
4102 } => Some((false, *base_arg, *offset_arg, *stride)),
4103 crate::verify::call_summary::CallEffect::ReturnPointerSub {
4104 base_arg,
4105 offset_arg,
4106 stride,
4107 } => Some((true, *base_arg, *offset_arg, *stride)),
4108 _ => None,
4109 });
4110
4111 let (is_sub, base_arg, offset_arg, stride) = effect.or_else(|| {
4112 if is_pointer_add_call(&call.func) {
4113 Some((false, 0, 1, self.call_destination_stride(call)))
4114 } else if is_pointer_sub_call(&call.func) {
4115 Some((true, 0, 1, self.call_destination_stride(call)))
4116 } else {
4117 None
4118 }
4119 })?;
4120
4121 let base = call.args.get(base_arg)?;
4122 let offset = call.args.get(offset_arg)?;
4123 let base = self.term_for_value_at(base, cursor, seen)?;
4124 let offset = self.term_for_value_at(offset, cursor, seen)?;
4125 let stride = Int::from_u64(self.ctx, stride.unwrap_or(1));
4126 let scaled_offset = Int::mul(self.ctx, &[offset, stride]);
4127
4128 if is_sub {
4129 Some(Int::sub(self.ctx, &[base, scaled_offset]))
4130 } else {
4131 Some(Int::add(self.ctx, &[base, scaled_offset]))
4132 }
4133 }
4134
4135 fn term_for_length_call(
4137 &mut self,
4138 call: &CallSummary<'tcx>,
4139 cursor: ValueCursor,
4140 seen: &mut TraceSeen,
4141 ) -> Option<Int<'ctx>> {
4142 let arg = call.effects.iter().find_map(|effect| {
4143 let crate::verify::call_summary::CallEffect::ReturnLengthOfArg { arg } = effect else {
4144 return None;
4145 };
4146 Some(*arg)
4147 })?;
4148 let source = call.args.get(arg)?;
4149 let source = self
4150 .origin_key_for_value_before(source, cursor, seen)
4151 .unwrap_or_else(|| value_label(source));
4152 let len_key = format!("len({source})");
4153 Some(self.symbolic_len_term(&len_key))
4154 }
4155
4156 fn const_param_symbol(&mut self, name_or_debug: &str) -> Int<'ctx> {
4162 let plain = const_param_name_from_debug(name_or_debug)
4163 .unwrap_or_else(|| name_or_debug.to_string());
4164 let key = format!("const_{}", sanitize_smt_name(&plain));
4165 if let Some(term) = self.const_terms.get(&key) {
4166 return term.clone();
4167 }
4168 let term = Int::new_const(self.ctx, key.as_str());
4169 self.const_terms.insert(key, term.clone());
4170 term
4171 }
4172
4173 fn term_for_value_at(
4175 &mut self,
4176 value: &AbstractValue<'tcx>,
4177 cursor: ValueCursor,
4178 seen: &mut TraceSeen,
4179 ) -> Option<Int<'ctx>> {
4180 match value {
4181 AbstractValue::ConstInt(value) => Some(Int::from_u64(self.ctx, *value as u64)),
4182 AbstractValue::ConstParam(name) => Some(self.const_param_symbol(name)),
4183 AbstractValue::Const(text) => {
4184 const_int_from_debug(text).map(|value| Int::from_u64(self.ctx, value as u64))
4185 .or_else(|| {
4186 if let Some(param) = const_param_name_from_debug(text) {
4192 return Some(self.const_param_symbol(¶m));
4193 }
4194 let name = sanitize_smt_name(text);
4195 if name.is_empty() { None }
4196 else { Some(Int::new_const(self.ctx, format!("const_{name}"))) }
4197 })
4198 }
4199 AbstractValue::Place(place) => self.term_for_place_before(place, cursor, seen),
4200 AbstractValue::Cast(inner, _) => self.term_for_value_at(inner, cursor, seen),
4201 AbstractValue::Ref(place) | AbstractValue::RawPtr(place) => Some(Int::new_const(
4202 self.ctx,
4203 format!("addr_{}", place_name(place)),
4204 )),
4205 AbstractValue::Binary(op, lhs, rhs) => {
4206 let lhs = self.term_for_value_at(lhs, cursor, seen)?;
4207 let rhs = self.term_for_value_at(rhs, cursor, seen)?;
4208 self.term_for_binary(*op, &lhs, &rhs)
4209 }
4210 AbstractValue::CallResult(call) => {
4211 if let Some(term) = self.term_for_pointer_arith_call(call, cursor, seen) {
4212 return Some(term);
4213 }
4214 if let Some(term) = self.term_for_length_call(call, cursor, seen) {
4215 return Some(term);
4216 }
4217 let place = PlaceKey {
4218 base: PlaceBaseKey::Local(call.destination.as_usize()),
4219 fields: Vec::new(),
4220 };
4221 Some(Int::new_const(self.ctx, place_name(&place)))
4222 }
4223 AbstractValue::Unknown(_)
4224 | AbstractValue::ThreadLocal(_)
4225 | AbstractValue::Repeat(_)
4226 | AbstractValue::Nullary(_)
4227 | AbstractValue::Discriminant(_)
4228 | AbstractValue::Aggregate(_, _) => None,
4229 AbstractValue::Unary(op, inner) => match op {
4230 UnOp::PtrMetadata => {
4231 let source = self
4232 .origin_key_for_value_before(inner, cursor, seen)
4233 .unwrap_or_else(|| value_label(inner));
4234 let len_key = format!("len({source})");
4235 Some(self.symbolic_len_term(&len_key))
4236 }
4237 _ => None,
4238 },
4239 #[cfg(not(rapx_rustc_ge_196))]
4240 AbstractValue::ShallowInitBox(_, _) => None,
4241 }
4242 }
4243
4244 fn term_for_smt_term(&mut self, term: &SmtTerm) -> Option<Int<'ctx>> {
4246 match term {
4247 SmtTerm::Place(place) => self.term_for_place(place),
4248 SmtTerm::Value(name) => {
4249 if name.starts_with("len(") {
4250 Some(self.symbolic_len_term(name))
4251 } else {
4252 Some(Int::new_const(self.ctx, sanitize_smt_name(name)))
4253 }
4254 }
4255 SmtTerm::Const(value) => Some(Int::from_u64(self.ctx, *value)),
4256 SmtTerm::ConstParam(text) => {
4257 if sanitize_smt_name(text).is_empty() {
4258 return None;
4259 }
4260 Some(self.const_param_symbol(text))
4261 }
4262 SmtTerm::Add(lhs, rhs) => {
4263 let lhs = self.term_for_smt_term(lhs)?;
4264 let rhs = self.term_for_smt_term(rhs)?;
4265 Some(Int::add(self.ctx, &[lhs, rhs]))
4266 }
4267 SmtTerm::Sub(lhs, rhs) => {
4268 let lhs = self.term_for_smt_term(lhs)?;
4269 let rhs = self.term_for_smt_term(rhs)?;
4270 Some(Int::sub(self.ctx, &[lhs, rhs]))
4271 }
4272 SmtTerm::Mul(lhs, rhs) => {
4273 let lhs = self.term_for_smt_term(lhs)?;
4274 let rhs = self.term_for_smt_term(rhs)?;
4275 Some(Int::mul(self.ctx, &[lhs, rhs]))
4276 }
4277 SmtTerm::Div(lhs, rhs) => {
4278 let lhs = self.term_for_smt_term(lhs)?;
4279 let rhs = self.term_for_smt_term(rhs)?;
4280 Some(lhs.div(&rhs))
4281 }
4282 SmtTerm::Rem(lhs, rhs) => {
4283 let lhs = self.term_for_smt_term(lhs)?;
4284 let rhs = self.term_for_smt_term(rhs)?;
4285 Some(lhs.modulo(&rhs))
4286 }
4287 }
4288 }
4289
4290 fn bool_for_predicates(&mut self, predicates: &[SmtPredicate]) -> Option<Bool<'ctx>> {
4292 match predicates {
4293 [] => None,
4294 [predicate] => self.bool_for_predicate(predicate),
4295 predicates => {
4296 let bools = predicates
4297 .iter()
4298 .map(|predicate| self.bool_for_predicate(predicate))
4299 .collect::<Option<Vec<_>>>()?;
4300 let refs = bools.iter().collect::<Vec<_>>();
4301 Some(Bool::and(self.ctx, &refs))
4302 }
4303 }
4304 }
4305
4306 fn bool_for_predicate(&mut self, predicate: &SmtPredicate) -> Option<Bool<'ctx>> {
4308 match predicate {
4309 SmtPredicate::Eq(lhs, rhs) => {
4310 let lhs = self.term_for_smt_term(lhs)?;
4311 let rhs = self.term_for_smt_term(rhs)?;
4312 Some(lhs._eq(&rhs))
4313 }
4314 SmtPredicate::Ne(lhs, rhs) => {
4315 let lhs = self.term_for_smt_term(lhs)?;
4316 let rhs = self.term_for_smt_term(rhs)?;
4317 Some(lhs._eq(&rhs).not())
4318 }
4319 SmtPredicate::Le(lhs, rhs) => {
4320 let lhs = self.term_for_smt_term(lhs)?;
4321 let rhs = self.term_for_smt_term(rhs)?;
4322 Some(lhs.le(&rhs))
4323 }
4324 SmtPredicate::Lt(lhs, rhs) => {
4325 let lhs = self.term_for_smt_term(lhs)?;
4326 let rhs = self.term_for_smt_term(rhs)?;
4327 Some(lhs.lt(&rhs))
4328 }
4329 SmtPredicate::Ge(lhs, rhs) => {
4330 let lhs = self.term_for_smt_term(lhs)?;
4331 let rhs = self.term_for_smt_term(rhs)?;
4332 Some(lhs.ge(&rhs))
4333 }
4334 SmtPredicate::Gt(lhs, rhs) => {
4335 let lhs = self.term_for_smt_term(lhs)?;
4336 let rhs = self.term_for_smt_term(rhs)?;
4337 Some(lhs.gt(&rhs))
4338 }
4339 SmtPredicate::And(predicates) => self.bool_for_predicates(predicates),
4340 SmtPredicate::Divisible { term, modulus } => {
4341 let term = self.term_for_smt_term(term)?;
4342 let modulus = Int::from_u64(self.ctx, *modulus);
4343 let zero = Int::from_u64(self.ctx, 0);
4344 Some(term.modulo(&modulus)._eq(&zero))
4345 }
4346 SmtPredicate::InBounds {
4347 index,
4348 access_count,
4349 len,
4350 } => {
4351 let index = self.term_for_smt_term(index)?;
4352 let access_count = self.term_for_smt_term(access_count)?;
4353 let len = self.term_for_smt_term(len)?;
4354 let zero = Int::from_u64(self.ctx, 0);
4355 let covered_end = Int::add(self.ctx, &[index.clone(), access_count]);
4356 Some(Bool::and(
4357 self.ctx,
4358 &[&index.ge(&zero), &covered_end.le(&len)],
4359 ))
4360 }
4361 SmtPredicate::NonOverlapping {
4362 left,
4363 right,
4364 left_count,
4365 right_count,
4366 elem_size,
4367 } => {
4368 let left = self.term_for_smt_term(left)?;
4369 let right = self.term_for_smt_term(right)?;
4370 let left_count = self.term_for_smt_term(left_count)?;
4371 let right_count = self.term_for_smt_term(right_count)?;
4372 let elem_size = Int::from_u64(self.ctx, *elem_size);
4373 let left_end = Int::add(
4374 self.ctx,
4375 &[
4376 left.clone(),
4377 Int::mul(self.ctx, &[left_count, elem_size.clone()]),
4378 ],
4379 );
4380 let right_end = Int::add(
4381 self.ctx,
4382 &[right.clone(), Int::mul(self.ctx, &[right_count, elem_size])],
4383 );
4384 Some(Bool::or(
4385 self.ctx,
4386 &[&left_end.le(&right), &right_end.le(&left)],
4387 ))
4388 }
4389 SmtPredicate::Not(predicate) => Some(self.bool_for_predicate(predicate)?.not()),
4390 SmtPredicate::Custom(_) => None,
4391 }
4392 }
4393
4394 fn assert_unsigned_bounds_for_predicates(
4397 &mut self,
4398 solver: &Solver<'ctx>,
4399 predicates: &[SmtPredicate],
4400 ) {
4401 let mut seen = HashSet::new();
4402 for predicate in predicates {
4403 self.assert_unsigned_bounds_for_predicate(solver, predicate, &mut seen);
4404 }
4405 }
4406
4407 fn assert_unsigned_bounds_for_predicate(
4408 &mut self,
4409 solver: &Solver<'ctx>,
4410 predicate: &SmtPredicate,
4411 seen: &mut HashSet<PlaceKey>,
4412 ) {
4413 match predicate {
4414 SmtPredicate::Eq(lhs, rhs)
4415 | SmtPredicate::Ne(lhs, rhs)
4416 | SmtPredicate::Le(lhs, rhs)
4417 | SmtPredicate::Lt(lhs, rhs)
4418 | SmtPredicate::Ge(lhs, rhs)
4419 | SmtPredicate::Gt(lhs, rhs) => {
4420 self.assert_unsigned_bounds_for_term(solver, lhs, seen);
4421 self.assert_unsigned_bounds_for_term(solver, rhs, seen);
4422 }
4423 SmtPredicate::And(predicates) => {
4424 for predicate in predicates {
4425 self.assert_unsigned_bounds_for_predicate(solver, predicate, seen);
4426 }
4427 }
4428 SmtPredicate::Divisible { term, .. } => {
4429 self.assert_unsigned_bounds_for_term(solver, term, seen);
4430 }
4431 SmtPredicate::InBounds {
4432 index,
4433 access_count,
4434 len,
4435 } => {
4436 self.assert_unsigned_bounds_for_term(solver, index, seen);
4437 self.assert_unsigned_bounds_for_term(solver, access_count, seen);
4438 self.assert_unsigned_bounds_for_term(solver, len, seen);
4439 }
4440 SmtPredicate::NonOverlapping {
4441 left_count,
4442 right_count,
4443 ..
4444 } => {
4445 self.assert_unsigned_bounds_for_term(solver, left_count, seen);
4446 self.assert_unsigned_bounds_for_term(solver, right_count, seen);
4447 }
4448 SmtPredicate::Not(predicate) => {
4449 self.assert_unsigned_bounds_for_predicate(solver, predicate, seen);
4450 }
4451 SmtPredicate::Custom(_) => {}
4452 }
4453 }
4454
4455 fn assert_unsigned_bounds_for_term(
4456 &mut self,
4457 solver: &Solver<'ctx>,
4458 term: &SmtTerm,
4459 seen: &mut HashSet<PlaceKey>,
4460 ) {
4461 match term {
4462 SmtTerm::Place(place) => {
4463 if !seen.insert(place.clone()) {
4464 return;
4465 }
4466 let Some(ty) = self.place_ty(place) else {
4467 return;
4468 };
4469 if !is_unsigned_integral_ty(ty) {
4470 return;
4471 }
4472 let Some(int_term) = self.term_for_place(place) else {
4473 return;
4474 };
4475 let zero = Int::from_u64(self.ctx, 0);
4476 solver.assert(&int_term.ge(&zero));
4477 self.assumptions.push(SmtPredicate::Ge(
4478 SmtTerm::Place(place.clone()),
4479 SmtTerm::Const(0),
4480 ));
4481 }
4482 SmtTerm::Add(lhs, rhs)
4483 | SmtTerm::Sub(lhs, rhs)
4484 | SmtTerm::Mul(lhs, rhs)
4485 | SmtTerm::Div(lhs, rhs)
4486 | SmtTerm::Rem(lhs, rhs) => {
4487 self.assert_unsigned_bounds_for_term(solver, lhs, seen);
4488 self.assert_unsigned_bounds_for_term(solver, rhs, seen);
4489 }
4490 SmtTerm::Value(_) | SmtTerm::Const(_) | SmtTerm::ConstParam(_) => {}
4491 }
4492 }
4493
4494 fn term_for_binary(&self, op: BinOp, lhs: &Int<'ctx>, rhs: &Int<'ctx>) -> Option<Int<'ctx>> {
4496 let one = Int::from_u64(self.ctx, 1);
4497 let zero = Int::from_u64(self.ctx, 0);
4498 Some(match op {
4499 BinOp::Add | BinOp::AddWithOverflow => Int::add(self.ctx, &[lhs.clone(), rhs.clone()]),
4500 BinOp::Sub | BinOp::SubWithOverflow => Int::sub(self.ctx, &[lhs.clone(), rhs.clone()]),
4501 BinOp::Mul | BinOp::MulWithOverflow => Int::mul(self.ctx, &[lhs.clone(), rhs.clone()]),
4502 BinOp::Div => lhs.div(rhs),
4503 BinOp::Rem => lhs.modulo(rhs),
4504 BinOp::Eq => lhs._eq(rhs).ite(&one, &zero),
4505 BinOp::Ne => lhs._eq(rhs).not().ite(&one, &zero),
4506 BinOp::Lt => lhs.lt(rhs).ite(&one, &zero),
4507 BinOp::Le => lhs.le(rhs).ite(&one, &zero),
4508 BinOp::Gt => lhs.gt(rhs).ite(&one, &zero),
4509 BinOp::Ge => lhs.ge(rhs).ite(&one, &zero),
4510 _ => return None,
4511 })
4512 }
4513
4514 fn call_destination_stride(&self, call: &CallSummary<'tcx>) -> Option<u64> {
4516 let place = PlaceKey {
4517 base: PlaceBaseKey::Local(call.destination.as_usize()),
4518 fields: Vec::new(),
4519 };
4520 let destination_ty = self.place_ty(&place)?;
4521 let pointee = pointee_ty(destination_ty)?;
4522 self.type_layout(pointee).map(|(_, size)| size)
4523 }
4524
4525 fn place_ty(&self, place: &PlaceKey) -> Option<Ty<'tcx>> {
4527 if !place.fields.is_empty() {
4528 return self.forward.facts.iter().find_map(|fact| {
4529 let StateFact::Cast { target, ty, .. } = fact else {
4530 return None;
4531 };
4532 if target == place { Some(*ty) } else { None }
4533 });
4534 }
4535 let local = match place.base {
4536 PlaceBaseKey::Return => Local::from_usize(0),
4537 PlaceBaseKey::Local(local) => Local::from_usize(local),
4538 PlaceBaseKey::Arg(_) => return None,
4539 };
4540 Some(self.tcx.optimized_mir(self.checkpoint.caller).local_decls[local].ty)
4541 }
4542
4543 fn type_layout(&self, ty: Ty<'tcx>) -> Option<(u64, u64)> {
4544 safe_type_layout(self.tcx, self.checkpoint.caller, ty)
4545 }
4546
4547 fn guaranteed_alignment(&self, ty: Ty<'tcx>) -> Option<u64> {
4549 if let Some((align, _)) = self.type_layout(ty).filter(|(align, _)| *align > 0) {
4550 return Some(align);
4551 }
4552 if let Some(alignments) = self.generic_candidate_alignments(ty) {
4553 return alignments.into_iter().min();
4554 }
4555 if matches!(ty.kind(), TyKind::Param(_) | TyKind::Array(..) | TyKind::Ref(..) | TyKind::RawPtr(..)) {
4556 return Some(0);
4557 }
4558 None
4559 }
4560
4561 fn generic_candidate_alignments(&self, ty: Ty<'tcx>) -> Option<Vec<u64>> {
4562 let candidates = GenericTypeCandidates::for_def(self.tcx, self.checkpoint.caller);
4563 let alignments = candidates
4564 .candidates_for_ty(ty)?
4565 .iter()
4566 .filter_map(|candidate| self.type_layout(*candidate).map(|(align, _)| align))
4567 .filter(|align| *align > 0)
4568 .collect::<Vec<_>>();
4569 if alignments.is_empty() {
4570 None
4571 } else {
4572 Some(alignments)
4573 }
4574 }
4575
4576 fn pointer_add_call_for_place(&self, place: &PlaceKey) -> Option<CallSummary<'tcx>> {
4578 let value = self.resolved_value_for_place_before(
4579 place,
4580 self.latest_cursor(),
4581 &mut TraceSeen::new(),
4582 )?;
4583 match value {
4584 AbstractValue::CallResult(call)
4585 if call_has_pointer_add_effect(&call) || call_has_pointer_sub_effect(&call) =>
4586 {
4587 Some(call)
4588 }
4589 _ => None,
4590 }
4591 }
4592
4593 fn resolved_value_for_place(
4595 &self,
4596 place: &PlaceKey,
4597 seen: &mut TraceSeen,
4598 ) -> Option<AbstractValue<'tcx>> {
4599 self.resolved_value_for_place_before(place, self.latest_cursor(), seen)
4600 }
4601
4602 fn resolved_value_for_place_before(
4603 &self,
4604 place: &PlaceKey,
4605 cursor: ValueCursor,
4606 seen: &mut TraceSeen,
4607 ) -> Option<AbstractValue<'tcx>> {
4608 let seen_key = (place.clone(), cursor);
4609 if !seen.insert(seen_key) {
4610 return None;
4611 }
4612 if !place.fields.is_empty() {
4613 let mut base = place.clone();
4614 base.fields.clear();
4615 let local = base.local()?;
4616 if let Some(definition) = self.forward.latest_value_definition_before(local, cursor) {
4617 if let AbstractValue::CallResult(call) = &definition.value {
4619 let prim = PrimitiveCall::classify(&call.func);
4620 if prim == Some(PrimitiveCall::AsPtrRange)
4621 || prim == Some(PrimitiveCall::AsMutPtrRange)
4622 {
4623 if let Some(source_arg) = call.effects.iter().find_map(|effect| {
4624 match effect {
4625 crate::verify::call_summary::CallEffect::ReturnAliasArg { arg }
4626 | crate::verify::call_summary::CallEffect::ReturnPointerFromArg { arg } => Some(*arg),
4627 _ => None,
4628 }
4629 }) {
4630 let call_cursor = self.call_definition_cursor(call);
4631 if let Some(arg_value) = call.args.get(source_arg) {
4632 return self.resolved_value_before(arg_value, call_cursor, seen);
4633 }
4634 }
4635 }
4636 }
4637 if let AbstractValue::Aggregate(_, _) = &definition.value {
4640 for fact in &self.forward.facts {
4641 if let StateFact::Cast { target, source, .. } = fact {
4642 if *target == *place {
4643 return self.resolved_value_before(source, definition.ordinal, seen);
4644 }
4645 }
4646 }
4647 }
4648 }
4649 return Some(AbstractValue::Place(place.clone()));
4650 }
4651 let local = place.local()?;
4652 if let Some(definition) = self.forward.latest_value_definition_before(local, cursor) {
4653 return self.resolved_value_before(&definition.value, definition.ordinal, seen);
4654 }
4655 if let Some(value) = self.path_value_definition_before(place, cursor) {
4656 return self.resolved_value_before(&value, cursor, seen);
4657 }
4658 None
4659 }
4660
4661 fn resolved_value(
4663 &self,
4664 value: &AbstractValue<'tcx>,
4665 seen: &mut TraceSeen,
4666 ) -> Option<AbstractValue<'tcx>> {
4667 self.resolved_value_before(value, self.latest_cursor(), seen)
4668 }
4669
4670 fn resolved_value_before(
4671 &self,
4672 value: &AbstractValue<'tcx>,
4673 cursor: ValueCursor,
4674 seen: &mut TraceSeen,
4675 ) -> Option<AbstractValue<'tcx>> {
4676 match value {
4677 AbstractValue::Place(place) => {
4678 self.resolved_value_for_place_before(place, cursor, seen)
4679 }
4680 AbstractValue::Cast(inner, _) => self.resolved_value_before(inner, cursor, seen),
4681 _ => Some(value.clone()),
4682 }
4683 }
4684
4685 fn path_value_definition_before(
4692 &self,
4693 place: &PlaceKey,
4694 cursor: ValueCursor,
4695 ) -> Option<AbstractValue<'tcx>> {
4696 if !place.fields.is_empty() {
4697 return None;
4698 }
4699 let local = place.local()?;
4700 let cutoff = self.path_cursor_cutoff(cursor);
4701 let body = self.tcx.optimized_mir(self.forward.checkpoint.caller);
4702 let mut latest = None;
4703
4704 for step in &self.forward.path.steps {
4705 let PathStep::Block(block) = step else {
4706 continue;
4707 };
4708 let is_cutoff_block = *block == cutoff.block;
4709 let block_data = &body.basic_blocks[*block];
4710
4711 for (statement_index, statement) in block_data.statements.iter().enumerate() {
4712 if is_cutoff_block
4713 && let Some(cutoff_statement) = cutoff.statement_index
4714 && statement_index >= cutoff_statement
4715 {
4716 return latest;
4717 }
4718
4719 let rustc_middle::mir::StatementKind::Assign(assign) = &statement.kind else {
4720 continue;
4721 };
4722 let (target, rvalue) = &**assign;
4723 if target.local == local {
4724 latest = abstract_value_from_rvalue(rvalue);
4725 }
4726 }
4727
4728 if is_cutoff_block {
4729 return latest;
4730 }
4731 }
4732
4733 latest
4734 }
4735
4736 fn path_cursor_cutoff(&self, cursor: ValueCursor) -> PathCursorCutoff {
4737 if let Some(definition) = self.forward.value_definitions.get(cursor) {
4738 return PathCursorCutoff {
4739 block: definition.block,
4740 statement_index: definition.statement_index,
4741 };
4742 }
4743
4744 PathCursorCutoff {
4745 block: self.forward.checkpoint.block,
4746 statement_index: None,
4747 }
4748 }
4749
4750 fn origin_key_for_value(
4752 &self,
4753 value: &AbstractValue<'tcx>,
4754 seen: &mut TraceSeen,
4755 ) -> Option<String> {
4756 self.origin_key_for_value_before(value, self.latest_cursor(), seen)
4757 }
4758
4759 fn origin_key_for_value_before(
4760 &self,
4761 value: &AbstractValue<'tcx>,
4762 cursor: ValueCursor,
4763 seen: &mut TraceSeen,
4764 ) -> Option<String> {
4765 let resolved = self
4766 .resolved_value_before(value, cursor, seen)
4767 .unwrap_or_else(|| value.clone());
4768 match resolved {
4769 AbstractValue::Ref(place) | AbstractValue::RawPtr(place) => Some(place_label(&place)),
4770 AbstractValue::Place(place) => self
4771 .source_from_points_to(&place)
4772 .map(|source| place_label(&source))
4773 .or_else(|| {
4774 self.forward.facts.iter().find_map(|fact| match fact {
4777 StateFact::PointsTo { pointer, source } if *pointer == place => {
4778 self.origin_key_for_value_before(
4779 &AbstractValue::Place(source.clone()),
4780 cursor,
4781 seen,
4782 )
4783 }
4784 _ => None,
4785 })
4786 })
4787 .or_else(|| Some(place_label(&place))),
4788 AbstractValue::Cast(inner, _) => self.origin_key_for_value_before(&inner, cursor, seen),
4789 AbstractValue::CallResult(call) if is_as_ptr_call(&call.func) => {
4790 let source_arg = call.effects.iter().find_map(|effect| match effect {
4791 crate::verify::call_summary::CallEffect::ReturnPointerFromArg { arg }
4792 | crate::verify::call_summary::CallEffect::ReturnAliasArg { arg } => Some(*arg),
4793 _ => None,
4794 })?;
4795 let call_cursor = self.call_definition_cursor(&call);
4796 self.origin_key_for_value_before(call.args.get(source_arg)?, call_cursor, seen)
4797 }
4798 AbstractValue::CallResult(call) => {
4799 if let Some(source_arg) = call.effects.iter().find_map(|effect| match effect {
4800 crate::verify::call_summary::CallEffect::ReturnPointerFromArg { arg }
4801 | crate::verify::call_summary::CallEffect::ReturnAliasArg { arg } => Some(*arg),
4802 crate::verify::call_summary::CallEffect::ReturnPointerAdd { base_arg, .. }
4803 | crate::verify::call_summary::CallEffect::ReturnPointerSub { base_arg, .. } => Some(*base_arg),
4804 _ => None,
4805 }) {
4806 let call_cursor = self.call_definition_cursor(&call);
4807 return self.origin_key_for_value_before(
4808 call.args.get(source_arg)?,
4809 call_cursor,
4810 seen,
4811 );
4812 }
4813 let destination = PlaceKey {
4814 base: PlaceBaseKey::Local(call.destination.as_usize()),
4815 fields: Vec::new(),
4816 };
4817 Some(place_label(&destination))
4818 }
4819 _ => Some(value_label(&resolved)),
4820 }
4821 }
4822
4823 fn guarded_len_for_index(
4825 &self,
4826 base_origin: &str,
4827 index: &AbstractValue<'tcx>,
4828 ) -> Option<AbstractValue<'tcx>> {
4829 let index = self
4830 .resolved_value(index, &mut HashSet::new())
4831 .unwrap_or_else(|| index.clone());
4832 for fact in &self.forward.facts {
4833 let StateFact::BranchEq { value, equals: 1, .. } = fact else {
4834 continue;
4835 };
4836 let predicate = self
4837 .resolved_value(value, &mut HashSet::new())
4838 .unwrap_or_else(|| value.clone());
4839 let AbstractValue::Binary(op, lhs, rhs) = predicate else {
4840 continue;
4841 };
4842 match op {
4843 BinOp::Lt | BinOp::Le => {
4844 if self.value_mentions(&lhs, &index)
4845 && self.len_matches_origin(&rhs, base_origin)
4846 {
4847 return Some(*rhs);
4848 }
4849 }
4850 BinOp::Gt | BinOp::Ge => {
4851 if self.value_mentions(&rhs, &index)
4852 && self.len_matches_origin(&lhs, base_origin)
4853 {
4854 return Some(*lhs);
4855 }
4856 }
4857 _ => {}
4858 }
4859 }
4860 None
4861 }
4862
4863 fn value_mentions(&self, haystack: &AbstractValue<'tcx>, needle: &AbstractValue<'tcx>) -> bool {
4865 self.value_mentions_inner(haystack, needle, &mut HashSet::new())
4866 }
4867
4868 fn value_mentions_inner(
4869 &self,
4870 haystack: &AbstractValue<'tcx>,
4871 needle: &AbstractValue<'tcx>,
4872 seen: &mut HashSet<(String, String)>,
4873 ) -> bool {
4874 let haystack = self
4875 .resolved_value(haystack, &mut HashSet::new())
4876 .unwrap_or_else(|| haystack.clone());
4877 let needle = self
4878 .resolved_value(needle, &mut HashSet::new())
4879 .unwrap_or_else(|| needle.clone());
4880 let haystack_label = value_label(&haystack);
4881 let needle_label = value_label(&needle);
4882 if haystack_label == needle_label {
4883 return true;
4884 }
4885 if !seen.insert((haystack_label, needle_label)) {
4886 return false;
4887 }
4888 match haystack {
4889 AbstractValue::Cast(inner, _) | AbstractValue::Unary(_, inner) => {
4890 self.value_mentions_inner(&inner, &needle, seen)
4891 }
4892 AbstractValue::Binary(_, lhs, rhs) => {
4893 self.value_mentions_inner(&lhs, &needle, seen)
4894 || self.value_mentions_inner(&rhs, &needle, seen)
4895 }
4896 _ => false,
4897 }
4898 }
4899
4900 fn len_matches_origin(&self, len: &AbstractValue<'tcx>, base_origin: &str) -> bool {
4902 self.len_matches_origin_inner(len, base_origin, &mut HashSet::new())
4903 }
4904
4905 fn len_matches_origin_inner(
4906 &self,
4907 len: &AbstractValue<'tcx>,
4908 base_origin: &str,
4909 seen: &mut HashSet<String>,
4910 ) -> bool {
4911 let label = value_label(len);
4912 if !seen.insert(label) {
4913 return false;
4914 }
4915 let resolved = self
4916 .resolved_value(len, &mut HashSet::new())
4917 .unwrap_or_else(|| len.clone());
4918 match resolved {
4919 AbstractValue::Place(place) => value_for_place(self.forward, &place)
4920 .is_some_and(|value| self.len_matches_origin_inner(value, base_origin, seen)),
4921 AbstractValue::Unary(UnOp::PtrMetadata, inner) => self
4922 .origin_key_for_value(&inner, &mut HashSet::new())
4923 .is_some_and(|origin| origin == base_origin),
4924 AbstractValue::CallResult(call) => call.effects.iter().any(|effect| {
4925 let crate::verify::call_summary::CallEffect::ReturnLengthOfArg { arg } = effect
4926 else {
4927 return false;
4928 };
4929 call.args
4930 .get(*arg)
4931 .and_then(|value| self.origin_key_for_value(value, &mut HashSet::new()))
4932 .is_some_and(|origin| origin == base_origin)
4933 }),
4934 _ => false,
4935 }
4936 }
4937
4938 fn source_from_points_to(&self, pointer: &PlaceKey) -> Option<PlaceKey> {
4940 self.forward.facts.iter().find_map(|fact| match fact {
4941 StateFact::PointsTo {
4942 pointer: fact_pointer,
4943 source,
4944 } if fact_pointer == pointer => Some(source.clone()),
4945 _ => None,
4946 })
4947 .or_else(|| {
4948 if pointer.fields.is_empty() {
4949 return None;
4950 }
4951 let mut base = pointer.clone();
4952 base.fields.clear();
4953 self.forward.facts.iter().find_map(|fact| match fact {
4954 StateFact::PointsTo {
4955 pointer: fact_pointer,
4956 source,
4957 } if *fact_pointer == base => Some(source.clone()),
4958 _ => None,
4959 })
4960 })
4961 }
4962
4963 fn init_target_terms(&mut self, place: &PlaceKey) -> Vec<Int<'ctx>> {
4969 let mut terms = Vec::new();
4970 if let Some(term) = self.term_for_place(place) {
4971 terms.push(term);
4972 }
4973 if let Some(term) = self.storage_addr_for_place(place, &mut HashSet::new()) {
4974 if !terms.iter().any(|existing| existing == &term) {
4975 terms.push(term);
4976 }
4977 }
4978 terms
4979 }
4980
4981 fn init_source_terms(&mut self, place: &PlaceKey) -> Vec<Int<'ctx>> {
4983 let mut terms = Vec::new();
4984 if let Some(term) = self.term_for_place(place) {
4985 terms.push(term);
4986 }
4987 if let Some(source) = self.source_from_points_to(place)
4988 && let Some(term) = self.storage_addr_for_place(&source, &mut HashSet::new())
4989 && !terms.iter().any(|existing| existing == &term)
4990 {
4991 terms.push(term);
4992 }
4993 terms
4994 }
4995
4996 fn storage_addr_for_place(
4998 &mut self,
4999 place: &PlaceKey,
5000 seen: &mut HashSet<PlaceKey>,
5001 ) -> Option<Int<'ctx>> {
5002 if !seen.insert(place.clone()) {
5003 return None;
5004 }
5005 if let Some(value) = value_for_place(self.forward, place) {
5006 match &value {
5007 AbstractValue::Place(inner) => {
5008 return self.storage_addr_for_place(inner, seen);
5009 }
5010 AbstractValue::Cast(inner, _) => {
5011 if let AbstractValue::Place(inner_place) = inner.as_ref() {
5012 return self.storage_addr_for_place(inner_place, seen);
5013 }
5014 }
5015 _ => {}
5016 }
5017 }
5018 if let Some(source) = self.source_from_points_to(place) {
5019 return self.storage_addr_for_place(&source, seen);
5020 }
5021 Some(Int::new_const(
5022 self.ctx,
5023 format!("addr_{}", place_name(place)),
5024 ))
5025 }
5026
5027 fn bounds_len_for_origin(
5029 &mut self,
5030 origin_key: &str,
5031 index: Option<&AbstractValue<'tcx>>,
5032 ) -> Option<(Int<'ctx>, SmtTerm)> {
5033 if let Some(len_place) = self.len_place_for_origin(origin_key) {
5034 return Some((self.term_for_place(&len_place)?, SmtTerm::Place(len_place)));
5035 }
5036 if let Some(index) = index
5037 && let Some(len_value) = self.guarded_len_for_index(origin_key, index)
5038 {
5039 return Some((
5040 self.term_for_value(&len_value, &mut HashSet::new())?,
5041 SmtTerm::Value(value_label(&len_value)),
5042 ));
5043 }
5044 let result = self.allocated_len_for_origin(origin_key)
5045 .filter(|&len| len > 0)
5046 .map(|len| (Int::from_u64(self.ctx, len), SmtTerm::Const(len)))
5047 .or_else(|| {
5048 if self.is_maybe_uninit_origin(origin_key) {
5049 let len_term = Int::from_u64(self.ctx, u64::MAX / 8);
5050 Some((len_term, SmtTerm::Const(u64::MAX / 8)))
5051 } else if self.is_slice_pointer_origin(origin_key) {
5052 let len_key = format!("len({})", origin_key);
5053 let len_term = self.symbolic_len_term(&len_key);
5054 Some((len_term, SmtTerm::Value(len_key)))
5055 } else {
5056 None
5057 }
5058 });
5059 result
5060 }
5061
5062 fn is_slice_pointer_origin(&self, origin_key: &str) -> bool {
5067 self.forward.facts.iter().any(|fact| {
5068 let StateFact::Call(call) = fact else { return false };
5069 let is_ptr_like = is_as_ptr_call(&call.func);
5070 let is_ptr_range = PrimitiveCall::classify(&call.func)
5071 .is_some_and(|p| matches!(p, PrimitiveCall::AsPtrRange | PrimitiveCall::AsMutPtrRange));
5072 (is_ptr_like || is_ptr_range)
5073 && call.effects.iter().any(|effect| {
5074 matches!(
5075 effect,
5076 crate::verify::call_summary::CallEffect::ReturnAliasArg { .. }
5077 | crate::verify::call_summary::CallEffect::ReturnPointerFromArg { .. }
5078 )
5079 })
5080 && call.args.iter().any(|arg| {
5081 self.origin_key_for_value(arg, &mut HashSet::new())
5082 .is_some_and(|key| key == origin_key)
5083 })
5084 })
5085 }
5086
5087 fn is_maybe_uninit_origin(&self, origin_key: &str) -> bool {
5091 self.forward.facts.iter().any(|fact| {
5092 if let StateFact::KnownAllocated { object, ty_name, elements, .. } = fact {
5093 *elements == 0
5094 && (place_label(object) == origin_key)
5095 && ty_name.contains("MaybeUninit")
5096 } else {
5097 false
5098 }
5099 })
5100 }
5101
5102 fn len_place_for_origin(&self, origin_key: &str) -> Option<PlaceKey> {
5103 for fact in &self.forward.facts {
5104 let StateFact::Call(call) = fact else {
5105 continue;
5106 };
5107 let Some(source_arg) = call.effects.iter().find_map(|effect| {
5108 let crate::verify::call_summary::CallEffect::ReturnLengthOfArg { arg } = effect
5109 else {
5110 return None;
5111 };
5112 Some(*arg)
5113 }) else {
5114 continue;
5115 };
5116 let Some(source) = call.args.get(source_arg) else {
5117 continue;
5118 };
5119 let Some(key) = self.origin_key_for_value(source, &mut HashSet::new()) else {
5120 continue;
5121 };
5122 if key == origin_key {
5123 return Some(PlaceKey {
5124 base: PlaceBaseKey::Local(call.destination.as_usize()),
5125 fields: Vec::new(),
5126 });
5127 }
5128 }
5129 None
5130 }
5131
5132 fn allocated_len_for_origin(&self, origin_key: &str) -> Option<u64> {
5133 self.forward.facts.iter().find_map(|fact| match fact {
5134 StateFact::KnownAllocated {
5135 place,
5136 object,
5137 elements,
5138 ty_name,
5139 ..
5140 } if place_label(object) == origin_key || place_label(place) == origin_key => {
5141 if *elements == 0 || (ty_name.contains("MaybeUninit") && ty_name.contains('[')) {
5145 return None;
5146 }
5147 Some(*elements)
5148 }
5149 _ => None,
5150 })
5151 }
5152
5153 fn origin_is_initialized_for_ty(&self, origin_key: &str, required_ty_name: &str) -> bool {
5154 if self.forward.facts.iter().any(|fact| {
5155 let StateFact::KnownAllocated {
5156 place,
5157 object,
5158 ty_name,
5159 ..
5160 } = fact
5161 else {
5162 return false;
5163 };
5164 if place_label(object) != origin_key && place_label(place) != origin_key {
5165 return false;
5166 }
5167 if ty_name.contains("MaybeUninit") {
5168 return false;
5169 }
5170 init_type_compatible(ty_name, required_ty_name)
5171 || self
5172 .initialized_element_ty_for_place(object)
5173 .is_some_and(|elem| init_type_compatible(&elem, required_ty_name))
5174 }) {
5175 return true;
5176 }
5177 if let Some(local_index) = origin_key
5178 .strip_prefix('_')
5179 .and_then(|s| s.parse::<usize>().ok())
5180 {
5181 let local = rustc_middle::mir::Local::from_usize(local_index);
5182 let ty = self.tcx.optimized_mir(self.checkpoint.caller).local_decls[local].ty;
5183 if let Some(elem_ty_name) = initialized_element_ty_name(ty) {
5184 if init_type_compatible(&elem_ty_name, required_ty_name) {
5185 return true;
5186 }
5187 }
5188 }
5189 false
5190 }
5191
5192 fn initialized_element_ty_for_place(&self, place: &PlaceKey) -> Option<String> {
5193 let ty = self.place_ty(place)?;
5194 initialized_element_ty_name(ty)
5195 }
5196}
5197
5198pub(crate) struct PointerBounds<'ctx> {
5200 index: Int<'ctx>,
5201 len: Int<'ctx>,
5202 index_term: SmtTerm,
5203 len_term: SmtTerm,
5204 origin_key: String,
5205}
5206
5207fn operand_place(operand: &Operand<'_>) -> Option<PlaceKey> {
5209 match operand {
5210 Operand::Copy(place) | Operand::Move(place) => Some(PlaceKey::from_mir_place(place)),
5211 Operand::Constant(_) => None,
5212 #[cfg(rapx_rustc_ge_196)]
5213 Operand::RuntimeChecks(_) => None,
5214 }
5215}
5216
5217fn contract_expr_from_place_key<'tcx>(place: PlaceKey) -> ContractExpr<'tcx> {
5218 let base = match place.base {
5219 PlaceBaseKey::Return => PlaceBase::Return,
5220 PlaceBaseKey::Local(local) => PlaceBase::Local(local),
5221 PlaceBaseKey::Arg(arg) => PlaceBase::Arg(arg),
5222 };
5223 let projections = place
5224 .fields
5225 .into_iter()
5226 .map(|index| ContractProjection::Field { index, ty: None })
5227 .collect();
5228 ContractExpr::Place(ContractPlace { base, projections })
5229}
5230
5231fn value_for_place<'a, 'tcx>(
5233 forward: &'a ForwardVisitResult<'tcx>,
5234 place: &PlaceKey,
5235) -> Option<&'a AbstractValue<'tcx>> {
5236 let local = place.local()?;
5237 forward.values.get(&local)
5238}
5239
5240fn pointee_ty<'tcx>(ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
5242 match ty.kind() {
5243 TyKind::RawPtr(ty, _) | TyKind::Ref(_, ty, _) => Some(*ty),
5244 _ => None,
5245 }
5246}
5247
5248fn pointee_ty_str<'tcx>(ty: Ty<'tcx>) -> Option<String> {
5250 match ty.kind() {
5251 TyKind::RawPtr(inner, _) | TyKind::Ref(_, inner, _) => Some(format!("{inner:?}")),
5252 _ => None,
5253 }
5254}
5255
5256fn is_len_carrying_ty(ty: Ty<'_>) -> bool {
5257 match ty.kind() {
5258 TyKind::Ref(_, inner, _) => is_len_carrying_ty(*inner),
5259 TyKind::Slice(_) | TyKind::Str => true,
5260 _ => false,
5261 }
5262}
5263
5264fn initialized_element_ty_name<'tcx>(ty: Ty<'tcx>) -> Option<String> {
5265 let ty_name = format!("{ty:?}");
5266 if ty_name.contains("MaybeUninit") {
5267 return None;
5268 }
5269 match ty.kind() {
5270 TyKind::Ref(_, inner, _) | TyKind::RawPtr(inner, _) => {
5271 initialized_element_ty_name(*inner).or_else(|| Some(format!("{inner:?}")))
5272 }
5273 TyKind::Array(elem, _) | TyKind::Slice(elem) => Some(format!("{elem:?}")),
5274 TyKind::Adt(def, args) => {
5275 let def_name = format!("{:?}", def.did());
5276 let is_vec = def_name.contains("Vec")
5277 || ty_name.starts_with("std::vec::Vec<")
5278 || ty_name.starts_with("alloc::vec::Vec<")
5279 || ty_name.starts_with("Vec<");
5280 if is_vec {
5281 return args.iter().find_map(|arg| match arg.kind() {
5282 GenericArgKind::Type(ty) => Some(format!("{ty:?}")),
5283 _ => None,
5284 });
5285 }
5286 Some(ty_name)
5287 }
5288 _ => Some(ty_name),
5289 }
5290}
5291
5292fn is_unsigned_integral_ty(ty: Ty<'_>) -> bool {
5293 matches!(ty.kind(), TyKind::Uint(_))
5294}
5295
5296fn is_pointer_add_call(func: &str) -> bool {
5298 PrimitiveCall::classify(func).is_some_and(PrimitiveCall::is_pointer_add_like)
5299}
5300
5301fn is_pointer_sub_call(func: &str) -> bool {
5303 PrimitiveCall::classify(func).is_some_and(PrimitiveCall::is_pointer_sub_like)
5304}
5305
5306pub(crate) fn is_as_ptr_call(func: &str) -> bool {
5308 PrimitiveCall::classify(func).is_some_and(PrimitiveCall::is_as_ptr_like)
5309}
5310
5311fn call_has_pointer_add_effect(call: &CallSummary<'_>) -> bool {
5313 call.effects.iter().any(|effect| {
5314 matches!(
5315 effect,
5316 crate::verify::call_summary::CallEffect::ReturnPointerAdd { .. }
5317 )
5318 })
5319}
5320
5321fn call_has_pointer_sub_effect(call: &CallSummary<'_>) -> bool {
5322 call.effects.iter().any(|effect| {
5323 matches!(
5324 effect,
5325 crate::verify::call_summary::CallEffect::ReturnPointerSub { .. }
5326 )
5327 })
5328}
5329
5330fn abstract_value_from_rvalue<'tcx>(rvalue: &Rvalue<'tcx>) -> Option<AbstractValue<'tcx>> {
5331 Some(match rvalue {
5332 Rvalue::Use(operand, ..) => abstract_value_from_operand(operand),
5333 Rvalue::Repeat(operand, _) => {
5334 AbstractValue::Repeat(Box::new(abstract_value_from_operand(operand)))
5335 }
5336 Rvalue::Ref(_, _, place) => AbstractValue::Ref(PlaceKey::from_mir_place(place)),
5337 Rvalue::RawPtr(_, place) => AbstractValue::RawPtr(PlaceKey::from_mir_place(place)),
5338 Rvalue::Cast(_, operand, ty) => {
5339 AbstractValue::Cast(Box::new(abstract_value_from_operand(operand)), *ty)
5340 }
5341 Rvalue::BinaryOp(op, pair) => {
5342 let (lhs, rhs) = &**pair;
5343 AbstractValue::Binary(
5344 *op,
5345 Box::new(abstract_value_from_operand(lhs)),
5346 Box::new(abstract_value_from_operand(rhs)),
5347 )
5348 }
5349 Rvalue::UnaryOp(op, operand) => {
5350 AbstractValue::Unary(*op, Box::new(abstract_value_from_operand(operand)))
5351 }
5352 Rvalue::CopyForDeref(place) => AbstractValue::Place(PlaceKey::from_mir_place(place)),
5353 Rvalue::ThreadLocalRef(def_id) => AbstractValue::ThreadLocal(format!("{def_id:?}")),
5354 #[cfg(all(rapx_rustc_ge_193, not(rapx_rustc_ge_196)))]
5355 Rvalue::NullaryOp(op) => AbstractValue::Nullary(format!("{op:?}")),
5356 #[cfg(all(not(rapx_rustc_ge_193), not(rapx_rustc_ge_196)))]
5357 Rvalue::NullaryOp(op, _) => AbstractValue::Nullary(format!("{op:?}")),
5358 Rvalue::Discriminant(place) => AbstractValue::Discriminant(PlaceKey::from_mir_place(place)),
5359 Rvalue::Aggregate(kind, operands) => {
5360 AbstractValue::Aggregate(format!("{kind:?}"), operands.len())
5361 }
5362 #[cfg(not(rapx_rustc_ge_196))]
5363 Rvalue::ShallowInitBox(operand, ty) => {
5364 AbstractValue::ShallowInitBox(Box::new(abstract_value_from_operand(operand)), *ty)
5365 }
5366 _ => return None,
5367 })
5368}
5369
5370fn infer_element_ty<'tcx>(ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
5371 match ty.kind() {
5372 TyKind::Slice(elem_ty) => Some(*elem_ty),
5373 TyKind::Array(elem_ty, _) => Some(*elem_ty),
5374 TyKind::Ref(_, inner, _) => infer_element_ty(*inner),
5375 TyKind::RawPtr(inner_ty, _) => infer_element_ty(*inner_ty),
5376 _ => Some(ty),
5377 }
5378}
5379
5380fn abstract_value_from_operand<'tcx>(operand: &Operand<'tcx>) -> AbstractValue<'tcx> {
5381 match operand {
5382 Operand::Copy(place) | Operand::Move(place) => {
5383 AbstractValue::Place(PlaceKey::from_mir_place(place))
5384 }
5385 Operand::Constant(constant) => {
5386 let text = format!("{:?}", constant.const_);
5387 const_int_from_debug(&text)
5388 .map(AbstractValue::ConstInt)
5389 .unwrap_or(AbstractValue::Const(text))
5390 }
5391 #[cfg(rapx_rustc_ge_196)]
5392 Operand::RuntimeChecks(_) => AbstractValue::Unknown("runtime-checks".to_string()),
5393 }
5394}
5395
5396fn place_name(place: &PlaceKey) -> String {
5398 let base = match place.base {
5399 PlaceBaseKey::Return => "return".to_string(),
5400 PlaceBaseKey::Local(local) => format!("local_{local}"),
5401 PlaceBaseKey::Arg(arg) => format!("arg_{arg}"),
5402 };
5403 if place.fields.is_empty() {
5404 base
5405 } else {
5406 format!(
5407 "{}_{}",
5408 base,
5409 place
5410 .fields
5411 .iter()
5412 .map(|field| field.to_string())
5413 .collect::<Vec<_>>()
5414 .join("_")
5415 )
5416 }
5417}
5418
5419pub(crate) fn place_label(place: &PlaceKey) -> String {
5421 let base = match place.base {
5422 PlaceBaseKey::Return => "return".to_string(),
5423 PlaceBaseKey::Local(local) => format!("_{local}"),
5424 PlaceBaseKey::Arg(arg) => format!("arg{arg}"),
5425 };
5426 if place.fields.is_empty() {
5427 base
5428 } else {
5429 format!(
5430 "{}.{}",
5431 base,
5432 place
5433 .fields
5434 .iter()
5435 .map(|field| field.to_string())
5436 .collect::<Vec<_>>()
5437 .join(".")
5438 )
5439 }
5440}
5441
5442pub(crate) fn value_label(value: &AbstractValue<'_>) -> String {
5444 match value {
5445 AbstractValue::Unknown(text) => format!("unknown({text})"),
5446 AbstractValue::Place(place) => place_label(place),
5447 AbstractValue::Ref(place) => format!("&{}", place_label(place)),
5448 AbstractValue::RawPtr(place) => format!("raw({})", place_label(place)),
5449 AbstractValue::ThreadLocal(name) => format!("thread_local({name})"),
5450 AbstractValue::ConstInt(value) => value.to_string(),
5451 AbstractValue::ConstParam(name) => format!("const_{name}"),
5452 AbstractValue::Const(text) => const_int_from_debug(text)
5453 .map(|value| value.to_string())
5454 .unwrap_or_else(|| text.trim().to_string()),
5455 AbstractValue::Repeat(inner) => format!("repeat({})", value_label(inner)),
5456 AbstractValue::Cast(inner, ty) => format!("cast({}, {ty:?})", value_label(inner)),
5457 AbstractValue::Unary(op, inner) => format!("{op:?}({})", value_label(inner)),
5458 AbstractValue::Binary(op, lhs, rhs) => {
5459 format!(
5460 "({} {} {})",
5461 value_label(lhs),
5462 binop_label(*op),
5463 value_label(rhs)
5464 )
5465 }
5466 AbstractValue::Nullary(name) => name.clone(),
5467 AbstractValue::Discriminant(place) => format!("discriminant({})", place_label(place)),
5468 AbstractValue::Aggregate(name, len) => format!("{name}[{len}]"),
5469 #[cfg(not(rapx_rustc_ge_196))]
5470 AbstractValue::ShallowInitBox(inner, ty) => {
5471 format!("box({}, {ty:?})", value_label(inner))
5472 }
5473 AbstractValue::CallResult(call) if is_pointer_add_call(&call.func) => {
5474 let base = call
5475 .args
5476 .first()
5477 .map(value_label)
5478 .unwrap_or_else(|| "?".to_string());
5479 let index = call
5480 .args
5481 .get(1)
5482 .map(value_label)
5483 .unwrap_or_else(|| "?".to_string());
5484 format!("{base}.add({index})")
5485 }
5486 AbstractValue::CallResult(call) => {
5487 let destination = PlaceKey {
5488 base: PlaceBaseKey::Local(call.destination.as_usize()),
5489 fields: Vec::new(),
5490 };
5491 format!(
5492 "{} = call({})",
5493 place_label(&destination),
5494 short_func_name(&call.func)
5495 )
5496 }
5497 }
5498}
5499
5500fn smt_term_for_value(value: &AbstractValue<'_>) -> Option<SmtTerm> {
5501 match value {
5502 AbstractValue::ConstInt(value) => u64::try_from(*value).ok().map(SmtTerm::Const),
5503 AbstractValue::Const(text) => const_int_from_debug(text)
5504 .and_then(|value| u64::try_from(value).ok())
5505 .map(SmtTerm::Const)
5506 .or_else(|| {
5507 let name = sanitize_smt_name(text);
5508 if name.is_empty() {
5509 None
5510 } else {
5511 Some(SmtTerm::Value(format!("const_{name}")))
5512 }
5513 }),
5514 AbstractValue::Place(place) => Some(SmtTerm::Place(place.clone())),
5515 AbstractValue::Cast(inner, _) => smt_term_for_value(inner),
5516 AbstractValue::Binary(op, lhs, rhs) => {
5517 let lhs = Box::new(smt_term_for_value(lhs)?);
5518 let rhs = Box::new(smt_term_for_value(rhs)?);
5519 match op {
5520 BinOp::Add | BinOp::AddWithOverflow => Some(SmtTerm::Add(lhs, rhs)),
5521 BinOp::Sub | BinOp::SubWithOverflow => Some(SmtTerm::Sub(lhs, rhs)),
5522 BinOp::Mul | BinOp::MulWithOverflow => Some(SmtTerm::Mul(lhs, rhs)),
5523 BinOp::Div => Some(SmtTerm::Div(lhs, rhs)),
5524 BinOp::Rem => Some(SmtTerm::Rem(lhs, rhs)),
5525 _ => None,
5526 }
5527 }
5528 _ => None,
5529 }
5530}
5531
5532fn binop_label(op: BinOp) -> &'static str {
5534 match op {
5535 BinOp::Add => "+",
5536 BinOp::Sub => "-",
5537 BinOp::Mul => "*",
5538 BinOp::Div => "/",
5539 BinOp::Rem => "%",
5540 BinOp::Eq => "==",
5541 BinOp::Ne => "!=",
5542 BinOp::Lt => "<",
5543 BinOp::Le => "<=",
5544 BinOp::Gt => ">",
5545 BinOp::Ge => ">=",
5546 _ => "?",
5547 }
5548}
5549
5550fn short_func_name(func: &str) -> String {
5552 func.rsplit("::")
5553 .next()
5554 .unwrap_or(func)
5555 .trim_matches('"')
5556 .to_string()
5557}
5558
5559fn const_param_name_from_debug(text: &str) -> Option<String> {
5561 let text = text.trim();
5562 if let Some(rest) = text.find("Param(").map(|i| &text[i + 6..]) {
5564 let end = rest.find(')')?;
5565 let name = rest[..end].trim().to_string();
5566 if !name.is_empty() { return Some(name); }
5567 }
5568 if let Some(rest) = text.strip_prefix("Ty(") {
5570 let comma = rest.find(',')?;
5571 let after_comma = rest[comma + 1..].trim();
5572 let end = after_comma.find(')').unwrap_or(after_comma.len());
5573 let name = after_comma[..end].trim();
5574 if let Some(slash) = name.rfind('/') {
5576 let name = name[..slash].trim();
5577 if !name.is_empty() { return Some(name.to_string()); }
5578 }
5579 if !name.is_empty() { return Some(name.to_string()); }
5580 }
5581 None
5582}
5583
5584fn const_int_from_debug(text: &str) -> Option<u128> {
5586 let text = text.trim();
5587 if text == "const true" {
5588 return Some(1);
5589 }
5590 if text == "const false" {
5591 return Some(0);
5592 }
5593 if let Some(rest) = text.strip_prefix("const ") {
5594 let digits = rest
5595 .chars()
5596 .take_while(|ch| ch.is_ascii_digit())
5597 .collect::<String>();
5598 if digits.is_empty() {
5599 return None;
5600 }
5601 return digits.parse().ok();
5602 }
5603
5604 let scalar = text.strip_prefix("Val(Scalar(0x")?;
5605 let digits = scalar
5606 .chars()
5607 .take_while(|ch| ch.is_ascii_hexdigit())
5608 .collect::<String>();
5609 if digits.is_empty() {
5610 None
5611 } else {
5612 u128::from_str_radix(&digits, 16).ok()
5613 }
5614}
5615
5616fn init_type_compatible(init_ty_name: &str, required_ty_name: &str) -> bool {
5617 if normalize_init_ty_name(init_ty_name) == normalize_init_ty_name(required_ty_name) {
5618 return true;
5619 }
5620 if let Some(array_elem) = array_elem_type(required_ty_name) {
5621 if init_type_compatible(init_ty_name, &array_elem) {
5622 return true;
5623 }
5624 }
5625 false
5626}
5627
5628fn allocated_type_compatible(allocated_ty_name: &str, required_ty_name: &str) -> bool {
5629 if normalize_init_ty_name(allocated_ty_name) == normalize_init_ty_name(required_ty_name) {
5630 return true;
5631 }
5632 if let Some(array_elem) = array_elem_type(required_ty_name) {
5633 if allocated_type_compatible(allocated_ty_name, &array_elem) {
5634 return true;
5635 }
5636 }
5637 false
5638}
5639
5640fn array_elem_type(ty_name: &str) -> Option<String> {
5641 let name = ty_name.trim();
5642 if name.starts_with('[') && name.ends_with(']') {
5643 let inner = &name[1..name.len() - 1];
5644 if let Some(semi) = inner.rfind("; ") {
5645 return Some(format!(" {}", &inner[..semi]));
5646 }
5647 }
5648 None
5649}
5650
5651fn allocation_object_invalidated<'tcx>(
5652 forward: &ForwardVisitResult<'tcx>,
5653 object: &PlaceKey,
5654) -> bool {
5655 forward.facts.iter().any(|fact| match fact {
5656 StateFact::LocalDead(local) => object.local() == Some(*local),
5657 StateFact::Drop(place) => place.overlaps(object) || object.overlaps(place),
5658 _ => false,
5659 })
5660}
5661
5662fn normalize_init_ty_name(ty_name: &str) -> String {
5663 let ty_name = ty_name.trim();
5664 for prefix in [
5665 "std::mem::MaybeUninit<",
5666 "core::mem::MaybeUninit<",
5667 "MaybeUninit<",
5668 ] {
5669 if let Some(inner) = ty_name
5670 .strip_prefix(prefix)
5671 .and_then(|s| s.strip_suffix('>'))
5672 {
5673 return normalize_init_ty_name(inner);
5674 }
5675 }
5676 if let Some(rest) = ty_name.strip_prefix('[')
5677 && rest.ends_with(']')
5678 {
5679 let inner = &rest[..rest.len() - 1];
5680 if let Some(semi_pos) = inner.rfind("; ") {
5681 return normalize_init_ty_name(&inner[..semi_pos]);
5682 }
5683 return normalize_init_ty_name(inner);
5685 }
5686 let mut result = ty_name.to_string();
5687 while let Some(slash) = result.rfind('/')
5688 && slash + 1 < result.len()
5689 && result[slash + 1..].starts_with('#')
5690 {
5691 let trimmed = result[..slash].to_string();
5692 if let Some(after_hash) = result[slash + 2..].chars().next()
5693 && after_hash.is_ascii_digit()
5694 {
5695 result = trimmed;
5696 continue;
5697 }
5698 break;
5699 }
5700 result
5701}
5702
5703fn sanitize_smt_name(name: &str) -> String {
5705 name.chars()
5706 .map(|ch| {
5707 if ch.is_ascii_alphanumeric() || ch == '_' {
5708 ch
5709 } else {
5710 '_'
5711 }
5712 })
5713 .collect()
5714}
5715
5716fn ptr_metadata_origin<'tcx>(
5717 value: &AbstractValue<'tcx>,
5718 model: &SmtModel<'_, '_, 'tcx>,
5719) -> Option<String> {
5720 let resolved = model.resolved_value(value, &mut TraceSeen::new())
5721 .unwrap_or_else(|| value.clone());
5722 match &resolved {
5723 AbstractValue::Unary(UnOp::PtrMetadata, inner) => {
5724 let cursor = model.latest_cursor();
5725 model.origin_key_for_value_before(inner, cursor, &mut TraceSeen::new())
5726 }
5727 AbstractValue::CallResult(call) => {
5728 if call.effects.iter().any(|e| matches!(e, crate::verify::call_summary::CallEffect::ReturnLengthOfArg { .. }))
5729 {
5730 let arg = call.effects.iter().find_map(|e| match e {
5731 crate::verify::call_summary::CallEffect::ReturnLengthOfArg { arg } => Some(*arg),
5732 _ => None,
5733 })?;
5734 let call_cursor = model.call_definition_cursor(call);
5735 let source = call.args.get(arg)?;
5736 model.origin_key_for_value_before(source, call_cursor, &mut TraceSeen::new())
5737 } else {
5738 None
5739 }
5740 }
5741 _ => None,
5742 }
5743}
5744
5745fn pointee_stride_from_types<'tcx>(
5746 tcx: TyCtxt<'tcx>,
5747 src_pointee: Ty<'tcx>,
5748 dst_pointee: Ty<'tcx>,
5749) -> Option<SmtTerm> {
5750 use rustc_middle::ty::ConstKind;
5751 if src_pointee == dst_pointee {
5752 return Some(SmtTerm::Const(1));
5753 }
5754 if let TyKind::Array(elem, len) = src_pointee.kind()
5755 && *elem == dst_pointee
5756 {
5757 return len.try_to_target_usize(tcx).map(SmtTerm::Const).or_else(|| {
5758 if let ConstKind::Param(param) = len.kind() {
5759 Some(SmtTerm::ConstParam(param.name.to_string()))
5760 } else {
5761 None
5762 }
5763 });
5764 }
5765 None
5766}