forge_lint/sol/med/
uninitialized_local.rs1use super::UninitializedLocal;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{
5 Severity, SolLint,
6 analysis::{branch_always_exits, for_each_lhs_var, loop_stmts},
7 },
8};
9use solar::{
10 ast::ElementaryType,
11 interface::{Span, data_structures::Never},
12 sema::{
13 Gcx, Hir,
14 hir::{
15 BinOpKind, Block, Expr, ExprKind, Function, LoopSource, Stmt, StmtKind, TypeKind,
16 UnOpKind, VarKind, VariableId, Visit,
17 },
18 },
19};
20use std::{
21 collections::{HashMap, HashSet},
22 ops::ControlFlow,
23};
24
25declare_forge_lint!(
26 UNINITIALIZED_LOCAL,
27 Severity::Med,
28 "uninitialized-local",
29 "local variable is read before being initialized"
30);
31
32impl<'gcx> LateLintPass<'gcx> for UninitializedLocal {
33 fn check_function(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, func: &'gcx Function<'gcx>) {
34 let Some(body) = func.body else { return };
35 let mut checker =
36 Checker { gcx, hir: &gcx.hir, uninitialized: HashSet::new(), findings: HashMap::new() };
37 for stmt in body.stmts {
38 let _ = checker.visit_stmt(stmt);
39 }
40 for span in checker.findings.into_values() {
41 ctx.emit(&UNINITIALIZED_LOCAL, span);
42 }
43 }
44}
45
46struct Checker<'gcx> {
47 gcx: Gcx<'gcx>,
48 hir: &'gcx Hir<'gcx>,
49 uninitialized: HashSet<VariableId>,
52 findings: HashMap<VariableId, Span>,
54}
55
56impl Checker<'_> {
57 fn mark_written(&mut self, lhs: &Expr<'_>) {
58 for_each_lhs_var(self.gcx, lhs, &mut |v| {
59 self.uninitialized.remove(&v);
60 });
61 }
62}
63
64fn defaulted_counter_loop<'gcx>(
69 gcx: Gcx<'gcx>,
70 block: &'gcx Block<'gcx>,
71) -> Option<&'gcx Stmt<'gcx>> {
72 if let [Stmt { kind: StmtKind::DeclSingle(vid), .. }, loop_stmt] = block.stmts
73 && block.span == loop_stmt.span
74 && let StmtKind::Loop(body, LoopSource::For { update: Some(update) }) = &loop_stmt.kind
75 && let var = gcx.hir.variable(*vid)
76 && var.initializer.is_none()
77 && matches!(var.ty.kind, TypeKind::Elementary(ElementaryType::UInt(_)))
78 && let [Stmt { kind: StmtKind::If(cond, _, Some(else_)), .. }] = body.stmts
79 && matches!(else_.kind, StmtKind::Break)
80 && let ExprKind::Binary(left, op, right) = &cond.peel_parens().kind
81 && ((matches!(op.kind, BinOpKind::Lt | BinOpKind::Le)
82 && gcx.resolved_variable(left) == Some(*vid))
83 || (matches!(op.kind, BinOpKind::Gt | BinOpKind::Ge)
84 && gcx.resolved_variable(right) == Some(*vid)))
85 && let StmtKind::Expr(update) = &update.kind
86 && let ExprKind::Unary(op, target) = &update.peel_parens().kind
87 && matches!(op.kind, UnOpKind::PreInc | UnOpKind::PostInc)
88 && gcx.resolved_variable(target) == Some(*vid)
89 {
90 Some(loop_stmt)
91 } else {
92 None
93 }
94}
95
96impl<'gcx> Visit<'gcx> for Checker<'gcx> {
97 type BreakValue = Never;
98
99 fn hir(&self) -> &'gcx Hir<'gcx> {
100 self.hir
101 }
102
103 fn visit_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> ControlFlow<Never> {
104 match &stmt.kind {
105 StmtKind::Block(block) => {
106 if let Some(loop_stmt) = defaulted_counter_loop(self.gcx, block) {
107 return self.visit_stmt(loop_stmt);
110 }
111 }
112 StmtKind::DeclSingle(vid) => {
113 let v = self.hir.variable(*vid);
114 if v.kind == VarKind::Statement
115 && v.initializer.is_none()
116 && self.gcx.type_of_item((*vid).into()).is_value_type()
117 {
118 self.uninitialized.insert(*vid);
119 }
120 }
121 StmtKind::If(cond, then, else_) => {
123 self.visit_expr(cond)?;
124 let before = self.uninitialized.clone();
125 self.visit_stmt(then)?;
126 let after_then = std::mem::replace(&mut self.uninitialized, before);
127 if let Some(else_) = else_ {
128 self.visit_stmt(else_)?;
129 }
130 if branch_always_exits(self.gcx, then) {
131 } else if else_.is_some_and(|expr| branch_always_exits(self.gcx, expr)) {
133 self.uninitialized = after_then;
134 } else {
135 self.uninitialized.extend(after_then);
136 }
137 return ControlFlow::Continue(());
138 }
139 StmtKind::Loop(block, source) => {
142 let before = self.uninitialized.clone();
143 for s in loop_stmts(*block, *source) {
144 self.visit_stmt(s)?;
145 }
146 if !matches!(source, LoopSource::DoWhile) {
147 self.uninitialized = before;
148 }
149 return ControlFlow::Continue(());
150 }
151 StmtKind::Try(t) => {
153 self.visit_expr(&t.expr)?;
154 let before = self.uninitialized.clone();
155 let mut merged = HashSet::new();
156 for clause in t.clauses {
157 self.uninitialized = before.clone();
158 for s in clause.block.stmts {
159 self.visit_stmt(s)?;
160 }
161 merged.extend(self.uninitialized.drain());
162 }
163 self.uninitialized = merged;
164 return ControlFlow::Continue(());
165 }
166 _ => {}
167 }
168 self.walk_stmt(stmt)
169 }
170
171 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<Never> {
172 match &expr.kind {
173 ExprKind::Assign(lhs, op, rhs) => {
176 if op.is_some() {
177 self.visit_expr(lhs)?;
178 }
179 self.visit_expr(rhs)?;
180 self.mark_written(lhs);
181 if op.is_none() {
182 self.visit_expr(lhs)?;
183 }
184 ControlFlow::Continue(())
185 }
186 ExprKind::Delete(target) => {
188 self.mark_written(target);
189 self.visit_expr(target)
190 }
191 ExprKind::Ident(_) => {
192 if let Some(vid) =
193 self.gcx.resolved_variable(expr).filter(|v| self.uninitialized.contains(v))
194 {
195 self.findings.entry(vid).or_insert(expr.span);
196 }
197 ControlFlow::Continue(())
198 }
199 _ => self.walk_expr(expr),
200 }
201 }
202}