mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Control flow side-effects for user-defined functions.
This commit is contained in:
parent
d443fe415d
commit
9417d6775f
@ -533,14 +533,14 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
|
|||||||
yul::ASTWalker::operator()(_functionCall);
|
yul::ASTWalker::operator()(_functionCall);
|
||||||
|
|
||||||
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
|
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
|
||||||
if (builtinFunction->controlFlowSideEffects.terminates)
|
{
|
||||||
{
|
if (builtinFunction->controlFlowSideEffects.canTerminate)
|
||||||
if (builtinFunction->controlFlowSideEffects.reverts)
|
connect(m_currentNode, m_transactionReturnNode);
|
||||||
connect(m_currentNode, m_revertNode);
|
if (builtinFunction->controlFlowSideEffects.canRevert)
|
||||||
else
|
connect(m_currentNode, m_revertNode);
|
||||||
connect(m_currentNode, m_transactionReturnNode);
|
if (!builtinFunction->controlFlowSideEffects.canContinue)
|
||||||
m_currentNode = newLabel();
|
m_currentNode = newLabel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
|
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
|
||||||
|
@ -34,6 +34,9 @@ add_library(yul
|
|||||||
AssemblyStack.cpp
|
AssemblyStack.cpp
|
||||||
CompilabilityChecker.cpp
|
CompilabilityChecker.cpp
|
||||||
CompilabilityChecker.h
|
CompilabilityChecker.h
|
||||||
|
ControlFlowSideEffects.h
|
||||||
|
ControlFlowSideEffectsCollector.cpp
|
||||||
|
ControlFlowSideEffectsCollector.h
|
||||||
Dialect.cpp
|
Dialect.cpp
|
||||||
Dialect.h
|
Dialect.h
|
||||||
Exceptions.h
|
Exceptions.h
|
||||||
|
@ -18,22 +18,32 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
namespace solidity::yul
|
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
|
struct ControlFlowSideEffects
|
||||||
{
|
{
|
||||||
/// If true, this code terminates the control flow.
|
/// If true, the function contains at least one reachable branch that terminates successfully.
|
||||||
/// State may or may not be reverted as indicated by the ``reverts`` flag.
|
bool canTerminate = false;
|
||||||
bool terminates = false;
|
/// If true, the function contains at least one reachable branch that reverts.
|
||||||
/// If true, this code reverts all state changes in the transaction.
|
bool canRevert = false;
|
||||||
/// Whenever this is true, ``terminates`` has to be true as well.
|
/// If true, the function has a regular outgoing control-flow.
|
||||||
bool reverts = false;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <libyul/YulString.h>
|
#include <libyul/YulString.h>
|
||||||
#include <libyul/SideEffects.h>
|
|
||||||
#include <libyul/ControlFlowSideEffects.h>
|
#include <libyul/ControlFlowSideEffects.h>
|
||||||
|
#include <libyul/SideEffects.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
@ -94,7 +94,7 @@ struct SideEffects
|
|||||||
cannotLoop && _other.cannotLoop,
|
cannotLoop && _other.cannotLoop,
|
||||||
otherState + _other.otherState,
|
otherState + _other.otherState,
|
||||||
storage + _other.storage,
|
storage + _other.storage,
|
||||||
memory + _other.memory
|
memory + _other.memory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
|
|||||||
// not only for builtins.
|
// not only for builtins.
|
||||||
if (auto const* funCall = get_if<FunctionCall>(&_exprStmt.expression))
|
if (auto const* funCall = get_if<FunctionCall>(&_exprStmt.expression))
|
||||||
if (BuiltinFunction const* builtin = m_dialect.builtin(funCall->functionName.name))
|
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->exit = CFG::BasicBlock::Terminated{};
|
||||||
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
|
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.parameters.resize(static_cast<size_t>(info.args));
|
||||||
f.returns.resize(static_cast<size_t>(info.ret));
|
f.returns.resize(static_cast<size_t>(info.ret));
|
||||||
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
||||||
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
|
if (evmasm::SemanticInformation::terminatesControlFlow(_instruction))
|
||||||
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_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.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
||||||
f.literalArguments.clear();
|
f.literalArguments.clear();
|
||||||
f.instruction = _instruction;
|
f.instruction = _instruction;
|
||||||
|
@ -484,7 +484,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
|
|||||||
yulAssert(!_block.operations.empty(), "");
|
yulAssert(!_block.operations.empty(), "");
|
||||||
CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation);
|
CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation);
|
||||||
yulAssert(builtinCall, "");
|
yulAssert(builtinCall, "");
|
||||||
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminates, "");
|
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), "");
|
||||||
}
|
}
|
||||||
}, _block.exit);
|
}, _block.exit);
|
||||||
// TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump.
|
// 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.storage = SideEffects::None;
|
||||||
m_functions["unreachable"_yulstring].sideEffects.memory = SideEffects::None;
|
m_functions["unreachable"_yulstring].sideEffects.memory = SideEffects::None;
|
||||||
m_functions["unreachable"_yulstring].sideEffects.otherState = SideEffects::None;
|
m_functions["unreachable"_yulstring].sideEffects.otherState = SideEffects::None;
|
||||||
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.canTerminate = false;
|
||||||
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.canRevert = true;
|
||||||
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.canContinue = false;
|
||||||
|
|
||||||
addFunction("datasize", {i64}, {i64}, true, {LiteralKind::String});
|
addFunction("datasize", {i64}, {i64}, true, {LiteralKind::String});
|
||||||
addFunction("dataoffset", {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", "log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
||||||
{"eth", "getBlockNumber", {}, {i64}},
|
{"eth", "getBlockNumber", {}, {i64}},
|
||||||
{"eth", "getTxOrigin", {i32ptr}, {}},
|
{"eth", "getTxOrigin", {i32ptr}, {}},
|
||||||
{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}},
|
{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false, false}},
|
||||||
{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}},
|
{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{false, true, false}},
|
||||||
{"eth", "getReturnDataSize", {}, {i32}},
|
{"eth", "getReturnDataSize", {}, {i32}},
|
||||||
{"eth", "returnDataCopy", {i32ptr, i32, i32}, {}},
|
{"eth", "returnDataCopy", {i32ptr, i32, i32}, {}},
|
||||||
{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}},
|
{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{false, true, false}},
|
||||||
{"eth", "getBlockTimestamp", {}, {i64}},
|
{"eth", "getBlockTimestamp", {}, {i64}},
|
||||||
{"debug", "print32", {i32}, {}},
|
{"debug", "print32", {i32}, {}},
|
||||||
{"debug", "print64", {i64}, {}},
|
{"debug", "print64", {i64}, {}},
|
||||||
@ -240,7 +241,7 @@ void WasmDialect::addExternals()
|
|||||||
// TODO some of them are side effect free.
|
// TODO some of them are side effect free.
|
||||||
f.sideEffects = SideEffects::worst();
|
f.sideEffects = SideEffects::worst();
|
||||||
f.sideEffects.cannotLoop = true;
|
f.sideEffects.cannotLoop = true;
|
||||||
f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminates;
|
f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminatesOrReverts();
|
||||||
f.controlFlowSideEffects = ext.controlFlowSideEffects;
|
f.controlFlowSideEffects = ext.controlFlowSideEffects;
|
||||||
f.isMSize = false;
|
f.isMSize = false;
|
||||||
f.literalArguments.clear();
|
f.literalArguments.clear();
|
||||||
|
@ -22,7 +22,7 @@ using namespace std;
|
|||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::yul;
|
using namespace solidity::yul;
|
||||||
|
|
||||||
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block& _block)
|
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block const& _block)
|
||||||
{
|
{
|
||||||
FunctionDefinitionCollector functionDefinitionCollector;
|
FunctionDefinitionCollector functionDefinitionCollector;
|
||||||
functionDefinitionCollector(_block);
|
functionDefinitionCollector(_block);
|
||||||
|
@ -34,7 +34,7 @@ namespace solidity::yul
|
|||||||
class FunctionDefinitionCollector: ASTWalker
|
class FunctionDefinitionCollector: ASTWalker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static std::map<YulString, FunctionDefinition const*> run(Block& _block);
|
static std::map<YulString, FunctionDefinition const*> run(Block const& _block);
|
||||||
private:
|
private:
|
||||||
using ASTWalker::operator();
|
using ASTWalker::operator();
|
||||||
void operator()(FunctionDefinition const& _functionDefinition) override;
|
void operator()(FunctionDefinition const& _functionDefinition) override;
|
||||||
|
@ -130,6 +130,8 @@ set(libyul_sources
|
|||||||
libyul/CompilabilityChecker.cpp
|
libyul/CompilabilityChecker.cpp
|
||||||
libyul/ControlFlowGraphTest.cpp
|
libyul/ControlFlowGraphTest.cpp
|
||||||
libyul/ControlFlowGraphTest.h
|
libyul/ControlFlowGraphTest.h
|
||||||
|
libyul/ControlFlowSideEffectsTest.cpp
|
||||||
|
libyul/ControlFlowSideEffectsTest.h
|
||||||
libyul/EVMCodeTransformTest.cpp
|
libyul/EVMCodeTransformTest.cpp
|
||||||
libyul/EVMCodeTransformTest.h
|
libyul/EVMCodeTransformTest.h
|
||||||
libyul/EwasmTranslationTest.cpp
|
libyul/EwasmTranslationTest.cpp
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include <test/libyul/YulOptimizerTest.h>
|
#include <test/libyul/YulOptimizerTest.h>
|
||||||
#include <test/libyul/YulInterpreterTest.h>
|
#include <test/libyul/YulInterpreterTest.h>
|
||||||
#include <test/libyul/ObjectCompilerTest.h>
|
#include <test/libyul/ObjectCompilerTest.h>
|
||||||
|
#include <test/libyul/ControlFlowSideEffectsTest.h>
|
||||||
#include <test/libyul/FunctionSideEffects.h>
|
#include <test/libyul/FunctionSideEffects.h>
|
||||||
#include <test/libyul/StackLayoutGeneratorTest.h>
|
#include <test/libyul/StackLayoutGeneratorTest.h>
|
||||||
#include <test/libyul/SyntaxTest.h>
|
#include <test/libyul/SyntaxTest.h>
|
||||||
@ -57,12 +58,12 @@ struct Testsuite
|
|||||||
Testsuite const g_interactiveTestsuites[] = {
|
Testsuite const g_interactiveTestsuites[] = {
|
||||||
/*
|
/*
|
||||||
Title Path Subpath SMT NeedsVM Creator function */
|
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 Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create},
|
||||||
{"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create},
|
{"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create},
|
||||||
{"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::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 Control Flow Graph", "libyul", "yulControlFlowGraph", false, false, &yul::test::ControlFlowGraphTest::create},
|
||||||
{"Yul Stack Layout", "libyul", "yulStackLayout", false, false, &yul::test::StackLayoutGeneratorTest::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},
|
{"Function Side Effects", "libyul", "functionSideEffects", false, false, &yul::test::FunctionSideEffects::create},
|
||||||
{"Yul Syntax", "libyul", "yulSyntaxTests", false, false, &yul::test::SyntaxTest::create},
|
{"Yul Syntax", "libyul", "yulSyntaxTests", false, false, &yul::test::SyntaxTest::create},
|
||||||
{"EVM Code Transform", "libyul", "evmCodeTransform", false, false, &yul::test::EVMCodeTransformTest::create, {"nooptions"}},
|
{"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 AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create},
|
||||||
{"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create},
|
{"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create},
|
||||||
{"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
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
|
@ -33,6 +33,7 @@ add_executable(isoltest
|
|||||||
../libsolidity/SMTCheckerTest.cpp
|
../libsolidity/SMTCheckerTest.cpp
|
||||||
../libyul/Common.cpp
|
../libyul/Common.cpp
|
||||||
../libyul/ControlFlowGraphTest.cpp
|
../libyul/ControlFlowGraphTest.cpp
|
||||||
|
../libyul/ControlFlowSideEffectsTest.cpp
|
||||||
../libyul/EVMCodeTransformTest.cpp
|
../libyul/EVMCodeTransformTest.cpp
|
||||||
../libyul/EwasmTranslationTest.cpp
|
../libyul/EwasmTranslationTest.cpp
|
||||||
../libyul/FunctionSideEffects.cpp
|
../libyul/FunctionSideEffects.cpp
|
||||||
|
Loading…
Reference in New Issue
Block a user