1use super::*;
2
3pub(crate) fn normalize_constraints_for_solver(
5 cx: &mut SymCx,
6 constraints: &[SymBoolExpr],
7) -> Vec<SymBoolExpr> {
8 let normalized = normalize_constraint_batch(
9 constraints.iter().cloned().map(|constraint| normalize_bool_for_solver(cx, constraint)),
10 constraints.len(),
11 );
12 if matches!(normalized.as_slice(), [expr] if expr.as_const() == Some(false)) {
13 return normalized;
14 }
15
16 let context = ConstraintContext::new(&normalized);
17 let normalized_len = normalized.len();
18 normalize_constraint_batch(
19 normalized.into_iter().map(|constraint| context.normalize_bool(cx, constraint)),
20 normalized_len,
21 )
22}
23
24fn normalize_constraint_batch(
25 constraints: impl IntoIterator<Item = SymBoolExpr>,
26 capacity: usize,
27) -> Vec<SymBoolExpr> {
28 let mut normalized = Vec::with_capacity(capacity);
29 for constraint in constraints {
30 if constraint.as_const() == Some(false) {
31 return vec![constraint];
32 }
33 constraint.push_normalized_conjuncts(&mut normalized);
34 }
35 sort_dedup_bool_exprs(&mut normalized);
36 normalized
37}
38
39fn sort_dedup_bool_exprs(exprs: &mut Vec<SymBoolExpr>) {
40 exprs.sort_by_cached_key(bool_structural_key);
41 exprs.dedup();
42}
43
44fn bool_structural_key(expr: &SymBoolExpr) -> String {
45 let mut key = String::new();
46 write_bool_structural_key(&mut key, expr);
47 key
48}
49
50fn write_bool_structural_key(out: &mut String, expr: &SymBoolExpr) {
51 match expr.kind() {
52 SymBoolExprKind::Const(value) => {
53 let _ = write!(out, "0:{value}");
54 }
55 SymBoolExprKind::Not(value) => {
56 out.push_str("1:");
57 write_bool_structural_key(out, value);
58 }
59 SymBoolExprKind::And(values) => {
60 let _ = write!(out, "2:{}:", values.len());
61 for value in values.iter() {
62 write_bool_structural_key(out, value);
63 out.push(';');
64 }
65 }
66 SymBoolExprKind::Cmp(op, left, right) => {
67 let _ = write!(out, "3:{}:", cmp_op_key(*op));
68 write_expr_structural_key(out, left);
69 out.push(':');
70 write_expr_structural_key(out, right);
71 }
72 }
73}
74
75fn write_expr_structural_key(out: &mut String, expr: &SymExpr) {
76 match expr.kind() {
77 SymExprKind::Const(value) => {
78 let _ = write!(out, "0:{value:064x}");
79 }
80 SymExprKind::Var(name) => {
81 let _ = write!(out, "1:{}", name.id());
82 }
83 SymExprKind::GasLeft(symbol) => {
84 let _ = write!(out, "2:{}", symbol.id());
85 }
86 SymExprKind::Keccak { name, len, bytes } => {
87 let _ = write!(out, "3:{}:", name.id());
88 write_expr_structural_key(out, len);
89 write_exprs_structural_key(out, bytes);
90 }
91 SymExprKind::Hash { name, algorithm, bytes } => {
92 let _ = write!(out, "4:{}:{algorithm}:", name.id());
93 write_exprs_structural_key(out, bytes);
94 }
95 SymExprKind::Not(value) => {
96 out.push_str("5:");
97 write_expr_structural_key(out, value);
98 }
99 SymExprKind::BinOp(op, left, right) => {
100 let _ = write!(out, "6:{}:", expr_binop_key(*op));
101 write_expr_structural_key(out, left);
102 out.push(':');
103 write_expr_structural_key(out, right);
104 }
105 SymExprKind::TernOp(op, left, right, modulus) => {
106 let _ = write!(out, "7:{}:", expr_ternop_key(*op));
107 write_expr_structural_key(out, left);
108 out.push(':');
109 write_expr_structural_key(out, right);
110 out.push(':');
111 write_expr_structural_key(out, modulus);
112 }
113 SymExprKind::Ite(condition, then_expr, else_expr) => {
114 out.push_str("9:");
115 write_bool_structural_key(out, condition);
116 out.push(':');
117 write_expr_structural_key(out, then_expr);
118 out.push(':');
119 write_expr_structural_key(out, else_expr);
120 }
121 }
122}
123
124fn write_exprs_structural_key(out: &mut String, exprs: &[SymExpr]) {
125 let _ = write!(out, "{}:", exprs.len());
126 for expr in exprs {
127 write_expr_structural_key(out, expr);
128 out.push(';');
129 }
130}
131
132const fn cmp_op_key(op: SymCmpOp) -> u8 {
133 match op {
134 SymCmpOp::Eq => 0,
135 SymCmpOp::Ult => 1,
136 SymCmpOp::Ugt => 2,
137 SymCmpOp::Ule => 3,
138 SymCmpOp::Uge => 4,
139 SymCmpOp::Slt => 5,
140 SymCmpOp::Sgt => 6,
141 }
142}
143
144const fn expr_binop_key(op: SymBinOp) -> u8 {
145 match op {
146 SymBinOp::Add => 0,
147 SymBinOp::Sub => 1,
148 SymBinOp::Mul => 2,
149 SymBinOp::UDiv => 3,
150 SymBinOp::URem => 4,
151 SymBinOp::SDiv => 5,
152 SymBinOp::SRem => 6,
153 SymBinOp::And => 7,
154 SymBinOp::Or => 8,
155 SymBinOp::Xor => 9,
156 SymBinOp::Shl => 10,
157 SymBinOp::Shr => 11,
158 SymBinOp::Sar => 12,
159 }
160}
161
162const fn expr_ternop_key(op: SymTernOp) -> u8 {
163 match op {
164 SymTernOp::AddMod => 0,
165 SymTernOp::MulMod => 1,
166 }
167}
168
169pub(super) fn constraints_are_directly_unsat(cx: &mut SymCx, constraints: &[SymBoolExpr]) -> bool {
171 constraints.iter().any(|constraint| match constraint.kind() {
172 SymBoolExprKind::Const(false) => true,
173 SymBoolExprKind::Not(inner) => constraints.contains(inner),
174 _ => {
175 let negated = constraint.clone().not(cx);
176 constraints.contains(&negated)
177 }
178 })
179}
180
181pub(super) fn sorted_bool_exprs_are_subset(
183 subset: &[SymBoolExpr],
184 superset: &[SymBoolExpr],
185) -> bool {
186 if subset.len() > superset.len() {
187 return false;
188 }
189
190 let superset: HashSet<_> = superset.iter().collect();
191 subset.iter().all(|expected| superset.contains(expected))
192}
193
194pub(crate) fn normalize_bool_for_solver(cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
196 expr.fold(cx, &mut normalize_bool_node_for_solver)
197}
198
199impl SymBoolExpr {
200 fn push_normalized_conjuncts(self, out: &mut Vec<Self>) {
201 match self.kind() {
202 SymBoolExprKind::Const(true) => {}
203 SymBoolExprKind::And(values) => {
204 for value in values.iter().cloned() {
205 value.push_normalized_conjuncts(out);
206 }
207 }
208 _ => out.push(self),
209 }
210 }
211}
212
213pub(super) fn write_smt_assertions(
214 cx: &SymCx,
215 out: &mut String,
216 constraints: &[SymBoolExpr],
217) -> Result<(), SymbolicError> {
218 if constraints.is_empty() {
219 return Ok(());
220 }
221 if constraints.iter().any(SymBoolExpr::contains_gasleft) {
222 return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
223 }
224
225 let plan = SmtCsePlan::new(constraints);
226 if plan.bindings.is_empty() {
227 for constraint in constraints {
228 let _ = writeln!(out, "(assert {})", constraint.smt(cx));
229 }
230 return Ok(());
231 }
232
233 let writer = SmtCseWriter { cx, plan: &plan };
234 for (idx, binding) in plan.bindings.iter().enumerate() {
241 out.push_str("(define-fun ");
242 binding.write_definition_header(out, idx);
243 match binding {
244 SmtBinding::Expr(expr) => writer.write_expr(out, expr, Some(idx), None),
245 SmtBinding::Bool(expr) => writer.write_bool(out, expr, None, Some(idx)),
246 }
247 out.push_str(")\n");
248 }
249 for constraint in constraints {
250 out.push_str("(assert ");
251 writer.write_bool(out, constraint, None, None);
252 out.push_str(")\n");
253 }
254 Ok(())
255}
256
257#[derive(Default)]
258struct SmtCseVisit {
259 count: usize,
260 binding: Option<usize>,
261 collected: bool,
262}
263
264struct SmtCsePlan {
265 expr_visits: HashMap<SymExpr, SmtCseVisit>,
266 bool_visits: HashMap<SymBoolExpr, SmtCseVisit>,
267 bindings: Vec<SmtBinding>,
268}
269
270impl SmtCsePlan {
271 fn new(constraints: &[SymBoolExpr]) -> Self {
272 let mut plan = Self {
273 expr_visits: HashMap::default(),
274 bool_visits: HashMap::default(),
275 bindings: Vec::new(),
276 };
277 for constraint in constraints {
278 plan.count_bool(constraint);
279 }
280 for constraint in constraints {
281 plan.collect_bool_binding(constraint);
282 }
283 plan
284 }
285
286 fn count_expr(&mut self, expr: &SymExpr) {
287 let visit = self.expr_visits.entry(expr.clone()).or_default();
288 visit.count += 1;
289 if visit.count != 1 {
290 return;
291 }
292 match expr.kind() {
293 SymExprKind::Const(_)
294 | SymExprKind::Var(_)
295 | SymExprKind::GasLeft(_)
296 | SymExprKind::Keccak { .. }
297 | SymExprKind::Hash { .. } => {}
298 SymExprKind::Not(value) => self.count_expr(value),
299 SymExprKind::BinOp(_, left, right) => {
300 self.count_expr(left);
301 self.count_expr(right);
302 }
303 SymExprKind::TernOp(_, left, right, modulus) => {
304 self.count_expr(modulus);
305 self.count_expr(left);
306 self.count_expr(right);
307 self.count_expr(modulus);
308 }
309 SymExprKind::Ite(cond, left, right) => {
310 self.count_bool(cond);
311 self.count_expr(left);
312 self.count_expr(right);
313 }
314 }
315 }
316
317 fn count_bool(&mut self, expr: &SymBoolExpr) {
318 let visit = self.bool_visits.entry(expr.clone()).or_default();
319 visit.count += 1;
320 if visit.count != 1 {
321 return;
322 }
323 match expr.kind() {
324 SymBoolExprKind::Const(_) => {}
325 SymBoolExprKind::Not(value) => self.count_bool(value),
326 SymBoolExprKind::And(values) => {
327 for value in values.iter() {
328 self.count_bool(value);
329 }
330 }
331 SymBoolExprKind::Cmp(_, left, right) => {
332 self.count_expr(left);
333 self.count_expr(right);
334 }
335 }
336 }
337
338 fn collect_expr_binding(&mut self, expr: &SymExpr) {
339 {
340 let Some(visit) = self.expr_visits.get_mut(expr) else { return };
341 if visit.collected {
342 return;
343 }
344 visit.collected = true;
345 }
346 match expr.kind() {
347 SymExprKind::Const(_)
348 | SymExprKind::Var(_)
349 | SymExprKind::GasLeft(_)
350 | SymExprKind::Keccak { .. }
351 | SymExprKind::Hash { .. } => {}
352 SymExprKind::Not(value) => self.collect_expr_binding(value),
353 SymExprKind::BinOp(_, left, right) => {
354 self.collect_expr_binding(left);
355 self.collect_expr_binding(right);
356 }
357 SymExprKind::TernOp(_, left, right, modulus) => {
358 self.collect_expr_binding(modulus);
359 self.collect_expr_binding(left);
360 self.collect_expr_binding(right);
361 }
362 SymExprKind::Ite(cond, left, right) => {
363 self.collect_bool_binding(cond);
364 self.collect_expr_binding(left);
365 self.collect_expr_binding(right);
366 }
367 }
368 self.bind_expr(expr);
369 }
370
371 fn collect_bool_binding(&mut self, expr: &SymBoolExpr) {
372 {
373 let Some(visit) = self.bool_visits.get_mut(expr) else { return };
374 if visit.collected {
375 return;
376 }
377 visit.collected = true;
378 }
379 match expr.kind() {
380 SymBoolExprKind::Const(_) => {}
381 SymBoolExprKind::Not(value) => self.collect_bool_binding(value),
382 SymBoolExprKind::And(values) => {
383 for value in values.iter() {
384 self.collect_bool_binding(value);
385 }
386 }
387 SymBoolExprKind::Cmp(_, left, right) => {
388 self.collect_expr_binding(left);
389 self.collect_expr_binding(right);
390 }
391 }
392 self.bind_bool(expr);
393 }
394
395 fn bind_expr(&mut self, expr: &SymExpr) {
396 let Some(visit) = self.expr_visits.get_mut(expr) else { return };
397 if visit.count <= 1 || visit.binding.is_some() || !Self::expr_can_bind(expr) {
398 return;
399 }
400 let idx = self.bindings.len();
401 visit.binding = Some(idx);
402 self.bindings.push(SmtBinding::Expr(expr.clone()));
403 }
404
405 fn bind_bool(&mut self, expr: &SymBoolExpr) {
406 let Some(visit) = self.bool_visits.get_mut(expr) else { return };
407 if visit.count <= 1 || visit.binding.is_some() || !Self::bool_can_bind(expr) {
408 return;
409 }
410 let idx = self.bindings.len();
411 visit.binding = Some(idx);
412 self.bindings.push(SmtBinding::Bool(expr.clone()));
413 }
414
415 fn expr_binding(&self, expr: &SymExpr) -> Option<usize> {
416 self.expr_visits.get(expr).and_then(|visit| visit.binding)
417 }
418
419 fn bool_binding(&self, expr: &SymBoolExpr) -> Option<usize> {
420 self.bool_visits.get(expr).and_then(|visit| visit.binding)
421 }
422
423 fn expr_can_bind(expr: &SymExpr) -> bool {
424 !matches!(
425 expr.kind(),
426 SymExprKind::Const(_)
427 | SymExprKind::Var(_)
428 | SymExprKind::GasLeft(_)
429 | SymExprKind::Keccak { .. }
430 | SymExprKind::Hash { .. }
431 )
432 }
433
434 fn bool_can_bind(expr: &SymBoolExpr) -> bool {
435 !matches!(expr.kind(), SymBoolExprKind::Const(_))
436 }
437}
438
439enum SmtBinding {
440 Expr(SymExpr),
441 Bool(SymBoolExpr),
442}
443
444impl SmtBinding {
445 fn write_definition_header(&self, out: &mut String, idx: usize) {
446 match self {
447 Self::Expr(_) => {
448 Self::write_expr_name(out, idx);
449 out.push_str(" () (_ BitVec 256) ");
450 }
451 Self::Bool(_) => {
452 Self::write_bool_name(out, idx);
453 out.push_str(" () Bool ");
454 }
455 }
456 }
457
458 fn write_expr_name(out: &mut String, idx: usize) {
459 let _ = write!(out, "__sym_expr_{idx}");
460 }
461
462 fn write_bool_name(out: &mut String, idx: usize) {
463 let _ = write!(out, "__sym_bool_{idx}");
464 }
465}
466
467struct SmtCseWriter<'a> {
468 cx: &'a SymCx,
469 plan: &'a SmtCsePlan,
470}
471
472impl SmtCseWriter<'_> {
473 fn write_expr(
474 &self,
475 out: &mut String,
476 expr: &SymExpr,
477 skip_expr: Option<usize>,
478 skip_bool: Option<usize>,
479 ) {
480 if let Some(idx) = self.plan.expr_binding(expr)
481 && Some(idx) != skip_expr
482 {
483 SmtBinding::write_expr_name(out, idx);
484 return;
485 }
486
487 match expr.kind() {
488 SymExprKind::Const(value) => {
489 let _ = write!(out, "(_ bv{value} 256)");
490 }
491 SymExprKind::Var(symbol)
492 | SymExprKind::GasLeft(symbol)
493 | SymExprKind::Keccak { name: symbol, .. }
494 | SymExprKind::Hash { name: symbol, .. } => out.push_str(self.cx.symbol_name(*symbol)),
495 SymExprKind::Not(value) => {
496 out.push_str("(bvnot ");
497 self.write_expr(out, value, skip_expr, skip_bool);
498 out.push(')');
499 }
500 SymExprKind::BinOp(op, left, right) => {
501 let _ = write!(out, "({} ", op.smt());
502 self.write_expr(out, left, skip_expr, skip_bool);
503 out.push(' ');
504 self.write_expr(out, right, skip_expr, skip_bool);
505 out.push(')');
506 }
507 SymExprKind::TernOp(op, left, right, modulus) => {
508 self.write_wide_modular_arithmetic(out, op.smt(), left, right, modulus);
509 }
510 SymExprKind::Ite(cond, left, right) => {
511 out.push_str("(ite ");
512 self.write_bool(out, cond, skip_expr, skip_bool);
513 out.push(' ');
514 self.write_expr(out, left, skip_expr, skip_bool);
515 out.push(' ');
516 self.write_expr(out, right, skip_expr, skip_bool);
517 out.push(')');
518 }
519 }
520 }
521
522 fn write_wide_modular_arithmetic(
523 &self,
524 out: &mut String,
525 op: &'static str,
526 left: &SymExpr,
527 right: &SymExpr,
528 modulus: &SymExpr,
529 ) {
530 out.push_str("(ite (= ");
535 self.write_expr(out, modulus, None, None);
536 out.push_str(" (_ bv0 256)) (_ bv0 256) ((_ extract 255 0) (bvurem (");
537 out.push_str(op);
538 out.push_str(" ((_ zero_extend 256) ");
539 self.write_expr(out, left, None, None);
540 out.push_str(") ((_ zero_extend 256) ");
541 self.write_expr(out, right, None, None);
542 out.push_str(")) ((_ zero_extend 256) ");
543 self.write_expr(out, modulus, None, None);
544 out.push_str("))))");
545 }
546
547 fn write_bool(
548 &self,
549 out: &mut String,
550 expr: &SymBoolExpr,
551 skip_expr: Option<usize>,
552 skip_bool: Option<usize>,
553 ) {
554 if let Some(idx) = self.plan.bool_binding(expr)
555 && Some(idx) != skip_bool
556 {
557 SmtBinding::write_bool_name(out, idx);
558 return;
559 }
560
561 match expr.kind() {
562 SymBoolExprKind::Const(value) => out.push_str(if *value { "true" } else { "false" }),
563 SymBoolExprKind::Not(value) => {
564 out.push_str("(not ");
565 self.write_bool(out, value, skip_expr, skip_bool);
566 out.push(')');
567 }
568 SymBoolExprKind::And(values) => {
569 out.push_str("(and");
570 for value in values.iter() {
571 out.push(' ');
572 self.write_bool(out, value, skip_expr, skip_bool);
573 }
574 out.push(')');
575 }
576 SymBoolExprKind::Cmp(op, left, right) => {
577 let _ = write!(out, "({} ", op.smt());
578 self.write_expr(out, left, skip_expr, skip_bool);
579 out.push(' ');
580 self.write_expr(out, right, skip_expr, skip_bool);
581 out.push(')');
582 }
583 }
584 }
585}
586
587fn normalize_bool_node_for_solver(cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
588 if let Some(normalized) = expr.normalize_udiv_for_solver(cx) {
589 return normalized;
590 }
591
592 match expr.kind() {
593 SymBoolExprKind::Cmp(op, left, right) => {
594 let left = normalize_expr_for_solver(cx, left.clone());
595 let right = normalize_expr_for_solver(cx, right.clone());
596 let normalized = normalize_cmp_for_solver(cx, *op, left, right);
597 normalized.normalize_udiv_for_solver(cx).unwrap_or(normalized)
598 }
599 _ => expr,
600 }
601}
602
603fn normalize_cmp_for_solver(
604 cx: &mut SymCx,
605 op: SymCmpOp,
606 left: SymExpr,
607 right: SymExpr,
608) -> SymBoolExpr {
609 match op {
610 SymCmpOp::Ugt => SymBoolExpr::cmp(cx, SymCmpOp::Ult, right, left),
612 SymCmpOp::Uge => SymBoolExpr::cmp(cx, SymCmpOp::Ule, right, left),
614 SymCmpOp::Sgt => SymBoolExpr::cmp(cx, SymCmpOp::Slt, right, left),
616 SymCmpOp::Eq | SymCmpOp::Ult | SymCmpOp::Ule | SymCmpOp::Slt => {
617 SymBoolExpr::cmp(cx, op, left, right)
618 }
619 }
620}
621
622#[derive(Default)]
624pub(super) struct ConstraintContext {
625 upper_bounds: HashMap<SymExpr, U256>,
626 lower_bounds: HashMap<SymExpr, U256>,
627}
628
629#[derive(Clone, Copy)]
630struct WordInterval {
631 min: U256,
632 max: U256,
633}
634
635impl WordInterval {
636 fn new(min: U256, max: U256) -> Option<Self> {
637 (min <= max).then_some(Self { min, max })
638 }
639
640 const fn exact(value: U256) -> Self {
641 Self { min: value, max: value }
642 }
643
644 fn with_bounds(self, lower: Option<U256>, upper: Option<U256>) -> Option<Self> {
645 Self::new(
646 self.min.max(lower.unwrap_or(U256::ZERO)),
647 self.max.min(upper.unwrap_or(U256::MAX)),
648 )
649 }
650}
651
652impl ConstraintContext {
653 pub(super) fn new(constraints: &[SymBoolExpr]) -> Self {
654 let mut context = Self::default();
655 for constraint in constraints {
656 context.record_upper_bound_constraint(constraint);
657 context.record_lower_bound_constraint(constraint);
658 }
659 for _ in 0..constraints.len() {
663 let mut changed = false;
664 for constraint in constraints {
665 changed |= context.propagate_order_bounds(constraint);
666 }
667 if !changed {
668 break;
669 }
670 }
671 context
672 }
673
674 fn upper_bound(&self, expr: &SymExpr) -> Option<U256> {
675 self.upper_bounds.get(expr).copied()
676 }
677
678 fn lower_bound(&self, expr: &SymExpr) -> Option<U256> {
679 self.lower_bounds.get(expr).copied()
680 }
681
682 fn normalize_bool(&self, cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
683 match expr.kind() {
684 SymBoolExprKind::Not(value) if self.unsigned_bool_always_true(value) => {
685 SymBoolExpr::constant(cx, false)
686 }
687 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
688 if self.masked_word_eq_self(left, right) =>
689 {
690 SymBoolExpr::constant(cx, true)
692 }
693 SymBoolExprKind::Not(value) if self.masked_eq_self_condition(value) => {
694 SymBoolExpr::constant(cx, false)
696 }
697 _ if expr
698 .zero_check_operand()
699 .is_some_and(|left| self.word_bool_always_true(cx, left)) =>
700 {
701 SymBoolExpr::constant(cx, false)
703 }
704 SymBoolExprKind::Not(value)
705 if value
706 .zero_check_operand()
707 .is_some_and(|left| self.word_bool_always_true(cx, left)) =>
708 {
709 SymBoolExpr::constant(cx, true)
711 }
712 _ => expr,
713 }
714 }
715
716 fn masked_eq_self_condition(&self, expr: &SymBoolExpr) -> bool {
717 match expr.kind() {
718 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
719 self.masked_word_eq_self(left, right)
720 }
721 _ => false,
722 }
723 }
724
725 fn masked_word_eq_self(&self, left: &SymExpr, right: &SymExpr) -> bool {
726 self.masked_word_side_eq_self(left, right) || self.masked_word_side_eq_self(right, left)
727 }
728
729 fn masked_word_side_eq_self(&self, masked: &SymExpr, value: &SymExpr) -> bool {
730 let SymExprKind::BinOp(SymBinOp::And, left, right) = masked.kind() else { return false };
731 let Some((source, mask)) = right
732 .as_const()
733 .map(|mask| (left, mask))
734 .or_else(|| left.as_const().map(|mask| (right, mask)))
735 else {
736 return false;
737 };
738 let Some(bits) = mask_low_bits(mask) else { return false };
739 source == value && self.unsigned_bits(value) <= bits
740 }
741
742 fn record_upper_bound_constraint(&mut self, constraint: &SymBoolExpr) {
743 if let Some((expr, bound)) = self.upper_bound_constraint(constraint) {
744 self.record_upper_bound(expr.clone(), bound);
745 }
746 }
747
748 fn record_upper_bound(&mut self, expr: SymExpr, bound: U256) -> bool {
749 match self.upper_bounds.entry(expr) {
750 alloy_primitives::map::Entry::Occupied(mut entry) if bound < *entry.get() => {
751 entry.insert(bound);
752 true
753 }
754 alloy_primitives::map::Entry::Vacant(entry) => {
755 entry.insert(bound);
756 true
757 }
758 alloy_primitives::map::Entry::Occupied(_) => false,
759 }
760 }
761
762 fn record_lower_bound_constraint(&mut self, constraint: &SymBoolExpr) {
763 if let Some((expr, bound)) = self.lower_bound_constraint(constraint) {
764 self.record_lower_bound(expr.clone(), bound);
765 }
766 }
767
768 fn record_lower_bound(&mut self, expr: SymExpr, bound: U256) -> bool {
769 match self.lower_bounds.entry(expr) {
770 alloy_primitives::map::Entry::Occupied(mut entry) if bound > *entry.get() => {
771 entry.insert(bound);
772 true
773 }
774 alloy_primitives::map::Entry::Vacant(entry) => {
775 entry.insert(bound);
776 true
777 }
778 alloy_primitives::map::Entry::Occupied(_) => false,
779 }
780 }
781
782 fn propagate_order_bounds(&mut self, constraint: &SymBoolExpr) -> bool {
783 match constraint.kind() {
784 SymBoolExprKind::Cmp(op, left, right) => match op {
785 SymCmpOp::Ult | SymCmpOp::Ule => self.propagate_less_or_equal_bounds(left, right),
786 SymCmpOp::Ugt | SymCmpOp::Uge => self.propagate_less_or_equal_bounds(right, left),
787 SymCmpOp::Eq => {
788 let changed = self.propagate_less_or_equal_bounds(left, right);
789 self.propagate_less_or_equal_bounds(right, left) || changed
790 }
791 SymCmpOp::Slt | SymCmpOp::Sgt => false,
792 },
793 SymBoolExprKind::Not(value) => match value.kind() {
794 SymBoolExprKind::Cmp(op, left, right) => match op {
795 SymCmpOp::Ult | SymCmpOp::Ule => {
796 self.propagate_less_or_equal_bounds(right, left)
797 }
798 SymCmpOp::Ugt | SymCmpOp::Uge => {
799 self.propagate_less_or_equal_bounds(left, right)
800 }
801 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => false,
802 },
803 _ => false,
804 },
805 SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => false,
806 }
807 }
808
809 fn propagate_less_or_equal_bounds(&mut self, left: &SymExpr, right: &SymExpr) -> bool {
811 let upper = self.upper_bound(right);
812 let lower = self.lower_bound(left);
813 let upper_changed = upper.is_some_and(|bound| self.record_upper_bound(left.clone(), bound));
814 let lower_changed =
815 lower.is_some_and(|bound| self.record_lower_bound(right.clone(), bound));
816 upper_changed || lower_changed
817 }
818
819 fn upper_bound_constraint<'a>(
820 &self,
821 constraint: &'a SymBoolExpr,
822 ) -> Option<(&'a SymExpr, U256)> {
823 match constraint.kind() {
824 SymBoolExprKind::Cmp(op, left, right) => match *op {
825 SymCmpOp::Eq => const_side_bound(left, right),
826 SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
827 (_, Some(bound)) => (!bound.is_zero()).then(|| (left, bound - U256::from(1))),
828 _ => None,
829 },
830 SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
831 (_, Some(bound)) => Some((left, bound)),
832 _ => None,
833 },
834 SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
835 (Some(bound), _) => (!bound.is_zero()).then(|| (right, bound - U256::from(1))),
836 _ => None,
837 },
838 SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
839 (Some(bound), _) => Some((right, bound)),
840 _ => None,
841 },
842 SymCmpOp::Slt | SymCmpOp::Sgt => None,
843 },
844 SymBoolExprKind::Not(value) => match value.kind() {
845 SymBoolExprKind::Cmp(op, left, right) => match *op {
846 SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
847 (_, Some(bound)) => Some((left, bound)),
848 _ => None,
849 },
850 SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
851 (_, Some(bound)) => {
852 (!bound.is_zero()).then(|| (left, bound - U256::from(1)))
853 }
854 _ => None,
855 },
856 SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
857 (Some(bound), _) => Some((right, bound)),
858 _ => None,
859 },
860 SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
861 (Some(bound), _) => {
862 (!bound.is_zero()).then(|| (right, bound - U256::from(1)))
863 }
864 _ => None,
865 },
866 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
867 },
868 _ => None,
869 },
870 SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => None,
871 }
872 }
873
874 fn lower_bound_constraint<'a>(
875 &self,
876 constraint: &'a SymBoolExpr,
877 ) -> Option<(&'a SymExpr, U256)> {
878 match constraint.kind() {
879 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => const_side_bound(left, right),
880 SymBoolExprKind::Not(value) => match value.kind() {
881 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
882 nonzero_bound(left, right).or_else(|| nonzero_bound(right, left))
883 }
884 _ => None,
885 },
886 _ => None,
887 }
888 }
889
890 fn unsigned_bool_always_true(&self, expr: &SymBoolExpr) -> bool {
891 match expr.kind() {
892 SymBoolExprKind::Cmp(op, left, right) => {
893 self.unsigned_cmp_always_true(*op, left, right)
894 }
895 _ => false,
896 }
897 }
898
899 fn unsigned_cmp_always_true(&self, op: SymCmpOp, left: &SymExpr, right: &SymExpr) -> bool {
900 let Some(left) = self.interval(left) else { return false };
901 let Some(right) = self.interval(right) else { return false };
902 match op {
903 SymCmpOp::Ult => left.max < right.min,
904 SymCmpOp::Ule => left.max <= right.min,
905 SymCmpOp::Ugt => left.min > right.max,
906 SymCmpOp::Uge => left.min >= right.max,
907 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => false,
908 }
909 }
910
911 fn interval(&self, expr: &SymExpr) -> Option<WordInterval> {
912 let lower = self.lower_bound(expr);
913 let upper = self.upper_bound(expr);
914 let interval = self.structural_interval(expr).or_else(|| {
915 (lower.is_some() || upper.is_some()).then(|| WordInterval {
916 min: lower.unwrap_or(U256::ZERO),
917 max: upper.unwrap_or(U256::MAX),
918 })
919 })?;
920 interval.with_bounds(lower, upper)
921 }
922
923 fn structural_interval(&self, expr: &SymExpr) -> Option<WordInterval> {
924 match expr.kind() {
925 SymExprKind::Const(value) => Some(WordInterval::exact(*value)),
926 SymExprKind::BinOp(SymBinOp::And, left, right) => {
927 let mask = left.as_const().or_else(|| right.as_const())?;
928 Some(WordInterval { min: U256::ZERO, max: mask })
929 }
930 SymExprKind::BinOp(SymBinOp::Add, left, right) => {
931 let left = self.interval(left)?;
932 let right = self.interval(right)?;
933 Some(WordInterval {
934 min: left.min.checked_add(right.min)?,
935 max: left.max.checked_add(right.max)?,
936 })
937 }
938 SymExprKind::BinOp(SymBinOp::Sub, left, right) => {
939 let left = self.interval(left)?;
940 let right = self.interval(right)?;
941 if left.min < right.max {
942 return None;
943 }
944 Some(WordInterval {
945 min: left.min.checked_sub(right.max)?,
946 max: left.max.checked_sub(right.min)?,
947 })
948 }
949 SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
950 let left = self.interval(left)?;
951 let right = self.interval(right)?;
952 Some(WordInterval {
953 min: left.min.checked_mul(right.min)?,
954 max: left.max.checked_mul(right.max)?,
955 })
956 }
957 SymExprKind::Ite(_, left, right) => {
958 let left = self.interval(left)?;
959 let right = self.interval(right)?;
960 Some(WordInterval { min: left.min.min(right.min), max: left.max.max(right.max) })
961 }
962 _ => None,
963 }
964 }
965}
966
967fn const_side_bound<'a>(left: &'a SymExpr, right: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
968 right
969 .as_const()
970 .map(|value| (left, value))
971 .or_else(|| left.as_const().map(|value| (right, value)))
972}
973
974fn nonzero_bound<'a>(expr: &'a SymExpr, value: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
975 value.as_const().is_some_and(|value| value.is_zero()).then(|| (expr, U256::from(1)))
976}
977
978pub(crate) fn normalize_expr_for_solver(cx: &mut SymCx, expr: SymExpr) -> SymExpr {
980 if !expr.contains_ite() {
981 return expr;
982 }
983 expr.fold(cx, &mut normalize_expr_node_for_solver)
984}
985
986fn normalize_expr_node_for_solver(cx: &mut SymCx, expr: SymExpr) -> SymExpr {
987 match expr.kind() {
988 SymExprKind::Ite(cond, left, right) => {
989 normalize_ite_expr_for_solver(cx, cond.clone(), left.clone(), right.clone())
990 }
991 _ => expr,
992 }
993}
994
995fn normalize_ite_expr_for_solver(
996 cx: &mut SymCx,
997 cond: SymBoolExpr,
998 left: SymExpr,
999 right: SymExpr,
1000) -> SymExpr {
1001 let cond = normalize_bool_for_solver(cx, cond);
1002 if left == right {
1003 return left;
1005 }
1006 if left.as_const() == Some(U256::from(1))
1007 && right.normalized_bool_word_condition(cx).as_ref() == Some(&cond)
1008 {
1009 return right;
1011 }
1012 if right.as_const().is_some_and(|value| value.is_zero())
1013 && left.normalized_bool_word_condition(cx).as_ref() == Some(&cond)
1014 {
1015 return left;
1017 }
1018 SymExpr::ite(cx, cond, left, right)
1019}
1020
1021impl SymExpr {
1022 fn add_cannot_overflow_256(&self, right: &Self) -> bool {
1023 self.unsigned_bits().max(right.unsigned_bits()).saturating_add(1) <= 256
1024 }
1025
1026 fn word_bool_always_true(&self, cx: &mut SymCx) -> bool {
1027 ConstraintContext::default().word_bool_always_true(cx, self)
1028 }
1029}
1030
1031impl SymBoolExpr {
1032 fn normalize_udiv_for_solver(&self, cx: &mut SymCx) -> Option<Self> {
1033 match self.kind() {
1034 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1035 if right.as_const().is_some_and(|value| value.is_zero()) =>
1036 {
1037 left.normalized_bool_word_condition(cx).map(|value| value.not(cx)).or_else(|| {
1038 if left.word_bool_always_true(cx) {
1039 Some(Self::constant(cx, false))
1041 } else {
1042 let zero = SymExpr::zero(cx);
1043 Self::normalize_udiv_eq_zero(cx, left, &zero)
1044 }
1045 })
1046 }
1047 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1048 if right.as_const() == Some(U256::from(1)) =>
1049 {
1050 left.normalized_bool_word_condition(cx)
1052 }
1053 SymBoolExprKind::Not(value) => match value.kind() {
1054 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1055 if right.as_const().is_some_and(|value| value.is_zero()) =>
1056 {
1057 if left.word_bool_always_true(cx) {
1058 Some(Self::constant(cx, true))
1060 } else {
1061 let zero = SymExpr::zero(cx);
1062 Self::normalize_udiv_eq_zero(cx, left, &zero).map(|value| value.not(cx))
1063 }
1064 }
1065 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1066 Self::normalize_udiv_eq_zero(cx, left, right).map(|value| value.not(cx))
1067 }
1068 SymBoolExprKind::Cmp(op, left, right) => {
1069 Self::normalize_add_overflow_cmp(cx, *op, left, right)
1070 .map(|value| value.not(cx))
1071 .or_else(|| {
1072 Self::normalize_udiv_cmp(cx, *op, left, right)
1073 .map(|value| value.not(cx))
1074 })
1075 }
1076 _ => None,
1077 },
1078 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1079 Self::normalize_udiv_eq_zero(cx, left, right)
1080 }
1081 SymBoolExprKind::Cmp(op, left, right) => {
1082 Self::normalize_add_overflow_cmp(cx, *op, left, right)
1083 .or_else(|| Self::normalize_udiv_cmp(cx, *op, left, right))
1084 }
1085 SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => None,
1086 }
1087 }
1088
1089 fn normalize_add_overflow_cmp(
1090 cx: &mut SymCx,
1091 op: SymCmpOp,
1092 left: &SymExpr,
1093 right: &SymExpr,
1094 ) -> Option<Self> {
1095 match op {
1096 SymCmpOp::Ugt if left.add_overflow_check(right) => Some(Self::constant(cx, false)),
1098 SymCmpOp::Ult if right.add_overflow_check(left) => Some(Self::constant(cx, false)),
1100 _ => None,
1101 }
1102 }
1103
1104 fn normalize_udiv_eq_zero(cx: &mut SymCx, left: &SymExpr, right: &SymExpr) -> Option<Self> {
1105 if right.as_const().is_some_and(|value| value.is_zero())
1106 && let Some(condition) = left.normalize_eq_zero_for_solver(cx)
1107 {
1108 return Some(condition);
1110 }
1111 None
1112 }
1113
1114 fn normalize_udiv_cmp(
1115 cx: &mut SymCx,
1116 op: SymCmpOp,
1117 left: &SymExpr,
1118 right: &SymExpr,
1119 ) -> Option<Self> {
1120 match op {
1121 SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
1122 (_, Some(value)) if value.is_zero() => left
1124 .normalize_ne_zero_for_solver(cx)
1125 .or_else(|| Some(Self::eq_zero(cx, left).not(cx))),
1126 (Some(value), _) if value == U256::from(1) => right
1128 .normalize_eq_zero_for_solver(cx)
1129 .or_else(|| Some(Self::eq_zero(cx, right))),
1130 _ => None,
1131 },
1132 SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
1133 (_, Some(value)) if value == U256::from(1) => left
1135 .normalize_ne_zero_for_solver(cx)
1136 .or_else(|| Some(Self::eq_zero(cx, left).not(cx))),
1137 (Some(value), _) if value.is_zero() => right
1139 .normalize_eq_zero_for_solver(cx)
1140 .or_else(|| Some(Self::eq_zero(cx, right))),
1141 _ => None,
1142 },
1143 SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
1144 (_, Some(value)) if value.is_zero() => {
1146 left.normalize_eq_zero_for_solver(cx).or_else(|| Some(Self::eq_zero(cx, left)))
1147 }
1148 (Some(value), _) if value == U256::from(1) => right
1150 .normalize_ne_zero_for_solver(cx)
1151 .or_else(|| Some(Self::eq_zero(cx, right).not(cx))),
1152 _ => None,
1153 },
1154 SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
1155 (_, Some(value)) if value == U256::from(1) => {
1157 left.normalize_eq_zero_for_solver(cx).or_else(|| Some(Self::eq_zero(cx, left)))
1158 }
1159 (Some(value), _) if value.is_zero() => right
1161 .normalize_ne_zero_for_solver(cx)
1162 .or_else(|| Some(Self::eq_zero(cx, right).not(cx))),
1163 _ => None,
1164 },
1165 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
1166 }
1167 }
1168
1169 fn eq_zero(cx: &mut SymCx, expr: &SymExpr) -> Self {
1170 let zero = SymExpr::zero(cx);
1171 Self::eq(cx, expr.clone(), zero)
1172 }
1173}
1174
1175impl SymExpr {
1176 fn normalized_bool_word_condition(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1177 self.strip_low_byte_mask()
1178 .bool_word_condition()
1179 .map(|condition| normalize_bool_for_solver(cx, condition))
1180 }
1181
1182 fn add_overflow_check(&self, right: &Self) -> bool {
1183 let Some((base, increment)) = right.add_with_operand(self) else { return false };
1184 base == self && base.add_cannot_overflow_256(increment)
1185 }
1186
1187 fn add_with_operand<'a>(&'a self, operand: &Self) -> Option<(&'a Self, &'a Self)> {
1188 let SymExprKind::BinOp(SymBinOp::Add, left, right) = self.kind() else { return None };
1189 if left == operand {
1190 Some((left, right))
1191 } else if right == operand {
1192 Some((right, left))
1193 } else {
1194 None
1195 }
1196 }
1197
1198 fn normalize_eq_zero_for_solver(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1199 if let Some((numerator, denominator)) = self.udiv_operands() {
1200 return Some(Self::udiv_zero_condition(cx, numerator, denominator));
1202 }
1203 if let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() {
1204 let then_zero = match then_expr.normalize_eq_zero_for_solver(cx) {
1205 Some(condition) => condition,
1206 None => {
1207 let then_expr = normalize_expr_for_solver(cx, then_expr.clone());
1208 let zero = Self::zero(cx);
1209 SymBoolExpr::eq(cx, then_expr, zero)
1210 }
1211 };
1212 let else_zero = match else_expr.normalize_eq_zero_for_solver(cx) {
1213 Some(condition) => condition,
1214 None => {
1215 let else_expr = normalize_expr_for_solver(cx, else_expr.clone());
1216 let zero = Self::zero(cx);
1217 SymBoolExpr::eq(cx, else_expr, zero)
1218 }
1219 };
1220 if then_zero.contains_udiv() || else_zero.contains_udiv() {
1221 return None;
1222 }
1223 let condition = normalize_bool_for_solver(cx, condition.clone());
1225 let then_condition = SymBoolExpr::and(cx, vec![condition.clone(), then_zero]);
1226 let not_condition = condition.not(cx);
1227 let else_condition = SymBoolExpr::and(cx, vec![not_condition, else_zero]);
1228 return Some(SymBoolExpr::or(cx, vec![then_condition, else_condition]));
1229 }
1230 None
1231 }
1232
1233 fn normalize_ne_zero_for_solver(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1234 if let Some((numerator, denominator)) = self.udiv_operands() {
1235 return Some(Self::udiv_nonzero_condition(cx, numerator, denominator));
1237 }
1238 if let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() {
1239 let then_nonzero = match then_expr.normalize_ne_zero_for_solver(cx) {
1240 Some(condition) => condition,
1241 None => {
1242 let then_expr = normalize_expr_for_solver(cx, then_expr.clone());
1243 let zero = Self::zero(cx);
1244 SymBoolExpr::eq(cx, then_expr, zero).not(cx)
1245 }
1246 };
1247 let else_nonzero = match else_expr.normalize_ne_zero_for_solver(cx) {
1248 Some(condition) => condition,
1249 None => {
1250 let else_expr = normalize_expr_for_solver(cx, else_expr.clone());
1251 let zero = Self::zero(cx);
1252 SymBoolExpr::eq(cx, else_expr, zero).not(cx)
1253 }
1254 };
1255 if then_nonzero.contains_udiv() || else_nonzero.contains_udiv() {
1256 return None;
1257 }
1258 let condition = normalize_bool_for_solver(cx, condition.clone());
1260 let then_condition = SymBoolExpr::and(cx, vec![condition.clone(), then_nonzero]);
1261 let not_condition = condition.not(cx);
1262 let else_condition = SymBoolExpr::and(cx, vec![not_condition, else_nonzero]);
1263 return Some(SymBoolExpr::or(cx, vec![then_condition, else_condition]));
1264 }
1265 None
1266 }
1267
1268 fn udiv_zero_condition(cx: &mut SymCx, numerator: &Self, denominator: &Self) -> SymBoolExpr {
1269 let numerator = normalize_expr_for_solver(cx, numerator.clone());
1270 let denominator = normalize_expr_for_solver(cx, denominator.clone());
1271 let zero = Self::zero(cx);
1272 let denominator_zero = SymBoolExpr::eq(cx, denominator.clone(), zero);
1273 let below_denominator = SymBoolExpr::cmp(cx, SymCmpOp::Ult, numerator, denominator);
1274 SymBoolExpr::or(cx, vec![denominator_zero, below_denominator])
1275 }
1276
1277 fn udiv_nonzero_condition(cx: &mut SymCx, numerator: &Self, denominator: &Self) -> SymBoolExpr {
1278 let numerator = normalize_expr_for_solver(cx, numerator.clone());
1279 let denominator = normalize_expr_for_solver(cx, denominator.clone());
1280 let zero = Self::zero(cx);
1281 let denominator_nonzero = SymBoolExpr::eq(cx, denominator.clone(), zero).not(cx);
1282 let at_least_denominator = SymBoolExpr::cmp(cx, SymCmpOp::Uge, numerator, denominator);
1283 SymBoolExpr::and(cx, vec![denominator_nonzero, at_least_denominator])
1284 }
1285}
1286
1287impl ConstraintContext {
1288 fn word_bool_always_true(&self, cx: &mut SymCx, expr: &SymExpr) -> bool {
1289 let mut terms = Vec::new();
1290 expr.push_or_terms(&mut terms);
1291 if terms.len() <= 1 {
1292 return false;
1293 }
1294
1295 let bool_terms = terms
1296 .iter()
1297 .filter_map(|term| term.normalized_bool_word_condition(cx))
1298 .collect::<Vec<_>>();
1299 if bool_terms.iter().any(|term| {
1300 let negated = term.clone().not(cx);
1301 bool_terms.contains(&negated)
1302 }) {
1303 return true;
1305 }
1306 for zero_term in &bool_terms {
1307 let Some(zero_operand) = zero_term.zero_check_operand() else { continue };
1308 if bool_terms.iter().any(|term| self.checked_mul_guard_for_operand(term, zero_operand))
1309 {
1310 return true;
1312 }
1313 }
1314 false
1315 }
1316
1317 fn checked_mul_guard_for_operand(&self, expr: &SymBoolExpr, zero_operand: &SymExpr) -> bool {
1318 let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = expr.kind() else {
1319 return false;
1320 };
1321 self.checked_mul_guard_side(left, right, zero_operand)
1322 || self.checked_mul_guard_side(right, left, zero_operand)
1323 }
1324
1325 fn checked_mul_guard_side(
1326 &self,
1327 div_expr: &SymExpr,
1328 expected: &SymExpr,
1329 zero_operand: &SymExpr,
1330 ) -> bool {
1331 let SymExprKind::Ite(condition, then_expr, else_expr) = div_expr.kind() else {
1332 return false;
1333 };
1334 if condition.zero_check_operand().is_none_or(|operand| operand != zero_operand) {
1335 return false;
1336 }
1337 if !then_expr.as_const().is_some_and(|value| value.is_zero()) {
1338 return false;
1339 }
1340 let Some((numerator, denominator)) = else_expr.udiv_operands() else { return false };
1341 if denominator != zero_operand {
1342 return false;
1343 }
1344 let SymExprKind::BinOp(SymBinOp::Mul, left, right) = numerator.kind() else {
1345 return false;
1346 };
1347 let other = if left == zero_operand {
1348 right
1349 } else if right == zero_operand {
1350 left
1351 } else {
1352 return false;
1353 };
1354 other == expected && self.mul_cannot_overflow_256(zero_operand, other)
1355 }
1356
1357 pub(super) fn mul_cannot_overflow_256(&self, left: &SymExpr, right: &SymExpr) -> bool {
1358 self.unsigned_bits(left).saturating_add(self.unsigned_bits(right)) <= 256
1359 }
1360
1361 pub(super) fn unsigned_bits(&self, expr: &SymExpr) -> usize {
1362 let bits = match expr.kind() {
1363 SymExprKind::Const(_)
1364 | SymExprKind::Var(_)
1365 | SymExprKind::GasLeft(_)
1366 | SymExprKind::Keccak { .. }
1367 | SymExprKind::Hash { .. }
1368 | SymExprKind::Not(_) => expr.unsigned_bits(),
1369 SymExprKind::BinOp(SymBinOp::And, left, right) => {
1370 if let Some(mask) = right.as_const() {
1371 self.unsigned_bits(left).min(mask.bit_len())
1372 } else {
1373 256
1374 }
1375 }
1376 SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1377 self.unsigned_bits(left).max(self.unsigned_bits(right)).saturating_add(1).min(256)
1378 }
1379 SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1380 self.unsigned_bits(left).saturating_add(self.unsigned_bits(right)).min(256)
1381 }
1382 SymExprKind::BinOp(SymBinOp::UDiv, left, _) => self.unsigned_bits(left),
1383 SymExprKind::Ite(_, left, right) => {
1384 self.unsigned_bits(left).max(self.unsigned_bits(right))
1385 }
1386 _ => 256,
1387 };
1388
1389 self.upper_bound(expr).map(|bound| bits.min(bound.bit_len().max(1))).unwrap_or(bits)
1390 }
1391}