1use super::*;
4
5mod polynomial;
6mod rounding;
7
8use polynomial::polynomial_identity;
9
10#[cfg(test)]
12pub(crate) fn normalize_constraints_for_solver(
13 cx: &mut SymCx,
14 constraints: &[SymBoolExpr],
15) -> Vec<SymBoolExpr> {
16 normalize_constraints_for_solver_with(cx, constraints, |cx, constraint| {
17 normalize_bool_for_solver(cx, constraint.clone())
18 })
19}
20
21pub(super) fn normalize_constraints_for_solver_cached(
23 cx: &mut SymCx,
24 constraints: &[SymBoolExpr],
25 normalization_cache: &mut HashMap<SymBoolExpr, SymBoolExpr>,
26) -> Vec<SymBoolExpr> {
27 normalize_constraints_for_solver_with(cx, constraints, |cx, constraint| {
28 if let Some(normalized) = normalization_cache.get(constraint) {
29 return normalized.clone();
30 }
31 let normalized = normalize_bool_for_solver(cx, constraint.clone());
32 if normalization_cache.len() < SYMBOLIC_SOLVER_SAT_CACHE_MAX_ENTRIES {
34 normalization_cache.insert(constraint.clone(), normalized.clone());
35 }
36 normalized
37 })
38}
39
40fn normalize_constraints_for_solver_with(
41 cx: &mut SymCx,
42 constraints: &[SymBoolExpr],
43 mut normalize: impl FnMut(&mut SymCx, &SymBoolExpr) -> SymBoolExpr,
44) -> Vec<SymBoolExpr> {
45 let mut changed_conjuncts = HashSet::default();
46 let normalized = normalize_constraint_batch(
47 constraints.iter().map(|constraint| {
48 let normalized = normalize(cx, constraint);
49 if normalized != *constraint {
50 mark_conjuncts(&normalized, &mut changed_conjuncts);
51 }
52 normalized
53 }),
54 constraints.len(),
55 );
56 if matches!(normalized.as_slice(), [expr] if expr.as_const() == Some(false)) {
57 return normalized;
58 }
59
60 let retained_count = normalized
64 .iter()
65 .filter(|constraint| !ConstraintContext::requires_independent_context(constraint))
66 .count();
67 let retained = normalized
68 .iter()
69 .filter(|constraint| !ConstraintContext::requires_independent_context(constraint));
70 let context =
71 ConstraintContext::from_constraints_with_lower_bounds(retained, retained_count, false);
72 let normalized_len = normalized.len();
73 let normalized = normalize_constraint_batch(
74 normalized.into_iter().map(|constraint| {
75 let changed = changed_conjuncts.contains(&constraint);
76 context.normalize_bool(cx, constraint, changed)
77 }),
78 normalized_len,
79 );
80 normalize_bounded_comparisons(cx, normalized)
81}
82
83fn normalize_bounded_comparisons(
85 cx: &mut SymCx,
86 mut constraints: Vec<SymBoolExpr>,
87) -> Vec<SymBoolExpr> {
88 for _ in 0..MAX_CONTEXTUAL_PASSES {
91 let previous = constraints.clone();
92 let mut index = 0;
93 while index < constraints.len() {
94 let context = ConstraintContext::for_rewrite(cx, &constraints, index);
95 constraints[index] = context.normalize_bool(cx, constraints[index].clone(), false);
96 if let SymBoolExprKind::And(terms) = constraints[index].kind() {
99 let terms = terms.to_vec();
100 constraints.splice(index..=index, terms);
101 continue;
102 }
103 match context.bounded_bool_value(&constraints[index]) {
104 Some(false) => return vec![SymBoolExpr::constant(cx, false)],
105 Some(true) => {
106 constraints.remove(index);
107 }
108 None => index += 1,
109 }
110 }
111 let count = constraints.len();
113 constraints = normalize_constraint_batch(constraints, count);
114 if constraints == previous || constraints.iter().any(|c| c.as_const() == Some(false)) {
115 break;
116 }
117 }
118 constraints
119}
120
121fn mark_conjuncts(expr: &SymBoolExpr, out: &mut HashSet<SymBoolExpr>) {
122 let mut pending = vec![expr.clone()];
123 while let Some(expr) = pending.pop() {
124 if !out.insert(expr.clone()) {
125 continue;
126 }
127 if let SymBoolExprKind::And(values) = expr.kind() {
128 pending.extend(values.iter().cloned());
129 }
130 }
131}
132
133fn normalize_constraint_batch(
134 constraints: impl IntoIterator<Item = SymBoolExpr>,
135 capacity: usize,
136) -> Vec<SymBoolExpr> {
137 let mut normalized = Vec::with_capacity(capacity);
138 for constraint in constraints {
139 if constraint.as_const() == Some(false) {
140 return vec![constraint];
141 }
142 constraint.push_normalized_conjuncts(&mut normalized);
143 }
144 sort_dedup_bool_exprs(&mut normalized);
145 normalized
146}
147
148fn sort_dedup_bool_exprs(exprs: &mut Vec<SymBoolExpr>) {
149 exprs.sort_unstable_by(bool_expr_cmp);
152 exprs.dedup();
153}
154
155fn bool_expr_cmp(left: &SymBoolExpr, right: &SymBoolExpr) -> std::cmp::Ordering {
156 if left == right {
157 return std::cmp::Ordering::Equal;
158 }
159 left.stable_hash_cmp(right)
160 .then_with(|| bool_structural_key(left).cmp(&bool_structural_key(right)))
161}
162
163fn bool_structural_key(expr: &SymBoolExpr) -> String {
164 let mut key = String::new();
165 write_bool_structural_key(&mut key, expr);
166 key
167}
168
169fn write_bool_structural_key(out: &mut String, expr: &SymBoolExpr) {
170 match expr.kind() {
171 SymBoolExprKind::Const(value) => {
172 let _ = write!(out, "0:{value}");
173 }
174 SymBoolExprKind::Not(value) => {
175 out.push_str("1:");
176 write_bool_structural_key(out, value);
177 }
178 SymBoolExprKind::And(values) => {
179 let _ = write!(out, "2:{}:", values.len());
180 for value in values.iter() {
181 write_bool_structural_key(out, value);
182 out.push(';');
183 }
184 }
185 SymBoolExprKind::Cmp(op, left, right) => {
186 let _ = write!(out, "3:{}:", cmp_op_key(*op));
187 write_expr_structural_key(out, left);
188 out.push(':');
189 write_expr_structural_key(out, right);
190 }
191 }
192}
193
194fn write_expr_structural_key(out: &mut String, expr: &SymExpr) {
195 match expr.kind() {
196 SymExprKind::Const(value) => {
197 let _ = write!(out, "0:{value:064x}");
198 }
199 SymExprKind::Var(name) => {
200 let _ = write!(out, "1:{}", name.id());
201 }
202 SymExprKind::GasLeft(symbol) => {
203 let _ = write!(out, "2:{}", symbol.id());
204 }
205 SymExprKind::Keccak { name, len, bytes } => {
206 let _ = write!(out, "3:{}:", name.id());
207 write_expr_structural_key(out, len);
208 write_exprs_structural_key(out, bytes);
209 }
210 SymExprKind::Hash { name, algorithm, bytes } => {
211 let _ = write!(out, "4:{}:{algorithm}:", name.id());
212 write_exprs_structural_key(out, bytes);
213 }
214 SymExprKind::Not(value) => {
215 out.push_str("5:");
216 write_expr_structural_key(out, value);
217 }
218 SymExprKind::BinOp(op, left, right) => {
219 let _ = write!(out, "6:{}:", expr_binop_key(*op));
220 write_expr_structural_key(out, left);
221 out.push(':');
222 write_expr_structural_key(out, right);
223 }
224 SymExprKind::TernOp(op, left, right, modulus) => {
225 let _ = write!(out, "7:{}:", expr_ternop_key(*op));
226 write_expr_structural_key(out, left);
227 out.push(':');
228 write_expr_structural_key(out, right);
229 out.push(':');
230 write_expr_structural_key(out, modulus);
231 }
232 SymExprKind::Ite(condition, then_expr, else_expr) => {
233 out.push_str("9:");
234 write_bool_structural_key(out, condition);
235 out.push(':');
236 write_expr_structural_key(out, then_expr);
237 out.push(':');
238 write_expr_structural_key(out, else_expr);
239 }
240 }
241}
242
243fn write_exprs_structural_key(out: &mut String, exprs: &[SymExpr]) {
244 let _ = write!(out, "{}:", exprs.len());
245 for expr in exprs {
246 write_expr_structural_key(out, expr);
247 out.push(';');
248 }
249}
250
251const fn cmp_op_key(op: SymCmpOp) -> u8 {
252 match op {
253 SymCmpOp::Eq => 0,
254 SymCmpOp::Ult => 1,
255 SymCmpOp::Ugt => 2,
256 SymCmpOp::Ule => 3,
257 SymCmpOp::Uge => 4,
258 SymCmpOp::Slt => 5,
259 SymCmpOp::Sgt => 6,
260 }
261}
262
263const fn expr_binop_key(op: SymBinOp) -> u8 {
264 match op {
265 SymBinOp::Add => 0,
266 SymBinOp::Sub => 1,
267 SymBinOp::Mul => 2,
268 SymBinOp::UDiv => 3,
269 SymBinOp::URem => 4,
270 SymBinOp::SDiv => 5,
271 SymBinOp::SRem => 6,
272 SymBinOp::And => 7,
273 SymBinOp::Or => 8,
274 SymBinOp::Xor => 9,
275 SymBinOp::Shl => 10,
276 SymBinOp::Shr => 11,
277 SymBinOp::Sar => 12,
278 }
279}
280
281const fn expr_ternop_key(op: SymTernOp) -> u8 {
282 match op {
283 SymTernOp::AddMod => 0,
284 SymTernOp::MulMod => 1,
285 }
286}
287
288pub(super) fn constraints_are_directly_unsat(cx: &mut SymCx, constraints: &[SymBoolExpr]) -> bool {
290 let mut derived = Vec::new();
291 for constraint in constraints {
292 let Some(fact) = bitwise_bool_word_fact(cx, constraint) else {
293 continue;
294 };
295 if let SymBoolExprKind::And(values) = fact.kind() {
296 derived.extend(values.iter().cloned());
299 }
300 derived.push(fact);
301 }
302 let contains = |expected: &SymBoolExpr| {
303 constraints.binary_search_by(|candidate| bool_expr_cmp(candidate, expected)).is_ok()
304 || derived.contains(expected)
305 };
306 constraints.iter().chain(&derived).any(|constraint| match constraint.kind() {
307 SymBoolExprKind::Const(false) => true,
308 SymBoolExprKind::Not(inner)
309 if let SymBoolExprKind::And(values) = inner.kind()
310 && values.iter().all(&contains) =>
311 {
312 true
313 }
314 SymBoolExprKind::Not(inner) => contains(inner),
315 _ => {
316 let negated = constraint.clone().not(cx);
317 contains(&negated)
318 }
319 })
320}
321
322fn bitwise_bool_word_fact(cx: &mut SymCx, constraint: &SymBoolExpr) -> Option<SymBoolExpr> {
323 match constraint.kind() {
324 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
325 if right.as_const().is_some_and(|value| value.is_zero()) =>
326 {
327 left.bitwise_bool_word_condition(cx).map(|condition| condition.not(cx))
328 }
329 SymBoolExprKind::Not(inner) => {
330 let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = inner.kind() else {
331 return None;
332 };
333 if !right.as_const().is_some_and(|value| value.is_zero()) {
334 return None;
335 }
336 left.bitwise_bool_word_condition(cx)
337 }
338 _ => None,
339 }
340}
341
342pub(super) fn sorted_bool_exprs_are_subset(
344 subset: &[SymBoolExpr],
345 superset: &[SymBoolExpr],
346) -> bool {
347 if subset.len() > superset.len() {
348 return false;
349 }
350
351 let superset: HashSet<_> = superset.iter().collect();
352 subset.iter().all(|expected| superset.contains(expected))
353}
354
355pub(crate) fn normalize_bool_for_solver(cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
357 expr.fold(cx, &mut normalize_bool_node_for_solver)
358}
359
360impl SymBoolExpr {
361 fn push_normalized_conjuncts(self, out: &mut Vec<Self>) {
362 match self.kind() {
363 SymBoolExprKind::Const(true) => {}
364 SymBoolExprKind::And(values) => {
365 for value in values.iter().cloned() {
366 value.push_normalized_conjuncts(out);
367 }
368 }
369 _ => out.push(self),
370 }
371 }
372}
373
374fn normalize_bool_node_for_solver(cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
375 if let Some(normalized) = expr.normalize_udiv_for_solver(cx) {
376 return normalized;
377 }
378
379 match expr.kind() {
380 SymBoolExprKind::Not(value) => match value.kind() {
381 SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right)
382 if matches!(left.kind(), SymExprKind::Not(_)) =>
383 {
384 normalize_cmp_for_solver(cx, SymCmpOp::Ule, right.clone(), left.clone())
385 }
386 _ => expr,
387 },
388 SymBoolExprKind::Cmp(op, left, right) => {
389 let left = normalize_expr_for_solver(cx, left.clone());
390 let right = normalize_expr_for_solver(cx, right.clone());
391 if *op == SymCmpOp::Eq && polynomial_identity(&left, &right) {
392 return SymBoolExpr::constant(cx, true);
393 }
394 let normalized = normalize_cmp_for_solver(cx, *op, left, right);
395 normalized.normalize_udiv_for_solver(cx).unwrap_or(normalized)
396 }
397 _ => expr,
398 }
399}
400
401fn normalize_cmp_for_solver(
402 cx: &mut SymCx,
403 op: SymCmpOp,
404 left: SymExpr,
405 right: SymExpr,
406) -> SymBoolExpr {
407 if op == SymCmpOp::Eq {
408 if right.as_const().is_some_and(|value| value.is_zero())
409 && let SymExprKind::BinOp(SymBinOp::Sub, minuend, subtrahend) = left.kind()
410 {
411 return SymBoolExpr::eq(cx, minuend.clone(), subtrahend.clone());
414 }
415 if left.as_const().is_some_and(|value| value.is_zero())
416 && let SymExprKind::BinOp(SymBinOp::Sub, minuend, subtrahend) = right.kind()
417 {
418 return SymBoolExpr::eq(cx, minuend.clone(), subtrahend.clone());
419 }
420 }
421
422 let (left, right) =
423 if matches!(op, SymCmpOp::Ult | SymCmpOp::Ule | SymCmpOp::Ugt | SymCmpOp::Uge) {
424 match (left.kind(), right.kind()) {
427 (SymExprKind::Not(value), SymExprKind::Const(limit)) => {
428 (SymExpr::constant(cx, !*limit), value.clone())
429 }
430 (SymExprKind::Const(limit), SymExprKind::Not(value)) => {
431 (value.clone(), SymExpr::constant(cx, !*limit))
432 }
433 _ => (left, right),
434 }
435 } else {
436 (left, right)
437 };
438
439 match op {
440 SymCmpOp::Ugt => SymBoolExpr::cmp(cx, SymCmpOp::Ult, right, left),
442 SymCmpOp::Uge => SymBoolExpr::cmp(cx, SymCmpOp::Ule, right, left),
444 SymCmpOp::Sgt => SymBoolExpr::cmp(cx, SymCmpOp::Slt, right, left),
446 SymCmpOp::Eq | SymCmpOp::Ult | SymCmpOp::Ule | SymCmpOp::Slt => {
447 SymBoolExpr::cmp(cx, op, left, right)
448 }
449 }
450}
451
452#[derive(Default)]
454pub(super) struct ConstraintContext {
455 upper_bounds: HashMap<SymExpr, U256>,
456 lower_bounds: HashMap<SymExpr, U256>,
457 unsigned_lower_bounds: HashMap<SymExpr, U256>,
458 exact_values: HashMap<SymExpr, U256>,
459 conflicting_exact_values: HashSet<SymExpr>,
460 non_wrapping_products: HashSet<(SymExpr, SymExpr)>,
461}
462
463#[derive(Clone, Copy)]
464struct WordInterval {
465 min: U256,
466 max: U256,
467}
468
469const MAX_LOCAL_ANALYSIS_NODES: usize = 256;
473const MAX_CONTEXTUAL_PASSES: usize = 4;
474
475impl WordInterval {
476 fn new(min: U256, max: U256) -> Option<Self> {
477 (min <= max).then_some(Self { min, max })
478 }
479
480 const fn exact(value: U256) -> Self {
481 Self { min: value, max: value }
482 }
483
484 fn with_bounds(self, lower: Option<U256>, upper: Option<U256>) -> Option<Self> {
485 Self::new(
486 self.min.max(lower.unwrap_or(U256::ZERO)),
487 self.max.min(upper.unwrap_or(U256::MAX)),
488 )
489 }
490}
491
492impl ConstraintContext {
493 pub(super) fn new(constraints: &[SymBoolExpr]) -> Self {
494 Self::from_constraints(constraints.iter(), constraints.len())
495 }
496
497 fn from_constraints<'a>(
498 constraints: impl Clone + Iterator<Item = &'a SymBoolExpr>,
499 constraint_count: usize,
500 ) -> Self {
501 Self::from_constraints_with_lower_bounds(constraints, constraint_count, true)
502 }
503
504 fn for_rewrite(cx: &mut SymCx, constraints: &[SymBoolExpr], index: usize) -> Self {
506 let supporting = constraints
507 .iter()
508 .enumerate()
509 .filter_map(|(i, constraint)| (i != index).then_some(constraint));
510 let mut context = Self::from_constraints(supporting.clone(), constraints.len() - 1);
511 for _ in 0..MAX_CONTEXTUAL_PASSES {
513 let mut changed = false;
514 for constraint in supporting.clone() {
515 changed |= context.record_non_wrapping_product(cx, constraint);
516 }
517 if !changed {
518 break;
519 }
520 }
521 for constraint in supporting {
523 context.record_scaled_zero_fact(cx, constraint);
524 }
525 context
526 }
527
528 fn from_constraints_with_lower_bounds<'a>(
529 constraints: impl Clone + Iterator<Item = &'a SymBoolExpr>,
530 constraint_count: usize,
531 promote_unsigned_bounds: bool,
532 ) -> Self {
533 let mut context = Self::default();
534 for constraint in constraints.clone() {
535 context.record_exact_value_constraint(constraint);
536 context.record_upper_bound_constraint(constraint);
537 context.record_lower_bound_constraint(constraint);
538 context.record_unsigned_lower_bound_constraint(constraint, promote_unsigned_bounds);
539 }
540 for _ in 0..constraint_count {
544 let mut changed = false;
545 for constraint in constraints.clone() {
546 changed |= context.propagate_order_bounds(constraint);
547 }
548 if !changed {
549 break;
550 }
551 }
552 context
553 }
554
555 fn upper_bound(&self, expr: &SymExpr) -> Option<U256> {
556 self.upper_bounds.get(expr).copied()
557 }
558
559 fn lower_bound(&self, expr: &SymExpr) -> Option<U256> {
560 self.lower_bounds.get(expr).copied()
561 }
562
563 fn requires_independent_context(expr: &SymBoolExpr) -> bool {
565 let root_candidate = match expr.kind() {
566 SymBoolExprKind::Cmp(op, left, right) => match op {
567 SymCmpOp::Eq => {
568 Self::mul_div_identity_operands(left, right).is_some()
569 || Self::mul_div_identity_operands(right, left).is_some()
570 || Self::masked_word_side_eq_self_shape(left, right).is_some()
571 || Self::masked_word_side_eq_self_shape(right, left).is_some()
572 }
573 SymCmpOp::Ult | SymCmpOp::Ule => {
574 Self::udiv_comparison_operands(*op, left, right).is_some()
575 }
576 SymCmpOp::Slt | SymCmpOp::Sgt => true,
577 SymCmpOp::Ugt | SymCmpOp::Uge => false,
578 },
579 SymBoolExprKind::Not(value) => match value.kind() {
580 SymBoolExprKind::Cmp(op, left, right)
581 if Self::udiv_comparison_operands(*op, left, right).is_some() =>
582 {
583 true
584 }
585 SymBoolExprKind::Cmp(SymCmpOp::Slt | SymCmpOp::Sgt, _, _) => true,
586 _ => value.zero_check_operand().is_some_and(|word| {
587 matches!(word.kind(), SymExprKind::BinOp(SymBinOp::Or, _, _))
588 }),
589 },
590 SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => false,
591 };
592 root_candidate
593 || expr.contains_udiv()
594 || expr.visit_unique_bool(|word| matches!(word.kind(), SymExprKind::Ite(_, _, _)))
595 }
596
597 fn normalize_bool(
598 &self,
599 cx: &mut SymCx,
600 expr: SymBoolExpr,
601 context_free_changed: bool,
602 ) -> SymBoolExpr {
603 let may_normalize_word = !self.is_exact_value_constraint(&expr)
604 && expr.visit_unique_bool(|word| self.may_normalize_word(word));
605 let expr = if may_normalize_word {
606 let expr = expr.fold_exprs(cx, &mut |cx, expr| self.normalize_word(cx, expr));
607 normalize_bool_for_solver(cx, expr)
608 } else if context_free_changed {
609 normalize_bool_for_solver(cx, expr)
612 } else {
613 expr
614 };
615 if let Some(normalized) = self.normalize_signed_add_comparison(cx, &expr) {
616 return normalized;
617 }
618 if let Some(value) = self.rounding_comparison_value(&expr) {
619 return SymBoolExpr::constant(cx, value);
620 }
621 if let SymBoolExprKind::Not(value) = expr.kind()
622 && let Some(normalized) = self.normalize_signed_add_comparison(cx, value)
623 {
624 return normalized.not(cx);
625 }
626 if let SymBoolExprKind::Cmp(op, left, right) = expr.kind()
627 && let Some(normalized) = self.normalize_udiv_comparison(cx, *op, left, right)
628 {
629 return normalized;
630 }
631 if let SymBoolExprKind::Not(value) = expr.kind()
632 && let SymBoolExprKind::Cmp(op, left, right) = value.kind()
633 && let Some(normalized) = self.normalize_udiv_comparison(cx, *op, left, right)
634 {
635 return normalized.not(cx);
636 }
637
638 match expr.kind() {
639 SymBoolExprKind::Not(value) if self.unsigned_bool_always_true(value) => {
640 SymBoolExpr::constant(cx, false)
641 }
642 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
643 if self.mul_div_identity(left, right) || self.mul_div_identity(right, left) =>
644 {
645 SymBoolExpr::constant(cx, true)
646 }
647 SymBoolExprKind::Not(value)
648 if matches!(
649 value.kind(),
650 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
651 if self.mul_div_identity(left, right)
652 || self.mul_div_identity(right, left)
653 ) =>
654 {
655 SymBoolExpr::constant(cx, false)
656 }
657 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
658 if self.masked_word_eq_self(left, right) =>
659 {
660 SymBoolExpr::constant(cx, true)
662 }
663 SymBoolExprKind::Not(value) if self.masked_eq_self_condition(value) => {
664 SymBoolExpr::constant(cx, false)
666 }
667 _ if expr
668 .zero_check_operand()
669 .is_some_and(|left| self.word_bool_always_true(cx, left)) =>
670 {
671 SymBoolExpr::constant(cx, false)
673 }
674 SymBoolExprKind::Not(value)
675 if value
676 .zero_check_operand()
677 .is_some_and(|left| self.word_bool_always_true(cx, left)) =>
678 {
679 SymBoolExpr::constant(cx, true)
681 }
682 _ => expr,
683 }
684 }
685
686 fn record_exact_value_constraint(&mut self, constraint: &SymBoolExpr) {
687 let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = constraint.kind() else {
688 return;
689 };
690 let Some((expr, value)) = const_side_bound(left, right) else {
691 return;
692 };
693 if !matches!(expr.kind(), SymExprKind::Var(_))
694 || self.conflicting_exact_values.contains(expr)
695 {
696 return;
697 }
698 if self.exact_values.get(expr).is_some_and(|current| *current != value) {
699 self.exact_values.remove(expr);
700 self.conflicting_exact_values.insert(expr.clone());
701 } else {
702 self.exact_values.insert(expr.clone(), value);
703 }
704 }
705
706 fn exact_value(&self, expr: &SymExpr) -> Option<U256> {
707 self.exact_values.get(expr).copied()
708 }
709
710 fn may_normalize_word(&self, expr: &SymExpr) -> bool {
711 if self.exact_values.contains_key(expr)
712 || Self::mul_div_operands(expr).is_some()
713 || Self::ceil_div_product(expr).is_some()
714 {
715 return true;
716 }
717 match expr.kind() {
718 SymExprKind::Ite(_, _, _) => true,
719 SymExprKind::BinOp(SymBinOp::Or, left, right) => {
720 left.as_const() == Some(U256::ONE) || right.as_const() == Some(U256::ONE)
721 }
722 SymExprKind::BinOp(SymBinOp::Mul, _, _) => Self::constant_mul_operands(expr)
723 .is_some_and(|(value, _)| Self::constant_mul_operands(value).is_some()),
724 SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => {
725 Self::rounded_product_operands(numerator).is_some()
726 || (denominator.as_const().is_some_and(|value| !value.is_zero())
727 && Self::constant_mul_operands(numerator).is_some())
728 }
729 _ => false,
730 }
731 }
732
733 fn normalize_word(&self, cx: &mut SymCx, expr: SymExpr) -> SymExpr {
734 if let Some(value) = self.exact_value(&expr) {
735 return SymExpr::constant(cx, value);
736 }
737 if let SymExprKind::Ite(condition, then_value, else_value) = expr.kind() {
738 if let Some(value) = self.bounded_bool_value(condition) {
739 return if value { then_value.clone() } else { else_value.clone() };
740 }
741 if let Some(condition) = self.normalize_signed_add_comparison(cx, condition) {
742 return SymExpr::ite(cx, condition, then_value.clone(), else_value.clone());
743 }
744 }
745 if let Some(value) = self.quotient_of_rounded_product(&expr) {
746 return value.clone();
747 }
748 if let SymExprKind::BinOp(SymBinOp::And, value, mask) = expr.kind()
749 && mask.as_const() == Some(U256::ONE)
750 && value.normalized_bool_word_condition(cx).is_some()
751 {
752 return value.clone();
753 }
754 if let SymExprKind::BinOp(SymBinOp::Or, left, right) = expr.kind()
755 && ((left.as_const() == Some(U256::from(1))
756 && right.normalized_bool_word_condition(cx).is_some())
757 || (right.as_const() == Some(U256::from(1))
758 && left.normalized_bool_word_condition(cx).is_some()))
759 {
760 return SymExpr::one(cx);
761 }
762 if let Some((value, outer_factor)) = Self::constant_mul_operands(&expr)
763 && let Some((value, inner_factor)) = Self::constant_mul_operands(value)
764 {
765 let factor = SymExpr::constant(cx, inner_factor.wrapping_mul(outer_factor));
766 return SymExpr::binop(cx, SymBinOp::Mul, value.clone(), factor);
767 }
768 if let Some((value, factor)) =
769 self.exact_ceil_div_factor(&expr).or_else(|| self.exact_scaled_div_factor(&expr))
770 {
771 let factor = SymExpr::constant(cx, factor);
772 return SymExpr::binop(cx, SymBinOp::Mul, value.clone(), factor);
773 }
774 if let Some((denominator, other)) = Self::mul_div_operands(&expr)
775 && self.interval(denominator).is_some_and(|interval| !interval.min.is_zero())
776 && self.mul_cannot_overflow_256(denominator, other)
777 {
778 return other.clone();
779 }
780 expr
781 }
782
783 fn bounded_bool_value(&self, expr: &SymBoolExpr) -> Option<bool> {
784 match expr.kind() {
785 SymBoolExprKind::Const(value) => Some(*value),
786 SymBoolExprKind::Not(value) => self.bounded_bool_value(value).map(|value| !value),
787 SymBoolExprKind::Cmp(op, left, right) => {
788 let left = self.interval(left)?;
789 let right = self.interval(right)?;
790 if *op == SymCmpOp::Eq {
791 return if left.max < right.min || right.max < left.min {
792 Some(false)
793 } else if left.min == left.max && right.min == right.max {
794 Some(left.min == right.min)
795 } else {
796 None
797 };
798 }
799 if matches!(op, SymCmpOp::Slt | SymCmpOp::Sgt)
800 && (left.min.bit(255) != left.max.bit(255)
801 || right.min.bit(255) != right.max.bit(255))
802 {
803 return None;
804 }
805 let (always, possible) =
806 if matches!(op, SymCmpOp::Ult | SymCmpOp::Ule | SymCmpOp::Slt) {
807 (op.eval(left.max, right.min), op.eval(left.min, right.max))
808 } else {
809 (op.eval(left.min, right.max), op.eval(left.max, right.min))
810 };
811 if always {
812 Some(true)
813 } else if !possible {
814 Some(false)
815 } else {
816 None
817 }
818 }
819 SymBoolExprKind::And(_) => None,
820 }
821 }
822
823 fn normalize_signed_add_comparison(
825 &self,
826 cx: &mut SymCx,
827 expr: &SymBoolExpr,
828 ) -> Option<SymBoolExpr> {
829 let SymBoolExprKind::Cmp(op, left, right) = expr.kind() else { return None };
830 let (sum, base) = match op {
831 SymCmpOp::Slt => (left, right),
832 SymCmpOp::Sgt => (right, left),
833 _ => return None,
834 };
835 let signed_max = U256::MAX >> 1;
836 if let Some((_, increment)) = sum.add_with_operand(base) {
837 let base_range = self.interval(base)?;
838 let increment_range = self.interval(increment)?;
839 if base_range.min > signed_max && increment_range.max <= signed_max {
843 return Some(SymBoolExpr::constant(cx, false));
844 }
845 if base_range.max <= signed_max && increment_range.min > signed_max {
846 return Some(SymBoolExpr::constant(cx, true));
847 }
848 } else if base.as_const() != Some(U256::ZERO) {
849 return None;
850 }
851 if base.as_const() == Some(U256::ZERO)
852 && let SymExprKind::BinOp(SymBinOp::Add, left, right) = sum.kind()
853 {
854 for (positive, negative) in [(left, right), (right, left)] {
855 if let SymExprKind::BinOp(SymBinOp::Sub, zero, amount) = negative.kind()
856 && zero.as_const() == Some(U256::ZERO)
857 && self.interval(positive).is_some_and(|range| range.max <= signed_max)
858 && self.interval(amount).is_some_and(|range| range.max <= signed_max)
859 {
860 return Some(SymBoolExpr::cmp(
862 cx,
863 SymCmpOp::Ult,
864 positive.clone(),
865 amount.clone(),
866 ));
867 }
868 }
869 }
870 let SymExprKind::BinOp(SymBinOp::Add, increment, base) = sum.kind() else {
873 return None;
874 };
875 if self.interval(base)?.max > signed_max || self.interval(increment)?.max > signed_max {
876 return None;
877 }
878 let limit = SymExpr::constant(cx, signed_max);
881 let remaining = SymExpr::binop(cx, SymBinOp::Sub, limit, base.clone());
882 Some(SymBoolExpr::cmp(cx, SymCmpOp::Ult, remaining, increment.clone()))
883 }
884
885 fn exact_ceil_div_factor<'a>(&self, expr: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
886 let (product, denominator) = Self::ceil_div_product(expr)?;
887 let (value, multiplier) = Self::constant_mul_operands(product)?;
888 self.max_scaled_product(value, multiplier)?.checked_add(denominator)?;
889 let factor = multiplier.checked_div(denominator)?;
890 (multiplier % denominator).is_zero().then_some((value, factor))
891 }
892
893 fn ceil_div_product(expr: &SymExpr) -> Option<(&SymExpr, U256)> {
895 let (numerator, denominator) = expr.udiv_operands()?;
896 let scale = denominator.as_const().filter(|value| !value.is_zero())?;
897 if let SymExprKind::BinOp(SymBinOp::Sub, sum, one) = numerator.kind()
898 && one.as_const() == Some(U256::ONE)
899 && let SymExprKind::BinOp(SymBinOp::Add, product, rounding) = sum.kind()
900 && rounding.as_const() == Some(scale)
901 && matches!(product.kind(), SymExprKind::BinOp(SymBinOp::Mul, _, _))
902 {
903 Some((product, scale))
904 } else {
905 None
906 }
907 }
908
909 fn exact_scaled_div_factor<'a>(&self, expr: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
910 let (numerator, denominator) = expr.udiv_operands()?;
911 let denominator = denominator.as_const().filter(|value| !value.is_zero())?;
912 let (value, multiplier) = Self::constant_mul_operands(numerator)?;
913 if !(multiplier % denominator).is_zero()
914 || self.max_scaled_product(value, multiplier).is_none()
915 {
916 return None;
917 }
918 Some((value, multiplier / denominator))
919 }
920
921 fn max_scaled_product(&self, value: &SymExpr, multiplier: U256) -> Option<U256> {
922 self.interval(value)?.max.checked_mul(multiplier)
923 }
924
925 fn constant_mul_operands(expr: &SymExpr) -> Option<(&SymExpr, U256)> {
926 let SymExprKind::BinOp(SymBinOp::Mul, left, right) = expr.kind() else {
927 return None;
928 };
929 right
930 .as_const()
931 .map(|factor| (left, factor))
932 .or_else(|| left.as_const().map(|factor| (right, factor)))
933 }
934
935 fn is_exact_value_constraint(&self, constraint: &SymBoolExpr) -> bool {
936 let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = constraint.kind() else {
937 return false;
938 };
939 const_side_bound(left, right)
940 .is_some_and(|(expr, value)| self.exact_value(expr) == Some(value))
941 }
942
943 fn masked_eq_self_condition(&self, expr: &SymBoolExpr) -> bool {
944 match expr.kind() {
945 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
946 self.masked_word_eq_self(left, right)
947 }
948 _ => false,
949 }
950 }
951
952 fn masked_word_eq_self(&self, left: &SymExpr, right: &SymExpr) -> bool {
953 self.masked_word_side_eq_self(left, right) || self.masked_word_side_eq_self(right, left)
954 }
955
956 fn masked_word_side_eq_self(&self, masked: &SymExpr, value: &SymExpr) -> bool {
957 Self::masked_word_side_eq_self_shape(masked, value)
958 .is_some_and(|bits| self.unsigned_bits(value) <= bits)
959 }
960
961 fn masked_word_side_eq_self_shape(masked: &SymExpr, value: &SymExpr) -> Option<usize> {
962 let SymExprKind::BinOp(SymBinOp::And, left, right) = masked.kind() else {
963 return None;
964 };
965 let (source, mask) = right
966 .as_const()
967 .map(|mask| (left, mask))
968 .or_else(|| left.as_const().map(|mask| (right, mask)))?;
969 let bits = mask_low_bits(mask)?;
970 (source == value).then_some(bits)
971 }
972
973 fn record_upper_bound_constraint(&mut self, constraint: &SymBoolExpr) {
974 if let Some((expr, bound)) = self.upper_bound_constraint(constraint) {
975 self.record_upper_bound(expr.clone(), bound);
976 }
977 }
978
979 fn record_upper_bound(&mut self, expr: SymExpr, bound: U256) -> bool {
980 match self.upper_bounds.entry(expr) {
981 alloy_primitives::map::Entry::Occupied(mut entry) if bound < *entry.get() => {
982 entry.insert(bound);
983 true
984 }
985 alloy_primitives::map::Entry::Vacant(entry) => {
986 entry.insert(bound);
987 true
988 }
989 alloy_primitives::map::Entry::Occupied(_) => false,
990 }
991 }
992
993 fn record_lower_bound_constraint(&mut self, constraint: &SymBoolExpr) {
994 if let Some((expr, bound)) = self.lower_bound_constraint(constraint) {
995 self.record_lower_bound(expr.clone(), bound);
996 }
997 }
998
999 fn record_unsigned_lower_bound_constraint(
1000 &mut self,
1001 constraint: &SymBoolExpr,
1002 promote_to_interval: bool,
1003 ) {
1004 if let Some((expr, bound)) = self.unsigned_lower_bound_constraint(constraint) {
1005 let entry = self.unsigned_lower_bounds.entry(expr.clone()).or_default();
1006 *entry = (*entry).max(bound);
1007 if promote_to_interval {
1011 self.record_lower_bound(expr.clone(), bound);
1012 }
1013 }
1014 }
1015
1016 fn unsigned_lower_bound_constraint<'a>(
1017 &self,
1018 constraint: &'a SymBoolExpr,
1019 ) -> Option<(&'a SymExpr, U256)> {
1020 match constraint.kind() {
1021 SymBoolExprKind::Cmp(op, left, right) => match *op {
1022 SymCmpOp::Eq => const_side_bound(left, right),
1023 SymCmpOp::Ult => {
1024 left.as_const()?.checked_add(U256::ONE).map(|bound| (right, bound))
1025 }
1026 SymCmpOp::Ule => left.as_const().map(|bound| (right, bound)),
1027 SymCmpOp::Ugt => {
1028 right.as_const()?.checked_add(U256::ONE).map(|bound| (left, bound))
1029 }
1030 SymCmpOp::Uge => right.as_const().map(|bound| (left, bound)),
1031 SymCmpOp::Slt | SymCmpOp::Sgt => None,
1032 },
1033 SymBoolExprKind::Not(value) => match value.kind() {
1034 SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right) => {
1035 right.as_const().map(|bound| (left, bound))
1036 }
1037 SymBoolExprKind::Cmp(SymCmpOp::Ule, left, right) => {
1038 right.as_const()?.checked_add(U256::ONE).map(|bound| (left, bound))
1039 }
1040 SymBoolExprKind::Cmp(SymCmpOp::Ugt, left, right) => {
1041 left.as_const().map(|bound| (right, bound))
1042 }
1043 SymBoolExprKind::Cmp(SymCmpOp::Uge, left, right) => {
1044 left.as_const()?.checked_add(U256::ONE).map(|bound| (right, bound))
1045 }
1046 _ => None,
1047 },
1048 _ => None,
1049 }
1050 }
1051
1052 fn record_lower_bound(&mut self, expr: SymExpr, bound: U256) -> bool {
1053 match self.lower_bounds.entry(expr) {
1054 alloy_primitives::map::Entry::Occupied(mut entry) if bound > *entry.get() => {
1055 entry.insert(bound);
1056 true
1057 }
1058 alloy_primitives::map::Entry::Vacant(entry) => {
1059 entry.insert(bound);
1060 true
1061 }
1062 alloy_primitives::map::Entry::Occupied(_) => false,
1063 }
1064 }
1065
1066 fn propagate_order_bounds(&mut self, constraint: &SymBoolExpr) -> bool {
1067 match constraint.kind() {
1068 SymBoolExprKind::Cmp(op, left, right) => match op {
1069 SymCmpOp::Ult | SymCmpOp::Ule => self.propagate_less_or_equal_bounds(left, right),
1070 SymCmpOp::Ugt | SymCmpOp::Uge => self.propagate_less_or_equal_bounds(right, left),
1071 SymCmpOp::Eq => {
1072 let changed = self.propagate_less_or_equal_bounds(left, right);
1073 self.propagate_less_or_equal_bounds(right, left) || changed
1074 }
1075 SymCmpOp::Slt | SymCmpOp::Sgt => false,
1076 },
1077 SymBoolExprKind::Not(value) => match value.kind() {
1078 SymBoolExprKind::Cmp(op, left, right) => match op {
1079 SymCmpOp::Ult | SymCmpOp::Ule => {
1080 self.propagate_less_or_equal_bounds(right, left)
1081 }
1082 SymCmpOp::Ugt | SymCmpOp::Uge => {
1083 self.propagate_less_or_equal_bounds(left, right)
1084 }
1085 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => false,
1086 },
1087 _ => false,
1088 },
1089 SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => false,
1090 }
1091 }
1092
1093 fn propagate_less_or_equal_bounds(&mut self, left: &SymExpr, right: &SymExpr) -> bool {
1095 let upper = self.upper_bound(right);
1096 let lower = self.lower_bound(left);
1097 let upper_changed = upper.is_some_and(|bound| self.record_upper_bound(left.clone(), bound));
1098 let lower_changed =
1099 lower.is_some_and(|bound| self.record_lower_bound(right.clone(), bound));
1100 upper_changed || lower_changed
1101 }
1102
1103 fn upper_bound_constraint<'a>(
1104 &self,
1105 constraint: &'a SymBoolExpr,
1106 ) -> Option<(&'a SymExpr, U256)> {
1107 match constraint.kind() {
1108 SymBoolExprKind::Cmp(op, left, right) => match *op {
1109 SymCmpOp::Eq => const_side_bound(left, right),
1110 SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
1111 (_, Some(bound)) => (!bound.is_zero()).then(|| (left, bound - U256::from(1))),
1112 _ => None,
1113 },
1114 SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
1115 (_, Some(bound)) => Some((left, bound)),
1116 _ => None,
1117 },
1118 SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
1119 (Some(bound), _) => (!bound.is_zero()).then(|| (right, bound - U256::from(1))),
1120 _ => None,
1121 },
1122 SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
1123 (Some(bound), _) => Some((right, bound)),
1124 _ => None,
1125 },
1126 SymCmpOp::Slt | SymCmpOp::Sgt => None,
1127 },
1128 SymBoolExprKind::Not(value) => match value.kind() {
1129 SymBoolExprKind::Cmp(op, left, right) => match *op {
1130 SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
1131 (_, Some(bound)) => Some((left, bound)),
1132 _ => None,
1133 },
1134 SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
1135 (_, Some(bound)) => {
1136 (!bound.is_zero()).then(|| (left, bound - U256::from(1)))
1137 }
1138 _ => None,
1139 },
1140 SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
1141 (Some(bound), _) => Some((right, bound)),
1142 _ => None,
1143 },
1144 SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
1145 (Some(bound), _) => {
1146 (!bound.is_zero()).then(|| (right, bound - U256::from(1)))
1147 }
1148 _ => None,
1149 },
1150 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
1151 },
1152 _ => None,
1153 },
1154 SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => None,
1155 }
1156 }
1157
1158 fn lower_bound_constraint<'a>(
1159 &self,
1160 constraint: &'a SymBoolExpr,
1161 ) -> Option<(&'a SymExpr, U256)> {
1162 match constraint.kind() {
1163 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => const_side_bound(left, right),
1164 SymBoolExprKind::Not(value) => match value.kind() {
1165 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1166 if right.as_const().is_some_and(|value| value.is_zero()) {
1167 Some((left, U256::from(1)))
1168 } else if left.as_const().is_some_and(|value| value.is_zero()) {
1169 Some((right, U256::from(1)))
1170 } else {
1171 None
1172 }
1173 }
1174 _ => None,
1175 },
1176 _ => None,
1177 }
1178 }
1179
1180 fn unsigned_bool_always_true(&self, expr: &SymBoolExpr) -> bool {
1181 match expr.kind() {
1182 SymBoolExprKind::Cmp(op, left, right) => {
1183 self.unsigned_cmp_always_true(*op, left, right)
1184 }
1185 _ => false,
1186 }
1187 }
1188
1189 fn unsigned_cmp_always_true(&self, op: SymCmpOp, left: &SymExpr, right: &SymExpr) -> bool {
1190 if op == SymCmpOp::Eq
1191 && (self.mul_div_identity(left, right) || self.mul_div_identity(right, left))
1192 {
1193 return true;
1194 }
1195 let Some(left) = self.interval(left) else {
1196 return false;
1197 };
1198 let Some(right) = self.interval(right) else {
1199 return false;
1200 };
1201 match op {
1202 SymCmpOp::Ult => left.max < right.min,
1203 SymCmpOp::Ule => left.max <= right.min,
1204 SymCmpOp::Ugt => left.min > right.max,
1205 SymCmpOp::Uge => left.min >= right.max,
1206 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => false,
1207 }
1208 }
1209
1210 fn mul_div_identity(&self, quotient: &SymExpr, expected: &SymExpr) -> bool {
1211 let Some((denominator, other)) = Self::mul_div_identity_operands(quotient, expected) else {
1212 return false;
1213 };
1214
1215 self.interval(denominator).is_some_and(|interval| !interval.min.is_zero())
1216 && self.mul_cannot_overflow_256(denominator, other)
1217 }
1218
1219 fn mul_div_identity_operands<'a>(
1220 quotient: &'a SymExpr,
1221 expected: &SymExpr,
1222 ) -> Option<(&'a SymExpr, &'a SymExpr)> {
1223 let (denominator, other) = Self::mul_div_operands(quotient)?;
1224 (other == expected).then_some((denominator, other))
1225 }
1226
1227 fn mul_div_operands(quotient: &SymExpr) -> Option<(&SymExpr, &SymExpr)> {
1228 let (numerator, denominator) = quotient.udiv_operands()?;
1229 let SymExprKind::BinOp(SymBinOp::Mul, left, right) = numerator.kind() else {
1230 return None;
1231 };
1232 let other = if left == denominator {
1233 right
1234 } else if right == denominator {
1235 left
1236 } else {
1237 return None;
1238 };
1239 Some((denominator, other))
1240 }
1241
1242 fn udiv_comparison_operands<'a>(
1243 op: SymCmpOp,
1244 left: &'a SymExpr,
1245 right: &'a SymExpr,
1246 ) -> Option<(&'a SymExpr, &'a SymExpr, &'a SymExpr, bool)> {
1247 if !matches!(op, SymCmpOp::Ult | SymCmpOp::Ule) {
1248 return None;
1249 }
1250 if let Some((numerator, denominator)) = left.udiv_operands()
1251 && denominator.as_const().is_some_and(|value| !value.is_zero())
1252 && !right.contains_udiv()
1253 {
1254 return Some((numerator, denominator, right, true));
1255 }
1256 if let Some((numerator, denominator)) = right.udiv_operands()
1257 && denominator.as_const().is_some_and(|value| !value.is_zero())
1258 && !left.contains_udiv()
1259 {
1260 return Some((numerator, denominator, left, false));
1261 }
1262 None
1263 }
1264
1265 fn normalize_udiv_comparison(
1266 &self,
1267 cx: &mut SymCx,
1268 op: SymCmpOp,
1269 left: &SymExpr,
1270 right: &SymExpr,
1271 ) -> Option<SymBoolExpr> {
1272 let (numerator, denominator, threshold, quotient_on_left) =
1273 Self::udiv_comparison_operands(op, left, right)?;
1274 let increment_threshold =
1275 matches!((op, quotient_on_left), (SymCmpOp::Ule, true) | (SymCmpOp::Ult, false));
1276 let threshold = if increment_threshold {
1277 self.interval(threshold)?.max.checked_add(U256::ONE)?;
1279 let one = SymExpr::one(cx);
1280 SymExpr::binop(cx, SymBinOp::Add, threshold.clone(), one)
1281 } else {
1282 threshold.clone()
1283 };
1284 if !self.mul_cannot_overflow_256(&threshold, denominator) {
1285 return None;
1286 }
1287
1288 let scaled_threshold = SymExpr::binop(cx, SymBinOp::Mul, threshold, denominator.clone());
1289 Some(if quotient_on_left {
1290 SymBoolExpr::cmp(cx, SymCmpOp::Ult, numerator.clone(), scaled_threshold)
1292 } else {
1293 SymBoolExpr::cmp(cx, SymCmpOp::Ule, scaled_threshold, numerator.clone())
1295 })
1296 }
1297
1298 fn interval(&self, expr: &SymExpr) -> Option<WordInterval> {
1299 let mut intervals = HashMap::default();
1300 let mut remaining = MAX_LOCAL_ANALYSIS_NODES;
1301 self.interval_cached(expr, &mut intervals, &mut remaining)
1302 }
1303
1304 fn interval_cached(
1305 &self,
1306 expr: &SymExpr,
1307 intervals: &mut HashMap<SymExpr, Option<WordInterval>>,
1308 remaining: &mut usize,
1309 ) -> Option<WordInterval> {
1310 if let Some(interval) = intervals.get(expr) {
1311 return *interval;
1312 }
1313
1314 let lower = self.lower_bound(expr);
1315 let upper = self.upper_bound(expr);
1316 let explicit_bounds = || {
1317 if lower.is_none() && upper.is_none() {
1318 return None;
1319 }
1320 WordInterval::new(lower.unwrap_or(U256::ZERO), upper.unwrap_or(U256::MAX))
1321 };
1322 if *remaining == 0 {
1323 let interval = explicit_bounds();
1324 intervals.insert(expr.clone(), interval);
1325 return interval;
1326 }
1327 *remaining -= 1;
1328
1329 let interval =
1330 self.structural_interval(expr, intervals, remaining).or_else(explicit_bounds);
1331 let interval = interval.and_then(|interval| interval.with_bounds(lower, upper));
1332 intervals.insert(expr.clone(), interval);
1333 interval
1334 }
1335
1336 fn structural_interval(
1337 &self,
1338 expr: &SymExpr,
1339 intervals: &mut HashMap<SymExpr, Option<WordInterval>>,
1340 remaining: &mut usize,
1341 ) -> Option<WordInterval> {
1342 match expr.kind() {
1343 SymExprKind::Const(value) => Some(WordInterval::exact(*value)),
1344 SymExprKind::BinOp(SymBinOp::And, left, right) => {
1345 let mask = left.as_const().or_else(|| right.as_const())?;
1346 Some(WordInterval { min: U256::ZERO, max: mask })
1347 }
1348 SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1349 let left = self.interval_cached(left, intervals, remaining)?;
1350 let right = self.interval_cached(right, intervals, remaining)?;
1351 Some(WordInterval {
1352 min: left.min.checked_add(right.min)?,
1353 max: left.max.checked_add(right.max)?,
1354 })
1355 }
1356 SymExprKind::BinOp(SymBinOp::Sub, left, right) => {
1357 if let Some(interval) =
1358 self.rounding_error_interval(left, right, intervals, remaining)
1359 {
1360 return Some(interval);
1361 }
1362 let left = self.interval_cached(left, intervals, remaining)?;
1363 let right = self.interval_cached(right, intervals, remaining)?;
1364 if left.max < right.min {
1365 return Some(WordInterval {
1367 min: left.min.wrapping_sub(right.max),
1368 max: left.max.wrapping_sub(right.min),
1369 });
1370 }
1371 if left.min < right.max {
1372 return None;
1373 }
1374 Some(WordInterval {
1375 min: left.min.checked_sub(right.max)?,
1376 max: left.max.checked_sub(right.min)?,
1377 })
1378 }
1379 SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1380 let guarded = self.has_non_wrapping_product(left, right);
1381 let left = self.interval_cached(left, intervals, remaining)?;
1382 let right = self.interval_cached(right, intervals, remaining)?;
1383 Some(WordInterval {
1384 min: left.min.checked_mul(right.min)?,
1385 max: left
1388 .max
1389 .checked_mul(right.max)
1390 .or_else(|| guarded.then_some(U256::MAX))?,
1391 })
1392 }
1393 SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => {
1394 let numerator = self
1397 .interval_cached(numerator, intervals, remaining)
1398 .unwrap_or(WordInterval { min: U256::ZERO, max: U256::MAX });
1399 let denominator = self.interval_cached(denominator, intervals, remaining)?;
1400 if denominator.min.is_zero() {
1401 return None;
1402 }
1403 Some(WordInterval {
1404 min: numerator.min / denominator.max,
1405 max: numerator.max / denominator.min,
1406 })
1407 }
1408 SymExprKind::BinOp(SymBinOp::Shr, value, shift) => {
1409 let shift = shift.as_const()?;
1410 if shift >= U256::from(256) {
1411 return Some(WordInterval::exact(U256::ZERO));
1412 }
1413 let value = self.interval_cached(value, intervals, remaining)?;
1414 let shift = shift.to::<usize>();
1415 Some(WordInterval { min: value.min >> shift, max: value.max >> shift })
1416 }
1417 SymExprKind::Ite(_, left, right) => {
1418 let left = self.interval_cached(left, intervals, remaining)?;
1419 let right = self.interval_cached(right, intervals, remaining)?;
1420 Some(WordInterval { min: left.min.min(right.min), max: left.max.max(right.max) })
1421 }
1422 _ => None,
1423 }
1424 }
1425}
1426
1427fn const_side_bound<'a>(left: &'a SymExpr, right: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
1428 right
1429 .as_const()
1430 .map(|value| (left, value))
1431 .or_else(|| left.as_const().map(|value| (right, value)))
1432}
1433
1434pub(crate) fn normalize_expr_for_solver(cx: &mut SymCx, expr: SymExpr) -> SymExpr {
1436 if expr.contains_ite() { expr.fold(cx, &mut normalize_expr_node_for_solver) } else { expr }
1437}
1438
1439fn normalize_expr_node_for_solver(cx: &mut SymCx, expr: SymExpr) -> SymExpr {
1440 match expr.kind() {
1441 SymExprKind::Ite(cond, left, right) => {
1442 normalize_ite_expr_for_solver(cx, cond.clone(), left.clone(), right.clone())
1443 }
1444 _ => expr,
1445 }
1446}
1447
1448fn normalize_ite_expr_for_solver(
1449 cx: &mut SymCx,
1450 cond: SymBoolExpr,
1451 left: SymExpr,
1452 right: SymExpr,
1453) -> SymExpr {
1454 let cond = normalize_bool_for_solver(cx, cond);
1455 if left == right {
1456 return left;
1458 }
1459 if left.as_const() == Some(U256::from(1))
1460 && right.normalized_bool_word_condition(cx).as_ref() == Some(&cond)
1461 {
1462 return right;
1464 }
1465 if right.as_const().is_some_and(|value| value.is_zero())
1466 && left.normalized_bool_word_condition(cx).as_ref() == Some(&cond)
1467 {
1468 return left;
1470 }
1471 SymExpr::ite(cx, cond, left, right)
1472}
1473
1474impl SymExpr {
1475 fn add_cannot_overflow_256(&self, right: &Self) -> bool {
1476 self.unsigned_bits().max(right.unsigned_bits()).saturating_add(1) <= 256
1477 }
1478
1479 fn word_bool_always_true(&self, cx: &mut SymCx) -> bool {
1480 ConstraintContext::default().word_bool_always_true(cx, self)
1481 }
1482}
1483
1484impl SymBoolExpr {
1485 fn normalize_udiv_for_solver(&self, cx: &mut SymCx) -> Option<Self> {
1486 if let SymBoolExprKind::Cmp(op, left, right) = self.kind()
1487 && let Some(normalized) = Self::normalize_const_over_self_udiv_cmp(cx, *op, left, right)
1488 {
1489 return Some(normalized);
1490 }
1491 if let SymBoolExprKind::Not(value) = self.kind()
1492 && let SymBoolExprKind::Cmp(op, left, right) = value.kind()
1493 && let Some(normalized) = Self::normalize_const_over_self_udiv_cmp(cx, *op, left, right)
1494 {
1495 return Some(normalized.not(cx));
1496 }
1497
1498 match self.kind() {
1499 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1500 if right.as_const().is_some_and(|value| value.is_zero()) =>
1501 {
1502 left.normalized_bool_word_condition(cx).map(|value| value.not(cx)).or_else(|| {
1503 if left.word_bool_always_true(cx) {
1504 Some(Self::constant(cx, false))
1506 } else {
1507 let zero = SymExpr::zero(cx);
1508 Self::normalize_udiv_eq_zero(cx, left, &zero)
1509 }
1510 })
1511 }
1512 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1513 if right.as_const() == Some(U256::from(1)) =>
1514 {
1515 left.normalized_bool_word_condition(cx)
1517 }
1518 SymBoolExprKind::Not(value) => match value.kind() {
1519 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1520 if right.as_const().is_some_and(|value| value.is_zero()) =>
1521 {
1522 if left.word_bool_always_true(cx) {
1523 Some(Self::constant(cx, true))
1525 } else {
1526 let zero = SymExpr::zero(cx);
1527 Self::normalize_udiv_eq_zero(cx, left, &zero).map(|value| value.not(cx))
1528 }
1529 }
1530 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1531 Self::normalize_udiv_eq_zero(cx, left, right).map(|value| value.not(cx))
1532 }
1533 SymBoolExprKind::Cmp(op, left, right) => {
1534 Self::normalize_add_overflow_cmp(cx, *op, left, right)
1535 .map(|value| value.not(cx))
1536 .or_else(|| {
1537 Self::normalize_udiv_cmp(cx, *op, left, right)
1538 .map(|value| value.not(cx))
1539 })
1540 }
1541 _ => None,
1542 },
1543 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1544 Self::normalize_udiv_eq_zero(cx, left, right)
1545 }
1546 SymBoolExprKind::Cmp(op, left, right) => {
1547 Self::normalize_add_overflow_cmp(cx, *op, left, right)
1548 .or_else(|| Self::normalize_udiv_cmp(cx, *op, left, right))
1549 }
1550 SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => None,
1551 }
1552 }
1553
1554 fn normalize_add_overflow_cmp(
1555 cx: &mut SymCx,
1556 op: SymCmpOp,
1557 left: &SymExpr,
1558 right: &SymExpr,
1559 ) -> Option<Self> {
1560 if let Some(normalized) = Self::normalize_sub_underflow_cmp(cx, op, left, right) {
1561 return Some(normalized);
1562 }
1563 let (base, increment, overflow) = match op {
1566 SymCmpOp::Ugt => {
1567 right.add_with_operand(left).map(|(_, increment)| (left, increment, true))
1568 }
1569 SymCmpOp::Ult => {
1570 left.add_with_operand(right).map(|(_, increment)| (right, increment, true))
1571 }
1572 SymCmpOp::Uge => {
1573 left.add_with_operand(right).map(|(_, increment)| (right, increment, false))
1574 }
1575 SymCmpOp::Ule => {
1576 right.add_with_operand(left).map(|(_, increment)| (left, increment, false))
1577 }
1578 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
1579 }?;
1580 if base.add_cannot_overflow_256(increment) {
1581 return Some(Self::constant(cx, !overflow));
1582 }
1583
1584 let limit = match base.kind() {
1585 SymExprKind::BinOp(SymBinOp::Sub, max, value) if max.as_const() == Some(U256::MAX) => {
1586 value.clone()
1587 }
1588 _ => SymExpr::not(cx, base.clone()),
1589 };
1590 Some(if overflow {
1591 Self::cmp(cx, SymCmpOp::Ult, limit, increment.clone())
1592 } else {
1593 Self::cmp(cx, SymCmpOp::Ule, increment.clone(), limit)
1594 })
1595 }
1596
1597 fn normalize_sub_underflow_cmp(
1598 cx: &mut SymCx,
1599 op: SymCmpOp,
1600 left: &SymExpr,
1601 right: &SymExpr,
1602 ) -> Option<Self> {
1603 let (base, difference, underflow) = match op {
1604 SymCmpOp::Ult => (left, right, true),
1605 SymCmpOp::Ugt => (right, left, true),
1606 SymCmpOp::Ule => (right, left, false),
1607 SymCmpOp::Uge => (left, right, false),
1608 _ => return None,
1609 };
1610 let SymExprKind::BinOp(SymBinOp::Sub, minuend, subtrahend) = difference.kind() else {
1611 return None;
1612 };
1613 if minuend != base {
1614 return None;
1615 }
1616 Some(if underflow {
1618 Self::cmp(cx, SymCmpOp::Ult, base.clone(), subtrahend.clone())
1619 } else {
1620 Self::cmp(cx, SymCmpOp::Ule, subtrahend.clone(), base.clone())
1621 })
1622 }
1623
1624 fn normalize_udiv_eq_zero(cx: &mut SymCx, left: &SymExpr, right: &SymExpr) -> Option<Self> {
1625 if right.as_const().is_some_and(|value| value.is_zero())
1626 && let Some(condition) = left.normalize_eq_zero_for_solver(cx)
1627 {
1628 return Some(condition);
1630 }
1631 None
1632 }
1633
1634 fn normalize_udiv_cmp(
1635 cx: &mut SymCx,
1636 op: SymCmpOp,
1637 left: &SymExpr,
1638 right: &SymExpr,
1639 ) -> Option<Self> {
1640 match op {
1641 SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
1642 (_, Some(value)) if value.is_zero() => left
1644 .normalize_ne_zero_for_solver(cx)
1645 .or_else(|| Some(Self::eq_zero(cx, left).not(cx))),
1646 (Some(value), _) if value == U256::from(1) => right
1648 .normalize_eq_zero_for_solver(cx)
1649 .or_else(|| Some(Self::eq_zero(cx, right))),
1650 _ => None,
1651 },
1652 SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
1653 (_, Some(value)) if value == U256::from(1) => left
1655 .normalize_ne_zero_for_solver(cx)
1656 .or_else(|| Some(Self::eq_zero(cx, left).not(cx))),
1657 (Some(value), _) if value.is_zero() => right
1659 .normalize_eq_zero_for_solver(cx)
1660 .or_else(|| Some(Self::eq_zero(cx, right))),
1661 _ => None,
1662 },
1663 SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
1664 (_, Some(value)) if value.is_zero() => {
1666 left.normalize_eq_zero_for_solver(cx).or_else(|| Some(Self::eq_zero(cx, left)))
1667 }
1668 (Some(value), _) if value == U256::from(1) => right
1670 .normalize_ne_zero_for_solver(cx)
1671 .or_else(|| Some(Self::eq_zero(cx, right).not(cx))),
1672 _ => None,
1673 },
1674 SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
1675 (_, Some(value)) if value == U256::from(1) => {
1677 left.normalize_eq_zero_for_solver(cx).or_else(|| Some(Self::eq_zero(cx, left)))
1678 }
1679 (Some(value), _) if value.is_zero() => right
1681 .normalize_ne_zero_for_solver(cx)
1682 .or_else(|| Some(Self::eq_zero(cx, right).not(cx))),
1683 _ => None,
1684 },
1685 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
1686 }
1687 }
1688
1689 fn normalize_const_over_self_udiv_cmp(
1690 cx: &mut SymCx,
1691 op: SymCmpOp,
1692 left: &SymExpr,
1693 right: &SymExpr,
1694 ) -> Option<Self> {
1695 let (value, quotient, complement) = match op {
1696 SymCmpOp::Ule => (left, right, false),
1698 SymCmpOp::Ult => (right, left, true),
1700 SymCmpOp::Eq | SymCmpOp::Ugt | SymCmpOp::Uge | SymCmpOp::Slt | SymCmpOp::Sgt => {
1701 return None;
1702 }
1703 };
1704 let (numerator, denominator) = match quotient.kind() {
1705 SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => (numerator, denominator),
1706 SymExprKind::Ite(condition, zero, division)
1707 if zero.as_const().is_some_and(|value| value.is_zero()) =>
1708 {
1709 let (numerator, denominator) = division.udiv_operands()?;
1710 if condition.zero_check_operand() != Some(denominator) {
1711 return None;
1712 }
1713 (numerator, denominator)
1714 }
1715 _ => return None,
1716 };
1717 if denominator != value {
1718 return None;
1719 }
1720
1721 let threshold = numerator.as_const()?.root(2);
1722 let threshold = SymExpr::constant(cx, threshold);
1723 Some(if complement {
1724 Self::cmp(cx, SymCmpOp::Ult, threshold, value.clone())
1725 } else {
1726 Self::cmp(cx, SymCmpOp::Ule, value.clone(), threshold)
1727 })
1728 }
1729
1730 fn eq_zero(cx: &mut SymCx, expr: &SymExpr) -> Self {
1731 let zero = SymExpr::zero(cx);
1732 Self::eq(cx, expr.clone(), zero)
1733 }
1734}
1735
1736impl SymExpr {
1737 fn normalized_bool_word_condition(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1738 self.strip_low_byte_mask()
1739 .bool_word_condition()
1740 .map(|condition| normalize_bool_for_solver(cx, condition))
1741 }
1742
1743 fn add_with_operand<'a>(&'a self, operand: &Self) -> Option<(&'a Self, &'a Self)> {
1744 let SymExprKind::BinOp(SymBinOp::Add, left, right) = self.kind() else {
1745 return None;
1746 };
1747 if left == operand {
1748 Some((left, right))
1749 } else if right == operand {
1750 Some((right, left))
1751 } else {
1752 None
1753 }
1754 }
1755
1756 fn normalize_eq_zero_for_solver(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1757 if let Some((numerator, denominator)) = self.udiv_operands() {
1758 return Some(Self::udiv_zero_condition(cx, numerator, denominator));
1760 }
1761 if let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() {
1762 let then_zero = match then_expr.normalize_eq_zero_for_solver(cx) {
1763 Some(condition) => condition,
1764 None => {
1765 let then_expr = normalize_expr_for_solver(cx, then_expr.clone());
1766 let zero = Self::zero(cx);
1767 SymBoolExpr::eq(cx, then_expr, zero)
1768 }
1769 };
1770 let else_zero = match else_expr.normalize_eq_zero_for_solver(cx) {
1771 Some(condition) => condition,
1772 None => {
1773 let else_expr = normalize_expr_for_solver(cx, else_expr.clone());
1774 let zero = Self::zero(cx);
1775 SymBoolExpr::eq(cx, else_expr, zero)
1776 }
1777 };
1778 if then_zero.contains_udiv() || else_zero.contains_udiv() {
1779 return None;
1780 }
1781 let condition = normalize_bool_for_solver(cx, condition.clone());
1783 let then_condition = SymBoolExpr::and(cx, vec![condition.clone(), then_zero]);
1784 let not_condition = condition.not(cx);
1785 let else_condition = SymBoolExpr::and(cx, vec![not_condition, else_zero]);
1786 return Some(SymBoolExpr::or(cx, vec![then_condition, else_condition]));
1787 }
1788 None
1789 }
1790
1791 fn normalize_ne_zero_for_solver(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1792 if let Some((numerator, denominator)) = self.udiv_operands() {
1793 return Some(Self::udiv_nonzero_condition(cx, numerator, denominator));
1795 }
1796 if let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() {
1797 let then_nonzero = match then_expr.normalize_ne_zero_for_solver(cx) {
1798 Some(condition) => condition,
1799 None => {
1800 let then_expr = normalize_expr_for_solver(cx, then_expr.clone());
1801 let zero = Self::zero(cx);
1802 SymBoolExpr::eq(cx, then_expr, zero).not(cx)
1803 }
1804 };
1805 let else_nonzero = match else_expr.normalize_ne_zero_for_solver(cx) {
1806 Some(condition) => condition,
1807 None => {
1808 let else_expr = normalize_expr_for_solver(cx, else_expr.clone());
1809 let zero = Self::zero(cx);
1810 SymBoolExpr::eq(cx, else_expr, zero).not(cx)
1811 }
1812 };
1813 if then_nonzero.contains_udiv() || else_nonzero.contains_udiv() {
1814 return None;
1815 }
1816 let condition = normalize_bool_for_solver(cx, condition.clone());
1818 let then_condition = SymBoolExpr::and(cx, vec![condition.clone(), then_nonzero]);
1819 let not_condition = condition.not(cx);
1820 let else_condition = SymBoolExpr::and(cx, vec![not_condition, else_nonzero]);
1821 return Some(SymBoolExpr::or(cx, vec![then_condition, else_condition]));
1822 }
1823 None
1824 }
1825
1826 fn udiv_zero_condition(cx: &mut SymCx, numerator: &Self, denominator: &Self) -> SymBoolExpr {
1827 let numerator = normalize_expr_for_solver(cx, numerator.clone());
1828 let denominator = normalize_expr_for_solver(cx, denominator.clone());
1829 let zero = Self::zero(cx);
1830 let denominator_zero = SymBoolExpr::eq(cx, denominator.clone(), zero);
1831 let below_denominator = SymBoolExpr::cmp(cx, SymCmpOp::Ult, numerator, denominator);
1832 SymBoolExpr::or(cx, vec![denominator_zero, below_denominator])
1833 }
1834
1835 fn udiv_nonzero_condition(cx: &mut SymCx, numerator: &Self, denominator: &Self) -> SymBoolExpr {
1836 let numerator = normalize_expr_for_solver(cx, numerator.clone());
1837 let denominator = normalize_expr_for_solver(cx, denominator.clone());
1838 let zero = Self::zero(cx);
1839 let denominator_nonzero = SymBoolExpr::eq(cx, denominator.clone(), zero).not(cx);
1840 let at_least_denominator = SymBoolExpr::cmp(cx, SymCmpOp::Uge, numerator, denominator);
1841 SymBoolExpr::and(cx, vec![denominator_nonzero, at_least_denominator])
1842 }
1843}
1844
1845impl ConstraintContext {
1846 fn word_bool_always_true(&self, cx: &mut SymCx, expr: &SymExpr) -> bool {
1847 let mut terms = Vec::new();
1848 expr.push_or_terms(&mut terms);
1849 if terms.len() <= 1 {
1850 return false;
1851 }
1852
1853 let bool_terms = terms
1854 .iter()
1855 .filter_map(|term| term.normalized_bool_word_condition(cx))
1856 .collect::<Vec<_>>();
1857 if bool_terms.iter().any(|term| {
1858 let negated = term.clone().not(cx);
1859 bool_terms.contains(&negated)
1860 }) {
1861 return true;
1863 }
1864 for zero_term in &bool_terms {
1865 if bool_terms
1866 .iter()
1867 .any(|term| self.checked_mul_guard_for_zero_condition(term, zero_term))
1868 {
1869 return true;
1871 }
1872 }
1873 false
1874 }
1875
1876 fn record_scaled_zero_fact(&mut self, cx: &mut SymCx, constraint: &SymBoolExpr) {
1878 let (condition, nonzero) = match constraint.kind() {
1879 SymBoolExprKind::Not(inner) => (inner, true),
1880 _ => (constraint, false),
1881 };
1882 if matches!(condition.kind(), SymBoolExprKind::Cmp(SymCmpOp::Ult, _, _))
1883 && let Some(value) = self.bounded_zero_check_operand(condition).cloned()
1884 {
1885 if nonzero {
1886 self.record_lower_bound(value, U256::ONE);
1887 } else {
1888 self.record_upper_bound(value.clone(), U256::ZERO);
1889 let zero = SymExpr::zero(cx);
1890 let exact = SymBoolExpr::eq(cx, value, zero);
1891 self.record_exact_value_constraint(&exact);
1892 }
1893 }
1894 }
1895
1896 fn bounded_zero_check_operand<'a>(&self, expr: &'a SymBoolExpr) -> Option<&'a SymExpr> {
1898 if let Some(value) = expr.zero_check_operand() {
1899 return Some(value);
1900 }
1901 let SymBoolExprKind::Cmp(SymCmpOp::Ult, product, limit) = expr.kind() else {
1902 return None;
1903 };
1904 let (value, scale) = Self::constant_mul_operands(product)?;
1905 if scale.is_zero() || limit.as_const() != Some(scale) {
1907 return None;
1908 }
1909 self.max_scaled_product(value, scale)?;
1910 Some(value)
1911 }
1912
1913 fn zero_check_for_operand(&self, condition: &SymBoolExpr, operand: &SymExpr) -> bool {
1915 if self.bounded_zero_check_operand(condition) == Some(operand) {
1916 return true;
1917 }
1918 if let Some((numerator, denominator)) = operand.udiv_operands()
1921 && denominator.as_const().is_some_and(|d| !d.is_zero())
1922 && let SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right) = condition.kind()
1923 {
1924 return left == numerator && right == denominator;
1925 }
1926 false
1927 }
1928
1929 fn checked_mul_guard_for_zero_condition(
1930 &self,
1931 expr: &SymBoolExpr,
1932 zero_condition: &SymBoolExpr,
1933 ) -> bool {
1934 let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = expr.kind() else {
1935 return false;
1936 };
1937 [(left, right), (right, left)].into_iter().any(|(quotient, expected)| {
1938 matches!(quotient.kind(), SymExprKind::Ite(_, _, _))
1939 && self
1940 .checked_quotient_factors(quotient, expected, Some(zero_condition))
1941 .is_some_and(|(left, right)| self.mul_cannot_overflow_256(left, right))
1942 })
1943 }
1944
1945 fn checked_quotient_factors<'a>(
1947 &self,
1948 quotient: &'a SymExpr,
1949 expected: &SymExpr,
1950 zero_condition: Option<&SymBoolExpr>,
1951 ) -> Option<(&'a SymExpr, &'a SymExpr)> {
1952 let (quotient, branch_condition) =
1953 if let SymExprKind::Ite(condition, zero, quotient) = quotient.kind() {
1954 if zero.as_const() != Some(U256::ZERO) || zero_condition.is_none() {
1955 return None;
1956 }
1957 (quotient, Some(condition))
1958 } else {
1959 (quotient, None)
1960 };
1961 let (divisor, other) = Self::mul_div_identity_operands(quotient, expected)?;
1962 (zero_condition.is_none_or(|condition| self.zero_check_for_operand(condition, divisor))
1963 && branch_condition
1964 .is_none_or(|condition| self.zero_check_for_operand(condition, divisor)))
1965 .then_some((divisor, other))
1966 }
1967
1968 fn record_non_wrapping_product(&mut self, cx: &mut SymCx, constraint: &SymBoolExpr) -> bool {
1970 let fact = bitwise_bool_word_fact(cx, constraint).unwrap_or_else(|| constraint.clone());
1971 let factors = if let Some(factors) = self.checked_product_factors(&fact, None) {
1972 Some(factors)
1973 } else if let SymBoolExprKind::Not(inner) = fact.kind()
1974 && let SymBoolExprKind::And(terms) = inner.kind()
1975 && terms.len() == 2
1976 {
1977 let first = terms[0].clone().not(cx);
1980 let second = terms[1].clone().not(cx);
1981 self.checked_product_factors(&second, Some(&first))
1982 .or_else(|| self.checked_product_factors(&first, Some(&second)))
1983 } else {
1984 None
1985 };
1986 if let Some((left, right)) = factors {
1987 let mut changed = false;
1991 for (value, factor) in [(&left, &right), (&right, &left)] {
1992 if let Some(range) = self.interval(factor)
1993 && !range.min.is_zero()
1994 {
1995 changed |= self.record_upper_bound(value.clone(), U256::MAX / range.min);
1996 }
1997 }
1998 self.non_wrapping_products.insert((left, right)) || changed
1999 } else {
2000 false
2001 }
2002 }
2003
2004 fn checked_product_factors(
2005 &self,
2006 predicate: &SymBoolExpr,
2007 zero_condition: Option<&SymBoolExpr>,
2008 ) -> Option<(SymExpr, SymExpr)> {
2009 let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = predicate.kind() else {
2010 return None;
2011 };
2012 for (quotient, expected) in [(left, right), (right, left)] {
2013 if let Some((divisor, other)) =
2014 self.checked_quotient_factors(quotient, expected, zero_condition)
2015 {
2016 return Some((divisor.clone(), other.clone()));
2019 }
2020 }
2021 None
2022 }
2023
2024 fn has_non_wrapping_product(&self, left: &SymExpr, right: &SymExpr) -> bool {
2025 self.non_wrapping_products.contains(&(left.clone(), right.clone()))
2026 || self.non_wrapping_products.contains(&(right.clone(), left.clone()))
2027 }
2028
2029 pub(super) fn mul_cannot_overflow_256(&self, left: &SymExpr, right: &SymExpr) -> bool {
2030 if self.has_non_wrapping_product(left, right) {
2031 return true;
2032 }
2033 let mut intervals = HashMap::default();
2034 let mut remaining = MAX_LOCAL_ANALYSIS_NODES;
2035 if self
2036 .interval_cached(left, &mut intervals, &mut remaining)
2037 .zip(self.interval_cached(right, &mut intervals, &mut remaining))
2038 .is_some_and(|(left, right)| left.max.checked_mul(right.max).is_some())
2039 {
2040 return true;
2041 }
2042
2043 let mut bit_widths = HashMap::default();
2044 let mut remaining = MAX_LOCAL_ANALYSIS_NODES;
2045 self.unsigned_bits_cached(left, &mut bit_widths, &mut remaining)
2046 .zip(self.unsigned_bits_cached(right, &mut bit_widths, &mut remaining))
2047 .is_some_and(|(left, right)| left.saturating_add(right) <= 256)
2048 }
2049
2050 pub(super) fn unsigned_bits(&self, expr: &SymExpr) -> usize {
2051 let mut bit_widths = HashMap::default();
2052 let mut remaining = MAX_LOCAL_ANALYSIS_NODES;
2053 self.unsigned_bits_cached(expr, &mut bit_widths, &mut remaining).unwrap_or(256)
2054 }
2055
2056 fn unsigned_bits_cached(
2057 &self,
2058 expr: &SymExpr,
2059 bit_widths: &mut HashMap<SymExpr, usize>,
2060 remaining: &mut usize,
2061 ) -> Option<usize> {
2062 if let Some(bits) = bit_widths.get(expr) {
2063 return Some(*bits);
2064 }
2065 if *remaining == 0 {
2066 return None;
2067 }
2068 *remaining -= 1;
2069
2070 let bits = match expr.kind() {
2071 SymExprKind::Const(value) => value.bit_len().max(1),
2072 SymExprKind::Var(_)
2073 | SymExprKind::GasLeft(_)
2074 | SymExprKind::Keccak { .. }
2075 | SymExprKind::Hash { .. }
2076 | SymExprKind::Not(_) => 256,
2077 SymExprKind::BinOp(SymBinOp::And, left, right) => {
2078 if let Some(mask) = right.as_const() {
2079 self.unsigned_bits_cached(left, bit_widths, remaining)?.min(mask.bit_len())
2080 } else {
2081 256
2082 }
2083 }
2084 SymExprKind::BinOp(SymBinOp::Add, left, right) => self
2085 .unsigned_bits_cached(left, bit_widths, remaining)?
2086 .max(self.unsigned_bits_cached(right, bit_widths, remaining)?)
2087 .saturating_add(1)
2088 .min(256),
2089 SymExprKind::BinOp(SymBinOp::Mul, left, right) => self
2090 .unsigned_bits_cached(left, bit_widths, remaining)?
2091 .saturating_add(self.unsigned_bits_cached(right, bit_widths, remaining)?)
2092 .min(256),
2093 SymExprKind::BinOp(SymBinOp::UDiv, left, _) => {
2094 self.unsigned_bits_cached(left, bit_widths, remaining)?
2095 }
2096 SymExprKind::Ite(_, left, right) => self
2097 .unsigned_bits_cached(left, bit_widths, remaining)?
2098 .max(self.unsigned_bits_cached(right, bit_widths, remaining)?),
2099 _ => 256,
2100 };
2101
2102 let bits =
2103 self.upper_bound(expr).map(|bound| bits.min(bound.bit_len().max(1))).unwrap_or(bits);
2104 bit_widths.insert(expr.clone(), bits);
2105 Some(bits)
2106 }
2107}
2108
2109#[cfg(test)]
2110mod tests;