Control flow side-effects for user-defined functions.

This commit is contained in:
chriseth 2021-09-30 17:01:54 +02:00
parent d443fe415d
commit 9417d6775f
20 changed files with 637 additions and 32 deletions

View File

@ -533,14 +533,14 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
yul::ASTWalker::operator()(_functionCall);
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
if (builtinFunction->controlFlowSideEffects.terminates)
{
if (builtinFunction->controlFlowSideEffects.reverts)
connect(m_currentNode, m_revertNode);
else
connect(m_currentNode, m_transactionReturnNode);
{
if (builtinFunction->controlFlowSideEffects.canTerminate)
connect(m_currentNode, m_transactionReturnNode);
if (builtinFunction->controlFlowSideEffects.canRevert)
connect(m_currentNode, m_revertNode);
if (!builtinFunction->controlFlowSideEffects.canContinue)
m_currentNode = newLabel();
}
}
}
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)

View File

@ -34,6 +34,9 @@ add_library(yul
AssemblyStack.cpp
CompilabilityChecker.cpp
CompilabilityChecker.h
ControlFlowSideEffects.h
ControlFlowSideEffectsCollector.cpp
ControlFlowSideEffectsCollector.h
Dialect.cpp
Dialect.h
Exceptions.h

View File

@ -18,22 +18,32 @@
#pragma once
#include <set>
namespace solidity::yul
{
/**
* Side effects of code related to control flow.
* Side effects of a user-defined or builtin function.
*
* Each of the three booleans represents a reachability condition. There is an implied
* fourth alternative, which is going out of gas while executing the function. Since
* this can always happen and depends on the supply of gas, it is not considered.
*
* If all three booleans are false, it means that the function always leads to infinite
* recursion.
*/
struct ControlFlowSideEffects
{
/// If true, this code terminates the control flow.
/// State may or may not be reverted as indicated by the ``reverts`` flag.
bool terminates = false;
/// If true, this code reverts all state changes in the transaction.
/// Whenever this is true, ``terminates`` has to be true as well.
bool reverts = false;
/// If true, the function contains at least one reachable branch that terminates successfully.
bool canTerminate = false;
/// If true, the function contains at least one reachable branch that reverts.
bool canRevert = false;
/// If true, the function has a regular outgoing control-flow.
bool canContinue = true;
bool terminatesOrReverts() const
{
return (canTerminate || canRevert) && !canContinue;
}
};
}

View File

@ -0,0 +1,274 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libyul/ControlFlowSideEffectsCollector.h>
#include <libyul/optimiser/FunctionDefinitionCollector.h>
#include <libyul/AST.h>
#include <libyul/Dialect.h>
#include <libsolutil/Common.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/Algorithms.h>
#include <range/v3/view/map.hpp>
#include <range/v3/view/reverse.hpp>
#include <range/v3/algorithm/find_if.hpp>
using namespace std;
using namespace solidity::yul;
ControlFlowBuilder::ControlFlowBuilder(Block const& _ast)
{
for (auto const& statement: _ast.statements)
if (auto const* function = get_if<FunctionDefinition>(&statement))
(*this)(*function);
}
void ControlFlowBuilder::operator()(FunctionCall const& _functionCall)
{
walkVector(_functionCall.arguments | ranges::views::reverse);
newConnectedNode();
m_currentNode->functionCall = _functionCall.functionName.name;
}
void ControlFlowBuilder::operator()(If const& _if)
{
visit(*_if.condition);
ControlFlowNode* node = m_currentNode;
(*this)(_if.body);
newConnectedNode();
node->successors.emplace_back(m_currentNode);
}
void ControlFlowBuilder::operator()(Switch const& _switch)
{
visit(*_switch.expression);
ControlFlowNode* initialNode = m_currentNode;
ControlFlowNode* finalNode = newNode();
if (_switch.cases.back().value)
initialNode->successors.emplace_back(finalNode);
for (Case const& case_: _switch.cases)
{
m_currentNode = initialNode;
(*this)(case_.body);
newConnectedNode();
m_currentNode->successors.emplace_back(finalNode);
}
m_currentNode = finalNode;
}
void ControlFlowBuilder::operator()(FunctionDefinition const& _function)
{
ScopedSaveAndRestore currentNode(m_currentNode, nullptr);
yulAssert(!m_leave && !m_break && !m_continue, "Function hoister has not been used.");
FunctionFlow flow;
flow.exit = newNode();
m_currentNode = newNode();
flow.entry = m_currentNode;
m_leave = flow.exit;
(*this)(_function.body);
m_currentNode->successors.emplace_back(flow.exit);
m_functionFlows[_function.name] = move(flow);
m_leave = nullptr;
}
void ControlFlowBuilder::operator()(ForLoop const& _for)
{
ScopedSaveAndRestore scopedBreakNode(m_break, nullptr);
ScopedSaveAndRestore scopedContinueNode(m_continue, nullptr);
(*this)(_for.pre);
ControlFlowNode* breakNode = newNode();
m_break = breakNode;
ControlFlowNode* continueNode = newNode();
m_continue = continueNode;
newConnectedNode();
ControlFlowNode* loopNode = m_currentNode;
visit(*_for.condition);
m_currentNode->successors.emplace_back(m_break);
newConnectedNode();
(*this)(_for.body);
m_currentNode->successors.emplace_back(m_continue);
m_currentNode = continueNode;
(*this)(_for.post);
m_currentNode->successors.emplace_back(loopNode);
m_currentNode = breakNode;
}
void ControlFlowBuilder::operator()(Break const&)
{
yulAssert(m_break);
m_currentNode->successors.emplace_back(m_break);
m_currentNode = newNode();
}
void ControlFlowBuilder::operator()(Continue const&)
{
yulAssert(m_continue);
m_currentNode->successors.emplace_back(m_continue);
m_currentNode = newNode();
}
void ControlFlowBuilder::operator()(Leave const&)
{
yulAssert(m_leave);
m_currentNode->successors.emplace_back(m_leave);
m_currentNode = newNode();
}
void ControlFlowBuilder::newConnectedNode()
{
ControlFlowNode* node = newNode();
m_currentNode->successors.emplace_back(node);
m_currentNode = node;
}
ControlFlowNode* ControlFlowBuilder::newNode()
{
m_nodes.emplace_back(make_shared<ControlFlowNode>());
return m_nodes.back().get();
}
ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
Dialect const& _dialect,
Block const& _ast
):
m_dialect(_dialect),
m_cfgBuilder(_ast)
{
for (auto&& [name, flow]: m_cfgBuilder.functionFlows())
{
yulAssert(!flow.entry->functionCall);
m_processedNodes[name] = {};
m_pendingNodes[name].push_front(flow.entry);
m_functionSideEffects[name] = {false, false, false};
}
// Process functions while we have progress. For now, we are only interested
// in `canContinue`.
bool progress = true;
while (progress)
{
progress = false;
for (auto const& functionName: m_pendingNodes | ranges::views::keys)
if (processFunction(functionName))
progress = true;
}
// No progress anymore: All remaining nodes are calls
// to functions that always recurse.
// If we have not set `canContinue` by now, the function's exit
// is not reachable.
for (auto&& [functionName, calls]: m_functionCalls)
{
ControlFlowSideEffects& sideEffects = m_functionSideEffects[functionName];
auto _visit = [&, visited = std::set<YulString>{}](YulString _function, auto&& _recurse) mutable {
if (sideEffects.canTerminate && sideEffects.canRevert)
return;
if (!visited.insert(_function).second)
return;
ControlFlowSideEffects const* calledSideEffects = nullptr;
if (BuiltinFunction const* f = _dialect.builtin(_function))
calledSideEffects = &f->controlFlowSideEffects;
else
calledSideEffects = &m_functionSideEffects.at(_function);
if (calledSideEffects->canTerminate)
sideEffects.canTerminate = true;
if (calledSideEffects->canRevert)
sideEffects.canRevert = true;
for (YulString callee: util::valueOrDefault(m_functionCalls, _function))
_recurse(callee, _recurse);
};
for (auto const& call: calls)
_visit(call, _visit);
}
}
bool ControlFlowSideEffectsCollector::processFunction(YulString _name)
{
bool progress = false;
while (ControlFlowNode const* node = nextProcessableNode(_name))
{
if (node == m_cfgBuilder.functionFlows().at(_name).exit)
{
m_functionSideEffects[_name].canContinue = true;
return true;
}
for (ControlFlowNode const* s: node->successors)
recordReachabilityAndQueue(_name, s);
progress = true;
}
return progress;
}
ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulString _functionName)
{
std::list<ControlFlowNode const*>& nodes = m_pendingNodes[_functionName];
auto it = ranges::find_if(nodes, [this](ControlFlowNode const* _node) {
return !_node->functionCall || sideEffects(*_node->functionCall).canContinue;
});
if (it == nodes.end())
return nullptr;
ControlFlowNode const* node = *it;
nodes.erase(it);
return node;
}
ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(YulString _functionName) const
{
if (auto const* builtin = m_dialect.builtin(_functionName))
return builtin->controlFlowSideEffects;
else
return m_functionSideEffects.at(_functionName);
}
void ControlFlowSideEffectsCollector::recordReachabilityAndQueue(
YulString _functionName,
ControlFlowNode const* _node
)
{
if (_node->functionCall)
m_functionCalls[_functionName].insert(*_node->functionCall);
if (m_processedNodes[_functionName].insert(_node).second)
m_pendingNodes.at(_functionName).push_front(_node);
}

