Skip to main content

forge_lint/sol/low/
deprecated_oz_function.rs

1use super::DeprecatedOzFunction;
2use crate::{
3    linter::{LateLintPass, LintContext},
4    sol::{
5        Severity, SolLint,
6        analysis::{OPENZEPPELIN_ROOTS, source_in_package},
7    },
8};
9use solar::sema::{
10    Gcx,
11    hir::{Expr, ExprKind, FunctionId},
12};
13
14declare_forge_lint!(
15    DEPRECATED_OZ_FUNCTION,
16    Severity::Low,
17    "deprecated-oz-function",
18    "this OpenZeppelin function is deprecated"
19);
20
21impl<'gcx> LateLintPass<'gcx> for DeprecatedOzFunction {
22    fn check_expr(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, expr: &'gcx Expr<'gcx>) {
23        // A name or member expression typed as a function is a resolved reference, called or
24        // used as a value: judge the single declaration the type checker selected (overloads,
25        // overrides, `super.`, `using for` and import aliases already accounted for).
26        if matches!(expr.kind, ExprKind::Ident(..) | ExprKind::Member(..))
27            && let Some(function_id) = gcx.resolved_function(expr)
28            && is_deprecated_oz(gcx, function_id)
29        {
30            ctx.emit(&DEPRECATED_OZ_FUNCTION, expr.span);
31        }
32    }
33}
34
35/// Whether `function_id` is one of the functions OpenZeppelin deprecated: `SafeERC20.safeApprove`
36/// and `AccessControl._setupRole` (plus their upgradeable variants). Extensions inherit these
37/// functions rather than redeclare them, so resolution still lands on the canonical declaration;
38/// a same-name function of an unrelated contract or library stays out, and so does a same-name
39/// local declaration, which fails the provenance check.
40fn is_deprecated_oz(gcx: Gcx<'_>, function_id: FunctionId) -> bool {
41    let function = gcx.hir.function(function_id);
42    let (Some(name), Some(contract_id)) = (function.name, function.contract) else { return false };
43    if !source_in_package(&gcx.hir, function.source, OPENZEPPELIN_ROOTS) {
44        return false;
45    }
46    let contract = gcx.hir.contract(contract_id);
47    matches!(
48        (name.as_str(), contract.kind.is_library(), contract.name.as_str()),
49        ("safeApprove", true, "SafeERC20" | "SafeERC20Upgradeable")
50            | ("_setupRole", false, "AccessControl" | "AccessControlUpgradeable")
51    )
52}