From 9417d6775f25da81cdd57ff59a0ea05be16e6a97 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 30 Sep 2021 17:01:54 +0200 Subject: [PATCH 1/2] Control flow side-effects for user-defined functions. --- libsolidity/analysis/ControlFlowBuilder.cpp | 14 +- libyul/CMakeLists.txt | 3 + libyul/ControlFlowSideEffects.h | 28 +- libyul/ControlFlowSideEffectsCollector.cpp | 274 ++++++++++++++++++ libyul/ControlFlowSideEffectsCollector.h | 130 +++++++++ libyul/Dialect.h | 2 +- libyul/SideEffects.h | 2 +- .../backends/evm/ControlFlowGraphBuilder.cpp | 2 +- libyul/backends/evm/EVMDialect.cpp | 16 +- .../evm/OptimizedEVMCodeTransform.cpp | 2 +- libyul/backends/wasm/WasmDialect.cpp | 13 +- .../optimiser/FunctionDefinitionCollector.cpp | 2 +- .../optimiser/FunctionDefinitionCollector.h | 2 +- test/CMakeLists.txt | 2 + test/InteractiveTests.h | 6 +- test/libyul/ControlFlowSideEffectsTest.cpp | 79 +++++ test/libyul/ControlFlowSideEffectsTest.h | 40 +++ .../simple_conditionals.yul | 34 +++ .../simple_functions.yul | 17 ++ test/tools/CMakeLists.txt | 1 + 20 files changed, 637 insertions(+), 32 deletions(-) create mode 100644 libyul/ControlFlowSideEffectsCollector.cpp create mode 100644 libyul/ControlFlowSideEffectsCollector.h create mode 100644 test/libyul/ControlFlowSideEffectsTest.cpp create mode 100644 test/libyul/ControlFlowSideEffectsTest.h create mode 100644 test/libyul/controlFlowSideEffects/simple_conditionals.yul create mode 100644 test/libyul/controlFlowSideEffects/simple_functions.yul diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index 2d03aaf9c..ff8e2a29a 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -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&) diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 0ddb9c64a..30b97b5f7 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -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 diff --git a/libyul/ControlFlowSideEffects.h b/libyul/ControlFlowSideEffects.h index 5e22ad862..d4debab93 100644 --- a/libyul/ControlFlowSideEffects.h +++ b/libyul/ControlFlowSideEffects.h @@ -18,22 +18,32 @@ #pragma once -#include - 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; + } }; } diff --git a/libyul/ControlFlowSideEffectsCollector.cpp b/libyul/ControlFlowSideEffectsCollector.cpp new file mode 100644 index 000000000..047480f14 --- /dev/null +++ b/libyul/ControlFlowSideEffectsCollector.cpp @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace solidity::yul; + + +ControlFlowBuilder::ControlFlowBuilder(Block const& _ast) +{ + for (auto const& statement: _ast.statements) + if (auto const* function = get_if(&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()); + 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 _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& 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); +} + diff --git a/libyul/ControlFlowSideEffectsCollector.h b/libyul/ControlFlowSideEffectsCollector.h new file mode 100644 index 000000000..f130294ba --- /dev/null +++ b/libyul/ControlFlowSideEffectsCollector.h @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace solidity::yul +{ + +struct Dialect; + +struct ControlFlowNode +{ + std::vector successors; + /// Name of the called function if the node calls a function. + std::optional 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 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> m_nodes; + + ControlFlowNode* m_currentNode = nullptr; + ControlFlowNode const* m_leave = nullptr; + ControlFlowNode const* m_break = nullptr; + ControlFlowNode const* m_continue = nullptr; + + std::map m_functionFlows; +}; + + +/** + * Requires: Disambiguator, Function Hoister. + */ +class ControlFlowSideEffectsCollector +{ +public: + explicit ControlFlowSideEffectsCollector( + Dialect const& _dialect, + Block const& _ast + ); + + std::map 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 m_functionSideEffects; + std::map> m_pendingNodes; + std::map> m_processedNodes; + /// `x` is in `m_functionCalls[y]` if a direct call to `x` is reachable inside `y` + std::map> m_functionCalls; +}; + + +} diff --git a/libyul/Dialect.h b/libyul/Dialect.h index 87deb04b9..e7a5e4827 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -22,8 +22,8 @@ #pragma once #include -#include #include +#include #include #include diff --git a/libyul/SideEffects.h b/libyul/SideEffects.h index 7433e40d2..b6df8df83 100644 --- a/libyul/SideEffects.h +++ b/libyul/SideEffects.h @@ -94,7 +94,7 @@ struct SideEffects cannotLoop && _other.cannotLoop, otherState + _other.otherState, storage + _other.storage, - memory + _other.memory + memory + _other.memory }; } diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.cpp b/libyul/backends/evm/ControlFlowGraphBuilder.cpp index 2c61bfb89..7e57b8257 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.cpp +++ b/libyul/backends/evm/ControlFlowGraphBuilder.cpp @@ -229,7 +229,7 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt) // not only for builtins. if (auto const* funCall = get_if(&_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)); diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index c90784a03..0fefb24d0 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -57,8 +57,20 @@ pair createEVMFunction( f.parameters.resize(static_cast(info.args)); f.returns.resize(static_cast(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; diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp index 106e636c8..ca57c59d2 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp @@ -484,7 +484,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block) yulAssert(!_block.operations.empty(), ""); CFG::BuiltinCall const* builtinCall = get_if(&_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. diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index faaf366c1..817d53467 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -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(); diff --git a/libyul/optimiser/FunctionDefinitionCollector.cpp b/libyul/optimiser/FunctionDefinitionCollector.cpp index 70f2a268d..dff57a32a 100644 --- a/libyul/optimiser/FunctionDefinitionCollector.cpp +++ b/libyul/optimiser/FunctionDefinitionCollector.cpp @@ -22,7 +22,7 @@ using namespace std; using namespace solidity; using namespace solidity::yul; -map FunctionDefinitionCollector::run(Block& _block) +map FunctionDefinitionCollector::run(Block const& _block) { FunctionDefinitionCollector functionDefinitionCollector; functionDefinitionCollector(_block); diff --git a/libyul/optimiser/FunctionDefinitionCollector.h b/libyul/optimiser/FunctionDefinitionCollector.h index c97f20d67..c9828aced 100644 --- a/libyul/optimiser/FunctionDefinitionCollector.h +++ b/libyul/optimiser/FunctionDefinitionCollector.h @@ -34,7 +34,7 @@ namespace solidity::yul class FunctionDefinitionCollector: ASTWalker { public: - static std::map run(Block& _block); + static std::map run(Block const& _block); private: using ASTWalker::operator(); void operator()(FunctionDefinition const& _functionDefinition) override; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3135b53d5..3a0b5f7b6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index e27c00b84..d8f3ef07e 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -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} }; } diff --git a/test/libyul/ControlFlowSideEffectsTest.cpp b/test/libyul/ControlFlowSideEffectsTest.cpp new file mode 100644 index 000000000..aff3c564d --- /dev/null +++ b/test/libyul/ControlFlowSideEffectsTest.cpp @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include + +#include +#include +#include +#include + +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 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 sideEffects = + ControlFlowSideEffectsCollector( + EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()), + *obj.code + ).functionSideEffects(); + + std::map 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); +} diff --git a/test/libyul/ControlFlowSideEffectsTest.h b/test/libyul/ControlFlowSideEffectsTest.h new file mode 100644 index 000000000..f91378e55 --- /dev/null +++ b/test/libyul/ControlFlowSideEffectsTest.h @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include +#include +#include +#include + +namespace solidity::yul::test +{ + +class ControlFlowSideEffectsTest: public solidity::frontend::test::TestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { return std::make_unique(_config.filename); } + explicit ControlFlowSideEffectsTest(std::string const& _filename); + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; +}; + +} diff --git a/test/libyul/controlFlowSideEffects/simple_conditionals.yul b/test/libyul/controlFlowSideEffects/simple_conditionals.yul new file mode 100644 index 000000000..ec15193df --- /dev/null +++ b/test/libyul/controlFlowSideEffects/simple_conditionals.yul @@ -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 diff --git a/test/libyul/controlFlowSideEffects/simple_functions.yul b/test/libyul/controlFlowSideEffects/simple_functions.yul new file mode 100644 index 000000000..b6b14d39d --- /dev/null +++ b/test/libyul/controlFlowSideEffects/simple_functions.yul @@ -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 diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 18b04afe8..18e3d4377 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -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 From 2c2269d30024f7859e40ab400863a3829424059f Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 4 Oct 2021 18:09:08 +0200 Subject: [PATCH 2/2] Tests --- .../controlFlowSideEffects/eval_order.yul | 19 +++++++ .../controlFlowSideEffects/for_loop.yul | 55 +++++++++++++++++++ test/libyul/controlFlowSideEffects/leave.yul | 13 +++++ .../controlFlowSideEffects/recursion.yul | 37 +++++++++++++ test/libyul/controlFlowSideEffects/switch.yul | 41 ++++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 test/libyul/controlFlowSideEffects/eval_order.yul create mode 100644 test/libyul/controlFlowSideEffects/for_loop.yul create mode 100644 test/libyul/controlFlowSideEffects/leave.yul create mode 100644 test/libyul/controlFlowSideEffects/recursion.yul create mode 100644 test/libyul/controlFlowSideEffects/switch.yul diff --git a/test/libyul/controlFlowSideEffects/eval_order.yul b/test/libyul/controlFlowSideEffects/eval_order.yul new file mode 100644 index 000000000..a207d7a19 --- /dev/null +++ b/test/libyul/controlFlowSideEffects/eval_order.yul @@ -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 diff --git a/test/libyul/controlFlowSideEffects/for_loop.yul b/test/libyul/controlFlowSideEffects/for_loop.yul new file mode 100644 index 000000000..1f3714a48 --- /dev/null +++ b/test/libyul/controlFlowSideEffects/for_loop.yul @@ -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 diff --git a/test/libyul/controlFlowSideEffects/leave.yul b/test/libyul/controlFlowSideEffects/leave.yul new file mode 100644 index 000000000..bbc977820 --- /dev/null +++ b/test/libyul/controlFlowSideEffects/leave.yul @@ -0,0 +1,13 @@ +{ + function a() { + revert(0, 0) + leave + } + function b() { + leave + revert(0, 0) + } +} +// ---- +// a: can revert +// b: can continue diff --git a/test/libyul/controlFlowSideEffects/recursion.yul b/test/libyul/controlFlowSideEffects/recursion.yul new file mode 100644 index 000000000..c4176d502 --- /dev/null +++ b/test/libyul/controlFlowSideEffects/recursion.yul @@ -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: diff --git a/test/libyul/controlFlowSideEffects/switch.yul b/test/libyul/controlFlowSideEffects/switch.yul new file mode 100644 index 000000000..63b45ce30 --- /dev/null +++ b/test/libyul/controlFlowSideEffects/switch.yul @@ -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