1use crate::sol::analysis::{is_builtin, loop_stmts};
8use solar::{
9 ast::{StateMutability, Visibility},
10 interface::sym,
11 sema::{
12 Gcx,
13 hir::{
14 Block, ContractId, Expr, ExprKind, Function, FunctionId, FunctionKind, Hir, Modifier,
15 Stmt, StmtKind, Visit,
16 },
17 ty::TyKind,
18 },
19};
20use std::{convert::Infallible, ops::ControlFlow};
21
22pub(super) enum LoopItem<'gcx> {
24 Stmt(&'gcx Stmt<'gcx>),
25 Expr(&'gcx Expr<'gcx>),
26}
27
28pub(super) fn for_each_payable_loop_expr<'gcx>(
31 gcx: Gcx<'gcx>,
32 func: &'gcx Function<'gcx>,
33 mut f: impl FnMut(&'gcx Expr<'gcx>),
34) {
35 if !matches!(func.kind, FunctionKind::Constructor | FunctionKind::Modifier)
36 && func.state_mutability == StateMutability::Payable
37 && matches!(func.visibility, Visibility::Public | Visibility::External)
38 {
39 for_each_loop_item(gcx, func, true, |item| {
40 if let LoopItem::Expr(expr) = item {
41 f(expr);
42 }
43 });
44 }
45}
46
47pub(super) fn for_each_loop_item<'gcx>(
51 gcx: Gcx<'gcx>,
52 func: &'gcx Function<'gcx>,
53 follow_calls_outside_loop: bool,
54 f: impl FnMut(LoopItem<'gcx>),
55) {
56 let Some(body) = func.body else { return };
57 let mut walker = LoopWalker {
58 gcx,
59 f,
60 loop_depth: 0,
61 placeholder: None,
62 stack: Vec::new(),
63 dispatch: func.contract,
64 current: func.contract,
65 follow_calls_outside_loop,
66 };
67 walker.visit_modifiers(func.modifiers, 0, body, func.contract);
68}
69
70type Continuation<'gcx> = (&'gcx [Modifier<'gcx>], usize, Block<'gcx>, Option<ContractId>);
72
73struct LoopWalker<'gcx, F> {
74 gcx: Gcx<'gcx>,
75 f: F,
76 loop_depth: usize,
77 placeholder: Option<Continuation<'gcx>>,
78 stack: Vec<FunctionId>,
80 dispatch: Option<ContractId>,
82 current: Option<ContractId>,
84 follow_calls_outside_loop: bool,
85}
86
87impl<'gcx, F: FnMut(LoopItem<'gcx>)> LoopWalker<'gcx, F> {
88 fn visit_modifiers(
89 &mut self,
90 modifiers: &'gcx [Modifier<'gcx>],
91 index: usize,
92 body: Block<'gcx>,
93 contract: Option<ContractId>,
94 ) {
95 let Some(modifier) = modifiers.get(index) else {
96 return self.visit_scoped(body, None, contract);
97 };
98 let _ = self.visit_call_args(&modifier.args);
99 if let Some(id) = self.dispatch.map_or_else(
100 || modifier.id.as_function(),
101 |contract| self.gcx.resolve_modifier_target(contract, modifier),
102 ) && let Some(modifier_body) = self.gcx.hir.function(id).body
103 && !self.stack.contains(&id)
104 {
105 self.stack.push(id);
106 let continuation = Some((modifiers, index + 1, body, contract));
107 self.visit_scoped(modifier_body, continuation, self.gcx.hir.function(id).contract);
108 self.stack.pop();
109 } else {
110 self.visit_modifiers(modifiers, index + 1, body, contract);
111 }
112 }
113
114 fn visit_scoped(
115 &mut self,
116 block: Block<'gcx>,
117 placeholder: Option<Continuation<'gcx>>,
118 contract: Option<ContractId>,
119 ) {
120 let saved = (self.placeholder, self.current);
121 (self.placeholder, self.current) = (placeholder, contract);
122 for stmt in block.stmts {
123 let _ = self.visit_stmt(stmt);
124 }
125 (self.placeholder, self.current) = saved;
126 }
127
128 fn visit_call(&mut self, id: FunctionId) {
129 let func = self.gcx.hir.function(id);
130 if let Some(body) = func.body
131 && !self.stack.contains(&id)
132 {
133 self.stack.push(id);
134 self.visit_modifiers(func.modifiers, 0, body, func.contract);
135 self.stack.pop();
136 }
137 }
138
139 fn callee(&self, callee: &'gcx Expr<'gcx>) -> Option<FunctionId> {
143 let callee = callee.peel_parens();
144 let TyKind::Fn(function) = self.gcx.type_of_expr(callee.id)?.kind else { return None };
145 if !function.is_internal() && !function.is_delegate_call() {
146 return None;
147 }
148 let func_id = self.gcx.resolved_function(callee)?;
149 let ExprKind::Member(base, _) = &callee.kind else {
150 return Some(
151 self.dispatch.map_or(func_id, |contract| {
152 self.gcx.resolve_virtual_function(contract, func_id)
153 }),
154 );
155 };
156 if is_builtin(self.gcx, base, sym::super_) {
157 return Some(self.gcx.resolve_super_function(self.dispatch?, self.current?, func_id));
158 }
159 Some(func_id)
160 }
161}
162
163impl<'gcx, F: FnMut(LoopItem<'gcx>)> Visit<'gcx> for LoopWalker<'gcx, F> {
164 type BreakValue = Infallible;
165
166 fn hir(&self) -> &'gcx Hir<'gcx> {
167 &self.gcx.hir
168 }
169
170 fn visit_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> ControlFlow<Infallible> {
171 if self.loop_depth > 0 {
172 (self.f)(LoopItem::Stmt(stmt));
173 }
174 match stmt.kind {
175 StmtKind::Loop(block, source) => {
176 self.loop_depth += 1;
177 for stmt in loop_stmts(block, source) {
178 self.visit_stmt(stmt)?;
179 }
180 self.loop_depth -= 1;
181 }
182 StmtKind::Placeholder => {
183 if let Some((modifiers, index, body, contract)) = self.placeholder {
184 self.visit_modifiers(modifiers, index, body, contract);
185 }
186 }
187 _ => self.walk_stmt(stmt)?,
188 }
189 ControlFlow::Continue(())
190 }
191
192 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<Infallible> {
193 self.walk_expr(expr)?;
194 let in_loop = self.loop_depth > 0;
195 if in_loop {
196 (self.f)(LoopItem::Expr(expr));
197 }
198 if (in_loop || self.follow_calls_outside_loop)
199 && let ExprKind::Call(callee, ..) = &expr.kind
200 && let Some(id) = self.callee(callee)
201 {
202 self.visit_call(id);
203 }
204 ControlFlow::Continue(())
205 }
206}