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
155type Signature<'gcx> = &'gcx Expr<'gcx>;
157
158struct Analyzer<'gcx> {
159 gcx: Gcx<'gcx>,
160 returns: &'gcx [VariableId],
161 state: FlowState,
162 next_value: u32,
163 hits: Vec<Span>,
164 deferred: HashMap<ExprId, Option<PendingRecovery>>,
167 loop_exits: Vec<FlowState>,
169 loop_next: Option<&'gcx Stmt<'gcx>>,
171}
172
173impl<'gcx> Analyzer<'gcx> {
174 const fn fresh_value(&mut self) -> ValueId {
175 self.next_value += 1;
176 ValueId::Assigned(self.next_value)
177 }
178
179 fn join(&mut self, left: FlowState, right: FlowState) -> FlowState {
180 let mut field_epoch = left.field_epoch.clone();
183 for (var, epoch) in &right.field_epoch {
184 let entry = field_epoch.entry(*var).or_insert(0);
185 *entry = (*entry).max(*epoch);
186 }
187 let mut joined = FlowState {
188 low_s: left.low_s.intersection(&right.low_s).copied().collect(),
189 field_epoch,
190 ..FlowState::default()
191 };
192 let mut merged = HashMap::new();
193 let keys: HashSet<_> = left.values.keys().chain(right.values.keys()).copied().collect();
194 for key in keys {
195 let (l, r) = (left.value(key), right.value(key));
196 let value = if l == r {
197 l
198 } else {
199 let value = *merged.entry((l, r)).or_insert_with(|| self.fresh_value());
200 if left.low_s.contains(&l) && right.low_s.contains(&r) {
201 joined.low_s.insert(value);
202 }
203 value
204 };
205 joined.values.insert(key, value);
206 for recovery in left.pending.get(&l).into_iter().chain(right.pending.get(&r)).flatten()
207 {
208 joined.add_pending(value, *recovery);
209 }
210 }
211 joined
212 }
213
214 fn join_all(&mut self, states: Vec<FlowState>) -> bool {
216 let Some(joined) = states.into_iter().reduce(|l, r| self.join(l, r)) else { return false };
217 self.state = joined;
218 true
219 }
220
221 fn emit_hit(&mut self, span: Span) {
222 if !self.hits.contains(&span) {
223 self.hits.push(span);
224 }
225 }
226
227 fn use_value(&mut self, value: ValueId) {
228 for recovery in self.state.pending.remove(&value).unwrap_or_default() {
229 self.emit_hit(recovery.span);
230 }
231 }
232
233 fn use_return_values(&mut self) {
234 for &var in self.returns {
235 self.use_value(self.state.value(ValueKey::Var(var)));
236 }
237 }
238
239 fn use_all_pending(&mut self) {
240 for recoveries in mem::take(&mut self.state.pending).into_values() {
241 for recovery in recoveries {
242 self.emit_hit(recovery.span);
243 }
244 }
245 }
246
247 fn validate_pending(&mut self) {
249 let low_s = &self.state.low_s;
250 self.state.pending.retain(|_, recoveries| {
251 recoveries.retain(|r| !r.signature.is_some_and(|s| low_s.contains(&s)));
252 !recoveries.is_empty()
253 });
254 }
255
256 fn current_value(&self, expr: &Expr<'_>) -> Option<ValueId> {
257 match &expr.peel_parens().kind {
258 ExprKind::Assign(lhs, None, _) => self.current_value(lhs),
259 _ => self.place_key(expr).map(|key| self.state.value(key)),
260 }
261 }
262
263 fn place_key(&self, expr: &Expr<'_>) -> Option<ValueKey> {
266 match &expr.peel_parens().kind {
267 ExprKind::Call(callee, args, _) if is_transparent_cast(callee) && args.len() == 1 => {
268 self.place_key(args.exprs().next()?)
269 }
270 ExprKind::Member(base, field) => {
271 var_of(self.gcx, base).map(|var| self.state.field_key(var, field.name))
272 }
273 _ => var_of(self.gcx, expr).map(ValueKey::Var),
274 }
275 }
276
277 fn reference_args(
280 &self,
281 callee: &'gcx Expr<'gcx>,
282 args: &'gcx hir::CallArgs<'gcx>,
283 ) -> Vec<VariableId> {
284 let callee = callee.peel_parens();
285 let Some(TyKind::Fn(function)) = self.gcx.type_of_expr(callee.id).map(|ty| ty.kind) else {
286 return Vec::new();
287 };
288 if !function.is_internal() {
289 return Vec::new();
290 }
291 let receiver = match &callee.kind {
292 ExprKind::Member(base, _)
293 if self.gcx.resolved_callee(callee.id).is_some_and(|c| c.attached) =>
294 {
295 Some(*base)
296 }
297 _ => None,
298 };
299 receiver
300 .into_iter()
301 .chain(args.exprs())
302 .filter_map(|expr| var_of(self.gcx, expr))
303 .filter(|&var| {
304 matches!(
305 self.gcx.hir.variable(var).data_location,
306 Some(hir::DataLocation::Memory | hir::DataLocation::Storage)
307 )
308 })
309 .collect()
310 }
311
312 fn is_local(&self, var: VariableId) -> bool {
313 self.gcx.hir.variable(var).is_local_variable()
314 }
315
316 fn ecrecover_call(
318 &self,
319 expr: &'gcx Expr<'gcx>,
320 ) -> Option<(&'gcx Expr<'gcx>, Signature<'gcx>)> {
321 let expr = expr.peel_parens();
322 let ExprKind::Call(callee, args, _) = &expr.kind else { return None };
323 let callee = self.gcx.resolved_builtin(callee.peel_parens());
324 (callee == Some(Builtin::EcRecover) && args.len() == 4)
325 .then_some(expr)
326 .zip(args.exprs().nth(3))
327 }
328
329 fn pending_recovery(&self, expr: &'gcx Expr<'gcx>) -> Option<PendingRecovery> {
330 let (call, signature) = self.ecrecover_call(expr)?;
331 (!self.is_proven_low_s(signature))
332 .then(|| PendingRecovery { signature: self.current_value(signature), span: call.span })
333 }
334
335 fn live_arms<T>(&self, cond: &Expr<'_>, then: T, otherwise: T) -> impl Iterator<Item = T> {
337 match self.const_bool(cond) {
338 Some(true) => [Some(then), None],
339 Some(false) => [None, Some(otherwise)],
340 None => [Some(then), Some(otherwise)],
341 }
342 .into_iter()
343 .flatten()
344 }
345
346 fn result_calls(&self, expr: &'gcx Expr<'gcx>, out: &mut Vec<ExprId>) {
348 let expr = expr.peel_parens();
349 if self.ecrecover_call(expr).is_some() {
350 out.push(expr.id);
351 }
352 match &expr.kind {
353 ExprKind::Ternary(cond, then, otherwise) => {
354 for arm in self.live_arms(cond, *then, *otherwise) {
355 self.result_calls(arm, out);
356 }
357 }
358 ExprKind::Assign(_, None, rhs) => self.result_calls(rhs, out),
359 _ => {}
360 }
361 }
362
363 fn const_value(&self, expr: &Expr<'_>) -> Option<U256> {
364 let expr = expr.peel_parens();
365 match &expr.kind {
366 ExprKind::Call(callee, args, _) if is_transparent_cast(callee) && args.len() == 1 => {
367 self.const_value(args.exprs().next()?)
368 }
369 ExprKind::Binary(lhs, op, rhs)
371 if matches!(op.kind, BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul) =>
372 {
373 let (lhs, rhs) = (self.const_value(lhs)?, self.const_value(rhs)?);
374 Some(match op.kind {
375 BinOpKind::Add => lhs.wrapping_add(rhs),
376 BinOpKind::Sub => lhs.wrapping_sub(rhs),
377 _ => lhs.wrapping_mul(rhs),
378 })
379 }
380 _ if self.gcx.resolved_builtin(expr) == Some(Builtin::TypeMax) => {
381 let TyKind::Elementary(ElementaryType::UInt(size)) =
382 self.gcx.type_of_expr(expr.id)?.kind
383 else {
384 return None;
385 };
386 Some(U256::MAX >> (256 - size.bits()))
387 }
388 _ => self.gcx.try_eval_const(expr).ok()?.as_u256(),
389 }
390 }
391
392 fn const_bool(&self, expr: &Expr<'_>) -> Option<bool> {
393 match self.gcx.try_eval_const_value(expr).ok()? {
394 ConstValue::Bool(value) => Some(*value),
395 _ => None,
396 }
397 }
398
399 fn is_proven_low_s(&self, expr: &'gcx Expr<'gcx>) -> bool {
400 self.const_value(expr).is_some_and(|value| value <= SECP256K1_HALF_ORDER)
401 || self.current_value(expr).is_some_and(|value| self.state.low_s.contains(&value))
402 || matches!(&expr.peel_parens().kind, ExprKind::Ternary(cond, then, otherwise)
403 if self.live_arms(cond, *then, *otherwise).all(|arm| self.is_proven_low_s(arm)))
404 }
405
406 fn assigned(&self, rhs: Option<&'gcx Expr<'gcx>>) -> (Option<ValueId>, bool) {
408 (
409 rhs.and_then(|rhs| self.current_value(rhs)),
410 rhs.is_some_and(|rhs| self.is_proven_low_s(rhs)),
411 )
412 }
413
414 fn assign(&mut self, key: ValueKey, (value, low_s): (Option<ValueId>, bool)) {
415 let value = value.unwrap_or_else(|| self.fresh_value());
416 if low_s {
417 self.state.low_s.insert(value);
418 }
419 if let ValueKey::Field(var, field, _) = key {
420 if let Some(location) = self.aliasable_location(var) {
423 for other in self.state.tracked_vars() {
424 if other != var && self.aliasable_location(other) == Some(location) {
425 self.state.reset_fields(other);
426 }
427 }
428 }
429 let key = self.state.field_key(var, field);
430 self.state.set(key, value);
431 } else {
432 self.state.set(key, value);
433 }
434 }
435
436 fn aliasable_location(&self, var: VariableId) -> Option<hir::DataLocation> {
438 let var = self.gcx.hir.variable(var);
439 if var.kind.is_state() && var.mutability.is_none() {
440 return Some(hir::DataLocation::Storage);
441 }
442 var.data_location
443 .filter(|loc| matches!(loc, hir::DataLocation::Memory | hir::DataLocation::Storage))
444 }
445
446 fn pairs(
448 &self,
449 lhs: &'gcx Expr<'gcx>,
450 rhs: Option<&'gcx Expr<'gcx>>,
451 out: &mut Vec<Pair<'gcx>>,
452 ) {
453 let Some(elems) = tuple_elems(lhs) else { return out.push((self.place_key(lhs), rhs)) };
454 let rhs_elems = rhs.and_then(tuple_elems);
455 for (i, lhs) in elems.iter().enumerate() {
456 let rhs = rhs_elems.and_then(|elems| elems.get(i).copied().flatten());
457 match lhs {
458 Some(lhs) => self.pairs(lhs, rhs, out),
459 None => out.push((None, rhs)),
460 }
461 }
462 }
463
464 fn assign_pairs(&mut self, pairs: &[Pair<'gcx>]) {
466 let assigned: Vec<_> =
467 pairs.iter().filter_map(|(key, rhs)| Some(((*key)?, self.assigned(*rhs)))).collect();
468 for (key, assigned) in assigned {
469 self.assign(key, assigned);
470 }
471 }
472
473 fn assign_lhs(&mut self, lhs: &'gcx Expr<'gcx>, rhs: Option<&'gcx Expr<'gcx>>) {
474 let mut pairs = Vec::new();
475 self.pairs(lhs, rhs, &mut pairs);
476 self.assign_pairs(&pairs);
477 }
478
479 fn store(&mut self, pairs: &[Pair<'gcx>], rhs: Option<&'gcx Expr<'gcx>>) {
483 let mut calls = Vec::new();
484 for &(key, rhs) in pairs {
485 let local = key.filter(|key| self.is_local(key.var()));
486 let mut result_calls = Vec::new();
487 if let Some(rhs) = rhs {
488 self.result_calls(rhs, &mut result_calls);
489 }
490 for call in result_calls {
491 match local {
492 Some(_) => self.deferred.insert(call, None),
493 None => self.deferred.remove(&call),
494 };
495 calls.push((call, local));
496 }
497 }
498 let local_target = matches!(pairs, [(Some(key), _)] if self.is_local(key.var()));
499 if let Some(rhs) = rhs {
500 match &rhs.peel_parens().kind {
501 ExprKind::Assign(lhs, None, inner) if local_target => self.store_expr(lhs, inner),
502 _ if local_target && self.current_value(rhs).is_some() => {}
504 _ => {
505 let _ = self.visit_expr(rhs);
506 }
507 }
508 }
509 self.assign_pairs(pairs);
510 for (call, key) in calls {
511 if let (Some(key), Some(Some(recovery))) = (key, self.deferred.remove(&call)) {
512 let value = self.state.value(key);
513 self.state.add_pending(value, recovery);
514 }
515 }
516 }
517
518 fn store_expr(&mut self, lhs: &'gcx Expr<'gcx>, rhs: &'gcx Expr<'gcx>) {
519 let mut pairs = Vec::new();
520 self.pairs(lhs, Some(rhs), &mut pairs);
521 self.store(&pairs, Some(rhs));
522 }
523
524 fn visit_lhs(&mut self, lhs: &'gcx Expr<'gcx>) {
526 if let Some(elems) = tuple_elems(lhs) {
527 for elem in elems.iter().flatten() {
528 self.visit_lhs(elem);
529 }
530 } else if self.place_key(lhs).is_none() {
531 let _ = self.visit_expr(lhs);
532 }
533 }
534
535 fn invalidate_mutable_state(&mut self) {
538 for var in self.state.tracked_vars() {
539 let variable = self.gcx.hir.variable(var);
540 if (variable.kind.is_state() && variable.mutability.is_none())
541 || variable.data_location == Some(hir::DataLocation::Storage)
542 {
543 self.assign(ValueKey::Var(var), (None, false));
544 }
545 }
546 }
547
548 fn has_side_effect(&self, expr: &'gcx Expr<'gcx>) -> bool {
549 SideEffects(self).visit_expr(expr).is_break()
550 }
551
552 fn assume(&mut self, predicate: &'gcx Expr<'gcx>, negate: bool) {
553 self.add_facts(predicate, negate);
554 self.validate_pending();
555 }
556
557 fn add_facts(&mut self, predicate: &'gcx Expr<'gcx>, negate: bool) {
558 if self.has_side_effect(predicate) {
560 return;
561 }
562 match &predicate.peel_parens().kind {
563 ExprKind::Ternary(cond, then, otherwise) => {
564 if let Some(value) = self.const_bool(cond) {
565 self.add_facts(if value { then } else { otherwise }, negate);
566 }
567 }
568 ExprKind::Unary(op, inner) if op.kind == UnOpKind::Not => {
569 self.add_facts(inner, !negate);
570 }
571 ExprKind::Binary(lhs, op, rhs) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
572 let is_and = op.kind == BinOpKind::And;
573 for (side, other) in [(lhs, rhs), (rhs, lhs)] {
575 if let Some(value) = self.const_bool(side) {
576 if is_and == value {
577 self.add_facts(other, negate);
578 }
579 return;
580 }
581 }
582 if is_and == negate {
583 let baseline = self.state.low_s.clone();
585 self.add_facts(lhs, negate);
586 let from_lhs = mem::replace(&mut self.state.low_s, baseline.clone());
587 self.add_facts(rhs, negate);
588 let from_rhs = mem::replace(&mut self.state.low_s, baseline);
589 self.state.low_s.extend(from_lhs.intersection(&from_rhs));
590 } else {
591 self.add_facts(lhs, negate);
592 self.add_facts(rhs, negate);
593 }
594 }
595 ExprKind::Binary(lhs, op, rhs) => {
596 let op = if negate { negate_comparison(op.kind) } else { op.kind };
597 for (candidate, bound, op) in [(lhs, rhs, op), (rhs, lhs, reverse_comparison(op))] {
598 let (Some(value), Some(bound)) =
599 (self.current_value(candidate), self.const_value(bound))
600 else {
601 continue;
602 };
603 let proves_low = match op {
604 BinOpKind::Lt => bound <= SECP256K1_HALF_ORDER + U256::from(1),
605 BinOpKind::Le | BinOpKind::Eq => bound <= SECP256K1_HALF_ORDER,
606 _ => false,
607 };
608 if proves_low {
609 self.state.low_s.insert(value);
610 }
611 }
612 }
613 _ => {}
614 }
615 }
616
617 fn branch(
620 &mut self,
621 cond: &'gcx Expr<'gcx>,
622 then: impl FnOnce(&mut Self) -> bool,
623 otherwise: impl FnOnce(&mut Self) -> bool,
624 ) -> bool {
625 if let Some(value) = self.const_bool(cond) {
626 self.assume(cond, !value);
627 return if value { then(self) } else { otherwise(self) };
628 }
629 let baseline = self.state.clone();
630 self.assume(cond, false);
631 let then_live = then(self);
632 let after_then = mem::replace(&mut self.state, baseline);
633 self.assume(cond, true);
634 let else_live = otherwise(self);
635 if then_live && else_live {
636 let after_else = mem::take(&mut self.state);
637 self.state = self.join(after_then, after_else);
638 } else if then_live {
639 self.state = after_then;
640 }
641 then_live || else_live
642 }
643
644 fn run_block(&mut self, stmts: &'gcx [Stmt<'gcx>]) -> bool {
645 stmts.iter().all(|stmt| self.run_stmt(stmt))
646 }
647
648 fn run_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> bool {
650 match &stmt.kind {
651 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => self.run_block(block.stmts),
652 StmtKind::If(cond, then, otherwise) => {
653 let _ = self.visit_expr(cond);
654 self.branch(
655 cond,
656 |this| this.run_stmt(then),
657 |this| otherwise.is_none_or(|otherwise| this.run_stmt(otherwise)),
658 )
659 }
660 StmtKind::Loop(block, source) => self.run_loop(block, *source),
661 StmtKind::Try(stmt_try) => {
662 let _ = self.visit_expr(&stmt_try.expr);
663 let after_call = self.state.clone();
664 let mut live = Vec::new();
665 for clause in stmt_try.clauses {
666 self.state = after_call.clone();
667 if self.run_block(clause.block.stmts) {
668 live.push(self.state.clone());
669 }
670 }
671 self.join_all(live)
672 }
673 StmtKind::Break | StmtKind::Continue => {
674 if matches!(stmt.kind, StmtKind::Continue)
675 && let Some(next) = self.loop_next
676 {
677 self.run_stmt(next);
678 }
679 self.loop_exits.push(self.state.clone());
680 false
681 }
682 StmtKind::DeclSingle(var) => {
683 let init = self.gcx.hir.variable(*var).initializer;
684 self.store(&[(Some(ValueKey::Var(*var)), init)], init);
685 true
686 }
687 StmtKind::DeclMulti(vars, init) => {
688 let pairs: Vec<_> = match tuple_elems(init) {
689 Some(elems) => vars
690 .iter()
691 .zip(elems)
692 .map(|(var, rhs)| (var.map(ValueKey::Var), *rhs))
693 .collect(),
694 None => vars.iter().map(|var| (var.map(ValueKey::Var), None)).collect(),
695 };
696 self.store(&pairs, Some(init));
697 true
698 }
699 StmtKind::Expr(expr) => {
700 match &expr.peel_parens().kind {
701 ExprKind::Assign(lhs, None, rhs) => self.store_expr(lhs, rhs),
702 _ => {
703 let _ = self.visit_expr(expr);
704 }
705 }
706 !is_exit_call(self.gcx, expr)
707 }
708 StmtKind::AssemblyBlock(_) | StmtKind::Err(_) => {
709 self.use_all_pending();
712 self.state = FlowState::default();
713 true
714 }
715 StmtKind::Return(None) => {
716 self.use_return_values();
717 false
718 }
719 StmtKind::Return(Some(expr)) | StmtKind::Revert(expr) => {
720 let _ = self.visit_expr(expr);
721 false
722 }
723 _ => {
724 let _ = self.walk_stmt(stmt);
725 true
726 }
727 }
728 }
729
730 fn run_loop(&mut self, block: &'gcx hir::Block<'gcx>, source: LoopSource<'gcx>) -> bool {
731 let next = loop_update(source);
732 let outer_exits = mem::take(&mut self.loop_exits);
733 let outer_next = mem::replace(&mut self.loop_next, next);
734 let single_iteration = matches!(source, LoopSource::DoWhile)
738 && matches!(block.stmts.last().map(|stmt| &stmt.kind),
739 Some(StmtKind::If(cond, ..)) if self.const_bool(cond) == Some(false));
740 if !single_iteration {
741 let entry = self.state.clone();
742 let hits = self.hits.len();
743 self.run_loop_body(block);
744 self.hits.truncate(hits);
745 let mut states = mem::take(&mut self.loop_exits);
746 states.push(entry);
747 self.join_all(states);
748 }
749 self.run_loop_body(block);
750 let exits = mem::replace(&mut self.loop_exits, outer_exits);
751 self.loop_next = outer_next;
752 self.join_all(exits)
753 }
754
755 fn run_loop_body(&mut self, block: &'gcx hir::Block<'gcx>) {
756 if self.run_block(block.stmts) && self.loop_next.is_none_or(|next| self.run_stmt(next)) {
757 self.loop_exits.push(self.state.clone());
758 }
759 }
760}
761
762impl<'gcx> Visit<'gcx> for Analyzer<'gcx> {
763 type BreakValue = Never;
764
765 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
766 &self.gcx.hir
767 }
768
769 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<Never> {
770 match &expr.kind {
771 ExprKind::Ident(_) => {
772 if let Some(value) = self.current_value(expr) {
773 self.use_value(value);
774 }
775 }
776 ExprKind::Binary(lhs, op, rhs) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
777 let _ = self.visit_expr(lhs);
778 let run_rhs = |this: &mut Self| {
779 let _ = this.visit_expr(rhs);
780 true
781 };
782 let skip_rhs = |_: &mut Self| true;
783 if op.kind == BinOpKind::And {
784 self.branch(lhs, run_rhs, skip_rhs);
785 } else {
786 self.branch(lhs, skip_rhs, run_rhs);
787 }
788 }
789 ExprKind::Ternary(cond, then, otherwise) => {
790 let _ = self.visit_expr(cond);
791 self.branch(
792 cond,
793 |this| {
794 let _ = this.visit_expr(then);
795 true
796 },
797 |this| {
798 let _ = this.visit_expr(otherwise);
799 true
800 },
801 );
802 }
803 ExprKind::Call(callee, args, _) if is_require_or_assert(self.gcx, callee) => {
804 let _ = self.walk_expr(expr);
805 if let Some(cond) = args.exprs().next() {
806 self.assume(cond, false);
807 }
808 }
809 ExprKind::Assign(lhs, None, rhs) => {
810 self.visit_lhs(lhs);
811 let _ = self.visit_expr(rhs);
812 self.assign_lhs(lhs, Some(rhs));
813 }
814 ExprKind::Assign(lhs, Some(_), _) => {
815 let _ = self.walk_expr(expr);
816 self.assign_lhs(lhs, None);
817 }
818 ExprKind::Delete(target) => {
819 self.visit_lhs(target);
820 if let Some(key) = self.place_key(target) {
821 self.assign(key, (None, true));
822 }
823 }
824 ExprKind::Unary(op, target) if op.kind.has_side_effects() => {
825 let _ = self.walk_expr(expr);
826 if let Some(key) = self.place_key(target) {
827 self.assign(key, (None, false));
828 }
829 }
830 ExprKind::Call(callee, args, _) => {
831 let _ = self.walk_expr(expr);
832 if call_may_mutate_state(self.gcx, callee) {
833 self.invalidate_mutable_state();
834 }
835 for var in self.reference_args(callee, args) {
836 self.assign(ValueKey::Var(var), (None, false));
837 }
838 if let Some(recovery) = self.pending_recovery(expr) {
839 match self.deferred.get_mut(&expr.id) {
840 Some(captured) => *captured = Some(recovery),
841 None => self.emit_hit(recovery.span),
842 }
843 }
844 }
845 _ => {
846 let _ = self.walk_expr(expr);
847 }
848 }
849 ControlFlow::Continue(())
850 }
851}
852
853struct SideEffects<'a, 'gcx>(&'a Analyzer<'gcx>);
855
856impl<'gcx> Visit<'gcx> for SideEffects<'_, 'gcx> {
857 type BreakValue = ();
858
859 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
860 &self.0.gcx.hir
861 }
862
863 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<()> {
864 match &expr.kind {
865 ExprKind::Assign(..) | ExprKind::Delete(_) => ControlFlow::Break(()),
866 ExprKind::Unary(op, _) if op.kind.has_side_effects() => ControlFlow::Break(()),
867 ExprKind::Call(callee, ..) if call_may_mutate_state(self.0.gcx, callee) => {
868 ControlFlow::Break(())
869 }
870 ExprKind::Ternary(cond, then, otherwise) => {
871 self.visit_expr(cond)?;
872 for arm in self.0.live_arms(cond, *then, *otherwise) {
873 self.visit_expr(arm)?;
874 }
875 ControlFlow::Continue(())
876 }
877 ExprKind::Binary(lhs, op, _)
878 if matches!(op.kind, BinOpKind::And | BinOpKind::Or)
879 && self
880 .0
881 .const_bool(lhs)
882 .is_some_and(|value| (op.kind == BinOpKind::And) != value) =>
883 {
884 self.visit_expr(lhs)
885 }
886 _ => self.walk_expr(expr),
887 }
888 }
889}
890
891fn var_of(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<VariableId> {
893 match &expr.peel_parens().kind {
894 ExprKind::Ident(_) => gcx.resolved_variable(expr),
895 ExprKind::Call(callee, args, _) if is_transparent_cast(callee) && args.len() == 1 => {
896 args.exprs().next().and_then(|expr| var_of(gcx, expr))
897 }
898 _ => None,
899 }
900}
901
902fn is_transparent_cast(callee: &Expr<'_>) -> bool {
903 matches!(
904 &callee.peel_parens().kind,
905 ExprKind::Type(hir::Type {
906 kind: TypeKind::Elementary(
907 ElementaryType::UInt(size) | ElementaryType::FixedBytes(size)
908 ),
909 ..
910 }) if size.bits() == 256
911 )
912}
913
914const fn negate_comparison(op: BinOpKind) -> BinOpKind {
915 match op {
916 BinOpKind::Lt => BinOpKind::Ge,
917 BinOpKind::Le => BinOpKind::Gt,
918 BinOpKind::Gt => BinOpKind::Le,
919 BinOpKind::Ge => BinOpKind::Lt,
920 BinOpKind::Eq => BinOpKind::Ne,
921 BinOpKind::Ne => BinOpKind::Eq,
922 _ => op,
923 }
924}
925
926const fn reverse_comparison(op: BinOpKind) -> BinOpKind {
927 match op {
928 BinOpKind::Lt => BinOpKind::Gt,
929 BinOpKind::Le => BinOpKind::Ge,
930 BinOpKind::Gt => BinOpKind::Lt,
931 BinOpKind::Ge => BinOpKind::Le,
932 _ => op,
933 }
934}
935
936fn call_may_mutate_state(gcx: Gcx<'_>, callee: &Expr<'_>) -> bool {
937 let callee = callee.peel_parens();
938 if matches!(callee.kind, ExprKind::Type(_)) {
939 return false;
940 }
941 if let Some(ty) = gcx.type_of_expr(callee.id)
942 && let TyKind::Fn(function) = ty.peel_refs().kind
943 {
944 return function.state_mutability > StateMutability::View;
945 }
946 gcx.resolved_builtin(callee).is_none()
947}