1use super::ArbitrarySendEth;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast::{self, LitKind},
8 interface::{Span, Symbol, data_structures::Never, kw, sym},
9 sema::{
10 Gcx, Ty,
11 builtins::Builtin,
12 hir::{
13 self, CallArgs, ContractKind, ElementaryType, ExprKind, FunctionId, FunctionKind,
14 ItemId, LoopSource, Res, StmtKind, TypeKind, Visit,
15 },
16 ty::TyKind,
17 },
18};
19use std::{
20 collections::{HashMap, HashSet},
21 ops::ControlFlow,
22};
23
24declare_forge_lint!(
25 ARBITRARY_SEND_ETH,
26 Severity::High,
27 "arbitrary-send-eth",
28 "ETH is sent to a user-controlled destination; restrict the destination or the caller"
29);
30
31impl<'hir> LateLintPass<'hir> for ArbitrarySendEth {
32 fn check_function(
33 &mut self,
34 ctx: &LintContext,
35 gcx: Gcx<'hir>,
36 hir: &'hir hir::Hir<'hir>,
37 func: &'hir hir::Function<'hir>,
38 ) {
39 if matches!(func.state_mutability, ast::StateMutability::Pure | ast::StateMutability::View)
40 || matches!(func.kind, FunctionKind::Constructor)
41 || func.contract.is_some_and(|cid| hir.contract(cid).kind == ContractKind::Library)
42 {
43 return;
44 }
45 let Some(body) = func.body else { return };
46 let mut entry = Analyzer::new(gcx, hir);
47 for m in func.modifiers {
48 for arg in m.args.exprs() {
49 let _ = entry.visit_expr(arg);
50 }
51 }
52 for span in &entry.hits {
53 ctx.emit(&ARBITRARY_SEND_ETH, *span);
54 }
55 let mut analyzer = Analyzer::new(gcx, hir);
56 for m in func.modifiers {
57 collect_modifier_safety(gcx, hir, m, &mut analyzer.safe_vars);
58 }
59 for stmt in body.stmts {
60 let _ = analyzer.visit_stmt(stmt);
61 if branch_always_exits(stmt) {
62 break;
63 }
64 }
65 if analyzer.hits.is_empty() {
66 return;
67 }
68 if func.modifiers.iter().any(|m| modifier_restricts_caller(gcx, hir, m)) {
69 return;
70 }
71 for span in analyzer.hits {
72 ctx.emit(&ARBITRARY_SEND_ETH, span);
73 }
74 }
75}
76
77struct Analyzer<'hir> {
78 gcx: Gcx<'hir>,
79 hir: &'hir hir::Hir<'hir>,
80 self_aliases: SelfAliasAnalysis<'hir>,
81 safe_vars: HashSet<hir::VariableId>,
83 safe_fn_ptrs: HashSet<hir::VariableId>,
85 caller_restricted: bool,
87 hits: Vec<Span>,
88}
89
90#[derive(Clone)]
91struct FlowState {
92 safe_vars: HashSet<hir::VariableId>,
93 safe_fn_ptrs: HashSet<hir::VariableId>,
94 caller_restricted: bool,
95}
96
97impl FlowState {
98 fn intersection(a: &Self, b: &Self) -> Self {
99 Self {
100 safe_vars: a.safe_vars.intersection(&b.safe_vars).copied().collect(),
101 safe_fn_ptrs: a.safe_fn_ptrs.intersection(&b.safe_fn_ptrs).copied().collect(),
102 caller_restricted: a.caller_restricted && b.caller_restricted,
103 }
104 }
105
106 fn intersection_all(mut states: impl Iterator<Item = Self>) -> Self {
107 let mut out = states.next().unwrap_or_else(|| Self {
108 safe_vars: HashSet::new(),
109 safe_fn_ptrs: HashSet::new(),
110 caller_restricted: false,
111 });
112 for state in states {
113 out = Self::intersection(&out, &state);
114 }
115 out
116 }
117}
118
119const HELPER_DEPTH: u8 = 3;
121
122const SELF_ALIAS_DEPTH: u8 = 8;
124
125impl<'hir> Analyzer<'hir> {
126 fn new(gcx: Gcx<'hir>, hir: &'hir hir::Hir<'hir>) -> Self {
127 Self {
128 gcx,
129 hir,
130 self_aliases: SelfAliasAnalysis::new(gcx, hir),
131 safe_vars: HashSet::new(),
132 safe_fn_ptrs: HashSet::new(),
133 caller_restricted: false,
134 hits: Vec::new(),
135 }
136 }
137
138 fn snapshot(&self) -> FlowState {
139 FlowState {
140 safe_vars: self.safe_vars.clone(),
141 safe_fn_ptrs: self.safe_fn_ptrs.clone(),
142 caller_restricted: self.caller_restricted,
143 }
144 }
145
146 fn restore(&mut self, state: FlowState) {
147 self.safe_vars = state.safe_vars;
148 self.safe_fn_ptrs = state.safe_fn_ptrs;
149 self.caller_restricted = state.caller_restricted;
150 }
151
152 fn is_safe(&self, expr: &'hir hir::Expr<'hir>) -> bool {
153 self.is_safe_inner(expr, HELPER_DEPTH)
154 }
155
156 fn is_safe_inner(&self, expr: &'hir hir::Expr<'hir>, depth: u8) -> bool {
157 match &expr.peel_parens().kind {
158 ExprKind::Member(base, ident) if ident.name == sym::sender => {
159 is_builtin(base, sym::msg)
160 }
161 ExprKind::Member(base, ident) if ident.name == kw::Origin => is_builtin(base, sym::tx),
162 ExprKind::Ident(_) if is_builtin(expr, sym::this) => true,
163 ExprKind::Lit(lit) => match &lit.kind {
165 LitKind::Address(_) => true,
166 LitKind::Number(n) => n.is_zero(),
167 _ => false,
168 },
169 ExprKind::Ident(reses) => reses.iter().any(|r| match r {
170 Res::Item(ItemId::Variable(vid)) => self.is_safe_var(*vid),
171 _ => false,
172 }),
173 ExprKind::Call(callee, args, _)
175 if is_address_like_cast_callee(callee) || is_numeric_cast_callee(callee) =>
176 {
177 args.exprs().next().is_some_and(|e| self.is_safe_inner(e, depth))
178 }
179 ExprKind::Payable(inner) => self.is_safe_inner(inner, depth),
180 ExprKind::Ternary(_, t, f) => {
181 self.is_safe_inner(t, depth) && self.is_safe_inner(f, depth)
182 }
183 ExprKind::Call(callee, args, _)
184 if depth > 0
185 && args.exprs().next().is_none()
186 && callee_no_arg_returns(self.hir, callee, |e| {
187 self.is_safe_inner(e, depth - 1)
188 }) =>
189 {
190 true
191 }
192 _ => false,
193 }
194 }
195
196 fn is_safe_var(&self, vid: hir::VariableId) -> bool {
199 if self.safe_vars.contains(&vid) {
200 return true;
201 }
202 let var = self.hir.variable(vid);
203 var.kind.is_state() && (var.is_immutable() || var.is_constant()) && var_is_address_like(var)
204 }
205
206 fn assign(&mut self, target: hir::VariableId, rhs: &'hir hir::Expr<'hir>) {
208 if self.is_safe(rhs) {
209 self.safe_vars.insert(target);
210 } else {
211 self.safe_vars.remove(&target);
212 }
213 }
214
215 fn handle_assign(&mut self, lhs: &hir::Expr<'_>, rhs: &'hir hir::Expr<'hir>) {
217 let lhs = lhs.peel_parens();
218 if let ExprKind::Tuple(lhs_elems) = &lhs.kind {
219 let rhs_elems = tuple_elems(rhs);
220 for (i, lhs_elem) in lhs_elems.iter().enumerate() {
221 if let Some(lhs_expr) = lhs_elem {
222 self.assign_one(lhs_expr, tuple_slot(rhs_elems, i));
223 }
224 }
225 } else {
226 self.assign_one(lhs, Some(rhs));
227 }
228 }
229
230 fn assign_one(&mut self, lhs: &hir::Expr<'_>, rhs: Option<&'hir hir::Expr<'hir>>) {
232 let Some(target) = underlying_var(lhs) else { return };
233 self.safe_vars.remove(&target);
234 self.safe_fn_ptrs.remove(&target);
235 if self.hir.variable(target).kind.is_state() {
236 return;
237 }
238 if matches!(self.hir.variable(target).ty.kind, TypeKind::Function(_)) {
239 if rhs.is_some_and(|r| self.is_fn_ptr_safe_rhs(r)) {
240 self.safe_fn_ptrs.insert(target);
241 }
242 return;
243 }
244 if rhs.is_some_and(|r| self.is_safe(r)) {
245 self.safe_vars.insert(target);
246 }
247 }
248
249 fn is_fn_ptr_safe_rhs(&self, expr: &hir::Expr<'_>) -> bool {
251 match &expr.peel_parens().kind {
252 ExprKind::Member(base, _) => is_address_self(base),
253 ExprKind::Ident(reses) => reses.iter().any(|r| {
254 matches!(r, Res::Item(ItemId::Variable(vid)) if self.safe_fn_ptrs.contains(vid))
255 }),
256 ExprKind::Ternary(_, t, f) => self.is_fn_ptr_safe_rhs(t) && self.is_fn_ptr_safe_rhs(f),
257 _ => false,
258 }
259 }
260
261 fn fn_ptr_call_routes_to_self(&self, expr: &'hir hir::Expr<'hir>) -> bool {
263 let ExprKind::Call(callee, _, _) = &expr.kind else { return false };
264 let callee_inner = callee.peel_parens();
265 let is_fn_ptr = match &callee_inner.kind {
266 ExprKind::Ident(reses) => reses.iter().any(|r| {
267 matches!(r, Res::Item(ItemId::Variable(vid))
268 if matches!(self.hir.variable(*vid).ty.kind, TypeKind::Function(_)))
269 }),
270 _ => expr_is_function(self.gcx, callee_inner),
271 };
272 is_fn_ptr && self.is_fn_ptr_safe_rhs(callee_inner)
273 }
274
275 fn add_facts(&mut self, pred: &'hir hir::Expr<'hir>, negate: bool) {
277 match &pred.peel_parens().kind {
278 ExprKind::Binary(lhs, op, rhs) => {
279 let (eq, and_op, or_op) = if negate {
280 (ast::BinOpKind::Ne, ast::BinOpKind::Or, ast::BinOpKind::And)
281 } else {
282 (ast::BinOpKind::Eq, ast::BinOpKind::And, ast::BinOpKind::Or)
283 };
284 if op.kind == and_op {
285 self.add_facts(lhs, negate);
286 self.add_facts(rhs, negate);
287 } else if op.kind == or_op {
288 self.add_facts_disjunction(lhs, rhs, negate);
289 } else if op.kind == eq {
290 for (a, b) in [(lhs, rhs), (rhs, lhs)] {
291 if self.is_safe(a)
292 && let Some(v) = underlying_var(b)
293 && self.is_safe_target(v)
294 {
295 self.safe_vars.insert(v);
296 }
297 }
298 }
299 }
300 ExprKind::Unary(op, inner) if matches!(op.kind, ast::UnOpKind::Not) => {
301 self.add_facts(inner, !negate);
302 }
303 _ => {}
304 }
305 }
306
307 fn add_facts_disjunction(
309 &mut self,
310 lhs: &'hir hir::Expr<'hir>,
311 rhs: &'hir hir::Expr<'hir>,
312 negate: bool,
313 ) {
314 let baseline = self.safe_vars.clone();
315 self.add_facts(lhs, negate);
316 let lhs_added: HashSet<_> = self.safe_vars.difference(&baseline).copied().collect();
317 self.safe_vars.clone_from(&baseline);
318 self.add_facts(rhs, negate);
319 let rhs_added: HashSet<_> = self.safe_vars.difference(&baseline).copied().collect();
320 self.safe_vars = baseline;
321 for v in lhs_added.intersection(&rhs_added) {
322 self.safe_vars.insert(*v);
323 }
324 }
325
326 fn is_safe_target(&self, v: hir::VariableId) -> bool {
328 let var = self.hir.variable(v);
329 !var.kind.is_state() || var.is_immutable() || var.is_constant()
330 }
331
332 fn visit_isolated(&mut self, stmts: &'hir [hir::Stmt<'hir>]) {
334 let mut exits = vec![self.snapshot()];
335 if let Some(fallthrough) = self.visit_stmts_until_loop_exit(stmts, &mut exits) {
336 exits.push(fallthrough);
337 }
338 self.restore(FlowState::intersection_all(exits.into_iter()));
339 }
340
341 fn visit_stmts_until_loop_exit(
342 &mut self,
343 stmts: &'hir [hir::Stmt<'hir>],
344 exits: &mut Vec<FlowState>,
345 ) -> Option<FlowState> {
346 for stmt in stmts {
347 self.visit_stmt_until_loop_exit(stmt, exits)?;
348 }
349 Some(self.snapshot())
350 }
351
352 fn visit_stmt_until_loop_exit(
353 &mut self,
354 stmt: &'hir hir::Stmt<'hir>,
355 exits: &mut Vec<FlowState>,
356 ) -> Option<()> {
357 match &stmt.kind {
358 StmtKind::Break | StmtKind::Continue => {
359 exits.push(self.snapshot());
360 None
361 }
362 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
363 let state = self.visit_stmts_until_loop_exit(block.stmts, exits)?;
364 self.restore(state);
365 Some(())
366 }
367 StmtKind::If(cond, then, else_) => {
368 let _ = self.visit_expr(cond);
369 let baseline = self.snapshot();
370 self.add_facts(cond, false);
371 let then_fallthrough = self
372 .visit_stmt_until_loop_exit(then, exits)
373 .and_then(|_| (!branch_always_exits(then)).then(|| self.snapshot()));
374 self.restore(baseline);
375 self.add_facts(cond, true);
376 let else_fallthrough = match else_ {
377 Some(else_stmt) => self
378 .visit_stmt_until_loop_exit(else_stmt, exits)
379 .and_then(|_| (!branch_always_exits(else_stmt)).then(|| self.snapshot())),
380 None => Some(self.snapshot()),
381 };
382 match (then_fallthrough, else_fallthrough) {
383 (Some(then_state), Some(else_state)) => {
384 self.restore(FlowState::intersection(&then_state, &else_state));
385 Some(())
386 }
387 (Some(state), None) | (None, Some(state)) => {
388 self.restore(state);
389 Some(())
390 }
391 (None, None) => None,
392 }
393 }
394 StmtKind::Loop(..) => {
395 let _ = self.visit_stmt(stmt);
396 Some(())
397 }
398 _ => {
399 let _ = self.visit_stmt(stmt);
400 (!branch_always_exits(stmt)).then_some(())
401 }
402 }
403 }
404}
405
406impl<'hir> hir::Visit<'hir> for Analyzer<'hir> {
407 type BreakValue = Never;
408
409 fn hir(&self) -> &'hir hir::Hir<'hir> {
410 self.hir
411 }
412
413 fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'hir>) -> ControlFlow<Self::BreakValue> {
414 match &stmt.kind {
415 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
416 for s in block.stmts {
417 let _ = self.visit_stmt(s);
418 if branch_always_exits(s) {
419 break;
420 }
421 }
422 return ControlFlow::Continue(());
423 }
424 StmtKind::If(cond, then, else_) => {
425 let _ = self.visit_expr(cond);
426 let baseline = self.snapshot();
427 self.add_facts(cond, false);
428 if cond_restricts_caller(self.hir, cond, true, &[], &mut self.self_aliases) {
429 self.caller_restricted = true;
430 }
431 let _ = self.visit_stmt(then);
432 let then_exits = branch_always_exits(then);
433 let after_then = self.snapshot();
434 self.restore(baseline);
435 self.add_facts(cond, true);
436 if cond_restricts_caller(self.hir, cond, false, &[], &mut self.self_aliases) {
437 self.caller_restricted = true;
438 }
439 let else_exits = match else_ {
440 Some(e) => {
441 let _ = self.visit_stmt(e);
442 branch_always_exits(e)
443 }
444 None => false,
445 };
446 let after_else = self.snapshot();
447 let joined = match (then_exits, else_exits) {
450 (true, false) => after_else,
451 (false, true) => after_then,
452 _ => FlowState::intersection(&after_then, &after_else),
453 };
454 self.restore(joined);
455 return ControlFlow::Continue(());
456 }
457 StmtKind::Loop(block, source) => {
458 if matches!(source, LoopSource::DoWhile)
459 && !do_while_user_stmts(block.stmts).iter().any(stmt_has_break_or_continue)
460 {
461 for s in block.stmts {
462 let _ = self.visit_stmt(s);
463 }
464 } else {
465 self.visit_isolated(block.stmts);
466 }
467 return ControlFlow::Continue(());
468 }
469 StmtKind::Try(t) => {
470 let _ = self.visit_expr(&t.expr);
471 let outer = self.snapshot();
472 let mut clause_exits = Vec::new();
473 for clause in t.clauses {
474 self.restore(outer.clone());
475 let mut exited = false;
476 for stmt in clause.block.stmts {
477 let _ = self.visit_stmt(stmt);
478 if branch_always_exits(stmt) {
479 exited = true;
480 break;
481 }
482 }
483 if !exited {
484 clause_exits.push(self.snapshot());
485 }
486 }
487 self.restore(
488 clause_exits
489 .into_iter()
490 .reduce(|a, b| FlowState::intersection(&a, &b))
491 .unwrap_or(outer),
492 );
493 return ControlFlow::Continue(());
494 }
495 StmtKind::Err(_) => {
496 self.safe_vars.clear();
497 }
498 StmtKind::DeclSingle(vid) => {
499 let var = self.hir.variable(*vid);
500 if var_is_address_like(var)
501 && let Some(init) = var.initializer
502 {
503 self.assign(*vid, init);
504 } else if matches!(var.ty.kind, TypeKind::Function(_)) {
505 if var.initializer.is_some_and(|init| self.is_fn_ptr_safe_rhs(init)) {
506 self.safe_fn_ptrs.insert(*vid);
507 } else {
508 self.safe_fn_ptrs.remove(vid);
509 }
510 }
511 }
512 StmtKind::DeclMulti(vars, init) => {
513 if let ExprKind::Tuple(rhs) = &init.peel_parens().kind {
514 for (lhs, rhs) in vars.iter().zip(rhs.iter()) {
515 let (Some(vid), Some(expr)) = (lhs, rhs) else { continue };
516 let var = self.hir.variable(*vid);
517 if var_is_address_like(var) {
518 self.assign(*vid, expr);
519 } else if matches!(var.ty.kind, TypeKind::Function(_)) {
520 if self.is_fn_ptr_safe_rhs(expr) {
521 self.safe_fn_ptrs.insert(*vid);
522 } else {
523 self.safe_fn_ptrs.remove(vid);
524 }
525 }
526 }
527 }
528 }
529 _ => {}
530 }
531 self.walk_stmt(stmt)
532 }
533
534 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow<Self::BreakValue> {
535 if let ExprKind::Binary(lhs, op, rhs) = &expr.kind
536 && matches!(op.kind, ast::BinOpKind::And | ast::BinOpKind::Or)
537 {
538 let _ = self.visit_expr(lhs);
539 let negate = matches!(op.kind, ast::BinOpKind::Or);
540 let skipped_rhs = self.snapshot();
541 self.add_facts(lhs, negate);
542 let result = self.visit_expr(rhs);
543 let ran_rhs = self.snapshot();
544 self.restore(FlowState::intersection(&skipped_rhs, &ran_rhs));
545 return result;
546 }
547 if let ExprKind::Ternary(cond, then_e, else_e) = &expr.kind {
548 let _ = self.visit_expr(cond);
549 let pre_arm = self.snapshot();
550 self.add_facts(cond, false);
551 let _ = self.visit_expr(then_e);
552 let post_then = self.snapshot();
553 self.restore(pre_arm);
554 self.add_facts(cond, true);
555 let _ = self.visit_expr(else_e);
556 let post_else = self.snapshot();
557 self.restore(FlowState::intersection(&post_then, &post_else));
558 return ControlFlow::Continue(());
559 }
560 match &expr.kind {
561 ExprKind::Call(callee, args, _) if is_require_or_assert(callee) => {
562 let result = self.walk_expr(expr);
563 if let Some(cond) = args.exprs().next() {
564 self.add_facts(cond, false);
565 if cond_restricts_caller(self.hir, cond, true, &[], &mut self.self_aliases) {
566 self.caller_restricted = true;
567 }
568 }
569 return result;
570 }
571 ExprKind::Call(..) => {
572 if !self.caller_restricted
573 && let Some(dest) = match_sink(self.gcx, self.hir, expr)
574 && !self.is_safe(dest)
575 && !self.fn_ptr_call_routes_to_self(expr)
576 {
577 self.hits.push(expr.span);
578 }
579 }
580 ExprKind::Assign(lhs, _, rhs) => self.handle_assign(lhs, rhs),
581 ExprKind::Delete(target) => self.assign_one(target.peel_parens(), None),
582 _ => {}
583 }
584 self.walk_expr(expr)
585 }
586}
587
588fn match_sink<'hir>(
590 gcx: Gcx<'hir>,
591 hir: &'hir hir::Hir<'hir>,
592 expr: &'hir hir::Expr<'hir>,
593) -> Option<&'hir hir::Expr<'hir>> {
594 let ExprKind::Call(callee, args, opts) = &expr.kind else { return None };
595 if let ExprKind::Ident(reses) = &callee.peel_parens().kind
596 && reses.iter().any(|r| matches!(r, Res::Builtin(Builtin::Selfdestruct)))
597 {
598 let dest = args.exprs().next()?;
599 if is_address_self(dest) {
600 return None;
601 }
602 return Some(dest);
603 }
604
605 if let Some(opts) = opts
606 && opts.args.iter().any(|arg| arg.name.name == sym::value && !is_literal_zero(&arg.value))
607 {
608 let callee_inner = callee.peel_parens();
609 match &callee_inner.kind {
610 ExprKind::Member(recv, _) if !is_address_self(recv) => return Some(recv),
611 ExprKind::Ident(reses)
612 if reses.iter().any(|r| {
613 matches!(
614 r,
615 Res::Item(ItemId::Variable(vid))
616 if matches!(hir.variable(*vid).ty.kind, TypeKind::Function(_))
617 )
618 }) =>
619 {
620 return Some(callee);
621 }
622 _ if expr_is_function(gcx, callee_inner) => {
623 return Some(callee);
624 }
625 _ => {}
626 }
627 }
628
629 let ExprKind::Member(recv, member) = &callee.peel_parens().kind else { return None };
630 if matches!(member.name, sym::transfer | sym::send)
631 && args.len() == 1
632 && receiver_is_address(gcx, recv)
633 && !is_address_self(recv)
634 {
635 let amt = args.exprs().next()?;
636 if !is_literal_zero(amt) {
637 return Some(recv);
638 }
639 }
640 match_eth_library_call(gcx, hir, recv, member.name, args)
641}
642
643fn match_eth_library_call<'hir>(
645 gcx: Gcx<'hir>,
646 hir: &'hir hir::Hir<'hir>,
647 recv: &'hir hir::Expr<'hir>,
648 member: Symbol,
649 args: &'hir CallArgs<'hir>,
650) -> Option<&'hir hir::Expr<'hir>> {
651 let n = args.len();
652 let using = receiver_is_address(gcx, recv);
653 let recv_is_lib = matches!(&recv.peel_parens().kind, ExprKind::Ident(reses)
654 if reses.iter().any(|r| matches!(
655 r,
656 Res::Item(ItemId::Contract(cid))
657 if hir.contract(*cid).kind == ContractKind::Library
658 )));
659 if !using && !recv_is_lib {
660 return None;
661 }
662 let name = member.as_str();
663 let valid = match name {
664 "sendValue" | "safeTransferETH" | "safeMoveETH" => (using && n == 1) || (!using && n == 2),
665 "forceSafeTransferETH" => (using && matches!(n, 1 | 2)) || (!using && matches!(n, 2 | 3)),
666 "trySafeTransferETH" => (using && n == 2) || (!using && n == 3),
667 "functionCallWithValue" => (using && matches!(n, 2 | 3)) || (!using && matches!(n, 3 | 4)),
668 "safeTransferAllETH" => (using && n == 0) || (!using && n == 1),
669 "forceSafeTransferAllETH" => {
670 (using && matches!(n, 0 | 1)) || (!using && matches!(n, 1 | 2))
671 }
672 "trySafeTransferAllETH" => (using && n == 1) || (!using && n == 2),
673 _ => false,
674 };
675
676 if !valid {
677 return None;
678 }
679
680 let dest = if using { recv } else { arg(args, 0, &["to", "target", "recipient"])? };
681 let amount = match name {
682 "safeTransferAllETH" | "forceSafeTransferAllETH" | "trySafeTransferAllETH" => None,
683 "functionCallWithValue" => {
684 Some(arg(args, if using { 1 } else { 2 }, &["value", "amount"])?)
685 }
686 _ => Some(arg(args, if using { 0 } else { 1 }, &["amount", "value"])?),
687 };
688 if amount.is_some_and(is_literal_zero) || is_address_self(dest) {
689 return None;
690 }
691 Some(dest)
692}
693
694fn arg<'hir>(
696 args: &'hir CallArgs<'hir>,
697 pos: usize,
698 names: &[&str],
699) -> Option<&'hir hir::Expr<'hir>> {
700 match args.kind {
701 hir::CallArgsKind::Unnamed(exprs) => exprs.get(pos),
702 hir::CallArgsKind::Named(named) => {
703 named.iter().find(|a| names.iter().any(|n| a.name.as_str() == *n)).map(|a| &a.value)
704 }
705 }
706}
707
708fn modifier_restricts_caller<'hir>(
710 gcx: Gcx<'hir>,
711 hir: &'hir hir::Hir<'hir>,
712 invocation: &hir::Modifier<'_>,
713) -> bool {
714 let ItemId::Function(fid) = invocation.id else { return false };
715 let mut self_aliases = SelfAliasAnalysis::new(gcx, hir);
716 modifier_function_restricts_caller(hir, fid, &mut Vec::new(), &mut self_aliases)
717}
718
719fn invoked_function(hir: &hir::Hir<'_>, invocation: &hir::Modifier<'_>) -> Option<FunctionId> {
721 match invocation.id {
722 ItemId::Function(fid) => Some(fid),
723 ItemId::Contract(cid) => hir.contract(cid).ctor,
724 _ => None,
725 }
726}
727
728fn modifier_function_restricts_caller<'hir>(
729 hir: &'hir hir::Hir<'hir>,
730 fid: FunctionId,
731 stack: &mut Vec<FunctionId>,
732 self_aliases: &mut SelfAliasAnalysis<'hir>,
733) -> bool {
734 if stack.contains(&fid) {
735 return false;
736 }
737 let Some((modifier, prefix)) = modifier_prefix(hir, fid) else { return false };
738 stack.push(fid);
739 let restricts = prefix
740 .iter()
741 .any(|s| stmt_restricts_caller(hir, s, modifier.parameters, stack, self_aliases));
742 stack.pop();
743 restricts
744}
745
746fn modifier_prefix<'hir>(
749 hir: &'hir hir::Hir<'hir>,
750 fid: FunctionId,
751) -> Option<(&'hir hir::Function<'hir>, Vec<&'hir hir::Stmt<'hir>>)> {
752 let modifier = hir.function(fid);
753 if !matches!(modifier.kind, FunctionKind::Modifier) {
754 return None;
755 }
756 let body = modifier.body?;
757 if count_placeholders(body.stmts) != 1 {
758 return None;
759 }
760 let mut prefix = Vec::new();
761 collect_stmts_before_placeholder(body.stmts, &mut prefix)?;
762 Some((modifier, prefix))
763}
764
765fn stmt_restricts_caller<'hir>(
766 hir: &'hir hir::Hir<'hir>,
767 stmt: &'hir hir::Stmt<'hir>,
768 params: &[hir::VariableId],
769 stack: &mut Vec<FunctionId>,
770 self_aliases: &mut SelfAliasAnalysis<'hir>,
771) -> bool {
772 match &stmt.kind {
773 StmtKind::Expr(e) => expr_restricts_caller(hir, e, params, stack, self_aliases),
774 StmtKind::If(cond, then, else_) => {
775 let then_exits = branch_always_exits(then);
776 let else_exits = else_.as_ref().is_some_and(|e| branch_always_exits(e));
777 let by_if_revert = match (then_exits, else_exits) {
778 (true, false) => cond_restricts_caller(hir, cond, false, params, self_aliases),
779 (false, true) => cond_restricts_caller(hir, cond, true, params, self_aliases),
780 _ => false,
781 };
782 if by_if_revert {
783 return true;
784 }
785 let then_restricts = stmt_restricts_caller(hir, then, params, stack, self_aliases);
786 let else_restricts = else_
787 .as_ref()
788 .is_some_and(|e| stmt_restricts_caller(hir, e, params, stack, self_aliases));
789 match (then_exits, else_exits) {
790 (true, true) => true,
791 (true, false) => else_.is_some() && else_restricts,
792 (false, true) => then_restricts,
793 (false, false) => then_restricts && else_.is_some() && else_restricts,
794 }
795 }
796 StmtKind::Block(b) | StmtKind::UncheckedBlock(b) => {
797 b.stmts.iter().any(|s| stmt_restricts_caller(hir, s, params, stack, self_aliases))
798 }
799 _ => false,
800 }
801}
802
803fn expr_restricts_caller<'hir>(
804 hir: &'hir hir::Hir<'hir>,
805 expr: &'hir hir::Expr<'hir>,
806 params: &[hir::VariableId],
807 stack: &mut Vec<FunctionId>,
808 self_aliases: &mut SelfAliasAnalysis<'hir>,
809) -> bool {
810 let ExprKind::Call(callee, args, _) = &expr.peel_parens().kind else { return false };
811 if is_require_or_assert(callee) {
812 return args
813 .exprs()
814 .next()
815 .is_some_and(|c| cond_restricts_caller(hir, c, true, params, self_aliases));
816 }
817 let ExprKind::Ident(reses) = &callee.peel_parens().kind else { return false };
818 reses.iter().any(|r| match r {
819 Res::Item(ItemId::Function(fid)) => {
820 if stack.contains(fid) {
821 return false;
822 }
823 let f = hir.function(*fid);
824 let Some(body) = f.body else { return false };
825 let mut stmts = body.stmts;
827 while let Some((last, init)) = stmts.split_last() {
828 if matches!(last.kind, StmtKind::Return(None)) {
829 stmts = init;
830 } else {
831 break;
832 }
833 }
834 if stmts.iter().any(stmt_contains_return) {
835 return false;
836 }
837 stack.push(*fid);
838 let r = stmts
839 .iter()
840 .any(|s| stmt_restricts_caller(hir, s, f.parameters, stack, self_aliases));
841 stack.pop();
842 r
843 }
844 _ => false,
845 })
846}
847
848fn stmt_contains_return(stmt: &hir::Stmt<'_>) -> bool {
851 match &stmt.kind {
852 StmtKind::Return(_) => true,
853 StmtKind::Block(b) | StmtKind::UncheckedBlock(b) | StmtKind::Loop(b, _) => {
854 b.stmts.iter().any(stmt_contains_return)
855 }
856 StmtKind::If(_, t, e) => {
857 stmt_contains_return(t) || e.as_ref().is_some_and(|s| stmt_contains_return(s))
858 }
859 StmtKind::Try(t) => {
860 t.clauses.iter().any(|c| c.block.stmts.iter().any(stmt_contains_return))
861 }
862 _ => false,
863 }
864}
865
866fn cond_restricts_caller<'hir>(
868 hir: &'hir hir::Hir<'hir>,
869 cond: &'hir hir::Expr<'hir>,
870 polarity: bool,
871 params: &[hir::VariableId],
872 self_aliases: &mut SelfAliasAnalysis<'hir>,
873) -> bool {
874 match &cond.peel_parens().kind {
875 ExprKind::Binary(lhs, op, rhs) => {
876 let (eq, any_op, all_op) = if polarity {
877 (ast::BinOpKind::Eq, ast::BinOpKind::And, ast::BinOpKind::Or)
878 } else {
879 (ast::BinOpKind::Ne, ast::BinOpKind::Or, ast::BinOpKind::And)
880 };
881 if op.kind == any_op {
882 cond_restricts_caller(hir, lhs, polarity, params, self_aliases)
883 || cond_restricts_caller(hir, rhs, polarity, params, self_aliases)
884 } else if op.kind == all_op {
885 cond_restricts_caller(hir, lhs, polarity, params, self_aliases)
886 && cond_restricts_caller(hir, rhs, polarity, params, self_aliases)
887 } else if op.kind == eq {
888 let mut pair = |a: &'hir hir::Expr<'hir>, b: &'hir hir::Expr<'hir>| {
889 is_msg_sender_like(hir, a, HELPER_DEPTH)
890 && is_trusted_principal_inner(hir, b, params, HELPER_DEPTH, self_aliases)
891 };
892 pair(lhs, rhs) || pair(rhs, lhs)
893 } else {
894 false
895 }
896 }
897 ExprKind::Unary(op, inner) if matches!(op.kind, ast::UnOpKind::Not) => {
898 cond_restricts_caller(hir, inner, !polarity, params, self_aliases)
899 }
900 _ => false,
901 }
902}
903
904fn is_msg_sender_like<'hir>(
906 hir: &'hir hir::Hir<'hir>,
907 expr: &'hir hir::Expr<'hir>,
908 depth: u8,
909) -> bool {
910 is_caller_like(hir, expr, depth, sym::msg, sym::sender)
911}
912
913fn is_tx_origin_like<'hir>(
915 hir: &'hir hir::Hir<'hir>,
916 expr: &'hir hir::Expr<'hir>,
917 depth: u8,
918) -> bool {
919 is_caller_like(hir, expr, depth, sym::tx, kw::Origin)
920}
921
922fn callee_no_arg_returns<'hir>(
924 hir: &'hir hir::Hir<'hir>,
925 callee: &'hir hir::Expr<'hir>,
926 mut pred: impl FnMut(&'hir hir::Expr<'hir>) -> bool,
927) -> bool {
928 let ExprKind::Ident(reses) = &callee.peel_parens().kind else { return false };
929 reses.iter().any(|r| {
930 matches!(r, Res::Item(ItemId::Function(fid)) if function_no_arg_returns(hir, *fid, &mut pred))
931 })
932}
933
934fn function_no_arg_returns<'hir>(
937 hir: &'hir hir::Hir<'hir>,
938 fid: FunctionId,
939 pred: &mut impl FnMut(&'hir hir::Expr<'hir>) -> bool,
940) -> bool {
941 let f = hir.function(fid);
942 let Some(body) = f.body else { return false };
943 if !f.parameters.is_empty() {
944 return false;
945 }
946 let stmts: &[_] = match body.stmts.split_last() {
948 Some((last, rest)) if matches!(last.kind, StmtKind::Return(None)) => rest,
949 _ => body.stmts,
950 };
951 if stmts.len() != 1 {
952 return false;
953 }
954 match &stmts[0].kind {
955 StmtKind::Return(Some(e)) => pred(e),
956 StmtKind::Expr(e) => match &e.peel_parens().kind {
958 ExprKind::Assign(lhs, None, rhs) => {
959 f.returns.len() == 1
960 && underlying_var(lhs).is_some_and(|v| v == f.returns[0])
961 && pred(rhs)
962 }
963 _ => false,
964 },
965 _ => false,
966 }
967}
968
969fn is_caller_like<'hir>(
971 hir: &'hir hir::Hir<'hir>,
972 expr: &'hir hir::Expr<'hir>,
973 depth: u8,
974 ns: Symbol,
975 member: Symbol,
976) -> bool {
977 match &expr.peel_parens().kind {
978 ExprKind::Member(base, ident) if ident.name == member => is_builtin(base, ns),
979 ExprKind::Payable(inner) => is_caller_like(hir, inner, depth, ns, member),
980 ExprKind::Call(callee, args, _) if is_address_like_cast_callee(callee) => {
981 args.exprs().next().is_some_and(|e| is_caller_like(hir, e, depth, ns, member))
982 }
983 ExprKind::Call(callee, args, _) if depth > 0 && args.exprs().next().is_none() => {
984 callee_no_arg_returns(hir, callee, |e| is_caller_like(hir, e, depth - 1, ns, member))
985 }
986 _ => false,
987 }
988}
989
990fn is_trusted_principal_inner<'hir>(
992 hir: &'hir hir::Hir<'hir>,
993 expr: &'hir hir::Expr<'hir>,
994 params: &[hir::VariableId],
995 depth: u8,
996 self_aliases: &mut SelfAliasAnalysis<'hir>,
997) -> bool {
998 if expr_touches_param(expr, params)
999 || is_msg_sender_like(hir, expr, HELPER_DEPTH)
1000 || is_tx_origin_like(hir, expr, HELPER_DEPTH)
1001 || is_address_self(expr)
1002 {
1003 return false;
1004 }
1005 let expr = expr.peel_parens();
1006 match &expr.kind {
1007 ExprKind::Lit(lit) => match &lit.kind {
1008 LitKind::Address(_) => true,
1009 LitKind::Number(n) => n.is_zero(),
1010 _ => false,
1011 },
1012 ExprKind::Call(callee, args, _) if is_address_like_cast_callee(callee) => {
1013 args.exprs().next().is_some_and(|inner| match &inner.peel_parens().kind {
1014 ExprKind::Lit(lit) => match &lit.kind {
1016 LitKind::Address(_) => true,
1017 LitKind::Number(n) => n.is_zero(),
1018 _ => false,
1019 },
1020 _ => is_trusted_principal_inner(hir, inner, params, depth, self_aliases),
1021 })
1022 }
1023 ExprKind::Payable(inner) => {
1024 is_trusted_principal_inner(hir, inner, params, depth, self_aliases)
1025 }
1026 ExprKind::Ident(reses) => reses.iter().any(|r| match r {
1027 Res::Item(ItemId::Variable(vid)) => {
1028 let var = hir.variable(*vid);
1029 var.kind.is_state() && !self_aliases.state_var_aliases_self(*vid, SELF_ALIAS_DEPTH)
1030 }
1031 _ => false,
1032 }),
1033 ExprKind::Member(base, _) => {
1034 is_trusted_principal_inner(hir, base, params, depth, self_aliases)
1035 }
1036 ExprKind::Index(base, idx) => {
1037 is_trusted_principal_inner(hir, base, params, depth, self_aliases)
1038 && idx.is_none_or(|i| index_is_static(hir, i, params))
1039 }
1040 ExprKind::Call(callee, args, _) => {
1041 depth > 0
1042 && args.exprs().next().is_none()
1043 && callee_no_arg_returns(hir, callee, |e| {
1044 is_trusted_principal_inner(hir, e, &[], depth - 1, self_aliases)
1045 })
1046 }
1047 _ => false,
1048 }
1049}
1050
1051struct SelfAliasAnalysis<'hir> {
1053 gcx: Gcx<'hir>,
1054 hir: &'hir hir::Hir<'hir>,
1055 cache: HashMap<(hir::VariableId, u8), bool>,
1056 active: HashSet<(hir::VariableId, u8)>,
1057}
1058
1059impl<'hir> SelfAliasAnalysis<'hir> {
1060 fn new(gcx: Gcx<'hir>, hir: &'hir hir::Hir<'hir>) -> Self {
1061 Self { gcx, hir, cache: HashMap::new(), active: HashSet::new() }
1062 }
1063
1064 fn state_var_aliases_self(&mut self, vid: hir::VariableId, depth: u8) -> bool {
1066 if depth == 0 {
1067 return false;
1068 }
1069 let var = self.hir.variable(vid);
1070 if !var.kind.is_state() {
1071 return false;
1072 }
1073
1074 let key = (vid, depth);
1075 if let Some(result) = self.cache.get(&key) {
1076 return *result;
1077 }
1078 if !self.active.insert(key) {
1079 return false;
1080 }
1081
1082 let result = self.state_var_aliases_self_uncached(vid, depth);
1083 self.active.remove(&key);
1084 self.cache.insert(key, result);
1085 result
1086 }
1087
1088 fn state_var_aliases_self_uncached(&mut self, vid: hir::VariableId, depth: u8) -> bool {
1089 let var = self.hir.variable(vid);
1090 if let Some(init) = var.initializer {
1091 let initializer_aliases = if var_is_address_like(var) {
1092 self.expr_resolves_to_self(init, depth - 1)
1093 } else {
1094 self.expr_may_contain_self_in(init, depth - 1, &HashSet::new())
1095 };
1096 if initializer_aliases {
1097 return true;
1098 }
1099 }
1100 let Some(cid) = var.contract else { return false };
1101 if self.contract_function_assigns_to_self(cid, vid, depth - 1) {
1102 return true;
1103 }
1104 let derived_contracts: Vec<_> = self
1105 .hir
1106 .contracts_enumerated()
1107 .filter_map(|(other_cid, other)| {
1108 (other_cid != cid && other.linearized_bases.contains(&cid)).then_some(other_cid)
1109 })
1110 .collect();
1111 derived_contracts
1112 .into_iter()
1113 .any(|other_cid| self.contract_function_assigns_to_self(other_cid, vid, depth - 1))
1114 }
1115
1116 fn expr_may_contain_self_in(
1118 &mut self,
1119 expr: &'hir hir::Expr<'hir>,
1120 depth: u8,
1121 local_aliases: &HashSet<hir::VariableId>,
1122 ) -> bool {
1123 if self.expr_resolves_to_self(expr, depth) {
1124 return true;
1125 }
1126 if let Some(vid) = lhs_root_var(expr)
1127 && local_aliases.contains(&vid)
1128 {
1129 return true;
1130 }
1131 if depth == 0 {
1132 return false;
1133 }
1134 match &expr.peel_parens().kind {
1135 ExprKind::Payable(inner) => {
1136 self.expr_may_contain_self_in(inner, depth - 1, local_aliases)
1137 }
1138 ExprKind::Call(callee, args, _)
1139 if is_address_like_cast_callee(callee) || is_numeric_cast_callee(callee) =>
1140 {
1141 args.exprs()
1142 .next()
1143 .is_some_and(|e| self.expr_may_contain_self_in(e, depth - 1, local_aliases))
1144 }
1145 ExprKind::Call(_, args, _) => {
1146 args.exprs().any(|e| self.expr_may_contain_self_in(e, depth - 1, local_aliases))
1147 }
1148 ExprKind::Ternary(_, t, f) => {
1149 self.expr_may_contain_self_in(t, depth - 1, local_aliases)
1150 || self.expr_may_contain_self_in(f, depth - 1, local_aliases)
1151 }
1152 ExprKind::Tuple(elems) => elems
1153 .iter()
1154 .flatten()
1155 .any(|e| self.expr_may_contain_self_in(e, depth - 1, local_aliases)),
1156 ExprKind::Array(elems) => {
1157 elems.iter().any(|e| self.expr_may_contain_self_in(e, depth - 1, local_aliases))
1158 }
1159 _ => false,
1160 }
1161 }
1162
1163 fn expr_resolves_to_self(&mut self, expr: &'hir hir::Expr<'hir>, depth: u8) -> bool {
1165 if is_address_self(expr) {
1166 return true;
1167 }
1168 if depth == 0 {
1169 return false;
1170 }
1171 match &expr.peel_parens().kind {
1172 ExprKind::Payable(inner) => self.expr_resolves_to_self(inner, depth - 1),
1173 ExprKind::Call(callee, args, _)
1174 if is_address_like_cast_callee(callee) || is_numeric_cast_callee(callee) =>
1175 {
1176 args.exprs().next().is_some_and(|e| self.expr_resolves_to_self(e, depth - 1))
1177 }
1178 ExprKind::Ident(reses) => reses.iter().any(|r| match r {
1179 Res::Item(ItemId::Variable(other_vid)) => {
1180 self.state_var_aliases_self(*other_vid, depth)
1181 }
1182 _ => false,
1183 }),
1184 ExprKind::Member(_, _) | ExprKind::Index(_, _) => lhs_root_var(expr)
1185 .map(|vid| self.state_var_aliases_self(vid, depth))
1186 .unwrap_or(false),
1187 ExprKind::Call(callee, args, _) => {
1188 if args.exprs().count() == 0 {
1189 self.callee_returns_self(callee, depth - 1)
1190 } else if let Some(arg) = identity_helper_arg(self.hir, callee, args) {
1191 self.expr_resolves_to_self(arg, depth - 1)
1192 } else {
1193 false
1194 }
1195 }
1196 ExprKind::Ternary(_, t, f) => {
1197 self.expr_resolves_to_self(t, depth - 1) || self.expr_resolves_to_self(f, depth - 1)
1198 }
1199 ExprKind::Assign(_, _, rhs) => self.expr_resolves_to_self(rhs, depth - 1),
1200 _ => false,
1201 }
1202 }
1203
1204 fn callee_returns_self(&mut self, callee: &'hir hir::Expr<'hir>, depth: u8) -> bool {
1206 if callee_no_arg_returns(self.hir, callee, |e| self.expr_resolves_to_self(e, depth)) {
1207 return true;
1208 }
1209 let ExprKind::Member(base, member) = &callee.peel_parens().kind else { return false };
1210 let ExprKind::Ident(reses) = &base.peel_parens().kind else { return false };
1211 let Some(cid) = reses.iter().find_map(|r| match r {
1212 Res::Item(ItemId::Contract(cid))
1213 if self.hir.contract(*cid).kind == ContractKind::Library =>
1214 {
1215 Some(*cid)
1216 }
1217 _ => None,
1218 }) else {
1219 return false;
1220 };
1221 let contract_ids: Vec<_> = if self.hir.contract(cid).linearization_failed() {
1222 vec![cid]
1223 } else {
1224 self.hir.contract(cid).linearized_bases.to_vec()
1225 };
1226 for bid in contract_ids {
1227 let fids: Vec<_> = self.hir.contract(bid).all_functions().collect();
1228 for fid in fids {
1229 if self.hir.function(fid).name.is_none_or(|n| n.name != member.name) {
1230 continue;
1231 }
1232 if function_no_arg_returns(self.hir, fid, &mut |e| {
1233 self.expr_resolves_to_self(e, depth)
1234 }) {
1235 return true;
1236 }
1237 }
1238 }
1239 false
1240 }
1241
1242 fn contract_function_assigns_to_self(
1244 &mut self,
1245 cid: hir::ContractId,
1246 vid: hir::VariableId,
1247 depth: u8,
1248 ) -> bool {
1249 let fids: Vec<_> = self.hir.contract(cid).all_functions().collect();
1250 for fid in fids {
1251 let f = self.hir.function(fid);
1252 let Some(body) = f.body else { continue };
1253 let mut found = false;
1254 let mut scan = SelfAssignScan {
1255 hir: self.hir,
1256 aliases: self,
1257 target: vid,
1258 depth,
1259 found: &mut found,
1260 helper_stack: Vec::new(),
1261 local_self_aliases: HashSet::new(),
1262 };
1263 for inv in f.modifiers {
1264 if *scan.found {
1265 break;
1266 }
1267 if let Some(invoked_fid) = invoked_function(scan.hir, inv) {
1268 scan.scan_invoked(invoked_fid, &inv.args);
1269 }
1270 }
1271 if *scan.found {
1272 return true;
1273 }
1274 for stmt in body.stmts {
1275 if *scan.found {
1276 break;
1277 }
1278 let _ = scan.visit_stmt(stmt);
1279 }
1280 if found {
1281 return true;
1282 }
1283 }
1284 false
1285 }
1286}
1287
1288fn function_returns_param(hir: &hir::Hir<'_>, fid: FunctionId) -> Option<hir::VariableId> {
1290 let f = hir.function(fid);
1291 let body = f.body?;
1292 if body.stmts.len() != 1 || f.returns.len() != 1 {
1293 return None;
1294 }
1295 let StmtKind::Return(Some(ret)) = &body.stmts[0].kind else { return None };
1296 fn unwrap<'a>(e: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
1297 let e = e.peel_parens();
1298 match &e.kind {
1299 ExprKind::Payable(inner) => unwrap(inner),
1300 ExprKind::Call(callee, args, _)
1301 if is_address_like_cast_callee(callee) || is_numeric_cast_callee(callee) =>
1302 {
1303 args.exprs().next().map(unwrap).unwrap_or(e)
1304 }
1305 _ => e,
1306 }
1307 }
1308 let inner = unwrap(ret);
1309 let ExprKind::Ident(reses) = &inner.kind else { return None };
1310 for r in *reses {
1311 let Res::Item(ItemId::Variable(vid)) = r else { continue };
1312 if f.parameters.iter().any(|p| p == vid) {
1313 return Some(*vid);
1314 }
1315 }
1316 None
1317}
1318
1319fn identity_helper_arg<'hir>(
1321 hir: &'hir hir::Hir<'hir>,
1322 callee: &'hir hir::Expr<'hir>,
1323 args: &'hir hir::CallArgs<'hir>,
1324) -> Option<&'hir hir::Expr<'hir>> {
1325 let callee = callee.peel_parens();
1326 let call_arity = args.exprs().count();
1327 let try_fid = |fid: FunctionId| -> Option<&'hir hir::Expr<'hir>> {
1328 let f = hir.function(fid);
1329 if f.parameters.len() != call_arity {
1330 return None;
1331 }
1332 let param = function_returns_param(hir, fid)?;
1333 arg_for_param(hir, f, param, args)
1334 };
1335 match &callee.kind {
1336 ExprKind::Ident(reses) => reses.iter().find_map(|r| match r {
1337 Res::Item(ItemId::Function(fid)) => try_fid(*fid),
1338 _ => None,
1339 }),
1340 ExprKind::Member(base, member) => {
1341 let ExprKind::Ident(reses) = &base.peel_parens().kind else { return None };
1342 let cid = reses.iter().find_map(|r| match r {
1343 Res::Item(ItemId::Contract(cid)) => Some(*cid),
1344 _ => None,
1345 })?;
1346 if hir.contract(cid).kind != ContractKind::Library {
1347 return None;
1348 }
1349 find_in_bases_or_self(hir, cid, |bid| {
1350 hir.contract(bid).all_functions().find_map(|fid| {
1351 hir.function(fid)
1352 .name
1353 .is_some_and(|n| n.name == member.name)
1354 .then(|| try_fid(fid))
1355 .flatten()
1356 })
1357 })
1358 }
1359 _ => None,
1360 }
1361}
1362
1363fn arg_for_param<'hir>(
1365 hir: &'hir hir::Hir<'hir>,
1366 f: &hir::Function<'hir>,
1367 param: hir::VariableId,
1368 args: &'hir hir::CallArgs<'hir>,
1369) -> Option<&'hir hir::Expr<'hir>> {
1370 let param_idx = f.parameters.iter().position(|p| *p == param)?;
1371 match args.kind {
1372 hir::CallArgsKind::Unnamed(exprs) => exprs.get(param_idx),
1373 hir::CallArgsKind::Named(named) => {
1374 let pname = hir.variable(param).name?;
1375 named.iter().find(|a| a.name.name == pname.name).map(|a| &a.value)
1376 }
1377 }
1378}
1379
1380fn is_numeric_cast_callee(callee: &hir::Expr<'_>) -> bool {
1382 matches!(
1383 &callee.peel_parens().kind,
1384 ExprKind::Type(hir::Type {
1385 kind: TypeKind::Elementary(ElementaryType::UInt(_) | ElementaryType::Int(_)),
1386 ..
1387 })
1388 )
1389}
1390
1391const HELPER_CALL_DEPTH: u8 = 4;
1393
1394struct SelfAssignScan<'a, 'hir> {
1396 hir: &'hir hir::Hir<'hir>,
1397 aliases: &'a mut SelfAliasAnalysis<'hir>,
1398 target: hir::VariableId,
1399 depth: u8,
1400 found: &'a mut bool,
1401 helper_stack: Vec<FunctionId>,
1402 local_self_aliases: HashSet<hir::VariableId>,
1404}
1405
1406impl<'hir> SelfAssignScan<'_, 'hir> {
1407 fn expr_may_contain_self(&mut self, expr: &'hir hir::Expr<'hir>) -> bool {
1408 self.aliases.expr_may_contain_self_in(expr, self.depth, &self.local_self_aliases)
1409 }
1410
1411 fn lhs_aliases_target(
1413 &mut self,
1414 lhs: &'hir hir::Expr<'hir>,
1415 rhs: &'hir hir::Expr<'hir>,
1416 ) -> bool {
1417 let lhs = lhs.peel_parens();
1418 let rhs = rhs.peel_parens();
1419 if let ExprKind::Tuple(lhs_elems) = &lhs.kind {
1420 let rhs_elems = tuple_elems(rhs);
1421 return lhs_elems.iter().enumerate().any(|(i, lhs_elem)| {
1422 lhs_elem.is_some_and(|le| {
1423 tuple_slot(rhs_elems, i).is_some_and(|r| self.lhs_aliases_target(le, r))
1424 })
1425 });
1426 }
1427 if lhs_root_var(lhs) != Some(self.target) {
1428 return false;
1429 }
1430 let target = self.hir.variable(self.target);
1431 if var_is_address_like(target) {
1432 self.aliases.expr_resolves_to_self(rhs, self.depth)
1433 || lhs_root_var(rhs).is_some_and(|vid| self.local_self_aliases.contains(&vid))
1434 } else {
1435 self.expr_may_contain_self(rhs)
1436 }
1437 }
1438
1439 fn record_local_self_alias(&mut self, lhs: &hir::Expr<'_>, rhs: &'hir hir::Expr<'hir>) {
1441 let lhs = lhs.peel_parens();
1442 let rhs = rhs.peel_parens();
1443 if let ExprKind::Tuple(lhs_elems) = &lhs.kind {
1444 let rhs_elems = tuple_elems(rhs);
1445 for (i, lhs_elem) in lhs_elems.iter().enumerate() {
1446 if let Some(le) = lhs_elem
1447 && let Some(re) = tuple_slot(rhs_elems, i)
1448 {
1449 self.record_local_self_alias(le, re);
1450 }
1451 }
1452 return;
1453 }
1454 if let Some(vid) = lhs_root_var(lhs)
1455 && !self.hir.variable(vid).kind.is_state()
1456 && self.expr_may_contain_self(rhs)
1457 {
1458 self.local_self_aliases.insert(vid);
1459 }
1460 }
1461
1462 fn helper_callee(&self, callee: &hir::Expr<'_>) -> Option<FunctionId> {
1464 let ExprKind::Ident(reses) = &callee.peel_parens().kind else { return None };
1465 let mut fid_iter = reses.iter().filter_map(|r| match r {
1466 Res::Item(ItemId::Function(fid)) => Some(*fid),
1467 _ => None,
1468 });
1469 let fid = fid_iter.next()?;
1470 fid_iter.next().is_none().then_some(fid)
1471 }
1472
1473 fn seed_helper_param_aliases(
1475 &mut self,
1476 f: &hir::Function<'hir>,
1477 call_args: &'hir hir::CallArgs<'hir>,
1478 ) {
1479 for ¶m in f.parameters {
1480 if let Some(arg) = arg_for_param(self.hir, f, param, call_args)
1481 && self.expr_may_contain_self(arg)
1482 {
1483 self.local_self_aliases.insert(param);
1484 }
1485 }
1486 }
1487
1488 fn scan_invoked(&mut self, invoked_fid: FunctionId, inv_args: &'hir hir::CallArgs<'hir>) {
1490 if (self.helper_stack.len() as u8) >= HELPER_CALL_DEPTH
1491 || self.helper_stack.contains(&invoked_fid)
1492 {
1493 return;
1494 }
1495 let invoked = self.hir.function(invoked_fid);
1496 let Some(inv_body) = invoked.body else { return };
1497 let saved = self.local_self_aliases.clone();
1498 self.seed_helper_param_aliases(invoked, inv_args);
1499 self.helper_stack.push(invoked_fid);
1500 for inner in invoked.modifiers {
1501 if *self.found {
1502 break;
1503 }
1504 if let Some(inner_fid) = invoked_function(self.hir, inner) {
1505 self.scan_invoked(inner_fid, &inner.args);
1506 }
1507 }
1508 for stmt in inv_body.stmts {
1509 if *self.found {
1510 break;
1511 }
1512 let _ = self.visit_stmt(stmt);
1513 }
1514 self.helper_stack.pop();
1515 self.local_self_aliases = saved;
1516 }
1517}
1518
1519impl<'hir> hir::Visit<'hir> for SelfAssignScan<'_, 'hir> {
1520 type BreakValue = Never;
1521
1522 fn hir(&self) -> &'hir hir::Hir<'hir> {
1523 self.hir
1524 }
1525
1526 fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'hir>) -> ControlFlow<Self::BreakValue> {
1527 if *self.found {
1528 return ControlFlow::Continue(());
1529 }
1530 match &stmt.kind {
1531 StmtKind::DeclSingle(vid) => {
1532 let var = self.hir.variable(*vid);
1533 if !var.kind.is_state()
1534 && let Some(init) = var.initializer
1535 && self.expr_may_contain_self(init)
1536 {
1537 self.local_self_aliases.insert(*vid);
1538 }
1539 }
1540 StmtKind::DeclMulti(vars, init) => {
1541 if let ExprKind::Tuple(rhs) = &init.peel_parens().kind {
1542 for (lhs, rhs) in vars.iter().zip(rhs.iter()) {
1543 if let (Some(vid), Some(expr)) = (lhs, rhs)
1544 && !self.hir.variable(*vid).kind.is_state()
1545 && self.expr_may_contain_self(expr)
1546 {
1547 self.local_self_aliases.insert(*vid);
1548 }
1549 }
1550 }
1551 }
1552 _ => {}
1553 }
1554 self.walk_stmt(stmt)
1555 }
1556
1557 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow<Self::BreakValue> {
1558 if *self.found {
1559 return ControlFlow::Continue(());
1560 }
1561 if let ExprKind::Assign(lhs, _, rhs) = &expr.peel_parens().kind {
1562 self.record_local_self_alias(lhs, rhs);
1563 if self.lhs_aliases_target(lhs, rhs) {
1564 *self.found = true;
1565 return ControlFlow::Continue(());
1566 }
1567 }
1568 if let ExprKind::Call(callee, call_args, _) = &expr.peel_parens().kind
1569 && let ExprKind::Member(recv, member) = &callee.peel_parens().kind
1570 && member.name.as_str() == "push"
1571 && lhs_root_var(recv) == Some(self.target)
1572 && expr_is_array_or_bytes(self.aliases.gcx, recv)
1573 && call_args.exprs().any(|a| self.expr_may_contain_self(a))
1574 {
1575 *self.found = true;
1576 return ControlFlow::Continue(());
1577 }
1578 if let ExprKind::Call(callee, call_args, _) = &expr.peel_parens().kind
1579 && (self.helper_stack.len() as u8) < HELPER_CALL_DEPTH
1580 && let Some(fid) = self.helper_callee(callee)
1581 && !self.helper_stack.contains(&fid)
1582 {
1583 let f = self.hir.function(fid);
1584 if let Some(body) = f.body {
1585 let saved = self.local_self_aliases.clone();
1586 self.seed_helper_param_aliases(f, call_args);
1587 self.helper_stack.push(fid);
1588 for stmt in body.stmts {
1589 if *self.found {
1590 break;
1591 }
1592 let _ = self.visit_stmt(stmt);
1593 }
1594 self.helper_stack.pop();
1595 self.local_self_aliases = saved;
1596 }
1597 }
1598 self.walk_expr(expr)
1599 }
1600}
1601
1602fn tuple_elems<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir [Option<&'hir hir::Expr<'hir>>]> {
1605 match &expr.peel_parens().kind {
1606 ExprKind::Tuple(elems) => Some(*elems),
1607 _ => None,
1608 }
1609}
1610
1611fn tuple_slot<'hir>(
1613 elems: Option<&'hir [Option<&'hir hir::Expr<'hir>>]>,
1614 i: usize,
1615) -> Option<&'hir hir::Expr<'hir>> {
1616 elems.and_then(|e| e.get(i).copied()).flatten()
1617}
1618
1619fn find_in_bases_or_self<T>(
1622 hir: &hir::Hir<'_>,
1623 cid: hir::ContractId,
1624 mut f: impl FnMut(hir::ContractId) -> Option<T>,
1625) -> Option<T> {
1626 let contract = hir.contract(cid);
1627 if contract.linearization_failed() {
1628 f(cid)
1629 } else {
1630 contract.linearized_bases.iter().find_map(|&bid| f(bid))
1631 }
1632}
1633
1634fn lhs_root_var(lhs: &hir::Expr<'_>) -> Option<hir::VariableId> {
1636 match &lhs.peel_parens().kind {
1637 ExprKind::Ident(_) => underlying_var(lhs),
1638 ExprKind::Member(base, _) => lhs_root_var(base),
1639 ExprKind::Index(base, _) => lhs_root_var(base),
1640 ExprKind::Call(callee, args, _) if is_address_like_cast_callee(callee) => {
1641 args.exprs().next().and_then(lhs_root_var)
1642 }
1643 ExprKind::Payable(inner) => lhs_root_var(inner),
1644 _ => None,
1645 }
1646}
1647
1648fn index_is_static<'hir>(
1650 hir: &'hir hir::Hir<'hir>,
1651 expr: &'hir hir::Expr<'hir>,
1652 params: &[hir::VariableId],
1653) -> bool {
1654 fn walk<'hir>(
1655 hir: &'hir hir::Hir<'hir>,
1656 e: &'hir hir::Expr<'hir>,
1657 params: &[hir::VariableId],
1658 ) -> bool {
1659 if expr_touches_param(e, params)
1660 || is_msg_sender_like(hir, e, HELPER_DEPTH)
1661 || is_tx_origin_like(hir, e, HELPER_DEPTH)
1662 {
1663 return false;
1664 }
1665 match &e.peel_parens().kind {
1666 ExprKind::Lit(_) => true,
1667 ExprKind::Ident(reses) => reses.iter().all(|r| match r {
1668 Res::Item(ItemId::Variable(vid)) => hir.variable(*vid).kind.is_state(),
1669 Res::Builtin(_) => false,
1670 _ => true,
1671 }),
1672 ExprKind::Payable(i) | ExprKind::Unary(_, i) => walk(hir, i, params),
1673 ExprKind::Binary(l, _, r) => walk(hir, l, params) && walk(hir, r, params),
1674 ExprKind::Member(base, _) => walk(hir, base, params),
1675 ExprKind::Index(base, idx) => {
1676 walk(hir, base, params) && idx.is_none_or(|i| walk(hir, i, params))
1677 }
1678 ExprKind::Ternary(c, t, f) => {
1679 walk(hir, c, params) && walk(hir, t, params) && walk(hir, f, params)
1680 }
1681 ExprKind::Call(callee, args, _) => {
1682 let callee_ok = match &callee.peel_parens().kind {
1683 ExprKind::Type(_) => true,
1684 ExprKind::Ident(reses) => {
1685 reses.iter().any(|r| matches!(r, Res::Item(ItemId::Contract(_))))
1686 }
1687 _ => false,
1688 };
1689 callee_ok && args.exprs().all(|a| walk(hir, a, params))
1690 }
1691 _ => false,
1692 }
1693 }
1694 walk(hir, expr, params)
1695}
1696
1697fn expr_touches_param(expr: &hir::Expr<'_>, params: &[hir::VariableId]) -> bool {
1699 match &expr.peel_parens().kind {
1700 ExprKind::Ident(reses) => reses
1701 .iter()
1702 .any(|r| matches!(r, Res::Item(ItemId::Variable(vid)) if params.contains(vid))),
1703 ExprKind::Binary(l, _, r) | ExprKind::Assign(l, _, r) => {
1704 expr_touches_param(l, params) || expr_touches_param(r, params)
1705 }
1706 ExprKind::Unary(_, i)
1707 | ExprKind::Payable(i)
1708 | ExprKind::Delete(i)
1709 | ExprKind::Member(i, _) => expr_touches_param(i, params),
1710 ExprKind::Index(b, idx) => {
1711 expr_touches_param(b, params) || idx.is_some_and(|i| expr_touches_param(i, params))
1712 }
1713 ExprKind::Ternary(c, t, f) => {
1714 expr_touches_param(c, params)
1715 || expr_touches_param(t, params)
1716 || expr_touches_param(f, params)
1717 }
1718 ExprKind::Call(callee, args, _) => {
1719 expr_touches_param(callee, params)
1720 || args.exprs().any(|a| expr_touches_param(a, params))
1721 }
1722 _ => false,
1723 }
1724}
1725
1726fn collect_modifier_safety<'hir>(
1728 gcx: Gcx<'hir>,
1729 hir: &'hir hir::Hir<'hir>,
1730 invocation: &'hir hir::Modifier<'hir>,
1731 out_safe: &mut HashSet<hir::VariableId>,
1732) {
1733 let ItemId::Function(fid) = invocation.id else { return };
1734 let Some((modifier, prefix)) = modifier_prefix(hir, fid) else { return };
1735 let arg_map: Vec<(hir::VariableId, hir::VariableId)> = modifier
1736 .parameters
1737 .iter()
1738 .filter_map(|&mp| {
1739 let arg = arg_for_param(hir, modifier, mp, &invocation.args)?;
1740 Some((mp, underlying_var(arg)?))
1741 })
1742 .collect();
1743 if arg_map.is_empty() {
1744 return;
1745 }
1746 let mut assigned_params: HashSet<hir::VariableId> = HashSet::new();
1747 let mut collector = AssignedParamCollector { hir, out: &mut assigned_params };
1748 for stmt in &prefix {
1749 let _ = collector.visit_stmt(stmt);
1750 }
1751 let mut a = Analyzer::new(gcx, hir);
1752 for stmt in &prefix {
1753 let _ = a.visit_stmt(stmt);
1754 }
1755 for (mp, caller) in arg_map {
1756 if !assigned_params.contains(&mp) && a.safe_vars.contains(&mp) && a.is_safe_target(caller) {
1757 out_safe.insert(caller);
1758 }
1759 }
1760}
1761
1762fn collect_stmts_before_placeholder<'hir>(
1764 stmts: &'hir [hir::Stmt<'hir>],
1765 out: &mut Vec<&'hir hir::Stmt<'hir>>,
1766) -> Option<()> {
1767 for (i, stmt) in stmts.iter().enumerate() {
1768 match &stmt.kind {
1769 StmtKind::Placeholder => {
1770 out.extend(stmts[..i].iter());
1771 return Some(());
1772 }
1773 StmtKind::Block(b) | StmtKind::UncheckedBlock(b)
1774 if count_placeholders(b.stmts) >= 1 =>
1775 {
1776 out.extend(stmts[..i].iter());
1777 return collect_stmts_before_placeholder(b.stmts, out);
1778 }
1779 _ => {
1780 if count_placeholders_in_stmt(stmt) > 0 {
1781 return None;
1782 }
1783 }
1784 }
1785 }
1786 None
1787}
1788
1789struct AssignedParamCollector<'a, 'hir> {
1791 hir: &'hir hir::Hir<'hir>,
1792 out: &'a mut HashSet<hir::VariableId>,
1793}
1794
1795impl AssignedParamCollector<'_, '_> {
1796 fn add_lhs(&mut self, lhs: &hir::Expr<'_>) {
1797 match &lhs.peel_parens().kind {
1798 ExprKind::Tuple(elems) => {
1799 for e in elems.iter().flatten() {
1800 self.add_lhs(e);
1801 }
1802 }
1803 _ => {
1804 if let Some(vid) = underlying_var(lhs) {
1805 self.out.insert(vid);
1806 }
1807 }
1808 }
1809 }
1810}
1811
1812impl<'hir> hir::Visit<'hir> for AssignedParamCollector<'_, 'hir> {
1813 type BreakValue = Never;
1814 fn hir(&self) -> &'hir hir::Hir<'hir> {
1815 self.hir
1816 }
1817 fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> ControlFlow<Self::BreakValue> {
1818 match &expr.peel_parens().kind {
1819 ExprKind::Assign(lhs, _, _) => self.add_lhs(lhs),
1820 ExprKind::Delete(target) => self.add_lhs(target),
1821 _ => {}
1822 }
1823 self.walk_expr(expr)
1824 }
1825}
1826
1827fn do_while_user_stmts<'a, 'hir>(stmts: &'a [hir::Stmt<'hir>]) -> &'a [hir::Stmt<'hir>] {
1829 if let Some((last, rest)) = stmts.split_last()
1830 && let StmtKind::If(_, t, e) = &last.kind
1831 && (is_break_stmt(t) || e.as_ref().is_some_and(|e| is_break_stmt(e)))
1832 {
1833 return rest;
1834 }
1835 stmts
1836}
1837
1838fn is_break_stmt(stmt: &hir::Stmt<'_>) -> bool {
1839 match &stmt.kind {
1840 StmtKind::Break => true,
1841 StmtKind::Block(b) | StmtKind::UncheckedBlock(b) => {
1842 b.stmts.len() == 1 && is_break_stmt(&b.stmts[0])
1843 }
1844 _ => false,
1845 }
1846}
1847
1848fn stmt_has_break_or_continue(stmt: &hir::Stmt<'_>) -> bool {
1849 match &stmt.kind {
1850 StmtKind::Break | StmtKind::Continue => true,
1851 StmtKind::Block(b) | StmtKind::UncheckedBlock(b) => {
1852 b.stmts.iter().any(stmt_has_break_or_continue)
1853 }
1854 StmtKind::If(_, t, e) => {
1855 stmt_has_break_or_continue(t)
1856 || e.as_ref().is_some_and(|s| stmt_has_break_or_continue(s))
1857 }
1858 StmtKind::Try(t) => {
1859 t.clauses.iter().any(|c| c.block.stmts.iter().any(stmt_has_break_or_continue))
1860 }
1861 StmtKind::Loop(..) => false,
1862 _ => false,
1863 }
1864}
1865
1866fn count_placeholders(stmts: &[hir::Stmt<'_>]) -> usize {
1867 stmts.iter().map(count_placeholders_in_stmt).sum()
1868}
1869
1870fn count_placeholders_in_stmt(stmt: &hir::Stmt<'_>) -> usize {
1871 match &stmt.kind {
1872 StmtKind::Placeholder => 1,
1873 StmtKind::Block(b) | StmtKind::UncheckedBlock(b) | StmtKind::Loop(b, _) => {
1874 count_placeholders(b.stmts)
1875 }
1876 StmtKind::If(_, t, e) => {
1877 count_placeholders_in_stmt(t) + e.as_ref().map_or(0, |s| count_placeholders_in_stmt(s))
1878 }
1879 StmtKind::Try(t) => t.clauses.iter().map(|c| count_placeholders(c.block.stmts)).sum(),
1880 _ => 0,
1881 }
1882}
1883
1884fn underlying_var(expr: &hir::Expr<'_>) -> Option<hir::VariableId> {
1886 match &expr.peel_parens().kind {
1887 ExprKind::Ident(reses) => reses.iter().find_map(|r| match r {
1888 Res::Item(ItemId::Variable(vid)) => Some(*vid),
1889 _ => None,
1890 }),
1891 ExprKind::Call(callee, args, _) if is_address_like_cast_callee(callee) => {
1892 args.exprs().next().and_then(underlying_var)
1893 }
1894 ExprKind::Payable(inner) => underlying_var(inner),
1895 _ => None,
1896 }
1897}
1898
1899const fn var_is_address_like(var: &hir::Variable<'_>) -> bool {
1901 matches!(
1902 var.ty.kind,
1903 TypeKind::Elementary(ElementaryType::Address(_)) | TypeKind::Custom(ItemId::Contract(_))
1904 )
1905}
1906
1907fn receiver_is_address<'hir>(gcx: Gcx<'hir>, expr: &'hir hir::Expr<'hir>) -> bool {
1909 expr_ty(gcx, expr).is_some_and(ty_is_address)
1910}
1911
1912fn is_address_like_cast_callee(callee: &hir::Expr<'_>) -> bool {
1914 match &callee.peel_parens().kind {
1915 ExprKind::Type(hir::Type {
1916 kind: TypeKind::Elementary(ElementaryType::Address(_)),
1917 ..
1918 }) => true,
1919 ExprKind::Ident(reses) => reses.iter().any(|r| matches!(r, Res::Item(ItemId::Contract(_)))),
1920 _ => false,
1921 }
1922}
1923
1924fn expr_ty<'hir>(gcx: Gcx<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<Ty<'hir>> {
1925 gcx.type_of_expr(expr.peel_parens().id)
1926}
1927
1928fn expr_is_function<'hir>(gcx: Gcx<'hir>, expr: &'hir hir::Expr<'hir>) -> bool {
1929 expr_ty(gcx, expr).is_some_and(|ty| matches!(ty.peel_refs().kind, TyKind::Fn(_)))
1930}
1931
1932fn expr_is_array_or_bytes<'hir>(gcx: Gcx<'hir>, expr: &'hir hir::Expr<'hir>) -> bool {
1933 expr_ty(gcx, expr).is_some_and(|ty| {
1934 matches!(
1935 ty.peel_refs().kind,
1936 TyKind::Array(..) | TyKind::DynArray(_) | TyKind::Elementary(ElementaryType::Bytes)
1937 )
1938 })
1939}
1940
1941fn ty_is_address(ty: Ty<'_>) -> bool {
1942 matches!(ty.peel_refs().kind, TyKind::Elementary(ElementaryType::Address(_)))
1943}
1944
1945fn is_require_or_assert(callee: &hir::Expr<'_>) -> bool {
1946 let ExprKind::Ident(reses) = &callee.kind else { return false };
1947 reses.iter().any(
1948 |r| matches!(r, Res::Builtin(b) if b.name() == sym::require || b.name() == sym::assert),
1949 )
1950}
1951
1952fn is_address_self(expr: &hir::Expr<'_>) -> bool {
1954 let expr = expr.peel_parens();
1955 if is_builtin(expr, sym::this) {
1956 return true;
1957 }
1958 if let ExprKind::Payable(inner) = &expr.kind {
1959 return is_address_self(inner);
1960 }
1961 matches!(&expr.kind, ExprKind::Call(callee, args, _) if is_address_like_cast_callee(callee)
1962 && args.exprs().next().is_some_and(is_address_self))
1963}
1964
1965fn is_builtin(expr: &hir::Expr<'_>, name: Symbol) -> bool {
1966 let ExprKind::Ident(reses) = &expr.peel_parens().kind else { return false };
1967 reses.iter().any(|r| matches!(r, Res::Builtin(b) if b.name() == name))
1968}
1969
1970fn is_literal_zero(expr: &hir::Expr<'_>) -> bool {
1971 if let ExprKind::Lit(lit) = &expr.peel_parens().kind
1972 && let LitKind::Number(n) = &lit.kind
1973 {
1974 return n.is_zero();
1975 }
1976 false
1977}
1978
1979fn branch_always_exits(stmt: &hir::Stmt<'_>) -> bool {
1981 match &stmt.kind {
1982 StmtKind::Return(_) | StmtKind::Revert(_) => true,
1983 StmtKind::Expr(expr) => is_exit_call(expr),
1984 StmtKind::Block(b) | StmtKind::UncheckedBlock(b) => b.stmts.iter().any(branch_always_exits),
1985 StmtKind::If(_, t, Some(e)) => branch_always_exits(t) && branch_always_exits(e),
1986 StmtKind::Try(t) => {
1987 !t.clauses.is_empty()
1988 && t.clauses.iter().all(|c| c.block.stmts.iter().any(branch_always_exits))
1989 }
1990 _ => false,
1991 }
1992}
1993
1994fn is_exit_call(expr: &hir::Expr<'_>) -> bool {
1995 let ExprKind::Call(callee, args, _) = &expr.kind else { return false };
1996 if is_builtin(callee, kw::Revert) {
1997 return true;
1998 }
1999 if let ExprKind::Ident(reses) = &callee.peel_parens().kind
2000 && reses.iter().any(|r| matches!(r, Res::Builtin(Builtin::Selfdestruct)))
2001 {
2002 return true;
2003 }
2004 if is_require_or_assert(callee)
2005 && let hir::CallArgsKind::Unnamed(unnamed) = args.kind
2006 && let Some(first) = unnamed.first()
2007 && matches!(
2008 &first.peel_parens().kind,
2009 ExprKind::Lit(lit) if matches!(lit.kind, ast::LitKind::Bool(false))
2010 )
2011 {
2012 return true;
2013 }
2014 false
2015}