forge_lint/sol/codesize/
unwrapped_modifier_logic.rs1use super::UnwrappedModifierLogic;
2use crate::{
3 linter::{LateLintPass, LintContext, Suggestion},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast,
8 sema::hir::{self, Res},
9};
10
11declare_forge_lint!(
12 UNWRAPPED_MODIFIER_LOGIC,
13 Severity::CodeSize,
14 "unwrapped-modifier-logic",
15 "wrap modifier logic to reduce code size"
16);
17
18impl<'hir> LateLintPass<'hir> for UnwrappedModifierLogic {
19 fn check_function(
20 &mut self,
21 ctx: &LintContext,
22 _gcx: solar::sema::Gcx<'hir>,
23 hir: &'hir hir::Hir<'hir>,
24 func: &'hir hir::Function<'hir>,
25 ) {
26 let body = match (func.kind, &func.body, func.name) {
28 (ast::FunctionKind::Modifier, Some(body), Some(_)) => body,
29 _ => return,
30 };
31
32 let stmts = body.stmts[..].as_ref();
34 let (before, after) = stmts
35 .iter()
36 .position(|s| matches!(s.kind, hir::StmtKind::Placeholder))
37 .map_or((stmts, &[][..]), |idx| (&stmts[..idx], &stmts[idx + 1..]));
38
39 if let Some(suggestion) = self.get_snippet(ctx, hir, func, before, after) {
41 ctx.emit_with_suggestion(
42 &UNWRAPPED_MODIFIER_LOGIC,
43 func.span.to(func.body_span),
44 suggestion,
45 );
46 }
47 }
48}
49
50impl UnwrappedModifierLogic {
51 fn is_valid_expr(&self, hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool {
53 if let hir::ExprKind::Call(func_expr, _, _) = &expr.kind {
54 if let hir::ExprKind::Ident(resolutions) = &func_expr.kind {
55 return !resolutions.iter().any(|r| matches!(r, Res::Builtin(_)));
56 }
57
58 if let hir::ExprKind::Member(base, _) = &func_expr.kind
59 && let hir::ExprKind::Ident(resolutions) = &base.kind
60 {
61 return resolutions.iter().any(|r| {
62 matches!(r, Res::Item(hir::ItemId::Contract(id)) if hir.contract(*id).kind == ast::ContractKind::Library)
63 });
64 }
65 }
66
67 false
68 }
69
70 fn stmts_require_wrapping(&self, hir: &hir::Hir<'_>, stmts: &[hir::Stmt<'_>]) -> bool {
80 let (mut res, mut has_valid_stmt) = (false, false);
81 for stmt in stmts {
82 match &stmt.kind {
83 hir::StmtKind::Placeholder => {}
84 hir::StmtKind::Expr(expr) => {
85 if !self.is_valid_expr(hir, expr) || has_valid_stmt {
86 res = true;
87 }
88 has_valid_stmt = true;
89 }
90 hir::StmtKind::AssemblyBlock(_)
92 | hir::StmtKind::Switch(_)
93 | hir::StmtKind::Err(_) => return false,
94 _ => res = true,
95 }
96 }
97
98 res
99 }
100
101 fn get_snippet<'a>(
102 &self,
103 ctx: &LintContext,
104 hir: &hir::Hir<'_>,
105 func: &hir::Function<'_>,
106 before: &'a [hir::Stmt<'a>],
107 after: &'a [hir::Stmt<'a>],
108 ) -> Option<Suggestion> {
109 let wrap_before = !before.is_empty() && self.stmts_require_wrapping(hir, before);
110 let wrap_after = !after.is_empty() && self.stmts_require_wrapping(hir, after);
111
112 if !(wrap_before || wrap_after) {
113 return None;
114 }
115
116 let binding = func.name.unwrap();
117 let modifier_name = binding.name.as_str();
118 let mut param_list = vec![];
119 let mut param_decls = vec![];
120
121 for var_id in func.parameters {
122 let var = hir.variable(*var_id);
123 let ty = ctx
124 .span_to_snippet(var.ty.span)
125 .unwrap_or_else(|| "/* unknown type */".to_string());
126
127 if let Some(ident) = var.name {
129 param_list.push(ident.to_string());
130 param_decls.push(format!("{ty} {}", ident.to_string()));
131 }
132 }
133
134 let param_list = param_list.join(", ");
135 let param_decls = param_decls.join(", ");
136
137 let body_indent = " ".repeat(ctx.get_span_indentation(
138 before.first().or(after.first()).map(|stmt| stmt.span).unwrap_or(func.span),
139 ));
140 let body = match (wrap_before, wrap_after) {
141 (true, true) => format!(
142 "{body_indent}_{modifier_name}Before({param_list});\n{body_indent}_;\n{body_indent}_{modifier_name}After({param_list});"
143 ),
144 (true, false) => {
145 format!("{body_indent}_{modifier_name}({param_list});\n{body_indent}_;")
146 }
147 (false, true) => {
148 format!("{body_indent}_;\n{body_indent}_{modifier_name}({param_list});")
149 }
150 _ => unreachable!(),
151 };
152
153 let mod_indent = " ".repeat(ctx.get_span_indentation(func.span));
154 let mut replacement =
155 format!("modifier {modifier_name}({param_decls}) {{\n{body}\n{mod_indent}}}");
156
157 let build_func = |stmts: &[hir::Stmt<'_>], suffix: &str| {
158 let body_stmts = stmts
159 .iter()
160 .filter_map(|s| ctx.span_to_snippet(s.span))
161 .map(|code| format!("\n{body_indent}{code}"))
162 .collect::<String>();
163 format!(
164 "\n\n{mod_indent}function _{modifier_name}{suffix}({param_decls}) internal {{{body_stmts}\n{mod_indent}}}"
165 )
166 };
167
168 if wrap_before {
169 replacement.push_str(&build_func(before, if wrap_after { "Before" } else { "" }));
170 }
171 if wrap_after {
172 replacement.push_str(&build_func(after, if wrap_before { "After" } else { "" }));
173 }
174
175 Some(
176 Suggestion::fix(
177 replacement,
178 ast::interface::diagnostics::Applicability::MachineApplicable,
179 )
180 .with_desc("wrap modifier logic to reduce code size"),
181 )
182 }
183}