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, opts) => self.read_call_parts(callee, args, *opts),
60 _ => self.reads(expr),
61 },
62 StmtKind::Return(expr) => {
65 if let Some(expr) = expr {
66 self.reads(expr);
67 }
68 self.pending.clear();
69 return false;
70 }
71 StmtKind::Revert(expr) => {
72 self.reads(expr);
73 self.pending.clear();
74 return false;
75 }
76 StmtKind::Break | StmtKind::Continue => {
77 self.pending.clear();
78 return false;
79 }
80 StmtKind::If(cond, then_stmt, else_stmt) => {
83 self.reads(cond);
84 let then_continues = self.isolated(|this| this.check_stmt(then_stmt));
85 if let Some(else_stmt) = else_stmt {
86 let else_continues = self.isolated(|this| this.check_stmt(else_stmt));
87 return then_continues || else_continues;
88 }
89 }
90 StmtKind::Loop(block, source) => {
92 self.isolated(|this| {
93 this.check_block(*block)
94 && loop_update(*source).is_none_or(|update| this.check_stmt(update))
95 });
96 }
97 StmtKind::Try(try_stmt) => {
98 self.reads(&try_stmt.expr);
99 for clause in try_stmt.clauses {
100 self.isolated(|this| this.check_block(clause.block));
101 }
102 }
103 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
105 return self.check_block(*block);
106 }
107 StmtKind::Placeholder
109 | StmtKind::AssemblyBlock(_)
110 | StmtKind::Switch(_)
111 | StmtKind::Err(_) => self.pending.clear(),
112 }
113 true
114 }
115
116 fn isolated<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
118 self.pending.clear();
119 let result = f(self);
120 self.pending.clear();
121 result
122 }
123
124 fn process_expr(&mut self, expr: &Expr<'_>) {
126 let expr = expr.peel_parens();
127 match &expr.kind {
128 ExprKind::Assign(lhs, op, rhs) => {
129 self.reads(rhs);
132 if op.is_none() {
133 self.write_lhs(lhs, expr.span);
134 } else {
135 self.reads(lhs);
136 }
137 }
138 ExprKind::Unary(op, inner) if op.kind.has_side_effects() => {
140 self.reads(inner);
141 if let Some(var) = self.state_var(inner) {
142 self.pending.insert(var, expr.span);
143 }
144 }
145 ExprKind::Delete(inner) => match self.state_var(inner) {
147 Some(var) => self.write(var, expr.span),
148 None => self.reads(inner),
149 },
150 ExprKind::Call(callee, args, opts) => {
152 self.read_call_parts(callee, args, *opts);
153 self.pending.clear();
154 }
155 _ => self.reads(expr),
156 }
157 }
158
159 fn write_lhs(&mut self, lhs: &Expr<'_>, span: Span) {
161 match &lhs.peel_parens().kind {
162 ExprKind::Tuple(exprs) => {
163 exprs.iter().flatten().for_each(|e| self.write_lhs(e, e.span));
164 }
165 _ => match self.state_var(lhs) {
166 Some(var) => self.write(var, span),
167 None => self.reads(lhs),
169 },
170 }
171 }
172
173 fn write(&mut self, var: VariableId, span: Span) {
174 if let Some(prev_span) = self.pending.insert(var, span) {
175 self.ctx.emit(&WRITE_AFTER_WRITE, prev_span);
176 }
177 }
178
179 fn reads(&mut self, expr: &Expr<'_>) {
182 let expr = expr.peel_parens();
183 match &expr.kind {
184 ExprKind::Ident(_) => {
185 if let Some(var) = self.gcx.resolved_variable(expr) {
186 self.pending.remove(&var);
187 }
188 }
189 ExprKind::Assign(..) | ExprKind::Delete(_) => self.process_expr(expr),
190 ExprKind::Binary(lhs, op, rhs) if matches!(op.kind, BinOpKind::And | BinOpKind::Or) => {
193 self.reads(lhs);
194 self.isolated(|this| this.reads(rhs));
195 }
196 ExprKind::Ternary(cond, then_expr, else_expr) => {
197 self.reads(cond);
198 self.isolated(|this| this.reads(then_expr));
199 self.isolated(|this| this.reads(else_expr));
200 }
201 ExprKind::Call(callee, args, opts) => {
202 self.read_call_parts(callee, args, *opts);
203 self.pending.clear();
204 }
205 ExprKind::Binary(lhs, _, rhs) => {
206 self.reads(lhs);
207 self.reads(rhs);
208 }
209 ExprKind::Unary(_, inner) | ExprKind::Payable(inner) | ExprKind::Member(inner, _) => {
210 self.reads(inner);
211 }
212 ExprKind::Index(base, index) => {
213 self.reads(base);
214 if let Some(index) = index {
215 self.reads(index);
216 }
217 }
218 ExprKind::Slice(base, start, end) => {
219 self.reads(base);
220 for expr in [*start, *end].into_iter().flatten() {
221 self.reads(expr);
222 }
223 }
224 ExprKind::Tuple(exprs) => exprs.iter().flatten().for_each(|e| self.reads(e)),
225 ExprKind::Array(exprs) => exprs.iter().for_each(|e| self.reads(e)),
226 ExprKind::Lit(_) | ExprKind::New(_) | ExprKind::TypeCall(_) | ExprKind::Type(_) => {}
227 ExprKind::YulMember(..) | ExprKind::Err(_) => self.pending.clear(),
228 }
229 }
230
231 fn read_call_parts(
233 &mut self,
234 callee: &Expr<'_>,
235 args: &CallArgs<'_>,
236 opts: Option<&CallOptions<'_>>,
237 ) {
238 self.reads(callee);
239 for arg in args.exprs() {
240 self.reads(arg);
241 }
242 for opt in opts.into_iter().flat_map(|opts| opts.args) {
243 self.reads(&opt.value);
244 }
245 }
246
247 fn state_var(&self, expr: &Expr<'_>) -> Option<VariableId> {
249 self.gcx
250 .resolved_variable(expr)
251 .filter(|&var| self.gcx.hir.variable(var).is_state_variable())
252 }
253}