Skip to main content

forge_lint/sol/med/
mapping_deletion.rs

1use super::MappingDeletion;
2use crate::{
3    linter::{LateLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::sema::{
7    Gcx,
8    hir::{self, ExprKind},
9    ty::{Ty, TyKind},
10};
11
12declare_forge_lint!(
13    MAPPING_DELETION,
14    Severity::Med,
15    "mapping-deletion",
16    "`delete` on a value containing a mapping does not clear the mapping"
17);
18
19impl<'hir> LateLintPass<'hir> for MappingDeletion {
20    fn check_expr(
21        &mut self,
22        ctx: &LintContext,
23        gcx: Gcx<'hir>,
24        hir: &'hir hir::Hir<'hir>,
25        expr: &'hir hir::Expr<'hir>,
26    ) {
27        // `delete <expr>` where the operand's type reaches a `mapping`. Deleting a whole mapping is
28        // not valid Solidity, so the operand is a struct/array that holds one.
29        if let ExprKind::Delete(operand) = &expr.kind
30            && let Some(ty) = gcx.type_of_expr(operand.peel_parens().id)
31            && ty_contains_mapping(gcx, hir, ty, &mut Vec::new())
32        {
33            ctx.emit(&MAPPING_DELETION, expr.span);
34        }
35    }
36}
37
38/// Returns `true` if `ty` is, or transitively contains, a `mapping`.
39///
40/// `delete` zeroes each member of a storage value, but it cannot enumerate a mapping's keys, so any
41/// mapping reachable from `ty` keeps its entries. Recurses through structs and arrays; `seen`
42/// guards against recursive struct definitions (only reachable through mapping/array members, which
43/// Solidity permits).
44fn ty_contains_mapping<'hir>(
45    gcx: Gcx<'hir>,
46    hir: &'hir hir::Hir<'hir>,
47    ty: Ty<'hir>,
48    seen: &mut Vec<hir::StructId>,
49) -> bool {
50    match ty.peel_refs().kind {
51        TyKind::Mapping(..) => true,
52        TyKind::Array(elem, _) | TyKind::DynArray(elem) | TyKind::Slice(elem) => {
53            ty_contains_mapping(gcx, hir, elem, seen)
54        }
55        TyKind::Struct(id) => {
56            if seen.contains(&id) {
57                return false;
58            }
59            seen.push(id);
60            hir.strukt(id)
61                .fields
62                .iter()
63                .any(|&field| ty_contains_mapping(gcx, hir, gcx.type_of_item(field.into()), seen))
64        }
65        _ => false,
66    }
67}