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::LitKind,
8 sema::{
9 Gcx,
10 builtins::Builtin,
11 hir::{Expr, ExprKind},
12 },
13};
14
15declare_forge_lint!(
16 ENCODE_PACKED_COLLISION,
17 Severity::High,
18 "encode-packed-collision",
19 "`abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible"
20);
21
22impl<'gcx> LateLintPass<'gcx> for EncodedPackedCollision {
23 fn check_expr(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, expr: &'gcx Expr<'gcx>) {
24 let ExprKind::Call(callee, args, _) = &expr.kind else { return };
25 if gcx.resolved_builtin(callee) != Some(Builtin::AbiEncodePacked) {
26 return;
27 }
28 let dynamic_count =
32 args.exprs().filter(|arg| !is_str_lit(arg) && is_dynamic_arg(gcx, arg)).count();
33 if dynamic_count >= 2 {
34 ctx.emit(&ENCODE_PACKED_COLLISION, expr.span);
35 }
36 }
37}
38
39fn is_str_lit(expr: &Expr<'_>) -> bool {
40 matches!(expr.peel_parens().kind, ExprKind::Lit(lit) if matches!(lit.kind, LitKind::Str(..)))
41}
42
43fn is_dynamic_arg<'gcx>(gcx: Gcx<'gcx>, expr: &Expr<'gcx>) -> bool {
44 gcx.type_of_expr(expr.peel_parens().id).is_some_and(|ty| ty.peel_refs().is_dynamically_sized())
45}