diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c6c8040f8536..0ca70f9a6e0d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -79,6 +79,8 @@ set(libsolidity_sources libsolidity/ASTJSONTest.h libsolidity/ErrorCheck.cpp libsolidity/ErrorCheck.h + libsolidity/EthdebugTest.cpp + libsolidity/EthdebugTest.h libsolidity/FunctionDependencyGraphTest.cpp libsolidity/FunctionDependencyGraphTest.h libsolidity/GasCosts.cpp @@ -100,6 +102,8 @@ set(libsolidity_sources libsolidity/SemVerMatcher.cpp libsolidity/SMTCheckerTest.cpp libsolidity/SMTCheckerTest.h + libsolidity/StandardJSONTest.cpp + libsolidity/StandardJSONTest.h libsolidity/SolidityCompiler.cpp libsolidity/SolidityEndToEndTest.cpp libsolidity/SolidityExecutionFramework.cpp diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 063e21ccbc1f..e94a8d6f89b6 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -89,6 +90,7 @@ Testsuite const g_interactiveTestsuites[] = { {"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create}, {"JSON Natspec", "libsolidity", "natspecJSON", false, false, &NatspecJSONTest::create}, {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create}, + {"Ethdebug", "libsolidity", "ethdebugTests", false, false, &EthdebugTest::create}, {"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create}, {"Memory Guard", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create}, {"AST Properties", "libsolidity", "astPropertyTests", false, false, &ASTPropertyTest::create}, diff --git a/test/libsolidity/EthdebugTest.cpp b/test/libsolidity/EthdebugTest.cpp new file mode 100644 index 000000000000..7642b9239b74 --- /dev/null +++ b/test/libsolidity/EthdebugTest.cpp @@ -0,0 +1,132 @@ +/* + 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 + +using namespace solidity; +using namespace solidity::frontend; +using namespace solidity::frontend::test; +using namespace solidity::langutil; + +EthdebugTest::EthdebugTest(std::string const& _filename): + StandardJSONTest(_filename) +{ + m_optimise = m_reader.boolSetting("optimize", false); + m_optimiseYul = m_reader.boolSetting("optimize-yul", false); + m_useSSACFG = m_reader.boolSetting("compileViaSSACFG", false); + + auto revertStrings = revertStringsFromString(m_reader.stringSetting("revertStrings", "default")); + if (!revertStrings) + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid revertStrings setting.")); + m_revertStrings = *revertStrings; +} + +void EthdebugTest::setupCompiler(CompilerStack& _compiler) +{ + AnalysisFramework::setupCompiler(_compiler); + + _compiler.setViaIR(true); + _compiler.setExperimental(true); + if (m_useSSACFG) + _compiler.setViaSSACFG(true); + + DebugInfoSelection selection = DebugInfoSelection::Default(); + selection.enable("ethdebug"); + _compiler.selectDebugInfo(selection); + + _compiler.setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); + _compiler.setRevertStringBehaviour(m_revertStrings); + + OptimiserSettings settings = m_optimise ? OptimiserSettings::standard() : OptimiserSettings::minimal(); + if (m_optimiseYul) + settings.runYulOptimiser = true; + _compiler.setOptimiserSettings(settings); +} + +std::map EthdebugTest::collectScopes() const +{ + std::map result; + + if (compiler().state() < CompilerStack::State::CompilationSuccessful) + return result; + + result[".resources"] = compiler().ethdebug(); + result[".compilation"] = compiler().ethdebugCompilation(); + + auto shortNameOf = [](std::string const& _qualified) { + size_t colon = _qualified.rfind(':'); + return colon == std::string::npos ? _qualified : _qualified.substr(colon + 1); + }; + + std::map shortNameCount; + for (std::string const& contractName: compiler().contractNames()) + ++shortNameCount[shortNameOf(contractName)]; + + for (std::string const& contractName: compiler().contractNames()) + { + std::string shortName = shortNameOf(contractName); + Json creation = compiler().ethdebug(contractName); + Json runtime = compiler().ethdebugRuntime(contractName); + bool emitShortAlias = shortNameCount[shortName] == 1; + + result[contractName + ".creation"] = creation; + result[contractName + ".runtime"] = runtime; + if (emitShortAlias) + { + result[shortName + ".creation"] = creation; + result[shortName + ".runtime"] = runtime; + } + // Shortcut: "C.contract" -> the contract sub-object from creation + if (!creation.is_null() && creation.contains("contract")) + { + result[contractName + ".contract"] = creation["contract"]; + if (emitShortAlias) + result[shortName + ".contract"] = creation["contract"]; + } + } + + return result; +} + +std::pair EthdebugTest::splitNonGlobalPath(std::string_view _path) const +{ + // Qualified paths look like "source.sol:C.kind.subpath" — used to disambiguate + // when the same short contract name appears in multisource tests. + auto colonPos = _path.find(':'); + size_t contractStart = colonPos == std::string_view::npos ? 0 : colonPos + 1; + + auto firstDot = _path.find('.', contractStart); + if (firstDot == std::string_view::npos) + return {std::string(_path), ""}; + + auto secondDot = _path.find('.', firstDot + 1); + if (secondDot == std::string_view::npos) + return {std::string(_path), ""}; + + solAssert(secondDot < _path.size()); + return {std::string(_path.substr(0, secondDot)), std::string(_path.substr(secondDot + 1))}; +} diff --git a/test/libsolidity/EthdebugTest.h b/test/libsolidity/EthdebugTest.h new file mode 100644 index 000000000000..11f2fb00297e --- /dev/null +++ b/test/libsolidity/EthdebugTest.h @@ -0,0 +1,50 @@ +/* + 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 + +namespace solidity::frontend::test +{ + +class EthdebugTest: public StandardJSONTest +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::make_unique(_config.filename); + } + + EthdebugTest(std::string const& _filename); + +protected: + void setupCompiler(CompilerStack& _compiler) override; + std::map collectScopes() const override; + std::pair splitNonGlobalPath(std::string_view _path) const override; + +private: + bool m_optimise = false; + bool m_optimiseYul = false; + bool m_useSSACFG = false; + RevertStrings m_revertStrings = RevertStrings::Default; +}; + +} diff --git a/test/libsolidity/StandardJSONTest.cpp b/test/libsolidity/StandardJSONTest.cpp new file mode 100644 index 000000000000..b50a6a5c51b1 --- /dev/null +++ b/test/libsolidity/StandardJSONTest.cpp @@ -0,0 +1,418 @@ +/* + 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 + +using namespace solidity; +using namespace solidity::frontend; +using namespace solidity::frontend::test; +using namespace solidity::util; +using namespace std::string_literals; + +StandardJSONTest::StandardJSONTest(std::string const& _filename): + EVMVersionRestrictedTestCase(_filename), + m_sources(m_reader.sources()) +{ + parseExpectations(m_reader.stream()); +} + +void StandardJSONTest::parseExpectations(std::istream& _stream) +{ + std::string line; + while (getline(_stream, line)) + { + if (!boost::starts_with(line, "// ")) + { + if (line == "//") + continue; + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid expectation: expected \"// \" prefix in line: " + line)); + } + + std::string_view stripped(line); + stripped.remove_prefix(3); + + if (stripped.empty() || stripped.find_first_not_of(" \t") == std::string_view::npos) + continue; + + // Check if this is a multi-line value (line ends with ":" and no inline value after it) + auto colonPos = stripped.rfind(':'); + if (colonPos != std::string_view::npos && + stripped.find_first_not_of(" \t", colonPos + 1) == std::string_view::npos) + { + Expectation exp = parsePathPart(stripped.substr(0, colonPos)); + exp.value = extractExpectationJSON(_stream); + m_expectations.push_back(std::move(exp)); + } + else + { + // Single-line mode: "path: value" + m_expectations.push_back(parseExpectationLine(stripped)); + } + } +} + +std::string StandardJSONTest::extractExpectationJSON(std::istream& _stream) +{ + std::string rawJSON; + std::string line; + while (getline(_stream, line)) + { + std::string_view stripped; + if (boost::starts_with(line, "// ")) + stripped = std::string_view(line).substr(3); + else if (line == "//") + stripped = ""; + else + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid expectation: expected \"// \" prefix in JSON block: " + line)); + + rawJSON += stripped; + rawJSON += "\n"; + + if (Json::accept(rawJSON)) + return rawJSON; + } + BOOST_THROW_EXCEPTION(std::runtime_error( + "Unterminated or malformed multi-line JSON expectation block:\n" + rawJSON + )); +} + +StandardJSONTest::Expectation StandardJSONTest::parseExpectationLine(std::string_view _line) +{ + auto colonPos = _line.find(": "); + if (colonPos == std::string_view::npos) + BOOST_THROW_EXCEPTION(std::runtime_error( + "Missing ': ' separator in expectation line: "s.append(_line) + )); + + std::string_view pathPart = _line.substr(0, colonPos); + std::string value(_line.substr(colonPos + 2)); + + Expectation result = parsePathPart(pathPart); + result.value = std::move(value); + return result; +} + +StandardJSONTest::Expectation StandardJSONTest::parsePathPart(std::string_view _pathPart) +{ + Expectation result; + + auto pipePos = _pathPart.find(" | "); + if (pipePos != std::string_view::npos) + { + result.filter = std::string(_pathPart.substr(pipePos + 3)); + _pathPart = _pathPart.substr(0, pipePos); + } + + result.fullPath = std::string(_pathPart); + return result; +} + +std::pair StandardJSONTest::resolveOutputKey(std::string_view _path) const +{ + if (_path.empty()) + BOOST_THROW_EXCEPTION(std::runtime_error("Empty path in expectation")); + + if (_path[0] == '.') + { + // Global path: ".compilation.compiler.name" -> outputKey=".compilation", jsonPath="compiler.name" + // ".resources.compilation.sources" -> outputKey=".resources", jsonPath="compilation.sources" + auto secondDot = _path.find('.', 1); + if (secondDot == std::string_view::npos) + return {std::string(_path), ""}; + return {std::string(_path.substr(0, secondDot)), std::string(_path.substr(secondDot + 1))}; + } + return splitNonGlobalPath(_path); +} + +std::pair StandardJSONTest::splitNonGlobalPath(std::string_view _path) const +{ + // Default: first segment is the scope key, the rest is the JSON path. + auto firstDot = _path.find('.'); + if (firstDot == std::string_view::npos) + return {std::string(_path), ""}; + solAssert(firstDot < _path.size()); + return {std::string(_path.substr(0, firstDot)), std::string(_path.substr(firstDot + 1))}; +} + +namespace +{ +// Convert expectation path, e.g. "instructions[0].offset" to RFC 6901 JSON pointer, i.e. +// /instructions/0/offset to be later used in JSON lookups. +nlohmann::json::json_pointer pathToPointer(std::string_view _path) +{ + if (_path.empty()) + return nlohmann::json::json_pointer{}; + + std::string ptr = "/"; + for (char c: _path) + { + if (c == '.' || c == '[') + ptr += '/'; + else if (c != ']') + ptr += c; + } + return nlohmann::json::json_pointer(ptr); +} +} + +std::optional StandardJSONTest::resolvePath(Json const& _json, std::string const& _path) const +{ + auto ptr = pathToPointer(_path); + if (!_json.contains(ptr)) + return std::nullopt; + return _json.at(ptr); +} + +Json StandardJSONTest::applyFilter(Json const& _json, std::string const& _filter) const +{ + if (_filter == "length") + { + if (_json.is_array()) + return Json(static_cast(_json.size())); + if (_json.is_object()) + return Json(static_cast(_json.size())); + if (_json.is_string()) + return Json(static_cast(_json.get().size())); + solAssert(false, "Cannot apply 'length' filter to this JSON type"); + } + if (_filter == "type") + return Json(_json.type_name()); + if (_filter == "keys") + { + solAssert(_json.is_object(), "Cannot apply 'keys' filter to non-object"); + Json result = Json::array(); + for (auto const& [key, _]: _json.items()) + result.push_back(key); + return result; + } + + solAssert(false, "Unknown filter: " + _filter); +} + +std::string StandardJSONTest::formatValue(Json const& _json) const +{ + if (_json.is_string()) + return _json.get(); + if (_json.is_object() || _json.is_array()) + return jsonCompactPrint(_json); + return _json.dump(); +} + +bool StandardJSONTest::isPlaceholder(std::string const& _value) const +{ + return placeholders().count(_value) > 0; +} + +void StandardJSONTest::applyPlaceholders(Json const& _expected, Json& _obtained) const +{ + if (_expected.is_string() && isPlaceholder(_expected.get())) + { + _obtained = _expected.get(); + return; + } + if (_expected.is_object() && _obtained.is_object()) + { + for (auto const& [key, expectedVal]: _expected.items()) + if (_obtained.contains(key)) + applyPlaceholders(expectedVal, _obtained[key]); + } + else if (_expected.is_array() && _obtained.is_array()) + { + for (size_t i = 0; i < std::min(_expected.size(), _obtained.size()); ++i) + applyPlaceholders(_expected[i], _obtained[i]); + } +} + +TestCase::TestResult StandardJSONTest::run(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) +{ + if (!runFramework(withPreamble(m_sources.sources), PipelineStage::Compilation)) + { + printPrefixed(_stream, formatErrors(filteredErrors(false /* _includeWarningsAndInfos */), _formatted), _linePrefix); + return TestResult::FatalError; + } + + auto outputs = collectScopes(); + bool allMatch = true; + + for (auto const& expectation: m_expectations) + { + auto [outputKey, jsonPath] = resolveOutputKey(expectation.fullPath); + auto it = outputs.find(outputKey); + if (it == outputs.end()) + { + allMatch = false; + continue; + } + + auto resolved = resolvePath(it->second, jsonPath); + if (!resolved) + { + allMatch = false; + continue; + } + + Json value = *resolved; + if (!expectation.filter.empty()) + value = applyFilter(value, expectation.filter); + + std::string obtained = formatValue(value); + + if (expectation.value.find('\n') != std::string::npos) + { + Json expectedJson; + std::string errors; + if (!jsonParseStrict(expectation.value, expectedJson, &errors)) + { + allMatch = false; + continue; + } + Json patchedObtained = value; + applyPlaceholders(expectedJson, patchedObtained); + + if (jsonPrint(patchedObtained, {JsonFormat::Pretty, 4}) != + jsonPrint(expectedJson, {JsonFormat::Pretty, 4})) + allMatch = false; + } + else if (isPlaceholder(expectation.value)) + { + if (!value.is_string()) + allMatch = false; + } + else if (value.is_object() || value.is_array()) + { + // Compound single-line value: parse expected as JSON and compare structurally + Json expectedJson; + std::string errors; + if (!jsonParseStrict(expectation.value, expectedJson, &errors) || + value != expectedJson) + allMatch = false; + } + else if (obtained != expectation.value) + allMatch = false; + } + + if (allMatch) + return TestResult::Success; + + _stream << _linePrefix << "Expected:" << std::endl; + printPrefixed(_stream, formatExpectations(m_expectations), _linePrefix + " "); + _stream << _linePrefix << "Obtained:" << std::endl; + printUpdatedExpectations(_stream, _linePrefix + " "); + return TestResult::Failure; +} + +void StandardJSONTest::printSource(std::ostream& _stream, std::string const& _linePrefix, bool /*_formatted*/) const +{ + if (m_sources.sources.empty()) + return; + + bool outputSourceNames = m_sources.sources.size() != 1 || !m_sources.sources.begin()->first.empty(); + for (auto const& [name, source]: m_sources.sources) + { + if (outputSourceNames) + _stream << _linePrefix << "==== Source: " << name << " ====" << std::endl; + printPrefixed(_stream, source, _linePrefix); + } +} + +std::string StandardJSONTest::formatExpectations(std::vector const& _expectations) const +{ + std::string output; + for (auto const& exp: _expectations) + { + std::string pathPrefix = exp.fullPath; + if (!exp.filter.empty()) + pathPrefix += " | " + exp.filter; + + if (exp.value.find('\n') != std::string::npos) + { + output += pathPrefix + ":\n"; + output += exp.value; + } + else + output += pathPrefix + ": " + exp.value + "\n"; + } + return output; +} + +void StandardJSONTest::printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const +{ + auto outputs = collectScopes(); + + for (auto const& exp: m_expectations) + { + auto [outputKey, jsonPath] = resolveOutputKey(exp.fullPath); + auto it = outputs.find(outputKey); + + std::string pathPrefix = exp.fullPath; + if (!exp.filter.empty()) + pathPrefix += " | " + exp.filter; + + if (it == outputs.end()) + { + _stream << _linePrefix << pathPrefix << ": " << std::endl; + continue; + } + + auto resolved = resolvePath(it->second, jsonPath); + if (!resolved) + { + _stream << _linePrefix << pathPrefix << ": " << std::endl; + continue; + } + + Json value = *resolved; + if (!exp.filter.empty()) + value = applyFilter(value, exp.filter); + + // Preserve placeholders + if (isPlaceholder(exp.value)) + { + _stream << _linePrefix << pathPrefix << ": " << exp.value << std::endl; + continue; + } + + if (exp.value.find('\n') != std::string::npos) + { + Json expectedJson; + std::string errors; + Json patchedObtained = value; + if (jsonParseStrict(exp.value, expectedJson, &errors)) + applyPlaceholders(expectedJson, patchedObtained); + + std::string formatted = jsonPrint(patchedObtained, {JsonFormat::Pretty, 4}); + _stream << _linePrefix << pathPrefix << ":" << std::endl; + printPrefixed(_stream, formatted, _linePrefix); + } + else + { + std::string formatted = formatValue(value); + _stream << _linePrefix << pathPrefix << ": " << formatted << std::endl; + } + } +} diff --git a/test/libsolidity/StandardJSONTest.h b/test/libsolidity/StandardJSONTest.h new file mode 100644 index 000000000000..a154d0864e0b --- /dev/null +++ b/test/libsolidity/StandardJSONTest.h @@ -0,0 +1,76 @@ +/* + 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 +#include +#include +#include +#include +#include + +namespace solidity::frontend::test +{ + +class StandardJSONTest: public AnalysisFramework, public EVMVersionRestrictedTestCase +{ +public: + StandardJSONTest(std::string const& _filename); + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; + void printSource(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) const override; + void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; + +protected: + struct Expectation + { + std::string fullPath; + std::string filter; // optional filter, e.g. "length" + std::string value; // expected value (could be multi-line JSON or a primitive) + }; + + virtual std::map collectScopes() const = 0; + virtual std::pair splitNonGlobalPath(std::string_view _path) const; + virtual std::set placeholders() const { return {""}; } + +private: + void parseExpectations(std::istream& _stream); + Expectation parseExpectationLine(std::string_view _line); + Expectation parsePathPart(std::string_view _pathPart); + std::pair resolveOutputKey(std::string_view _path) const; + std::string extractExpectationJSON(std::istream& _stream); + + std::optional resolvePath(Json const& _json, std::string const& _path) const; + Json applyFilter(Json const& _json, std::string const& _filter) const; + std::string formatValue(Json const& _json) const; + void applyPlaceholders(Json const& _expected, Json& _obtained) const; + bool isPlaceholder(std::string const& _value) const; + std::string formatExpectations(std::vector const& _expectations) const; + + SourceMap m_sources; + std::vector m_expectations; +}; + +} // namespace diff --git a/test/libsolidity/ethdebugTests/abstract_and_interface.sol b/test/libsolidity/ethdebugTests/abstract_and_interface.sol new file mode 100644 index 000000000000..5f4feacc8249 --- /dev/null +++ b/test/libsolidity/ethdebugTests/abstract_and_interface.sol @@ -0,0 +1,16 @@ +interface I { + function f() external; +} + +abstract contract A { + function g() public virtual; +} + +contract C is A, I { + function f() external override {} + function g() public override {} +} +// ---- +// C.contract.name: C +// C.creation.environment: create +// C.runtime.environment: call diff --git a/test/libsolidity/ethdebugTests/basic_contract.sol b/test/libsolidity/ethdebugTests/basic_contract.sol new file mode 100644 index 000000000000..69637daaffa0 --- /dev/null +++ b/test/libsolidity/ethdebugTests/basic_contract.sol @@ -0,0 +1,24 @@ +contract C { + uint public x; + function set(uint _x) public { + x = _x; + } + function get() public view returns (uint) { + return x; + } +} +// ---- +// .compilation.compiler.name: solc +// .resources.compilation.sources | length: 1 +// .resources.compilation.sources[0].id: 0 +// +// C.contract.name: C +// C.creation.environment: create +// C.contract.definition.source.id: 0 +// C.creation.instructions[0].offset: 0 +// C.creation.instructions[0].operation.mnemonic: PUSH1 +// C.creation.instructions[0].operation.arguments[0]: 0x80 +// C.creation.instructions[2].operation.mnemonic: MSTORE +// +// C.runtime.contract.name: C +// C.runtime.environment: call diff --git a/test/libsolidity/ethdebugTests/compilation_metadata.sol b/test/libsolidity/ethdebugTests/compilation_metadata.sol new file mode 100644 index 000000000000..5363d13f3968 --- /dev/null +++ b/test/libsolidity/ethdebugTests/compilation_metadata.sol @@ -0,0 +1,14 @@ +contract C { + function f() public pure {} +} +// ---- +// .compilation.compiler.name: solc +// .compilation.compiler | type: object +// .compilation.compiler | keys: ["name", "version"] +// .compilation: +// { +// "compiler": { +// "name": "solc", +// "version": "" +// } +// } diff --git a/test/libsolidity/ethdebugTests/context_source_mapping.sol b/test/libsolidity/ethdebugTests/context_source_mapping.sol new file mode 100644 index 000000000000..075d66322395 --- /dev/null +++ b/test/libsolidity/ethdebugTests/context_source_mapping.sol @@ -0,0 +1,23 @@ +contract C { + uint public x; + function set(uint _x) public { + x = _x; + } + function get() public view returns (uint) { + return x; + } +} +// ---- +// C.creation.instructions[0].context.code.range.offset: 59 +// C.creation.instructions[0].context.code.range.length: 162 +// C.creation.instructions[0].context.code.source.id: 0 +// C.runtime.instructions[0].context.code: +// { +// "range": { +// "length": 162, +// "offset": 59 +// }, +// "source": { +// "id": 0 +// } +// } diff --git a/test/libsolidity/ethdebugTests/filters.sol b/test/libsolidity/ethdebugTests/filters.sol new file mode 100644 index 000000000000..5d17a0fb33d8 --- /dev/null +++ b/test/libsolidity/ethdebugTests/filters.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure returns (uint) { + return 42; + } +} +// ---- +// .resources.compilation.sources | length: 1 +// C.contract | type: object +// C.contract | keys: ["definition", "name"] +// C.creation.instructions | type: array +// C.creation.environment | type: string diff --git a/test/libsolidity/ethdebugTests/multi_source.sol b/test/libsolidity/ethdebugTests/multi_source.sol new file mode 100644 index 000000000000..1ba4970e8424 --- /dev/null +++ b/test/libsolidity/ethdebugTests/multi_source.sol @@ -0,0 +1,15 @@ +==== Source: lib.sol ==== +contract Lib { + function helper() public pure returns (uint) { return 1; } +} +==== Source: main.sol ==== +import "lib.sol"; +contract Main { + function f() public pure returns (uint) { return 42; } +} +// ---- +// .resources.compilation.sources | length: 2 +// Lib.contract.name: Lib +// Main.contract.name: Main +// Lib.creation.environment: create +// Main.creation.environment: create diff --git a/test/libsolidity/ethdebugTests/multi_source_name_collision.sol b/test/libsolidity/ethdebugTests/multi_source_name_collision.sol new file mode 100644 index 000000000000..11569827be83 --- /dev/null +++ b/test/libsolidity/ethdebugTests/multi_source_name_collision.sol @@ -0,0 +1,19 @@ +==== Source: a.sol ==== +contract C { + function fromA() public pure returns (uint) { return 1; } +} +contract Unique { + function only() public pure returns (uint) { return 99; } +} +==== Source: b.sol ==== +contract C { + function fromB() public pure returns (uint) { return 2; } +} +// ---- +// .resources.compilation.sources | length: 2 +// a.sol:C.contract.name: C +// b.sol:C.contract.name: C +// a.sol:C.creation.environment: create +// b.sol:C.creation.environment: create +// Unique.contract.name: Unique +// Unique.creation.environment: create diff --git a/test/libsolidity/ethdebugTests/object_matching.sol b/test/libsolidity/ethdebugTests/object_matching.sol new file mode 100644 index 000000000000..9be3947dc14c --- /dev/null +++ b/test/libsolidity/ethdebugTests/object_matching.sol @@ -0,0 +1,36 @@ +contract C { + function f() public pure returns (uint) { + return 42; + } +} +// ---- +// C.contract: +// { +// "definition": { +// "source": { +// "id": 0 +// } +// }, +// "name": "C" +// } +// C.creation.instructions[0]: +// { +// "context": { +// "code": { +// "range": { +// "length": 85, +// "offset": 59 +// }, +// "source": { +// "id": 0 +// } +// } +// }, +// "offset": 0, +// "operation": { +// "arguments": [ +// "0x80" +// ], +// "mnemonic": "PUSH1" +// } +// } diff --git a/test/libsolidity/ethdebugTests/resources_only.sol b/test/libsolidity/ethdebugTests/resources_only.sol new file mode 100644 index 000000000000..2566d5788b3b --- /dev/null +++ b/test/libsolidity/ethdebugTests/resources_only.sol @@ -0,0 +1,8 @@ +contract C { + function f() public {} +} +// ---- +// .compilation.compiler.name: solc +// .compilation.compiler.version: +// .resources.compilation.sources | length: 1 +// .resources.compilation.sources[0].id: 0 diff --git a/test/libsolidity/ethdebugTests/via_ssa_cfg.sol b/test/libsolidity/ethdebugTests/via_ssa_cfg.sol new file mode 100644 index 000000000000..2d6ab4e24ab7 --- /dev/null +++ b/test/libsolidity/ethdebugTests/via_ssa_cfg.sol @@ -0,0 +1,8 @@ +contract C {} +// ==== +// compileViaSSACFG: true +// ---- +// .compilation.compiler.name: solc +// C.contract.name: C +// C.creation.environment: create +// C.runtime.environment: call diff --git a/test/libsolidity/ethdebugTests/via_ssa_cfg_with_function.sol b/test/libsolidity/ethdebugTests/via_ssa_cfg_with_function.sol new file mode 100644 index 000000000000..9dd9fed9037b --- /dev/null +++ b/test/libsolidity/ethdebugTests/via_ssa_cfg_with_function.sol @@ -0,0 +1,13 @@ +contract C { + function f() public {} +} +// ==== +// compileViaSSACFG: true +// ---- +// .compilation.compiler.name: solc +// .resources.compilation.sources | length: 1 +// C.contract.name: C +// C.creation.environment: create +// C.runtime.environment: call +// C.creation.instructions | type: array +// C.runtime.instructions | type: array diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 46cd0eedb51c..3f72fbe336af 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(isoltest ../libsolidity/util/ContractABIUtils.cpp ../libsolidity/util/TestFileParser.cpp ../libsolidity/util/TestFunctionCall.cpp + ../libsolidity/EthdebugTest.cpp ../libsolidity/GasTest.cpp ../libsolidity/MemoryGuardTest.cpp ../libsolidity/NatspecJSONTest.cpp @@ -40,6 +41,7 @@ add_executable(isoltest ../libsolidity/ASTPropertyTest.cpp ../libsolidity/FunctionDependencyGraphTest.cpp ../libsolidity/SMTCheckerTest.cpp + ../libsolidity/StandardJSONTest.cpp ../libyul/Common.cpp ../libyul/ControlFlowGraphTest.cpp ../libyul/ControlFlowSideEffectsTest.cpp