forge_lint/sol/high/
encode_packed_collision.rs1use super::EncodedPackedCollision;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast,
8 interface::{Ident, Symbol, sym},
9 sema::{
10 Gcx,
11 hir::{ElementaryType, Expr, ExprKind, Hir, Res, TypeKind},
12 ty::{Ty, TyKind},
13 },
14};
15
16declare_forge_lint!(
17 ENCODE_PACKED_COLLISION,
18 Severity::High,
19 "encode-packed-collision",
20 "`abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible"
21);
22
23impl<'hir> LateLintPass<'hir> for EncodedPackedCollision {
24 fn check_expr(
25 &mut self,
26 ctx: &LintContext,
27 gcx: Gcx<'hir>,
28 _hir: &'hir Hir<'hir>,
29 expr: &'hir Expr<'hir>,
30 ) {
31 let ExprKind::Call(callee, args, _) = &expr.kind else { return };
32 let ExprKind::Member(base, member) = &callee.peel_parens().kind else { return };
33 if member.name != sym::encodePacked || !is_abi_builtin(base) {
34 return;
35 }
36 let dynamic_count = args
40 .exprs()
41 .filter(|arg| {
42 !matches!(
43 arg.peel_parens().kind,
44 ExprKind::Lit(lit) if matches!(lit.kind, ast::LitKind::Str(..))
45 ) && is_dynamic_arg(gcx, arg)
46 })
47 .count();
48 if dynamic_count >= 2 {
49 ctx.emit(&ENCODE_PACKED_COLLISION, expr.span);
50 }
51 }
52}
53
54fn is_abi_builtin(expr: &Expr<'_>) -> bool {
55 is_builtin_named(expr, sym::abi)
56}
57
58fn is_dynamic_arg<'hir>(gcx: Gcx<'hir>, expr: &'hir Expr<'hir>) -> bool {
59 let expr = expr.peel_parens();
60 match &expr.kind {
61 ExprKind::Lit(lit) if matches!(lit.kind, ast::LitKind::Str(..)) => true,
63 ExprKind::Ternary(_, then, else_) => {
66 is_dynamic_arg(gcx, then) && is_dynamic_arg(gcx, else_)
67 }
68 ExprKind::Call(callee, _, _) => {
70 is_dynamic_call(callee)
71 || gcx.type_of_expr(expr.id).is_some_and(ty_is_dynamic_bytes_string_or_array)
72 }
73 ExprKind::Member(base, member) => {
76 if let Some(ty) = gcx.type_of_expr(expr.id) {
77 ty_is_dynamic_bytes_string_or_array(ty)
78 } else {
79 is_dynamic_builtin_member(base, member)
80 }
81 }
82 _ => gcx.type_of_expr(expr.id).is_some_and(ty_is_dynamic_bytes_string_or_array),
83 }
84}
85
86fn ty_is_dynamic_bytes_string_or_array(ty: Ty<'_>) -> bool {
87 matches!(
88 ty.peel_refs().kind,
89 TyKind::Elementary(ElementaryType::Bytes | ElementaryType::String)
90 | TyKind::DynArray(_)
91 | TyKind::Slice(_)
92 )
93}
94
95fn is_dynamic_call(callee: &Expr<'_>) -> bool {
97 let callee = callee.peel_parens();
98 match &callee.kind {
99 ExprKind::New(ty) => return is_dynamic_hir_type(&ty.kind),
101 ExprKind::Member(recv, method) => {
102 if is_abi_builtin(recv) && is_abi_encode_method(method.name) {
104 return true;
105 }
106 if method.name == sym::concat
108 && let ExprKind::Type(ty) = &recv.peel_parens().kind
109 && is_dynamic_hir_type(&ty.kind)
110 {
111 return true;
112 }
113 }
114 _ => {}
115 }
116 false
117}
118
119fn is_dynamic_builtin_member(base: &Expr<'_>, member: &Ident) -> bool {
121 match member.name {
122 n if n == sym::data => is_builtin_named(base, sym::msg),
124 n if n == sym::code || n == sym::creationCode || n == sym::runtimeCode => true,
126 _ => false,
127 }
128}
129
130fn is_abi_encode_method(name: Symbol) -> bool {
131 name == sym::encode
132 || name == sym::encodePacked
133 || name == sym::encodeWithSelector
134 || name == sym::encodeWithSignature
135 || name == sym::encodeCall
136}
137
138fn is_builtin_named(expr: &Expr<'_>, name: Symbol) -> bool {
139 matches!(&expr.peel_parens().kind,
140 ExprKind::Ident(reses) if reses.iter().any(|r| matches!(r, Res::Builtin(b) if b.name() == name))
141 )
142}
143
144const fn is_dynamic_hir_type(kind: &TypeKind<'_>) -> bool {
145 match kind {
146 TypeKind::Elementary(ElementaryType::String | ElementaryType::Bytes) => true,
147 TypeKind::Array(arr) => arr.size.is_none(),
148 _ => false,
149 }
150}