View File

@ -0,0 +1,130 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/ControlFlowSideEffects.h>
#include <set>
#include <stack>
#include <optional>
#include <list>
namespace solidity::yul
{
struct Dialect;
struct ControlFlowNode
{
std::vector<ControlFlowNode const*> successors;
/// Name of the called function if the node calls a function.
std::optional<YulString> functionCall;
};
/**
* The control flow of a function with entry and exit nodes.
*/
struct FunctionFlow
{
ControlFlowNode const* entry;
ControlFlowNode const* exit;
};
/**
* Requires: Disambiguator, Function Hoister.
*/
class ControlFlowBuilder: private ASTWalker
{
public:
/// Computes the control-flows of all function defined in the block.
/// Assumes the functions are hoisted to the topmost block.
explicit ControlFlowBuilder(Block const& _ast);
std::map<YulString, FunctionFlow> const& functionFlows() const { return m_functionFlows; }
private:
using ASTWalker::operator();
void operator()(FunctionCall const& _functionCall) override;
void operator()(If const& _if) override;
void operator()(Switch const& _switch) override;
void operator()(FunctionDefinition const& _functionDefinition) override;
void operator()(ForLoop const& _forLoop) override;
void operator()(Break const& _break) override;
void operator()(Continue const& _continue) override;
void operator()(Leave const& _leaveStatement) override;
void newConnectedNode();
ControlFlowNode* newNode();
std::vector<std::shared_ptr<ControlFlowNode>> m_nodes;
ControlFlowNode* m_currentNode = nullptr;
ControlFlowNode const* m_leave = nullptr;
ControlFlowNode const* m_break = nullptr;
ControlFlowNode const* m_continue = nullptr;
std::map<YulString, FunctionFlow> m_functionFlows;
};
/**
* Requires: Disambiguator, Function Hoister.
*/
class ControlFlowSideEffectsCollector
{
public:
explicit ControlFlowSideEffectsCollector(
Dialect const& _dialect,
Block const& _ast
);
std::map<YulString, ControlFlowSideEffects> const& functionSideEffects() const
{
return m_functionSideEffects;
}
private:
/// @returns false if nothing could be processed.
bool processFunction(YulString _name);
/// @returns the next pending node of the function that is not
/// a function call to a function that might not continue.
/// De-queues the node or returns nullptr if no such node is found.
ControlFlowNode const* nextProcessableNode(YulString _functionName);
/// @returns the side-effects of either a builtin call or a user defined function
/// call (as far as already computed).
ControlFlowSideEffects const& sideEffects(YulString _functionName) const;
/// Queues the given node to be processed (if not already visited)
/// and if it is a function call, records that `_functionName` calls
/// `*_node->functionCall`.
void recordReachabilityAndQueue(YulString _functionName, ControlFlowNode const* _node);
Dialect const& m_dialect;
ControlFlowBuilder m_cfgBuilder;
std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
std::map<YulString, std::list<ControlFlowNode const*>> m_pendingNodes;
std::map<YulString, std::set<ControlFlowNode const*>> m_processedNodes;
/// `x` is in `m_functionCalls[y]` if a direct call to `x` is reachable inside `y`
std::map<YulString, std::set<YulString>> m_functionCalls;
};
}

