forge_lint/sol/med/
uninitialized_local.rs1use super::UninitializedLocal;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 interface::{Span, data_structures::Never},
8 sema::{
9 Hir,
10 hir::{
11 Expr, ExprKind, Function, ItemId, LoopSource, Res, Stmt, StmtKind, TypeKind, VarKind,
12 VariableId, Visit,
13 },
14 },
15};
16use std::{
17 collections::{HashMap, HashSet},
18 ops::ControlFlow,
19};
20
21declare_forge_lint!(
22 UNINITIALIZED_LOCAL,
23 Severity::Med,
24 "uninitialized-local",
25 "local variable is read before being initialized"
26);
27
28impl<'hir> LateLintPass<'hir> for UninitializedLocal {
29 fn check_function(
30 &mut self,
31 ctx: &LintContext,
32 _gcx: solar::sema::Gcx<'hir>,
33 hir: &'hir Hir<'hir>,
34 func: &'hir Function<'hir>,
35 ) {
36 let Some(body) = func.body else { return };
37
38 let mut checker = Checker { hir, uninitialized: HashSet::new(), findings: HashMap::new() };
39 for stmt in body.stmts {
40 let _ = checker.visit_stmt(stmt);
41 }
42
43 for (_vid, read_span) in checker.findings {
44 ctx.emit(&UNINITIALIZED_LOCAL, read_span);
45 }
46 }
47}
48
49struct Checker<'hir> {
50 hir: &'hir Hir<'hir>,
51 uninitialized: HashSet<VariableId>,
53 findings: HashMap<VariableId, Span>,
55}
56
57impl<'hir> Visit<'hir> for Checker<'hir> {
58 type BreakValue = Never;
59
60 fn hir(&self) -> &'hir Hir<'hir> {
61 self.hir
62 }
63
64 fn visit_stmt(&mut self, stmt: &'hir Stmt<'hir>) -> ControlFlow<Self::BreakValue> {
65 match &stmt.kind {
66 StmtKind::DeclSingle(vid) => {
67 let v = self.hir.variable(*vid);
68 let is_value_type =
69 matches!(v.ty.kind, TypeKind::Elementary(ty) if ty.is_value_type());
70 if matches!(v.kind, VarKind::Statement) && v.initializer.is_none() && is_value_type
71 {
72 self.uninitialized.insert(*vid);
73 }
74 if let Some(init) = v.initializer {
76 let _ = self.visit_expr(init);
77 }
78 return ControlFlow::Continue(());
79 }
80
81 StmtKind::If(cond, then, else_) => {
85 let _ = self.visit_expr(cond);
86
87 let before = self.uninitialized.clone();
88
89 let _ = self.visit_stmt(then);
90 let after_then = self.uninitialized.clone();
91
92 self.uninitialized = before;
93 if let Some(else_stmt) = else_ {
94 let _ = self.visit_stmt(else_stmt);
95 }
96 let after_else = self.uninitialized.clone();
97
98 let then_exits = branch_always_exits(then);
99 let else_exits = else_.is_some_and(branch_always_exits);
100 self.uninitialized = match (then_exits, else_exits) {
101 (true, _) => after_else,
102 (_, true) => after_then,
103 _ => after_then.union(&after_else).copied().collect(),
104 };
105 return ControlFlow::Continue(());
106 }
107
108 StmtKind::Loop(block, source) => {
111 let before = self.uninitialized.clone();
112 for s in block.stmts {
113 let _ = self.visit_stmt(s);
114 }
115 if !matches!(source, LoopSource::DoWhile) {
116 self.uninitialized = before;
117 }
118 return ControlFlow::Continue(());
119 }
120
121 StmtKind::Try(t) => {
123 let _ = self.visit_expr(&t.expr);
124 let mut clause_states: Vec<HashSet<VariableId>> = Vec::new();
125 for clause in t.clauses {
126 let before = self.uninitialized.clone();
127 for s in clause.block.stmts {
128 let _ = self.visit_stmt(s);
129 }
130 clause_states.push(self.uninitialized.clone());
131 self.uninitialized = before;
132 }
133 self.uninitialized = clause_states
136 .iter()
137 .fold(HashSet::new(), |acc, s| acc.union(s).copied().collect());
138 return ControlFlow::Continue(());
139 }
140
141 _ => {}
142 }
143 self.walk_stmt(stmt)
144 }
145
146 fn visit_expr(&mut self, expr: &'hir Expr<'hir>) -> ControlFlow<Self::BreakValue> {
147 match &expr.kind {
148 ExprKind::Assign(lhs, None, rhs) => {
150 let _ = self.visit_expr(rhs);
151 mark_written(lhs, &mut self.uninitialized);
152 let _ = self.visit_expr(lhs);
154 return ControlFlow::Continue(());
155 }
156
157 ExprKind::Assign(lhs, Some(_), rhs) => {
159 let _ = self.visit_expr(lhs);
160 let _ = self.visit_expr(rhs);
161 mark_written(lhs, &mut self.uninitialized);
162 return ControlFlow::Continue(());
163 }
164
165 ExprKind::Delete(target) => {
167 mark_written(target, &mut self.uninitialized);
168 let _ = self.visit_expr(target);
169 return ControlFlow::Continue(());
170 }
171
172 ExprKind::Ident(reses) => {
173 for res in *reses {
174 if let Res::Item(ItemId::Variable(vid)) = res
175 && self.uninitialized.contains(vid)
176 {
177 self.findings.entry(*vid).or_insert(expr.span);
178 break;
179 }
180 }
181 }
182
183 _ => {}
184 }
185 self.walk_expr(expr)
186 }
187}
188
189fn branch_always_exits(stmt: &Stmt<'_>) -> bool {
190 match &stmt.kind {
191 StmtKind::Return(_) | StmtKind::Revert(_) => true,
192 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
193 block.stmts.last().is_some_and(branch_always_exits)
194 }
195 StmtKind::If(_, t, Some(e)) => branch_always_exits(t) && branch_always_exits(e),
196 _ => false,
197 }
198}
199
200fn mark_written(expr: &Expr<'_>, uninitialized: &mut HashSet<VariableId>) {
202 match &expr.kind {
203 ExprKind::Ident(reses) => {
204 for res in *reses {
205 if let Res::Item(ItemId::Variable(vid)) = res {
206 uninitialized.remove(vid);
207 }
208 }
209 }
210 ExprKind::Tuple(elems) => {
211 for elem in elems.iter().flatten() {
212 mark_written(elem, uninitialized);
213 }
214 }
215 _ => {}
216 }
217}