1use super::UnprotectedInitializer;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast::{ContractKind, DataLocation, FunctionKind, StateMutability, Visibility},
8 interface::{Symbol, kw, sym},
9 sema::{
10 Gcx,
11 builtins::Builtin,
12 hir::{self, ContractId, ExprKind, FunctionId, ItemId, Res, StmtKind, VariableId},
13 },
14};
15use std::collections::HashSet;
16
17declare_forge_lint!(
18 UNPROTECTED_INITIALIZER,
19 Severity::High,
20 "unprotected-initializer",
21 "upgradeable initializer is not protected against direct implementation calls"
22);
23
24impl<'hir> LateLintPass<'hir> for UnprotectedInitializer {
25 fn check_nested_contract(
26 &mut self,
27 ctx: &LintContext,
28 gcx: Gcx<'hir>,
29 hir: &'hir hir::Hir<'hir>,
30 contract_id: ContractId,
31 ) {
32 let contract = hir.contract(contract_id);
33 if !matches!(contract.kind, ContractKind::Contract) || contract.linearization_failed() {
34 return;
35 }
36
37 let upgradeable = contract
38 .linearized_bases
39 .iter()
40 .any(|&base_id| hir.contract(base_id).name.as_str() == "Initializable");
41 let runtime_entries = effective_runtime_dispatch_surface(hir, contract.linearized_bases);
42 if !upgradeable
43 && !runtime_entries.iter().any(|&fid| has_initializer_modifier(hir, hir.function(fid)))
44 {
45 return;
46 }
47
48 if initializers_disabled_in_constructor(hir, contract) {
49 return;
50 }
51
52 if !has_destructive_entrypoint(hir, contract, &runtime_entries) {
53 return;
54 }
55
56 for fid in runtime_entries {
57 let func = hir.function(fid);
58 if !is_public_initializer(hir, func) || has_modifier_named(hir, func, "onlyProxy") {
59 continue;
60 }
61
62 let Some(body) = func.body else { continue };
63 let mut analyzer = StateWriteAnalyzer {
64 gcx,
65 hir,
66 bases: contract.linearized_bases,
67 stack: Vec::new(),
68 };
69 if analyzer.block_writes_state(body) {
70 ctx.emit(&UNPROTECTED_INITIALIZER, func.name.map_or(func.span, |name| name.span));
71 }
72 }
73 }
74}
75
76fn is_public_initializer(hir: &hir::Hir<'_>, func: &hir::Function<'_>) -> bool {
77 func.kind.is_function()
78 && matches!(func.visibility, Visibility::Public | Visibility::External)
79 && !matches!(func.state_mutability, StateMutability::Pure | StateMutability::View)
80 && has_initializer_modifier(hir, func)
81}
82
83fn initializers_disabled_in_constructor<'hir>(
84 hir: &'hir hir::Hir<'hir>,
85 contract: &hir::Contract<'hir>,
86) -> bool {
87 contract.linearized_bases.iter().filter_map(|&cid| hir.contract(cid).ctor).any(|ctor_id| {
88 let ctor = hir.function(ctor_id);
89 function_calls_named(hir, ctor, contract.linearized_bases, "_disableInitializers")
90 })
91}
92
93fn has_destructive_entrypoint<'hir>(
94 hir: &'hir hir::Hir<'hir>,
95 contract: &hir::Contract<'hir>,
96 runtime_entries: &[FunctionId],
97) -> bool {
98 runtime_entries.iter().copied().any(|fid| {
99 let func = hir.function(fid);
100 if has_modifier_named(hir, func, "onlyProxy") {
101 return false;
102 }
103
104 let Some(body) = func.body else { return false };
105 let mut finder =
106 DestructiveSinkFinder { hir, bases: contract.linearized_bases, stack: vec![fid] };
107 finder.block_has_destructive_sink(body)
108 })
109}
110
111fn effective_runtime_dispatch_surface(hir: &hir::Hir<'_>, bases: &[ContractId]) -> Vec<FunctionId> {
112 let mut seen_functions: HashSet<(Symbol, String)> = HashSet::new();
113 let mut seen_fallback = false;
114 let mut seen_receive = false;
115 let mut entries = Vec::new();
116
117 for &cid in bases {
118 for fid in hir.contract(cid).all_functions() {
119 let func = hir.function(fid);
120 match func.kind {
121 FunctionKind::Function => {
122 if !matches!(func.visibility, Visibility::Public | Visibility::External) {
123 continue;
124 }
125 let Some(name) = func.name else { continue };
126 if seen_functions.insert((name.name, parameter_signature(hir, func.parameters)))
127 {
128 entries.push(fid);
129 }
130 }
131 FunctionKind::Fallback => {
132 if !seen_fallback {
133 seen_fallback = true;
134 entries.push(fid);
135 }
136 }
137 FunctionKind::Receive => {
138 if !seen_receive {
139 seen_receive = true;
140 entries.push(fid);
141 }
142 }
143 FunctionKind::Constructor | FunctionKind::Modifier => {}
144 }
145 }
146 }
147
148 entries
149}
150
151fn parameter_signature(hir: &hir::Hir<'_>, params: &[VariableId]) -> String {
152 params
153 .iter()
154 .map(|¶m| format!("{:?}", hir.variable(param).ty.kind))
155 .collect::<Vec<_>>()
156 .join(",")
157}
158
159struct DestructiveSinkFinder<'hir> {
160 hir: &'hir hir::Hir<'hir>,
161 bases: &'hir [ContractId],
162 stack: Vec<FunctionId>,
163}
164
165impl<'hir> DestructiveSinkFinder<'hir> {
166 fn block_has_destructive_sink(&mut self, block: hir::Block<'hir>) -> bool {
167 block.stmts.iter().any(|stmt| self.stmt_has_destructive_sink(stmt))
168 }
169
170 fn stmt_has_destructive_sink(&mut self, stmt: &'hir hir::Stmt<'hir>) -> bool {
171 match &stmt.kind {
172 StmtKind::DeclSingle(var_id) => self
173 .hir
174 .variable(*var_id)
175 .initializer
176 .is_some_and(|init| self.expr_has_destructive_sink(init)),
177 StmtKind::DeclMulti(_, expr)
178 | StmtKind::Emit(expr)
179 | StmtKind::Revert(expr)
180 | StmtKind::Return(Some(expr))
181 | StmtKind::Expr(expr) => self.expr_has_destructive_sink(expr),
182 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => {
183 self.block_has_destructive_sink(*block)
184 }
185 StmtKind::If(condition, then_stmt, else_stmt) => {
186 self.expr_has_destructive_sink(condition)
187 || self.stmt_has_destructive_sink(then_stmt)
188 || else_stmt.is_some_and(|stmt| self.stmt_has_destructive_sink(stmt))
189 }
190 StmtKind::Try(stmt_try) => {
191 self.expr_has_destructive_sink(&stmt_try.expr)
192 || stmt_try
193 .clauses
194 .iter()
195 .any(|clause| self.block_has_destructive_sink(clause.block))
196 }
197 StmtKind::Return(None)
198 | StmtKind::Break
199 | StmtKind::Continue
200 | StmtKind::Placeholder
201 | StmtKind::AssemblyBlock(_)
202 | StmtKind::Switch(_)
203 | StmtKind::Err(_) => false,
204 }
205 }
206
207 fn expr_has_destructive_sink(&mut self, expr: &'hir hir::Expr<'hir>) -> bool {
208 match &expr.kind {
209 ExprKind::Call(callee, args, opts) => {
210 if is_destructive_call(callee) {
211 return true;
212 }
213
214 if self.expr_has_destructive_sink(callee)
215 || opts.is_some_and(|opts| {
216 opts.args.iter().any(|opt| self.expr_has_destructive_sink(&opt.value))
217 })
218 || args.exprs().any(|arg| self.expr_has_destructive_sink(arg))
219 {
220 return true;
221 }
222
223 resolved_internal_function_ids(self.hir, callee, self.bases)
224 .into_iter()
225 .any(|func_id| self.function_has_destructive_sink(func_id))
226 }
227 ExprKind::Assign(lhs, _, rhs) | ExprKind::Binary(lhs, _, rhs) => {
228 self.expr_has_destructive_sink(lhs) || self.expr_has_destructive_sink(rhs)
229 }
230 ExprKind::Unary(_, inner) | ExprKind::Delete(inner) | ExprKind::Payable(inner) => {
231 self.expr_has_destructive_sink(inner)
232 }
233 ExprKind::Index(base, index) => {
234 self.expr_has_destructive_sink(base)
235 || index.is_some_and(|index| self.expr_has_destructive_sink(index))
236 }
237 ExprKind::Slice(base, start, end) => {
238 self.expr_has_destructive_sink(base)
239 || start.is_some_and(|start| self.expr_has_destructive_sink(start))
240 || end.is_some_and(|end| self.expr_has_destructive_sink(end))
241 }
242 ExprKind::Member(base, _) => self.expr_has_destructive_sink(base),
243 ExprKind::Ternary(condition, if_true, if_false) => {
244 self.expr_has_destructive_sink(condition)
245 || self.expr_has_destructive_sink(if_true)
246 || self.expr_has_destructive_sink(if_false)
247 }
248 ExprKind::Array(exprs) => exprs.iter().any(|expr| self.expr_has_destructive_sink(expr)),
249 ExprKind::Tuple(exprs) => {
250 exprs.iter().flatten().any(|expr| self.expr_has_destructive_sink(expr))
251 }
252 ExprKind::Lit(_)
253 | ExprKind::Ident(_)
254 | ExprKind::New(_)
255 | ExprKind::TypeCall(_)
256 | ExprKind::Type(_)
257 | ExprKind::YulMember(..)
258 | ExprKind::Err(_) => false,
259 }
260 }
261
262 fn function_has_destructive_sink(&mut self, func_id: FunctionId) -> bool {
263 if self.stack.contains(&func_id) {
264 return false;
265 }
266
267 let func = self.hir.function(func_id);
268 let Some(body) = func.body else { return false };
269 self.stack.push(func_id);
270 let found = self.block_has_destructive_sink(body);
271 self.stack.pop();
272 found
273 }
274}
275
276fn is_destructive_call(callee: &hir::Expr<'_>) -> bool {
277 match &callee.peel_parens().kind {
278 ExprKind::Member(_, member) => matches!(member.name, kw::Delegatecall | kw::Callcode),
279 ExprKind::Ident(resolutions) => {
280 resolutions.iter().any(|res| matches!(res, Res::Builtin(Builtin::Selfdestruct)))
281 }
282 _ => false,
283 }
284}
285
286fn has_initializer_modifier(hir: &hir::Hir<'_>, func: &hir::Function<'_>) -> bool {
287 has_modifier_named(hir, func, "initializer") || has_modifier_named(hir, func, "reinitializer")
288}
289
290fn has_modifier_named(hir: &hir::Hir<'_>, func: &hir::Function<'_>, name: &str) -> bool {
291 func.modifiers.iter().any(|modifier| modifier_name_is(hir, modifier, name))
292}
293
294fn modifier_name_is(hir: &hir::Hir<'_>, modifier: &hir::Modifier<'_>, name: &str) -> bool {
295 match modifier.id {
296 ItemId::Function(fid) => hir.function(fid).name.is_some_and(|ident| ident.as_str() == name),
297 ItemId::Contract(cid) => hir.contract(cid).name.as_str() == name,
298 _ => false,
299 }
300}
301
302fn function_calls_named<'hir>(
303 hir: &'hir hir::Hir<'hir>,
304 func: &hir::Function<'hir>,
305 bases: &'hir [ContractId],
306 name: &str,
307) -> bool {
308 let Some(body) = func.body else { return false };
309 let mut finder = CallNameFinder { hir, name, bases, stack: vec![] };
310 finder.block_calls_named(body)
311}
312
313struct CallNameFinder<'a, 'hir> {
314 hir: &'hir hir::Hir<'hir>,
315 name: &'a str,
316 bases: &'hir [ContractId],
317 stack: Vec<FunctionId>,
318}
319
320impl<'hir> CallNameFinder<'_, 'hir> {
321 fn block_calls_named(&mut self, block: hir::Block<'hir>) -> bool {
322 block.stmts.iter().any(|stmt| self.stmt_calls_named(stmt))
323 }
324
325 fn stmt_calls_named(&mut self, stmt: &'hir hir::Stmt<'hir>) -> bool {
326 match &stmt.kind {
327 StmtKind::DeclSingle(var_id) => self
328 .hir
329 .variable(*var_id)
330 .initializer
331 .is_some_and(|init| self.expr_calls_named(init)),
332 StmtKind::DeclMulti(_, expr)
333 | StmtKind::Emit(expr)
334 | StmtKind::Revert(expr)
335 | StmtKind::Return(Some(expr))
336 | StmtKind::Expr(expr) => self.expr_calls_named(expr),
337 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => {
338 self.block_calls_named(*block)
339 }
340 StmtKind::If(condition, then_stmt, else_stmt) => {
341 self.expr_calls_named(condition)
342 || self.stmt_calls_named(then_stmt)
343 || else_stmt.is_some_and(|stmt| self.stmt_calls_named(stmt))
344 }
345 StmtKind::Try(stmt_try) => {
346 self.expr_calls_named(&stmt_try.expr)
347 || stmt_try.clauses.iter().any(|clause| self.block_calls_named(clause.block))
348 }
349 StmtKind::Return(None)
350 | StmtKind::Break
351 | StmtKind::Continue
352 | StmtKind::Placeholder
353 | StmtKind::AssemblyBlock(_)
354 | StmtKind::Switch(_)
355 | StmtKind::Err(_) => false,
356 }
357 }
358
359 fn expr_calls_named(&mut self, expr: &'hir hir::Expr<'hir>) -> bool {
360 match &expr.kind {
361 ExprKind::Call(callee, args, opts) => {
362 let called_functions = resolved_internal_function_ids(self.hir, callee, self.bases);
363 if called_functions
364 .iter()
365 .copied()
366 .any(|func_id| self.function_matches_name(func_id))
367 {
368 return true;
369 }
370
371 if let Some(opts) = opts
372 && opts.args.iter().any(|opt| self.expr_calls_named(&opt.value))
373 {
374 return true;
375 }
376
377 if args.exprs().any(|arg| self.expr_calls_named(arg)) {
378 return true;
379 }
380
381 for func_id in called_functions {
382 if self.function_belongs_to_bases(func_id) && self.function_calls_named(func_id)
383 {
384 return true;
385 }
386 }
387
388 false
389 }
390 ExprKind::Assign(lhs, _, rhs) | ExprKind::Binary(lhs, _, rhs) => {
391 self.expr_calls_named(lhs) || self.expr_calls_named(rhs)
392 }
393 ExprKind::Unary(_, inner) | ExprKind::Delete(inner) | ExprKind::Payable(inner) => {
394 self.expr_calls_named(inner)
395 }
396 ExprKind::Index(base, index) => {
397 self.expr_calls_named(base)
398 || index.is_some_and(|index| self.expr_calls_named(index))
399 }
400 ExprKind::Slice(base, start, end) => {
401 self.expr_calls_named(base)
402 || start.is_some_and(|start| self.expr_calls_named(start))
403 || end.is_some_and(|end| self.expr_calls_named(end))
404 }
405 ExprKind::Member(base, _) => self.expr_calls_named(base),
406 ExprKind::Ternary(condition, if_true, if_false) => {
407 self.expr_calls_named(condition)
408 || self.expr_calls_named(if_true)
409 || self.expr_calls_named(if_false)
410 }
411 ExprKind::Array(exprs) => exprs.iter().any(|expr| self.expr_calls_named(expr)),
412 ExprKind::Tuple(exprs) => {
413 exprs.iter().flatten().any(|expr| self.expr_calls_named(expr))
414 }
415 ExprKind::Lit(_)
416 | ExprKind::Ident(_)
417 | ExprKind::New(_)
418 | ExprKind::TypeCall(_)
419 | ExprKind::Type(_)
420 | ExprKind::YulMember(..)
421 | ExprKind::Err(_) => false,
422 }
423 }
424
425 fn function_calls_named(&mut self, func_id: FunctionId) -> bool {
426 if self.stack.contains(&func_id) {
427 return false;
428 }
429
430 let func = self.hir.function(func_id);
431 let Some(body) = func.body else { return false };
432 self.stack.push(func_id);
433 let found = self.block_calls_named(body);
434 self.stack.pop();
435 found
436 }
437
438 fn function_matches_name(&self, func_id: FunctionId) -> bool {
439 self.function_belongs_to_bases(func_id)
440 && self.hir.function(func_id).name.is_some_and(|ident| ident.as_str() == self.name)
441 }
442
443 fn function_belongs_to_bases(&self, func_id: FunctionId) -> bool {
444 self.hir
445 .function(func_id)
446 .contract
447 .is_some_and(|contract_id| self.bases.contains(&contract_id))
448 }
449}
450
451struct StateWriteAnalyzer<'hir> {
452 gcx: Gcx<'hir>,
453 hir: &'hir hir::Hir<'hir>,
454 bases: &'hir [ContractId],
455 stack: Vec<FunctionId>,
456}
457
458impl<'hir> StateWriteAnalyzer<'hir> {
459 fn block_writes_state(&mut self, block: hir::Block<'hir>) -> bool {
460 block.stmts.iter().any(|stmt| self.stmt_writes_state(stmt))
461 }
462
463 fn stmt_writes_state(&mut self, stmt: &'hir hir::Stmt<'hir>) -> bool {
464 match &stmt.kind {
465 StmtKind::DeclSingle(var_id) => self
466 .hir
467 .variable(*var_id)
468 .initializer
469 .is_some_and(|init| self.expr_writes_state(init)),
470 StmtKind::DeclMulti(_, expr)
471 | StmtKind::Emit(expr)
472 | StmtKind::Revert(expr)
473 | StmtKind::Return(Some(expr))
474 | StmtKind::Expr(expr) => self.expr_writes_state(expr),
475 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => {
476 self.block_writes_state(*block)
477 }
478 StmtKind::If(condition, then_stmt, else_stmt) => {
479 self.expr_writes_state(condition)
480 || self.stmt_writes_state(then_stmt)
481 || else_stmt.is_some_and(|stmt| self.stmt_writes_state(stmt))
482 }
483 StmtKind::Try(stmt_try) => {
484 self.expr_writes_state(&stmt_try.expr)
485 || stmt_try.clauses.iter().any(|clause| self.block_writes_state(clause.block))
486 }
487 StmtKind::Return(None)
488 | StmtKind::Break
489 | StmtKind::Continue
490 | StmtKind::Placeholder
491 | StmtKind::AssemblyBlock(_)
492 | StmtKind::Switch(_)
493 | StmtKind::Err(_) => false,
494 }
495 }
496
497 fn expr_writes_state(&mut self, expr: &'hir hir::Expr<'hir>) -> bool {
498 match &expr.kind {
499 ExprKind::Assign(lhs, _, rhs) => {
500 lhs_writes_state(self.gcx, self.hir, lhs)
501 || self.expr_writes_state(lhs)
502 || self.expr_writes_state(rhs)
503 }
504 ExprKind::Delete(inner) => {
505 lhs_writes_state(self.gcx, self.hir, inner) || self.expr_writes_state(inner)
506 }
507 ExprKind::Unary(op, inner) => {
508 (op.kind.has_side_effects() && lhs_writes_state(self.gcx, self.hir, inner))
509 || self.expr_writes_state(inner)
510 }
511 ExprKind::Call(callee, args, opts) => {
512 if member_call_writes_state(self.gcx, self.hir, callee) {
513 return true;
514 }
515
516 if self.expr_writes_state(callee)
517 || opts.is_some_and(|opts| {
518 opts.args.iter().any(|opt| self.expr_writes_state(&opt.value))
519 })
520 || args.exprs().any(|arg| self.expr_writes_state(arg))
521 {
522 return true;
523 }
524
525 resolved_internal_function_ids(self.hir, callee, self.bases)
526 .into_iter()
527 .any(|func_id| self.function_writes_state(func_id))
528 }
529 ExprKind::Binary(lhs, _, rhs) => {
530 self.expr_writes_state(lhs) || self.expr_writes_state(rhs)
531 }
532 ExprKind::Index(base, index) => {
533 self.expr_writes_state(base)
534 || index.is_some_and(|index| self.expr_writes_state(index))
535 }
536 ExprKind::Slice(base, start, end) => {
537 self.expr_writes_state(base)
538 || start.is_some_and(|start| self.expr_writes_state(start))
539 || end.is_some_and(|end| self.expr_writes_state(end))
540 }
541 ExprKind::Member(base, _) | ExprKind::Payable(base) => self.expr_writes_state(base),
542 ExprKind::Ternary(condition, if_true, if_false) => {
543 self.expr_writes_state(condition)
544 || self.expr_writes_state(if_true)
545 || self.expr_writes_state(if_false)
546 }
547 ExprKind::Array(exprs) => exprs.iter().any(|expr| self.expr_writes_state(expr)),
548 ExprKind::Tuple(exprs) => {
549 exprs.iter().flatten().any(|expr| self.expr_writes_state(expr))
550 }
551 ExprKind::Lit(_)
552 | ExprKind::Ident(_)
553 | ExprKind::New(_)
554 | ExprKind::TypeCall(_)
555 | ExprKind::Type(_)
556 | ExprKind::YulMember(..)
557 | ExprKind::Err(_) => false,
558 }
559 }
560
561 fn function_writes_state(&mut self, func_id: FunctionId) -> bool {
562 if self.stack.contains(&func_id) {
563 return false;
564 }
565
566 let func = self.hir.function(func_id);
567 let Some(body) = func.body else { return false };
568 self.stack.push(func_id);
569 let writes = self.block_writes_state(body);
570 self.stack.pop();
571 writes
572 }
573}
574
575fn member_call_writes_state(gcx: Gcx<'_>, hir: &hir::Hir<'_>, callee: &hir::Expr<'_>) -> bool {
576 let ExprKind::Member(base, member) = &callee.peel_parens().kind else { return false };
577 matches!(member.as_str(), "push" | "pop") && lhs_writes_state(gcx, hir, base)
578}
579
580fn lhs_writes_state(gcx: Gcx<'_>, hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool {
581 match &expr.peel_parens().kind {
582 ExprKind::Ident(resolutions) => {
583 resolutions.iter().any(|res| matches!(res, Res::Item(ItemId::Variable(var_id)) if hir.variable(*var_id).kind.is_state()))
584 }
585 ExprKind::Index(base, _) | ExprKind::Slice(base, _, _) | ExprKind::Member(base, _) => {
586 expr_references_storage(gcx, hir, base)
587 }
588 ExprKind::Call(..) => expr_references_storage(gcx, hir, expr),
589 ExprKind::Tuple(exprs) => {
590 exprs.iter().flatten().any(|expr| lhs_writes_state(gcx, hir, expr))
591 }
592 _ => false,
593 }
594}
595
596fn expr_references_storage(gcx: Gcx<'_>, hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool {
597 match &expr.peel_parens().kind {
598 ExprKind::Ident(resolutions) => resolutions.iter().any(|res| {
599 matches!(res, Res::Item(ItemId::Variable(var_id)) if variable_references_storage(hir.variable(*var_id)))
600 }),
601 ExprKind::Index(base, _) | ExprKind::Slice(base, _, _) | ExprKind::Member(base, _) => {
602 expr_references_storage(gcx, hir, base)
603 }
604 ExprKind::Call(..) | ExprKind::Ternary(..) => gcx
605 .type_of_expr(expr.peel_parens().id)
606 .is_some_and(|ty| ty.loc() == Some(DataLocation::Storage)),
607 _ => false,
608 }
609}
610
611fn variable_references_storage(var: &hir::Variable<'_>) -> bool {
612 var.kind.is_state() || var.data_location == Some(DataLocation::Storage)
613}
614
615fn resolved_internal_function_ids(
616 hir: &hir::Hir<'_>,
617 callee: &hir::Expr<'_>,
618 bases: &[ContractId],
619) -> Vec<FunctionId> {
620 match &callee.peel_parens().kind {
621 ExprKind::Ident(resolutions) => resolutions
622 .iter()
623 .filter_map(|res| match res {
624 Res::Item(ItemId::Function(func_id)) => Some(*func_id),
625 _ => None,
626 })
627 .collect(),
628 ExprKind::Member(base, method) => {
629 let ExprKind::Ident(resolutions) = &base.peel_parens().kind else { return vec![] };
630 let is_super = resolutions
631 .iter()
632 .any(|res| matches!(res, Res::Builtin(builtin) if builtin.name() == sym::super_));
633
634 let contracts: Vec<_> = if is_super {
635 bases.get(1..).unwrap_or_default().to_vec()
636 } else {
637 resolutions
638 .iter()
639 .filter_map(|res| match res {
640 Res::Item(ItemId::Contract(cid)) => Some(*cid),
641 _ => None,
642 })
643 .collect()
644 };
645
646 contracts
647 .into_iter()
648 .flat_map(|cid| hir.contract(cid).all_functions())
649 .filter(|&fid| {
650 hir.function(fid).name.is_some_and(|name| name.as_str() == method.as_str())
651 })
652 .collect()
653 }
654 _ => vec![],
655 }
656}