View File

@ -22,8 +22,8 @@
#pragma once
#include <libyul/YulString.h>
#include <libyul/SideEffects.h>
#include <libyul/ControlFlowSideEffects.h>
#include <libyul/SideEffects.h>
#include <vector>
#include <set>

View File

@ -94,7 +94,7 @@ struct SideEffects
cannotLoop && _other.cannotLoop,
otherState + _other.otherState,
storage + _other.storage,
memory + _other.memory
memory + _other.memory
};
}

View File

@ -229,7 +229,7 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
// not only for builtins.
if (auto const* funCall = get_if<FunctionCall>(&_exprStmt.expression))
if (BuiltinFunction const* builtin = m_dialect.builtin(funCall->functionName.name))
if (builtin->controlFlowSideEffects.terminates)
if (builtin->controlFlowSideEffects.terminatesOrReverts())
{
m_currentBlock->exit = CFG::BasicBlock::Terminated{};
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));

View File

@ -57,8 +57,20 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.parameters.resize(static_cast<size_t>(info.args));
f.returns.resize(static_cast<size_t>(info.ret));
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
if (evmasm::SemanticInformation::terminatesControlFlow(_instruction))
{
f.controlFlowSideEffects.canContinue = false;
if (evmasm::SemanticInformation::reverts(_instruction))
{
f.controlFlowSideEffects.canTerminate = false;
f.controlFlowSideEffects.canRevert = true;
}
else
{
f.controlFlowSideEffects.canTerminate = true;
f.controlFlowSideEffects.canRevert = false;
}
}
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
f.literalArguments.clear();
f.instruction = _instruction;

