forge_lint/sol/low/
return_bomb.rs1use super::ReturnBomb;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint, analysis::is_call_with_gas_limit},
5};
6use solar::sema::{
7 Gcx, Ty,
8 builtins::Builtin,
9 hir::{self, ExprKind},
10 ty::TyKind,
11};
12
13declare_forge_lint!(
14 RETURN_BOMB,
15 Severity::Low,
16 "return-bomb",
17 "external call with a gas limit may copy unbounded return data"
18);
19
20impl<'gcx> LateLintPass<'gcx> for ReturnBomb {
21 fn check_expr(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, expr: &'gcx hir::Expr<'gcx>) {
22 let expr = expr.peel_parens();
25 if !is_call_with_gas_limit(expr) {
26 return;
27 }
28 let ExprKind::Call(callee, ..) = &expr.kind else { return };
29 let low_level = matches!(
30 gcx.resolved_builtin(callee),
31 Some(Builtin::AddressCall | Builtin::AddressDelegatecall | Builtin::AddressStaticcall)
32 );
33 if low_level || gcx.type_of_expr(expr.id).is_some_and(|ty| is_dynamic_ty(gcx, ty)) {
34 ctx.emit(&RETURN_BOMB, expr.span);
35 }
36 }
37}
38
39fn is_dynamic_ty<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> bool {
40 let ty = ty.peel_refs();
41 match ty.kind {
42 TyKind::Tuple(elements) => elements.iter().any(|ty| is_dynamic_ty(gcx, *ty)),
43 _ => ty.is_dynamically_encoded(gcx),
44 }
45}