Skip to main content

forge_lint/sol/gas/
unused_state_variables.rs

1use super::UnusedStateVariables;
2use crate::{
3    linter::{LateLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    ast::ContractKind,
8    interface::data_structures::Never,
9    sema::{
10        Gcx,
11        hir::{self, Visit as _},
12    },
13};
14use std::{collections::HashSet, ops::ControlFlow};
15
16declare_forge_lint!(
17    UNUSED_STATE_VARIABLES,
18    Severity::Gas,
19    "unused-state-variables",
20    "state variable is never used"
21);
22
23impl<'gcx> LateLintPass<'gcx> for UnusedStateVariables {
24    fn check_contract(
25        &mut self,
26        ctx: &LintContext,
27        gcx: Gcx<'gcx>,
28        contract: &'gcx hir::Contract<'gcx>,
29    ) {
30        if contract.kind == ContractKind::Interface {
31            return;
32        }
33
34        // Functions (including modifier call args) and state variable initializers cover every
35        // variable reference in the contract.
36        let mut collector = UsedVarCollector { gcx, used: HashSet::new() };
37        for func_id in contract.all_functions() {
38            let _ = collector.visit_nested_function(func_id);
39        }
40        for var_id in contract.variables() {
41            let _ = collector.visit_nested_var(var_id);
42        }
43
44        // Constants and immutables do not occupy storage slots.
45        for var_id in contract.variables() {
46            let var = gcx.hir.variable(var_id);
47            if !var.is_constant() && !var.is_immutable() && !collector.used.contains(&var_id) {
48                ctx.emit(&UNUSED_STATE_VARIABLES, var.span);
49            }
50        }
51    }
52}
53
54struct UsedVarCollector<'gcx> {
55    gcx: Gcx<'gcx>,
56    used: HashSet<hir::VariableId>,
57}
58
59impl<'gcx> hir::Visit<'gcx> for UsedVarCollector<'gcx> {
60    type BreakValue = Never;
61
62    fn hir(&self) -> &'gcx hir::Hir<'gcx> {
63        &self.gcx.hir
64    }
65
66    fn visit_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
67        self.used.extend(self.gcx.resolved_variable(expr));
68        self.walk_expr(expr)
69    }
70}