Skip to main content

forge_lint/sol/high/
incorrect_shift.rs

1use super::IncorrectShift;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use alloy_primitives::U256;
7use solar::{
8    ast::{LitKind, Stmt, StmtKind, visit::Visit, yul},
9    data_structures::Never,
10    interface::kw,
11};
12use std::ops::ControlFlow;
13
14declare_forge_lint!(
15    INCORRECT_SHIFT,
16    Severity::High,
17    "incorrect-shift",
18    "the order of args in a shift operation is incorrect"
19);
20
21impl<'ast> EarlyLintPass<'ast> for IncorrectShift {
22    fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) {
23        if let StmtKind::Assembly(assembly) = &stmt.kind {
24            let _ = ShiftChecker { ctx }.visit_yul_block(&assembly.block);
25        }
26    }
27}
28
29struct ShiftChecker<'a, 's> {
30    ctx: &'a LintContext<'s, 'a>,
31}
32
33impl<'ast> Visit<'ast> for ShiftChecker<'_, '_> {
34    type BreakValue = Never;
35
36    fn visit_yul_expr(&mut self, expr: &'ast yul::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
37        // A computed shift of a literal suggests swapped arguments, except `shl(n, 1)`,
38        // which constructs a single-bit mask.
39        if let yul::ExprKind::Call(call) = &expr.kind
40            && matches!(call.name.name, kw::Shl | kw::Shr | kw::Sar)
41            && let [left, right] = call.arguments.as_ref()
42            && !matches!(left.kind, yul::ExprKind::Lit(_))
43            && let yul::ExprKind::Lit(lit) = &right.kind
44            && !(call.name.name == kw::Shl
45                && matches!(lit.kind, LitKind::Number(value) if value == U256::ONE))
46        {
47            self.ctx.emit(&INCORRECT_SHIFT, expr.span);
48        }
49        self.walk_yul_expr(expr)
50    }
51}