1use solar::{
2 interface::data_structures::Never,
3 sema::{Gcx, hir},
4};
5use std::ops::ControlFlow;
6
7use super::LintContext;
8
9pub trait LateLintPass<'hir>: Send + Sync {
12 fn check_nested_source(
13 &mut self,
14 _ctx: &LintContext,
15 _gcx: Gcx<'hir>,
16 _hir: &'hir hir::Hir<'hir>,
17 _id: hir::SourceId,
18 ) {
19 }
20 fn check_nested_item(
21 &mut self,
22 _ctx: &LintContext,
23 _hir: &'hir hir::Hir<'hir>,
24 _id: hir::ItemId,
25 ) {
26 }
27 fn check_nested_contract(
28 &mut self,
29 _ctx: &LintContext,
30 _gcx: Gcx<'hir>,
31 _hir: &'hir hir::Hir<'hir>,
32 _id: hir::ContractId,
33 ) {
34 }
35 fn check_nested_function(
36 &mut self,
37 _ctx: &LintContext,
38 _hir: &'hir hir::Hir<'hir>,
39 _id: hir::FunctionId,
40 ) {
41 }
42 fn check_nested_var(
43 &mut self,
44 _ctx: &LintContext,
45 _hir: &'hir hir::Hir<'hir>,
46 _id: hir::VariableId,
47 ) {
48 }
49 fn check_item(
50 &mut self,
51 _ctx: &LintContext,
52 _hir: &'hir hir::Hir<'hir>,
53 _item: hir::Item<'hir, 'hir>,
54 ) {
55 }
56 fn check_contract(
57 &mut self,
58 _ctx: &LintContext,
59 _gcx: Gcx<'hir>,
60 _hir: &'hir hir::Hir<'hir>,
61 _contract: &'hir hir::Contract<'hir>,
62 ) {
63 }
64 fn check_function(
65 &mut self,
66 _ctx: &LintContext,
67 _gcx: Gcx<'hir>,
68 _hir: &'hir hir::Hir<'hir>,
69 _func: &'hir hir::Function<'hir>,
70 ) {
71 }
72 fn check_modifier(
73 &mut self,
74 _ctx: &LintContext,
75 _hir: &'hir hir::Hir<'hir>,
76 _mod: &'hir hir::Modifier<'hir>,
77 ) {
78 }
79 fn check_var(
80 &mut self,
81 _ctx: &LintContext,
82 _hir: &'hir hir::Hir<'hir>,
83 _var: &'hir hir::Variable<'hir>,
84 ) {
85 }
86 fn check_expr(
87 &mut self,
88 _ctx: &LintContext,
89 _gcx: Gcx<'hir>,
90 _hir: &'hir hir::Hir<'hir>,
91 _expr: &'hir hir::Expr<'hir>,
92 ) {
93 }
94 fn check_call_args(
95 &mut self,
96 _ctx: &LintContext,
97 _hir: &'hir hir::Hir<'hir>,
98 _args: &'hir hir::CallArgs<'hir>,
99 ) {
100 }
101 fn check_stmt(
102 &mut self,
103 _ctx: &LintContext,
104 _gcx: Gcx<'hir>,
105 _hir: &'hir hir::Hir<'hir>,
106 _stmt: &'hir hir::Stmt<'hir>,
107 ) {
108 }
109 fn check_ty(
110 &mut self,
111 _ctx: &LintContext,
112 _hir: &'hir hir::Hir<'hir>,
113 _ty: &'hir hir::Type<'hir>,
114 ) {
115 }
116}
117
118pub struct LateLintVisitor<'a, 's, 'hir> {
120 ctx: &'a LintContext<'s, 'a>,
121 passes: &'a mut [Box<dyn LateLintPass<'hir> + 's>],
122 gcx: Gcx<'hir>,
123 hir: &'hir hir::Hir<'hir>,
124}
125
126impl<'a, 's, 'hir> LateLintVisitor<'a, 's, 'hir>
127where
128 's: 'hir,
129{
130 pub fn new(
131 ctx: &'a LintContext<'s, 'a>,
132 passes: &'a mut [Box<dyn LateLintPass<'hir> + 's>],
133 gcx: Gcx<'hir>,
134 hir: &'hir hir::Hir<'hir>,
135 ) -> Self {
136 Self { ctx, passes, gcx, hir }
137 }
138}
139
140impl<'s, 'hir> hir::Visit<'hir> for LateLintVisitor<'_, 's, 'hir>
141where
142 's: 'hir,
143{
144 type BreakValue = Never;
145
146 fn hir(&self) -> &'hir hir::Hir<'hir> {
147 self.hir
148 }
149
150 fn visit_nested_source(&mut self, id: hir::SourceId) -> ControlFlow<Self::BreakValue> {
151 for pass in self.passes.iter_mut() {
152 pass.check_nested_source(self.ctx, self.gcx, self.hir, id);
153 }
154 self.walk_nested_source(id)
155 }
156
157 fn visit_nested_item(&mut self, id: hir::ItemId) -> ControlFlow<Self::BreakValue> {
158 for pass in self.passes.iter_mut() {
159 pass.check_nested_item(self.ctx, self.hir, id);
160 }
161 self.walk_nested_item(id)
162 }
163
164 fn visit_nested_contract(&mut self, id: hir::ContractId) -> ControlFlow<Self::BreakValue> {
165 for pass in self.passes.iter_mut() {
166 pass.check_nested_contract(self.ctx, self.gcx, self.hir, id);
167 }
168 self.walk_nested_contract(id)
169 }
170
171 fn visit_nested_function(&mut self, id: hir::FunctionId) -> ControlFlow<Self::BreakValue> {
172 for pass in self.passes.iter_mut() {
173 pass.check_nested_function(self.ctx, self.hir, id);
174 }
175 self.walk_nested_function(id)
176 }
177
178 fn visit_nested_var(&mut self, id: hir::VariableId) -> ControlFlow<Self::BreakValue> {
179 for pass in self.passes.iter_mut() {
180 pass.check_nested_var(self.ctx, self.hir, id);
181 }
182 self.walk_nested_var(id)
183 }
184
185 fn visit_contract(
186 &mut self,
187 contract: &'hir hir::Contract<'hir>,
188 ) -> ControlFlow<Self::BreakValue> {
189 for pass in self.passes.iter_mut() {
190 pass.check_contract(self.ctx, self.gcx, self.hir, contract);
191 }
192 self.walk_contract(contract)
193 }
194
195 fn visit_function(&mut self, func: &'hir hir::Function<'hir>) -> ControlFlow<Self::BreakValue> {
196 for pass in self.passes.iter_mut() {
197 pass.check_function(self.ctx, self.gcx, self.hir, func);
198 }
199 self.walk_function(func)
200 }
201
202 fn visit_modifier(
203 &mut self,
204 modifier: &'hir hir::Modifier<'hir>,
205 ) -> ControlFlow<Self::BreakValue> {
206 for pass in self.passes.iter_mut() {
207 pass.check_modifier(self.ctx, self.hir, modifier);
208 }
209 self.walk_modifier(modifier)
210 }
211
212 fn visit_item(&mut self, item: hir::Item<'hir, 'hir>) -> ControlFlow<Self::BreakValue> {
213 for pass in self.passes.iter_mut() {
214 pass.check_item(self.ctx, self.hir, item);
215 }
216 self.walk_item(item)
217 }
218
219 fn visit_var(&mut self, var: &'hir hir::Variable<'hir>) -> ControlFlow<Self::BreakValue> {
220 for pass in self.passes.iter_mut() {
221 pass.check_var(self.ctx, self.hir, var);
222 }
223 self.walk_var(var)
224 }
225
226 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow<Self::BreakValue> {
227 for pass in self.passes.iter_mut() {
228 pass.check_expr(self.ctx, self.gcx, self.hir, expr);
229 }
230 self.walk_expr(expr)
231 }
232
233 fn visit_call_args(
234 &mut self,
235 args: &'hir hir::CallArgs<'hir>,
236 ) -> ControlFlow<Self::BreakValue> {
237 for pass in self.passes.iter_mut() {
238 pass.check_call_args(self.ctx, self.hir, args);
239 }
240 self.walk_call_args(args)
241 }
242
243 fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'hir>) -> ControlFlow<Self::BreakValue> {
244 for pass in self.passes.iter_mut() {
245 pass.check_stmt(self.ctx, self.gcx, self.hir, stmt);
246 }
247 self.walk_stmt(stmt)
248 }
249
250 fn visit_ty(&mut self, ty: &'hir hir::Type<'hir>) -> ControlFlow<Self::BreakValue> {
251 for pass in self.passes.iter_mut() {
252 pass.check_ty(self.ctx, self.hir, ty);
253 }
254 self.walk_ty(ty)
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::linter::LinterConfig;
262 use foundry_common::comments::inline_config::InlineConfig;
263 use foundry_config::lint::LintSpecificConfig;
264 use solar::{
265 interface::{Session, source_map::FileName},
266 sema::Compiler,
267 };
268 use std::sync::{Arc, Mutex};
269
270 #[derive(Debug, Default)]
271 struct HookCounts {
272 nested_item: usize,
273 nested_contract: usize,
274 nested_function: usize,
275 nested_var: usize,
276 modifier: usize,
277 call_args: usize,
278 }
279
280 struct RecordingPass {
281 counts: Arc<Mutex<HookCounts>>,
282 }
283
284 impl RecordingPass {
285 fn record(&self, update: impl FnOnce(&mut HookCounts)) {
286 update(&mut self.counts.lock().unwrap());
287 }
288 }
289
290 impl<'hir> LateLintPass<'hir> for RecordingPass {
291 fn check_nested_item(
292 &mut self,
293 _ctx: &LintContext,
294 _hir: &'hir hir::Hir<'hir>,
295 _id: hir::ItemId,
296 ) {
297 self.record(|counts| counts.nested_item += 1);
298 }
299
300 fn check_nested_contract(
301 &mut self,
302 _ctx: &LintContext,
303 _gcx: solar::sema::Gcx<'hir>,
304 _hir: &'hir hir::Hir<'hir>,
305 _id: hir::ContractId,
306 ) {
307 self.record(|counts| counts.nested_contract += 1);
308 }
309
310 fn check_nested_function(
311 &mut self,
312 _ctx: &LintContext,
313 _hir: &'hir hir::Hir<'hir>,
314 _id: hir::FunctionId,
315 ) {
316 self.record(|counts| counts.nested_function += 1);
317 }
318
319 fn check_nested_var(
320 &mut self,
321 _ctx: &LintContext,
322 _hir: &'hir hir::Hir<'hir>,
323 _id: hir::VariableId,
324 ) {
325 self.record(|counts| counts.nested_var += 1);
326 }
327
328 fn check_modifier(
329 &mut self,
330 _ctx: &LintContext,
331 _hir: &'hir hir::Hir<'hir>,
332 _modifier: &'hir hir::Modifier<'hir>,
333 ) {
334 self.record(|counts| counts.modifier += 1);
335 }
336
337 fn check_call_args(
338 &mut self,
339 _ctx: &LintContext,
340 _hir: &'hir hir::Hir<'hir>,
341 _args: &'hir hir::CallArgs<'hir>,
342 ) {
343 self.record(|counts| counts.call_args += 1);
344 }
345 }
346
347 #[test]
348 fn calls_hooks_for_nested_items_modifiers_and_call_args() {
349 let counts = Arc::new(Mutex::new(HookCounts::default()));
350 let inline = InlineConfig::default();
351 let lint_specific = LintSpecificConfig::default();
352 let source = r#"
353 pragma solidity ^0.8.20;
354
355 contract Base {
356 function hook(uint256 value) internal pure returns (uint256) {
357 return value;
358 }
359 }
360
361 contract Test is Base {
362 uint256 stored;
363
364 modifier gated(uint256 amount) {
365 _;
366 }
367
368 function run(uint256 amount) public gated(amount) returns (uint256) {
369 return hook(amount + stored);
370 }
371 }
372 "#;
373
374 let mut compiler =
375 Compiler::new(Session::builder().with_buffer_emitter(Default::default()).build());
376 compiler
377 .enter_mut(|compiler| -> solar::interface::Result<()> {
378 let mut pcx = compiler.parse();
379 pcx.set_resolve_imports(false);
380 let file = compiler
381 .sess()
382 .source_map()
383 .new_source_file(FileName::Stdin, source)
384 .expect("failed to create source file");
385 pcx.add_file(file);
386 pcx.parse();
387
388 let ControlFlow::Continue(()) = compiler.lower_asts()? else {
389 panic!("expected HIR lowering to continue");
390 };
391
392 let gcx = compiler.gcx();
393 let source_id = gcx.hir.source_ids().next().expect("expected one lowered source");
394 let ctx = LintContext::new(
395 gcx.sess,
396 false,
397 false,
398 LinterConfig { inline: &inline, lint_specific: &lint_specific },
399 Vec::new(),
400 None,
401 );
402 let mut passes: Vec<Box<dyn LateLintPass<'_>>> =
403 vec![Box::new(RecordingPass { counts: counts.clone() })];
404 let mut visitor = LateLintVisitor::new(&ctx, &mut passes, gcx, &gcx.hir);
405 let _ = hir::Visit::visit_nested_source(&mut visitor, source_id);
406 Ok(())
407 })
408 .expect("failed to lower test source");
409
410 let counts = counts.lock().unwrap();
411 assert!(counts.nested_item > 0, "expected nested item hook to run");
412 assert!(counts.nested_contract > 0, "expected nested contract hook to run");
413 assert!(counts.nested_function > 0, "expected nested function hook to run");
414 assert!(counts.nested_var > 0, "expected nested var hook to run");
415 assert!(counts.modifier > 0, "expected modifier hook to run");
416 assert!(counts.call_args > 0, "expected call args hook to run");
417 }
418}