1use super::WriteAfterWrite;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint, analysis::loop_update},
5};
6use solar::{
7 interface::Span,
8 sema::{
9 Gcx,
10 hir::{
11 BinOpKind, Block, CallArgs, CallOptions, Expr, ExprKind, Function, Stmt, StmtKind,
12 VariableId,
13 },
14 },
15};
16use std::collections::HashMap;
17
18declare_forge_lint!(
19 WRITE_AFTER_WRITE,
20 Severity::Gas,
21 "write-after-write",
22 "redundant storage write; value overwritten before being read"
23);
24
25impl<'gcx> LateLintPass<'gcx> for WriteAfterWrite {
26 fn check_function(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, func: &'gcx Function<'gcx>) {
27 if let Some(body) = func.body {
28 Analyzer { ctx, gcx, pending: HashMap::new() }.check_block(body);
29 }
30 }
31}
32
33struct Analyzer<'a, 'gcx> {
36 ctx: &'a LintContext<'a, 'a>,
37 gcx: Gcx<'gcx>,
38 pending: HashMap<VariableId, Span>,
39}
40
41impl Analyzer<'_, '_> {
42 fn check_block(&mut self, block: Block<'_>) -> bool {
44 block.stmts.iter().all(|stmt| self.check_stmt(stmt))
45 }
46
47 fn check_stmt(&mut self, stmt: &Stmt<'_>) -> bool {
49 match &stmt.kind {
50 StmtKind::Expr(expr) => self.process_expr(expr),
51 StmtKind::DeclSingle(var_id) => {
52 if let Some(init) = self.gcx.hir.variable(*var_id).initializer {
53 self.reads(init);
54 }
55 }
56 StmtKind::DeclMulti(_, expr) => self.reads(expr),
57 StmtKind::Emit(expr) => match &expr.peel_parens().kind {
59 ExprKind::Call(callee, args) => {
60 let (callee, opts) = callee.split_call_options();
61 self.read_call_parts(callee, args, opts);
62 }
63 _ => self.reads(expr),
64 },
65 StmtKind::Return(expr) => {
68 if let Some(expr) = expr {
69 self.reads(expr);
70 }
71 self.pending.clear();
72 return false;
73 }
74 StmtKind::Revert(expr) => {
75 self.reads(expr);
76 self.pending.clear();
77 return false;
78 }
79 StmtKind::Break | StmtKind::Continue => {
80 self.pending.clear();
81 return false;
82 }
83 StmtKind::If(cond, then_stmt, else_stmt) => {
86 self.reads(cond);
87 let then_continues = self.isolated(|this| this.check_stmt(then_stmt));
88 if let Some(else_stmt) = else_stmt {
89 let else_continues = self.isolated(|this| this.check_stmt(else_stmt));
90 return then_continues || else_continues;
91 }
92 }
93 StmtKind::Loop(block, source) => {
95 self.isolated(|this| {
96 this.check_block(*block)
97 && loop_update(*source).is_none_or(|update| this.check_stmt(update))
98 });
99 }
100 StmtKind::Try(try_stmt) => {
101 self.reads(&try_stmt.expr);
102 for clause in try_stmt.clauses {
103 self.isolated(|this| this.check_block(clause.block));
104 }
105 }
106 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
108 return self.check_block(*block);
109 }
110 StmtKind::Placeholder
112 | StmtKind::AssemblyBlock(_)
113 | StmtKind::Switch(_)
114 | StmtKind::Err(_) => self.pending.clear(),
115 }
116 true
117 }
118
119 fn isolated<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
121 self.pending.clear();
122 let result = f(self);
123 self.pending.clear();
124 result
125 }
126
127 fn process_expr(&mut self, expr: &Expr<'_>) {
129 let expr = expr.peel_parens();
130 match &expr.kind {
131 ExprKind::Assign(lhs, op, rhs) => {
132 self.reads(rhs);
134 if op.is_some() {
135 self.reads(lhs);
136 if let Some(var) = self.state_var(lhs) {
137 self.pending.insert(var, expr.span);
138 }
139 } else {
140 self.write_lhs(lhs, expr.span);
141 }
142 }
143 ExprKind::Unary(op, inner) if op.kind.has_side_effects() => {
145 self.reads(inner);
146 if let Some(var) = self.state_var(inner) {
147 self.pending.insert(var, expr.span);
148 }
149 }
150 ExprKind::Delete(inner) => match self.state_var(inner) {
152 Some(var) => self.write(var, expr.span),
153 None => self.reads(inner),
154 },
155 ExprKind::Call(callee, args) => {
157 let (callee, opts) = callee.split_call_options();
158 self.read_call_parts(callee, args, opts);
159 self.pending.clear();
160 }
161 _ => self.reads(expr),
162 }
163 }
164
165 fn write_lhs(&mut self, lhs: &Expr<'_>, span: Span) {
167 match &lhs.peel_parens().kind {
168 ExprKind::Tuple(exprs) => {
169 exprs.iter().flatten().for_each(|e| self.write_lhs(e, e.span));
170 }
171 _ => match self.state_var(lhs) {
172 Some(var) => self.write(var, span),
173 None => self.reads(lhs),
175 },
176 }
177 }
178
179 fn write(&mut self, var: VariableId, span: Span) {
180 if let Some(prev_span) = self.pending.insert(var, span) {
181 self.ctx.emit(&WRITE_AFTER_WRITE, prev_span);
182 }
183 }
184
185 fn reads(&mut self, expr: &Expr<'_>) {
188 let expr = expr.peel_parens();
189 match &expr.kind {
190 ExprKind::Ident(_) => {
191 if let Some(var) = self.gcx.resolved_variable(expr) {
192 self.pending.remove(&var);
193 }
194 }
195 ExprKind::Assign(..) | ExprKind::Delete(_) => self.process_expr(expr),
196 ExprKind::Binary(lhs, op, rhs) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
199 self.reads(lhs);
200 self.isolated(|this| this.reads(rhs));
201 }
202 ExprKind::Ternary(cond, then_expr, else_expr) => {
203 self.reads(cond);
204 self.isolated(|this| this.reads(then_expr));
205 self.isolated(|this| this.reads(else_expr));
206 }
207 ExprKind::Call(callee, args) => {
208 let (callee, opts) = callee.split_call_options();
209 self.read_call_parts(callee, args, opts);
210 self.pending.clear();
211 }
212 ExprKind::CallOptions(callee, opts) => {
213 self.reads(callee);
214 for opt in opts.args {
215 self.reads(&opt.value);
216 }
217 }
218 ExprKind::Binary(lhs, _, rhs) => {
219 self.reads(lhs);
220 self.reads(rhs);
221 }
222 ExprKind::Member(inner, _) => {
223 if let Some(var) = self.state_var(expr) {
224 self.pending.remove(&var);
225 }
226 self.reads(inner);
227 }
228 ExprKind::Unary(_, inner) | ExprKind::Payable(inner) => {
229 self.reads(inner);
230 }
231 ExprKind::Index(base, index) => {
232 self.reads(base);
233 if let Some(index) = index {
234 self.reads(index);
235 }
236 }
237 ExprKind::Slice(base, start, end) => {
238 self.reads(base);
239 for expr in [*start, *end].into_iter().flatten() {
240 self.reads(expr);
241 }
242 }
243 ExprKind::Tuple(exprs) => exprs.iter().flatten().for_each(|e| self.reads(e)),
244 ExprKind::Array(exprs) => exprs.iter().for_each(|e| self.reads(e)),
245 ExprKind::Lit(_) | ExprKind::New(_) | ExprKind::TypeCall(_) | ExprKind::Type(_) => {}
246 ExprKind::YulMember(..) | ExprKind::Err(_) => self.pending.clear(),
247 }
248 }
249
250 fn read_call_parts(
252 &mut self,
253 callee: &Expr<'_>,
254 args: &CallArgs<'_>,
255 opts: Option<&CallOptions<'_>>,
256 ) {
257 self.reads(callee);
258 for arg in args.exprs() {
259 self.reads(arg);
260 }
261 for opt in opts.into_iter().flat_map(|opts| opts.args) {
262 self.reads(&opt.value);
263 }
264 }
265
266 fn state_var(&self, expr: &Expr<'_>) -> Option<VariableId> {
268 self.gcx
269 .resolved_variable(expr)
270 .filter(|&var| self.gcx.hir.variable(var).is_state_variable())
271 }
272}