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