Skip to content

Commit 1e630fc

Browse files
authored
Merge pull request #12082 from ethereum/controlFlowSideEffectsUserDefined
Control flow side effects of user defined functions
2 parents 6d47168 + 2c2269d commit 1e630fc

25 files changed

+802
-32
lines changed

libsolidity/analysis/ControlFlowBuilder.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -533,14 +533,14 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
533533
yul::ASTWalker::operator()(_functionCall);
534534

535535
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
536-
if (builtinFunction->controlFlowSideEffects.terminates)
537-
{
538-
if (builtinFunction->controlFlowSideEffects.reverts)
539-
connect(m_currentNode, m_revertNode);
540-
else
541-
connect(m_currentNode, m_transactionReturnNode);
536+
{
537+
if (builtinFunction->controlFlowSideEffects.canTerminate)
538+
connect(m_currentNode, m_transactionReturnNode);
539+
if (builtinFunction->controlFlowSideEffects.canRevert)
540+
connect(m_currentNode, m_revertNode);
541+
if (!builtinFunction->controlFlowSideEffects.canContinue)
542542
m_currentNode = newLabel();
543-
}
543+
}
544544
}
545545

546546
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)

libyul/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ add_library(yul
3434
AssemblyStack.cpp
3535
CompilabilityChecker.cpp
3636
CompilabilityChecker.h
37+
ControlFlowSideEffects.h
38+
ControlFlowSideEffectsCollector.cpp
39+
ControlFlowSideEffectsCollector.h
3740
Dialect.cpp
3841
Dialect.h
3942
Exceptions.h

libyul/ControlFlowSideEffects.h

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,32 @@
1818

1919
#pragma once
2020

21-
#include <set>
22-
2321
namespace solidity::yul
2422
{
2523

2624
/**
27-
* Side effects of code related to control flow.
25+
* Side effects of a user-defined or builtin function.
26+
*
27+
* Each of the three booleans represents a reachability condition. There is an implied
28+
* fourth alternative, which is going out of gas while executing the function. Since
29+
* this can always happen and depends on the supply of gas, it is not considered.
30+
*
31+
* If all three booleans are false, it means that the function always leads to infinite
32+
* recursion.
2833
*/
2934
struct ControlFlowSideEffects
3035
{
31-
/// If true, this code terminates the control flow.
32-
/// State may or may not be reverted as indicated by the ``reverts`` flag.
33-
bool terminates = false;
34-
/// If true, this code reverts all state changes in the transaction.
35-
/// Whenever this is true, ``terminates`` has to be true as well.
36-
bool reverts = false;
36+
/// If true, the function contains at least one reachable branch that terminates successfully.
37+
bool canTerminate = false;
38+
/// If true, the function contains at least one reachable branch that reverts.
39+
bool canRevert = false;
40+
/// If true, the function has a regular outgoing control-flow.
41+
bool canContinue = true;
42+
43+
bool terminatesOrReverts() const
44+
{
45+
return (canTerminate || canRevert) && !canContinue;
46+
}
3747
};
3848

3949
}
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#include <libyul/ControlFlowSideEffectsCollector.h>
20+
21+
#include <libyul/optimiser/FunctionDefinitionCollector.h>
22+
23+
#include <libyul/AST.h>
24+
#include <libyul/Dialect.h>
25+
26+
#include <libsolutil/Common.h>
27+
#include <libsolutil/CommonData.h>
28+
#include <libsolutil/Algorithms.h>
29+
30+
#include <range/v3/view/map.hpp>
31+
#include <range/v3/view/reverse.hpp>
32+
#include <range/v3/algorithm/find_if.hpp>
33+
34+
using namespace std;
35+
using namespace solidity::yul;
36+
37+
38+
ControlFlowBuilder::ControlFlowBuilder(Block const& _ast)
39+
{
40+
for (auto const& statement: _ast.statements)
41+
if (auto const* function = get_if<FunctionDefinition>(&statement))
42+
(*this)(*function);
43+
}
44+
45+
void ControlFlowBuilder::operator()(FunctionCall const& _functionCall)
46+
{
47+
walkVector(_functionCall.arguments | ranges::views::reverse);
48+
newConnectedNode();
49+
m_currentNode->functionCall = _functionCall.functionName.name;
50+
}
51+
52+
void ControlFlowBuilder::operator()(If const& _if)
53+
{
54+
visit(*_if.condition);
55+
ControlFlowNode* node = m_currentNode;
56+
(*this)(_if.body);
57+
newConnectedNode();
58+
node->successors.emplace_back(m_currentNode);
59+
}
60+
61+
void ControlFlowBuilder::operator()(Switch const& _switch)
62+
{
63+
visit(*_switch.expression);
64+
ControlFlowNode* initialNode = m_currentNode;
65+
ControlFlowNode* finalNode = newNode();
66+
67+
if (_switch.cases.back().value)
68+
initialNode->successors.emplace_back(finalNode);
69+
70+
for (Case const& case_: _switch.cases)
71+
{
72+
m_currentNode = initialNode;
73+
(*this)(case_.body);
74+
newConnectedNode();
75+
m_currentNode->successors.emplace_back(finalNode);
76+
}
77+
m_currentNode = finalNode;
78+
}
79+
80+
void ControlFlowBuilder::operator()(FunctionDefinition const& _function)
81+
{
82+
ScopedSaveAndRestore currentNode(m_currentNode, nullptr);
83+
yulAssert(!m_leave && !m_break && !m_continue, "Function hoister has not been used.");
84+
85+
FunctionFlow flow;
86+
flow.exit = newNode();
87+
m_currentNode = newNode();
88+
flow.entry = m_currentNode;
89+
m_leave = flow.exit;
90+
91+
(*this)(_function.body);
92+
93+
m_currentNode->successors.emplace_back(flow.exit);
94+
95+
m_functionFlows[_function.name] = move(flow);
96+
97+
m_leave = nullptr;
98+
}
99+
100+
void ControlFlowBuilder::operator()(ForLoop const& _for)
101+
{
102+
ScopedSaveAndRestore scopedBreakNode(m_break, nullptr);
103+
ScopedSaveAndRestore scopedContinueNode(m_continue, nullptr);
104+
105+
(*this)(_for.pre);
106+
107+
ControlFlowNode* breakNode = newNode();
108+
m_break = breakNode;
109+
ControlFlowNode* continueNode = newNode();
110+
m_continue = continueNode;
111+
112+
newConnectedNode();
113+
ControlFlowNode* loopNode = m_currentNode;
114+
visit(*_for.condition);
115+
m_currentNode->successors.emplace_back(m_break);
116+
newConnectedNode();
117+
118+
(*this)(_for.body);
119+
120+
m_currentNode->successors.emplace_back(m_continue);
121+
m_currentNode = continueNode;
122+
123+
(*this)(_for.post);
124+
m_currentNode->successors.emplace_back(loopNode);
125+
126+
m_currentNode = breakNode;
127+
}
128+
129+
void ControlFlowBuilder::operator()(Break const&)
130+
{
131+
yulAssert(m_break);
132+
m_currentNode->successors.emplace_back(m_break);
133+
m_currentNode = newNode();
134+
}
135+
136+
void ControlFlowBuilder::operator()(Continue const&)
137+
{
138+
yulAssert(m_continue);
139+
m_currentNode->successors.emplace_back(m_continue);
140+
m_currentNode = newNode();
141+
}
142+
143+
void ControlFlowBuilder::operator()(Leave const&)
144+
{
145+
yulAssert(m_leave);
146+
m_currentNode->successors.emplace_back(m_leave);
147+
m_currentNode = newNode();
148+
}
149+
150+
void ControlFlowBuilder::newConnectedNode()
151+
{
152+
ControlFlowNode* node = newNode();
153+
m_currentNode->successors.emplace_back(node);
154+
m_currentNode = node;
155+
}
156+
157+
ControlFlowNode* ControlFlowBuilder::newNode()
158+
{
159+
m_nodes.emplace_back(make_shared<ControlFlowNode>());
160+
return m_nodes.back().get();
161+
}
162+
163+
164+
ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
165+
Dialect const& _dialect,
166+
Block const& _ast
167+
):
168+
m_dialect(_dialect),
169+
m_cfgBuilder(_ast)
170+
{
171+
for (auto&& [name, flow]: m_cfgBuilder.functionFlows())
172+
{
173+
yulAssert(!flow.entry->functionCall);
174+
m_processedNodes[name] = {};
175+
m_pendingNodes[name].push_front(flow.entry);
176+
m_functionSideEffects[name] = {false, false, false};
177+
}
178+
179+
// Process functions while we have progress. For now, we are only interested
180+
// in `canContinue`.
181+
bool progress = true;
182+
while (progress)
183+
{
184+
progress = false;
185+
for (auto const& functionName: m_pendingNodes | ranges::views::keys)
186+
if (processFunction(functionName))
187+
progress = true;
188+
}
189+
190+
// No progress anymore: All remaining nodes are calls
191+
// to functions that always recurse.
192+
// If we have not set `canContinue` by now, the function's exit
193+
// is not reachable.
194+
195+
for (auto&& [functionName, calls]: m_functionCalls)
196+
{
197+
ControlFlowSideEffects& sideEffects = m_functionSideEffects[functionName];
198+
auto _visit = [&, visited = std::set<YulString>{}](YulString _function, auto&& _recurse) mutable {
199+
if (sideEffects.canTerminate && sideEffects.canRevert)
200+
return;
201+
if (!visited.insert(_function).second)
202+
return;
203+
204+
ControlFlowSideEffects const* calledSideEffects = nullptr;
205+
if (BuiltinFunction const* f = _dialect.builtin(_function))
206+
calledSideEffects = &f->controlFlowSideEffects;
207+
else
208+
calledSideEffects = &m_functionSideEffects.at(_function);
209+
210+
if (calledSideEffects->canTerminate)
211+
sideEffects.canTerminate = true;
212+
if (calledSideEffects->canRevert)
213+
sideEffects.canRevert = true;
214+
215+
for (YulString callee: util::valueOrDefault(m_functionCalls, _function))
216+
_recurse(callee, _recurse);
217+
};
218+
for (auto const& call: calls)
219+
_visit(call, _visit);
220+
}
221+
222+
}
223+
224+
bool ControlFlowSideEffectsCollector::processFunction(YulString _name)
225+
{
226+
bool progress = false;
227+
while (ControlFlowNode const* node = nextProcessableNode(_name))
228+
{
229+
if (node == m_cfgBuilder.functionFlows().at(_name).exit)
230+
{
231+
m_functionSideEffects[_name].canContinue = true;
232+
return true;
233+
}
234+
for (ControlFlowNode const* s: node->successors)
235+
recordReachabilityAndQueue(_name, s);
236+
237+
progress = true;
238+
}
239+
return progress;
240+
}
241+
242+
ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulString _functionName)
243+
{
244+
std::list<ControlFlowNode const*>& nodes = m_pendingNodes[_functionName];
245+
auto it = ranges::find_if(nodes, [this](ControlFlowNode const* _node) {
246+
return !_node->functionCall || sideEffects(*_node->functionCall).canContinue;
247+
});
248+
if (it == nodes.end())
249+
return nullptr;
250+
251+
ControlFlowNode const* node = *it;
252+
nodes.erase(it);
253+
return node;
254+
}
255+
256+
ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(YulString _functionName) const
257+
{
258+
if (auto const* builtin = m_dialect.builtin(_functionName))
259+
return builtin->controlFlowSideEffects;
260+
else
261+
return m_functionSideEffects.at(_functionName);
262+
}
263+
264+
void ControlFlowSideEffectsCollector::recordReachabilityAndQueue(
265+
YulString _functionName,
266+
ControlFlowNode const* _node
267+
)
268+
{
269+
if (_node->functionCall)
270+
m_functionCalls[_functionName].insert(*_node->functionCall);
271+
if (m_processedNodes[_functionName].insert(_node).second)
272+
m_pendingNodes.at(_functionName).push_front(_node);
273+
}
274+

0 commit comments

Comments
 (0)