mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #12082 from ethereum/controlFlowSideEffectsUserDefined
Control flow side effects of user defined functions
This commit is contained in:
commit
1e630fc584
@ -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&)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
274
libyul/ControlFlowSideEffectsCollector.cpp
Normal file
274
libyul/ControlFlowSideEffectsCollector.cpp
Normal 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);
|
||||
}
|
||||
|
130
libyul/ControlFlowSideEffectsCollector.h
Normal file
130
libyul/ControlFlowSideEffectsCollector.h
Normal 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;
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -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>
|
||||
|
@ -94,7 +94,7 @@ struct SideEffects
|
||||
cannotLoop && _other.cannotLoop,
|
||||
otherState + _other.otherState,
|
||||
storage + _other.storage,
|
||||
memory + _other.memory
|
||||
memory + _other.memory
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
};
|
||||
|
||||
}
|
||||
|
79
test/libyul/ControlFlowSideEffectsTest.cpp
Normal file
79
test/libyul/ControlFlowSideEffectsTest.cpp
Normal 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);
|
||||
}
|
40
test/libyul/ControlFlowSideEffectsTest.h
Normal file
40
test/libyul/ControlFlowSideEffectsTest.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
19
test/libyul/controlFlowSideEffects/eval_order.yul
Normal file
19
test/libyul/controlFlowSideEffects/eval_order.yul
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
function a() -> x {
|
||||
revert(0, 0)
|
||||
}
|
||||
function b() -> x {
|
||||
return(0, 0)
|
||||
}
|
||||
function c() {
|
||||
sstore(a(), b())
|
||||
}
|
||||
function d() {
|
||||
sstore(b(), a())
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// a: can revert
|
||||
// b: can terminate
|
||||
// c: can terminate
|
||||
// d: can revert
|
55
test/libyul/controlFlowSideEffects/for_loop.yul
Normal file
55
test/libyul/controlFlowSideEffects/for_loop.yul
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
function a() {
|
||||
for { leave } calldataload(0) { } {
|
||||
break
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
function b() {
|
||||
for { } calldataload(0) { leave } {
|
||||
break
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
function b2() {
|
||||
for { } calldataload(0) { leave } {
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
function c() {
|
||||
for { } calldataload(0) { revert(0, 0) } {
|
||||
break
|
||||
}
|
||||
}
|
||||
function c2() {
|
||||
for { } calldataload(0) { revert(0, 0) } {
|
||||
break
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
function d() {
|
||||
for { } calldataload(0) { revert(0, 0) } {
|
||||
continue
|
||||
}
|
||||
}
|
||||
function e() {
|
||||
for { } calldataload(0) { revert(0, 0) } {
|
||||
if calldataload(1) { break }
|
||||
}
|
||||
}
|
||||
function f() {
|
||||
for { } calldataload(0) { } {
|
||||
if calldataload(1) { continue }
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// a: can continue
|
||||
// b: can continue
|
||||
// b2: can revert, can continue
|
||||
// c: can continue
|
||||
// c2: can continue
|
||||
// d: can revert, can continue
|
||||
// e: can revert, can continue
|
||||
// f: can revert, can continue
|
13
test/libyul/controlFlowSideEffects/leave.yul
Normal file
13
test/libyul/controlFlowSideEffects/leave.yul
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
function a() {
|
||||
revert(0, 0)
|
||||
leave
|
||||
}
|
||||
function b() {
|
||||
leave
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// a: can revert
|
||||
// b: can continue
|
37
test/libyul/controlFlowSideEffects/recursion.yul
Normal file
37
test/libyul/controlFlowSideEffects/recursion.yul
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
function a() {
|
||||
if calldataload(0) {
|
||||
revert(0, 0)
|
||||
}
|
||||
reg()
|
||||
b()
|
||||
}
|
||||
function b() {
|
||||
a()
|
||||
return(0, 0)
|
||||
}
|
||||
function c() {
|
||||
c()
|
||||
revert(0, 0)
|
||||
}
|
||||
function d() {
|
||||
switch calldataload(0)
|
||||
case 0 { x() }
|
||||
case 1 { y() reg() revert(0, 0) }
|
||||
default { z() }
|
||||
}
|
||||
function x() { d() revert(0, 0) }
|
||||
function y() { reg() x() }
|
||||
function z() { y() }
|
||||
|
||||
function reg() {}
|
||||
}
|
||||
// ----
|
||||
// a: can revert
|
||||
// b: can revert
|
||||
// c:
|
||||
// d:
|
||||
// reg: can continue
|
||||
// x:
|
||||
// y:
|
||||
// z:
|
34
test/libyul/controlFlowSideEffects/simple_conditionals.yul
Normal file
34
test/libyul/controlFlowSideEffects/simple_conditionals.yul
Normal 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
|
17
test/libyul/controlFlowSideEffects/simple_functions.yul
Normal file
17
test/libyul/controlFlowSideEffects/simple_functions.yul
Normal 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
|
41
test/libyul/controlFlowSideEffects/switch.yul
Normal file
41
test/libyul/controlFlowSideEffects/switch.yul
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
function a() {
|
||||
switch calldataload(0)
|
||||
case 0 { revert(0, 0) }
|
||||
}
|
||||
function b() {
|
||||
switch calldataload(0)
|
||||
case 0 { revert(0, 0) }
|
||||
default { revert(0, 0) }
|
||||
}
|
||||
function c() {
|
||||
return(0, 0)
|
||||
switch calldataload(0)
|
||||
case 0 { revert(0, 0) }
|
||||
default { }
|
||||
}
|
||||
function d() {
|
||||
switch calldataload(0)
|
||||
case 0 { return(0, 0) }
|
||||
default { return(0, 0) }
|
||||
revert(0, 0)
|
||||
}
|
||||
function e() {
|
||||
switch calldataload(0)
|
||||
case 0 { return(0, 0) }
|
||||
revert(0, 0)
|
||||
}
|
||||
function f() {
|
||||
switch calldataload(0)
|
||||
case 0 { leave }
|
||||
default { leave }
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// a: can revert, can continue
|
||||
// b: can revert
|
||||
// c: can terminate
|
||||
// d: can terminate
|
||||
// e: can terminate, can revert
|
||||
// f: can continue
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user