diff --git a/libdevcore/Algorithms.h b/libdevcore/Algorithms.h index 34a4dfe5a..b39939928 100644 --- a/libdevcore/Algorithms.h +++ b/libdevcore/Algorithms.h @@ -78,17 +78,18 @@ private: /** * Generic breadth first search. * + * Note that V needs to be a comparable value type. If it is not, use a pointer type, + * but note that this might lead to non-deterministic traversal. + * * Example: Gather all (recursive) children in a graph starting at (and including) ``root``: * * Node const* root = ...; - * std::set allNodes = BreadthFirstSearch{{root}}.run([](Node const& _node, auto&& _addChild) { + * std::set allNodes = BreadthFirstSearch{{root}}.run([](Node const* _node, auto&& _addChild) { * // Potentially process ``_node``. - * for (Node const& _child: _node.children()) + * for (Node const& _child: _node->children()) * // Potentially filter the children to be visited. - * _addChild(_child); + * _addChild(&_child); * }).visited; - * - * Note that the order of the traversal is *non-deterministic* (the children are stored in a std::set of pointers). */ template struct BreadthFirstSearch @@ -102,20 +103,20 @@ struct BreadthFirstSearch { while (!verticesToTraverse.empty()) { - V const* v = *verticesToTraverse.begin(); + V v = *verticesToTraverse.begin(); verticesToTraverse.erase(verticesToTraverse.begin()); visited.insert(v); - _forEachChild(*v, [this](V const& _vertex) { - if (!visited.count(&_vertex)) - verticesToTraverse.insert(&_vertex); + _forEachChild(v, [this](V _vertex) { + if (!visited.count(_vertex)) + verticesToTraverse.emplace(std::move(_vertex)); }); } return *this; } - std::set verticesToTraverse; - std::set visited{}; + std::set verticesToTraverse; + std::set visited{}; }; } diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp index ccc2e8a3c..66006d808 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -151,22 +151,22 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const { // collect all nodes reachable from the entry point - std::set reachable = BreadthFirstSearch{{_entry}}.run( - [](CFGNode const& _node, auto&& _addChild) { - for (CFGNode const* exit: _node.exits) - _addChild(*exit); + std::set reachable = BreadthFirstSearch{{_entry}}.run( + [](CFGNode const* _node, auto&& _addChild) { + for (CFGNode const* exit: _node->exits) + _addChild(exit); } ).visited; // traverse all paths backwards from exit and revert // and extract (valid) source locations of unreachable nodes into sorted set std::set unreachable; - BreadthFirstSearch{{_exit, _revert}}.run( - [&](CFGNode const& _node, auto&& _addChild) { - if (!reachable.count(&_node) && !_node.location.isEmpty()) - unreachable.insert(_node.location); - for (CFGNode const* entry: _node.entries) - _addChild(*entry); + BreadthFirstSearch{{_exit, _revert}}.run( + [&](CFGNode const* _node, auto&& _addChild) { + if (!reachable.count(_node) && !_node->location.isEmpty()) + unreachable.insert(_node->location); + for (CFGNode const* entry: _node->entries) + _addChild(entry); } ); diff --git a/libyul/SideEffects.h b/libyul/SideEffects.h index fff907bec..0a8e1595e 100644 --- a/libyul/SideEffects.h +++ b/libyul/SideEffects.h @@ -73,6 +73,16 @@ struct SideEffects *this = *this + _other; return *this; } + + bool operator==(SideEffects const& _other) const + { + return + movable == _other.movable && + sideEffectFree == _other.sideEffectFree && + sideEffectFreeIfNoMSize == _other.sideEffectFreeIfNoMSize && + invalidatesStorage == _other.invalidatesStorage && + invalidatesMemory == _other.invalidatesMemory; + } }; } diff --git a/libyul/optimiser/CallGraphGenerator.cpp b/libyul/optimiser/CallGraphGenerator.cpp index 0e2a63578..0c11464ee 100644 --- a/libyul/optimiser/CallGraphGenerator.cpp +++ b/libyul/optimiser/CallGraphGenerator.cpp @@ -29,18 +29,24 @@ using namespace std; using namespace dev; using namespace yul; +map> CallGraphGenerator::callGraph(Block const& _ast) +{ + CallGraphGenerator gen; + gen(_ast); + return std::move(gen.m_callGraph); +} void CallGraphGenerator::operator()(FunctionalInstruction const& _functionalInstruction) { - m_callGraph.insert(m_currentFunction, YulString{ - dev::eth::instructionInfo(_functionalInstruction.instruction).name - }); + string name = dev::eth::instructionInfo(_functionalInstruction.instruction).name; + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); + m_callGraph[m_currentFunction].insert(YulString{name}); ASTWalker::operator()(_functionalInstruction); } void CallGraphGenerator::operator()(FunctionCall const& _functionCall) { - m_callGraph.insert(m_currentFunction, _functionCall.functionName.name); + m_callGraph[m_currentFunction].insert(_functionCall.functionName.name); ASTWalker::operator()(_functionCall); } @@ -48,7 +54,14 @@ void CallGraphGenerator::operator()(FunctionDefinition const& _functionDefinitio { YulString previousFunction = m_currentFunction; m_currentFunction = _functionDefinition.name; + yulAssert(m_callGraph.count(m_currentFunction) == 0, ""); + m_callGraph[m_currentFunction] = {}; ASTWalker::operator()(_functionDefinition); m_currentFunction = previousFunction; } +CallGraphGenerator::CallGraphGenerator() +{ + m_callGraph[YulString{}] = {}; +} + diff --git a/libyul/optimiser/CallGraphGenerator.h b/libyul/optimiser/CallGraphGenerator.h index 212d29d04..6946721bd 100644 --- a/libyul/optimiser/CallGraphGenerator.h +++ b/libyul/optimiser/CallGraphGenerator.h @@ -40,8 +40,7 @@ namespace yul class CallGraphGenerator: public ASTWalker { public: - /// @returns the call graph of the visited AST. - InvertibleRelation const& callGraph() const { return m_callGraph; } + static std::map> callGraph(Block const& _ast); using ASTWalker::operator(); void operator()(FunctionalInstruction const& _functionalInstruction) override; @@ -49,7 +48,9 @@ public: void operator()(FunctionDefinition const& _functionDefinition) override; private: - InvertibleRelation m_callGraph; + CallGraphGenerator(); + + std::map> m_callGraph; /// The name of the function we are currently visiting during traversal. YulString m_currentFunction; }; diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 8fe3cc474..b4f3fc10a 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -28,6 +28,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -93,6 +94,33 @@ void MSizeFinder::operator()(FunctionCall const& _functionCall) m_msizeFound = true; } + +map SideEffectsPropagator::sideEffects( + Dialect const& _dialect, + map> const& _directCallGraph +) +{ + map ret; + for (auto const& call: _directCallGraph) + { + YulString funName = call.first; + SideEffects sideEffects; + BreadthFirstSearch{call.second, {funName}}.run( + [&](YulString _function, auto&& _addChild) { + if (sideEffects == SideEffects::worst()) + return; + if (BuiltinFunction const* f = _dialect.builtin(_function)) + sideEffects += f->sideEffects; + else + for (YulString callee: _directCallGraph.at(_function)) + _addChild(callee); + } + ); + ret[funName] = sideEffects; + } + return ret; +} + MovableChecker::MovableChecker(Dialect const& _dialect, Expression const& _expression): MovableChecker(_dialect) { diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index a924fe5ff..e8cfc615e 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -62,6 +62,21 @@ private: SideEffects m_sideEffects; }; +/** + * This class can be used to determine the side-effects of user-defined functions. + * + * It is given a dialect and a mapping that represents the direct calls from user-defined + * functions to other user-defined functions and built-in functions. + */ +class SideEffectsPropagator +{ +public: + static std::map sideEffects( + Dialect const& _dialect, + std::map> const& _directCallGraph + ); +}; + /** * Class that can be used to find out if certain code contains the MSize instruction. * diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index b12c0a0ca..b75be49e7 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -56,6 +57,7 @@ Testsuite const g_interactiveTestsuites[] = { {"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}, + {"Function Side Effects","libyul", "functionSideEffects", false, false, &yul::test::FunctionSideEffects::create}, {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, diff --git a/test/libyul/FunctionSideEffects.cpp b/test/libyul/FunctionSideEffects.cpp new file mode 100644 index 000000000..6ae6df8ab --- /dev/null +++ b/test/libyul/FunctionSideEffects.cpp @@ -0,0 +1,125 @@ +/* + 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 . +*/ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include + + +using namespace dev; +using namespace langutil; +using namespace yul; +using namespace yul::test; +using namespace dev::solidity; +using namespace dev::solidity::test; +using namespace std; + +namespace +{ +string toString(SideEffects const& _sideEffects) +{ + vector ret; + if (_sideEffects.movable) + ret.push_back("movable"); + if (_sideEffects.sideEffectFree) + ret.push_back("sideEffectFree"); + if (_sideEffects.sideEffectFreeIfNoMSize) + ret.push_back("sideEffectFreeIfNoMSize"); + if (_sideEffects.invalidatesStorage) + ret.push_back("invalidatesStorage"); + if (_sideEffects.invalidatesMemory) + ret.push_back("invalidatesMemory"); + return joinHumanReadable(ret); +} +} + +FunctionSideEffects::FunctionSideEffects(string const& _filename) +{ + ifstream file(_filename); + if (!file) + BOOST_THROW_EXCEPTION(runtime_error("Cannot open test input: \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + m_source = parseSourceAndSettings(file); + m_expectation = parseSimpleExpectations(file);} + +TestCase::TestResult FunctionSideEffects::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.")); + + map functionSideEffects = SideEffectsPropagator::sideEffects( + EVMDialect::strictAssemblyForEVM(langutil::EVMVersion()), + CallGraphGenerator::callGraph(*obj.code) + ); + + std::map functionSideEffectsStr; + for (auto const& fun: functionSideEffects) + functionSideEffectsStr[fun.first.str()] = toString(fun.second); + + m_obtainedResult.clear(); + for (auto const& fun: functionSideEffectsStr) + m_obtainedResult += fun.first + ": " + fun.second + "\n"; + + if (m_expectation != m_obtainedResult) + { + string nextIndentLevel = _linePrefix + " "; + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Expected result:" << endl; + printIndented(_stream, m_expectation, nextIndentLevel); + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Obtained result:" << endl; + printIndented(_stream, m_obtainedResult, nextIndentLevel); + return TestResult::Failure; + } + return TestResult::Success; +} + + +void FunctionSideEffects::printSource(ostream& _stream, string const& _linePrefix, bool const) const +{ + printIndented(_stream, m_source, _linePrefix); +} + +void FunctionSideEffects::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const +{ + printIndented(_stream, m_obtainedResult, _linePrefix); +} + +void FunctionSideEffects::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const +{ + stringstream output(_output); + string line; + while (getline(output, line)) + if (line.empty()) + // Avoid trailing spaces. + _stream << boost::trim_right_copy(_linePrefix) << endl; + else + _stream << _linePrefix << line << endl; +} diff --git a/test/libyul/FunctionSideEffects.h b/test/libyul/FunctionSideEffects.h new file mode 100644 index 000000000..8fad2961c --- /dev/null +++ b/test/libyul/FunctionSideEffects.h @@ -0,0 +1,54 @@ +/* + 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 . +*/ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace yul +{ +namespace test +{ + +class FunctionSideEffects: public dev::solidity::test::TestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { return std::unique_ptr(new FunctionSideEffects(_config.filename)); } + explicit FunctionSideEffects(std::string const& _filename); + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + + void printSource(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) const override; + void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; + +private: + void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; + + std::string m_source; + std::string m_expectation; + std::string m_obtainedResult; +}; + +} +} diff --git a/test/libyul/functionSideEffects/cyclic_graph.yul b/test/libyul/functionSideEffects/cyclic_graph.yul new file mode 100644 index 000000000..742d74087 --- /dev/null +++ b/test/libyul/functionSideEffects/cyclic_graph.yul @@ -0,0 +1,10 @@ +{ + function a() { b() } + function b() { c() } + function c() { b() } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: movable, sideEffectFree, sideEffectFreeIfNoMSize +// b: movable, sideEffectFree, sideEffectFreeIfNoMSize +// c: movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/doubly_recursive_function.yul b/test/libyul/functionSideEffects/doubly_recursive_function.yul new file mode 100644 index 000000000..cbc594f38 --- /dev/null +++ b/test/libyul/functionSideEffects/doubly_recursive_function.yul @@ -0,0 +1,8 @@ +{ + function a() { b() } + function b() { a() } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: movable, sideEffectFree, sideEffectFreeIfNoMSize +// b: movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/empty.yul b/test/libyul/functionSideEffects/empty.yul new file mode 100644 index 000000000..9e85e64b7 --- /dev/null +++ b/test/libyul/functionSideEffects/empty.yul @@ -0,0 +1,4 @@ +{ +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/empty_with_sstore.yul b/test/libyul/functionSideEffects/empty_with_sstore.yul new file mode 100644 index 000000000..42a1b564c --- /dev/null +++ b/test/libyul/functionSideEffects/empty_with_sstore.yul @@ -0,0 +1,5 @@ +{ + sstore(0, 1) +} +// ---- +// : invalidatesStorage diff --git a/test/libyul/functionSideEffects/multi_calls.yul b/test/libyul/functionSideEffects/multi_calls.yul new file mode 100644 index 000000000..f60b15e3d --- /dev/null +++ b/test/libyul/functionSideEffects/multi_calls.yul @@ -0,0 +1,22 @@ +{ + function a() { + b() + } + function b() { + sstore(0, 1) + b() + } + function c() { + mstore(0, 1) + a() + d() + } + function d() { + } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: invalidatesStorage +// b: invalidatesStorage +// c: invalidatesStorage, invalidatesMemory +// d: movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/recursive_function.yul b/test/libyul/functionSideEffects/recursive_function.yul new file mode 100644 index 000000000..cacefefb4 --- /dev/null +++ b/test/libyul/functionSideEffects/recursive_function.yul @@ -0,0 +1,6 @@ +{ + function a() { a() } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/simple_functions.yul b/test/libyul/functionSideEffects/simple_functions.yul new file mode 100644 index 000000000..fb0f5378e --- /dev/null +++ b/test/libyul/functionSideEffects/simple_functions.yul @@ -0,0 +1,14 @@ +{ + function a() {} + function f() { mstore(0, 1) } + function g() { sstore(0, 1) } + function h() { let x := msize() } + function i() { let z := mload(0) } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: movable, sideEffectFree, sideEffectFreeIfNoMSize +// f: invalidatesMemory +// g: invalidatesStorage +// h: sideEffectFree, sideEffectFreeIfNoMSize +// i: sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/structures.yul b/test/libyul/functionSideEffects/structures.yul new file mode 100644 index 000000000..de1fa5b0c --- /dev/null +++ b/test/libyul/functionSideEffects/structures.yul @@ -0,0 +1,40 @@ +{ + if calldataload(0) + { + f() + } + g() + + function f() { + pop(mload(0)) + } + function g() { + if sload(0) + { + h() + } + } + function h() { + switch t() + case 1 { + i() + } + } + function t() -> x { + mstore(0, 1) + } + function i() { + sstore(0, 1) + } + function r(a) -> b { + b := mul(a, 2) + } +} +// ---- +// : invalidatesStorage, invalidatesMemory +// f: sideEffectFreeIfNoMSize +// g: invalidatesStorage, invalidatesMemory +// h: invalidatesStorage, invalidatesMemory +// i: invalidatesStorage +// r: movable, sideEffectFree, sideEffectFreeIfNoMSize +// t: invalidatesMemory diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index a8922061b..f02b092bd 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -30,6 +30,8 @@ add_executable(isoltest ../libsolidity/ABIJsonTest.cpp ../libsolidity/ASTJSONTest.cpp ../libsolidity/SMTCheckerJSONTest.cpp + ../libyul/Common.cpp + ../libyul/FunctionSideEffects.cpp ../libyul/ObjectCompilerTest.cpp ../libyul/YulOptimizerTest.cpp ../libyul/YulInterpreterTest.cpp