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