Skip to main content

forge_lint/sol/analysis/
primitives.rs

1//! Side-effect-free syntactic/semantic probes over solar HIR.
2
3use solar::{
4    ast::LitKind,
5    interface::{Symbol, kw, sym},
6    sema::hir::{self, ElementaryType, Expr, ExprKind, Res, Stmt, StmtKind, TypeKind, VariableId},
7};
8
9/// True if `vid` is typed as `address`/`address payable`.
10pub fn is_address_type(hir: &hir::Hir<'_>, vid: VariableId) -> bool {
11    matches!(hir.variable(vid).ty.kind, TypeKind::Elementary(ElementaryType::Address(_)))
12}
13
14/// True if `callee` resolves to the builtin `require` or `assert`.
15pub fn is_require_or_assert(callee: &Expr<'_>) -> bool {
16    matches!(&callee.kind, ExprKind::Ident(reses)
17        if reses.iter().any(|r| matches!(r,
18            Res::Builtin(b) if b.name() == sym::require || b.name() == sym::assert)))
19}
20
21/// Receiver of `<expr>.{call,delegatecall,transfer,send}`, including the
22/// `.call{value: x}(...)` option form.
23pub fn address_call_receiver<'a>(callee: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
24    // `addr.call{...}(..)` lowers as `Call(Member(receiver, "call"), ..)`.
25    let inner = match &callee.kind {
26        ExprKind::Call(inner, ..) => inner,
27        _ => callee,
28    };
29    let target = if matches!(inner.kind, ExprKind::Member(..)) { inner } else { callee };
30    if let ExprKind::Member(receiver, name) = &target.kind {
31        let n = name.name;
32        if n == kw::Call || n == kw::Delegatecall || n == sym::transfer || n == sym::send {
33            return Some(receiver);
34        }
35    }
36    None
37}
38
39/// True when executing `stmt` provably prevents control from continuing past
40/// it: a `return`, `revert`/`revert(...)`, `require(false, ...)`,
41/// `assert(false)`, a block containing any such statement (any subsequent
42/// statements are unreachable), or an `if` whose both arms exit.
43pub fn branch_always_exits(stmt: &Stmt<'_>) -> bool {
44    match &stmt.kind {
45        StmtKind::Return(_) | StmtKind::Revert(_) => true,
46        StmtKind::Expr(expr) => is_exit_call(expr),
47        StmtKind::Block(b) | StmtKind::UncheckedBlock(b) => b.stmts.iter().any(branch_always_exits),
48        StmtKind::If(_, t, Some(e)) => branch_always_exits(t) && branch_always_exits(e),
49        _ => false,
50    }
51}
52
53/// True if `expr` references the named global builtin (`msg`, `tx`, `this`, ...).
54fn is_builtin(expr: &Expr<'_>, name: Symbol) -> bool {
55    matches!(&expr.peel_parens().kind, ExprKind::Ident(reses)
56        if reses.iter().any(|r| matches!(r, Res::Builtin(b) if b.name() == name)))
57}
58
59fn is_exit_call(expr: &Expr<'_>) -> bool {
60    let ExprKind::Call(callee, args, _) = &expr.peel_parens().kind else { return false };
61    if is_builtin(callee, kw::Revert) {
62        return true;
63    }
64    if is_require_or_assert(callee)
65        && let Some(first) = args.exprs().next()
66        && matches!(
67            &first.peel_parens().kind,
68            ExprKind::Lit(lit) if matches!(lit.kind, LitKind::Bool(false))
69        )
70    {
71        return true;
72    }
73    false
74}