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 hir: &'hir Hir<'hir>,
33 func: &'hir Function<'hir>,
34 ) {
35 let Some(body) = func.body else { return };
36
37 let mut checker = Checker { hir, uninitialized: HashSet::new(), findings: HashMap::new() };
38 for stmt in body.stmts {
39 let _ = checker.visit_stmt(stmt);
40 }
41
42 for (_vid, read_span) in checker.findings {
43 ctx.emit(&UNINITIALIZED_LOCAL, read_span);
44 }
45 }
46}
47
48struct Checker<'hir> {
49 hir: &'hir Hir<'hir>,
50 uninitialized: HashSet<VariableId>,
52 findings: HashMap<VariableId, Span>,
54}
55
56impl<'hir> Visit<'hir> for Checker<'hir> {
57 type BreakValue = Never;
58
59 fn hir(&self) -> &'hir Hir<'hir> {
60 self.hir
61 }
62
63 fn visit_stmt(&mut self, stmt: &'hir Stmt<'hir>) -> ControlFlow<Self::BreakValue> {
64 match &stmt.kind {
65 StmtKind::DeclSingle(vid) => {
66 let v = self.hir.variable(*vid);
67 let is_value_type =
68 matches!(v.ty.kind, TypeKind::Elementary(ty) if ty.is_value_type());
69 if matches!(v.kind, VarKind::Statement) && v.initializer.is_none() && is_value_type
70 {
71 self.uninitialized.insert(*vid);
72 }
73 if let Some(init) = v.initializer {
75 let _ = self.visit_expr(init);
76 }
77 return ControlFlow::Continue(());
78 }
79
80 StmtKind::If(cond, then, else_) => {
84 let _ = self.visit_expr(cond);
85
86 let before = self.uninitialized.clone();
87
88 let _ = self.visit_stmt(then);
89 let after_then = self.uninitialized.clone();
90
91 self.uninitialized = before;
92 if let Some(else_stmt) = else_ {
93 let _ = self.visit_stmt(else_stmt);
94 }
95 let after_else = self.uninitialized.clone();
96
97 let then_exits = branch_always_exits(then);
98 let else_exits = else_.is_some_and(branch_always_exits);
99 self.uninitialized = match (then_exits, else_exits) {
100 (true, _) => after_else,
101 (_, true) => after_then,
102 _ => after_then.union(&after_else).copied().collect(),
103 };
104 return ControlFlow::Continue(());
105 }
106
107 StmtKind::Loop(block, source) => {
110 let before = self.uninitialized.clone();
111 for s in block.stmts {
112 let _ = self.visit_stmt(s);
113 }
114 if !matches!(source, LoopSource::DoWhile) {
115 self.uninitialized = before;
116 }
117 return ControlFlow::Continue(());
118 }
119
120 StmtKind::Try(t) => {
122 let _ = self.visit_expr(&t.expr);
123 let mut clause_states: Vec<HashSet<VariableId>> = Vec::new();
124 for clause in t.clauses {
125 let before = self.uninitialized.clone();
126 for s in clause.block.stmts {
127 let _ = self.visit_stmt(s);
128 }
129 clause_states.push(self.uninitialized.clone());
130 self.uninitialized = before;
131 }
132 self.uninitialized = clause_states
135 .iter()
136 .fold(HashSet::new(), |acc, s| acc.union(s).copied().collect());
137 return ControlFlow::Continue(());
138 }
139
140 _ => {}
141 }
142 self.walk_stmt(stmt)
143 }
144
145 fn visit_expr(&mut self, expr: &'hir Expr<'hir>) -> ControlFlow<Self::BreakValue> {
146 match &expr.kind {
147 ExprKind::Assign(lhs, None, rhs) => {
149 let _ = self.visit_expr(rhs);
150 mark_written(lhs, &mut self.uninitialized);
151 let _ = self.visit_expr(lhs);
153 return ControlFlow::Continue(());
154 }
155
156 ExprKind::Assign(lhs, Some(_), rhs) => {
158 let _ = self.visit_expr(lhs);
159 let _ = self.visit_expr(rhs);
160 mark_written(lhs, &mut self.uninitialized);
161 return ControlFlow::Continue(());
162 }
163
164 ExprKind::Delete(target) => {
166 mark_written(target, &mut self.uninitialized);
167 let _ = self.visit_expr(target);
168 return ControlFlow::Continue(());
169 }
170
171 ExprKind::Ident(reses) => {
172 for res in *reses {
173 if let Res::Item(ItemId::Variable(vid)) = res
174 && self.uninitialized.contains(vid)
175 {
176 self.findings.entry(*vid).or_insert(expr.span);
177 break;
178 }
179 }
180 }
181
182 _ => {}
183 }
184 self.walk_expr(expr)
185 }
186}
187
188fn branch_always_exits(stmt: &Stmt<'_>) -> bool {
189 match &stmt.kind {
190 StmtKind::Return(_) | StmtKind::Revert(_) => true,
191 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
192 block.stmts.last().is_some_and(branch_always_exits)
193 }
194 StmtKind::If(_, t, Some(e)) => branch_always_exits(t) && branch_always_exits(e),
195 _ => false,
196 }
197}
198
199fn mark_written(expr: &Expr<'_>, uninitialized: &mut HashSet<VariableId>) {
201 match &expr.kind {
202 ExprKind::Ident(reses) => {
203 for res in *reses {
204 if let Res::Item(ItemId::Variable(vid)) = res {
205 uninitialized.remove(vid);
206 }
207 }
208 }
209 ExprKind::Tuple(elems) => {
210 for elem in elems.iter().flatten() {
211 mark_written(elem, uninitialized);
212 }
213 }
214 _ => {}
215 }
216}