1use super::Ecrecover;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{
5 Severity, SolLint,
6 analysis::{is_exit_call, is_require_or_assert, loop_update, tuple_elems},
7 },
8};
9use alloy_primitives::{U256, uint};
10use solar::{
11 ast::{BinOpKind, ElementaryType, UnOpKind},
12 interface::{Span, Symbol, data_structures::Never},
13 sema::{
14 Gcx,
15 builtins::Builtin,
16 eval::ConstValue,
17 hir::{
18 self, Expr, ExprId, ExprKind, LoopSource, StateMutability, Stmt, StmtKind, TypeKind,
19 VariableId, Visit,
20 },
21 ty::TyKind,
22 },
23};
24use std::{
25 collections::{HashMap, HashSet},
26 mem,
27 ops::ControlFlow,
28};
29
30declare_forge_lint!(
31 ECRECOVER,
32 Severity::Med,
33 "ecrecover",
34 "`ecrecover` call does not reject malleable signatures"
35);
36
37const SECP256K1_HALF_ORDER: U256 =
39 uint!(0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0_U256);
40
41impl<'gcx> LateLintPass<'gcx> for Ecrecover {
42 fn check_function(
43 &mut self,
44 ctx: &LintContext,
45 gcx: Gcx<'gcx>,
46 func: &'gcx hir::Function<'gcx>,
47 ) {
48 let Some(body) = func.body else { return };
49 let mut analyzer = Analyzer {
50 gcx,
51 returns: func.returns,
52 state: FlowState::default(),
53 next_value: 0,
54 hits: Vec::new(),
55 deferred: HashMap::new(),
56 loop_exits: Vec::new(),
57 loop_next: None,
58 };
59 if analyzer.run_block(body.stmts) {
60 analyzer.use_return_values();
61 }
62 for span in analyzer.hits {
63 ctx.emit(&ECRECOVER, span);
64 }
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75enum ValueKey {
76 Var(VariableId),
77 Field(VariableId, Symbol, u32),
78}
79
80impl ValueKey {
81 const fn var(self) -> VariableId {
82 match self {
83 Self::Var(var) | Self::Field(var, ..) => var,
84 }
85 }
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
90enum ValueId {
91 Initial(ValueKey),
92 Assigned(u32),
93}
94
95#[derive(Clone, Copy, PartialEq, Eq)]
97struct PendingRecovery {
98 signature: Option<ValueId>,
99 span: Span,
100}
101
102#[derive(Clone, Default)]
103struct FlowState {
104 values: HashMap<ValueKey, ValueId>,
105 low_s: HashSet<ValueId>,
107 pending: HashMap<ValueId, Vec<PendingRecovery>>,
108 field_epoch: HashMap<VariableId, u32>,
110}
111
112impl FlowState {
113 fn value(&self, key: ValueKey) -> ValueId {
114 self.values.get(&key).copied().unwrap_or(ValueId::Initial(key))
115 }
116
117 fn field_key(&self, var: VariableId, field: Symbol) -> ValueKey {
118 ValueKey::Field(var, field, self.field_epoch.get(&var).copied().unwrap_or(0))
119 }
120
121 fn set(&mut self, key: ValueKey, value: ValueId) {
124 if let ValueKey::Var(var) = key {
125 self.reset_fields(var);
126 }
127 self.values.insert(key, value);
128 }
129
130 fn reset_fields(&mut self, var: VariableId) {
131 self.values.retain(|key, _| !matches!(key, ValueKey::Field(base, ..) if *base == var));
132 *self.field_epoch.entry(var).or_insert(0) += 1;
133 }
134
135 fn tracked_vars(&self) -> HashSet<VariableId> {
137 let initial = self.low_s.iter().filter_map(|value| match value {
138 ValueId::Initial(key) => Some(key.var()),
139 ValueId::Assigned(_) => None,
140 });
141 self.values.keys().map(|key| key.var()).chain(initial).collect()
142 }
143
144 fn add_pending(&mut self, value: ValueId, recovery: PendingRecovery) {
145 let recoveries = self.pending.entry(value).or_default();
146 if !recoveries.contains(&recovery) {
147 recoveries.push(recovery);
148 }
149 }
150}
151
152type Pair<'gcx> = (Option<ValueKey>, Option<&'gcx Expr<'gcx>>);
154
155struct Analyzer<'gcx> {
156 gcx: Gcx<'gcx>,
157 returns: &'gcx [VariableId],
158 state: FlowState,
159 next_value: u32,
160 hits: Vec<Span>,
161 deferred: HashMap<ExprId, Option<PendingRecovery>>,
164 loop_exits: Vec<FlowState>,
166 loop_next: Option<&'gcx Stmt<'gcx>>,
168}
169
170impl<'gcx> Analyzer<'gcx> {
171 const fn fresh_value(&mut self) -> ValueId {
172 self.next_value += 1;
173 ValueId::Assigned(self.next_value)
174 }
175
176 fn join(&mut self, left: FlowState, right: FlowState) -> FlowState {
177 let mut field_epoch = left.field_epoch.clone();
180 for (var, epoch) in &right.field_epoch {
181 let entry = field_epoch.entry(*var).or_insert(0);
182 *entry = (*entry).max(*epoch);
183 }
184 let mut joined = FlowState {
185 low_s: left.low_s.intersection(&right.low_s).copied().collect(),
186 field_epoch,
187 ..FlowState::default()
188 };
189 let mut merged = HashMap::new();
190 let keys: HashSet<_> = left.values.keys().chain(right.values.keys()).copied().collect();
191 for key in keys {
192 let (l, r) = (left.value(key), right.value(key));
193 let value = if l == r {
194 l
195 } else {
196 let value = *merged.entry((l, r)).or_insert_with(|| self.fresh_value());
197 if left.low_s.contains(&l) && right.low_s.contains(&r) {
198 joined.low_s.insert(value);
199 }
200 value
201 };
202 joined.values.insert(key, value);
203 for recovery in left.pending.get(&l).into_iter().chain(right.pending.get(&r)).flatten()
204 {
205 joined.add_pending(value, *recovery);
206 }
207 }
208 joined
209 }
210
211 fn join_all(&mut self, states: Vec<FlowState>) -> bool {
213 let Some(joined) = states.into_iter().reduce(|l, r| self.join(l, r)) else { return false };
214 self.state = joined;
215 true
216 }
217
218 fn emit_hit(&mut self, span: Span) {
219 if !self.hits.contains(&span) {
220 self.hits.push(span);
221 }
222 }
223
224 fn use_value(&mut self, value: ValueId) {
225 for recovery in self.state.pending.remove(&value).unwrap_or_default() {
226 self.emit_hit(recovery.span);
227 }
228 }
229
230 fn use_return_values(&mut self) {
231 for &var in self.returns {
232 self.use_value(self.state.value(ValueKey::Var(var)));
233 }
234 }
235
236 fn use_all_pending(&mut self) {
237 for recoveries in mem::take(&mut self.state.pending).into_values() {
238 for recovery in recoveries {
239 self.emit_hit(recovery.span);
240 }
241 }
242 }
243
244 fn validate_pending(&mut self) {
246 let low_s = &self.state.low_s;
247 self.state.pending.retain(|_, recoveries| {
248 recoveries.retain(|r| !r.signature.is_some_and(|s| low_s.contains(&s)));
249 !recoveries.is_empty()
250 });
251 }
252
253 fn current_value(&self, expr: &Expr<'_>) -> Option<ValueId> {
254 match &expr.peel_parens().kind {
255 ExprKind::Assign(lhs, None, _) => self.current_value(lhs),
256 _ => self.place_key(expr).map(|key| self.state.value(key)),
257 }
258 }
259
260 fn place_key(&self, expr: &Expr<'_>) -> Option<ValueKey> {
263 match &expr.peel_parens().kind {
264 ExprKind::Call(callee, args) if is_transparent_cast(callee) && args.len() == 1 => {
265 self.place_key(args.exprs().next()?)
266 }
267 ExprKind::Member(base, field) => {
268 var_of(self.gcx, base).map(|var| self.state.field_key(var, field.name))
269 }
270 _ => var_of(self.gcx, expr).map(ValueKey::Var),
271 }
272 }
273
274 fn reference_args(
277 &self,
278 callee: &'gcx Expr<'gcx>,
279 args: &'gcx hir::CallArgs<'gcx>,
280 ) -> Vec<VariableId> {
281 let callee = callee.peel_parens();
282 let Some(TyKind::Fn(function)) = self.gcx.type_of_expr(callee.id).map(|ty| ty.kind) else {
283 return Vec::new();
284 };
285 if !function.is_internal() {
286 return Vec::new();
287 }
288 let receiver = match &callee.kind {
289 ExprKind::Member(base, _)
290 if self.gcx.resolved_callee(callee.id).is_some_and(|c| c.attached) =>
291 {
292 Some(*base)
293 }
294 _ => None,
295 };
296 receiver
297 .into_iter()
298 .chain(args.exprs())
299 .filter_map(|expr| var_of(self.gcx, expr))
300 .filter(|&var| {
301 matches!(
302 self.gcx.hir.variable(var).data_location,
303 Some(hir::DataLocation::Memory | hir::DataLocation::Storage)
304 )
305 })
306 .collect()
307 }
308
309 fn is_local(&self, var: VariableId) -> bool {
310 self.gcx.hir.variable(var).is_local_variable()
311 }
312
313 fn ecrecover_call(
315 &self,
316 expr: &'gcx Expr<'gcx>,
317 ) -> Option<(&'gcx Expr<'gcx>, &'gcx Expr<'gcx>)> {
318 let expr = expr.peel_parens();
319 let ExprKind::Call(callee, args) = &expr.kind else { return None };
320 let callee = self.gcx.resolved_builtin(callee.peel_parens());
321 (callee == Some(Builtin::EcRecover) && args.len() == 4)
322 .then_some(expr)
323 .zip(args.exprs().nth(3))
324 }
325
326 fn pending_recovery(&self, expr: &'gcx Expr<'gcx>) -> Option<PendingRecovery> {
327 let (call, signature) = self.ecrecover_call(expr)?;
328 (!self.is_proven_low_s(signature))
329 .then(|| PendingRecovery { signature: self.current_value(signature), span: call.span })
330 }
331
332 fn live_arms<T>(&self, cond: &Expr<'_>, then: T, otherwise: T) -> impl Iterator<Item = T> {
334 match self.const_bool(cond) {
335 Some(true) => [Some(then), None],
336 Some(false) => [None, Some(otherwise)],
337 None => [Some(then), Some(otherwise)],
338 }
339 .into_iter()
340 .flatten()
341 }
342
343 fn result_calls(&self, expr: &'gcx Expr<'gcx>, out: &mut Vec<ExprId>) {
345 let expr = expr.peel_parens();
346 if self.ecrecover_call(expr).is_some() {
347 out.push(expr.id);
348 }
349 match &expr.kind {
350 ExprKind::Ternary(cond, then, otherwise) => {
351 for arm in self.live_arms(cond, *then, *otherwise) {
352 self.result_calls(arm, out);
353 }
354 }
355 ExprKind::Assign(_, None, rhs) => self.result_calls(rhs, out),
356 _ => {}
357 }
358 }
359
360 fn const_value(&self, expr: &Expr<'_>) -> Option<U256> {
361 let expr = expr.peel_parens();
362 match &expr.kind {
363 ExprKind::Call(callee, args) if is_transparent_cast(callee) && args.len() == 1 => {
364 self.const_value(args.exprs().next()?)
365 }
366 ExprKind::Binary(lhs, op, rhs)
368 if matches!(op.kind, BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul) =>
369 {
370 let (lhs, rhs) = (self.const_value(lhs)?, self.const_value(rhs)?);
371 Some(match op.kind {
372 BinOpKind::Add => lhs.wrapping_add(rhs),
373 BinOpKind::Sub => lhs.wrapping_sub(rhs),
374 _ => lhs.wrapping_mul(rhs),
375 })
376 }
377 _ if self.gcx.resolved_builtin(expr) == Some(Builtin::TypeMax) => {
378 let TyKind::Elementary(ElementaryType::UInt(size)) =
379 self.gcx.type_of_expr(expr.id)?.kind
380 else {
381 return None;
382 };
383 Some(U256::MAX >> (256 - size.bits()))
384 }
385 _ => self.gcx.const_eval().eval_int(expr).ok()?.as_u256(),
386 }
387 }
388
389 fn const_bool(&self, expr: &Expr<'_>) -> Option<bool> {
390 match self.gcx.const_eval().eval_expr(expr).ok()? {
391 ConstValue::Bool(value) => Some(*value),
392 _ => None,
393 }
394 }
395
396 fn is_proven_low_s(&self, expr: &'gcx Expr<'gcx>) -> bool {
397 self.const_value(expr).is_some_and(|value| value <= SECP256K1_HALF_ORDER)
398 || self.current_value(expr).is_some_and(|value| self.state.low_s.contains(&value))
399 || matches!(&expr.peel_parens().kind, ExprKind::Ternary(cond, then, otherwise)
400 if self.live_arms(cond, *then, *otherwise).all(|arm| self.is_proven_low_s(arm)))
401 }
402
403 fn assigned(&self, rhs: Option<&'gcx Expr<'gcx>>) -> (Option<ValueId>, bool) {
405 (
406 rhs.and_then(|rhs| self.current_value(rhs)),
407 rhs.is_some_and(|rhs| self.is_proven_low_s(rhs)),
408 )
409 }
410
411 fn assign(&mut self, key: ValueKey, (value, low_s): (Option<ValueId>, bool)) {
412 let value = value.unwrap_or_else(|| self.fresh_value());
413 if low_s {
414 self.state.low_s.insert(value);
415 }
416 if let ValueKey::Field(var, field, _) = key {
417 if let Some(location) = self.aliasable_location(var) {
420 for other in self.state.tracked_vars() {
421 if other != var && self.aliasable_location(other) == Some(location) {
422 self.state.reset_fields(other);
423 }
424 }
425 }
426 let key = self.state.field_key(var, field);
427 self.state.set(key, value);
428 } else {
429 self.state.set(key, value);
430 }
431 }
432
433 fn aliasable_location(&self, var: VariableId) -> Option<hir::DataLocation> {
435 let var = self.gcx.hir.variable(var);
436 if var.kind.is_state() && var.mutability.is_none() {
437 return Some(hir::DataLocation::Storage);
438 }
439 var.data_location
440 .filter(|loc| matches!(loc, hir::DataLocation::Memory | hir::DataLocation::Storage))
441 }
442
443 fn pairs(
445 &self,
446 lhs: &'gcx Expr<'gcx>,
447 rhs: Option<&'gcx Expr<'gcx>>,
448 out: &mut Vec<Pair<'gcx>>,
449 ) {
450 let Some(elems) = tuple_elems(lhs) else { return out.push((self.place_key(lhs), rhs)) };
451 let rhs_elems = rhs.and_then(tuple_elems);
452 for (i, lhs) in elems.iter().enumerate() {
453 let rhs = rhs_elems.and_then(|elems| elems.get(i).copied().flatten());
454 match lhs {
455 Some(lhs) => self.pairs(lhs, rhs, out),
456 None => out.push((None, rhs)),
457 }
458 }
459 }
460
461 fn assign_pairs(&mut self, pairs: &[Pair<'gcx>]) {
463 let assigned: Vec<_> =
464 pairs.iter().filter_map(|(key, rhs)| Some(((*key)?, self.assigned(*rhs)))).collect();
465 for (key, assigned) in assigned {
466 self.assign(key, assigned);
467 }
468 }
469
470 fn assign_lhs(&mut self, lhs: &'gcx Expr<'gcx>, rhs: Option<&'gcx Expr<'gcx>>) {
471 let mut pairs = Vec::new();
472 self.pairs(lhs, rhs, &mut pairs);
473 self.assign_pairs(&pairs);
474 }
475
476 fn store(&mut self, pairs: &[Pair<'gcx>], rhs: Option<&'gcx Expr<'gcx>>) {
480 let mut calls = Vec::new();
481 for &(key, rhs) in pairs {
482 let local = key.filter(|key| self.is_local(key.var()));
483 let mut result_calls = Vec::new();
484 if let Some(rhs) = rhs {
485 self.result_calls(rhs, &mut result_calls);
486 }
487 for call in result_calls {
488 match local {
489 Some(_) => self.deferred.insert(call, None),
490 None => self.deferred.remove(&call),
491 };
492 calls.push((call, local));
493 }
494 }
495 let local_target = matches!(pairs, [(Some(key), _)] if self.is_local(key.var()));
496 if let Some(rhs) = rhs {
497 match &rhs.peel_parens().kind {
498 ExprKind::Assign(lhs, None, inner) if local_target => self.store_expr(lhs, inner),
499 _ if local_target && self.current_value(rhs).is_some() => {}
501 _ => {
502 let _ = self.visit_expr(rhs);
503 }
504 }
505 }
506 self.assign_pairs(pairs);
507 for (call, key) in calls {
508 if let (Some(key), Some(Some(recovery))) = (key, self.deferred.remove(&call)) {
509 let value = self.state.value(key);
510 self.state.add_pending(value, recovery);
511 }
512 }
513 }
514
515 fn store_expr(&mut self, lhs: &'gcx Expr<'gcx>, rhs: &'gcx Expr<'gcx>) {
516 let mut pairs = Vec::new();
517 self.pairs(lhs, Some(rhs), &mut pairs);
518 self.store(&pairs, Some(rhs));
519 }
520
521 fn visit_lhs(&mut self, lhs: &'gcx Expr<'gcx>) {
523 if let Some(elems) = tuple_elems(lhs) {
524 for elem in elems.iter().flatten() {
525 self.visit_lhs(elem);
526 }
527 } else if self.place_key(lhs).is_none() {
528 let _ = self.visit_expr(lhs);
529 }
530 }
531
532 fn invalidate_mutable_state(&mut self) {
535 for var in self.state.tracked_vars() {
536 let variable = self.gcx.hir.variable(var);
537 if (variable.kind.is_state() && variable.mutability.is_none())
538 || variable.data_location == Some(hir::DataLocation::Storage)
539 {
540 self.assign(ValueKey::Var(var), (None, false));
541 }
542 }
543 }
544
545 fn has_side_effect(&self, expr: &'gcx Expr<'gcx>) -> bool {
546 SideEffects(self).visit_expr(expr).is_break()
547 }
548
549 fn assume(&mut self, predicate: &'gcx Expr<'gcx>, negate: bool) {
550 self.add_facts(predicate, negate);
551 self.validate_pending();
552 }
553
554 fn add_facts(&mut self, predicate: &'gcx Expr<'gcx>, negate: bool) {
555 if self.has_side_effect(predicate) {
557 return;
558 }
559 match &predicate.peel_parens().kind {
560 ExprKind::Ternary(cond, then, otherwise) => {
561 if let Some(value) = self.const_bool(cond) {
562 self.add_facts(if value { then } else { otherwise }, negate);
563 }
564 }
565 ExprKind::Unary(op, inner) if op.kind == UnOpKind::Not => {
566 self.add_facts(inner, !negate);
567 }
568 ExprKind::Binary(lhs, op, rhs) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
569 let is_and = op.kind == BinOpKind::And;
570 for (side, other) in [(lhs, rhs), (rhs, lhs)] {
572 if let Some(value) = self.const_bool(side) {
573 if is_and == value {
574 self.add_facts(other, negate);
575 }
576 return;
577 }
578 }
579 if is_and == negate {
580 let baseline = self.state.low_s.clone();
582 self.add_facts(lhs, negate);
583 let from_lhs = mem::replace(&mut self.state.low_s, baseline.clone());
584 self.add_facts(rhs, negate);
585 let from_rhs = mem::replace(&mut self.state.low_s, baseline);
586 self.state.low_s.extend(from_lhs.intersection(&from_rhs));
587 } else {
588 self.add_facts(lhs, negate);
589 self.add_facts(rhs, negate);
590 }
591 }
592 ExprKind::Binary(lhs, op, rhs) => {
593 let op = if negate { negate_comparison(op.kind) } else { op.kind };
594 for (candidate, bound, op) in [(lhs, rhs, op), (rhs, lhs, reverse_comparison(op))] {
595 let (Some(value), Some(bound)) =
596 (self.current_value(candidate), self.const_value(bound))
597 else {
598 continue;
599 };
600 let proves_low = match op {
601 BinOpKind::Lt => bound <= SECP256K1_HALF_ORDER + U256::ONE,
602 BinOpKind::Le | BinOpKind::Eq => bound <= SECP256K1_HALF_ORDER,
603 _ => false,
604 };
605 if proves_low {
606 self.state.low_s.insert(value);
607 }
608 }
609 }
610 _ => {}
611 }
612 }
613
614 fn branch(
617 &mut self,
618 cond: &'gcx Expr<'gcx>,
619 then: impl FnOnce(&mut Self) -> bool,
620 otherwise: impl FnOnce(&mut Self) -> bool,
621 ) -> bool {
622 if let Some(value) = self.const_bool(cond) {
623 self.assume(cond, !value);
624 return if value { then(self) } else { otherwise(self) };
625 }
626 let baseline = self.state.clone();
627 self.assume(cond, false);
628 let then_live = then(self);
629 let after_then = mem::replace(&mut self.state, baseline);
630 self.assume(cond, true);
631 let else_live = otherwise(self);
632 if then_live && else_live {
633 let after_else = mem::take(&mut self.state);
634 self.state = self.join(after_then, after_else);
635 } else if then_live {
636 self.state = after_then;
637 }
638 then_live || else_live
639 }
640
641 fn run_block(&mut self, stmts: &'gcx [Stmt<'gcx>]) -> bool {
642 stmts.iter().all(|stmt| self.run_stmt(stmt))
643 }
644
645 fn run_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> bool {
647 match &stmt.kind {
648 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => self.run_block(block.stmts),
649 StmtKind::If(cond, then, otherwise) => {
650 let _ = self.visit_expr(cond);
651 self.branch(
652 cond,
653 |this| this.run_stmt(then),
654 |this| otherwise.is_none_or(|otherwise| this.run_stmt(otherwise)),
655 )
656 }
657 StmtKind::Loop(block, source) => self.run_loop(block, *source),
658 StmtKind::Try(stmt_try) => {
659 let _ = self.visit_expr(&stmt_try.expr);
660 let after_call = self.state.clone();
661 let mut live = Vec::new();
662 for clause in stmt_try.clauses {
663 self.state = after_call.clone();
664 if self.run_block(clause.block.stmts) {
665 live.push(self.state.clone());
666 }
667 }
668 self.join_all(live)
669 }
670 StmtKind::Break | StmtKind::Continue => {
671 if matches!(stmt.kind, StmtKind::Continue)
672 && let Some(next) = self.loop_next
673 {
674 self.run_stmt(next);
675 }
676 self.loop_exits.push(self.state.clone());
677 false
678 }
679 StmtKind::DeclSingle(var) => {
680 let init = self.gcx.hir.variable(*var).initializer;
681 self.store(&[(Some(ValueKey::Var(*var)), init)], init);
682 true
683 }
684 StmtKind::DeclMulti(vars, init) => {
685 let pairs: Vec<_> = match tuple_elems(init) {
686 Some(elems) => vars
687 .iter()
688 .zip(elems)
689 .map(|(var, rhs)| (var.map(ValueKey::Var), *rhs))
690 .collect(),
691 None => vars.iter().map(|var| (var.map(ValueKey::Var), None)).collect(),
692 };
693 self.store(&pairs, Some(init));
694 true
695 }
696 StmtKind::Expr(expr) => {
697 match &expr.peel_parens().kind {
698 ExprKind::Assign(lhs, None, rhs) => self.store_expr(lhs, rhs),
699 _ => {
700 let _ = self.visit_expr(expr);
701 }
702 }
703 !is_exit_call(self.gcx, expr)
704 }
705 StmtKind::AssemblyBlock(..) | StmtKind::Err(_) => {
706 self.use_all_pending();
709 self.state = FlowState::default();
710 true
711 }
712 StmtKind::Return(None) => {
713 self.use_return_values();
714 false
715 }
716 StmtKind::Return(Some(expr)) | StmtKind::Revert(expr) => {
717 let _ = self.visit_expr(expr);
718 false
719 }
720 _ => {
721 let _ = self.walk_stmt(stmt);
722 true
723 }
724 }
725 }
726
727 fn run_loop(&mut self, block: &'gcx hir::Block<'gcx>, source: LoopSource<'gcx>) -> bool {
728 let next = loop_update(source);
729 let outer_exits = mem::take(&mut self.loop_exits);
730 let outer_next = mem::replace(&mut self.loop_next, next);
731 let single_iteration = matches!(source, LoopSource::DoWhile)
735 && matches!(block.stmts.last().map(|stmt| &stmt.kind),
736 Some(StmtKind::If(cond, ..)) if self.const_bool(cond) == Some(false));
737 if !single_iteration {
738 let entry = self.state.clone();
739 let hits = self.hits.len();
740 self.run_loop_body(block);
741 self.hits.truncate(hits);
742 let mut states = mem::take(&mut self.loop_exits);
743 states.push(entry);
744 self.join_all(states);
745 }
746 self.run_loop_body(block);
747 let exits = mem::replace(&mut self.loop_exits, outer_exits);
748 self.loop_next = outer_next;
749 self.join_all(exits)
750 }
751
752 fn run_loop_body(&mut self, block: &'gcx hir::Block<'gcx>) {
753 if self.run_block(block.stmts) && self.loop_next.is_none_or(|next| self.run_stmt(next)) {
754 self.loop_exits.push(self.state.clone());
755 }
756 }
757}
758
759impl<'gcx> Visit<'gcx> for Analyzer<'gcx> {
760 type BreakValue = Never;
761
762 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
763 &self.gcx.hir
764 }
765
766 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<Never> {
767 match &expr.kind {
768 ExprKind::Ident(_) => {
769 if let Some(value) = self.current_value(expr) {
770 self.use_value(value);
771 }
772 }
773 ExprKind::Binary(lhs, op, rhs) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
774 let _ = self.visit_expr(lhs);
775 let run_rhs = |this: &mut Self| {
776 let _ = this.visit_expr(rhs);
777 true
778 };
779 let skip_rhs = |_: &mut Self| true;
780 if op.kind == BinOpKind::And {
781 self.branch(lhs, run_rhs, skip_rhs);
782 } else {
783 self.branch(lhs, skip_rhs, run_rhs);
784 }
785 }
786 ExprKind::Ternary(cond, then, otherwise) => {
787 let _ = self.visit_expr(cond);
788 self.branch(
789 cond,
790 |this| {
791 let _ = this.visit_expr(then);
792 true
793 },
794 |this| {
795 let _ = this.visit_expr(otherwise);
796 true
797 },
798 );
799 }
800 ExprKind::Call(callee, args) if is_require_or_assert(self.gcx, callee) => {
801 let _ = self.walk_expr(expr);
802 if let Some(cond) = args.exprs().next() {
803 self.assume(cond, false);
804 }
805 }
806 ExprKind::Assign(lhs, None, rhs) => {
807 self.visit_lhs(lhs);
808 let _ = self.visit_expr(rhs);
809 self.assign_lhs(lhs, Some(rhs));
810 }
811 ExprKind::Assign(lhs, Some(_), _) => {
812 let _ = self.walk_expr(expr);
813 self.assign_lhs(lhs, None);
814 }
815 ExprKind::Delete(target) => {
816 self.visit_lhs(target);
817 if let Some(key) = self.place_key(target) {
818 self.assign(key, (None, true));
819 }
820 }
821 ExprKind::Unary(op, target) if op.kind.has_side_effects() => {
822 let _ = self.walk_expr(expr);
823 if let Some(key) = self.place_key(target) {
824 self.assign(key, (None, false));
825 }
826 }
827 ExprKind::Call(callee, args) => {
828 let _ = self.walk_expr(expr);
829 if call_may_mutate_state(self.gcx, callee) {
830 self.invalidate_mutable_state();
831 }
832 for var in self.reference_args(callee, args) {
833 self.assign(ValueKey::Var(var), (None, false));
834 }
835 if let Some(recovery) = self.pending_recovery(expr) {
836 match self.deferred.get_mut(&expr.id) {
837 Some(captured) => *captured = Some(recovery),
838 None => self.emit_hit(recovery.span),
839 }
840 }
841 }
842 _ => {
843 let _ = self.walk_expr(expr);
844 }
845 }
846 ControlFlow::Continue(())
847 }
848}
849
850struct SideEffects<'a, 'gcx>(&'a Analyzer<'gcx>);
852
853impl<'gcx> Visit<'gcx> for SideEffects<'_, 'gcx> {
854 type BreakValue = ();
855
856 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
857 &self.0.gcx.hir
858 }
859
860 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<()> {
861 match &expr.kind {
862 ExprKind::Assign(..) | ExprKind::Delete(_) => ControlFlow::Break(()),
863 ExprKind::Unary(op, _) if op.kind.has_side_effects() => ControlFlow::Break(()),
864 ExprKind::Call(callee, ..) if call_may_mutate_state(self.0.gcx, callee) => {
865 ControlFlow::Break(())
866 }
867 ExprKind::Ternary(cond, then, otherwise) => {
868 self.visit_expr(cond)?;
869 for arm in self.0.live_arms(cond, *then, *otherwise) {
870 self.visit_expr(arm)?;
871 }
872 ControlFlow::Continue(())
873 }
874 ExprKind::Binary(lhs, op, _)
875 if matches!(op.kind, BinOpKind::And | BinOpKind::Or)
876 && self
877 .0
878 .const_bool(lhs)
879 .is_some_and(|value| (op.kind == BinOpKind::And) != value) =>
880 {
881 self.visit_expr(lhs)
882 }
883 _ => self.walk_expr(expr),
884 }
885 }
886}
887
888fn var_of(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<VariableId> {
890 match &expr.peel_parens().kind {
891 ExprKind::Ident(_) => gcx.resolved_variable(expr),
892 ExprKind::Call(callee, args) if is_transparent_cast(callee) && args.len() == 1 => {
893 args.exprs().next().and_then(|expr| var_of(gcx, expr))
894 }
895 _ => None,
896 }
897}
898
899fn is_transparent_cast(callee: &Expr<'_>) -> bool {
900 matches!(
901 &callee.peel_parens().kind,
902 ExprKind::Type(hir::Type {
903 kind: TypeKind::Elementary(
904 ElementaryType::UInt(size) | ElementaryType::FixedBytes(size)
905 ),
906 ..
907 }) if size.bits() == 256
908 )
909}
910
911const fn negate_comparison(op: BinOpKind) -> BinOpKind {
912 match op {
913 BinOpKind::Lt => BinOpKind::Ge,
914 BinOpKind::Le => BinOpKind::Gt,
915 BinOpKind::Gt => BinOpKind::Le,
916 BinOpKind::Ge => BinOpKind::Lt,
917 BinOpKind::Eq => BinOpKind::Ne,
918 BinOpKind::Ne => BinOpKind::Eq,
919 _ => op,
920 }
921}
922
923const fn reverse_comparison(op: BinOpKind) -> BinOpKind {
924 match op {
925 BinOpKind::Lt => BinOpKind::Gt,
926 BinOpKind::Le => BinOpKind::Ge,
927 BinOpKind::Gt => BinOpKind::Lt,
928 BinOpKind::Ge => BinOpKind::Le,
929 _ => op,
930 }
931}
932
933fn call_may_mutate_state(gcx: Gcx<'_>, callee: &Expr<'_>) -> bool {
934 let callee = callee.peel_parens();
935 if matches!(callee.kind, ExprKind::Type(_)) {
936 return false;
937 }
938 if let Some(ty) = gcx.type_of_expr(callee.id)
939 && let TyKind::Fn(function) = ty.peel_refs().kind
940 {
941 return function.state_mutability > StateMutability::View;
942 }
943 gcx.resolved_builtin(callee).is_none()
944}