1use rustc_middle::mir::Operand;
2use rustc_middle::ty::TyKind;
3#[cfg(not(rapx_has_skip_norm_wip))]
4use crate::compat::SkipNormWip;
5use rustc_hash::FxHashSet;
6use z3::{SatResult, Solver, ast::{Ast, Bool, Int}};
7use crate::verify::contract::{ContractExpr, NumericOp, PlaceBase, Property, PropertyArg, RelOp};
8use crate::verify::def_use::PlaceKey;
9use crate::verify::report::CheckResult;
10use crate::helpers::mir_scan::Checkpoint;
11use crate::verify::vm::state::VmState;
12
13use super::PropertyChecker;
14
15impl PropertyChecker {
16 pub(super) fn check_valid_num<'ctx, 'tcx>(&self, vm_state: &VmState<'ctx, 'tcx>, solver: &Solver<'ctx>,
17 checkpoint: &Checkpoint<'tcx>, property: &Property<'tcx>) -> CheckResult
18 {
19 if let Some(PropertyArg::Predicates(predicates)) = property.args().first() {
20 if self.all_predicates_are_slice_size_invariant(vm_state, checkpoint, predicates) {
21 return CheckResult::Proved;
22 }
23 for pred in predicates {
24 if let Some(r) = self.eval_numeric_predicate(vm_state, solver, Some(checkpoint), pred) {
25 if !matches!(r, CheckResult::Proved) {
26 return r;
27 }
28 }
29 }
30 return CheckResult::Proved;
31 }
32 let Some(value) = self.target_value(vm_state, checkpoint, property) else { return CheckResult::Unknown };
33 let ty = property.args().get(1).and_then(|a| if let PropertyArg::Ty(ty) = a { Some(*ty) } else { None });
34 if let Some(ty) = ty {
35 let size_bits = vm_state.size_of_ty(ty) * 8;
36 if size_bits > 0 && size_bits < 128 {
37 if let TyKind::Int(_) = ty.kind() {
38 let half = 1u128 << (size_bits - 1);
39 let min = -(half as i128);
40 let max = (half - 1) as i128;
41 solver.push();
42 let below = value.term.lt(&Int::from_i64(vm_state.ctx, min as i64));
43 let above = value.term.gt(&Int::from_i64(vm_state.ctx, max as i64));
44 solver.assert(&Bool::or(vm_state.ctx, &[&below, &above]));
45 let r = match solver.check() {
46 SatResult::Unsat => CheckResult::Proved,
47 SatResult::Sat => CheckResult::Failed,
48 _ => CheckResult::Unknown,
49 };
50 solver.pop(1);
51 return r;
52 }
53 let max = Int::from_u64(vm_state.ctx, ((1u128 << size_bits) - 1).min(u64::MAX as u128) as u64);
54 solver.push();
55 solver.assert(&value.term.gt(&max));
56 let r = match solver.check() { SatResult::Unsat => CheckResult::Proved, SatResult::Sat => CheckResult::Failed, _ => CheckResult::Unknown };
57 solver.pop(1);
58 return r;
59 }
60 return CheckResult::Proved;
61 }
62 CheckResult::Proved
63 }
64
65 pub(super) fn eval_numeric_predicate<'ctx, 'tcx>(&self, vm_state: &VmState<'ctx, 'tcx>, solver: &Solver<'ctx>,
66 checkpoint: Option<&Checkpoint<'tcx>>,
67 pred: &crate::verify::contract::NumericPredicate<'tcx>) -> Option<CheckResult>
68 {
69 let lhs = self.eval_contract_expr(vm_state, checkpoint, &pred.lhs)?;
70 let rhs = self.eval_contract_expr(vm_state, checkpoint, &pred.rhs)?;
71 let condition = match pred.op {
72 RelOp::Le => lhs.le(&rhs),
73 RelOp::Lt => lhs.lt(&rhs),
74 RelOp::Ge => lhs.ge(&rhs),
75 RelOp::Gt => lhs.gt(&rhs),
76 RelOp::Eq => lhs._eq(&rhs),
77 RelOp::Ne => lhs._eq(&rhs).not(),
78 };
79 solver.push();
80 vm_state.assert_all(solver);
81 for (_, off) in vm_state.iter_ptr_offset.iter() {
86 let one = Int::from_u64(vm_state.ctx, 1);
87 solver.assert(&off._eq(&Int::add(vm_state.ctx, &[&lhs, &one])));
88 }
89 if matches!(pred.op, RelOp::Le) {
94 if let Some(term) = self.try_get_iter_len_term(vm_state, &pred.rhs) {
95 if let Some(one) = lhs.as_u64().or(rhs.as_u64()) {
96 if one == 1 {
97 let one_term = Int::from_u64(vm_state.ctx, 1);
98 solver.assert(&term.ge(&one_term));
99 }
100 }
101 }
102 }
103 self.inject_nia_axioms(vm_state, solver, checkpoint, &pred.lhs);
106 self.inject_nia_axioms(vm_state, solver, checkpoint, &pred.rhs);
107 self.inject_vm_div_axioms(vm_state, solver, &pred.lhs);
110 self.inject_vm_div_axioms(vm_state, solver, &pred.rhs);
111 if matches!(pred.op, RelOp::Ne) && rhs.as_u64() == Some(0) {
115 if lhs.as_u64() == Some(0) {
118 return Some(CheckResult::Proved);
119 }
120 vm_state.assert_all(solver);
121 }
122 solver.assert(&condition.not());
123 let r0 = solver.check();
124 let mut r = match r0 { SatResult::Unsat => Some(CheckResult::Proved), SatResult::Sat => Some(CheckResult::Failed), _ => None };
125 if matches!(r, Some(CheckResult::Failed)) && matches!(pred.op, RelOp::Le | RelOp::Ge | RelOp::Lt | RelOp::Gt) {
128 solver.pop(1);
129 solver.push();
130 vm_state.assert_all(solver);
131 self.inject_nia_axioms(vm_state, solver, checkpoint, &pred.lhs);
132 self.inject_nia_axioms(vm_state, solver, checkpoint, &pred.rhs);
133 self.inject_vm_div_axioms(vm_state, solver, &pred.lhs);
134 self.inject_vm_div_axioms(vm_state, solver, &pred.rhs);
135 solver.assert(&condition.not());
136 r = match solver.check() { SatResult::Unsat => Some(CheckResult::Proved), SatResult::Sat => Some(CheckResult::Failed), _ => r };
137 }
138 solver.pop(1);
139 r
140 }
141
142 pub(super) fn inject_nia_axioms<'ctx, 'tcx>(&self, vm_state: &VmState<'ctx, 'tcx>,
143 solver: &Solver<'ctx>, checkpoint: Option<&Checkpoint<'tcx>>,
144 expr: &ContractExpr<'tcx>)
145 {
146 match expr {
147 ContractExpr::Binary { op: NumericOp::Div, lhs, rhs } => {
148 if let (Some(l), Some(r)) = (
149 self.eval_contract_expr(vm_state, checkpoint, lhs),
150 self.eval_contract_expr(vm_state, checkpoint, rhs),
151 ) {
152 let zero = Int::from_u64(vm_state.ctx, 0);
153 let mul_term = Int::mul(vm_state.ctx, &[&l.div(&r), &r]);
154 let rem_term = l.rem(&r);
155 let sum_term = Int::add(vm_state.ctx, &[&mul_term, &rem_term]);
156 solver.assert(&l._eq(&sum_term));
157 solver.assert(&rem_term.ge(&zero));
158 }
159 }
160 ContractExpr::Binary { op: NumericOp::Mul, lhs, rhs } => {
161 self.inject_nia_axioms(vm_state, solver, checkpoint, lhs);
163 self.inject_nia_axioms(vm_state, solver, checkpoint, rhs);
164 }
165 ContractExpr::Binary { lhs, rhs, .. } => {
166 self.inject_nia_axioms(vm_state, solver, checkpoint, lhs);
167 self.inject_nia_axioms(vm_state, solver, checkpoint, rhs);
168 }
169 ContractExpr::Unary { expr: inner, .. } => {
170 self.inject_nia_axioms(vm_state, solver, checkpoint, inner);
171 }
172 _ => {}
173 }
174 }
175
176 pub(super) fn inject_vm_div_axioms<'ctx, 'tcx>(&self,
177 vm_state: &VmState<'ctx, 'tcx>,
178 solver: &Solver<'ctx>,
179 expr: &ContractExpr<'tcx>,
180 ) {
181 let Some(val) = self.eval_contract_expr(vm_state, None, expr) else { return };
182 self.inject_div_axioms_for_term(vm_state, solver, &val, 4);
183 }
184
185 pub(super) fn inject_div_axioms_for_term<'ctx, 'tcx>(&self,
186 vm_state: &VmState<'ctx, 'tcx>,
187 solver: &Solver<'ctx>,
188 target: &Int<'ctx>,
189 depth: usize,
190 ) {
191 if depth == 0 { return; }
192
193 let op_sources: Vec<&(Option<PlaceKey>, Option<PlaceKey>)> = {
197 let mut src: Vec<&(Option<PlaceKey>, Option<PlaceKey>)> = Vec::new();
198 for (pk, pair) in vm_state.binary_op_sources.iter() {
199 if pk.local().and_then(|l| vm_state.local_value(l))
200 .map(|v| v.term == *target).unwrap_or(false)
201 {
202 src.push(pair);
203 }
204 }
205 for (pk, pair) in vm_state.other_op_sources.iter() {
206 if pk.local().and_then(|l| vm_state.local_value(l))
207 .map(|v| v.term == *target).unwrap_or(false)
208 {
209 src.push(pair);
210 }
211 }
212 src
213 };
214
215 let mut already_seen = FxHashSet::default();
216
217 for local_idx in 0..vm_state.body.local_decls.len() {
221 let local = rustc_middle::mir::Local::from_usize(local_idx);
222 let Some(val) = vm_state.local_value(local) else { continue };
223 if val.term != *target { continue }
224
225 for (pk, (lhs, rhs)) in vm_state.binary_op_sources.iter()
226 .chain(vm_state.other_op_sources.iter())
227 {
228 if let Some(dest_local) = pk.local() {
229 if let Some(dest_val) = vm_state.local_value(dest_local) {
230 let lhs_local = lhs.as_ref().and_then(|pk| pk.local());
231 let rhs_local = rhs.as_ref().and_then(|pk| pk.local());
232 if (lhs_local == Some(local) || rhs_local == Some(local))
233 && !already_seen.contains(&dest_val.term)
234 {
235 already_seen.insert(dest_val.term.clone());
236 self.inject_div_axioms_for_term(
237 vm_state, solver, &dest_val.term, depth - 1,
238 );
239 }
240 }
241 }
242 }
243 }
244
245 for (lhs_pk, rhs_pk) in &op_sources {
247 let (Some(lhs_pk), Some(rhs_pk)) = (lhs_pk, rhs_pk) else { continue };
248 let (Some(lhs_local), Some(rhs_local)) = (lhs_pk.local(), rhs_pk.local()) else { continue };
249 let (Some(lhs_val), Some(rhs_val)) = (vm_state.local_value(lhs_local), vm_state.local_value(rhs_local)) else { continue };
250
251 if let Some((div_lhs_pk, div_rhs_pk)) = vm_state.binary_op_sources.get(lhs_pk).cloned() {
253 let Some(div_lhs_local) = div_lhs_pk.and_then(|pk| pk.local()) else { continue };
254 let Some(div_rhs_local) = div_rhs_pk.and_then(|pk| pk.local()) else { continue };
255 let Some(div_lhs_val) = vm_state.local_value(div_lhs_local) else { continue };
256 let Some(div_rhs_val) = vm_state.local_value(div_rhs_local) else { continue };
257
258 let quot = div_lhs_val.term.div(&div_rhs_val.term);
259 let rem = div_lhs_val.term.rem(&div_rhs_val.term);
260 let mul_term = Int::mul(vm_state.ctx, &[", &div_rhs_val.term]);
261 let sum_term = Int::add(vm_state.ctx, &[&mul_term, &rem]);
262 solver.assert(&div_lhs_val.term._eq(&sum_term));
263 let zero = Int::from_u64(vm_state.ctx, 0);
264 solver.assert(&rem.ge(&zero));
265 solver.assert(&mul_term.le(&div_lhs_val.term));
266 }
267
268 self.inject_div_axioms_for_term(vm_state, solver, &lhs_val.term, depth - 1);
270 self.inject_div_axioms_for_term(vm_state, solver, &rhs_val.term, depth - 1);
271 }
272 }
273
274 pub(super) fn try_get_iter_len_term<'ctx, 'tcx>(
275 &self,
276 vm_state: &VmState<'ctx, 'tcx>,
277 expr: &ContractExpr<'tcx>,
278 ) -> Option<Int<'ctx>> {
279 let ContractExpr::Len(_) = expr else { return None };
280 for (_, val) in vm_state.locals.iter() {
281 let is_iter = match val.ty.kind() {
282 TyKind::Ref(_, pointee, _) => match pointee.kind() {
283 TyKind::Adt(adt_def, _) => {
284 let name = vm_state.tcx.def_path_str(adt_def.did());
285 name.ends_with("::Iter") || name == "Iter"
286 || name.ends_with("::IterMut") || name == "IterMut"
287 }
288 _ => false,
289 },
290 _ => false,
291 };
292 if !is_iter { continue; }
293 let alloc_id = val.provenance_alloc_id()?;
294 for (&l, lv) in vm_state.locals.iter() {
295 if lv.provenance_alloc_id() != Some(alloc_id) { continue; }
296 if let (Some(ptr), Some(end)) =
297 (vm_state.field_value(l, &[0]), vm_state.field_value(l, &[1]))
298 {
299 if let (Some(pp), Some(ep)) = (&ptr.provenance, &end.provenance) {
300 if pp.alloc_id == ep.alloc_id {
301 let elem_ty = match ptr.ty.kind() {
302 TyKind::Adt(_, substs) => substs.first().and_then(|s| s.as_type()),
303 _ => None,
304 };
305 let elem_size = elem_ty.map(|t| vm_state.size_of_ty(t).max(1)).unwrap_or(1) as u64;
306 let diff = Int::sub(vm_state.ctx, &[&ep.offset, &pp.offset]);
307 let sz = Int::from_u64(vm_state.ctx, elem_size);
308 return Some(diff.div(&sz));
309 }
310 }
311 }
312 }
313 }
314 None
315 }
316
317 pub(super) fn try_iter_len_from_fields<'ctx, 'tcx>(
318 &self,
319 vm_state: &VmState<'ctx, 'tcx>,
320 checkpoint: &Checkpoint<'tcx>,
321 expr: &ContractExpr<'tcx>,
322 ) -> Option<Int<'ctx>> {
323 use rustc_middle::mir::Place;
324 let ContractExpr::Place(cp) = expr else { return None };
325 let op: &Operand<'tcx> = match cp.base {
326 PlaceBase::Arg(n) => checkpoint.args.get(n)?,
327 PlaceBase::Local(n) => {
328 let callee = checkpoint.callee?;
329 let idx = crate::helpers::mir_utils::callee_param_index_for_local(
330 vm_state.tcx, callee, n)?;
331 checkpoint.args.get(idx)?
332 }
333 _ => return None,
334 };
335 let place: &Place<'tcx> = match op {
336 Operand::Copy(p) | Operand::Move(p) => p,
337 _ => return None,
338 };
339 let local = place.local;
340 let local_val = vm_state.locals.get(&local)?;
341 let is_iter = match local_val.ty.kind() {
342 TyKind::Ref(_, pointee, _) => match pointee.kind() {
343 TyKind::Adt(adt_def, _) => {
344 let name = vm_state.tcx.def_path_str(adt_def.did());
345 name.ends_with("::Iter") || name == "Iter"
346 || name.ends_with("::IterMut") || name == "IterMut"
347 }
348 _ => false,
349 },
350 _ => false,
351 };
352 if !is_iter { return None; }
353 if let (Some(ptr), Some(end)) =
355 (vm_state.field_value(local, &[0]), vm_state.field_value(local, &[1]))
356 {
357 if let (Some(pp), Some(ep)) = (&ptr.provenance, &end.provenance) {
358 if pp.alloc_id == ep.alloc_id {
359 let diff = Int::sub(vm_state.ctx, &[&ep.offset, &pp.offset]);
360 let sz = Int::from_u64(vm_state.ctx, vm_state.iter_elem_size(ptr));
361 return Some(diff.div(&sz));
362 }
363 }
364 }
365 let target_alloc = local_val.provenance_alloc_id()?;
367 for (&scan_local, scan_val) in vm_state.locals.iter() {
368 if scan_val.provenance_alloc_id() != Some(target_alloc) { continue; }
369 if let (Some(ptr), Some(end)) =
370 (vm_state.field_value(scan_local, &[0]), vm_state.field_value(scan_local, &[1]))
371 {
372 if let (Some(pp), Some(ep)) = (&ptr.provenance, &end.provenance) {
373 if pp.alloc_id == ep.alloc_id {
374 let diff = Int::sub(vm_state.ctx, &[&ep.offset, &pp.offset]);
375 let sz = Int::from_u64(vm_state.ctx, vm_state.iter_elem_size(ptr));
376 return Some(diff.div(&sz));
377 }
378 }
379 }
380 }
381 None
382 }
383}