View File

@ -484,7 +484,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
yulAssert(!_block.operations.empty(), "");
CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation);
yulAssert(builtinCall, "");
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminates, "");
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), "");
}
}, _block.exit);
// TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump.

View File

@ -129,8 +129,9 @@ WasmDialect::WasmDialect()
m_functions["unreachable"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["unreachable"_yulstring].sideEffects.memory = SideEffects::None;
m_functions["unreachable"_yulstring].sideEffects.otherState = SideEffects::None;
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
m_functions["unreachable"_yulstring].controlFlowSideEffects.canTerminate = false;
m_functions["unreachable"_yulstring].controlFlowSideEffects.canRevert = true;
m_functions["unreachable"_yulstring].controlFlowSideEffects.canContinue = false;
addFunction("datasize", {i64}, {i64}, true, {LiteralKind::String});
addFunction("dataoffset", {i64}, {i64}, true, {LiteralKind::String});
@ -215,11 +216,11 @@ void WasmDialect::addExternals()
{"eth", "log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
{"eth", "getBlockNumber", {}, {i64}},
{"eth", "getTxOrigin", {i32ptr}, {}},
{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}},
{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}},
{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false, false}},
{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{false, true, false}},
{"eth", "getReturnDataSize", {}, {i32}},
{"eth", "returnDataCopy", {i32ptr, i32, i32}, {}},
{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}},
{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{false, true, false}},
{"eth", "getBlockTimestamp", {}, {i64}},
{"debug", "print32", {i32}, {}},
{"debug", "print64", {i64}, {}},
@ -240,7 +241,7 @@ void WasmDialect::addExternals()
// TODO some of them are side effect free.
f.sideEffects = SideEffects::worst();
f.sideEffects.cannotLoop = true;
f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminates;
f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminatesOrReverts();
f.controlFlowSideEffects = ext.controlFlowSideEffects;
f.isMSize = false;
f.literalArguments.clear();

