1use crate::helpers::mir_scan::Checkpoint;
8use crate::verify::api_classify;
9use crate::verify::contract::{
10 ContractExpr, NumericBinOp, PlaceBase, Property, PropertyArg, RelOp,
11};
12use crate::verify::report::{CheckResult, UnknownReason};
13use crate::verify::vm::state::{OffsetKind, VmState, VmValue};
14use rustc_middle::mir::{Local, Operand, Rvalue, StatementKind};
15use rustc_middle::ty::{Ty, TyKind};
16use z3::{
17 SatResult, Solver,
18 ast::{Ast, Bool, Int},
19};
20
21use super::PropertyChecker;
22
23impl PropertyChecker {
24 pub(super) fn check_in_bound<'z3, 'tcx>(
25 &self,
26 vm_state: &VmState<'z3, 'tcx>,
27 solver: &Solver<'z3>,
28 checkpoint: &Checkpoint<'tcx>,
29 property: &Property<'tcx>,
30 ) -> CheckResult {
31 if vm_state.path_facts.has_checked_bounds {
34 return CheckResult::ProvedByRule;
35 }
36 if property.for_each().is_some() {
39 return CheckResult::ProvedByRule;
40 }
41
42 if let Some(PropertyArg::Expr(ContractExpr::IndexAccess { index: _, .. })) =
43 property.args().first()
44 {
45 return self.check_in_bound_slice(vm_state, solver, checkpoint, property);
46 }
47
48 let required_ty = Self::ty_arg(property, 1);
49 if self.zst_guard(vm_state, checkpoint, property) {
50 return CheckResult::ProvedByRule;
51 }
52
53 let Some(value) = self.target_value(vm_state, checkpoint, property) else {
54 return CheckResult::Unknown(UnknownReason::Unimplemented);
55 };
56 if value.facts.in_bounds {
60 let count_one = property
61 .args()
62 .get(2)
63 .and_then(|a| self.resolve_arg_term(vm_state, checkpoint, a))
64 .is_none_or(|c| c.simplify().as_u64() == Some(1));
65 if count_one {
66 return CheckResult::ProvedByRule;
67 }
68 }
69 if matches!(value.ty.kind(), TyKind::Ref(..)) {
70 return CheckResult::ProvedByRule;
71 }
72 if value.is_pointer() {
73 if let TyKind::Adt(adt_def, _) = value.ty.kind() {
74 if api_classify::is_std_nonnull(adt_def.did()) {
75 return CheckResult::ProvedByRule;
76 }
77 }
78 }
79 if self.count_is_offset_of(vm_state, checkpoint, property, &value) {
84 return CheckResult::ProvedByRule;
85 }
86 if self.count_is_zero(vm_state, checkpoint, property, 2) {
90 return CheckResult::ProvedByRule;
91 }
92 let access = self.access_bytes(vm_state, property, 1, 2, checkpoint, &value);
93 let Some(alloc_id) = value.provenance_alloc_id() else {
94 return CheckResult::Unknown(UnknownReason::Unimplemented);
95 };
96 let base = vm_state.allocation_base(alloc_id).clone();
97 let size = vm_state.allocation_size(alloc_id).clone();
98
99 let alloc = vm_state.alloc(alloc_id);
100 if let (Some(alloc_elem_ty), Some(req_ty)) = (alloc.element_ty.as_ty(), required_ty) {
101 if self.alloc_elem_is_array_of(alloc_elem_ty, req_ty) {
102 return CheckResult::ProvedByRule;
103 }
104 }
105
106 if alloc.is_external() && size.simplify().as_u64() == Some(i64::MAX as u64) {
112 return CheckResult::ProvedByRule;
113 }
114
115 if !api_classify::is_pointer_sub(checkpoint.callee) {
124 if let (Some(len), Some(k)) = (
125 alloc.slice_len().cloned(),
126 value
127 .provenance
128 .as_ref()
129 .and_then(|p| match &p.offset_kind {
130 Some(OffsetKind::Element(e)) => Some(e.clone()),
131 _ => None,
132 }),
133 ) {
134 let count_term = property
135 .args()
136 .get(2)
137 .and_then(|a| self.resolve_arg_term(vm_state, checkpoint, a))
138 .unwrap_or_else(|| Int::from_u64(vm_state.z3_ctx, 1));
139 let zero = Int::from_u64(vm_state.z3_ctx, 0);
140 let covered = Int::add(vm_state.z3_ctx, &[&k, &count_term]);
141 solver.push();
142 solver.assert(&z3::ast::Bool::or(
143 vm_state.z3_ctx,
144 &[&covered.gt(&len), &k.lt(&zero)],
145 ));
146 let r = match solver.check() {
147 SatResult::Unsat => CheckResult::ProvedBySmt,
148 SatResult::Sat => CheckResult::Failed,
149 _ => CheckResult::Unknown(UnknownReason::SmtTimeout),
150 };
151 solver.pop(1);
152 return r;
153 }
154 }
155
156 let alloc_elem_is_generic = vm_state
157 .alloc(alloc_id)
158 .element_ty
159 .as_ty()
160 .is_some_and(|ty| matches!(ty.kind(), TyKind::Param(_)));
161 let fallback_for_generic =
162 alloc_elem_is_generic && !size.as_u64().is_some() && !access.as_u64().is_some();
163
164 solver.push();
165 if value
171 .provenance
172 .as_ref()
173 .is_some_and(|prov| matches!(prov.offset_kind, Some(OffsetKind::Field)))
174 {
175 let field_size = crate::helpers::mir_utils::pointee_ty(value.ty)
176 .map(|ty| vm_state.size_sym_read(ty))
177 .unwrap_or_else(|| Int::from_u64(vm_state.z3_ctx, 1));
178 solver.assert(&access.le(&field_size).not());
179 let r = match solver.check() {
180 SatResult::Unsat => CheckResult::ProvedBySmt,
181 SatResult::Sat => CheckResult::Failed,
182 _ => CheckResult::Unknown(UnknownReason::SmtTimeout),
183 };
184 solver.pop(1);
185 return r;
186 }
187
188 let bound = Int::add(vm_state.z3_ctx, &[&base, &size]);
189 let (above_negated, below_negated) = if api_classify::is_pointer_sub(checkpoint.callee) {
193 let walked = Int::sub(vm_state.z3_ctx, &[&value.z3_term, &access]);
194 (value.z3_term.gt(&bound), walked.lt(&base))
195 } else {
196 let covered = Int::add(vm_state.z3_ctx, &[&value.z3_term, &access]);
197 (covered.gt(&bound), value.z3_term.lt(&base))
198 };
199 let negated = z3::ast::Bool::or(vm_state.z3_ctx, &[&above_negated, &below_negated]);
200
201 if let Some(s) = vm_state.generic_elem_size(alloc_id) {
204 solver.pop(1);
205 let on_sat = if fallback_for_generic {
206 CheckResult::Unknown(UnknownReason::Unimplemented)
207 } else {
208 CheckResult::Failed
209 };
210 return Self::smt_check_size_split(vm_state, &s, &negated, on_sat);
211 }
212
213 solver.assert(&negated);
214 let sat_result = solver.check();
215 let r = match sat_result {
216 SatResult::Unsat => CheckResult::ProvedBySmt,
217 SatResult::Sat if fallback_for_generic => {
218 CheckResult::Unknown(UnknownReason::Unimplemented)
219 }
220 SatResult::Sat => CheckResult::Failed,
221 _ => CheckResult::Unknown(UnknownReason::SmtTimeout),
222 };
223 solver.pop(1);
224 r
225 }
226
227 pub(super) fn count_is_offset_of<'z3, 'tcx>(
228 &self,
229 vm_state: &VmState<'z3, 'tcx>,
230 checkpoint: &Checkpoint<'tcx>,
231 property: &Property<'tcx>,
232 value: &VmValue<'z3, 'tcx>,
233 ) -> bool {
234 let Some(count_arg) = property.args().get(2) else {
235 return false;
236 };
237 let PropertyArg::Expr(ContractExpr::Place(cp)) = count_arg else {
238 return false;
239 };
240 let PlaceBase::Arg(n) = cp.base else {
241 return false;
242 };
243 let Some(operand) = checkpoint.args.get(n) else {
244 return false;
245 };
246 let Operand::Constant(c) = operand else {
247 return false;
248 };
249 let Some(container) =
250 crate::helpers::mir_utils::offset_of_container(vm_state.tcx, &c.const_)
251 else {
252 return false;
253 };
254 let at_base = value
257 .provenance
258 .as_ref()
259 .is_some_and(|p| p.offset.as_u64() == Some(0));
260 if !at_base {
261 return false;
262 }
263 crate::helpers::mir_utils::pointee_ty(value.ty).is_some_and(|pointee| pointee == container)
265 }
266
267 pub(super) fn resolve_index_access_args(
268 property: &Property<'_>,
269 ) -> (Option<usize>, Option<usize>) {
270 if let Some(PropertyArg::Expr(ContractExpr::IndexAccess { slice, index })) =
271 property.args().first()
272 {
273 let slice_idx = Self::extract_place_arg_index(slice);
274 let index_idx = Self::extract_place_arg_index(index);
275 (slice_idx, index_idx)
276 } else {
277 (Some(0), Some(1))
278 }
279 }
280
281 pub(super) fn extract_place_arg_index(expr: &ContractExpr<'_>) -> Option<usize> {
282 match expr {
283 ContractExpr::Place(cp) => match cp.base {
284 PlaceBase::Arg(n) => Some(n),
285 _ => None,
286 },
287 _ => None,
288 }
289 }
290
291 pub(super) fn check_in_bound_slice<'z3, 'tcx>(
292 &self,
293 vm_state: &VmState<'z3, 'tcx>,
294 solver: &Solver<'z3>,
295 checkpoint: &Checkpoint<'tcx>,
296 property: &Property<'tcx>,
297 ) -> CheckResult {
298 let (slice_arg_idx, index_arg_idx) = Self::resolve_index_access_args(property);
299
300 let slice_val = match slice_arg_idx.and_then(|idx| checkpoint.args.get(idx)) {
301 Some(op) => vm_state.value_of_operand(op),
302 None => return CheckResult::Unknown(UnknownReason::Unimplemented),
303 };
304 let (index_val, is_range) = match index_arg_idx.and_then(|idx| checkpoint.args.get(idx)) {
305 Some(op) => {
306 if let Some(end_val) = self.extract_range_end(vm_state, op) {
307 (end_val, true)
308 } else {
309 (vm_state.value_of_operand(op), false)
310 }
311 }
312 None => return CheckResult::Unknown(UnknownReason::Unimplemented),
313 };
314
315 let data_alloc_id = slice_val.provenance_alloc_id();
316 let Some(data_alloc_id) = data_alloc_id else {
317 return CheckResult::Unknown(UnknownReason::Unimplemented);
318 };
319
320 let len = vm_state
323 .slice_len_from_value(&slice_val)
324 .unwrap_or_else(|| {
325 let size = vm_state.allocation_size(data_alloc_id).clone();
326 let elem_sz = vm_state
327 .alloc(data_alloc_id)
328 .element_ty
329 .as_ty()
330 .map(|ty| vm_state.size_sym_read(ty))
331 .unwrap_or_else(|| Int::from_u64(vm_state.z3_ctx, 1));
332 size.div(&elem_sz)
333 });
334
335 solver.push();
336 let negated = if is_range {
337 index_val.z3_term.le(&len).not()
339 } else {
340 index_val.z3_term.lt(&len).not()
343 };
344 solver.assert(&negated);
345 let r = match solver.check() {
346 SatResult::Unsat => CheckResult::ProvedBySmt,
347 SatResult::Sat => CheckResult::Failed,
348 _ => CheckResult::Unknown(UnknownReason::SmtTimeout),
349 };
350 solver.pop(1);
351 r
352 }
353
354 pub(super) fn extract_range_end<'z3, 'tcx>(
355 &self,
356 vm_state: &VmState<'z3, 'tcx>,
357 op: &Operand<'tcx>,
358 ) -> Option<VmValue<'z3, 'tcx>> {
359 let place = match op {
360 Operand::Copy(p) | Operand::Move(p) => p,
361 _ => return None,
362 };
363 if !place.projection.is_empty() {
364 return None;
365 }
366 let range_local = place.local;
367 let ty = vm_state.body().local_decls[range_local].ty;
368 let adt_def = match ty.kind() {
369 TyKind::Adt(adt_def, _) => *adt_def,
370 _ => return None,
371 };
372 let end_idx = match crate::helpers::mir_utils::range_kind(vm_state.tcx, adt_def.did()) {
377 crate::helpers::mir_utils::RangeKind::RangeTo => {
378 Some(rustc_abi::FieldIdx::from_usize(0))
379 }
380 crate::helpers::mir_utils::RangeKind::Range
381 | crate::helpers::mir_utils::RangeKind::RangeInclusive => {
382 Some(rustc_abi::FieldIdx::from_usize(1))
383 }
384 crate::helpers::mir_utils::RangeKind::Other => {
385 let name = vm_state.tcx.def_path_str(adt_def.did());
388 if name.ends_with("::IndexRange") || name == "IndexRange" {
389 Some(rustc_abi::FieldIdx::from_usize(1))
390 } else {
391 None
392 }
393 }
394 _ => None,
395 };
396 let Some(end_idx) = end_idx else { return None };
397 for block in vm_state.body().basic_blocks.iter() {
398 for stmt in &block.statements {
399 if let StatementKind::Assign(assign) = &stmt.kind {
400 let (dest, rvalue) = &**assign;
401 if dest.local == range_local && dest.projection.is_empty() {
402 if let Rvalue::Aggregate(_kind, operands) = rvalue {
403 if let Some(end_op) = operands.get(end_idx) {
404 return Some(self.trace_value(vm_state, end_op));
405 }
406 }
407 }
408 }
409 }
410 }
411 None
412 }
413
414 pub(super) fn check_non_overlap<'z3, 'tcx>(
415 &self,
416 vm_state: &VmState<'z3, 'tcx>,
417 solver: &Solver<'z3>,
418 checkpoint: &Checkpoint<'tcx>,
419 property: &Property<'tcx>,
420 ) -> CheckResult {
421 let Some(v1) = self.target_value(vm_state, checkpoint, property) else {
422 return CheckResult::Unknown(UnknownReason::Unimplemented);
423 };
424 let v2 = property
427 .args()
428 .get(1)
429 .and_then(|a| {
430 let cp = match a {
431 PropertyArg::Expr(ContractExpr::Place(cp)) => cp.clone(),
432 _ => return None,
433 };
434 match cp.base {
435 PlaceBase::Arg(n) => checkpoint
436 .args
437 .get(n)
438 .map(|op| vm_state.value_of_operand(op)),
439 PlaceBase::Local(n) => vm_state.local_value(Local::from_usize(n)).cloned(),
440 _ => None,
441 }
442 })
443 .or_else(|| {
444 checkpoint
445 .args
446 .get(1)
447 .map(|op| vm_state.value_of_operand(op))
448 });
449 let Some(v2) = v2 else {
450 return CheckResult::Unknown(UnknownReason::Unimplemented);
452 };
453 if let (Some(a), Some(b)) = (v1.provenance_alloc_id(), v2.provenance_alloc_id()) {
458 if a != b {
459 return CheckResult::ProvedByRule;
460 }
461 }
462
463 if let Some(count_term) = checkpoint
465 .args
466 .get(2)
467 .map(|op| vm_state.value_of_operand(op).z3_term)
468 {
469 let elem_size = vm_state
471 .pointee_elem_size(v1.ty)
472 .max(vm_state.pointee_elem_size(v2.ty))
473 .max(1);
474 if let Some(count) = count_term.simplify().as_u64() {
475 let range = Int::from_u64(vm_state.z3_ctx, elem_size * count.max(1));
476 let src_end = Int::add(vm_state.z3_ctx, &[&v1.z3_term, &range]);
477 let dst_end = Int::add(vm_state.z3_ctx, &[&v2.z3_term, &range]);
478 solver.push();
479 let overlap = Bool::and(
480 vm_state.z3_ctx,
481 &[&v1.z3_term.lt(&dst_end), &v2.z3_term.lt(&src_end)],
482 );
483 solver.assert(&overlap);
484 let r = match solver.check() {
485 SatResult::Unsat => CheckResult::ProvedBySmt,
486 SatResult::Sat => CheckResult::Failed,
487 _ => CheckResult::Unknown(UnknownReason::SmtTimeout),
488 };
489 solver.pop(1);
490 return r;
491 }
492 }
493
494 solver.push();
496 let ne = v1.z3_term._eq(&v2.z3_term).not();
497 solver.assert(&ne);
498 let r = match solver.check() {
499 SatResult::Unsat => CheckResult::ProvedBySmt,
500 SatResult::Sat => CheckResult::Failed,
501 _ => CheckResult::Unknown(UnknownReason::SmtTimeout),
502 };
503 solver.pop(1);
504 r
505 }
506
507 pub(super) fn all_predicates_are_slice_size_invariant<'z3, 'tcx>(
508 &self,
509 vm_state: &VmState<'z3, 'tcx>,
510 checkpoint: &Checkpoint<'tcx>,
511 predicates: &[crate::verify::contract::NumericPredicate<'tcx>],
512 ) -> bool {
513 !predicates.is_empty()
514 && predicates
515 .iter()
516 .all(|p| self.predicate_is_slice_size_invariant(vm_state, checkpoint, p))
517 }
518
519 pub(super) fn predicate_is_slice_size_invariant<'z3, 'tcx>(
520 &self,
521 vm_state: &VmState<'z3, 'tcx>,
522 checkpoint: &Checkpoint<'tcx>,
523 pred: &crate::verify::contract::NumericPredicate<'tcx>,
524 ) -> bool {
525 if !matches!(pred.op, RelOp::Le | RelOp::Lt) {
526 return false;
527 }
528 let ContractExpr::Const(bound) = &pred.rhs else {
530 return false;
531 };
532 if *bound < i64::MAX as u128 {
533 return false;
534 }
535 let ContractExpr::Binary {
537 op: NumericBinOp::Mul,
538 lhs,
539 rhs,
540 } = &pred.lhs
541 else {
542 return false;
543 };
544 let (size_ty, count_expr) = match (lhs.as_ref(), rhs.as_ref()) {
545 (ContractExpr::SizeOf(ty), count) => (*ty, count),
546 (count, ContractExpr::SizeOf(ty)) => (*ty, count),
547 _ => return false,
548 };
549 let resolved_ty = self.instantiate_callsite_ty(vm_state, checkpoint, size_ty);
551 self.count_derives_from_slice_param(vm_state, checkpoint, count_expr, resolved_ty)
553 }
554
555 pub(super) fn count_derives_from_slice_param<'z3, 'tcx>(
556 &self,
557 vm_state: &VmState<'z3, 'tcx>,
558 checkpoint: &Checkpoint<'tcx>,
559 count_expr: &ContractExpr<'tcx>,
560 elem_ty: Ty<'tcx>,
561 ) -> bool {
562 let ContractExpr::Place(cp) = count_expr else {
564 return false;
565 };
566 if !cp.projections.is_empty() {
567 return false;
568 }
569 let Some(local) = cp.local_base() else {
570 return false;
571 };
572 if local == 0 {
573 return false;
574 }
575 let Some(callee) = checkpoint.callee else {
576 return false;
577 };
578 let Some(arg_idx) =
579 crate::helpers::mir_utils::callee_param_index_for_local(vm_state.tcx, callee, local)
580 else {
581 return false;
582 };
583 if matches!(checkpoint.args.get(arg_idx), Some(Operand::Constant(_))) {
585 return false;
586 }
587 let body = vm_state.body();
589 let has_slice_param = (1..=body.arg_count).any(|i| {
590 let param_ty = body.local_decls[Local::from_usize(i)].ty;
591 self.is_slice_ref_with_elem(param_ty, elem_ty, vm_state, checkpoint)
592 });
593 if has_slice_param {
594 return true;
595 }
596 if let Some(op) = checkpoint.args.first() {
599 let target_val = vm_state.value_of_operand(op);
600 if target_val.is_pointer() {
601 return true;
602 }
603 }
604 false
605 }
606
607 pub(super) fn is_slice_ref_with_elem<'z3, 'tcx>(
608 &self,
609 ty: Ty<'tcx>,
610 elem_ty: Ty<'tcx>,
611 vm_state: &VmState<'z3, 'tcx>,
612 checkpoint: &Checkpoint<'tcx>,
613 ) -> bool {
614 let rustc_middle::ty::TyKind::Ref(_, inner, _) = ty.kind() else {
615 return false;
616 };
617 match inner.kind() {
618 rustc_middle::ty::TyKind::Slice(slice_elem) => {
619 let resolved = self.instantiate_callsite_ty(vm_state, checkpoint, *slice_elem);
620 self.same_erased_ty(vm_state, resolved, elem_ty)
621 }
622 _ => false,
623 }
624 }
625
626 pub(super) fn same_erased_ty<'z3, 'tcx>(
627 &self,
628 vm_state: &VmState<'z3, 'tcx>,
629 a: Ty<'tcx>,
630 b: Ty<'tcx>,
631 ) -> bool {
632 vm_state.size_of_ty(a) > 0
633 && vm_state.size_of_ty(b) > 0
634 && vm_state.size_of_ty(a) == vm_state.size_of_ty(b)
635 }
636
637 pub(super) fn is_caller_type_param<'z3, 'tcx>(
638 &self,
639 vm_state: &VmState<'z3, 'tcx>,
640 ty: Ty<'tcx>,
641 ) -> bool {
642 let rustc_middle::ty::TyKind::Param(param_ty) = ty.kind() else {
643 return false;
644 };
645 let generics = vm_state.tcx.generics_of(vm_state.current_frame.current_def_id);
646 generics.own_params.iter().any(|p| {
647 matches!(p.kind, rustc_middle::ty::GenericParamDefKind::Type { .. })
648 && p.name == param_ty.name
649 })
650 }
651}