1use super::{hashcons::HashConsed, *};
2use foundry_evm::revm::interpreter::instructions::i256::{i256_div, i256_mod};
3
4const MAX_BITWISE_BOOL_WORD_VISITS: usize = 256;
7const MAX_CONSTANT_DIFFERENCE_VISITS: usize = 256;
8
9impl SymExpr {
10 pub(crate) fn select_storage_write(
11 self,
12 cx: &mut SymCx,
13 write_key: Self,
14 write_value: Self,
15 base: Self,
16 ) -> Self {
17 if write_value == base {
18 return base;
19 }
20 let condition = self.storage_key_eq(cx, &write_key);
21 match condition.as_const() {
22 Some(true) => write_value,
23 Some(false) => base,
24 None => Self::ite(cx, condition, write_value, base),
25 }
26 }
27
28 pub(crate) fn storage_key_eq(&self, cx: &mut SymCx, write_key: &Self) -> SymBoolExpr {
29 if let (Some(read_root), Some(write_root)) =
30 (self.storage_mapping_root_slot(cx), write_key.storage_mapping_root_slot(cx))
31 && read_root != write_root
32 {
33 return SymBoolExpr::constant(cx, false);
34 }
35 match (self.storage_layout_key(cx), write_key.storage_layout_key(cx)) {
36 (Some((read_base, read_offset)), Some((write_base, write_offset))) => {
37 let read_base = read_base
38 .storage_base_eq(cx, &write_base)
39 .unwrap_or_else(|| SymBoolExpr::eq(cx, read_base, write_base));
40 let read_offset = SymBoolExpr::eq(cx, read_offset, write_offset);
41 SymBoolExpr::and(cx, vec![read_base, read_offset])
42 }
43 (Some(_), None) if write_key.as_const().is_some() => SymBoolExpr::constant(cx, false),
44 (None, Some(_)) if self.as_const().is_some() => SymBoolExpr::constant(cx, false),
45 _ => SymBoolExpr::eq(cx, self.clone(), write_key.clone()),
46 }
47 }
48
49 fn storage_base_eq(&self, cx: &mut SymCx, other: &Self) -> Option<SymBoolExpr> {
50 let read = self.storage_mapping_key(cx)?;
51 let write = other.storage_mapping_key(cx)?;
52
53 let key_eq = storage_mapping_key_eq(cx, &read, &write);
54 let slot_eq = read
55 .slot
56 .storage_base_eq(cx, &write.slot)
57 .unwrap_or_else(|| SymBoolExpr::eq(cx, read.slot, write.slot));
58 Some(SymBoolExpr::and(cx, vec![key_eq, slot_eq]))
59 }
60
61 pub(crate) fn storage_mapping_key(&self, cx: &mut SymCx) -> Option<StorageMappingKey> {
62 let bytes = self.storage_mapping_key_bytes(cx)?;
63 let key_bytes = &bytes[..32];
64 let preserve_key_bytes =
65 (!storage_mapping_key_bytes_form_compact_word(key_bytes)).then(|| key_bytes.to_vec());
66 let key = Self::from_bytes(cx, key_bytes.iter().cloned());
67 let slot = Self::from_bytes(cx, bytes[32..64].iter().cloned());
68 Some(StorageMappingKey { key, key_bytes: preserve_key_bytes, slot })
69 }
70
71 pub(crate) fn storage_mapping_provenance_observed_with(
72 &self,
73 cx: &mut SymCx,
74 mut observed_preimage: impl FnMut(&Self) -> Option<Arc<[Self]>>,
75 ) -> Option<SymbolicMappingProvenance> {
76 let mut current = self.clone();
77 let mut keys = Vec::new();
78 let mut visited = Vec::new();
79 loop {
80 if visited.contains(¤t) {
81 return None;
82 }
83 visited.push(current.clone());
84 let bytes = observed_preimage(¤t)?;
85 if bytes.len() != 64 {
86 return None;
87 }
88 let key = Self::from_bytes(cx, bytes[..32].iter().cloned());
89 keys.push(key);
90 current = Self::from_bytes(cx, bytes[32..64].iter().cloned());
91 match current.kind() {
92 SymExprKind::Const(root_slot) if observed_preimage(¤t).is_none() => {
93 keys.reverse();
94 return Some(SymbolicMappingProvenance { root_slot: *root_slot, keys });
95 }
96 SymExprKind::Const(_) | SymExprKind::Keccak { .. } => {}
97 _ => return None,
98 }
99 }
100 }
101
102 fn storage_mapping_root_slot(&self, cx: &mut SymCx) -> Option<U256> {
103 let bytes = self.storage_mapping_key_bytes(cx)?;
104 let slot = Self::from_bytes(cx, bytes[32..64].iter().cloned());
105 match slot.kind() {
106 SymExprKind::Const(value) if cx.concrete_keccak_preimage(*value).is_some() => {
107 slot.storage_mapping_root_slot(cx)
108 }
109 SymExprKind::Const(slot) => Some(*slot),
110 SymExprKind::Keccak { .. } => slot.storage_mapping_root_slot(cx),
111 _ => None,
112 }
113 }
114
115 fn storage_mapping_key_bytes(&self, cx: &SymCx) -> Option<Arc<[Self]>> {
116 match self.kind() {
117 SymExprKind::Keccak { len, bytes, .. }
118 if len.as_const() == Some(U256::from(64)) && bytes.len() >= 64 =>
119 {
120 Some(bytes.clone())
121 }
122 SymExprKind::Const(hash) => cx.concrete_keccak_preimage(*hash),
123 _ => None,
124 }
125 }
126
127 fn storage_layout_key(&self, cx: &mut SymCx) -> Option<(Self, Self)> {
128 match self.kind() {
129 SymExprKind::Keccak { .. } => Some((self.clone(), Self::zero(cx))),
130 SymExprKind::Const(hash) if cx.concrete_keccak_preimage(*hash).is_some() => {
131 Some((self.clone(), Self::zero(cx)))
132 }
133 SymExprKind::BinOp(SymBinOp::Add, left, right) => {
134 if let Some((base, offset)) = left.storage_layout_key(cx)
135 && !right.contains_keccak()
136 {
137 let offset = Self::binop(cx, SymBinOp::Add, offset, right.clone());
138 return Some((base, offset));
139 }
140 if let Some((base, offset)) = right.storage_layout_key(cx)
141 && !left.contains_keccak()
142 {
143 let offset = Self::binop(cx, SymBinOp::Add, offset, left.clone());
144 return Some((base, offset));
145 }
146 None
147 }
148 _ => None,
149 }
150 }
151}
152
153pub(crate) struct StorageMappingKey {
154 key: SymExpr,
155 key_bytes: Option<Vec<SymExpr>>,
156 slot: SymExpr,
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
160pub(crate) struct SymbolicMappingProvenance {
161 pub(crate) root_slot: U256,
162 pub(crate) keys: Vec<SymExpr>,
163}
164
165fn storage_mapping_key_eq(
166 cx: &mut SymCx,
167 read: &StorageMappingKey,
168 write: &StorageMappingKey,
169) -> SymBoolExpr {
170 if read.key_bytes.is_some() || write.key_bytes.is_some() {
171 let read_owned;
172 let read_bytes = if let Some(bytes) = read.key_bytes.as_deref() {
173 bytes
174 } else {
175 read_owned = read.key.clone().into_byte_exprs(cx);
176 &read_owned
177 };
178 let write_owned;
179 let write_bytes = if let Some(bytes) = write.key_bytes.as_deref() {
180 bytes
181 } else {
182 write_owned = write.key.clone().into_byte_exprs(cx);
183 &write_owned
184 };
185 let byte_equalities = read_bytes
186 .iter()
187 .zip(write_bytes)
188 .map(|(read, write)| {
189 let read = read.byte_term(cx, 31).unwrap_or_else(|| read.clone().low_byte(cx));
190 let write = write.byte_term(cx, 31).unwrap_or_else(|| write.clone().low_byte(cx));
191 SymBoolExpr::eq(cx, read, write)
192 })
193 .collect();
194 SymBoolExpr::and(cx, byte_equalities)
195 } else {
196 SymBoolExpr::eq(cx, read.key.clone(), write.key.clone())
197 }
198}
199
200fn storage_mapping_key_bytes_form_compact_word(bytes: &[SymExpr]) -> bool {
201 bytes.iter().all(|byte| byte.as_const().is_some()) || word_from_extracted_bytes(bytes).is_some()
202}
203
204fn masked_expr_matches(candidate: &SymExprKind, target: &SymExpr) -> Option<U256> {
205 match candidate {
206 SymExprKind::BinOp(SymBinOp::And, left, right) if left == target => right.eval(),
207 SymExprKind::BinOp(SymBinOp::And, left, right) if right == target => left.eval(),
208 _ => None,
209 }
210}
211
212fn context_forces_masked_expr(context: &[SymBoolExpr], target: &SymExpr, mask: U256) -> bool {
213 context.iter().any(|condition| match condition.kind() {
214 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
215 (left == target && masked_expr_matches(right.kind(), target) == Some(mask))
216 || (right == target && masked_expr_matches(left.kind(), target) == Some(mask))
217 }
218 SymBoolExprKind::And(values) => context_forces_masked_expr(values, target, mask),
219 _ => false,
220 })
221}
222
223pub(crate) fn concrete_expr_bytes(
224 bytes: &[SymExpr],
225 reason: &'static str,
226) -> Result<Vec<u8>, SymbolicError> {
227 bytes
228 .iter()
229 .map(|byte| match byte.as_const() {
230 Some(value) => Ok(value.to::<u8>()),
231 None => Err(SymbolicError::Unsupported(reason)),
232 })
233 .collect()
234}
235
236pub(crate) fn mask_low_bits(mask: U256) -> Option<usize> {
237 let bits = mask.bit_len();
238 (mask == mask_bits(U256::MAX, bits)).then_some(bits)
239}
240
241fn power_of_two_shift(value: U256) -> Option<usize> {
242 if value <= U256::ONE || !value.is_power_of_two() {
243 return None;
244 }
245 Some(value.bit_len() - 1)
246}
247
248pub(in crate::runtime::expr) fn low_masked_source(expr: &SymExpr, bits: usize) -> Option<&SymExpr> {
249 match expr.kind() {
250 SymExprKind::BinOp(SymBinOp::And, left, right)
252 if right.as_const().and_then(mask_low_bits) == Some(bits) =>
253 {
254 Some(left)
255 }
256 _ => None,
257 }
258}
259
260pub(in crate::runtime::expr) fn low_masked_source_any(expr: &SymExpr) -> Option<&SymExpr> {
261 match expr.kind() {
262 SymExprKind::BinOp(SymBinOp::And, left, right)
264 if right.as_const().and_then(mask_low_bits).is_some() =>
265 {
266 Some(left)
267 }
268 _ => None,
269 }
270}
271
272fn word_from_extracted_bytes(bytes: &[SymExpr]) -> Option<SymExpr> {
273 if bytes.len() < 32 {
274 return None;
275 }
276
277 let source = bytes
278 .iter()
279 .take(32)
280 .enumerate()
281 .find_map(|(idx, byte)| byte.extracted_byte_source(idx))?;
282
283 for (idx, byte) in bytes.iter().take(32).enumerate() {
284 if let Some(byte_source) = byte.extracted_byte_source(idx) {
285 if byte_source != source {
286 return None;
287 }
288 continue;
289 }
290
291 let byte = byte.as_const()?;
292 if source.known_byte(idx) != Some(byte.to::<u8>()) {
293 return None;
294 }
295 }
296 Some(source)
297}
298
299#[derive(Clone, PartialEq, Eq, Hash)]
300pub(crate) struct SymExpr {
301 pub(in crate::runtime::expr) kind: HashConsed<SymExprKind>,
302}
303
304impl fmt::Debug for SymExpr {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 self.kind().fmt(f)
307 }
308}
309
310#[derive(Clone, Debug, PartialEq, Eq, Hash)]
311pub(in crate::runtime) enum SymExprKind {
312 Const(U256),
313 Var(Symbol),
314 GasLeft(Symbol),
315 Keccak { name: Symbol, len: SymExpr, bytes: Arc<[SymExpr]> },
316 Hash { name: Symbol, algorithm: &'static str, bytes: Arc<[SymExpr]> },
317 Not(SymExpr),
318 BinOp(SymBinOp, SymExpr, SymExpr),
319 TernOp(SymTernOp, SymExpr, SymExpr, SymExpr),
320 Ite(SymBoolExpr, SymExpr, SymExpr),
321}
322
323impl SymExprKind {
324 pub(in crate::runtime) const fn get_var(&self) -> Option<Symbol> {
325 match self {
326 Self::Var(symbol)
327 | Self::GasLeft(symbol)
328 | Self::Keccak { name: symbol, .. }
329 | Self::Hash { name: symbol, .. } => Some(*symbol),
330 _ => None,
331 }
332 }
333
334 pub(in crate::runtime) const fn get_eval_var(&self) -> Option<Symbol> {
335 match self {
336 Self::Var(symbol) | Self::GasLeft(symbol) | Self::Hash { name: symbol, .. } => {
337 Some(*symbol)
338 }
339 _ => None,
340 }
341 }
342}
343
344impl SymExpr {
345 pub(in crate::runtime) fn kind(&self) -> &SymExprKind {
346 self.kind.value()
347 }
348
349 #[cfg(test)]
350 pub(crate) fn get_var_name<'a>(&self, cx: &'a SymCx) -> Option<&'a str> {
351 self.kind().get_var().map(|symbol| cx.symbol_name(symbol))
352 }
353
354 #[cfg(test)]
355 pub(crate) fn is_keccak(&self) -> bool {
356 matches!(self.kind(), SymExprKind::Keccak { .. })
357 }
358
359 #[cfg(test)]
360 pub(crate) fn keccak_len_and_byte_count(&self) -> Option<(&Self, usize)> {
361 match self.kind() {
362 SymExprKind::Keccak { len, bytes, .. } => Some((len, bytes.len())),
363 _ => None,
364 }
365 }
366
367 #[cfg(test)]
368 pub(crate) fn hash_algorithm(&self) -> Option<&'static str> {
369 match self.kind() {
370 SymExprKind::Hash { algorithm, .. } => Some(algorithm),
371 _ => None,
372 }
373 }
374
375 pub(in crate::runtime) fn from_kind(cx: &mut SymCx, kind: SymExprKind) -> Self {
376 cx.mk_expr_kind(kind)
377 }
378
379 pub(crate) fn zero(cx: &mut SymCx) -> Self {
380 Self::constant(cx, U256::ZERO)
381 }
382
383 pub(crate) fn one(cx: &mut SymCx) -> Self {
384 Self::constant(cx, U256::ONE)
385 }
386
387 pub(crate) fn constant(cx: &mut SymCx, value: U256) -> Self {
388 if value.is_zero() {
389 return cx.cached_zero();
390 }
391 if value == U256::ONE {
392 return cx.cached_one();
393 }
394 Self::from_kind(cx, SymExprKind::Const(value))
395 }
396
397 pub(crate) fn var(cx: &mut SymCx, name: &str) -> Self {
398 let symbol = cx.intern(name);
399 Self::get_var(cx, symbol)
400 }
401
402 pub(crate) fn get_var(cx: &mut SymCx, symbol: Symbol) -> Self {
403 Self::from_kind(cx, SymExprKind::Var(symbol))
404 }
405
406 pub(crate) fn gas_left(cx: &mut SymCx, id: usize) -> Self {
407 let symbol = cx.intern(&format!("gasleft_{id}"));
408 Self::from_kind(cx, SymExprKind::GasLeft(symbol))
409 }
410
411 pub(crate) fn not(cx: &mut SymCx, value: Self) -> Self {
412 match value.kind() {
413 SymExprKind::Const(value) => Self::constant(cx, !*value),
414 SymExprKind::Not(value) => value.clone(),
415 _ => Self::from_kind(cx, SymExprKind::Not(value)),
416 }
417 }
418
419 pub(crate) fn binop(cx: &mut SymCx, binop: SymBinOp, left: Self, right: Self) -> Self {
420 match binop {
421 SymBinOp::Add => match (left.kind(), right.kind()) {
422 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
423 Self::constant(cx, binop.eval(*left_value, *right_value))
425 }
426 (SymExprKind::Const(value), _) if value.is_zero() => right,
428 (_, SymExprKind::Const(value)) if value.is_zero() => left,
430 (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
432 if *value == U256::MAX
433 && let Some(condition) = if left.as_const() == Some(U256::MAX) {
434 right.bitwise_bool_word_condition(cx)
435 } else {
436 left.bitwise_bool_word_condition(cx)
437 } =>
438 {
439 let zero = Self::zero(cx);
440 let max = Self::constant(cx, U256::MAX);
441 Self::ite(cx, condition, zero, max)
442 }
443 (SymExprKind::Const(value), _)
445 if let Some(condition) = right.bitwise_bool_word_condition(cx) =>
446 {
447 let incremented = Self::constant(cx, value.wrapping_add(U256::ONE));
448 let value = Self::constant(cx, *value);
449 Self::ite(cx, condition, incremented, value)
450 }
451 (_, SymExprKind::Const(value))
452 if let Some(condition) = left.bitwise_bool_word_condition(cx) =>
453 {
454 let incremented = Self::constant(cx, value.wrapping_add(U256::ONE));
455 let value = Self::constant(cx, *value);
456 Self::ite(cx, condition, incremented, value)
457 }
458 _ => {
459 let (left, right) = Self::ordered_commutative_operands(left, right);
460 if let Some(value) = Self::add_with_const_ite(cx, &left, &right) {
461 value
462 } else if let Some(value) = Self::add_with_const_ite(cx, &right, &left) {
463 value
464 } else {
465 Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
466 }
467 }
468 },
469 SymBinOp::Sub => match (left.kind(), right.kind()) {
470 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
471 Self::constant(cx, binop.eval(*left_value, *right_value))
473 }
474 (_, SymExprKind::Const(value)) if value.is_zero() => left,
476 _ if left == right => Self::zero(cx),
478 (_, SymExprKind::Const(value))
480 if *value == U256::ONE
481 && let Some(condition) = left.bitwise_bool_word_condition(cx) =>
482 {
483 let zero = Self::zero(cx);
484 let max = Self::constant(cx, U256::MAX);
485 Self::ite(cx, condition, zero, max)
486 }
487 (SymExprKind::Const(value), _)
489 if let Some(condition) = right.bitwise_bool_word_condition(cx) =>
490 {
491 let decremented = Self::constant(cx, value.wrapping_sub(U256::ONE));
492 let value = Self::constant(cx, *value);
493 Self::ite(cx, condition, decremented, value)
494 }
495 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
496 },
497 SymBinOp::Mul => match (left.kind(), right.kind()) {
498 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
499 Self::constant(cx, binop.eval(*left_value, *right_value))
501 }
502 (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
504 if value.is_zero() =>
505 {
506 Self::zero(cx)
507 }
508 (SymExprKind::Const(value), _) if *value == U256::ONE => right,
510 (_, SymExprKind::Const(value)) if *value == U256::ONE => left,
512 _ => {
513 let (left, right) = Self::ordered_commutative_operands(left, right);
514 if let Some(condition) = left.direct_bool_word_condition(cx) {
515 let zero = Self::zero(cx);
517 Self::ite(cx, condition, right, zero)
518 } else if let Some(condition) = right.direct_bool_word_condition(cx) {
519 let zero = Self::zero(cx);
521 Self::ite(cx, condition, left, zero)
522 } else if let Some(shift) = right.as_const().and_then(power_of_two_shift) {
523 let shift = Self::constant(cx, U256::from(shift));
525 Self::binop(cx, SymBinOp::Shl, left, shift)
526 } else {
527 Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
528 }
529 }
530 },
531 SymBinOp::UDiv | SymBinOp::SDiv => match (left.kind(), right.kind()) {
532 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
533 Self::constant(cx, binop.eval(*left_value, *right_value))
535 }
536 (_, SymExprKind::Const(value)) if value.is_zero() => Self::zero(cx),
538 (_, SymExprKind::Const(value)) if *value == U256::ONE => left,
540 (
542 SymExprKind::BinOp(SymBinOp::Sub, value, low_bits),
543 SymExprKind::Const(divisor),
544 ) if binop == SymBinOp::UDiv
545 && let Some(shift) = power_of_two_shift(*divisor)
546 && low_masked_source(low_bits, shift) == Some(value) =>
547 {
548 let shift = Self::constant(cx, U256::from(shift));
549 Self::binop(cx, SymBinOp::Shr, value.clone(), shift)
550 }
551 (_, SymExprKind::Const(divisor))
553 if binop == SymBinOp::UDiv
554 && let Some(shift) = power_of_two_shift(*divisor) =>
555 {
556 let shift = Self::constant(cx, U256::from(shift));
557 Self::binop(cx, SymBinOp::Shr, left, shift)
558 }
559 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
560 },
561 SymBinOp::URem | SymBinOp::SRem => match (left.kind(), right.kind()) {
562 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
563 Self::constant(cx, binop.eval(*left_value, *right_value))
565 }
566 (_, SymExprKind::Const(value)) if value.is_zero() => Self::zero(cx),
568 (_, SymExprKind::Const(value)) if *value == U256::ONE => Self::zero(cx),
570 (_, SymExprKind::Const(divisor))
572 if binop == SymBinOp::URem
573 && let Some(bits) = power_of_two_shift(*divisor) =>
574 {
575 Self::and_const(cx, left, mask_bits(U256::MAX, bits))
576 }
577 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
578 },
579 SymBinOp::And => match (left.kind(), right.kind()) {
580 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
581 Self::constant(cx, binop.eval(*left_value, *right_value))
583 }
584 (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
586 if value.is_zero() =>
587 {
588 Self::zero(cx)
589 }
590 (SymExprKind::Const(value), _) if *value == U256::MAX => right,
592 (_, SymExprKind::Const(value)) if *value == U256::MAX => left,
594 _ if left == right => left,
596 (SymExprKind::Const(mask), _) => Self::and_const(cx, right, *mask),
597 (_, SymExprKind::Const(mask)) => Self::and_const(cx, left, *mask),
598 _ => Self::commutative_binop(cx, binop, left, right),
599 },
600 SymBinOp::Or => match (left.kind(), right.kind()) {
601 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
602 Self::constant(cx, binop.eval(*left_value, *right_value))
604 }
605 (SymExprKind::Const(value), _) if value.is_zero() => right,
607 (_, SymExprKind::Const(value)) if value.is_zero() => left,
609 _ if left == right => left,
611 _ if let Some(value) = Self::or_with_absorbing_ite(cx, &left, right.clone()) => {
612 value
613 }
614 _ if let Some(value) = Self::or_with_absorbing_ite(cx, &right, left.clone()) => {
615 value
616 }
617 _ => Self::or(cx, left, right),
618 },
619 SymBinOp::Xor => match (left.kind(), right.kind()) {
620 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
621 Self::constant(cx, binop.eval(*left_value, *right_value))
623 }
624 (SymExprKind::Const(value), _) if value.is_zero() => right,
626 (_, SymExprKind::Const(value)) if value.is_zero() => left,
628 _ if left == right => Self::zero(cx),
630 _ => {
631 let (left, right) = Self::ordered_commutative_operands(left, right);
632 if let Some(value) = Self::xor_with_shared_operand(&left, &right)
634 .or_else(|| Self::xor_with_shared_operand(&right, &left))
635 {
636 value
637 } else if let Some(value) = Self::xor_with_bool_select(cx, &left, &right) {
639 value
640 } else if let Some(value) = Self::xor_with_bool_select(cx, &right, &left) {
641 value
642 } else if let Some(value) = Self::xor_with_zero_ite(cx, &left, &right) {
644 value
645 } else if let Some(value) = Self::xor_with_zero_ite(cx, &right, &left) {
646 value
647 } else {
648 Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
649 }
650 }
651 },
652 SymBinOp::Shl => match (left.kind(), right.kind()) {
653 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
654 Self::constant(cx, binop.eval(*left_value, *right_value))
656 }
657 (_, SymExprKind::Const(value)) if value.is_zero() => left,
659 (SymExprKind::Const(value), _) if value.is_zero() => Self::zero(cx),
661 (_, SymExprKind::Const(value)) if *value >= U256::from(256) => Self::zero(cx),
663 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
664 },
665 SymBinOp::Shr => match (left.kind(), right.kind()) {
666 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
667 Self::constant(cx, binop.eval(*left_value, *right_value))
669 }
670 (_, SymExprKind::Const(value)) if value.is_zero() => left,
672 (SymExprKind::Const(value), _) if value.is_zero() => Self::zero(cx),
674 (_, SymExprKind::Const(value)) => Self::shr_const(cx, left, *value),
675 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
676 },
677 SymBinOp::Sar => match (left.kind(), right.kind()) {
678 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
679 Self::constant(cx, binop.eval(*left_value, *right_value))
681 }
682 (_, SymExprKind::Const(value)) if value.is_zero() => left,
684 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
685 },
686 }
687 }
688
689 pub(crate) fn ternop(
690 cx: &mut SymCx,
691 ternop: SymTernOp,
692 left: Self,
693 right: Self,
694 modulus: Self,
695 ) -> Self {
696 match (left.kind(), right.kind(), modulus.kind()) {
697 (_, _, SymExprKind::Const(modulus)) if modulus.is_zero() || *modulus == U256::ONE => {
698 Self::zero(cx)
700 }
701 (SymExprKind::Const(left), SymExprKind::Const(right), SymExprKind::Const(modulus)) => {
702 Self::constant(cx, ternop.eval(*left, *right, *modulus))
704 }
705 (_, _, SymExprKind::Const(modulus))
707 if let Some(bits) = power_of_two_shift(*modulus) =>
708 {
709 let binop = match ternop {
710 SymTernOp::AddMod => SymBinOp::Add,
711 SymTernOp::MulMod => SymBinOp::Mul,
712 };
713 let value = Self::binop(cx, binop, left, right);
714 Self::and_const(cx, value, mask_bits(U256::MAX, bits))
715 }
716 _ => {
717 let (left, right) = Self::ordered_commutative_operands(left, right);
719 Self::from_kind(cx, SymExprKind::TernOp(ternop, left, right, modulus))
720 }
721 }
722 }
723
724 pub(crate) fn ite(
725 cx: &mut SymCx,
726 condition: SymBoolExpr,
727 then_expr: Self,
728 else_expr: Self,
729 ) -> Self {
730 match condition.as_const() {
731 Some(true) => then_expr,
733 Some(false) => else_expr,
735 None if then_expr == else_expr => then_expr,
737 None if then_expr.as_const().is_some_and(|value| value.is_zero())
739 && Self::self_div_expr_matches_zero_check(&condition, &else_expr) =>
740 {
741 let condition = condition.not(cx);
742 Self::bool_word(cx, condition)
743 }
744 None if then_expr.as_const() == Some(U256::ONE)
746 && else_expr.bool_word_condition().as_ref() == Some(&condition) =>
747 {
748 else_expr
749 }
750 None if else_expr.as_const().is_some_and(|value| value.is_zero())
752 && then_expr.bool_word_condition().as_ref() == Some(&condition) =>
753 {
754 then_expr
755 }
756 None => Self::from_kind(cx, SymExprKind::Ite(condition, then_expr, else_expr)),
757 }
758 }
759
760 pub(crate) fn bool_word(cx: &mut SymCx, value: SymBoolExpr) -> Self {
761 let one = Self::one(cx);
762 let zero = Self::zero(cx);
763 Self::ite(cx, value, one, zero)
764 }
765
766 fn self_div_expr_matches_zero_check(cond: &SymBoolExpr, expr: &Self) -> bool {
767 let Some(zero_operand) = cond.zero_check_operand() else { return false };
768 let Some((numerator, denominator)) = expr.udiv_operands() else { return false };
769 numerator == zero_operand && denominator == zero_operand
770 }
771
772 pub(crate) fn keccak_symbol(cx: &mut SymCx, name: Symbol, len: Self, bytes: Vec<Self>) -> Self {
773 Self::from_kind(cx, SymExprKind::Keccak { name, len, bytes: bytes.into() })
774 }
775
776 pub(crate) fn hash_symbol(
777 cx: &mut SymCx,
778 name: Symbol,
779 algorithm: &'static str,
780 bytes: Vec<Self>,
781 ) -> Self {
782 Self::from_kind(cx, SymExprKind::Hash { name, algorithm, bytes: bytes.into() })
783 }
784
785 fn or(cx: &mut SymCx, left: Self, right: Self) -> Self {
786 if let Some(rebuilt) = Self::rebuild_from_or_terms(&left, &right) {
787 return rebuilt;
789 }
790 Self::commutative_binop(cx, SymBinOp::Or, left, right)
791 }
792
793 fn or_with_absorbing_ite(cx: &mut SymCx, conditional: &Self, other: Self) -> Option<Self> {
794 let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
795 return None;
796 };
797 if then_expr.as_const().is_some_and(|value| value.is_zero())
798 && else_expr.as_const() == Some(U256::MAX)
799 {
800 return Some(Self::ite(cx, condition.clone(), other, else_expr.clone()));
801 }
802 if then_expr.as_const() == Some(U256::MAX)
803 && else_expr.as_const().is_some_and(|value| value.is_zero())
804 {
805 return Some(Self::ite(cx, condition.clone(), then_expr.clone(), other));
806 }
807 None
808 }
809
810 fn add_with_const_ite(cx: &mut SymCx, other: &Self, conditional: &Self) -> Option<Self> {
811 if other.as_const().is_some() {
812 return None;
813 }
814 let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
815 return None;
816 };
817 if then_expr.as_const().is_none() || else_expr.as_const().is_none() {
818 return None;
819 }
820 if !Self::duplicating_branchless_rewrite_fits(other, conditional) {
821 return None;
822 }
823 let then_expr = Self::binop(cx, SymBinOp::Add, other.clone(), then_expr.clone());
824 let else_expr = Self::binop(cx, SymBinOp::Add, other.clone(), else_expr.clone());
825 Some(Self::ite(cx, condition.clone(), then_expr, else_expr))
826 }
827
828 fn xor_with_bool_select(cx: &mut SymCx, base: &Self, selector: &Self) -> Option<Self> {
829 let SymExprKind::BinOp(SymBinOp::Mul, left, right) = selector.kind() else { return None };
830 let (condition_word, selected) = match left.kind() {
831 SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right) if delta_left == base => {
832 (right, delta_right.clone())
833 }
834 SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right) if delta_right == base => {
835 (right, delta_left.clone())
836 }
837 _ => match right.kind() {
838 SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right)
839 if delta_left == base =>
840 {
841 (left, delta_right.clone())
842 }
843 SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right)
844 if delta_right == base =>
845 {
846 (left, delta_left.clone())
847 }
848 _ => return None,
849 },
850 };
851 let condition = condition_word.bitwise_bool_word_condition(cx)?;
852 Some(Self::ite(cx, condition, selected, base.clone()))
853 }
854
855 fn xor_with_shared_operand(base: &Self, nested: &Self) -> Option<Self> {
856 let SymExprKind::BinOp(SymBinOp::Xor, left, right) = nested.kind() else { return None };
857 if left == base {
858 Some(right.clone())
859 } else if right == base {
860 Some(left.clone())
861 } else {
862 None
863 }
864 }
865
866 fn xor_with_zero_ite(cx: &mut SymCx, base: &Self, conditional: &Self) -> Option<Self> {
867 let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
868 return None;
869 };
870 if then_expr.as_const().is_some_and(|value| value.is_zero()) {
871 if !Self::duplicating_branchless_rewrite_fits(base, conditional) {
872 return None;
873 }
874 let selected = Self::binop(cx, SymBinOp::Xor, base.clone(), else_expr.clone());
875 return Some(Self::ite(cx, condition.clone(), base.clone(), selected));
876 }
877 if else_expr.as_const().is_some_and(|value| value.is_zero()) {
878 if !Self::duplicating_branchless_rewrite_fits(base, conditional) {
879 return None;
880 }
881 let selected = Self::binop(cx, SymBinOp::Xor, base.clone(), then_expr.clone());
882 return Some(Self::ite(cx, condition.clone(), selected, base.clone()));
883 }
884 None
885 }
886
887 fn duplicating_branchless_rewrite_fits(operand: &Self, conditional: &Self) -> bool {
893 let mut counter = UnfoldedNodeCounter::new();
894 let Some(operand_nodes) = counter.expr_nodes(operand) else {
895 return false;
896 };
897 let Some(conditional_nodes) = counter.expr_nodes(conditional) else {
898 return false;
899 };
900
901 operand_nodes
902 .checked_mul(2)
903 .and_then(|duplicated| conditional_nodes.checked_add(duplicated))
904 .and_then(|nodes| nodes.checked_add(2))
906 .is_some_and(|nodes| nodes <= MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES)
907 }
908
909 fn commutative_binop(cx: &mut SymCx, op: SymBinOp, left: Self, right: Self) -> Self {
910 let (left, right) = Self::ordered_commutative_operands(left, right);
912 Self::from_kind(cx, SymExprKind::BinOp(op, left, right))
913 }
914
915 pub(in crate::runtime::expr) fn ordered_commutative_operands(
916 left: Self,
917 right: Self,
918 ) -> (Self, Self) {
919 match left.complexity().cmp(&right.complexity()) {
920 std::cmp::Ordering::Less => (right, left),
922 std::cmp::Ordering::Greater => (left, right),
923 std::cmp::Ordering::Equal if right.kind.stable_hash_cmp(&left.kind).is_lt() => {
924 (right, left)
925 }
926 std::cmp::Ordering::Equal => (left, right),
927 }
928 }
929
930 pub(in crate::runtime) fn sort_interned_factors(factors: &mut [Self]) {
935 factors.sort_unstable_by(|left, right| left.kind.identity_cmp(&right.kind));
936 }
937
938 fn complexity(&self) -> usize {
939 match self.kind() {
940 SymExprKind::Const(_) => 0,
941 SymExprKind::Not(_) => 1,
942 SymExprKind::BinOp(..) => 2,
943 SymExprKind::TernOp(..) => 3,
944 _ => 4,
945 }
946 }
947
948 fn and_const(cx: &mut SymCx, expr: Self, mask: U256) -> Self {
949 if mask.is_zero() {
950 return Self::zero(cx);
952 }
953 if mask == U256::MAX {
954 return expr;
956 }
957
958 match expr.kind() {
959 SymExprKind::Const(value) => Self::constant(cx, *value & mask),
961 SymExprKind::BinOp(SymBinOp::Or, left, right) => {
962 let left = Self::and_const(cx, left.clone(), mask);
964 let right = Self::and_const(cx, right.clone(), mask);
965 Self::binop(cx, SymBinOp::Or, left, right)
966 }
967 SymExprKind::BinOp(SymBinOp::Shl, _, shift)
968 if mask_low_bits(mask).is_some_and(|bits| {
969 shift
970 .as_const()
971 .and_then(|shift| usize::try_from(shift).ok())
972 .is_some_and(|shift| bits <= shift)
973 }) =>
974 {
975 Self::zero(cx)
977 }
978 SymExprKind::BinOp(SymBinOp::And, left, right) => {
979 if right.as_const() == Some(mask) {
980 Self::and_const(cx, left.clone(), mask)
982 } else if left == right {
983 Self::and_const(cx, left.clone(), mask)
985 } else {
986 let mask = Self::constant(cx, mask);
987 Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::And, expr, mask))
988 }
989 }
990 _ => {
991 let mask = Self::constant(cx, mask);
992 Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::And, expr, mask))
993 }
994 }
995 }
996
997 fn shr_const(cx: &mut SymCx, expr: Self, shift: U256) -> Self {
998 if shift.is_zero() {
999 return expr;
1001 }
1002 if shift >= U256::from(256) {
1003 return Self::zero(cx);
1005 }
1006
1007 let shift = usize::try_from(shift).expect("shift is less than 256");
1008 if expr.unsigned_bits() <= shift {
1009 return Self::zero(cx);
1011 }
1012
1013 if let SymExprKind::BinOp(SymBinOp::Shl, inner, left_shift) = expr.kind()
1014 && left_shift.as_const() == Some(U256::from(shift))
1015 && inner.unsigned_bits() <= 256 - shift
1016 {
1017 return inner.clone();
1019 }
1020
1021 if let SymExprKind::BinOp(SymBinOp::Or, left, right) = expr.kind() {
1022 let left = Self::shr_const(cx, left.clone(), U256::from(shift));
1025 let right = Self::shr_const(cx, right.clone(), U256::from(shift));
1026 if left.as_const().is_some_and(|value| value.is_zero()) {
1027 return right;
1028 }
1029 if right.as_const().is_some_and(|value| value.is_zero()) {
1030 return left;
1031 }
1032 }
1033
1034 let shift = Self::constant(cx, U256::from(shift));
1035 Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::Shr, expr, shift))
1036 }
1037
1038 fn rebuild_from_or_terms(left: &Self, right: &Self) -> Option<Self> {
1039 let mut terms = Vec::new();
1040 left.push_or_terms(&mut terms);
1041 right.push_or_terms(&mut terms);
1042 Self::rebuild_from_extracted_byte_terms(&terms)
1043 .or_else(|| Self::rebuild_from_shifted_word_fragments(&terms))
1044 }
1045
1046 pub(in crate::runtime) fn push_or_terms<'a>(&'a self, terms: &mut Vec<&'a Self>) {
1047 match self.kind() {
1048 SymExprKind::BinOp(SymBinOp::Or, left, right) => {
1049 left.push_or_terms(terms);
1050 right.push_or_terms(terms);
1051 }
1052 _ => terms.push(self),
1053 }
1054 }
1055
1056 fn rebuild_from_extracted_byte_terms(terms: &[&Self]) -> Option<Self> {
1057 if terms.len() <= 1 {
1058 return None;
1059 }
1060
1061 let mut source = None;
1062 let mut seen = [false; 32];
1063 for term in terms {
1064 if term.as_const().is_some_and(|value| value.is_zero()) {
1065 continue;
1066 }
1067 let (term_source, index) = term.extracted_shifted_byte_term()?;
1068 match &source {
1069 Some(source) if source != &term_source => return None,
1070 Some(_) => {}
1071 None => source = Some(term_source),
1072 }
1073 seen[index] = true;
1074 }
1075
1076 let source = source?;
1077 for (index, seen) in seen.into_iter().enumerate() {
1078 if !seen && source.known_byte(index) != Some(0) {
1079 return None;
1080 }
1081 }
1082 Some(source)
1083 }
1084
1085 fn extracted_shifted_byte_term(&self) -> Option<(Self, usize)> {
1086 match self.kind() {
1087 SymExprKind::BinOp(SymBinOp::Shl, byte, shift) => {
1088 let shift = shift.as_const()?;
1089 let Ok(shift) = usize::try_from(shift) else { return None };
1090 if shift % 8 != 0 || shift > 248 {
1091 return None;
1092 }
1093 let index = 31 - shift / 8;
1094 let source = byte.extracted_unshifted_byte_source(index)?;
1095 Some((source, index))
1096 }
1097 _ => self.extracted_unshifted_byte_source(31).map(|source| (source, 31)),
1098 }
1099 }
1100
1101 fn extracted_unshifted_byte_source(&self, index: usize) -> Option<Self> {
1102 let expr = self.strip_low_byte_mask();
1103 if index == 31 {
1104 return Some(expr.clone());
1105 }
1106 let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1107 let shift = shift.as_const()?;
1108 (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1109 }
1110
1111 fn rebuild_from_shifted_word_fragments(terms: &[&Self]) -> Option<Self> {
1112 if terms.len() != 2 {
1113 return None;
1114 }
1115
1116 let left_low = terms[0].low_word_fragment();
1117 let right_low = terms[1].low_word_fragment();
1118 let left_high = terms[0].shifted_high_word_fragment();
1119 let right_high = terms[1].shifted_high_word_fragment();
1120 match (left_low, right_low, left_high, right_high) {
1121 (Some((low_source, low_bits)), None, None, Some((high_source, high_bits)))
1122 | (None, Some((low_source, low_bits)), Some((high_source, high_bits)), None)
1123 if low_source == high_source && low_bits == high_bits =>
1124 {
1125 Some(low_source)
1126 }
1127 _ => None,
1128 }
1129 }
1130
1131 fn low_word_fragment(&self) -> Option<(Self, usize)> {
1132 let SymExprKind::BinOp(SymBinOp::And, left, right) = self.kind() else { return None };
1133 let mask = right.as_const()?;
1134 mask_low_bits(mask).map(|bits| (left.clone(), bits))
1135 }
1136
1137 fn shifted_high_word_fragment(&self) -> Option<(Self, usize)> {
1138 let SymExprKind::BinOp(SymBinOp::Shl, value, shift) = self.kind() else { return None };
1139 let bits = shift.as_const().and_then(|shift| usize::try_from(shift).ok())?;
1140 if bits == 0 || bits >= 256 {
1141 return None;
1142 }
1143
1144 let (source, source_shift, width) = value.shifted_low_fragment_source()?;
1145 (source_shift == bits && width == 256 - bits).then_some((source, bits))
1146 }
1147
1148 fn shifted_low_fragment_source(&self) -> Option<(Self, usize, usize)> {
1149 let SymExprKind::BinOp(SymBinOp::And, left, right) = self.kind() else { return None };
1150 let mask = right.as_const()?;
1151 Self::shifted_low_fragment_source_with_mask(left, mask)
1152 }
1153
1154 fn shifted_low_fragment_source_with_mask(
1155 value: &Self,
1156 mask: U256,
1157 ) -> Option<(Self, usize, usize)> {
1158 let width = mask_low_bits(mask)?;
1159 match value.kind() {
1160 SymExprKind::BinOp(SymBinOp::Shr, source, shift) => {
1161 let shift = shift.as_const().and_then(|shift| usize::try_from(shift).ok())?;
1162 Some((source.clone(), shift, width))
1163 }
1164 _ => Some((value.clone(), 0, width)),
1165 }
1166 }
1167
1168 pub(crate) fn low_byte(self, cx: &mut SymCx) -> Self {
1169 if let Some(word) = self.as_const() {
1170 return Self::constant(cx, U256::from(word.to::<u8>()));
1171 }
1172 let mask = Self::constant(cx, U256::from(0xff));
1173 Self::binop(cx, SymBinOp::And, self, mask)
1174 }
1175
1176 pub(crate) fn into_byte_exprs(self, cx: &mut SymCx) -> Vec<Self> {
1177 SymBytes::word(cx, self).materialize(cx)
1178 }
1179
1180 pub(crate) fn into_bytes(self, cx: &mut SymCx) -> SymBytes {
1181 SymBytes::word(cx, self)
1182 }
1183
1184 pub(crate) fn from_bytes(cx: &mut SymCx, bytes: impl IntoIterator<Item = Self>) -> Self {
1185 let bytes = bytes.into_iter().collect::<Vec<_>>();
1186 if let Ok(concrete) = concrete_expr_bytes(&bytes, "symbolic word bytes") {
1187 let mut word = [0u8; 32];
1188 for (idx, byte) in concrete.into_iter().take(32).enumerate() {
1189 word[idx] = byte;
1190 }
1191 return Self::constant(cx, U256::from_be_bytes(word));
1192 }
1193
1194 if let Some(expr) = word_from_extracted_bytes(&bytes) {
1195 return expr;
1196 }
1197
1198 let mut expr = Self::zero(cx);
1199 for (idx, byte) in bytes.into_iter().take(32).enumerate() {
1200 let shift = (31 - idx) * 8;
1201 let byte = byte.low_byte(cx);
1202 let byte = if shift == 0 {
1203 byte
1204 } else {
1205 let shift = Self::constant(cx, U256::from(shift));
1206 Self::binop(cx, SymBinOp::Shl, byte, shift)
1207 };
1208 expr = Self::binop(cx, SymBinOp::Or, expr, byte);
1209 }
1210 expr
1211 }
1212
1213 pub(crate) fn as_const(&self) -> Option<U256> {
1214 match self.kind() {
1215 SymExprKind::Const(value) => Some(*value),
1216 _ => None,
1217 }
1218 }
1219
1220 pub(crate) fn eval(&self) -> Option<U256> {
1221 self.eval_model_if_complete(&NoopModel).ok().flatten()
1222 }
1223
1224 pub(crate) fn eval_model<M: SymbolicModelLookup + ?Sized>(
1225 &self,
1226 model: &M,
1227 ) -> Result<U256, SymbolicError> {
1228 ModelEvaluator::new(model).eval_word(self)
1229 }
1230
1231 pub(crate) fn eval_model_if_complete<M: SymbolicModelLookup + ?Sized>(
1232 &self,
1233 model: &M,
1234 ) -> Result<Option<U256>, SymbolicError> {
1235 let mut vars = SymbolicVars::default();
1236 self.collect_eval_vars(&mut vars);
1237 if vars.iter().copied().all(|var| model.contains_name(var)) {
1238 self.eval_model(model).map(Some)
1239 } else {
1240 Ok(None)
1241 }
1242 }
1243
1244 pub(crate) fn assign_model_value(&self, model: &mut SymbolicModel, value: U256) -> bool {
1245 match self.kind() {
1246 SymExprKind::Const(existing) => *existing == value,
1247 SymExprKind::Var(var) => {
1248 if let Some(existing) = model.get(var) {
1249 *existing == value
1250 } else {
1251 model.insert(*var, value);
1252 true
1253 }
1254 }
1255 SymExprKind::GasLeft(symbol) => {
1256 if let Some(existing) = model.get(symbol) {
1257 *existing == value
1258 } else {
1259 model.insert(*symbol, value);
1260 true
1261 }
1262 }
1263 _ => false,
1264 }
1265 }
1266
1267 pub(crate) fn bool_word_condition(&self) -> Option<SymBoolExpr> {
1268 let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() else {
1269 return None;
1270 };
1271 Self::bool_word_condition_from_parts(condition, then_expr, else_expr)
1272 }
1273
1274 pub(in crate::runtime) fn bitwise_bool_word_condition(
1275 &self,
1276 cx: &mut SymCx,
1277 ) -> Option<SymBoolExpr> {
1278 let mut pending = vec![self.clone()];
1279 let mut seen_words = HashSet::<Self>::default();
1280 let mut leaf_conditions = IndexSet::<SymBoolExpr>::default();
1281 let mut bit_widths = HashMap::default();
1282 let mut remaining = MAX_BITWISE_BOOL_WORD_VISITS;
1283 while let Some(word) = pending.pop() {
1284 if !seen_words.insert(word.clone()) {
1285 continue;
1286 }
1287 if remaining == 0 {
1288 return None;
1289 }
1290 remaining -= 1;
1291
1292 if let Some(condition) = word.direct_bool_word_condition(cx) {
1293 leaf_conditions.insert(condition);
1294 continue;
1295 }
1296 if let SymExprKind::BinOp(SymBinOp::Or, left, right) = word.kind() {
1297 pending.push(right.clone());
1298 pending.push(left.clone());
1299 continue;
1300 }
1301
1302 if word.unsigned_bits_cached(&mut bit_widths, &mut remaining) == Some(1) {
1303 let zero = Self::zero(cx);
1304 let (word, zero) = Self::ordered_commutative_operands(word, zero);
1305 let zero_check =
1306 SymBoolExpr::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, word, zero));
1307 leaf_conditions.insert(zero_check.not(cx));
1308 continue;
1309 }
1310 return None;
1311 }
1312
1313 Some(SymBoolExpr::or(cx, leaf_conditions.into_iter().collect()))
1314 }
1315
1316 fn direct_bool_word_condition(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1322 let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() else {
1323 return None;
1324 };
1325 match (then_expr.as_const(), else_expr.as_const()) {
1326 (Some(then_value), Some(else_value))
1327 if then_value == U256::ONE && else_value.is_zero() =>
1328 {
1329 Some(condition.clone())
1330 }
1331 (Some(then_value), Some(else_value))
1332 if then_value.is_zero() && else_value == U256::ONE =>
1333 {
1334 Some(condition.clone().not(cx))
1335 }
1336 _ => None,
1337 }
1338 }
1339
1340 fn bool_word_condition_from_parts(
1341 condition: &SymBoolExpr,
1342 then_expr: &Self,
1343 else_expr: &Self,
1344 ) -> Option<SymBoolExpr> {
1345 match (then_expr.as_const(), else_expr.as_const()) {
1346 (Some(then_value), Some(else_value))
1347 if then_value == U256::ONE && else_value.is_zero() =>
1348 {
1349 Some(condition.clone())
1350 }
1351 (Some(then_value), Some(else_value))
1352 if then_value.is_zero() && else_value == U256::ONE =>
1353 {
1354 None
1355 }
1356 _ => None,
1357 }
1358 }
1359
1360 pub(crate) fn truth(&self) -> Option<bool> {
1361 self.as_const().map(|value| !value.is_zero())
1362 }
1363
1364 pub(crate) fn into_zero_bool(self, cx: &mut SymCx) -> SymBoolExpr {
1365 match self.kind() {
1366 SymExprKind::Const(value) => SymBoolExpr::constant(cx, value.is_zero()),
1367 SymExprKind::Ite(condition, then_expr, else_expr) => {
1368 match Self::bool_word_condition_from_parts(condition, then_expr, else_expr) {
1369 Some(condition) => SymBoolExpr::not_bool(cx, condition),
1370 None => {
1371 let zero = Self::zero(cx);
1372 SymBoolExpr::eq(cx, self, zero)
1373 }
1374 }
1375 }
1376 _ => {
1377 let zero = Self::zero(cx);
1378 SymBoolExpr::eq(cx, self, zero)
1379 }
1380 }
1381 }
1382
1383 pub(crate) fn nonzero_bool(self, cx: &mut SymCx) -> SymBoolExpr {
1384 let zero = self.into_zero_bool(cx);
1385 SymBoolExpr::not_bool(cx, zero)
1386 }
1387
1388 pub(crate) fn as_const_or(&self, reason: &'static str) -> Result<U256, SymbolicError> {
1389 self.as_const().ok_or(SymbolicError::Unsupported(reason))
1390 }
1391
1392 pub(crate) fn as_usize_or(&self, reason: &'static str) -> Result<usize, SymbolicError> {
1393 let value = self.as_const_or(reason)?;
1394 usize::try_from(value).map_err(|_| SymbolicError::Unsupported(reason))
1395 }
1396
1397 pub(crate) fn contains_keccak(&self) -> bool {
1398 self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::Keccak { .. }))
1399 }
1400
1401 pub(crate) fn contains_gasleft(&self) -> bool {
1402 self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::GasLeft(_)))
1403 }
1404
1405 pub(crate) fn contains_udiv(&self) -> bool {
1406 self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::BinOp(SymBinOp::UDiv, _, _)))
1407 }
1408
1409 pub(crate) fn contains_ite(&self) -> bool {
1410 let mut visited = HashSet::<&Self>::default();
1411 self.contains_ite_cached(&mut visited, false)
1412 }
1413
1414 fn contains_ite_cached<'a>(&'a self, visited: &mut HashSet<&'a Self>, memoize: bool) -> bool {
1415 match self.kind() {
1416 SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => return false,
1417 SymExprKind::Ite(_, _, _) => return true,
1418 _ => {}
1419 }
1420 if memoize && !visited.insert(self) {
1421 return false;
1422 }
1423
1424 match self.kind() {
1425 SymExprKind::Keccak { len, bytes, .. } => {
1426 len.contains_ite_cached(visited, true)
1427 || bytes.iter().any(|byte| byte.contains_ite_cached(visited, true))
1428 }
1429 SymExprKind::Hash { bytes, .. } => {
1430 bytes.iter().any(|byte| byte.contains_ite_cached(visited, true))
1431 }
1432 SymExprKind::Not(value) => value.contains_ite_cached(visited, true),
1433 SymExprKind::BinOp(_, left, right) => {
1434 left.contains_ite_cached(visited, true) || right.contains_ite_cached(visited, true)
1435 }
1436 SymExprKind::TernOp(_, left, right, modulus) => {
1437 left.contains_ite_cached(visited, true)
1438 || right.contains_ite_cached(visited, true)
1439 || modulus.contains_ite_cached(visited, true)
1440 }
1441 SymExprKind::Const(_)
1442 | SymExprKind::Var(_)
1443 | SymExprKind::GasLeft(_)
1444 | SymExprKind::Ite(_, _, _) => unreachable!("leaf expression handled before descent"),
1445 }
1446 }
1447
1448 pub(in crate::runtime) fn udiv_operands(&self) -> Option<(&Self, &Self)> {
1449 match self.kind() {
1450 SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => {
1451 Some((numerator, denominator))
1452 }
1453 _ => None,
1454 }
1455 }
1456
1457 pub(crate) fn collect_eval_vars(&self, vars: &mut SymbolicVars) {
1458 let _ = self.visit(&mut |expr| {
1459 if let Some(var) = expr.kind().get_eval_var() {
1460 vars.insert(var);
1461 }
1462 ControlFlow::<()>::Continue(())
1463 });
1464 }
1465
1466 pub(crate) fn known_byte(&self, index: usize) -> Option<u8> {
1467 debug_assert!(index < 32);
1468 match self.kind() {
1469 SymExprKind::Const(value) => Some(value.to_be_bytes::<32>()[index]),
1470 SymExprKind::Var(_)
1471 | SymExprKind::GasLeft(_)
1472 | SymExprKind::Keccak { .. }
1473 | SymExprKind::Hash { .. } => None,
1474 SymExprKind::Not(value) => value.known_byte(index).map(|byte| !byte),
1475 SymExprKind::Ite(_, then_expr, else_expr) => {
1476 let then_byte = then_expr.known_byte(index)?;
1477 let else_byte = else_expr.known_byte(index)?;
1478 (then_byte == else_byte).then_some(then_byte)
1479 }
1480 SymExprKind::BinOp(op, left, right) => match op {
1481 SymBinOp::And => match (left.known_byte(index), right.known_byte(index)) {
1482 (Some(left), Some(right)) => Some(left & right),
1483 (Some(0), _) | (_, Some(0)) => Some(0),
1484 _ => None,
1485 },
1486 SymBinOp::Or => Some(left.known_byte(index)? | right.known_byte(index)?),
1487 SymBinOp::Xor => Some(left.known_byte(index)? ^ right.known_byte(index)?),
1488 SymBinOp::Shl => {
1489 let shift = right.as_const()?;
1490 if shift >= U256::from(256) {
1491 return Some(0);
1492 }
1493 let shift = usize::try_from(shift).expect("checked byte shift");
1494 if shift % 8 != 0 {
1495 return None;
1496 }
1497 let source_index = index + shift / 8;
1498 if source_index >= 32 { Some(0) } else { left.known_byte(source_index) }
1499 }
1500 SymBinOp::Shr => {
1501 let shift = right.as_const()?;
1502 if shift >= U256::from(256) {
1503 return Some(0);
1504 }
1505 let shift = usize::try_from(shift).expect("checked byte shift");
1506 if shift % 8 != 0 {
1507 return None;
1508 }
1509 let byte_shift = shift / 8;
1510 if index < byte_shift { Some(0) } else { left.known_byte(index - byte_shift) }
1511 }
1512 SymBinOp::Add
1513 | SymBinOp::Sub
1514 | SymBinOp::Mul
1515 | SymBinOp::UDiv
1516 | SymBinOp::URem
1517 | SymBinOp::SDiv
1518 | SymBinOp::SRem
1519 | SymBinOp::Sar => None,
1520 },
1521 SymExprKind::TernOp(_, _, _, _) => None,
1522 }
1523 }
1524
1525 pub(crate) fn known_word(&self) -> Option<U256> {
1526 let mut word = [0u8; 32];
1527 for (idx, byte) in word.iter_mut().enumerate() {
1528 *byte = self.known_byte(idx)?;
1529 }
1530 Some(U256::from_be_bytes(word))
1531 }
1532
1533 pub(crate) fn unsigned_bits(&self) -> usize {
1534 let mut bit_widths = HashMap::default();
1535 let mut remaining = usize::MAX;
1536 self.unsigned_bits_cached(&mut bit_widths, &mut remaining).unwrap_or(256)
1537 }
1538
1539 fn unsigned_bits_cached(
1540 &self,
1541 bit_widths: &mut HashMap<Self, usize>,
1542 remaining: &mut usize,
1543 ) -> Option<usize> {
1544 if let Some(bits) = bit_widths.get(self) {
1545 return Some(*bits);
1546 }
1547
1548 let mut pending = vec![(self.clone(), false)];
1549 while let Some((expr, children_visited)) = pending.pop() {
1550 if bit_widths.contains_key(&expr) {
1551 continue;
1552 }
1553 if !children_visited {
1554 if *remaining == 0 {
1555 return None;
1556 }
1557 *remaining -= 1;
1558 pending.push((expr.clone(), true));
1559 match expr.kind() {
1560 SymExprKind::BinOp(SymBinOp::And, left, right)
1561 if right.as_const().is_some() =>
1562 {
1563 pending.push((left.clone(), false));
1564 }
1565 SymExprKind::BinOp(SymBinOp::Add | SymBinOp::Mul, left, right)
1566 | SymExprKind::Ite(_, left, right) => {
1567 pending.push((right.clone(), false));
1568 pending.push((left.clone(), false));
1569 }
1570 SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, left, right)
1571 if right
1572 .as_const()
1573 .and_then(|shift| usize::try_from(shift).ok())
1574 .is_some() =>
1575 {
1576 pending.push((left.clone(), false));
1577 }
1578 SymExprKind::BinOp(SymBinOp::UDiv, left, _) => {
1579 pending.push((left.clone(), false));
1580 }
1581 SymExprKind::TernOp(_, _, _, modulus) => {
1582 pending.push((modulus.clone(), false));
1583 }
1584 _ => {}
1585 }
1586 continue;
1587 }
1588
1589 let bits = match expr.kind() {
1590 SymExprKind::Const(value) => value.bit_len().max(1),
1591 SymExprKind::BinOp(SymBinOp::And, left, right) => {
1592 if let Some(mask) = right.as_const() {
1593 bit_widths[left].min(mask.bit_len())
1594 } else {
1595 256
1596 }
1597 }
1598 SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1599 bit_widths[left].max(bit_widths[right]).saturating_add(1).min(256)
1600 }
1601 SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1602 bit_widths[left].saturating_add(bit_widths[right]).min(256)
1603 }
1604 SymExprKind::BinOp(SymBinOp::Shl, left, right) => right
1605 .as_const()
1606 .and_then(|shift| usize::try_from(shift).ok())
1607 .map_or(256, |shift| bit_widths[left].saturating_add(shift).min(256)),
1608 SymExprKind::BinOp(SymBinOp::Shr, left, right) => right
1609 .as_const()
1610 .and_then(|shift| usize::try_from(shift).ok())
1611 .map_or(256, |shift| bit_widths[left].saturating_sub(shift).max(1)),
1612 SymExprKind::BinOp(SymBinOp::UDiv, left, _) => bit_widths[left],
1613 SymExprKind::TernOp(_, _, _, modulus) => bit_widths[modulus],
1614 SymExprKind::Ite(_, left, right) => bit_widths[left].max(bit_widths[right]),
1615 _ => 256,
1616 };
1617 bit_widths.insert(expr, bits);
1618 }
1619 bit_widths.get(self).copied()
1620 }
1621
1622 pub(crate) fn extracted_byte(&self, cx: &mut SymCx, index: usize) -> Self {
1623 debug_assert!(index < 32);
1624 let shift = Self::constant(cx, U256::from((31 - index) * 8));
1625 let shifted = Self::binop(cx, SymBinOp::Shr, self.clone(), shift);
1626 let mask = Self::constant(cx, U256::from(0xff));
1627 Self::binop(cx, SymBinOp::And, shifted, mask)
1628 }
1629
1630 pub(crate) fn extracted_byte_source(&self, index: usize) -> Option<Self> {
1631 let expr = self.strip_low_byte_mask();
1632 if index == 31 {
1633 return Some(expr.clone());
1634 }
1635 let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1636 let shift = shift.as_const()?;
1637 (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1638 }
1639
1640 pub(crate) fn strip_low_byte_mask(&self) -> &Self {
1641 match self.kind() {
1642 SymExprKind::BinOp(SymBinOp::And, left, right)
1643 if right.as_const() == Some(U256::from(0xff)) =>
1644 {
1645 left.strip_low_byte_mask()
1646 }
1647 _ => self,
1648 }
1649 }
1650
1651 pub(crate) fn byte_term(&self, cx: &mut SymCx, index: usize) -> Option<Self> {
1652 debug_assert!(index < 32);
1653
1654 match self.kind() {
1655 SymExprKind::Const(value) => {
1656 Some(Self::constant(cx, U256::from(value.to_be_bytes::<32>()[index])))
1657 }
1658 SymExprKind::Var(_)
1659 | SymExprKind::GasLeft(_)
1660 | SymExprKind::Keccak { .. }
1661 | SymExprKind::Hash { .. } => Some(self.extracted_byte(cx, index)),
1662 SymExprKind::Not(value) => {
1663 let value = value.byte_term(cx, index)?;
1664 Some(Self::not(cx, value))
1665 }
1666 SymExprKind::Ite(cond, then_expr, else_expr) => {
1667 let then_expr = then_expr.byte_term(cx, index)?;
1668 let else_expr = else_expr.byte_term(cx, index)?;
1669 Some(Self::ite(cx, cond.clone(), then_expr, else_expr))
1670 }
1671 SymExprKind::BinOp(op, left, right) => match op {
1672 SymBinOp::And => Self::binary_byte_term(
1673 cx,
1674 left,
1675 right,
1676 index,
1677 SymBinOp::And,
1678 |byte| byte == 0xff,
1679 |byte| byte == 0,
1680 ),
1681 SymBinOp::Or => Self::binary_byte_term(
1682 cx,
1683 left,
1684 right,
1685 index,
1686 SymBinOp::Or,
1687 |byte| byte == 0,
1688 |_| false,
1689 ),
1690 SymBinOp::Xor => Self::binary_byte_term(
1691 cx,
1692 left,
1693 right,
1694 index,
1695 SymBinOp::Xor,
1696 |byte| byte == 0,
1697 |_| false,
1698 ),
1699 SymBinOp::Shl => {
1700 let shift = right.eval()?;
1701 if shift >= U256::from(256) {
1702 return Some(Self::zero(cx));
1703 }
1704 let shift = usize::try_from(shift).expect("checked byte shift");
1705 if shift % 8 != 0 {
1706 return None;
1707 }
1708 let source_index = index + shift / 8;
1709 if source_index >= 32 {
1710 Some(Self::zero(cx))
1711 } else {
1712 left.byte_term(cx, source_index)
1713 }
1714 }
1715 SymBinOp::Shr => {
1716 let shift = right.eval()?;
1717 if shift >= U256::from(256) {
1718 return Some(Self::zero(cx));
1719 }
1720 let shift = usize::try_from(shift).expect("checked byte shift");
1721 if shift % 8 != 0 {
1722 return None;
1723 }
1724 let byte_shift = shift / 8;
1725 if index < byte_shift {
1726 Some(Self::zero(cx))
1727 } else {
1728 left.byte_term(cx, index - byte_shift)
1729 }
1730 }
1731 SymBinOp::Add
1732 | SymBinOp::Sub
1733 | SymBinOp::Mul
1734 | SymBinOp::UDiv
1735 | SymBinOp::URem
1736 | SymBinOp::SDiv
1737 | SymBinOp::SRem
1738 | SymBinOp::Sar => None,
1739 },
1740 SymExprKind::TernOp(_, _, _, _) => None,
1741 }
1742 }
1743
1744 fn binary_byte_term(
1745 cx: &mut SymCx,
1746 left: &Self,
1747 right: &Self,
1748 index: usize,
1749 op: SymBinOp,
1750 identity: impl Fn(u8) -> bool,
1751 absorbing: impl Fn(u8) -> bool,
1752 ) -> Option<Self> {
1753 let left = left.byte_term(cx, index)?;
1754 let right = right.byte_term(cx, index)?;
1755 match (left.byte_const(), right.byte_const()) {
1756 (Some(left), _) if absorbing(left) => Some(Self::constant(cx, U256::from(left))),
1757 (_, Some(right)) if absorbing(right) => Some(Self::constant(cx, U256::from(right))),
1758 (Some(left), _) if identity(left) => Some(right),
1759 (_, Some(right)) if identity(right) => Some(left),
1760 _ => Some(Self::binop(cx, op, left, right)),
1761 }
1762 }
1763
1764 pub(crate) fn byte_const(&self) -> Option<u8> {
1765 self.as_const().map(|value| value.to::<u8>())
1766 }
1767
1768 pub(crate) fn equality_forces_const(
1769 &self,
1770 value: U256,
1771 expr: &Self,
1772 context: &[SymBoolExpr],
1773 ) -> Option<U256> {
1774 if self == expr {
1775 return Some(value);
1776 }
1777 self.equality_forces_const_inner(value, expr, context)
1778 }
1779
1780 fn equality_forces_const_inner(
1781 &self,
1782 value: U256,
1783 expr: &Self,
1784 context: &[SymBoolExpr],
1785 ) -> Option<U256> {
1786 let mask = masked_expr_matches(self.kind(), expr)?;
1787 if value & !mask != U256::ZERO || !context_forces_masked_expr(context, expr, mask) {
1788 return None;
1789 }
1790 Some(value)
1791 }
1792
1793 pub(crate) fn nonzero_forces_const(
1794 &self,
1795 target: &Self,
1796 context: &[SymBoolExpr],
1797 ) -> Option<U256> {
1798 match self.kind() {
1799 SymExprKind::Const(_)
1800 | SymExprKind::Var(_)
1801 | SymExprKind::GasLeft(_)
1802 | SymExprKind::Keccak { .. }
1803 | SymExprKind::Hash { .. }
1804 | SymExprKind::Not(_) => None,
1805 SymExprKind::Ite(cond, then_expr, else_expr) => {
1806 if then_expr.eval().is_some_and(|value| !value.is_zero())
1807 && else_expr.eval().is_some_and(|value| value.is_zero())
1808 {
1809 cond.forces_expr_const_with_context(target, context)
1810 } else {
1811 None
1812 }
1813 }
1814 SymExprKind::BinOp(SymBinOp::Or, left, right) => {
1815 if left.eval().is_some_and(|value| value.is_zero()) {
1816 return right.nonzero_forces_const(target, context);
1817 }
1818 if right.eval().is_some_and(|value| value.is_zero()) {
1819 return left.nonzero_forces_const(target, context);
1820 }
1821 None
1822 }
1823 SymExprKind::BinOp(SymBinOp::And, left, right) => {
1824 if left.eval().is_some_and(|value| !value.is_zero()) {
1825 return right.nonzero_forces_const(target, context);
1826 }
1827 if right.eval().is_some_and(|value| !value.is_zero()) {
1828 return left.nonzero_forces_const(target, context);
1829 }
1830 None
1831 }
1832 SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, value, shift)
1833 if shift.eval().is_some_and(|shift| shift.is_zero()) =>
1834 {
1835 value.nonzero_forces_const(target, context)
1836 }
1837 SymExprKind::TernOp(_, _, _, _) => None,
1838 SymExprKind::BinOp(_, _, _) => None,
1839 }
1840 }
1841
1842 pub(crate) fn is_raw_gasleft(&self) -> bool {
1843 matches!(self.kind(), SymExprKind::GasLeft(_))
1844 }
1845
1846 pub(crate) fn add_const(cx: &mut SymCx, expr: Self, value: U256) -> Self {
1847 if value.is_zero() {
1848 return expr;
1849 }
1850 match expr.kind() {
1851 SymExprKind::Const(expr) => Self::constant(cx, expr.wrapping_add(value)),
1852 _ => {
1853 let value = Self::constant(cx, value);
1854 Self::binop(cx, SymBinOp::Add, expr, value)
1855 }
1856 }
1857 }
1858
1859 pub(crate) fn constant_difference(&self, other: &Self) -> Option<U256> {
1862 let mut differences = HashMap::default();
1863 let mut remaining = MAX_CONSTANT_DIFFERENCE_VISITS;
1864 self.constant_difference_cached(other, &mut differences, &mut remaining)
1865 }
1866
1867 fn constant_difference_cached(
1868 &self,
1869 other: &Self,
1870 differences: &mut HashMap<(Self, Self), Option<U256>>,
1871 remaining: &mut usize,
1872 ) -> Option<U256> {
1873 if self == other {
1874 return Some(U256::ZERO);
1875 }
1876 let key = (self.clone(), other.clone());
1877 if let Some(difference) = differences.get(&key) {
1878 return *difference;
1879 }
1880 *remaining = remaining.checked_sub(1)?;
1881
1882 let difference = match (self.kind(), other.kind()) {
1883 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
1884 Some(left.wrapping_sub(*right))
1885 }
1886 (
1887 SymExprKind::Ite(left_condition, left_then, left_else),
1888 SymExprKind::Ite(right_condition, right_then, right_else),
1889 ) if left_condition == right_condition => {
1890 let then_difference =
1891 left_then.constant_difference_cached(right_then, differences, remaining)?;
1892 let else_difference =
1893 left_else.constant_difference_cached(right_else, differences, remaining)?;
1894 (then_difference == else_difference).then_some(then_difference)
1895 }
1896 (SymExprKind::BinOp(SymBinOp::Add, value, constant), _)
1897 if let Some(constant) = constant.as_const() =>
1898 {
1899 value
1900 .constant_difference_cached(other, differences, remaining)
1901 .map(|difference| difference.wrapping_add(constant))
1902 }
1903 (SymExprKind::BinOp(SymBinOp::Sub, value, constant), _)
1904 if let Some(constant) = constant.as_const() =>
1905 {
1906 value
1907 .constant_difference_cached(other, differences, remaining)
1908 .map(|difference| difference.wrapping_sub(constant))
1909 }
1910 (_, SymExprKind::BinOp(SymBinOp::Add, value, constant))
1911 if let Some(constant) = constant.as_const() =>
1912 {
1913 self.constant_difference_cached(value, differences, remaining)
1914 .map(|difference| difference.wrapping_sub(constant))
1915 }
1916 (_, SymExprKind::BinOp(SymBinOp::Sub, value, constant))
1917 if let Some(constant) = constant.as_const() =>
1918 {
1919 self.constant_difference_cached(value, differences, remaining)
1920 .map(|difference| difference.wrapping_add(constant))
1921 }
1922 _ => None,
1923 };
1924 differences.insert(key, difference);
1925 difference
1926 }
1927
1928 pub(crate) fn visit<B>(
1930 &self,
1931 visitor: &mut impl FnMut(&Self) -> ControlFlow<B>,
1932 ) -> ControlFlow<B> {
1933 visitor(self)?;
1934 match self.kind() {
1935 SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => {}
1936 SymExprKind::Keccak { len, bytes, .. } => {
1937 len.visit(visitor)?;
1938 for byte in bytes.iter() {
1939 byte.visit(visitor)?;
1940 }
1941 }
1942 SymExprKind::Hash { bytes, .. } => {
1943 for byte in bytes.iter() {
1944 byte.visit(visitor)?;
1945 }
1946 }
1947 SymExprKind::Not(value) => value.visit(visitor)?,
1948 SymExprKind::BinOp(_, left, right) => {
1949 left.visit(visitor)?;
1950 right.visit(visitor)?;
1951 }
1952 SymExprKind::TernOp(_, left, right, modulus) => {
1953 left.visit(visitor)?;
1954 right.visit(visitor)?;
1955 modulus.visit(visitor)?;
1956 }
1957 SymExprKind::Ite(cond, left, right) => {
1958 cond.visit_exprs(visitor)?;
1959 left.visit(visitor)?;
1960 right.visit(visitor)?;
1961 }
1962 }
1963 ControlFlow::Continue(())
1964 }
1965
1966 pub(crate) fn visit_bool(&self, mut visitor: impl FnMut(&Self) -> bool) -> bool {
1967 self.visit(&mut |expr| {
1968 if visitor(expr) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
1969 })
1970 .is_break()
1971 }
1972
1973 pub(crate) fn fold(
1977 &self,
1978 cx: &mut SymCx,
1979 folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1980 ) -> Self {
1981 let mut folded = ExpressionFoldCache::default();
1982 self.fold_cached(cx, folder, &mut folded)
1983 }
1984
1985 pub(in crate::runtime::expr) fn fold_cached<'a>(
1986 &'a self,
1987 cx: &mut SymCx,
1988 folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1989 folded: &mut ExpressionFoldCache<'a>,
1990 ) -> Self {
1991 if let Some(expr) = folded.words.get(self) {
1992 return expr.clone();
1993 }
1994
1995 let expr = match self.kind() {
1996 SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => self.clone(),
1997 SymExprKind::Keccak { name, len, bytes } => {
1998 let len = len.fold_cached(cx, folder, folded);
1999 let bytes = bytes.iter().map(|byte| byte.fold_cached(cx, folder, folded)).collect();
2000 Self::keccak_symbol(cx, *name, len, bytes)
2001 }
2002 SymExprKind::Hash { name, algorithm, bytes } => {
2003 let bytes = bytes.iter().map(|byte| byte.fold_cached(cx, folder, folded)).collect();
2004 Self::hash_symbol(cx, *name, algorithm, bytes)
2005 }
2006 SymExprKind::Not(value) => {
2007 let value = value.fold_cached(cx, folder, folded);
2008 Self::not(cx, value)
2009 }
2010 SymExprKind::BinOp(op, left, right) => {
2011 let left = left.fold_cached(cx, folder, folded);
2012 let right = right.fold_cached(cx, folder, folded);
2013 Self::binop(cx, *op, left, right)
2014 }
2015 SymExprKind::TernOp(op, left, right, modulus) => {
2016 let left = left.fold_cached(cx, folder, folded);
2017 let right = right.fold_cached(cx, folder, folded);
2018 let modulus = modulus.fold_cached(cx, folder, folded);
2019 Self::ternop(cx, *op, left, right, modulus)
2020 }
2021 SymExprKind::Ite(condition, then_expr, else_expr) => {
2022 let condition = condition.fold_exprs_cached(cx, folder, folded);
2023 let then_expr = then_expr.fold_cached(cx, folder, folded);
2024 let else_expr = else_expr.fold_cached(cx, folder, folded);
2025 Self::ite(cx, condition, then_expr, else_expr)
2026 }
2027 };
2028 let expr = folder(cx, expr);
2029 folded.words.insert(self, expr.clone());
2030 expr
2031 }
2032
2033 #[cfg(test)]
2034 pub(crate) fn smt(&self, cx: &SymCx) -> String {
2035 let mut smt = String::new();
2036 self.write_smt(cx, &mut smt);
2037 smt
2038 }
2039
2040 pub(in crate::runtime::expr) fn write_smt(&self, cx: &SymCx, out: &mut String) {
2041 match self.kind() {
2042 SymExprKind::Const(value) => {
2043 let _ = write!(out, "(_ bv{value} 256)");
2044 }
2045 SymExprKind::Var(symbol)
2046 | SymExprKind::GasLeft(symbol)
2047 | SymExprKind::Keccak { name: symbol, .. }
2048 | SymExprKind::Hash { name: symbol, .. } => out.push_str(cx.symbol_name(*symbol)),
2049 SymExprKind::Not(value) => {
2050 out.push_str("(bvnot ");
2051 value.write_smt(cx, out);
2052 out.push(')');
2053 }
2054 SymExprKind::BinOp(op, left, right) => {
2055 let _ = write!(out, "({} ", op.smt());
2056 left.write_smt(cx, out);
2057 out.push(' ');
2058 right.write_smt(cx, out);
2059 out.push(')');
2060 }
2061 SymExprKind::TernOp(op, left, right, modulus) => {
2062 write_smt_wide_modular_arithmetic(cx, out, op.smt(), left, right, modulus);
2063 }
2064 SymExprKind::Ite(cond, left, right) => {
2065 out.push_str("(ite ");
2066 cond.write_smt(cx, out);
2067 out.push(' ');
2068 left.write_smt(cx, out);
2069 out.push(' ');
2070 right.write_smt(cx, out);
2071 out.push(')');
2072 }
2073 }
2074 }
2075}
2076
2077const MAX_BRANCHLESS_REWRITE_NODES: usize = 256;
2080const MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES: usize = 8 * 1024;
2081
2082struct UnfoldedNodeCounter {
2083 expr_nodes: HashMap<SymExpr, usize>,
2084 bool_nodes: HashMap<SymBoolExpr, usize>,
2085 remaining_unique_nodes: usize,
2086}
2087
2088impl UnfoldedNodeCounter {
2089 fn new() -> Self {
2090 Self {
2091 expr_nodes: HashMap::default(),
2092 bool_nodes: HashMap::default(),
2093 remaining_unique_nodes: MAX_BRANCHLESS_REWRITE_NODES,
2094 }
2095 }
2096
2097 fn expr_nodes(&mut self, expr: &SymExpr) -> Option<usize> {
2100 if let Some(nodes) = self.expr_nodes.get(expr) {
2101 return Some(*nodes);
2102 }
2103 if self.remaining_unique_nodes == 0 {
2104 return None;
2105 }
2106 self.remaining_unique_nodes -= 1;
2107
2108 let nodes = match expr.kind() {
2109 SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => 1,
2110 SymExprKind::Keccak { len, bytes, .. } => {
2111 let mut nodes = 1usize.checked_add(self.expr_nodes(len)?)?;
2112 for byte in bytes.iter() {
2113 nodes = nodes.checked_add(self.expr_nodes(byte)?)?;
2114 }
2115 nodes
2116 }
2117 SymExprKind::Hash { bytes, .. } => {
2118 let mut nodes = 1usize;
2119 for byte in bytes.iter() {
2120 nodes = nodes.checked_add(self.expr_nodes(byte)?)?;
2121 }
2122 nodes
2123 }
2124 SymExprKind::Not(value) => 1usize.checked_add(self.expr_nodes(value)?)?,
2125 SymExprKind::BinOp(_, left, right) => {
2126 1usize.checked_add(self.expr_nodes(left)?)?.checked_add(self.expr_nodes(right)?)?
2127 }
2128 SymExprKind::TernOp(_, left, right, modulus) => 1usize
2129 .checked_add(self.expr_nodes(left)?)?
2130 .checked_add(self.expr_nodes(right)?)?
2131 .checked_add(self.expr_nodes(modulus)?)?,
2132 SymExprKind::Ite(condition, then_expr, else_expr) => 1usize
2133 .checked_add(self.bool_nodes(condition)?)?
2134 .checked_add(self.expr_nodes(then_expr)?)?
2135 .checked_add(self.expr_nodes(else_expr)?)?,
2136 };
2137 if nodes > MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES {
2138 return None;
2139 }
2140 self.expr_nodes.insert(expr.clone(), nodes);
2141 Some(nodes)
2142 }
2143
2144 fn bool_nodes(&mut self, expr: &SymBoolExpr) -> Option<usize> {
2145 if let Some(nodes) = self.bool_nodes.get(expr) {
2146 return Some(*nodes);
2147 }
2148 if self.remaining_unique_nodes == 0 {
2149 return None;
2150 }
2151 self.remaining_unique_nodes -= 1;
2152
2153 let nodes = match expr.kind() {
2154 SymBoolExprKind::Const(_) => 1,
2155 SymBoolExprKind::Not(value) => 1usize.checked_add(self.bool_nodes(value)?)?,
2156 SymBoolExprKind::And(values) => {
2157 let mut nodes = 1usize;
2158 for value in values.iter() {
2159 nodes = nodes.checked_add(self.bool_nodes(value)?)?;
2160 }
2161 nodes
2162 }
2163 SymBoolExprKind::Cmp(_, left, right) => {
2164 1usize.checked_add(self.expr_nodes(left)?)?.checked_add(self.expr_nodes(right)?)?
2165 }
2166 };
2167 if nodes > MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES {
2168 return None;
2169 }
2170 self.bool_nodes.insert(expr.clone(), nodes);
2171 Some(nodes)
2172 }
2173}
2174
2175fn write_smt_wide_modular_arithmetic(
2176 cx: &SymCx,
2177 out: &mut String,
2178 op: &'static str,
2179 left: &SymExpr,
2180 right: &SymExpr,
2181 modulus: &SymExpr,
2182) {
2183 out.push_str("(ite (= ");
2188 modulus.write_smt(cx, out);
2189 out.push_str(" (_ bv0 256)) (_ bv0 256) ((_ extract 255 0) (bvurem (");
2190 out.push_str(op);
2191 out.push_str(" ((_ zero_extend 256) ");
2192 left.write_smt(cx, out);
2193 out.push_str(") ((_ zero_extend 256) ");
2194 right.write_smt(cx, out);
2195 out.push_str(")) ((_ zero_extend 256) ");
2196 modulus.write_smt(cx, out);
2197 out.push_str("))))");
2198}
2199
2200#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2201pub(crate) enum SymTernOp {
2202 AddMod,
2203 MulMod,
2204}
2205
2206impl SymTernOp {
2207 pub(crate) const fn smt(self) -> &'static str {
2208 match self {
2209 Self::AddMod => "bvadd",
2210 Self::MulMod => "bvmul",
2211 }
2212 }
2213
2214 pub(crate) fn eval(self, left: U256, right: U256, modulus: U256) -> U256 {
2215 if modulus.is_zero() {
2216 return U256::ZERO;
2217 }
2218 match self {
2219 Self::AddMod => left.add_mod(right, modulus),
2220 Self::MulMod => left.mul_mod(right, modulus),
2221 }
2222 }
2223}
2224
2225#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2226pub(crate) enum SymBinOp {
2227 Add,
2228 Sub,
2229 Mul,
2230 UDiv,
2231 URem,
2232 SDiv,
2233 SRem,
2234 And,
2235 Or,
2236 Xor,
2237 Shl,
2238 Shr,
2239 Sar,
2240}
2241
2242impl SymBinOp {
2243 pub(crate) const fn smt(self) -> &'static str {
2244 match self {
2245 Self::Add => "bvadd",
2246 Self::Sub => "bvsub",
2247 Self::Mul => "bvmul",
2248 Self::UDiv => "bvudiv",
2249 Self::URem => "bvurem",
2250 Self::SDiv => "bvsdiv",
2251 Self::SRem => "bvsrem",
2252 Self::And => "bvand",
2253 Self::Or => "bvor",
2254 Self::Xor => "bvxor",
2255 Self::Shl => "bvshl",
2256 Self::Shr => "bvlshr",
2257 Self::Sar => "bvashr",
2258 }
2259 }
2260
2261 pub(crate) fn eval(self, left: U256, right: U256) -> U256 {
2262 match self {
2263 Self::Add => left.wrapping_add(right),
2264 Self::Sub => left.wrapping_sub(right),
2265 Self::Mul => left.wrapping_mul(right),
2266 Self::UDiv => {
2267 if right.is_zero() {
2268 U256::ZERO
2269 } else {
2270 left / right
2271 }
2272 }
2273 Self::URem => {
2274 if right.is_zero() {
2275 U256::ZERO
2276 } else {
2277 left % right
2278 }
2279 }
2280 Self::SDiv => i256_div(left, right),
2281 Self::SRem => i256_mod(left, right),
2282 Self::And => left & right,
2283 Self::Or => left | right,
2284 Self::Xor => left ^ right,
2285 Self::Shl => {
2286 if right >= U256::from(256) {
2287 U256::ZERO
2288 } else {
2289 left << usize::try_from(right).expect("checked word shift")
2290 }
2291 }
2292 Self::Shr => {
2293 if right >= U256::from(256) {
2294 U256::ZERO
2295 } else {
2296 left >> usize::try_from(right).expect("checked word shift")
2297 }
2298 }
2299 Self::Sar => {
2300 if right >= U256::from(256) {
2301 left.arithmetic_shr(256)
2302 } else {
2303 left.arithmetic_shr(usize::try_from(right).expect("checked word shift"))
2304 }
2305 }
2306 }
2307 }
2308}
2309
2310pub(crate) fn keccak_word(cx: &mut SymCx, bytes: Vec<SymExpr>) -> SymExpr {
2311 let len = bytes.len();
2312 let len = SymExpr::constant(cx, U256::from(len));
2313 keccak_word_with_len(cx, bytes, len)
2314}
2315
2316pub(crate) fn keccak_word_with_len(cx: &mut SymCx, bytes: Vec<SymExpr>, len: SymExpr) -> SymExpr {
2317 if let Some(len) = len.as_const()
2318 && let Ok(len) = usize::try_from(len)
2319 && len <= bytes.len()
2320 && let Ok(concrete) = concrete_expr_bytes(&bytes[..len], "symbolic keccak input")
2321 {
2322 let hash = U256::from_be_bytes(keccak256(concrete).0);
2323 if len == 64 {
2324 cx.record_concrete_keccak_preimage(hash, bytes[..len].to_vec().into());
2325 }
2326 return SymExpr::constant(cx, hash);
2327 }
2328
2329 let exprs = bytes;
2330 let name = stable_symbol(cx, "keccak", format!("{len:?}:{exprs:?}").as_bytes());
2331 SymExpr::keccak_symbol(cx, name, len, exprs)
2332}
2333
2334pub(crate) fn symbolic_hash_word_with_len(
2335 cx: &mut SymCx,
2336 algorithm: &'static str,
2337 bytes: Vec<SymExpr>,
2338 len: SymExpr,
2339) -> SymExpr {
2340 let exprs = bytes;
2341 let name = stable_symbol(cx, algorithm, format!("{len:?}:{exprs:?}").as_bytes());
2342 let mut identity = Vec::with_capacity(exprs.len() + 1);
2343 identity.push(len);
2344 identity.extend(exprs);
2345 SymExpr::hash_symbol(cx, name, algorithm, identity)
2346}
2347
2348pub(crate) fn create2_address_word(
2349 cx: &mut SymCx,
2350 state: &mut PathState,
2351 creator: Address,
2352 salt: SymExpr,
2353 initcode: &SymCode,
2354) -> Result<(SymExpr, Address), SymbolicError> {
2355 match (salt.as_const(), initcode.concrete_bytes(cx, "symbolic CREATE2 initcode")) {
2356 (Some(salt), Ok(initcode)) => {
2357 let address = creator.create2_from_code(salt.to_be_bytes::<32>(), &initcode);
2358 Ok((SymExpr::constant(cx, address_word(address)), address))
2359 }
2360 (None, Ok(initcode)) => {
2361 let initcode_hash = keccak256(&initcode);
2362 let word = symbolic_create2_address_word(
2363 cx,
2364 state,
2365 format!("{creator:?}"),
2366 salt,
2367 format!("{initcode_hash:?}"),
2368 );
2369 let address = state.world.symbolic_address_slot(word.clone());
2370 Ok((word, address))
2371 }
2372 (_, Err(SymbolicError::Unsupported("symbolic CREATE2 initcode"))) => {
2373 let initcode_bytes = initcode.read_byte_exprs(cx, 0, initcode.len());
2374 let word = symbolic_create2_address_word(
2375 cx,
2376 state,
2377 format!("{creator:?}"),
2378 salt,
2379 format!("{initcode_bytes:?}"),
2380 );
2381 let address = state.world.symbolic_address_slot(word.clone());
2382 Ok((word, address))
2383 }
2384 (_, Err(err)) => Err(err),
2385 }
2386}
2387
2388pub(crate) fn compute_create2_address_word(
2389 cx: &mut SymCx,
2390 state: &mut PathState,
2391 deployer: SymExpr,
2392 salt: SymExpr,
2393 init_code_hash: SymExpr,
2394) -> Result<SymExpr, SymbolicError> {
2395 let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
2396 let salt_concrete = state.constrained_word(cx, &salt);
2397 let init_code_hash_concrete = state.constrained_word(cx, &init_code_hash);
2398
2399 if let (Some(deployer), Some(salt), Some(init_code_hash)) =
2400 (deployer_concrete, salt_concrete, init_code_hash_concrete)
2401 {
2402 let init_code_hash = B256::from(init_code_hash.to_be_bytes::<32>());
2403 let address = deployer.create2(B256::from(salt.to_be_bytes::<32>()), init_code_hash);
2404 return Ok(SymExpr::constant(cx, address_word(address)));
2405 }
2406
2407 let deployer_identity = deployer_concrete
2408 .map(|deployer| format!("{deployer:?}"))
2409 .unwrap_or_else(|| format!("{deployer:?}"));
2410 let init_code_hash_identity = init_code_hash_concrete
2411 .map(|init_code_hash| {
2412 let init_code_hash = B256::from(init_code_hash.to_be_bytes::<32>());
2413 format!("{init_code_hash:?}")
2414 })
2415 .unwrap_or_else(|| format!("{init_code_hash:?}"));
2416
2417 Ok(symbolic_create2_address_word(cx, state, deployer_identity, salt, init_code_hash_identity))
2418}
2419
2420pub(crate) fn compute_create_address_word(
2421 cx: &mut SymCx,
2422 state: &mut PathState,
2423 deployer: SymExpr,
2424 nonce: SymExpr,
2425) -> Result<SymExpr, SymbolicError> {
2426 let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
2427 let nonce_concrete = state.constrained_word(cx, &nonce);
2428
2429 if let (Some(deployer), Some(nonce)) = (deployer_concrete, nonce_concrete) {
2430 let Ok(nonce) = u64::try_from(nonce) else {
2431 return Err(SymbolicError::Unsupported("symbolic vm.computeCreateAddress nonce"));
2432 };
2433 return Ok(SymExpr::constant(cx, address_word(deployer.create(nonce))));
2434 }
2435
2436 let deployer_identity = deployer_concrete
2437 .map(|deployer| format!("{deployer:?}"))
2438 .unwrap_or_else(|| format!("{deployer:?}"));
2439 Ok(symbolic_create_address_word(cx, state, deployer_identity, nonce))
2440}
2441
2442pub(crate) fn symbolic_create_address_word(
2443 cx: &mut SymCx,
2444 state: &mut PathState,
2445 creator_identity: String,
2446 nonce: SymExpr,
2447) -> SymExpr {
2448 let name =
2449 stable_symbol(cx, "create_address", format!("{creator_identity}:{nonce:?}").as_bytes());
2450 let word = SymExpr::get_var(cx, name);
2451 state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
2452 word
2453}
2454
2455pub(crate) fn symbolic_create2_address_word(
2456 cx: &mut SymCx,
2457 state: &mut PathState,
2458 creator_identity: String,
2459 salt: SymExpr,
2460 initcode_identity: String,
2461) -> SymExpr {
2462 let name = stable_symbol(
2463 cx,
2464 "create2_address",
2465 format!("{creator_identity}:{salt:?}:{initcode_identity}").as_bytes(),
2466 );
2467 let word = SymExpr::get_var(cx, name);
2468 state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
2469 word
2470}
2471
2472#[cfg(test)]
2473mod tests {
2474 use super::*;
2475
2476 fn indexed_bool_word(cx: &mut SymCx, source: &SymExpr, index: usize) -> SymExpr {
2477 let value = SymExpr::constant(cx, U256::from(index));
2478 let condition = SymBoolExpr::eq(cx, source.clone(), value);
2479 SymExpr::bool_word(cx, condition)
2480 }
2481
2482 #[test]
2483 fn bitwise_bool_word_condition_visits_shared_or_dag_once() {
2484 let mut cx = SymCx::new();
2485 let source = SymExpr::var(&mut cx, "source");
2486 let mut word = indexed_bool_word(&mut cx, &source, 0);
2487 for index in 1..=26 {
2488 let next_word = indexed_bool_word(&mut cx, &source, index);
2489 let nested_word = SymExpr::binop(&mut cx, SymBinOp::Or, word.clone(), next_word);
2490 word = SymExpr::binop(&mut cx, SymBinOp::Or, word, nested_word);
2491 }
2492
2493 assert!(word.bitwise_bool_word_condition(&mut cx).is_some());
2494 }
2495
2496 #[test]
2497 fn bitwise_bool_word_condition_stops_at_shared_visit_budget() {
2498 let mut cx = SymCx::new();
2499 let source = SymExpr::var(&mut cx, "source");
2500 let mut word = indexed_bool_word(&mut cx, &source, 0);
2501 for index in 1..=MAX_BITWISE_BOOL_WORD_VISITS {
2502 let next_word = indexed_bool_word(&mut cx, &source, index);
2503 word = SymExpr::binop(&mut cx, SymBinOp::Or, word, next_word);
2504 }
2505
2506 assert!(word.bitwise_bool_word_condition(&mut cx).is_none());
2507 let one = SymExpr::one(&mut cx);
2508 let mask = SymExpr::binop(&mut cx, SymBinOp::Sub, word, one);
2509 assert!(matches!(mask.kind(), SymExprKind::BinOp(SymBinOp::Sub, _, _)));
2510 }
2511
2512 #[test]
2513 fn bitwise_bool_word_condition_deduplicates_shared_or_dag() {
2514 let mut cx = SymCx::new();
2515 let base = SymExpr::var(&mut cx, "base");
2516 let selected = SymExpr::var(&mut cx, "selected");
2517 let x = SymExpr::var(&mut cx, "x");
2518 let y = SymExpr::var(&mut cx, "y");
2519 let condition = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ult, x, y);
2520 let mut condition_word = SymExpr::bool_word(&mut cx, condition);
2521 for _ in 0..64 {
2522 condition_word = SymExpr::from_kind(
2523 &mut cx,
2524 SymExprKind::BinOp(SymBinOp::Or, condition_word.clone(), condition_word.clone()),
2525 );
2526 }
2527 let delta = SymExpr::binop(&mut cx, SymBinOp::Xor, base.clone(), selected.clone());
2528 let selector = SymExpr::binop(&mut cx, SymBinOp::Mul, condition_word, delta);
2529 let actual = SymExpr::binop(&mut cx, SymBinOp::Xor, base.clone(), selector);
2530
2531 let SymExprKind::Ite(_, then_expr, else_expr) = actual.kind() else {
2532 panic!("shared boolean selector was not recovered");
2533 };
2534 assert_eq!(then_expr, &selected);
2535 assert_eq!(else_expr, &base);
2536 }
2537
2538 #[test]
2539 fn bitwise_bool_word_condition_deduplicates_overlapping_or_dag() {
2540 let mut cx = SymCx::new();
2541 let x = SymExpr::var(&mut cx, "x");
2542 let y = SymExpr::var(&mut cx, "y");
2543 let first = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ult, x.clone(), y.clone());
2544 let second = SymBoolExpr::cmp(&mut cx, SymCmpOp::Eq, x, y);
2545 let mut previous = SymExpr::bool_word(&mut cx, first.clone());
2546 let mut current = SymExpr::bool_word(&mut cx, second.clone());
2547 for _ in 0..28 {
2548 let next = SymExpr::from_kind(
2549 &mut cx,
2550 SymExprKind::BinOp(SymBinOp::Or, current.clone(), previous.clone()),
2551 );
2552 previous = current;
2553 current = next;
2554 }
2555
2556 let actual = current.bitwise_bool_word_condition(&mut cx).expect("boolean condition");
2557 let expected = SymBoolExpr::or(&mut cx, vec![second, first]);
2558
2559 assert_eq!(actual, expected);
2560 }
2561
2562 #[test]
2563 fn bitwise_bool_word_condition_stops_at_node_budget() {
2564 let mut cx = SymCx::new();
2565 let x = SymExpr::var(&mut cx, "x");
2566 let y = SymExpr::var(&mut cx, "y");
2567 let condition = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ult, x, y);
2568 let bool_word = SymExpr::bool_word(&mut cx, condition);
2569 let mut condition_word = bool_word.clone();
2570 for _ in 0..MAX_BITWISE_BOOL_WORD_VISITS {
2571 condition_word = SymExpr::from_kind(
2572 &mut cx,
2573 SymExprKind::BinOp(SymBinOp::Or, condition_word, bool_word.clone()),
2574 );
2575 }
2576
2577 assert!(condition_word.bitwise_bool_word_condition(&mut cx).is_none());
2578 }
2579
2580 #[test]
2581 fn commutative_branchless_rewrites_produce_canonical_ites() {
2582 let mut cx = SymCx::new();
2583 let x = SymExpr::var(&mut cx, "x");
2584 let y = SymExpr::var(&mut cx, "y");
2585 let first_condition = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ult, x.clone(), y.clone());
2586 let second_condition = SymBoolExpr::cmp(&mut cx, SymCmpOp::Eq, x, y);
2587
2588 let one = SymExpr::one(&mut cx);
2589 let two = SymExpr::constant(&mut cx, U256::from(2));
2590 let three = SymExpr::constant(&mut cx, U256::from(3));
2591 let four = SymExpr::constant(&mut cx, U256::from(4));
2592 let first_offset = SymExpr::ite(&mut cx, first_condition.clone(), one, two);
2593 let second_offset = SymExpr::ite(&mut cx, second_condition.clone(), three, four);
2594 let add_forward =
2595 SymExpr::binop(&mut cx, SymBinOp::Add, first_offset.clone(), second_offset.clone());
2596 let add_reverse = SymExpr::binop(&mut cx, SymBinOp::Add, second_offset, first_offset);
2597 assert_eq!(add_forward, add_reverse);
2598 let SymExprKind::Ite(_, then_expr, else_expr) = add_forward.kind() else {
2599 panic!("dual ITE addition did not rewrite");
2600 };
2601 assert!(matches!(then_expr.kind(), SymExprKind::BinOp(SymBinOp::Add, _, _)));
2602 assert!(matches!(else_expr.kind(), SymExprKind::BinOp(SymBinOp::Add, _, _)));
2603
2604 let first_word = SymExpr::bool_word(&mut cx, first_condition.clone());
2605 let second_word = SymExpr::bool_word(&mut cx, second_condition.clone());
2606 let mul_forward =
2607 SymExpr::binop(&mut cx, SymBinOp::Mul, first_word.clone(), second_word.clone());
2608 let mul_reverse = SymExpr::binop(&mut cx, SymBinOp::Mul, second_word, first_word);
2609 assert_eq!(mul_forward, mul_reverse);
2610 let SymExprKind::Ite(_, then_expr, else_expr) = mul_forward.kind() else {
2611 panic!("dual boolean-word multiplication did not rewrite");
2612 };
2613 assert!(matches!(then_expr.kind(), SymExprKind::Ite(..)));
2614 assert!(else_expr.as_const().is_some_and(|value| value.is_zero()));
2615
2616 let zero = SymExpr::zero(&mut cx);
2617 let first_value = SymExpr::var(&mut cx, "first_value");
2618 let second_value = SymExpr::var(&mut cx, "second_value");
2619 let first_selected = SymExpr::ite(&mut cx, first_condition, first_value, zero.clone());
2620 let second_selected = SymExpr::ite(&mut cx, second_condition, second_value, zero);
2621 let xor_forward =
2622 SymExpr::binop(&mut cx, SymBinOp::Xor, first_selected.clone(), second_selected.clone());
2623 let xor_reverse = SymExpr::binop(&mut cx, SymBinOp::Xor, second_selected, first_selected);
2624 assert_eq!(xor_forward, xor_reverse);
2625 let SymExprKind::Ite(_, then_expr, else_expr) = xor_forward.kind() else {
2626 panic!("dual zero-ITE XOR did not rewrite");
2627 };
2628 assert!(matches!(then_expr.kind(), SymExprKind::Ite(..)));
2629 assert!(matches!(else_expr.kind(), SymExprKind::Ite(..)));
2630 }
2631
2632 #[test]
2633 fn addition_keeps_exponentially_shared_ite_operand_raw() {
2634 let mut cx = SymCx::new();
2635 let mut value = SymExpr::var(&mut cx, "value");
2636 for index in 0..32 {
2637 let selector = SymExpr::var(&mut cx, &format!("add_selector_{index}"));
2638 let condition = SymBoolExpr::eq_word_const(&mut cx, &selector, U256::ZERO);
2639 let then_value = SymExpr::constant(&mut cx, U256::from(2 * index + 2));
2640 let else_value = SymExpr::constant(&mut cx, U256::from(2 * index + 3));
2641 let offset = SymExpr::ite(&mut cx, condition, then_value, else_value);
2642 value = SymExpr::binop(&mut cx, SymBinOp::Add, value, offset);
2643 }
2644
2645 assert!(matches!(value.kind(), SymExprKind::BinOp(SymBinOp::Add, _, _)));
2646 }
2647
2648 #[test]
2649 fn xor_keeps_exponentially_shared_ite_operand_raw() {
2650 let mut cx = SymCx::new();
2651 let zero = SymExpr::zero(&mut cx);
2652 let mut value = SymExpr::var(&mut cx, "value");
2653 for index in 0..32 {
2654 let selector = SymExpr::var(&mut cx, &format!("xor_selector_{index}"));
2655 let condition = SymBoolExpr::eq_word_const(&mut cx, &selector, U256::ZERO);
2656 let selected = SymExpr::var(&mut cx, &format!("xor_selected_{index}"));
2657 let conditional = SymExpr::ite(&mut cx, condition, selected, zero.clone());
2658 value = SymExpr::binop(&mut cx, SymBinOp::Xor, value, conditional);
2659 }
2660
2661 assert!(matches!(value.kind(), SymExprKind::BinOp(SymBinOp::Xor, _, _)));
2662 }
2663
2664 #[test]
2665 fn bitwise_bool_word_condition_bounds_bit_width_analysis() {
2666 let mut cx = SymCx::new();
2667 let one = SymExpr::one(&mut cx);
2668 let mut expression = one.clone();
2669 for _ in 0..MAX_BITWISE_BOOL_WORD_VISITS {
2670 expression = SymExpr::from_kind(
2671 &mut cx,
2672 SymExprKind::BinOp(SymBinOp::UDiv, expression, one.clone()),
2673 );
2674 }
2675
2676 assert!(expression.bitwise_bool_word_condition(&mut cx).is_none());
2677 }
2678
2679 #[test]
2680 fn bitwise_bool_word_condition_keeps_one_bit_leaf_comparison_raw() {
2681 let mut cx = SymCx::new();
2682 let x = SymExpr::var(&mut cx, "x");
2683 let y = SymExpr::var(&mut cx, "y");
2684 let first = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ult, x.clone(), y.clone());
2685 let second = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ugt, x, y);
2686 let zero = SymExpr::zero(&mut cx);
2687 let one = SymExpr::one(&mut cx);
2688 let nested = SymExpr::from_kind(&mut cx, SymExprKind::Ite(second, zero.clone(), one));
2689 let leaf = SymExpr::from_kind(&mut cx, SymExprKind::Ite(first, nested, zero.clone()));
2690
2691 let actual =
2692 leaf.bitwise_bool_word_condition(&mut cx).expect("one-bit leaf should be recovered");
2693 let (leaf, zero) = SymExpr::ordered_commutative_operands(leaf, zero);
2694 let raw_zero_check =
2695 SymBoolExpr::from_kind(&mut cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, leaf, zero));
2696 let expected = raw_zero_check.not(&mut cx);
2697
2698 assert_eq!(actual, expected);
2699 }
2700
2701 #[test]
2702 fn unsigned_bit_width_handles_deep_expression_iteratively() {
2703 let mut cx = SymCx::new();
2704 let one = SymExpr::one(&mut cx);
2705 let mut expression = one.clone();
2706 for _ in 0..2048 {
2707 expression = SymExpr::from_kind(
2708 &mut cx,
2709 SymExprKind::BinOp(SymBinOp::UDiv, expression, one.clone()),
2710 );
2711 }
2712
2713 assert_eq!(expression.unsigned_bits(), 1);
2714 }
2715
2716 #[test]
2717 fn xor_select_rejects_delta_before_recovering_condition() {
2718 let mut cx = SymCx::new();
2719 let base = SymExpr::var(&mut cx, "base");
2720 let unrelated_left = SymExpr::var(&mut cx, "unrelated_left");
2721 let unrelated_right = SymExpr::var(&mut cx, "unrelated_right");
2722 let delta = SymExpr::binop(&mut cx, SymBinOp::Xor, unrelated_left, unrelated_right);
2723 let x = SymExpr::var(&mut cx, "x");
2724 let y = SymExpr::var(&mut cx, "y");
2725 let condition = SymBoolExpr::cmp(&mut cx, SymCmpOp::Ult, x, y);
2726 let condition_word = SymExpr::bool_word(&mut cx, condition);
2727 let selector = SymExpr::binop(&mut cx, SymBinOp::Mul, condition_word, delta);
2728
2729 assert!(SymExpr::xor_with_bool_select(&mut cx, &base, &selector).is_none());
2730 }
2731
2732 #[test]
2733 fn saturating_mul_rewrite_preserves_boundary_values() {
2734 let mut cx = SymCx::new();
2735 let x = SymExpr::var(&mut cx, "x");
2736 let y = SymExpr::var(&mut cx, "y");
2737 let x_symbol = match x.kind() {
2738 SymExprKind::Var(symbol) => *symbol,
2739 _ => unreachable!("constructed symbolic variable"),
2740 };
2741 let y_symbol = match y.kind() {
2742 SymExprKind::Var(symbol) => *symbol,
2743 _ => unreachable!("constructed symbolic variable"),
2744 };
2745
2746 let zero = SymExpr::zero(&mut cx);
2747 let x_is_zero = SymBoolExpr::eq(&mut cx, x.clone(), zero);
2748 let product = SymExpr::binop(&mut cx, SymBinOp::Mul, x.clone(), y.clone());
2749 let quotient = SymExpr::binop(&mut cx, SymBinOp::UDiv, product.clone(), x);
2750 let product_is_exact = SymBoolExpr::eq(&mut cx, quotient, y);
2751 let safe = SymBoolExpr::or(&mut cx, vec![product_is_exact.clone(), x_is_zero.clone()]);
2752 let x_is_zero_word = SymExpr::bool_word(&mut cx, x_is_zero);
2753 let product_is_exact_word = SymExpr::bool_word(&mut cx, product_is_exact);
2754 let guard = SymExpr::binop(&mut cx, SymBinOp::Or, x_is_zero_word, product_is_exact_word);
2755 let one = SymExpr::one(&mut cx);
2756 let raw_mask =
2757 SymExpr::from_kind(&mut cx, SymExprKind::BinOp(SymBinOp::Sub, guard.clone(), one));
2758 let original = SymExpr::from_kind(
2759 &mut cx,
2760 SymExprKind::BinOp(SymBinOp::Or, raw_mask, product.clone()),
2761 );
2762
2763 let one = SymExpr::one(&mut cx);
2764 let simplified_mask = SymExpr::binop(&mut cx, SymBinOp::Sub, guard, one);
2765 let simplified = SymExpr::binop(&mut cx, SymBinOp::Or, simplified_mask, product.clone());
2766 let max = SymExpr::constant(&mut cx, U256::MAX);
2767 let expected = SymExpr::ite(&mut cx, safe, product, max);
2768 assert_eq!(simplified, expected);
2769
2770 let half_range = U256::ONE << 255;
2771 let boundaries = [
2772 (U256::ZERO, U256::MAX),
2773 (U256::MAX, U256::ZERO),
2774 (U256::MAX, U256::ONE),
2775 (U256::ONE, U256::MAX),
2776 (U256::MAX, U256::from(2)),
2777 (U256::from(2), U256::MAX),
2778 (half_range, U256::from(2)),
2779 (U256::from(2), half_range),
2780 ];
2781 for (x_value, y_value) in boundaries {
2782 let mut model = SymbolicModel::default();
2783 model.insert(x_symbol, x_value);
2784 model.insert(y_symbol, y_value);
2785 let expected_value = x_value.checked_mul(y_value).unwrap_or(U256::MAX);
2786 assert_eq!(original.eval_model(&model).unwrap(), expected_value);
2787 assert_eq!(simplified.eval_model(&model).unwrap(), expected_value);
2788 }
2789 }
2790
2791 #[test]
2792 fn constant_difference_follows_aligned_branches() {
2793 let mut cx = SymCx::new();
2794 let selector = SymExpr::var(&mut cx, "selector");
2795 let condition = SymBoolExpr::eq_word_const(&mut cx, &selector, U256::ZERO);
2796 let base = SymExpr::var(&mut cx, "base");
2797 let left_then = SymExpr::add_const(&mut cx, base.clone(), U256::from(196));
2798 let left_else = SymExpr::add_const(&mut cx, base.clone(), U256::from(228));
2799 let left = SymExpr::ite(&mut cx, condition.clone(), left_then, left_else);
2800 let right_else = SymExpr::add_const(&mut cx, base.clone(), U256::from(32));
2801 let right = SymExpr::ite(&mut cx, condition, base, right_else);
2802
2803 assert_eq!(left.constant_difference(&right), Some(U256::from(196)));
2804 }
2805
2806 #[test]
2807 fn constant_difference_rejects_misaligned_branches() {
2808 let mut cx = SymCx::new();
2809 let selector = SymExpr::var(&mut cx, "selector");
2810 let condition = SymBoolExpr::eq_word_const(&mut cx, &selector, U256::ZERO);
2811 let base = SymExpr::var(&mut cx, "base");
2812 let left_then = SymExpr::add_const(&mut cx, base.clone(), U256::from(196));
2813 let left_else = SymExpr::add_const(&mut cx, base.clone(), U256::from(229));
2814 let left = SymExpr::ite(&mut cx, condition.clone(), left_then, left_else);
2815 let right_else = SymExpr::add_const(&mut cx, base.clone(), U256::from(32));
2816 let right = SymExpr::ite(&mut cx, condition, base, right_else);
2817
2818 assert_eq!(left.constant_difference(&right), None);
2819 }
2820}