forge_lint/sol/info/function_init_state.rs
1use super::FunctionInitState;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast::StateMutability,
8 sema::{
9 Gcx,
10 hir::{
11 self, CallArgsKind, ContractId, Expr, ExprKind, FunctionId, Hir, ItemId, Res,
12 VariableId, Visit,
13 },
14 ty::TyKind,
15 },
16};
17use std::{convert::Infallible, ops::ControlFlow};
18
19declare_forge_lint!(
20 FUNCTION_INIT_STATE,
21 Severity::Info,
22 "function-init-state",
23 "state variable initializer depends on a non-pure function or another state variable"
24);
25
26impl<'hir> LateLintPass<'hir> for FunctionInitState {
27 fn check_nested_contract(
28 &mut self,
29 ctx: &LintContext,
30 gcx: Gcx<'hir>,
31 hir: &'hir Hir<'hir>,
32 id: ContractId,
33 ) {
34 // State variable initializers run at construction, before the constructor body, in
35 // base-to-derived order: reading another non-constant state variable or calling a
36 // non-pure function there observes that partial state. Constants are fixed at compile
37 // time, so both constant declarations and references to constants are fine.
38 let contract = hir.contract(id);
39 for item_id in contract.items {
40 if let ItemId::Variable(variable_id) = item_id {
41 let variable = hir.variable(*variable_id);
42 // A constant's initializer is restricted to compile-time constant expressions.
43 if variable.is_state_variable()
44 && !variable.is_constant()
45 && let Some(initializer) = variable.initializer
46 {
47 let mut finder = ImpureRefFinder {
48 gcx,
49 hir,
50 source: contract.source,
51 contract: id,
52 found: false,
53 };
54 let _ = finder.visit_expr(initializer);
55 if finder.found {
56 ctx.emit(&FUNCTION_INIT_STATE, variable.span);
57 }
58 }
59 }
60 }
61 }
62}
63
64/// Looks for a reference to a non-constant state variable or to a non-pure function anywhere in
65/// an initializer expression, arguments of nested calls included.
66struct ImpureRefFinder<'hir> {
67 gcx: Gcx<'hir>,
68 hir: &'hir Hir<'hir>,
69 /// The source and contract of the initializer, the viewpoint for `using for` lookups.
70 source: hir::SourceId,
71 contract: ContractId,
72 found: bool,
73}
74
75impl<'hir> Visit<'hir> for ImpureRefFinder<'hir> {
76 type BreakValue = Infallible;
77
78 fn hir(&self) -> &'hir Hir<'hir> {
79 self.hir
80 }
81
82 fn visit_expr(&mut self, expr: &'hir Expr<'hir>) -> ControlFlow<Self::BreakValue> {
83 match &expr.kind {
84 // A call: the type checker already resolved the one function it dispatches to
85 // (overload selection by argument types, override shadowing, `super.` and the
86 // qualified forms), so judge that target alone and walk the rest manually,
87 // skipping the default walk that would re-judge the callee by name matching.
88 ExprKind::Call(callee, args, opts) => {
89 if let Some(function_id) = self.resolved_function(callee) {
90 self.judge_function(function_id);
91 }
92 match &callee.peel_parens().kind {
93 // The callee name can also resolve to a variable: a call through a
94 // function pointer stored in state reads that variable.
95 ExprKind::Ident(resolutions) => {
96 for res in *resolutions {
97 if let Res::Item(ItemId::Variable(variable_id)) = res {
98 self.judge_variable(*variable_id);
99 }
100 }
101 }
102 // The receiver of a member call can read state itself.
103 ExprKind::Member(base, _) => {
104 let _ = self.visit_expr(base);
105 }
106 _ => {
107 let _ = self.visit_expr(callee);
108 }
109 }
110 if let Some(opts) = opts {
111 for arg in opts.args {
112 let _ = self.visit_expr(&arg.value);
113 }
114 }
115 match &args.kind {
116 CallArgsKind::Unnamed(exprs) => {
117 for arg in *exprs {
118 let _ = self.visit_expr(arg);
119 }
120 }
121 CallArgsKind::Named(named) => {
122 for arg in *named {
123 let _ = self.visit_expr(&arg.value);
124 }
125 }
126 }
127 return ControlFlow::Continue(());
128 }
129 // A plain reference to a name outside a call position.
130 ExprKind::Ident(resolutions) => self.judge_resolutions(resolutions),
131 // A member reference used as a value has a resolved target too (`x.f` selects an
132 // override like `x.f()` would): judge it when the type checker gives one, and
133 // scan by name only when there is no resolved function.
134 ExprKind::Member(base, member) => {
135 if let Some(function_id) = self.resolved_function(expr) {
136 self.judge_function(function_id);
137 } else {
138 self.judge_member(base, member);
139 }
140 }
141 _ => {}
142 }
143 self.walk_expr(expr)
144 }
145}
146
147impl ImpureRefFinder<'_> {
148 /// The single function an expression resolves to, for a callee or for a member reference
149 /// used as a value. `type_of_expr` is the function the type checker resolved, so overload
150 /// selection by argument types, override shadowing, `super.` and the qualified and
151 /// `using for` forms are already accounted for.
152 fn resolved_function(&self, expr: &Expr<'_>) -> Option<FunctionId> {
153 let ty = self.gcx.type_of_expr(expr.peel_parens().id)?;
154 match ty.kind {
155 TyKind::Fn(function_ty) => function_ty.function_id,
156 _ => None,
157 }
158 }
159
160 fn judge_resolutions(&mut self, resolutions: &[Res]) {
161 for res in resolutions {
162 match res {
163 Res::Item(ItemId::Variable(variable_id)) => self.judge_variable(*variable_id),
164 Res::Item(ItemId::Function(function_id)) => {
165 self.judge_function(*function_id);
166 }
167 _ => {}
168 }
169 }
170 }
171
172 /// Judges a member read with no resolved function type (`Base.stateVar`): the member
173 /// ident carries no resolution, so type the base and scan by name. Calls and function
174 /// references never come here, their target is resolved from the expression type.
175 fn judge_member(&mut self, base: &Expr<'_>, member: &solar::ast::Ident) {
176 let Some(ty) = self.gcx.type_of_expr(base.peel_parens().id) else { return };
177 // A contract name used as the base is a type-namespace item, so its type comes wrapped
178 // as `Type(Contract(..))`, while a contract-typed value comes bare. Peel only to
179 // discriminate: `members_of` needs reference types to keep their data location.
180 let peeled = ty.peel_refs();
181 let peeled = match peeled.kind {
182 TyKind::Type(inner) => inner.peel_refs(),
183 _ => peeled,
184 };
185 if let TyKind::Contract(contract_id) = peeled.kind {
186 // Walk the linearization: an inherited function or getter is not among the
187 // contract's own items.
188 for base_id in self.hir.contract(contract_id).linearized_bases {
189 for item_id in self.hir.contract(*base_id).items {
190 match item_id {
191 ItemId::Variable(variable_id)
192 if self
193 .hir
194 .variable(*variable_id)
195 .name
196 .is_some_and(|name| name.name == member.name) =>
197 {
198 self.judge_variable(*variable_id);
199 }
200 ItemId::Function(function_id)
201 if self
202 .hir
203 .function(*function_id)
204 .name
205 .is_some_and(|name| name.name == member.name) =>
206 {
207 self.judge_function(*function_id);
208 }
209 _ => {}
210 }
211 }
212 }
213 } else {
214 // A `using for` binding read as a value: the bound library function is a member of
215 // the value type.
216 for member_entry in self.gcx.members_of(ty, self.source, Some(self.contract)) {
217 if member_entry.name == member.name
218 && let TyKind::Fn(function_ty) = member_entry.ty.kind
219 && let Some(function_id) = function_ty.function_id
220 {
221 self.judge_function(function_id);
222 }
223 }
224 }
225 }
226
227 /// A read of another state variable: its initializer may not have run yet.
228 fn judge_variable(&mut self, variable_id: VariableId) {
229 let variable = self.hir.variable(variable_id);
230 if variable.is_state_variable() && !variable.is_constant() {
231 self.found = true;
232 }
233 }
234
235 /// A non-pure function observes the same partial state. A variable referenced through its
236 /// synthesized getter is judged as a read of the variable itself, so a public constant
237 /// stays fine.
238 fn judge_function(&mut self, function_id: FunctionId) {
239 let function = self.hir.function(function_id);
240 if let Some(variable_id) = function.gettee {
241 self.judge_variable(variable_id);
242 } else if function.state_mutability != StateMutability::Pure {
243 self.found = true;
244 }
245 }
246}