1use super::{hashcons::HashConsed, *};
2use foundry_evm::revm::interpreter::instructions::i256::i256_cmp;
3
4const MAX_CONSTANT_ITE_EQ_NODES: usize = 128;
7const MAX_CONSTANT_ITE_EQ_UNFOLDED_NODES: usize = 8 * 1024;
8
9#[derive(Clone, PartialEq, Eq, Hash)]
10pub(crate) struct SymBoolExpr {
11 pub(in crate::runtime::expr) kind: HashConsed<SymBoolExprKind>,
12}
13
14impl fmt::Debug for SymBoolExpr {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 self.kind().fmt(f)
17 }
18}
19
20#[derive(Clone, Debug, PartialEq, Eq, Hash)]
21pub(in crate::runtime) enum SymBoolExprKind {
22 Const(bool),
23 Not(SymBoolExpr),
24 And(Arc<[SymBoolExpr]>),
25 Cmp(SymCmpOp, SymExpr, SymExpr),
26}
27
28impl SymBoolExpr {
29 #[inline]
30 pub(in crate::runtime) fn stable_hash_cmp(&self, other: &Self) -> std::cmp::Ordering {
31 self.kind.stable_hash_cmp(&other.kind)
32 }
33
34 pub(in crate::runtime) fn kind(&self) -> &SymBoolExprKind {
35 self.kind.value()
36 }
37
38 pub(in crate::runtime) fn from_kind(cx: &mut SymCx, kind: SymBoolExprKind) -> Self {
39 cx.mk_bool_kind(kind)
40 }
41
42 pub(crate) fn constant(cx: &mut SymCx, value: bool) -> Self {
43 cx.cached_bool(value)
44 }
45
46 pub(crate) fn cmp_word_const(
47 cx: &mut SymCx,
48 op: SymCmpOp,
49 word: &SymExpr,
50 value: U256,
51 ) -> Self {
52 if let Some(word) = word.as_const() {
53 Self::constant(cx, op.eval(word, value))
54 } else {
55 let value = SymExpr::constant(cx, value);
56 Self::cmp(cx, op, word.clone(), value)
57 }
58 }
59
60 pub(crate) fn eq_word_const(cx: &mut SymCx, word: &SymExpr, value: U256) -> Self {
61 if let Some(word) = word.as_const() {
62 Self::constant(cx, word == value)
63 } else {
64 let value = SymExpr::constant(cx, value);
65 Self::eq(cx, word.clone(), value)
66 }
67 }
68
69 pub(crate) fn eq(cx: &mut SymCx, left: SymExpr, right: SymExpr) -> Self {
70 Self::cmp(cx, SymCmpOp::Eq, left, right)
71 }
72
73 pub(crate) fn cmp(cx: &mut SymCx, op: SymCmpOp, left: SymExpr, right: SymExpr) -> Self {
74 if let (
75 SymExprKind::Ite(left_condition, left_then, left_else),
76 SymExprKind::Ite(right_condition, right_then, right_else),
77 ) = (left.kind(), right.kind())
78 && left_condition == right_condition
79 && let (Some(left_then), Some(left_else), Some(right_then), Some(right_else)) = (
80 left_then.as_const(),
81 left_else.as_const(),
82 right_then.as_const(),
83 right_else.as_const(),
84 )
85 {
86 return match (op.eval(left_then, right_then), op.eval(left_else, right_else)) {
88 (true, true) => Self::constant(cx, true),
89 (false, false) => Self::constant(cx, false),
90 (true, false) => left_condition.clone(),
91 (false, true) => Self::not_bool(cx, left_condition.clone()),
92 };
93 }
94
95 match op {
96 SymCmpOp::Eq => {
97 if let Some(condition) = Self::ite_eq_arm(cx, &left, &right)
98 .or_else(|| Self::ite_eq_arm(cx, &right, &left))
99 {
100 return condition;
101 }
102 match (left.kind(), right.kind()) {
103 _ if left == right => Self::constant(cx, true),
105 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
106 Self::constant(cx, left == right)
108 }
109 (_, SymExprKind::Const(right_value)) => {
110 if let Some(condition) = Self::bool_word_eq_const(cx, &left, *right_value) {
111 return condition;
112 }
113 if let Some(left_value) = left.known_word() {
114 return Self::constant(cx, left_value == *right_value);
116 }
117 let (left, right) = SymExpr::ordered_commutative_operands(left, right);
119 Self::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right))
120 }
121 (SymExprKind::Const(left_value), _) => {
122 if let Some(condition) = Self::bool_word_eq_const(cx, &right, *left_value) {
123 return condition;
124 }
125 if let Some(right_value) = right.known_word() {
126 return Self::constant(cx, *left_value == right_value);
128 }
129 let (left, right) = SymExpr::ordered_commutative_operands(left, right);
131 Self::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right))
132 }
133 (
134 SymExprKind::Keccak { len: left_len, bytes: left_bytes, .. },
135 SymExprKind::Keccak { len: right_len, bytes: right_bytes, .. },
136 ) if left_bytes.len() == right_bytes.len() => {
137 let mut conditions =
139 vec![Self::eq(cx, left_len.clone(), right_len.clone())];
140 conditions.extend(
141 left_bytes
142 .iter()
143 .cloned()
144 .zip(right_bytes.iter().cloned())
145 .map(|(left, right)| Self::eq(cx, left, right)),
146 );
147 Self::and(cx, conditions)
148 }
149 (
150 SymExprKind::Hash { algorithm: left_algorithm, bytes: left_bytes, .. },
151 SymExprKind::Hash {
152 algorithm: right_algorithm, bytes: right_bytes, ..
153 },
154 ) if left_algorithm == right_algorithm
155 && left_bytes.len() == right_bytes.len() =>
156 {
157 let conditions = left_bytes
159 .iter()
160 .cloned()
161 .zip(right_bytes.iter().cloned())
162 .map(|(left, right)| Self::eq(cx, left, right))
163 .collect();
164 Self::and(cx, conditions)
165 }
166 _ => {
167 let (left, right) = SymExpr::ordered_commutative_operands(left, right);
169 Self::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right))
170 }
171 }
172 }
173 SymCmpOp::Ult => match (left.kind(), right.kind()) {
174 _ if left == right => Self::constant(cx, false),
176 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
177 Self::constant(cx, op.eval(*left, *right))
179 }
180 (_, SymExprKind::Const(value)) if value.is_zero() => Self::constant(cx, false),
182 (SymExprKind::Const(value), _) if *value == U256::MAX => Self::constant(cx, false),
184 _ if low_masked_source_any(&right) == Some(&left) => Self::constant(cx, false),
186 _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
187 },
188 SymCmpOp::Ugt => match (left.kind(), right.kind()) {
189 _ if left == right => Self::constant(cx, false),
191 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
192 Self::constant(cx, op.eval(*left, *right))
194 }
195 (SymExprKind::Const(value), _) if value.is_zero() => Self::constant(cx, false),
197 (_, SymExprKind::Const(value)) if *value == U256::MAX => Self::constant(cx, false),
199 _ if low_masked_source_any(&left) == Some(&right) => Self::constant(cx, false),
201 _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
202 },
203 SymCmpOp::Ule => match (left.kind(), right.kind()) {
204 _ if left == right => Self::constant(cx, true),
206 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
207 Self::constant(cx, op.eval(*left, *right))
209 }
210 (SymExprKind::Const(value), _) if value.is_zero() => Self::constant(cx, true),
212 (_, SymExprKind::Const(value)) if *value == U256::MAX => Self::constant(cx, true),
214 _ if low_masked_source_any(&left) == Some(&right) => Self::constant(cx, true),
216 _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
217 },
218 SymCmpOp::Uge => match (left.kind(), right.kind()) {
219 _ if left == right => Self::constant(cx, true),
221 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
222 Self::constant(cx, op.eval(*left, *right))
224 }
225 (_, SymExprKind::Const(value)) if value.is_zero() => Self::constant(cx, true),
227 (SymExprKind::Const(value), _) if *value == U256::MAX => Self::constant(cx, true),
229 _ if low_masked_source_any(&right) == Some(&left) => Self::constant(cx, true),
231 _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
232 },
233 SymCmpOp::Slt | SymCmpOp::Sgt => match (left.kind(), right.kind()) {
234 _ if left == right => Self::constant(cx, false),
236 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
237 Self::constant(cx, op.eval(*left, *right))
239 }
240 _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
241 },
242 }
243 }
244
245 pub(crate) fn and(cx: &mut SymCx, values: Vec<Self>) -> Self {
246 let mut out = Vec::new();
247 for value in values {
248 match value.kind() {
249 SymBoolExprKind::Const(true) => {}
251 SymBoolExprKind::Const(false) => return Self::constant(cx, false),
253 SymBoolExprKind::And(values) => out.extend(values.iter().cloned()),
255 _ => out.push(value),
256 }
257 }
258 if out.is_empty() {
259 Self::constant(cx, true)
261 } else if out.len() == 1 {
262 out.pop().expect("single item exists")
264 } else {
265 Self::from_kind(cx, SymBoolExprKind::And(out.into()))
266 }
267 }
268
269 pub(crate) fn or(cx: &mut SymCx, values: Vec<Self>) -> Self {
270 let mut out = Vec::new();
271 for value in values {
272 match value.kind() {
273 SymBoolExprKind::Const(false) => {}
275 SymBoolExprKind::Const(true) => return Self::constant(cx, true),
277 _ => out.push(value),
278 }
279 }
280 if out.is_empty() {
281 Self::constant(cx, false)
283 } else if out.len() == 1 {
284 out.pop().expect("single item exists")
286 } else {
287 let values = out.into_iter().map(|value| Self::not_bool(cx, value)).collect();
289 let and = Self::and(cx, values);
290 Self::not_bool(cx, and)
291 }
292 }
293
294 pub(crate) fn not_bool(cx: &mut SymCx, value: Self) -> Self {
295 match value.kind() {
296 SymBoolExprKind::Const(value) => Self::constant(cx, !*value),
298 SymBoolExprKind::Not(value) => value.clone(),
300 _ => Self::from_kind(cx, SymBoolExprKind::Not(value)),
301 }
302 }
303
304 fn bool_word_eq_const(cx: &mut SymCx, word: &SymExpr, value: U256) -> Option<Self> {
305 let SymExprKind::Ite(condition, then_expr, else_expr) = word.kind() else { return None };
306 match (then_expr.as_const(), else_expr.as_const()) {
307 (Some(then_value), Some(else_value))
308 if then_value == U256::from(1) && else_value.is_zero() =>
309 {
310 Some(if value.is_zero() {
311 Self::not_bool(cx, condition.clone())
312 } else if value == U256::from(1) {
313 condition.clone()
314 } else {
315 Self::constant(cx, false)
316 })
317 }
318 (Some(then_value), Some(else_value))
319 if then_value.is_zero() && else_value == U256::from(1) =>
320 {
321 Some(if value.is_zero() {
322 condition.clone()
323 } else if value == U256::from(1) {
324 Self::not_bool(cx, condition.clone())
325 } else {
326 Self::constant(cx, false)
327 })
328 }
329 _ => None,
330 }
331 }
332
333 fn ite_eq_arm(cx: &mut SymCx, conditional: &SymExpr, expected: &SymExpr) -> Option<Self> {
334 let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
335 return None;
336 };
337 if then_expr == expected {
338 let else_matches = Self::eq(cx, else_expr.clone(), expected.clone());
339 return Some(Self::or(cx, vec![condition.clone(), else_matches]));
340 }
341 if else_expr == expected {
342 let then_matches = Self::eq(cx, then_expr.clone(), expected.clone());
343 let condition = Self::not_bool(cx, condition.clone());
344 return Some(Self::or(cx, vec![condition, then_matches]));
345 }
346 if let Some(expected_value) = expected.as_const() {
347 let mut cost_cache = HashMap::default();
348 let mut remaining = MAX_CONSTANT_ITE_EQ_NODES;
349 if Self::constant_ite_eq_unfolded_nodes(conditional, &mut cost_cache, &mut remaining)
350 .is_none()
351 {
352 let (left, right) =
353 SymExpr::ordered_commutative_operands(conditional.clone(), expected.clone());
354 return Some(Self::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)));
355 }
356
357 let mut cache = HashMap::default();
358 let mut remaining = MAX_CONSTANT_ITE_EQ_NODES;
359 return Self::constant_ite_eq(
360 cx,
361 conditional,
362 expected_value,
363 &mut cache,
364 &mut remaining,
365 );
366 }
367 None
368 }
369
370 fn constant_ite_eq_unfolded_nodes(
376 expr: &SymExpr,
377 cache: &mut HashMap<SymExpr, Option<usize>>,
378 remaining: &mut usize,
379 ) -> Option<usize> {
380 if let Some(cached) = cache.get(expr) {
381 return *cached;
382 }
383 if *remaining == 0 {
384 cache.insert(expr.clone(), None);
385 return None;
386 }
387 *remaining -= 1;
388
389 let result = match expr.kind() {
390 SymExprKind::Const(_) => Some(1),
391 SymExprKind::Ite(condition, then_expr, else_expr) => {
392 let then_nodes = Self::constant_ite_eq_unfolded_nodes(then_expr, cache, remaining)?;
393 let else_nodes = Self::constant_ite_eq_unfolded_nodes(else_expr, cache, remaining)?;
394
395 let mut pending = vec![condition];
396 let mut condition_nodes = 0usize;
397 while let Some(condition) = pending.pop() {
398 condition_nodes = condition_nodes.checked_add(1)?;
399 if condition_nodes > MAX_CONSTANT_ITE_EQ_UNFOLDED_NODES {
400 return None;
401 }
402 match condition.kind() {
403 SymBoolExprKind::Not(value) => pending.push(value),
404 SymBoolExprKind::And(values) => pending.extend(values.iter()),
405 SymBoolExprKind::Const(_) | SymBoolExprKind::Cmp(_, _, _) => {}
406 }
407 }
408
409 then_nodes
412 .checked_add(else_nodes)
413 .and_then(|nodes| nodes.checked_add(2 * condition_nodes))
414 .and_then(|nodes| nodes.checked_add(7))
415 .filter(|nodes| *nodes <= MAX_CONSTANT_ITE_EQ_UNFOLDED_NODES)
416 }
417 _ => None,
418 };
419 cache.insert(expr.clone(), result);
420 result
421 }
422
423 fn constant_ite_eq(
424 cx: &mut SymCx,
425 expr: &SymExpr,
426 expected: U256,
427 cache: &mut HashMap<SymExpr, Option<Self>>,
428 remaining: &mut usize,
429 ) -> Option<Self> {
430 if let Some(cached) = cache.get(expr) {
431 return cached.clone();
432 }
433 if *remaining == 0 {
434 cache.insert(expr.clone(), None);
435 return None;
436 }
437 *remaining -= 1;
438
439 let result = match expr.kind() {
440 SymExprKind::Const(value) => Some(Self::constant(cx, *value == expected)),
441 SymExprKind::Ite(condition, then_expr, else_expr) => {
442 let condition = condition.clone();
443 let then_matches = Self::constant_ite_eq(cx, then_expr, expected, cache, remaining);
444 let else_matches = Self::constant_ite_eq(cx, else_expr, expected, cache, remaining);
445 match (then_matches, else_matches) {
446 (Some(then_matches), Some(else_matches)) => {
447 let then_selected =
448 Self::and_ite_branch(cx, condition.clone(), then_matches);
449 let condition = Self::not_bool(cx, condition);
450 let else_selected = Self::and_ite_branch(cx, condition, else_matches);
451 Some(Self::or(cx, vec![then_selected, else_selected]))
452 }
453 _ => None,
454 }
455 }
456 _ => None,
457 };
458 cache.insert(expr.clone(), result.clone());
459 result
460 }
461
462 fn and_ite_branch(cx: &mut SymCx, condition: Self, branch: Self) -> Self {
468 match (condition.as_const(), branch.as_const()) {
469 (Some(false), _) | (_, Some(false)) => Self::constant(cx, false),
470 (Some(true), _) => branch,
471 (_, Some(true)) => condition,
472 _ if condition == branch => condition,
473 _ => Self::from_kind(cx, SymBoolExprKind::And(vec![condition, branch].into())),
474 }
475 }
476
477 pub(crate) fn as_const(&self) -> Option<bool> {
478 match self.kind() {
479 SymBoolExprKind::Const(value) => Some(*value),
480 _ => None,
481 }
482 }
483
484 pub(in crate::runtime) fn zero_check_operand(&self) -> Option<&SymExpr> {
485 match self.kind() {
486 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
487 if right.as_const().is_some_and(|value| value.is_zero()) =>
488 {
489 Some(left)
490 }
491 _ => None,
492 }
493 }
494
495 pub(crate) fn contains_keccak(&self) -> bool {
496 self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::Keccak { .. }))
497 }
498
499 pub(crate) fn contains_gasleft(&self) -> bool {
500 self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::GasLeft(_)))
501 }
502
503 pub(crate) fn contains_udiv(&self) -> bool {
504 self.visit_bool(|expr| expr.contains_udiv())
505 }
506
507 pub(crate) fn implies_unsigned_less_or_equal(
508 &self,
509 expected: bool,
510 left: &SymExpr,
511 right: &SymExpr,
512 remaining: &mut usize,
513 ) -> bool {
514 if left == right {
515 return true;
516 }
517 let Some(next) = remaining.checked_sub(1) else { return false };
518 *remaining = next;
519
520 match self.kind() {
521 SymBoolExprKind::Not(value) => {
522 value.implies_unsigned_less_or_equal(!expected, left, right, remaining)
523 }
524 SymBoolExprKind::And(values) if expected => values
525 .iter()
526 .any(|value| value.implies_unsigned_less_or_equal(true, left, right, remaining)),
527 SymBoolExprKind::Cmp(op, fact_left, fact_right) => match (*op, expected) {
528 (SymCmpOp::Ult | SymCmpOp::Ule, true) => fact_left == left && fact_right == right,
529 (SymCmpOp::Uge | SymCmpOp::Ugt, true) => fact_right == left && fact_left == right,
530 (SymCmpOp::Ult | SymCmpOp::Ule, false) => fact_right == left && fact_left == right,
531 (SymCmpOp::Uge | SymCmpOp::Ugt, false) => fact_left == left && fact_right == right,
532 (SymCmpOp::Eq, true) => {
533 (fact_left == left && fact_right == right)
534 || (fact_right == left && fact_left == right)
535 }
536 (SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt, _) => false,
537 },
538 SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => false,
539 }
540 }
541
542 pub(crate) fn forces_expr_const_with_context(
543 &self,
544 expr: &SymExpr,
545 context: &[Self],
546 ) -> Option<U256> {
547 match self.kind() {
548 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => match right.kind() {
549 SymExprKind::Const(value) => left.equality_forces_const(*value, expr, context),
550 _ => None,
551 },
552 SymBoolExprKind::Not(value) => match value.kind() {
553 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => match right.kind() {
554 SymExprKind::Const(value) if value.is_zero() => {
555 left.nonzero_forces_const(expr, context)
556 }
557 _ => None,
558 },
559 SymBoolExprKind::Not(value) => value.forces_expr_const_with_context(expr, context),
560 _ => None,
561 },
562 SymBoolExprKind::And(values) => {
563 values.iter().find_map(|value| value.forces_expr_const_with_context(expr, context))
564 }
565 _ => None,
566 }
567 }
568
569 pub(crate) fn upper_bound_usize(&self, expr: &SymExpr) -> Option<usize> {
570 match self.kind() {
571 SymBoolExprKind::Const(_) | SymBoolExprKind::Not(_) => None,
572 SymBoolExprKind::And(values) => {
573 let mut bound: Option<usize> = None;
574 for value in values.iter() {
575 if let Some(candidate) = value.upper_bound_usize(expr) {
576 bound = Some(bound.map_or(candidate, |bound| bound.min(candidate)));
577 }
578 }
579 bound
580 }
581 SymBoolExprKind::Cmp(op, left, right) => {
582 if *op == SymCmpOp::Eq {
583 return match (left == expr, right == expr) {
584 (true, _) => right.as_const().and_then(|value| usize::try_from(value).ok()),
585 (_, true) => left.as_const().and_then(|value| usize::try_from(value).ok()),
586 _ => None,
587 };
588 }
589 if left == expr {
590 match *op {
591 SymCmpOp::Ult => right
592 .as_const()
593 .and_then(|bound| (!bound.is_zero()).then(|| bound - U256::from(1)))
594 .and_then(|value| usize::try_from(value).ok()),
595 SymCmpOp::Ule => {
596 right.as_const().and_then(|value| usize::try_from(value).ok())
597 }
598 _ => None,
599 }
600 } else if right == expr {
601 match *op {
602 SymCmpOp::Ugt => left
603 .as_const()
604 .and_then(|bound| (!bound.is_zero()).then(|| bound - U256::from(1)))
605 .and_then(|value| usize::try_from(value).ok()),
606 SymCmpOp::Uge => {
607 left.as_const().and_then(|value| usize::try_from(value).ok())
608 }
609 _ => None,
610 }
611 } else {
612 None
613 }
614 }
615 }
616 }
617
618 pub(crate) fn eval_model<M: SymbolicModelLookup + ?Sized>(
619 &self,
620 model: &M,
621 ) -> Result<bool, SymbolicError> {
622 ModelEvaluator::new(model).eval_bool(self)
623 }
624
625 pub(crate) fn eval_model_if_complete<M: SymbolicModelLookup + ?Sized>(
626 &self,
627 model: &M,
628 ) -> Result<Option<bool>, SymbolicError> {
629 let mut vars = SymbolicVars::default();
630 self.collect_eval_vars(&mut vars);
631 if vars.iter().copied().all(|var| model.contains_name(var)) {
632 self.eval_model(model).map(Some)
633 } else {
634 Ok(None)
635 }
636 }
637
638 pub(crate) fn visit_exprs<B>(
640 &self,
641 visitor: &mut impl FnMut(&SymExpr) -> ControlFlow<B>,
642 ) -> ControlFlow<B> {
643 match self.kind() {
644 SymBoolExprKind::Const(_) => {}
645 SymBoolExprKind::Not(value) => value.visit_exprs(visitor)?,
646 SymBoolExprKind::And(values) => {
647 for value in values.iter() {
648 value.visit_exprs(visitor)?;
649 }
650 }
651 SymBoolExprKind::Cmp(_, left, right) => {
652 left.visit(visitor)?;
653 right.visit(visitor)?;
654 }
655 }
656 ControlFlow::Continue(())
657 }
658
659 pub(crate) fn visit_bool(&self, mut visitor: impl FnMut(&SymExpr) -> bool) -> bool {
660 self.visit_exprs(&mut |expr| {
661 if visitor(expr) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
662 })
663 .is_break()
664 }
665
666 pub(crate) fn visit_unique_bool(&self, mut visitor: impl FnMut(&SymExpr) -> bool) -> bool {
668 let mut pending_bools = vec![self.clone()];
669 let mut pending_words = Vec::new();
670 let mut visited_bools = HashSet::<Self>::default();
671 let mut visited_words = HashSet::<SymExpr>::default();
672
673 loop {
674 if let Some(expr) = pending_bools.pop() {
675 if !visited_bools.insert(expr.clone()) {
676 continue;
677 }
678 match expr.kind() {
679 SymBoolExprKind::Const(_) => {}
680 SymBoolExprKind::Not(value) => pending_bools.push(value.clone()),
681 SymBoolExprKind::And(values) => {
682 pending_bools.extend(values.iter().cloned());
683 }
684 SymBoolExprKind::Cmp(_, left, right) => {
685 pending_words.push(left.clone());
686 pending_words.push(right.clone());
687 }
688 }
689 continue;
690 }
691
692 let Some(expr) = pending_words.pop() else { return false };
693 if !visited_words.insert(expr.clone()) {
694 continue;
695 }
696 if visitor(&expr) {
697 return true;
698 }
699 match expr.kind() {
700 SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => {}
701 SymExprKind::Keccak { len, bytes, .. } => {
702 pending_words.push(len.clone());
703 pending_words.extend(bytes.iter().cloned());
704 }
705 SymExprKind::Hash { bytes, .. } => {
706 pending_words.extend(bytes.iter().cloned());
707 }
708 SymExprKind::Not(value) => pending_words.push(value.clone()),
709 SymExprKind::BinOp(_, left, right) => {
710 pending_words.push(left.clone());
711 pending_words.push(right.clone());
712 }
713 SymExprKind::TernOp(_, left, right, modulus) => {
714 pending_words.push(left.clone());
715 pending_words.push(right.clone());
716 pending_words.push(modulus.clone());
717 }
718 SymExprKind::Ite(condition, left, right) => {
719 pending_bools.push(condition.clone());
720 pending_words.push(left.clone());
721 pending_words.push(right.clone());
722 }
723 }
724 }
725 }
726
727 pub(crate) fn fold(
731 &self,
732 cx: &mut SymCx,
733 folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
734 ) -> Self {
735 let mut folded = HashMap::default();
736 self.fold_cached(cx, folder, &mut folded)
737 }
738
739 fn fold_cached<'a>(
740 &'a self,
741 cx: &mut SymCx,
742 folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
743 folded: &mut HashMap<&'a Self, Self>,
744 ) -> Self {
745 if let Some(expr) = folded.get(self) {
746 return expr.clone();
747 }
748
749 let expr = match self.kind() {
750 SymBoolExprKind::Const(_) => self.clone(),
751 SymBoolExprKind::Not(value) => {
752 let value = value.fold_cached(cx, folder, folded);
753 Self::not_bool(cx, value)
754 }
755 SymBoolExprKind::And(values) => {
756 let values =
757 values.iter().map(|value| value.fold_cached(cx, folder, folded)).collect();
758 Self::and(cx, values)
759 }
760 SymBoolExprKind::Cmp(op, left, right) => {
761 Self::cmp(cx, *op, left.clone(), right.clone())
762 }
763 };
764 let expr = folder(cx, expr);
765 folded.insert(self, expr.clone());
766 expr
767 }
768
769 pub(crate) fn fold_exprs(
773 &self,
774 cx: &mut SymCx,
775 folder: &mut impl FnMut(&mut SymCx, SymExpr) -> SymExpr,
776 ) -> Self {
777 let mut folded = ExpressionFoldCache::default();
778 self.fold_exprs_cached(cx, folder, &mut folded)
779 }
780
781 pub(in crate::runtime::expr) fn fold_exprs_cached<'a>(
782 &'a self,
783 cx: &mut SymCx,
784 folder: &mut impl FnMut(&mut SymCx, SymExpr) -> SymExpr,
785 folded: &mut ExpressionFoldCache<'a>,
786 ) -> Self {
787 if let Some(expr) = folded.bools.get(self) {
788 return expr.clone();
789 }
790
791 let expr = match self.kind() {
792 SymBoolExprKind::Const(_) => self.clone(),
793 SymBoolExprKind::Not(value) => {
794 let value = value.fold_exprs_cached(cx, folder, folded);
795 Self::not_bool(cx, value)
796 }
797 SymBoolExprKind::And(values) => {
798 let values = values
799 .iter()
800 .map(|value| value.fold_exprs_cached(cx, folder, folded))
801 .collect();
802 Self::and(cx, values)
803 }
804 SymBoolExprKind::Cmp(op, left, right) => {
805 let left = left.fold_cached(cx, folder, folded);
806 let right = right.fold_cached(cx, folder, folded);
807 Self::cmp(cx, *op, left, right)
808 }
809 };
810 folded.bools.insert(self, expr.clone());
811 expr
812 }
813
814 #[cfg(test)]
815 pub(crate) fn raw_and(cx: &mut SymCx, values: Vec<Self>) -> Self {
816 Self::from_kind(cx, SymBoolExprKind::And(values.into()))
817 }
818
819 pub(crate) fn cmp_word_expr(
820 cx: &mut SymCx,
821 op: SymCmpOp,
822 word: &SymExpr,
823 expr: SymExpr,
824 ) -> Self {
825 Self::cmp(cx, op, word.clone(), expr)
826 }
827
828 pub(crate) fn not(self, cx: &mut SymCx) -> Self {
829 Self::not_bool(cx, self)
830 }
831
832 pub(crate) fn collect_vars(&self, vars: &mut SymbolicVars) {
833 let _ = self.visit_exprs(&mut |expr| {
834 if let Some(var) = expr.kind().get_var() {
835 vars.insert(var);
836 }
837 ControlFlow::<()>::Continue(())
838 });
839 }
840
841 pub(crate) fn collect_eval_vars(&self, vars: &mut SymbolicVars) {
842 let _ = self.visit_exprs(&mut |expr| {
843 if let Some(var) = expr.kind().get_eval_var() {
844 vars.insert(var);
845 }
846 ControlFlow::<()>::Continue(())
847 });
848 }
849
850 pub(crate) fn smt(&self, cx: &SymCx) -> String {
851 let mut smt = String::new();
852 self.write_smt(cx, &mut smt);
853 smt
854 }
855
856 pub(in crate::runtime::expr) fn write_smt(&self, cx: &SymCx, out: &mut String) {
857 match self.kind() {
858 SymBoolExprKind::Const(value) => out.push_str(if *value { "true" } else { "false" }),
859 SymBoolExprKind::Not(value) => {
860 out.push_str("(not ");
861 value.write_smt(cx, out);
862 out.push(')');
863 }
864 SymBoolExprKind::And(values) => {
865 out.push_str("(and");
866 for value in values.iter() {
867 out.push(' ');
868 value.write_smt(cx, out);
869 }
870 out.push(')');
871 }
872 SymBoolExprKind::Cmp(op, left, right) => {
873 let _ = write!(out, "({} ", op.smt());
874 left.write_smt(cx, out);
875 out.push(' ');
876 right.write_smt(cx, out);
877 out.push(')');
878 }
879 }
880 }
881}
882
883#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
884pub(crate) enum SymCmpOp {
885 Eq,
886 Ult,
887 Ugt,
888 Ule,
889 Uge,
890 Slt,
891 Sgt,
892}
893
894impl SymCmpOp {
895 pub(crate) const fn smt(self) -> &'static str {
896 match self {
897 Self::Eq => "=",
898 Self::Ult => "bvult",
899 Self::Ugt => "bvugt",
900 Self::Ule => "bvule",
901 Self::Uge => "bvuge",
902 Self::Slt => "bvslt",
903 Self::Sgt => "bvsgt",
904 }
905 }
906
907 pub(crate) fn eval(self, left: U256, right: U256) -> bool {
908 match self {
909 Self::Eq => left == right,
910 Self::Ult => left < right,
911 Self::Ugt => left > right,
912 Self::Ule => left <= right,
913 Self::Uge => left >= right,
914 Self::Slt => i256_cmp(&left, &right).is_lt(),
915 Self::Sgt => i256_cmp(&left, &right).is_gt(),
916 }
917 }
918}
919
920#[cfg(test)]
921mod tests {
922 use super::*;
923
924 #[test]
925 fn unique_word_visitor_deduplicates_shared_dag() {
926 let mut cx = SymCx::new();
927 let mut shared = SymExpr::var(&mut cx, "shared");
928 for _ in 0..16 {
929 shared = SymExpr::binop(&mut cx, SymBinOp::Add, shared.clone(), shared);
930 }
931 let zero = SymExpr::zero(&mut cx);
932 let condition = SymBoolExpr::eq(&mut cx, shared, zero);
933 let mut visits = 0;
934
935 assert!(!condition.visit_unique_bool(|_| {
936 visits += 1;
937 false
938 }));
939 assert_eq!(visits, 18);
940 }
941
942 #[test]
943 fn constant_ite_equality_rejects_exponential_shared_dag() {
944 let mut cx = SymCx::new();
945 let x = SymExpr::var(&mut cx, "x");
946 let y = SymExpr::var(&mut cx, "y");
947 let first = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ult, x.clone(), y.clone());
948 let second = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ugt, x.clone(), y.clone());
949 let third = SymBoolExpr::cmp(&mut cx, SymCmpOp::Eq, x, y);
950 let zero = SymExpr::zero(&mut cx);
951 let one = SymExpr::one(&mut cx);
952 let mut shared = SymExpr::ite(&mut cx, first.clone(), zero.clone(), one.clone());
953
954 for _ in 0..32 {
955 let left = SymExpr::ite(&mut cx, first.clone(), shared.clone(), zero.clone());
956 let right = SymExpr::ite(&mut cx, second.clone(), shared.clone(), one.clone());
957 shared = SymExpr::ite(&mut cx, third.clone(), left, right);
958 }
959
960 let raw = SymBoolExpr::from_kind(
961 &mut cx,
962 SymBoolExprKind::Cmp(SymCmpOp::Eq, shared.clone(), one.clone()),
963 );
964 let expanded = SymBoolExpr::eq(&mut cx, shared, one);
965 assert_eq!(expanded, raw);
966 }
967
968 #[test]
969 fn constant_ite_equality_keeps_linear_chain_linear() {
970 let mut cx = SymCx::new();
971 let zero = SymExpr::zero(&mut cx);
972 let one = SymExpr::one(&mut cx);
973 let mut value = zero.clone();
974 for index in 0..64 {
975 let selector = SymExpr::var(&mut cx, &format!("selector_{index}"));
976 let condition = SymBoolExpr::eq_word_const(&mut cx, &selector, U256::ZERO);
977 value = SymExpr::ite(&mut cx, condition, value, one.clone());
978 }
979
980 let expanded = SymBoolExpr::eq(&mut cx, value, zero);
981 let mut pending = vec![expanded];
982 let mut visited = HashSet::<SymBoolExpr>::default();
983 while let Some(expr) = pending.pop() {
984 if !visited.insert(expr.clone()) {
985 continue;
986 }
987 match expr.kind() {
988 SymBoolExprKind::Not(value) => pending.push(value.clone()),
989 SymBoolExprKind::And(values) => {
990 assert!(values.len() <= 2);
991 pending.extend(values.iter().cloned());
992 }
993 SymBoolExprKind::Const(_) | SymBoolExprKind::Cmp(_, _, _) => {}
994 }
995 }
996 assert!(visited.len() < 2 * 64);
997 }
998
999 #[test]
1000 fn constant_ite_equality_stops_at_expansion_budget() {
1001 let mut cx = SymCx::new();
1002 let zero = SymExpr::zero(&mut cx);
1003 let one = SymExpr::one(&mut cx);
1004 let mut value = zero;
1005 for index in 0..MAX_CONSTANT_ITE_EQ_NODES {
1006 let selector = SymExpr::var(&mut cx, &format!("selector_{index}"));
1007 let condition = SymBoolExpr::eq_word_const(&mut cx, &selector, U256::ZERO);
1008 value = SymExpr::ite(&mut cx, condition, value, one.clone());
1009 }
1010 let two = SymExpr::constant(&mut cx, U256::from(2));
1011 let comparison = SymBoolExpr::eq(&mut cx, value, two);
1012
1013 assert!(matches!(comparison.kind(), SymBoolExprKind::Cmp(SymCmpOp::Eq, _, _)));
1014 }
1015}