View File

@ -22,7 +22,7 @@ using namespace std;
using namespace solidity;
using namespace solidity::yul;
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block& _block)
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block const& _block)
{
FunctionDefinitionCollector functionDefinitionCollector;
functionDefinitionCollector(_block);

View File

@ -34,7 +34,7 @@ namespace solidity::yul
class FunctionDefinitionCollector: ASTWalker
{
public:
static std::map<YulString, FunctionDefinition const*> run(Block& _block);
static std::map<YulString, FunctionDefinition const*> run(Block const& _block);
private:
using ASTWalker::operator();
void operator()(FunctionDefinition const& _functionDefinition) override;

View File

@ -130,6 +130,8 @@ set(libyul_sources
libyul/CompilabilityChecker.cpp
libyul/ControlFlowGraphTest.cpp
libyul/ControlFlowGraphTest.h
libyul/ControlFlowSideEffectsTest.cpp
libyul/ControlFlowSideEffectsTest.h
libyul/EVMCodeTransformTest.cpp
libyul/EVMCodeTransformTest.h
libyul/EwasmTranslationTest.cpp

View File

@ -31,6 +31,7 @@
#include <test/libyul/YulOptimizerTest.h>
#include <test/libyul/YulInterpreterTest.h>
#include <test/libyul/ObjectCompilerTest.h>
#include <test/libyul/ControlFlowSideEffectsTest.h>
#include <test/libyul/FunctionSideEffects.h>
#include <test/libyul/StackLayoutGeneratorTest.h>
#include <test/libyul/SyntaxTest.h>
@ -57,12 +58,12 @@ struct Testsuite
Testsuite const g_interactiveTestsuites[] = {
/*
Title Path Subpath SMT NeedsVM Creator function */
{"Ewasm Translation", "libyul", "ewasmTranslationTests", false, false, &yul::test::EwasmTranslationTest::create},
{"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create},
{"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create},
{"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create},
{"Yul Control Flow Graph", "libyul", "yulControlFlowGraph", false, false, &yul::test::ControlFlowGraphTest::create},
{"Yul Stack Layout", "libyul", "yulStackLayout", false, false, &yul::test::StackLayoutGeneratorTest::create},
{"Control Flow Side Effects","libyul", "controlFlowSideEffects",false, false, &yul::test::ControlFlowSideEffectsTest::create},
{"Function Side Effects", "libyul", "functionSideEffects", false, false, &yul::test::FunctionSideEffects::create},
{"Yul Syntax", "libyul", "yulSyntaxTests", false, false, &yul::test::SyntaxTest::create},
{"EVM Code Transform", "libyul", "evmCodeTransform", false, false, &yul::test::EVMCodeTransformTest::create, {"nooptions"}},
@ -72,7 +73,8 @@ Testsuite const g_interactiveTestsuites[] = {
{"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create},
{"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create},
{"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create},
{"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create}
{"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create},
{"Ewasm Translation", "libyul", "ewasmTranslationTests", false, false, &yul::test::EwasmTranslationTest::create}
};
}

View File

@ -0,0 +1,79 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <test/libyul/ControlFlowSideEffectsTest.h>
#include <test/Common.h>
#include <test/libyul/Common.h>
#include <libyul/Object.h>
#include <libyul/ControlFlowSideEffects.h>
#include <libyul/ControlFlowSideEffectsCollector.h>
#include <libyul/backends/evm/EVMDialect.h>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
using namespace solidity::yul::test;
using namespace solidity::frontend::test;
namespace
{
string toString(ControlFlowSideEffects const& _sideEffects)
{
vector<string> r;
if (_sideEffects.canTerminate)
r.emplace_back("can terminate");
if (_sideEffects.canRevert)
r.emplace_back("can revert");
if (_sideEffects.canContinue)
r.emplace_back("can continue");
return util::joinHumanReadable(r);
}
}
ControlFlowSideEffectsTest::ControlFlowSideEffectsTest(string const& _filename):
TestCase(_filename)
{
m_source = m_reader.source();
m_expectation = m_reader.simpleExpectations();
}
TestCase::TestResult ControlFlowSideEffectsTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
{
Object obj;
std::tie(obj.code, obj.analysisInfo) = yul::test::parse(m_source, false);
if (!obj.code)
BOOST_THROW_EXCEPTION(runtime_error("Parsing input failed."));
std::map<YulString, ControlFlowSideEffects> sideEffects =
ControlFlowSideEffectsCollector(
EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()),
*obj.code
).functionSideEffects();
std::map<std::string, std::string> controlFlowSideEffectsStr;
for (auto&& [fun, effects]: sideEffects)
controlFlowSideEffectsStr[fun.str()] = toString(effects);
m_obtainedResult.clear();
for (auto&& [functionName, effect]: controlFlowSideEffectsStr)
m_obtainedResult += functionName + (effect.empty() ? ":" : ": " + effect) + "\n";
return checkResult(_stream, _linePrefix, _formatted);
}

View File

@ -0,0 +1,40 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <test/TestCase.h>
#include <iosfwd>
#include <string>
#include <vector>
#include <memory>
namespace solidity::yul::test
{
class ControlFlowSideEffectsTest: public solidity::frontend::test::TestCase
{
public:
static std::unique_ptr<TestCase> create(Config const& _config)
{ return std::make_unique<ControlFlowSideEffectsTest>(_config.filename); }
explicit ControlFlowSideEffectsTest(std::string const& _filename);
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override;
};
}

View File

@ -0,0 +1,34 @@
{
function a() {
if calldataload(0) { g() }
}
function b() {
g()
if calldataload(0) { }
}
function c() {
if calldataload(0) { }
g()
}
function d() {
stop()
if calldataload(0) { g() }
}
function e() {
if calldataload(0) { g() }
stop()
}
function f() {
g()
if calldataload(0) { g() }
}
function g() { revert(0, 0) }
}
// ----
// a: can revert, can continue
// b: can revert
// c: can revert
// d: can terminate
// e: can terminate, can revert
// f: can revert
// g: can revert

View File

@ -0,0 +1,17 @@
{
function a() {}
function f() { g() }
function g() { revert(0, 0) }
function h() { stop() }
function i() { h() }
function j() { h() g() }
function k() { g() h() }
}
// ----
// a: can continue
// f: can revert
// g: can revert
// h: can terminate
// i: can terminate
// j: can terminate
// k: can revert

View File

@ -33,6 +33,7 @@ add_executable(isoltest
../libsolidity/SMTCheckerTest.cpp
../libyul/Common.cpp
../libyul/ControlFlowGraphTest.cpp
../libyul/ControlFlowSideEffectsTest.cpp
../libyul/EVMCodeTransformTest.cpp
../libyul/EwasmTranslationTest.cpp
../libyul/FunctionSideEffects.cpp