forge_lint/sol/analysis/
modifier_outcome.rs1use super::is_literal_false;
4use solar::sema::{
5 Gcx,
6 builtins::Builtin,
7 hir::{Block, Expr, ExprKind, LoopSource, Stmt, StmtKind},
8};
9
10#[derive(Clone, Copy)]
16pub struct Outcome {
17 falls_through: bool,
19 returns: bool,
21 breaks: bool,
23 continues: bool,
25}
26
27impl Outcome {
28 const COVERED: Self =
30 Self { falls_through: false, returns: false, breaks: false, continues: false };
31 const FALLTHROUGH: Self = Self { falls_through: true, ..Self::COVERED };
32 const RETURNS: Self = Self { returns: true, ..Self::COVERED };
33 const BREAKS: Self = Self { breaks: true, ..Self::COVERED };
34 const CONTINUES: Self = Self { continues: true, ..Self::COVERED };
35
36 pub const fn can_skip_placeholder(self) -> bool {
39 self.falls_through || self.returns
40 }
41
42 const fn merge(self, other: Self) -> Self {
43 Self {
44 falls_through: self.falls_through || other.falls_through,
45 returns: self.returns || other.returns,
46 breaks: self.breaks || other.breaks,
47 continues: self.continues || other.continues,
48 }
49 }
50}
51
52pub fn block_outcome(gcx: Gcx<'_>, block: Block<'_>) -> Outcome {
53 let mut outcome = Outcome::FALLTHROUGH;
54 for stmt in block.stmts {
55 if !outcome.falls_through {
57 return outcome;
58 }
59 let stmt_outcome = stmt_outcome(gcx, stmt);
60 outcome = Outcome {
61 falls_through: stmt_outcome.falls_through,
62 returns: outcome.returns || stmt_outcome.returns,
63 breaks: outcome.breaks || stmt_outcome.breaks,
64 continues: outcome.continues || stmt_outcome.continues,
65 };
66 }
67 outcome
68}
69
70fn stmt_outcome(gcx: Gcx<'_>, stmt: &Stmt<'_>) -> Outcome {
71 match &stmt.kind {
72 StmtKind::Placeholder => Outcome::COVERED,
73 StmtKind::Return(_) => Outcome::RETURNS,
74 StmtKind::Break => Outcome::BREAKS,
75 StmtKind::Continue => Outcome::CONTINUES,
76 StmtKind::Expr(expr) => call_outcome(gcx, expr).unwrap_or(Outcome::FALLTHROUGH),
77 StmtKind::Revert(_) => Outcome::COVERED,
78 StmtKind::Block(block)
79 | StmtKind::UncheckedBlock(block)
80 | StmtKind::AssemblyBlock(block) => block_outcome(gcx, *block),
81 StmtKind::If(_, then_stmt, else_stmt) => {
82 let then_outcome = stmt_outcome(gcx, then_stmt);
83 let else_outcome =
84 else_stmt.map_or(Outcome::FALLTHROUGH, |stmt| stmt_outcome(gcx, stmt));
85 then_outcome.merge(else_outcome)
86 }
87 StmtKind::Loop(block, source) => {
88 let body = block_outcome(gcx, *block);
96 let falls_through =
97 body.breaks || (matches!(source, LoopSource::DoWhile) && body.continues);
98 Outcome { falls_through, returns: body.returns, ..Outcome::COVERED }
99 }
100 StmtKind::Try(try_stmt) => {
101 let mut outcome = Outcome::COVERED;
105 for clause in try_stmt.clauses {
106 outcome = outcome.merge(block_outcome(gcx, clause.block));
107 }
108 outcome
109 }
110 StmtKind::Switch(switch) => {
111 let has_default = switch.cases.last().is_some_and(|case| case.constant.is_none());
114 let mut outcome = if has_default { Outcome::COVERED } else { Outcome::FALLTHROUGH };
115 for case in switch.cases {
116 outcome = outcome.merge(block_outcome(gcx, case.body));
117 }
118 outcome
119 }
120 StmtKind::DeclSingle(_)
121 | StmtKind::DeclMulti(_, _)
122 | StmtKind::Emit(_)
123 | StmtKind::Err(_) => Outcome::FALLTHROUGH,
124 }
125}
126
127fn call_outcome(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<Outcome> {
137 let ExprKind::Call(callee, args, _) = &expr.peel_parens().kind else { return None };
138 match gcx.resolved_builtin(callee)? {
139 Builtin::Revert | Builtin::RevertMsg | Builtin::YulRevert | Builtin::YulInvalid => {
140 Some(Outcome::COVERED)
141 }
142 Builtin::Require | Builtin::Assert if args.exprs().next().is_some_and(is_literal_false) => {
143 Some(Outcome::COVERED)
144 }
145 Builtin::YulReturn
146 | Builtin::YulStop
147 | Builtin::YulSelfdestruct
148 | Builtin::Selfdestruct => Some(Outcome::RETURNS),
149 _ => None,
150 }
151}