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