diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9a7fdc8bd..dffac622e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -124,6 +124,8 @@ set(libyul_sources libyul/Common.cpp libyul/Common.h libyul/CompilabilityChecker.cpp + libyul/ControlFlowGraphTest.cpp + libyul/ControlFlowGraphTest.h libyul/EVMCodeTransformTest.cpp libyul/EVMCodeTransformTest.h libyul/EwasmTranslationTest.cpp diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 68ca721a5..4ffdf5a74 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -55,20 +56,21 @@ 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}, - {"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"}}, - {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, - {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, - {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, - {"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} + {"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}, + {"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"}}, + {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, + {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, + {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, + {"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} }; } diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index 35dd0de26..74f82622c 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -53,14 +53,19 @@ Dialect const& defaultDialect(bool _yul) } } -void yul::test::printErrors(ErrorList const& _errors) +void yul::test::printErrors(ostream& _stream, ErrorList const& _errors) { - SourceReferenceFormatter formatter(cout, true, false); + SourceReferenceFormatter formatter(_stream, true, false); for (auto const& error: _errors) formatter.printErrorInformation(*error); } +void yul::test::printErrors(ErrorList const& _errors) +{ + printErrors(cout, _errors); +} + pair, shared_ptr> yul::test::parse(string const& _source, bool _yul) { diff --git a/test/libyul/Common.h b/test/libyul/Common.h index 57f659400..d207412c6 100644 --- a/test/libyul/Common.h +++ b/test/libyul/Common.h @@ -44,6 +44,7 @@ struct Dialect; namespace solidity::yul::test { +void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors); void printErrors(langutil::ErrorList const& _errors); std::pair, std::shared_ptr> diff --git a/test/libyul/ControlFlowGraphTest.cpp b/test/libyul/ControlFlowGraphTest.cpp new file mode 100644 index 000000000..819df3c17 --- /dev/null +++ b/test/libyul/ControlFlowGraphTest.cpp @@ -0,0 +1,207 @@ +/* + 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 + +using namespace solidity; +using namespace solidity::util; +using namespace solidity::langutil; +using namespace solidity::yul; +using namespace solidity::yul::test; +using namespace solidity::frontend; +using namespace solidity::frontend::test; +using namespace std; + +ControlFlowGraphTest::ControlFlowGraphTest(string const& _filename): + TestCase(_filename) +{ + m_source = m_reader.source(); + auto dialectName = m_reader.stringSetting("dialect", "evm"); + m_dialect = &dialect(dialectName, solidity::test::CommonOptions::get().evmVersion()); + m_expectation = m_reader.simpleExpectations(); +} + +namespace +{ +static std::string stackSlotToString(StackSlot const& _slot) +{ + return std::visit(util::GenericVisitor{ + [](FunctionCallReturnLabelSlot const& _ret) -> std::string { return "RET[" + _ret.call.get().functionName.name.str() + "]"; }, + [](FunctionReturnLabelSlot const&) -> std::string { return "RET"; }, + [](VariableSlot const& _var) { return _var.variable.get().name.str(); }, + [](LiteralSlot const& _lit) { return util::toCompactHexWithPrefix(_lit.value); }, + [](TemporarySlot const& _tmp) -> std::string { return "TMP[" + _tmp.call.get().functionName.name.str() + ", " + std::to_string(_tmp.index) + "]"; }, + [](JunkSlot const&) -> std::string { return "JUNK"; } + }, _slot); +} + +static std::string stackToString(Stack const& _stack) +{ + std::string result("[ "); + for (auto const& slot: _stack) + result += stackSlotToString(slot) + ' '; + result += ']'; + return result; +} +static std::string variableSlotToString(VariableSlot const& _slot) +{ + return _slot.variable.get().name.str(); +} +} + +class ControlFlowGraphPrinter +{ +public: + ControlFlowGraphPrinter(std::ostream& _stream): + m_stream(_stream) + { + } + void operator()(CFG::BasicBlock const& _block) + { + getBlockId(_block); + while (!m_blocksToPrint.empty()) + { + CFG::BasicBlock const* block = *m_blocksToPrint.begin(); + m_blocksToPrint.erase(m_blocksToPrint.begin()); + printBlock(*block); + } + + } + void operator()( + CFG::FunctionInfo const& _info + ) + { + m_stream << m_indent << "function " << _info.function.name.str() << "("; + m_stream << joinHumanReadable(_info.parameters | ranges::views::transform(variableSlotToString)); + m_stream << ")"; + if (!_info.returnVariables.empty()) + { + m_stream << " -> "; + m_stream << joinHumanReadable(_info.returnVariables | ranges::views::transform(variableSlotToString)); + } + m_stream << ":\n"; + ScopedSaveAndRestore linePrefixRestore(m_indent, m_indent + " "); + (*this)(*_info.entry); + } + +private: + void printBlock(CFG::BasicBlock const& _block) + { + m_stream << m_indent << "Block " << getBlockId(_block) << ":\n"; + ScopedSaveAndRestore linePrefixRestore(m_indent, m_indent + " "); + + m_stream << m_indent << "Entries: "; + if (_block.entries.empty()) + m_stream << "None\n"; + else + m_stream << joinHumanReadable(_block.entries | ranges::views::transform([&](auto const* _entry) { + return to_string(getBlockId(*_entry)); + })) << "\n"; + + for (auto const& operation: _block.operations) + { + m_stream << m_indent; + std::visit(util::GenericVisitor{ + [&](CFG::FunctionCall const& _call) { + m_stream << _call.function.get().name.str() << ": "; + }, + [&](CFG::BuiltinCall const& _call) { + m_stream << _call.functionCall.get().functionName.name.str() << ": "; + + }, + [&](CFG::Assignment const& _assignment) { + m_stream << "Assignment("; + m_stream << joinHumanReadable(_assignment.variables | ranges::views::transform(variableSlotToString)); + m_stream << "): "; + } + }, operation.operation); + m_stream << stackToString(operation.input) << " => " << stackToString(operation.output) << "\n"; + } + std::visit(util::GenericVisitor{ + [&](CFG::BasicBlock::MainExit const&) + { + m_stream << m_indent << "MainExit\n"; + }, + [&](CFG::BasicBlock::Jump const& _jump) + { + m_stream << m_indent << "Jump" << (_jump.backwards ? " (backwards): " : ": ") << getBlockId(*_jump.target) << "\n"; + }, + [&](CFG::BasicBlock::ConditionalJump const& _conditionalJump) + { + m_stream << m_indent << "ConditionalJump " << stackSlotToString(_conditionalJump.condition) << ":\n"; + m_stream << m_indent << " NonZero: " << getBlockId(*_conditionalJump.nonZero) << "\n"; + m_stream << m_indent << " Zero: " << getBlockId(*_conditionalJump.zero) << "\n"; + }, + [&](CFG::BasicBlock::FunctionReturn const& _return) + { + m_stream << m_indent << "FunctionReturn of " << _return.info->function.name.str() << "\n"; + }, + [&](CFG::BasicBlock::Terminated const&) + { + m_stream << m_indent << "Terminated\n"; + } + }, _block.exit); + } + size_t getBlockId(CFG::BasicBlock const& _block) + { + if (size_t *id = util::valueOrNullptr(m_blockIds, &_block)) + return *id; + size_t id = m_blockIds[&_block] = m_blockCount++; + m_blocksToPrint.emplace_back(&_block); + return id; + } + std::ostream& m_stream; + std::string m_indent; + std::map m_blockIds; + size_t m_blockCount = 0; + std::list m_blocksToPrint; +}; + +TestCase::TestResult ControlFlowGraphTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + ErrorList errors; + auto [object, analysisInfo] = parse(m_source, *m_dialect, errors); + if (!object || !analysisInfo || !Error::containsOnlyWarnings(errors)) + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; + printErrors(errors); + return TestResult::FatalError; + } + + std::ostringstream output; + + std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, *object->code); + + ControlFlowGraphPrinter{output}(*cfg->entry); + for (auto function: cfg->functions) + ControlFlowGraphPrinter{output}(cfg->functionInfo.at(function)); + + m_obtainedResult = output.str(); + + return checkResult(_stream, _linePrefix, _formatted); +} diff --git a/test/libyul/ControlFlowGraphTest.h b/test/libyul/ControlFlowGraphTest.h new file mode 100644 index 000000000..44cea06d3 --- /dev/null +++ b/test/libyul/ControlFlowGraphTest.h @@ -0,0 +1,43 @@ +/* + 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 + +namespace solidity::yul +{ +struct Dialect; + +namespace test +{ + +class ControlFlowGraphTest: public solidity::frontend::test::TestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::make_unique(_config.filename); + } + explicit ControlFlowGraphTest(std::string const& _filename); + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; +private: + Dialect const* m_dialect = nullptr; +}; +} +} diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 36d9cefc2..2ba64bba1 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -120,11 +120,3 @@ std::pair, std::shared_ptr> YulOptimize } return {std::move(object), std::move(analysisInfo)}; } - -void YulOptimizerTest::printErrors(ostream& _stream, ErrorList const& _errors) -{ - SourceReferenceFormatter formatter(_stream, true, false); - - for (auto const& error: _errors) - formatter.printErrorInformation(*error); -} diff --git a/test/libyul/YulOptimizerTest.h b/test/libyul/YulOptimizerTest.h index b4ba4ca7a..832b5dfb8 100644 --- a/test/libyul/YulOptimizerTest.h +++ b/test/libyul/YulOptimizerTest.h @@ -51,7 +51,6 @@ private: std::pair, std::shared_ptr> parse( std::ostream& _stream, std::string const& _linePrefix, bool const _formatted, std::string const& _source ); - static void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors); std::string m_optimizerStep; diff --git a/test/libyul/yulControlFlowGraph/for.yul b/test/libyul/yulControlFlowGraph/for.yul new file mode 100644 index 000000000..1259cf5ac --- /dev/null +++ b/test/libyul/yulControlFlowGraph/for.yul @@ -0,0 +1,31 @@ +{ + sstore(0x01, 0x0101) + for { sstore(0x02, 0x0202) } sload(0x03) { sstore(0x04, 0x0404) } { + sstore(0x05, 0x0505) + } + sstore(0x06, 0x0506) +} +// ---- +// Block 0: +// Entries: None +// sstore: [ 0x0101 0x01 ] => [ ] +// sstore: [ 0x0202 0x02 ] => [ ] +// Jump: 1 +// Block 1: +// Entries: 0, 2 +// sload: [ 0x03 ] => [ TMP[sload, 0] ] +// ConditionalJump TMP[sload, 0]: +// NonZero: 3 +// Zero: 4 +// Block 2: +// Entries: 3 +// sstore: [ 0x0404 0x04 ] => [ ] +// Jump (backwards): 1 +// Block 3: +// Entries: 1 +// sstore: [ 0x0505 0x05 ] => [ ] +// Jump: 2 +// Block 4: +// Entries: 1 +// sstore: [ 0x0506 0x06 ] => [ ] +// MainExit diff --git a/test/libyul/yulControlFlowGraph/function.yul b/test/libyul/yulControlFlowGraph/function.yul new file mode 100644 index 000000000..ccc77de94 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/function.yul @@ -0,0 +1,54 @@ +{ + function f(a, b) -> r { + let x := add(a,b) + r := sub(x,a) + } + function g() { + sstore(0x01, 0x0101) + } + function h(x) { + h(f(x, 0)) + g() + } + function i() -> v, w { + v := 0x0202 + w := 0x0303 + } + let x, y := i() + h(x) + h(y) +} +// ---- +// Block 0: +// Entries: None +// i: [ RET[i] ] => [ TMP[i, 0] TMP[i, 1] ] +// Assignment(x, y): [ TMP[i, 0] TMP[i, 1] ] => [ x y ] +// h: [ RET[h] x ] => [ ] +// h: [ RET[h] y ] => [ ] +// MainExit +// function f(a, b) -> r: +// Block 0: +// Entries: None +// add: [ b a ] => [ TMP[add, 0] ] +// Assignment(x): [ TMP[add, 0] ] => [ x ] +// sub: [ a x ] => [ TMP[sub, 0] ] +// Assignment(r): [ TMP[sub, 0] ] => [ r ] +// FunctionReturn of f +// function g(): +// Block 0: +// Entries: None +// sstore: [ 0x0101 0x01 ] => [ ] +// FunctionReturn of g +// function h(x): +// Block 0: +// Entries: None +// f: [ RET[f] 0x00 x ] => [ TMP[f, 0] ] +// h: [ RET[h] TMP[f, 0] ] => [ ] +// g: [ RET[g] ] => [ ] +// FunctionReturn of h +// function i() -> v, w: +// Block 0: +// Entries: None +// Assignment(v): [ 0x0202 ] => [ v ] +// Assignment(w): [ 0x0303 ] => [ w ] +// FunctionReturn of i diff --git a/test/libyul/yulControlFlowGraph/if.yul b/test/libyul/yulControlFlowGraph/if.yul new file mode 100644 index 000000000..d3e15d550 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/if.yul @@ -0,0 +1,23 @@ +{ + sstore(0x01, 0x0101) + if calldataload(0) { + sstore(0x02, 0x0202) + } + sstore(0x03, 0x003) +} +// ---- +// Block 0: +// Entries: None +// sstore: [ 0x0101 0x01 ] => [ ] +// calldataload: [ 0x00 ] => [ TMP[calldataload, 0] ] +// ConditionalJump TMP[calldataload, 0]: +// NonZero: 1 +// Zero: 2 +// Block 1: +// Entries: 0 +// sstore: [ 0x0202 0x02 ] => [ ] +// Jump: 2 +// Block 2: +// Entries: 0, 1 +// sstore: [ 0x03 0x03 ] => [ ] +// MainExit diff --git a/test/libyul/yulControlFlowGraph/stub.yul b/test/libyul/yulControlFlowGraph/stub.yul new file mode 100644 index 000000000..6364a6bea --- /dev/null +++ b/test/libyul/yulControlFlowGraph/stub.yul @@ -0,0 +1,6 @@ +{ +} +// ---- +// Block 0: +// Entries: None +// MainExit diff --git a/test/libyul/yulControlFlowGraph/switch.yul b/test/libyul/yulControlFlowGraph/switch.yul new file mode 100644 index 000000000..2ebef90a2 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/switch.yul @@ -0,0 +1,46 @@ +{ + sstore(0, 0) + switch sload(0) + case 0 { + sstore(0x01, 0x0101) + } + case 1 { + sstore(0x02, 0x0101) + } + default { + sstore(0x03, 0x0101) + } + sstore(0x04, 0x0101) +} +// ---- +// Block 0: +// Entries: None +// sstore: [ 0x00 0x00 ] => [ ] +// sload: [ 0x00 ] => [ TMP[sload, 0] ] +// Assignment(GHOST[0]): [ TMP[sload, 0] ] => [ GHOST[0] ] +// eq: [ GHOST[0] 0x00 ] => [ TMP[eq, 0] ] +// ConditionalJump TMP[eq, 0]: +// NonZero: 1 +// Zero: 2 +// Block 1: +// Entries: 0 +// sstore: [ 0x0101 0x01 ] => [ ] +// Jump: 3 +// Block 2: +// Entries: 0 +// eq: [ GHOST[0] 0x01 ] => [ TMP[eq, 0] ] +// ConditionalJump TMP[eq, 0]: +// NonZero: 4 +// Zero: 5 +// Block 3: +// Entries: 1, 4, 5 +// sstore: [ 0x0101 0x04 ] => [ ] +// MainExit +// Block 4: +// Entries: 2 +// sstore: [ 0x0101 0x02 ] => [ ] +// Jump: 3 +// Block 5: +// Entries: 2 +// sstore: [ 0x0101 0x03 ] => [ ] +// Jump: 3 diff --git a/test/libyul/yulControlFlowGraph/variables.yul b/test/libyul/yulControlFlowGraph/variables.yul new file mode 100644 index 000000000..717a7975e --- /dev/null +++ b/test/libyul/yulControlFlowGraph/variables.yul @@ -0,0 +1,22 @@ +{ + let x := calldataload(0) + let y := calldataload(2) + + x := calldataload(3) + y := calldataload(4) + + sstore(x,y) +} +// ---- +// Block 0: +// Entries: None +// calldataload: [ 0x00 ] => [ TMP[calldataload, 0] ] +// Assignment(x): [ TMP[calldataload, 0] ] => [ x ] +// calldataload: [ 0x02 ] => [ TMP[calldataload, 0] ] +// Assignment(y): [ TMP[calldataload, 0] ] => [ y ] +// calldataload: [ 0x03 ] => [ TMP[calldataload, 0] ] +// Assignment(x): [ TMP[calldataload, 0] ] => [ x ] +// calldataload: [ 0x04 ] => [ TMP[calldataload, 0] ] +// Assignment(y): [ TMP[calldataload, 0] ] => [ y ] +// sstore: [ y x ] => [ ] +// MainExit diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 95eb3a636..73f04e097 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(isoltest ../libsolidity/ASTJSONTest.cpp ../libsolidity/SMTCheckerTest.cpp ../libyul/Common.cpp + ../libyul/ControlFlowGraphTest.cpp ../libyul/EVMCodeTransformTest.cpp ../libyul/EwasmTranslationTest.cpp ../libyul/FunctionSideEffects.cpp