1use super::{
2 ReentrancyEvents,
3 calls_loop::{
4 is_state_mutating_external_call, resolved_internal_function_ids,
5 resolved_super_function_ids,
6 },
7};
8use crate::{
9 linter::{LateLintPass, LintContext},
10 sol::{
11 Severity, SolLint,
12 analysis::helper_cache::{DEFAULT_HELPER_ANALYSIS_CACHE_LIMIT, HelperAnalysisCache},
13 },
14};
15use solar::{
16 ast::LitKind,
17 interface::{Span, kw, sym},
18 sema::{
19 Gcx,
20 hir::{
21 self, Block, ContractId, Expr, ExprKind, Function, FunctionId, Hir, Res, Stmt, StmtKind,
22 },
23 },
24};
25use std::collections::{HashMap, HashSet};
26
27declare_forge_lint!(
28 REENTRANCY_EVENTS,
29 Severity::Low,
30 "reentrancy-events",
31 "event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on"
32);
33
34impl<'hir> LateLintPass<'hir> for ReentrancyEvents {
35 fn check_function(
36 &mut self,
37 ctx: &LintContext,
38 gcx: Gcx<'hir>,
39 hir: &'hir Hir<'hir>,
40 func: &'hir Function<'hir>,
41 ) {
42 let Some(body) = func.body else { return };
43
44 let mut analyzer = Analyzer::new(ctx, gcx, hir, func.contract);
45 let _ = analyzer.analyze_callable(func, body, FlowState::default());
46 }
47}
48
49type Placeholder<'hir> = Option<(&'hir [hir::Modifier<'hir>], usize, Block<'hir>)>;
50
51#[derive(Clone, Debug, Default, PartialEq, Eq)]
53struct FlowState {
54 external_call_seen: bool,
56}
57
58impl FlowState {
59 const fn merge(&mut self, other: &Self) {
60 self.external_call_seen |= other.external_call_seen;
61 }
62}
63
64#[derive(Clone, Debug, Default)]
70struct Exits {
71 fallthrough: Option<FlowState>,
73 return_: Option<FlowState>,
75 break_: Option<FlowState>,
77 continue_: Option<FlowState>,
79}
80
81impl Exits {
82 fn fallthrough(state: FlowState) -> Self {
83 Self { fallthrough: Some(state), ..Default::default() }
84 }
85
86 fn return_(state: FlowState) -> Self {
87 Self { return_: Some(state), ..Default::default() }
88 }
89
90 fn break_(state: FlowState) -> Self {
91 Self { break_: Some(state), ..Default::default() }
92 }
93
94 fn continue_(state: FlowState) -> Self {
95 Self { continue_: Some(state), ..Default::default() }
96 }
97
98 fn abort() -> Self {
100 Self::default()
101 }
102
103 const fn merge(&mut self, other: Self) {
104 merge_opt(&mut self.fallthrough, other.fallthrough);
105 merge_opt(&mut self.return_, other.return_);
106 merge_opt(&mut self.break_, other.break_);
107 merge_opt(&mut self.continue_, other.continue_);
108 }
109}
110
111const fn merge_opt(dst: &mut Option<FlowState>, src: Option<FlowState>) {
112 match (dst.as_mut(), src) {
113 (None, src) => *dst = src,
114 (Some(_), None) => {}
115 (Some(d), Some(s)) => d.merge(&s),
116 }
117}
118
119#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
120struct InlineCallKey {
121 func_id: FunctionId,
122 external_call_seen: bool,
123 suppress_inline_reports: bool,
124}
125
126struct Analyzer<'ctx, 's, 'c, 'hir> {
127 ctx: &'ctx LintContext<'s, 'c>,
128 gcx: Gcx<'hir>,
129 hir: &'hir Hir<'hir>,
130 enclosing_contract: Option<ContractId>,
133 call_stack: Vec<FunctionId>,
135 inline_cache: HelperAnalysisCache<InlineCallKey, Exits>,
138 external_call_reachability: HashMap<FunctionId, bool>,
140 emitted: HashSet<Span>,
142 suppress_inline_reports: bool,
145 expr_aborted: bool,
148}
149
150impl<'ctx, 's, 'c, 'hir> Analyzer<'ctx, 's, 'c, 'hir> {
151 fn new(
152 ctx: &'ctx LintContext<'s, 'c>,
153 gcx: Gcx<'hir>,
154 hir: &'hir Hir<'hir>,
155 enclosing_contract: Option<ContractId>,
156 ) -> Self {
157 Self {
158 ctx,
159 gcx,
160 hir,
161 enclosing_contract,
162 call_stack: Vec::new(),
163 inline_cache: HelperAnalysisCache::new(DEFAULT_HELPER_ANALYSIS_CACHE_LIMIT),
164 external_call_reachability: HashMap::new(),
165 emitted: HashSet::new(),
166 suppress_inline_reports: false,
167 expr_aborted: false,
168 }
169 }
170
171 fn analyze_callable(
172 &mut self,
173 func: &'hir Function<'hir>,
174 body: Block<'hir>,
175 entry: FlowState,
176 ) -> Exits {
177 self.analyze_modifier_chain(func.modifiers, 0, body, entry)
178 }
179
180 fn analyze_modifier_chain(
181 &mut self,
182 modifiers: &'hir [hir::Modifier<'hir>],
183 index: usize,
184 body: Block<'hir>,
185 mut entry: FlowState,
186 ) -> Exits {
187 let Some(modifier) = modifiers.get(index) else {
188 return self.analyze_block(body, None, entry);
189 };
190
191 for arg in modifier.args.exprs() {
192 self.expr_aborted = false;
193 self.analyze_expr(arg, &mut entry);
194 if self.expr_aborted {
196 return Exits::abort();
197 }
198 }
199
200 let Some(modifier_id) = modifier.id.as_function() else {
201 return self.analyze_modifier_chain(modifiers, index + 1, body, entry);
202 };
203
204 let modifier_func = self.hir.function(modifier_id);
210 let Some(modifier_body) = modifier_func.body else {
211 return self.analyze_modifier_chain(modifiers, index + 1, body, entry);
212 };
213
214 self.call_stack.push(modifier_id);
215 let summary = self.analyze_block(modifier_body, Some((modifiers, index + 1, body)), entry);
216 self.call_stack.pop();
217 summary
218 }
219
220 fn analyze_block(
221 &mut self,
222 block: Block<'hir>,
223 placeholder: Placeholder<'hir>,
224 mut entry: FlowState,
225 ) -> Exits {
226 let mut summary = Exits::default();
227 for stmt in block.stmts {
228 let stmt_exits = self.analyze_stmt(stmt, placeholder, entry);
229 merge_opt(&mut summary.return_, stmt_exits.return_);
231 merge_opt(&mut summary.break_, stmt_exits.break_);
232 merge_opt(&mut summary.continue_, stmt_exits.continue_);
233 match stmt_exits.fallthrough {
235 Some(next) => entry = next,
236 None => return summary, }
238 }
239 summary.fallthrough = Some(entry);
240 summary
241 }
242
243 fn analyze_stmt(
244 &mut self,
245 stmt: &'hir Stmt<'hir>,
246 placeholder: Placeholder<'hir>,
247 mut entry: FlowState,
248 ) -> Exits {
249 self.expr_aborted = false;
252 match stmt.kind {
253 StmtKind::DeclSingle(var_id) => {
254 if let Some(init) = self.hir.variable(var_id).initializer {
255 self.analyze_expr(init, &mut entry);
256 }
257 if self.expr_aborted {
258 return Exits::abort();
259 }
260 Exits::fallthrough(entry)
261 }
262 StmtKind::DeclMulti(_, expr) | StmtKind::Expr(expr) => {
263 self.analyze_expr(expr, &mut entry);
264 if is_aborting_call(expr) || self.expr_aborted {
267 return Exits::abort();
268 }
269 Exits::fallthrough(entry)
270 }
271 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
272 self.analyze_block(block, placeholder, entry)
273 }
274 StmtKind::Emit(expr) => {
275 self.analyze_expr(expr, &mut entry);
278 if self.expr_aborted {
281 return Exits::abort();
282 }
283 if entry.external_call_seen
284 && !self.suppress_inline_reports
285 && self.emitted.insert(stmt.span)
286 {
287 self.ctx.emit(&REENTRANCY_EVENTS, stmt.span);
288 }
289 Exits::fallthrough(entry)
290 }
291 StmtKind::Revert(expr) => {
292 self.analyze_expr(expr, &mut entry);
293 Exits::abort()
294 }
295 StmtKind::Return(expr) => {
296 if let Some(expr) = expr {
297 self.analyze_expr(expr, &mut entry);
298 }
299 if self.expr_aborted {
301 return Exits::abort();
302 }
303 Exits::return_(entry)
304 }
305 StmtKind::Break => Exits::break_(entry),
306 StmtKind::Continue => Exits::continue_(entry),
307 StmtKind::Loop(block, _) => {
308 let first = self.analyze_block(block, placeholder, entry.clone());
313
314 let mut back_edge = entry.clone();
317 if let Some(ft) = &first.fallthrough {
318 back_edge.merge(ft);
319 }
320 if let Some(c) = &first.continue_ {
321 back_edge.merge(c);
322 }
323
324 let body = if back_edge == entry {
325 first
326 } else {
327 self.analyze_block(block, placeholder, back_edge)
328 };
329
330 let mut post = entry;
334 if let Some(ft) = body.fallthrough {
335 post.merge(&ft);
336 }
337 if let Some(b) = body.break_ {
338 post.merge(&b);
339 }
340 if let Some(c) = body.continue_ {
341 post.merge(&c);
342 }
343 Exits {
344 fallthrough: Some(post),
345 return_: body.return_,
346 break_: None,
347 continue_: None,
348 }
349 }
350 StmtKind::If(cond, then_stmt, else_stmt) => {
351 self.analyze_expr(cond, &mut entry);
352 if self.expr_aborted {
355 return Exits::abort();
356 }
357
358 let then_exits = self.analyze_stmt(then_stmt, placeholder, entry.clone());
359 let else_exits = if let Some(else_stmt) = else_stmt {
360 self.analyze_stmt(else_stmt, placeholder, entry)
361 } else {
362 Exits::fallthrough(entry)
363 };
364
365 let mut merged = then_exits;
366 merged.merge(else_exits);
367 merged
368 }
369 StmtKind::Try(try_stmt) => {
370 self.analyze_expr(&try_stmt.expr, &mut entry);
371 if self.expr_aborted {
374 return Exits::abort();
375 }
376
377 let mut summary = Exits::default();
378 for clause in try_stmt.clauses {
379 let clause_exits = self.analyze_block(clause.block, placeholder, entry.clone());
380 summary.merge(clause_exits);
381 }
382 summary
383 }
384 StmtKind::Placeholder => {
385 if let Some((modifiers, index, body)) = placeholder {
386 self.analyze_modifier_chain(modifiers, index, body, entry)
387 } else {
388 Exits::fallthrough(entry)
389 }
390 }
391 StmtKind::AssemblyBlock(_) | StmtKind::Switch(_) | StmtKind::Err(_) => {
392 entry.external_call_seen = true;
395 Exits::fallthrough(entry)
396 }
397 }
398 }
399
400 fn analyze_expr(&mut self, expr: &'hir Expr<'hir>, state: &mut FlowState) {
401 match &expr.kind {
402 ExprKind::Call(callee, args, opts) => {
403 self.analyze_expr(callee, state);
404 if let Some(opts) = opts {
405 for opt in opts.args {
406 self.analyze_expr(&opt.value, state);
407 }
408 }
409 for arg in args.exprs() {
410 self.analyze_expr(arg, state);
411 }
412
413 if is_state_mutating_external_call(
414 self.gcx,
415 self.hir,
416 callee,
417 args.len(),
418 self.enclosing_contract,
419 ) {
420 state.external_call_seen = true;
421 }
422
423 for func_id in resolved_internal_function_ids(self.hir, callee) {
426 self.analyze_internal_call(func_id, state);
427 }
428 for func_id in resolved_super_function_ids(
430 self.hir,
431 self.enclosing_contract,
432 callee,
433 args.len(),
434 ) {
435 self.analyze_internal_call(func_id, state);
436 }
437 }
438 ExprKind::Binary(lhs, op, rhs)
439 if matches!(op.kind, hir::BinOpKind::And | hir::BinOpKind::Or) =>
440 {
441 self.analyze_expr(lhs, state);
446 let lhs_aborted = std::mem::replace(&mut self.expr_aborted, false);
447
448 let mut rhs_state = state.clone();
449 self.analyze_expr(rhs, &mut rhs_state);
450 let rhs_aborted = self.expr_aborted;
451
452 self.expr_aborted = lhs_aborted;
455
456 if !lhs_aborted && !rhs_aborted {
457 state.merge(&rhs_state);
458 }
459 }
460 ExprKind::Assign(lhs, _, rhs) | ExprKind::Binary(lhs, _, rhs) => {
461 self.analyze_expr(lhs, state);
462 self.analyze_expr(rhs, state);
463 }
464 ExprKind::Unary(_, inner) | ExprKind::Delete(inner) | ExprKind::Payable(inner) => {
465 self.analyze_expr(inner, state);
466 }
467 ExprKind::Index(base, index) => {
468 self.analyze_expr(base, state);
469 if let Some(index) = index {
470 self.analyze_expr(index, state);
471 }
472 }
473 ExprKind::Slice(base, start, end) => {
474 self.analyze_expr(base, state);
475 if let Some(start) = start {
476 self.analyze_expr(start, state);
477 }
478 if let Some(end) = end {
479 self.analyze_expr(end, state);
480 }
481 }
482 ExprKind::Ternary(cond, then_expr, else_expr) => {
483 self.analyze_expr(cond, state);
484 let outer_aborted = std::mem::replace(&mut self.expr_aborted, false);
487
488 let mut then_state = state.clone();
489 self.analyze_expr(then_expr, &mut then_state);
490 let then_aborted = std::mem::replace(&mut self.expr_aborted, false);
491
492 let mut else_state = state.clone();
493 self.analyze_expr(else_expr, &mut else_state);
494 let else_aborted = self.expr_aborted;
495
496 self.expr_aborted = outer_aborted || (then_aborted && else_aborted);
497
498 match (then_aborted, else_aborted) {
500 (true, true) => {}
501 (true, false) => *state = else_state,
502 (false, true) => *state = then_state,
503 (false, false) => {
504 *state = then_state;
505 state.merge(&else_state);
506 }
507 }
508 }
509 ExprKind::Array(exprs) => {
510 for expr in *exprs {
511 self.analyze_expr(expr, state);
512 }
513 }
514 ExprKind::Tuple(exprs) => {
515 for expr in exprs.iter().copied().flatten() {
516 self.analyze_expr(expr, state);
517 }
518 }
519 ExprKind::Member(base, _) => self.analyze_expr(base, state),
520 ExprKind::Ident(_)
521 | ExprKind::Lit(_)
522 | ExprKind::New(_)
523 | ExprKind::TypeCall(_)
524 | ExprKind::Type(_)
525 | ExprKind::YulMember(..)
526 | ExprKind::Err(_) => {}
527 }
528 }
529
530 fn analyze_internal_call(&mut self, func_id: FunctionId, state: &mut FlowState) {
531 if self.call_stack.contains(&func_id) {
532 if self.helper_may_reach_external_call(func_id) {
535 state.external_call_seen = true;
536 }
537 return;
538 }
539
540 let func = self.hir.function(func_id);
541 let Some(body) = func.body else { return };
542
543 let suppress_inline_reports = self.suppress_inline_reports || !state.external_call_seen;
544 let key = InlineCallKey {
545 func_id,
546 external_call_seen: state.external_call_seen,
547 suppress_inline_reports,
548 };
549
550 if self.inline_cache.is_in_progress(&key) {
551 return;
552 }
553
554 if let Some(summary) = self.inline_cache.get(&key).cloned() {
555 self.apply_inline_summary(summary, state);
556 return;
557 }
558
559 let prev_suppress = self.suppress_inline_reports;
563 self.suppress_inline_reports = suppress_inline_reports;
564
565 self.inline_cache.start(key);
566 self.call_stack.push(func_id);
567 let summary = self.analyze_callable(func, body, state.clone());
568 self.call_stack.pop();
569
570 self.inline_cache.finish(key, summary.clone());
571
572 self.suppress_inline_reports = prev_suppress;
573
574 self.apply_inline_summary(summary, state);
575 }
576
577 fn apply_inline_summary(&mut self, summary: Exits, state: &mut FlowState) {
578 let any_normal = summary.fallthrough.is_some() || summary.return_.is_some();
581 if any_normal {
582 let mut after = FlowState::default();
583 if let Some(ft) = summary.fallthrough {
584 after.merge(&ft);
585 }
586 if let Some(rt) = summary.return_ {
587 after.merge(&rt);
588 }
589 *state = after;
590 } else {
591 self.expr_aborted = true;
592 }
593 }
594
595 fn helper_may_reach_external_call(&mut self, func_id: FunctionId) -> bool {
596 self.helper_may_reach_external_call_inner(func_id, &mut HashSet::new()).0
597 }
598
599 fn helper_may_reach_external_call_inner(
600 &mut self,
601 func_id: FunctionId,
602 seen: &mut HashSet<FunctionId>,
603 ) -> (bool, bool) {
604 if let Some(cached) = self.external_call_reachability.get(&func_id) {
605 return (*cached, false);
606 }
607 if !seen.insert(func_id) {
608 return (false, true);
609 }
610
611 let func = self.hir.function(func_id);
612 let mut may_reach = false;
613 let mut cut_recursive_edge = false;
614 for modifier in func.modifiers {
615 for arg in modifier.args.exprs() {
616 let (arg_may_reach, arg_cut_recursive_edge) =
617 self.expr_may_reach_external_call(arg, seen);
618 cut_recursive_edge |= arg_cut_recursive_edge;
619 if arg_may_reach {
620 may_reach = true;
621 break;
622 }
623 }
624 if may_reach {
625 break;
626 }
627 if let Some(modifier_id) = modifier.id.as_function() {
628 let (modifier_may_reach, modifier_cut_recursive_edge) =
629 self.helper_may_reach_external_call_inner(modifier_id, seen);
630 cut_recursive_edge |= modifier_cut_recursive_edge;
631 if modifier_may_reach {
632 may_reach = true;
633 break;
634 }
635 }
636 }
637 if !may_reach && let Some(body) = func.body {
638 let (body_may_reach, body_cut_recursive_edge) =
639 self.block_may_reach_external_call(body, seen);
640 may_reach = body_may_reach;
641 cut_recursive_edge |= body_cut_recursive_edge;
642 }
643
644 seen.remove(&func_id);
645 if may_reach || !cut_recursive_edge {
646 self.external_call_reachability.insert(func_id, may_reach);
647 }
648 (may_reach, cut_recursive_edge)
649 }
650
651 fn block_may_reach_external_call(
652 &mut self,
653 block: Block<'hir>,
654 seen: &mut HashSet<FunctionId>,
655 ) -> (bool, bool) {
656 let mut cut_recursive_edge = false;
657 for stmt in block.stmts {
658 let (may_reach, stmt_cut_recursive_edge) =
659 self.stmt_may_reach_external_call(stmt, seen);
660 cut_recursive_edge |= stmt_cut_recursive_edge;
661 if may_reach {
662 return (true, cut_recursive_edge);
663 }
664 }
665 (false, cut_recursive_edge)
666 }
667
668 fn stmt_may_reach_external_call(
669 &mut self,
670 stmt: &'hir Stmt<'hir>,
671 seen: &mut HashSet<FunctionId>,
672 ) -> (bool, bool) {
673 match stmt.kind {
674 StmtKind::DeclSingle(var_id) => match self.hir.variable(var_id).initializer {
675 Some(expr) => self.expr_may_reach_external_call(expr, seen),
676 None => (false, false),
677 },
678 StmtKind::DeclMulti(_, expr)
679 | StmtKind::Expr(expr)
680 | StmtKind::Emit(expr)
681 | StmtKind::Revert(expr) => self.expr_may_reach_external_call(expr, seen),
682 StmtKind::Return(expr) => match expr {
683 Some(expr) => self.expr_may_reach_external_call(expr, seen),
684 None => (false, false),
685 },
686 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) | StmtKind::Loop(block, _) => {
687 self.block_may_reach_external_call(block, seen)
688 }
689 StmtKind::If(cond, then_stmt, else_stmt) => Self::any_may_reach_external_call([
690 self.expr_may_reach_external_call(cond, seen),
691 self.stmt_may_reach_external_call(then_stmt, seen),
692 else_stmt
693 .map(|else_stmt| self.stmt_may_reach_external_call(else_stmt, seen))
694 .unwrap_or((false, false)),
695 ]),
696 StmtKind::Try(try_stmt) => {
697 let mut cut_recursive_edge = false;
698 let (expr_may_reach, expr_cut_recursive_edge) =
699 self.expr_may_reach_external_call(&try_stmt.expr, seen);
700 cut_recursive_edge |= expr_cut_recursive_edge;
701 if expr_may_reach {
702 return (true, cut_recursive_edge);
703 }
704 for clause in try_stmt.clauses {
705 let (clause_may_reach, clause_cut_recursive_edge) =
706 self.block_may_reach_external_call(clause.block, seen);
707 cut_recursive_edge |= clause_cut_recursive_edge;
708 if clause_may_reach {
709 return (true, cut_recursive_edge);
710 }
711 }
712 (false, cut_recursive_edge)
713 }
714 StmtKind::AssemblyBlock(_) | StmtKind::Switch(_) => (true, false),
715 StmtKind::Break | StmtKind::Continue | StmtKind::Placeholder | StmtKind::Err(_) => {
716 (false, false)
717 }
718 }
719 }
720
721 fn expr_may_reach_external_call(
722 &mut self,
723 expr: &'hir Expr<'hir>,
724 seen: &mut HashSet<FunctionId>,
725 ) -> (bool, bool) {
726 match &expr.kind {
727 ExprKind::Call(callee, args, opts) => {
728 let mut cut_recursive_edge = false;
729 let (callee_may_reach, callee_cut_recursive_edge) =
730 self.expr_may_reach_external_call(callee, seen);
731 cut_recursive_edge |= callee_cut_recursive_edge;
732 if callee_may_reach {
733 return (true, cut_recursive_edge);
734 }
735 if let Some(opts) = opts {
736 for opt in opts.args {
737 let (opt_may_reach, opt_cut_recursive_edge) =
738 self.expr_may_reach_external_call(&opt.value, seen);
739 cut_recursive_edge |= opt_cut_recursive_edge;
740 if opt_may_reach {
741 return (true, cut_recursive_edge);
742 }
743 }
744 }
745 for arg in args.exprs() {
746 let (arg_may_reach, arg_cut_recursive_edge) =
747 self.expr_may_reach_external_call(arg, seen);
748 cut_recursive_edge |= arg_cut_recursive_edge;
749 if arg_may_reach {
750 return (true, cut_recursive_edge);
751 }
752 }
753 if is_state_mutating_external_call(
754 self.gcx,
755 self.hir,
756 callee,
757 args.len(),
758 self.enclosing_contract,
759 ) {
760 return (true, cut_recursive_edge);
761 }
762
763 let internal: Vec<_> = resolved_internal_function_ids(self.hir, callee).collect();
764 for func_id in internal {
765 let (func_may_reach, func_cut_recursive_edge) =
766 self.helper_may_reach_external_call_inner(func_id, seen);
767 cut_recursive_edge |= func_cut_recursive_edge;
768 if func_may_reach {
769 return (true, cut_recursive_edge);
770 }
771 }
772
773 for func_id in resolved_super_function_ids(
774 self.hir,
775 self.enclosing_contract,
776 callee,
777 args.len(),
778 ) {
779 let (func_may_reach, func_cut_recursive_edge) =
780 self.helper_may_reach_external_call_inner(func_id, seen);
781 cut_recursive_edge |= func_cut_recursive_edge;
782 if func_may_reach {
783 return (true, cut_recursive_edge);
784 }
785 }
786
787 (false, cut_recursive_edge)
788 }
789 ExprKind::Assign(lhs, _, rhs) | ExprKind::Binary(lhs, _, rhs) => {
790 Self::any_may_reach_external_call([
791 self.expr_may_reach_external_call(lhs, seen),
792 self.expr_may_reach_external_call(rhs, seen),
793 ])
794 }
795 ExprKind::Unary(_, inner)
796 | ExprKind::Delete(inner)
797 | ExprKind::Payable(inner)
798 | ExprKind::Member(inner, _) => self.expr_may_reach_external_call(inner, seen),
799 ExprKind::Index(base, index) => Self::any_may_reach_external_call([
800 self.expr_may_reach_external_call(base, seen),
801 index
802 .map(|index| self.expr_may_reach_external_call(index, seen))
803 .unwrap_or((false, false)),
804 ]),
805 ExprKind::Slice(base, start, end) => Self::any_may_reach_external_call([
806 self.expr_may_reach_external_call(base, seen),
807 start
808 .map(|start| self.expr_may_reach_external_call(start, seen))
809 .unwrap_or((false, false)),
810 end.map(|end| self.expr_may_reach_external_call(end, seen))
811 .unwrap_or((false, false)),
812 ]),
813 ExprKind::Ternary(cond, then_expr, else_expr) => Self::any_may_reach_external_call([
814 self.expr_may_reach_external_call(cond, seen),
815 self.expr_may_reach_external_call(then_expr, seen),
816 self.expr_may_reach_external_call(else_expr, seen),
817 ]),
818 ExprKind::Array(exprs) => self.exprs_may_reach_external_call(exprs, seen),
819 ExprKind::Tuple(exprs) => {
820 let mut cut_recursive_edge = false;
821 for expr in exprs.iter().copied().flatten() {
822 let (expr_may_reach, expr_cut_recursive_edge) =
823 self.expr_may_reach_external_call(expr, seen);
824 cut_recursive_edge |= expr_cut_recursive_edge;
825 if expr_may_reach {
826 return (true, cut_recursive_edge);
827 }
828 }
829 (false, cut_recursive_edge)
830 }
831 ExprKind::Ident(_)
832 | ExprKind::Lit(_)
833 | ExprKind::New(_)
834 | ExprKind::TypeCall(_)
835 | ExprKind::Type(_)
836 | ExprKind::YulMember(..)
837 | ExprKind::Err(_) => (false, false),
838 }
839 }
840
841 fn exprs_may_reach_external_call(
842 &mut self,
843 exprs: &'hir [Expr<'hir>],
844 seen: &mut HashSet<FunctionId>,
845 ) -> (bool, bool) {
846 let mut cut_recursive_edge = false;
847 for expr in exprs {
848 let (expr_may_reach, expr_cut_recursive_edge) =
849 self.expr_may_reach_external_call(expr, seen);
850 cut_recursive_edge |= expr_cut_recursive_edge;
851 if expr_may_reach {
852 return (true, cut_recursive_edge);
853 }
854 }
855 (false, cut_recursive_edge)
856 }
857
858 fn any_may_reach_external_call(
859 results: impl IntoIterator<Item = (bool, bool)>,
860 ) -> (bool, bool) {
861 let mut cut_recursive_edge = false;
862 for (may_reach, result_cut_recursive_edge) in results {
863 cut_recursive_edge |= result_cut_recursive_edge;
864 if may_reach {
865 return (true, cut_recursive_edge);
866 }
867 }
868 (false, cut_recursive_edge)
869 }
870}
871
872fn is_aborting_call(expr: &Expr<'_>) -> bool {
876 let ExprKind::Call(callee, args, _) = &expr.peel_parens().kind else {
877 return false;
878 };
879 let ExprKind::Ident(reses) = &callee.peel_parens().kind else {
880 return false;
881 };
882 for res in *reses {
883 let Res::Builtin(b) = res else { continue };
884 let name = b.name();
885 if name == kw::Revert || name == kw::Selfdestruct {
886 return true;
887 }
888 if (name == sym::require || name == sym::assert)
889 && args.exprs().next().is_some_and(literal_false)
890 {
891 return true;
892 }
893 }
894 false
895}
896
897fn literal_false(expr: &Expr<'_>) -> bool {
899 matches!(
900 &expr.peel_parens().kind,
901 ExprKind::Lit(lit) if matches!(lit.kind, LitKind::Bool(false))
902 )
903}