From de0f0b4dbcec3f25ff2ee3b036aac0dcf2d91ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 16 Apr 2025 13:37:43 +0200 Subject: [PATCH 01/14] EVMAssemblyStack: Add extra accessors that do not require contract name --- libevmasm/EVMAssemblyStack.cpp | 24 +++++++++++++++++------- libevmasm/EVMAssemblyStack.h | 6 ++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/libevmasm/EVMAssemblyStack.cpp b/libevmasm/EVMAssemblyStack.cpp index 7cb05bf8bf00..bed4ff744ec4 100644 --- a/libevmasm/EVMAssemblyStack.cpp +++ b/libevmasm/EVMAssemblyStack.cpp @@ -74,13 +74,13 @@ void EVMAssemblyStack::assemble() LinkerObject const& EVMAssemblyStack::object(std::string const& _contractName) const { solAssert(_contractName == m_name); - return m_object; + return object(); } LinkerObject const& EVMAssemblyStack::runtimeObject(std::string const& _contractName) const { solAssert(_contractName == m_name); - return m_runtimeObject; + return runtimeObject(); } std::map EVMAssemblyStack::sourceIndices() const @@ -95,13 +95,13 @@ std::map EVMAssemblyStack::sourceIndices() const std::string const* EVMAssemblyStack::sourceMapping(std::string const& _contractName) const { solAssert(_contractName == m_name); - return &m_sourceMapping; + return &sourceMapping(); } std::string const* EVMAssemblyStack::runtimeSourceMapping(std::string const& _contractName) const { solAssert(_contractName == m_name); - return &m_runtimeSourceMapping; + return &runtimeSourceMapping(); } Json EVMAssemblyStack::ethdebug(std::string const& _contractName) const @@ -123,20 +123,30 @@ Json EVMAssemblyStack::ethdebug() const return {}; } -Json EVMAssemblyStack::assemblyJSON(std::string const& _contractName) const +Json EVMAssemblyStack::assemblyJSON() const { - solAssert(_contractName == m_name); solAssert(m_evmAssembly); return m_evmAssembly->assemblyJSON(sourceIndices()); } -std::string EVMAssemblyStack::assemblyString(std::string const& _contractName, StringMap const& _sourceCodes) const +Json EVMAssemblyStack::assemblyJSON(std::string const& _contractName) const { solAssert(_contractName == m_name); + return assemblyJSON(); +} + +std::string EVMAssemblyStack::assemblyString(StringMap const& _sourceCodes) const +{ solAssert(m_evmAssembly); return m_evmAssembly->assemblyString(m_debugInfoSelection, _sourceCodes); } +std::string EVMAssemblyStack::assemblyString(std::string const& _contractName, StringMap const& _sourceCodes) const +{ + solAssert(_contractName == m_name); + return assemblyString(_sourceCodes); +} + std::string const EVMAssemblyStack::filesystemFriendlyName(std::string const& _contractName) const { solAssert(_contractName == m_name); diff --git a/libevmasm/EVMAssemblyStack.h b/libevmasm/EVMAssemblyStack.h index 2fe63c11c2ec..444d444ba9e2 100644 --- a/libevmasm/EVMAssemblyStack.h +++ b/libevmasm/EVMAssemblyStack.h @@ -58,20 +58,26 @@ class EVMAssemblyStack: public AbstractAssemblyStack std::string const& name() const { return m_name; } + LinkerObject const& object() const { return m_object; } LinkerObject const& object(std::string const& _contractName) const override; + LinkerObject const& runtimeObject() const { return m_runtimeObject; } LinkerObject const& runtimeObject(std::string const& _contractName) const override; std::shared_ptr const& evmAssembly() const { return m_evmAssembly; } std::shared_ptr const& evmRuntimeAssembly() const { return m_evmRuntimeAssembly; } + std::string const& sourceMapping() const { return m_sourceMapping; } std::string const* sourceMapping(std::string const& _contractName) const override; + std::string const& runtimeSourceMapping() const { return m_runtimeSourceMapping; } std::string const* runtimeSourceMapping(std::string const& _contractName) const override; Json ethdebug(std::string const& _contractName) const override; Json ethdebugRuntime(std::string const& _contractName) const override; Json ethdebug() const override; + Json assemblyJSON() const; Json assemblyJSON(std::string const& _contractName) const override; + std::string assemblyString(StringMap const& _sourceCodes) const; std::string assemblyString(std::string const& _contractName, StringMap const& _sourceCodes) const override; std::string const filesystemFriendlyName(std::string const& _contractName) const override; From 4b7661adb85d311ec3659f92804983008bc1573e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 14 Mar 2025 04:04:14 +0100 Subject: [PATCH 02/14] TestCaseReader: Expose fileName() --- test/TestCaseReader.h | 1 + 1 file changed, 1 insertion(+) diff --git a/test/TestCaseReader.h b/test/TestCaseReader.h index d1ef95be3c2c..5830e97f3666 100644 --- a/test/TestCaseReader.h +++ b/test/TestCaseReader.h @@ -59,6 +59,7 @@ class TestCaseReader std::size_t lineNumber() const { return m_lineNumber; } std::map const& settings() const { return m_settings; } std::ifstream& stream() { return m_fileStream; } + boost::filesystem::path const& fileName() const { return m_fileName; } std::string simpleExpectations(); From 4c4d45de4e7980b72f8ac972c516221c5db6a5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 19 Apr 2025 04:36:11 +0200 Subject: [PATCH 03/14] Add a transparent comparator to c_instructions to allow string_view lookups --- libevmasm/Instruction.cpp | 2 +- libevmasm/Instruction.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index dad9804d09a8..b5568763573a 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -26,7 +26,7 @@ using namespace solidity; using namespace solidity::util; using namespace solidity::evmasm; -std::map const solidity::evmasm::c_instructions = +std::map> const solidity::evmasm::c_instructions = { { "STOP", Instruction::STOP }, { "ADD", Instruction::ADD }, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 137144715296..cfdb85d2fa4c 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -322,6 +322,6 @@ InstructionInfo instructionInfo(Instruction _inst, langutil::EVMVersion _evmVers bool isValidInstruction(Instruction _inst); /// Convert from string mnemonic to Instruction type. -extern const std::map c_instructions; +extern const std::map> c_instructions; } From 66c5d49b83424eadb4be58d506a90c2d11b3a3d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 14 Mar 2025 04:51:35 +0100 Subject: [PATCH 04/14] EVM assembly test case with JSON input --- test/CMakeLists.txt | 2 + test/InteractiveTests.h | 3 + test/TestCase.cpp | 7 +- test/libevmasm/EVMAssemblyTest.cpp | 158 ++++++++++++++++++ test/libevmasm/EVMAssemblyTest.h | 51 ++++++ .../code_generation_error.asmjson | 14 ++ .../isoltestTesting/invalid_json.asmjson | 3 + .../settings_eof_version.asmjson | 13 ++ .../settings_evm_version.asmjson | 13 ++ ...tings_optimizer_constant_optimizer.asmjson | 14 ++ .../settings_optimizer_cse.asmjson | 16 ++ .../settings_optimizer_deduplicate.asmjson | 26 +++ ...expected_executions_per_deployment.asmjson | 26 +++ .../settings_optimizer_inliner.asmjson | 33 ++++ ...ettings_optimizer_jumpdest_remover.asmjson | 14 ++ .../settings_optimizer_peephole.asmjson | 15 ++ .../settings_optimizer_preset.asmjson | 18 ++ .../isoltestTesting/settings_outputs.asmjson | 10 ++ .../isoltestTesting/smoke.asmjson | 11 ++ test/tools/CMakeLists.txt | 1 + test/tools/isoltest.cpp | 2 +- 21 files changed, 446 insertions(+), 4 deletions(-) create mode 100644 test/libevmasm/EVMAssemblyTest.cpp create mode 100644 test/libevmasm/EVMAssemblyTest.h create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/code_generation_error.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/invalid_json.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_eof_version.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_evm_version.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_constant_optimizer.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_cse.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_deduplicate.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_expected_executions_per_deployment.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_inliner.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_jumpdest_remover.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_peephole.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_preset.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/settings_outputs.asmjson create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/smoke.asmjson diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index da115f0d4a5b..6f48b8a8469e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,6 +51,8 @@ detect_stray_source_files("${libsolutil_sources}" "libsolutil/") set(libevmasm_sources libevmasm/Assembler.cpp + libevmasm/EVMAssemblyTest.cpp + libevmasm/EVMAssemblyTest.h libevmasm/Optimiser.cpp ) detect_stray_source_files("${libevmasm_sources}" "libevmasm/") diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index e043fcdbe676..107a6aae0bef 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -42,6 +42,8 @@ #include #include +#include + #include namespace solidity::frontend::test @@ -64,6 +66,7 @@ struct Testsuite Testsuite const g_interactiveTestsuites[] = { /* Title Path Subpath SMT NeedsVM Creator function */ + {"EVM Assembly", "libevmasm", "evmAssemblyTests", false, false, &evmasm::test::EVMAssemblyTest::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}, diff --git a/test/TestCase.cpp b/test/TestCase.cpp index da47c26fde90..a28ae0a8f49f 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -51,9 +51,10 @@ void TestCase::printUpdatedSettings(std::ostream& _stream, std::string const& _l bool TestCase::isTestFilename(boost::filesystem::path const& _filename) { std::string extension = _filename.extension().string(); - return (extension == ".sol" || extension == ".yul" || extension == ".stack") && - !boost::starts_with(_filename.string(), "~") && - !boost::starts_with(_filename.string(), "."); + // NOTE: .asmjson rather than .json because JSON files that do not represent test cases exist in some test dirs. + return (extension == ".sol" || extension == ".yul" || extension == ".asmjson" || extension == ".stack") && + !boost::starts_with(_filename.string(), "~") && + !boost::starts_with(_filename.string(), "."); } bool TestCase::shouldRun() diff --git a/test/libevmasm/EVMAssemblyTest.cpp b/test/libevmasm/EVMAssemblyTest.cpp new file mode 100644 index 000000000000..176226f68b2a --- /dev/null +++ b/test/libevmasm/EVMAssemblyTest.cpp @@ -0,0 +1,158 @@ +/* + 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 + +using namespace std::string_literals; +using namespace solidity; +using namespace solidity::test; +using namespace solidity::evmasm; +using namespace solidity::evmasm::test; +using namespace solidity::frontend; +using namespace solidity::frontend::test; +using namespace solidity::langutil; +using namespace solidity::util; + +std::vector const EVMAssemblyTest::c_outputLabels = { + "Assembly", + "Bytecode", + "Opcodes", + "SourceMappings", +}; + +std::unique_ptr EVMAssemblyTest::create(Config const& _config) +{ + return std::make_unique(_config.filename); +} + +EVMAssemblyTest::EVMAssemblyTest(std::string const& _filename): + EVMVersionRestrictedTestCase(_filename) +{ + m_source = m_reader.source(); + m_expectation = m_reader.simpleExpectations(); + + if (!boost::algorithm::ends_with(_filename, ".asmjson")) + BOOST_THROW_EXCEPTION(std::runtime_error("Not an assembly test: \"" + _filename + "\". Allowed extensions: .asmjson.")); + + m_selectedOutputs = m_reader.stringSetting("outputs", "Assembly,Bytecode,Opcodes,SourceMappings"); + OptimisationPreset optimizationPreset = m_reader.enumSetting( + "optimizationPreset", + { + {"none", OptimisationPreset::None}, + {"minimal", OptimisationPreset::Minimal}, + {"standard", OptimisationPreset::Standard}, + {"full", OptimisationPreset::Full}, + }, + "none" + ); + m_optimizerSettings = Assembly::OptimiserSettings::translateSettings(OptimiserSettings::preset(optimizationPreset)); + m_optimizerSettings.expectedExecutionsPerDeployment = m_reader.sizetSetting( + "optimizer.expectedExecutionsPerDeployment", + m_optimizerSettings.expectedExecutionsPerDeployment + ); + + auto const optimizerComponentSetting = [&](std::string const& _component, bool& _setting) { + _setting = m_reader.boolSetting("optimizer." + _component, _setting); + }; + optimizerComponentSetting("inliner", m_optimizerSettings.runInliner); + optimizerComponentSetting("jumpdestRemover", m_optimizerSettings.runJumpdestRemover); + optimizerComponentSetting("peephole", m_optimizerSettings.runPeephole); + optimizerComponentSetting("deduplicate", m_optimizerSettings.runDeduplicate); + optimizerComponentSetting("cse", m_optimizerSettings.runCSE); + optimizerComponentSetting("constantOptimizer", m_optimizerSettings.runConstantOptimiser); + + // TODO: Enable when assembly import for EOF is implemented. + if (CommonOptions::get().eofVersion().has_value()) + m_shouldRun = false; +} + +TestCase::TestResult EVMAssemblyTest::run(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted) +{ + EVMAssemblyStack evmAssemblyStack( + CommonOptions::get().evmVersion(), + CommonOptions::get().eofVersion(), + m_optimizerSettings + ); + + evmAssemblyStack.selectDebugInfo(DebugInfoSelection::AllExceptExperimental()); + + try + { + evmAssemblyStack.parseAndAnalyze(m_reader.fileName().filename().string(), m_source); + } + catch (AssemblyImportException const& _exception) + { + m_obtainedResult = "AssemblyImportException: "s + _exception.what() + "\n"; + return checkResult(_stream, _linePrefix, _formatted); + } + + try + { + evmAssemblyStack.assemble(); + } + catch (Error const& _error) + { + // TODO: EVMAssemblyStack should catch these on its own and provide an error reporter. + soltestAssert(_error.comment(), "Errors must include a message for the user."); + m_obtainedResult = Error::formatErrorType(_error.type()) + ": " + *_error.comment() + "\n"; + return checkResult(_stream, _linePrefix, _formatted); + } + soltestAssert(evmAssemblyStack.compilationSuccessful()); + + auto const produceOutput = [&](std::string const& _output) { + if (_output == "Assembly") + return evmAssemblyStack.assemblyString({{m_reader.fileName().filename().string(), m_source}}); + if (_output == "Bytecode") + return util::toHex(evmAssemblyStack.object().bytecode); + if (_output == "Opcodes") + return disassemble(evmAssemblyStack.object().bytecode, CommonOptions::get().evmVersion()); + if (_output == "SourceMappings") + return evmAssemblyStack.sourceMapping(); + soltestAssert(false); + unreachable(); + }; + + std::set selectedOutputSet; + boost::split(selectedOutputSet, m_selectedOutputs, boost::is_any_of(",")); + for (std::string const& output: c_outputLabels) + if (selectedOutputSet.contains(output)) + { + if (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n') + m_obtainedResult += "\n"; + + // Don't trim on the left to avoid stripping indentation. + std::string content = produceOutput(output); + boost::trim_right(content); + std::string separator = (content.empty() ? "" : (output == "Assembly" ? "\n" : " ")); + m_obtainedResult += output + ":" + separator + content; + } + if (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n') + m_obtainedResult += "\n"; + + return checkResult(_stream, _linePrefix, _formatted); +} diff --git a/test/libevmasm/EVMAssemblyTest.h b/test/libevmasm/EVMAssemblyTest.h new file mode 100644 index 000000000000..11474debb474 --- /dev/null +++ b/test/libevmasm/EVMAssemblyTest.h @@ -0,0 +1,51 @@ +/* + 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 + +namespace solidity::evmasm::test +{ + +class EVMAssemblyTest: public frontend::test::EVMVersionRestrictedTestCase +{ +public: + static std::unique_ptr create(Config const& _config); + + EVMAssemblyTest(std::string const& _filename); + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + +private: + static std::vector const c_outputLabels; + + std::string m_selectedOutputs; + evmasm::Assembly::OptimiserSettings m_optimizerSettings; +}; + +} diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/code_generation_error.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/code_generation_error.asmjson new file mode 100644 index 000000000000..4297c5f35b80 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/code_generation_error.asmjson @@ -0,0 +1,14 @@ +{ + ".code": [ + {"name": "PUSH [$]", "value": "0"} + ], + ".data": { + "0": { + ".code": [ + {"name": "PUSHIMMUTABLE", "value": "x"} + ] + } + } +} +// ---- +// CodeGenerationError: Some immutables were read from but never assigned, possibly because of optimization. diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/invalid_json.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/invalid_json.asmjson new file mode 100644 index 000000000000..a834108d1ad7 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/invalid_json.asmjson @@ -0,0 +1,3 @@ +{ +// ---- +// AssemblyImportException: Could not parse JSON file. diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_eof_version.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_eof_version.asmjson new file mode 100644 index 000000000000..5785b4b8931e --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_eof_version.asmjson @@ -0,0 +1,13 @@ +{ + ".code": [ + {"name": "CODESIZE"} + ] +} +// ==== +// bytecodeFormat: legacy +// ---- +// Assembly: +// codesize +// Bytecode: 38 +// Opcodes: CODESIZE +// SourceMappings: :::-:0 diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_evm_version.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_evm_version.asmjson new file mode 100644 index 000000000000..a0f042b32e88 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_evm_version.asmjson @@ -0,0 +1,13 @@ +{ + ".code": [ + {"name": "BLOBBASEFEE"} + ] +} +// ==== +// EVMVersion: >=cancun +// ---- +// Assembly: +// blobbasefee +// Bytecode: 4a +// Opcodes: BLOBBASEFEE +// SourceMappings: :::-:0 diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_constant_optimizer.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_constant_optimizer.asmjson new file mode 100644 index 000000000000..a3610217fbaf --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_constant_optimizer.asmjson @@ -0,0 +1,14 @@ +{ + ".code": [ + {"name": "PUSH", "value": "ffffffffffffffff"} + ] +} +// ==== +// optimizationPreset: none +// optimizer.constantOptimizer: true +// ---- +// Assembly: +// sub(shl(0x40, 0x01), 0x01) +// Bytecode: 6001600160401b03 +// Opcodes: PUSH1 0x1 PUSH1 0x1 PUSH1 0x40 SHL SUB +// SourceMappings: :::-:0;;;; diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_cse.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_cse.asmjson new file mode 100644 index 000000000000..c8133b43f799 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_cse.asmjson @@ -0,0 +1,16 @@ +{ + ".code": [ + {"name": "PUSH", "value": "0"}, + {"name": "DUP2"}, + {"name": "SUB"} + ] +} +// ==== +// optimizationPreset: none +// optimizer.cse: true +// ---- +// Assembly: +// dup1 +// Bytecode: 80 +// Opcodes: DUP1 +// SourceMappings: :::-:0 diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_deduplicate.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_deduplicate.asmjson new file mode 100644 index 000000000000..67b87e59e9ee --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_deduplicate.asmjson @@ -0,0 +1,26 @@ +{ + ".code": [ + {"name": "PUSH [tag]", "value": "1"}, + {"name": "PUSH [tag]", "value": "2"}, + {"name": "tag", "value": "1"}, + {"name": "JUMPDEST"}, + {"name": "JUMP"}, + {"name": "tag", "value": "2"}, + {"name": "JUMPDEST"}, + {"name": "JUMP"} + ] +} +// ==== +// optimizationPreset: none +// optimizer.deduplicate: true +// ---- +// Assembly: +// tag_1 +// tag_1 +// tag_1: +// jump +// tag_2: +// jump +// Bytecode: 600460045b565b56 +// Opcodes: PUSH1 0x4 PUSH1 0x4 JUMPDEST JUMP JUMPDEST JUMP +// SourceMappings: :::-:0;;;;; diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_expected_executions_per_deployment.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_expected_executions_per_deployment.asmjson new file mode 100644 index 000000000000..b9fd814d5c8e --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_expected_executions_per_deployment.asmjson @@ -0,0 +1,26 @@ +{ + ".code": [ + {"name": "PUSH [$]", "value": "0"} + ], + ".data": { + "0": { + ".code": [ + {"name": "PUSH", "value": "ffffffffffffffff"} + ] + } + } +} +// ==== +// optimizationPreset: standard +// optimizer.expectedExecutionsPerDeployment: 0 +// ---- +// Assembly: +// dataOffset(sub_0) +// stop +// +// sub_0: assembly { +// sub(shl(0x40, 0x01), 0x01) +// } +// Bytecode: 6003fe6001600160401b03 +// Opcodes: PUSH1 0x3 INVALID PUSH1 0x1 PUSH1 0x1 PUSH1 0x40 SHL SUB +// SourceMappings: :::-:0 diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_inliner.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_inliner.asmjson new file mode 100644 index 000000000000..81be6392c444 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_inliner.asmjson @@ -0,0 +1,33 @@ +{ + ".code": [ + {"name": "PUSH [tag]", "value": "1"}, + {"name": "PUSH [tag]", "value": "2"}, + {"name": "JUMP"}, + {"name": "tag", "value": "1"}, + {"name": "JUMPDEST"}, + {"name": "STOP"}, + {"name": "tag", "value": "2"}, + {"name": "JUMPDEST"}, + {"name": "CALLVALUE"}, + {"name": "SWAP1"}, + {"name": "JUMP"} + ] +} +// ==== +// optimizationPreset: none +// optimizer.inliner: true +// ---- +// Assembly: +// tag_1 +// callvalue +// swap1 +// jump +// tag_1: +// stop +// tag_2: +// callvalue +// swap1 +// jump +// Bytecode: 60053490565b005b349056 +// Opcodes: PUSH1 0x5 CALLVALUE SWAP1 JUMP JUMPDEST STOP JUMPDEST CALLVALUE SWAP1 JUMP +// SourceMappings: :::-:0;;;;;;;;; diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_jumpdest_remover.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_jumpdest_remover.asmjson new file mode 100644 index 000000000000..baca77b992fc --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_jumpdest_remover.asmjson @@ -0,0 +1,14 @@ +{ + ".code": [ + {"name": "tag", "value": "1"}, + {"name": "JUMPDEST"} + ] +} +// ==== +// optimizationPreset: none +// optimizer.jumpdestRemover: true +// ---- +// Assembly: +// Bytecode: +// Opcodes: +// SourceMappings: diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_peephole.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_peephole.asmjson new file mode 100644 index 000000000000..f9e7c3b8d550 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_peephole.asmjson @@ -0,0 +1,15 @@ +{ + ".code": [ + {"name": "STOP"}, + {"name": "STOP"} + ] +} +// ==== +// optimizationPreset: none +// optimizer.peephole: true +// ---- +// Assembly: +// stop +// Bytecode: 00 +// Opcodes: STOP +// SourceMappings: :::-:0 diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_preset.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_preset.asmjson new file mode 100644 index 000000000000..706a3ae8b61a --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_optimizer_preset.asmjson @@ -0,0 +1,18 @@ +{ + ".code": [ + {"name": "PUSH", "value": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"name": "PUSH", "value": "42"}, + {"name": "PUSH", "value": "24"}, + {"name": "SWAP1"}, + {"name": "ADD"} + ] +} +// ==== +// optimizationPreset: full +// ---- +// Assembly: +// not(0x00) +// 0x66 +// Bytecode: 5f196066 +// Opcodes: PUSH0 NOT PUSH1 0x66 +// SourceMappings: :::-:0;; diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_outputs.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_outputs.asmjson new file mode 100644 index 000000000000..2af7b57c3e75 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/settings_outputs.asmjson @@ -0,0 +1,10 @@ +{ + ".code": [ + {"name": "CALLVALUE"} + ] +} +// ==== +// outputs: SourceMappings,Opcodes +// ---- +// Opcodes: CALLVALUE +// SourceMappings: :::-:0 diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/smoke.asmjson b/test/libevmasm/evmAssemblyTests/isoltestTesting/smoke.asmjson new file mode 100644 index 000000000000..99e527a72587 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/smoke.asmjson @@ -0,0 +1,11 @@ +{ + ".code": [ + {"name": "CALLVALUE"} + ] +} +// ---- +// Assembly: +// callvalue +// Bytecode: 34 +// Opcodes: CALLVALUE +// SourceMappings: :::-:0 diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 5c1d41b7f595..13018bf18512 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(isoltest ../EVMHost.cpp ../TestCase.cpp ../TestCaseReader.cpp + ../libevmasm/EVMAssemblyTest.cpp ../libsolidity/util/BytesUtils.cpp ../libsolidity/util/Common.cpp ../libsolidity/util/ContractABIUtils.cpp diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 57ab4af16034..32309b005507 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -75,7 +75,7 @@ class TestFilter boost::replace_all(filter, "/", "\\/"); boost::replace_all(filter, "*", ".*"); - m_filterExpression = std::regex{"(" + filter + "(\\.sol|\\.yul|\\.stack))"}; + m_filterExpression = std::regex{"(" + filter + "(\\.sol|\\.yul|\\.asmjson|\\.stack))"}; } bool matches(fs::path const& _path, std::string const& _name) const From ce8ca0ace921bf72372c33493b0568c1669e5241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 18 Apr 2025 03:18:05 +0200 Subject: [PATCH 05/14] Support human-readable assembly in EVM assembly test case --- test/CMakeLists.txt | 2 + test/TestCase.cpp | 2 +- test/libevmasm/EVMAssemblyTest.cpp | 29 ++- test/libevmasm/EVMAssemblyTest.h | 7 + test/libevmasm/PlainAssemblyParser.cpp | 182 ++++++++++++++++++ test/libevmasm/PlainAssemblyParser.h | 79 ++++++++ .../isoltestTesting/comments.asm | 34 ++++ .../isoltestTesting/jumps.asm | 56 ++++++ .../isoltestTesting/operations.asm | 53 +++++ .../evmAssemblyTests/isoltestTesting/push.asm | 35 ++++ .../isoltestTesting/smoke_plain.asm | 16 ++ test/tools/CMakeLists.txt | 1 + test/tools/isoltest.cpp | 2 +- 13 files changed, 493 insertions(+), 5 deletions(-) create mode 100644 test/libevmasm/PlainAssemblyParser.cpp create mode 100644 test/libevmasm/PlainAssemblyParser.h create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/comments.asm create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/jumps.asm create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/operations.asm create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/push.asm create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/smoke_plain.asm diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6f48b8a8469e..c66963c190d4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,6 +54,8 @@ set(libevmasm_sources libevmasm/EVMAssemblyTest.cpp libevmasm/EVMAssemblyTest.h libevmasm/Optimiser.cpp + libevmasm/PlainAssemblyParser.cpp + libevmasm/PlainAssemblyParser.h ) detect_stray_source_files("${libevmasm_sources}" "libevmasm/") diff --git a/test/TestCase.cpp b/test/TestCase.cpp index a28ae0a8f49f..bd4c92b7dd8e 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -52,7 +52,7 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename) { std::string extension = _filename.extension().string(); // NOTE: .asmjson rather than .json because JSON files that do not represent test cases exist in some test dirs. - return (extension == ".sol" || extension == ".yul" || extension == ".asmjson" || extension == ".stack") && + return (extension == ".sol" || extension == ".yul" || extension == ".asm" || extension == ".asmjson" || extension == ".stack") && !boost::starts_with(_filename.string(), "~") && !boost::starts_with(_filename.string(), "."); } diff --git a/test/libevmasm/EVMAssemblyTest.cpp b/test/libevmasm/EVMAssemblyTest.cpp index 176226f68b2a..6d86592cdfce 100644 --- a/test/libevmasm/EVMAssemblyTest.cpp +++ b/test/libevmasm/EVMAssemblyTest.cpp @@ -17,6 +17,8 @@ #include +#include + #include #include @@ -39,6 +41,7 @@ using namespace solidity::langutil; using namespace solidity::util; std::vector const EVMAssemblyTest::c_outputLabels = { + "InputAssemblyJSON", "Assembly", "Bytecode", "Opcodes", @@ -56,8 +59,12 @@ EVMAssemblyTest::EVMAssemblyTest(std::string const& _filename): m_source = m_reader.source(); m_expectation = m_reader.simpleExpectations(); - if (!boost::algorithm::ends_with(_filename, ".asmjson")) - BOOST_THROW_EXCEPTION(std::runtime_error("Not an assembly test: \"" + _filename + "\". Allowed extensions: .asmjson.")); + if (boost::algorithm::ends_with(_filename, ".asmjson")) + m_assemblyFormat = AssemblyFormat::JSON; + else if (boost::algorithm::ends_with(_filename, ".asm")) + m_assemblyFormat = AssemblyFormat::Plain; + else + BOOST_THROW_EXCEPTION(std::runtime_error("Not an assembly test: \"" + _filename + "\". Allowed extensions: .asm, .asmjson.")); m_selectedOutputs = m_reader.stringSetting("outputs", "Assembly,Bytecode,Opcodes,SourceMappings"); OptimisationPreset optimizationPreset = m_reader.enumSetting( @@ -101,9 +108,23 @@ TestCase::TestResult EVMAssemblyTest::run(std::ostream& _stream, std::string con evmAssemblyStack.selectDebugInfo(DebugInfoSelection::AllExceptExperimental()); + std::string assemblyJSON; + switch (m_assemblyFormat) + { + case AssemblyFormat::JSON: + assemblyJSON = m_source; + break; + case AssemblyFormat::Plain: + assemblyJSON = jsonPrint( + PlainAssemblyParser{}.parse(m_reader.fileName().filename().string(), m_source), + {JsonFormat::Pretty, 4} + ); + break; + } + try { - evmAssemblyStack.parseAndAnalyze(m_reader.fileName().filename().string(), m_source); + evmAssemblyStack.parseAndAnalyze(m_reader.fileName().filename().string(), assemblyJSON); } catch (AssemblyImportException const& _exception) { @@ -125,6 +146,8 @@ TestCase::TestResult EVMAssemblyTest::run(std::ostream& _stream, std::string con soltestAssert(evmAssemblyStack.compilationSuccessful()); auto const produceOutput = [&](std::string const& _output) { + if (_output == "InputAssemblyJSON") + return assemblyJSON; if (_output == "Assembly") return evmAssemblyStack.assemblyString({{m_reader.fileName().filename().string(), m_source}}); if (_output == "Bytecode") diff --git a/test/libevmasm/EVMAssemblyTest.h b/test/libevmasm/EVMAssemblyTest.h index 11474debb474..041409393fdc 100644 --- a/test/libevmasm/EVMAssemblyTest.h +++ b/test/libevmasm/EVMAssemblyTest.h @@ -42,8 +42,15 @@ class EVMAssemblyTest: public frontend::test::EVMVersionRestrictedTestCase TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; private: + enum class AssemblyFormat + { + JSON, + Plain, + }; + static std::vector const c_outputLabels; + AssemblyFormat m_assemblyFormat{}; std::string m_selectedOutputs; evmasm::Assembly::OptimiserSettings m_optimizerSettings; }; diff --git a/test/libevmasm/PlainAssemblyParser.cpp b/test/libevmasm/PlainAssemblyParser.cpp new file mode 100644 index 000000000000..5c12a3b02120 --- /dev/null +++ b/test/libevmasm/PlainAssemblyParser.cpp @@ -0,0 +1,182 @@ +/* + 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 + +using namespace std::string_literals; +using namespace solidity; +using namespace solidity::test; +using namespace solidity::evmasm; +using namespace solidity::evmasm::test; +using namespace solidity::langutil; + +Json PlainAssemblyParser::parse(std::string _sourceName, std::string const& _source) +{ + m_sourceName = std::move(_sourceName); + Json codeJSON = Json::array(); + std::istringstream sourceStream(_source); + while (getline(sourceStream, m_line)) + { + advanceLine(m_line); + if (m_lineTokens.empty()) + continue; + + if (c_instructions.contains(currentToken().value)) + { + expectNoMoreArguments(); + codeJSON.push_back({{"name", currentToken().value}}); + } + else if (currentToken().value == "PUSH") + { + if (hasMoreTokens() && nextToken().value == "[tag]") + { + advanceToken(); + std::string_view tagID = expectArgument(); + expectNoMoreArguments(); + codeJSON.push_back({{"name", "PUSH [tag]"}, {"value", tagID}}); + } + else + { + std::string_view immediateArgument = expectArgument(); + expectNoMoreArguments(); + + if (!immediateArgument.starts_with("0x")) + BOOST_THROW_EXCEPTION(std::runtime_error(formatError("The immediate argument to PUSH must be a hex number prefixed with '0x'."))); + + immediateArgument.remove_prefix("0x"s.size()); + codeJSON.push_back({{"name", "PUSH"}, {"value", immediateArgument}}); + } + } + else if (currentToken().value == "tag") + { + std::string_view tagID = expectArgument(); + expectNoMoreArguments(); + + codeJSON.push_back({{"name", "tag"}, {"value", tagID}}); + codeJSON.push_back({{"name", "JUMPDEST"}}); + } + else + BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Unknown instruction."))); + } + return {{".code", codeJSON}}; +} + +PlainAssemblyParser::Token const& PlainAssemblyParser::currentToken() const +{ + soltestAssert(m_tokenIndex < m_lineTokens.size()); + return m_lineTokens[m_tokenIndex]; +} + +PlainAssemblyParser::Token const& PlainAssemblyParser::nextToken() const +{ + soltestAssert(m_tokenIndex + 1 < m_lineTokens.size()); + return m_lineTokens[m_tokenIndex + 1]; +} + +bool PlainAssemblyParser::advanceToken() +{ + if (!hasMoreTokens()) + return false; + + ++m_tokenIndex; + return true; +} + +std::string_view PlainAssemblyParser::expectArgument() +{ + bool hasArgument = advanceToken(); + if (!hasArgument) + BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Missing argument(s)."))); + + return currentToken().value; +} + +void PlainAssemblyParser::expectNoMoreArguments() +{ + bool hasArgument = advanceToken(); + if (hasArgument) + BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Too many arguments."))); +} + +void PlainAssemblyParser::advanceLine(std::string_view _line) +{ + ++m_lineNumber; + m_line = _line; + m_lineTokens = tokenizeLine(m_line); + m_tokenIndex = 0; +} + +std::vector PlainAssemblyParser::tokenizeLine(std::string_view _line) +{ + auto const notWhiteSpace = [](char _c) { return !isWhiteSpace(_c); }; + + std::vector tokens; + auto tokenLocation = boost::find_token(_line, notWhiteSpace, boost::token_compress_on); + while (!tokenLocation.empty()) + { + std::string_view value{tokenLocation.begin(), tokenLocation.end()}; + if (value.starts_with("//")) + break; + + tokens.push_back({ + .value = value, + .position = static_cast(std::distance(_line.begin(), tokenLocation.begin())), + }); + soltestAssert(!value.empty()); + soltestAssert(tokens.back().position < _line.size()); + soltestAssert(tokens.back().position + value.size() <= _line.size()); + + std::string_view tail{tokenLocation.end(), _line.end()}; + tokenLocation = boost::find_token(tail, notWhiteSpace, boost::token_compress_on); + } + + return tokens; +} + +std::string PlainAssemblyParser::formatError(std::string_view _message) const +{ + soltestAssert(currentToken().value.size() >= 1); + + std::string lineNumberString = std::to_string(m_lineNumber); + std::string padding(lineNumberString.size(), ' '); + std::string underline = std::string(currentToken().position, ' ') + std::string(currentToken().value.size(), '^'); + return fmt::format( + "Error while parsing plain assembly: {}\n" + "{}--> {}\n" + "{} | \n" + "{} | {}\n" + "{} | {}\n", + _message, + padding, m_sourceName, + padding, + m_lineNumber, m_line, + padding, underline + ); +} diff --git a/test/libevmasm/PlainAssemblyParser.h b/test/libevmasm/PlainAssemblyParser.h new file mode 100644 index 000000000000..39957f611347 --- /dev/null +++ b/test/libevmasm/PlainAssemblyParser.h @@ -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 + +#pragma once + +#include + +#include +#include +#include + +namespace solidity::evmasm::test +{ + +/// Parser for the plain assembly format. The format is meant to be good enough for humans to read +/// while being sstraightforward to map the assembly JSON format that solc can import. +/// +/// Syntax: +/// - Every line consists of zero or more whitespace-separated tokens. +/// - A token that begins with `//` starts a comment, which extends to the end of the line. +/// - A non-empty line represents a single assembly item. +/// - The name of the item is the first thing on the line and may consist of one or more tokens. +/// - One or more arguments follow the name. +/// +/// Supported items: +/// - All instruction names. +/// - PUSH +/// - PUSH [tag] +/// - tag +class PlainAssemblyParser +{ +public: + /// Parses plain assembly format and returns the equivalent assembly JSON. + /// Errors are reported by throwing runtime_error. + Json parse(std::string _sourceName, std::string const& _source); + +protected: + struct Token + { + std::string_view value; ///< Substring of m_line that represents a complete token. + size_t position; ///< Position of the first character of the token within m_line. + }; + + Token const& currentToken() const; + Token const& nextToken() const; + bool hasMoreTokens() const { return m_tokenIndex + 1 < m_lineTokens.size(); } + + bool advanceToken(); + std::string_view expectArgument(); + void expectNoMoreArguments(); + void advanceLine(std::string_view _line); + + static std::vector tokenizeLine(std::string_view _line); + std::string formatError(std::string_view _message) const; + +private: + std::string m_sourceName; ///< Name of the file the source comes from. + size_t m_lineNumber = 0; ///< The number of the current line within the source, 1-based. + std::string m_line; ///< The current line, unparsed. + std::vector m_lineTokens; ///< Decomposition of the current line into tokens (does not include comments). + size_t m_tokenIndex = 0; ///< Points at a token within m_lineTokens. +}; + +} diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/comments.asm b/test/libevmasm/evmAssemblyTests/isoltestTesting/comments.asm new file mode 100644 index 000000000000..13dca3ad1b2f --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/comments.asm @@ -0,0 +1,34 @@ +// +//// comment + // comment +CALLVALUE // 0xff +CALLVALUE //0xff + +PUSH 0xff // comment // //0xff +// + +// +// ==== +// outputs: InputAssemblyJSON,Assembly,Bytecode,Opcodes,SourceMappings +// ---- +// InputAssemblyJSON: { +// ".code": [ +// { +// "name": "CALLVALUE" +// }, +// { +// "name": "CALLVALUE" +// }, +// { +// "name": "PUSH", +// "value": "ff" +// } +// ] +// } +// Assembly: +// callvalue +// callvalue +// 0xff +// Bytecode: 343460ff +// Opcodes: CALLVALUE CALLVALUE PUSH1 0xFF +// SourceMappings: :::-:0;; diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/jumps.asm b/test/libevmasm/evmAssemblyTests/isoltestTesting/jumps.asm new file mode 100644 index 000000000000..be92671d8141 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/jumps.asm @@ -0,0 +1,56 @@ +PUSH [tag] 1 +JUMP +tag 1 +PUSH 0x01 +JUMPI +PUSH [tag] 0x012AB +tag 0x012AB +// ==== +// outputs: InputAssemblyJSON,Assembly,Bytecode,Opcodes,SourceMappings +// ---- +// InputAssemblyJSON: { +// ".code": [ +// { +// "name": "PUSH [tag]", +// "value": "1" +// }, +// { +// "name": "JUMP" +// }, +// { +// "name": "tag", +// "value": "1" +// }, +// { +// "name": "JUMPDEST" +// }, +// { +// "name": "PUSH", +// "value": "01" +// }, +// { +// "name": "JUMPI" +// }, +// { +// "name": "PUSH [tag]", +// "value": "0x012AB" +// }, +// { +// "name": "tag", +// "value": "0x012AB" +// }, +// { +// "name": "JUMPDEST" +// } +// ] +// } +// Assembly: +// jump(tag_1) +// tag_1: +// 0x01 +// jumpi +// tag_4779 +// tag_4779: +// Bytecode: 6003565b60015760095b +// Opcodes: PUSH1 0x3 JUMP JUMPDEST PUSH1 0x1 JUMPI PUSH1 0x9 JUMPDEST +// SourceMappings: :::-:0;;;;;; diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/operations.asm b/test/libevmasm/evmAssemblyTests/isoltestTesting/operations.asm new file mode 100644 index 000000000000..923b0a6b1f37 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/operations.asm @@ -0,0 +1,53 @@ +NUMBER +SLOAD +ADDRESS +ORIGIN +ADD +DUP1 +SWAP1 +MSTORE8 +STOP +// ==== +// outputs: InputAssemblyJSON,Assembly,Bytecode,Opcodes,SourceMappings +// ---- +// InputAssemblyJSON: { +// ".code": [ +// { +// "name": "NUMBER" +// }, +// { +// "name": "SLOAD" +// }, +// { +// "name": "ADDRESS" +// }, +// { +// "name": "ORIGIN" +// }, +// { +// "name": "ADD" +// }, +// { +// "name": "DUP1" +// }, +// { +// "name": "SWAP1" +// }, +// { +// "name": "MSTORE8" +// }, +// { +// "name": "STOP" +// } +// ] +// } +// Assembly: +// sload(number) +// add(origin, address) +// dup1 +// swap1 +// mstore8 +// stop +// Bytecode: 435430320180905300 +// Opcodes: NUMBER SLOAD ADDRESS ORIGIN ADD DUP1 SWAP1 MSTORE8 STOP +// SourceMappings: :::-:0;;;;;;;; diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/push.asm b/test/libevmasm/evmAssemblyTests/isoltestTesting/push.asm new file mode 100644 index 000000000000..51bc160507c7 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/push.asm @@ -0,0 +1,35 @@ +PUSH 0x0 +PUSH 0x1 +PUSH 0x0123456789ABCDEF +PUSH 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// ==== +// outputs: InputAssemblyJSON,Assembly,Bytecode,Opcodes,SourceMappings +// ---- +// InputAssemblyJSON: { +// ".code": [ +// { +// "name": "PUSH", +// "value": "0" +// }, +// { +// "name": "PUSH", +// "value": "1" +// }, +// { +// "name": "PUSH", +// "value": "0123456789ABCDEF" +// }, +// { +// "name": "PUSH", +// "value": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +// } +// ] +// } +// Assembly: +// 0x00 +// 0x01 +// 0x0123456789abcdef +// 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// Bytecode: 5f6001670123456789abcdef7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// Opcodes: PUSH0 PUSH1 0x1 PUSH8 0x123456789ABCDEF PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// SourceMappings: :::-:0;;; diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/smoke_plain.asm b/test/libevmasm/evmAssemblyTests/isoltestTesting/smoke_plain.asm new file mode 100644 index 000000000000..a1714241fb86 --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/smoke_plain.asm @@ -0,0 +1,16 @@ +CALLVALUE +// ==== +// outputs: InputAssemblyJSON,Assembly,Bytecode,Opcodes,SourceMappings +// ---- +// InputAssemblyJSON: { +// ".code": [ +// { +// "name": "CALLVALUE" +// } +// ] +// } +// Assembly: +// callvalue +// Bytecode: 34 +// Opcodes: CALLVALUE +// SourceMappings: :::-:0 diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 13018bf18512..289f2f2def35 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(isoltest ../TestCase.cpp ../TestCaseReader.cpp ../libevmasm/EVMAssemblyTest.cpp + ../libevmasm/PlainAssemblyParser.cpp ../libsolidity/util/BytesUtils.cpp ../libsolidity/util/Common.cpp ../libsolidity/util/ContractABIUtils.cpp diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 32309b005507..26efc089020b 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -75,7 +75,7 @@ class TestFilter boost::replace_all(filter, "/", "\\/"); boost::replace_all(filter, "*", ".*"); - m_filterExpression = std::regex{"(" + filter + "(\\.sol|\\.yul|\\.asmjson|\\.stack))"}; + m_filterExpression = std::regex{"(" + filter + "(\\.sol|\\.yul|\\.asm|\\.asmjson|\\.stack))"}; } bool matches(fs::path const& _path, std::string const& _name) const From db5f1f72b174664a331feed8ffac91f0f5a557e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 16 Apr 2025 02:49:54 +0200 Subject: [PATCH 06/14] Remove unnecessary namespace prefixes, some aliases and unused imports from a few test casees --- libevmasm/Assembly.cpp | 2 +- test/Common.cpp | 10 ++++---- test/CommonSyntaxTest.cpp | 5 ++-- test/CommonSyntaxTest.h | 1 - test/ExecutionFramework.h | 2 -- test/InteractiveTests.h | 5 +++- test/TestCase.cpp | 7 +++--- test/libevmasm/Optimiser.cpp | 1 - test/libsolidity/ABIJsonTest.cpp | 8 +++--- test/libsolidity/ASTJSONTest.cpp | 9 +++---- test/libsolidity/ASTPropertyTest.cpp | 5 ++-- test/libsolidity/GasTest.cpp | 4 ++- test/libsolidity/NatspecJSONTest.cpp | 2 -- test/libsolidity/SemanticTest.cpp | 20 +++++++-------- test/libsolidity/SyntaxTest.cpp | 10 ++++---- test/libyul/Common.cpp | 2 -- test/libyul/ObjectCompilerTest.cpp | 5 ++-- test/libyul/YulOptimizerTest.cpp | 2 +- test/soltest.cpp | 33 +++++++++++++------------ test/tools/isoltest.cpp | 37 +++++++++++++--------------- 20 files changed, 84 insertions(+), 86 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index a0711cd41756..d27e26c3e7b4 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -43,9 +43,9 @@ #include #include -#include #include #include +#include #include using namespace solidity; diff --git a/test/Common.cpp b/test/Common.cpp index d39c6db1ea9e..a140b31cc858 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -113,7 +113,7 @@ void CommonOptions::addOptions() ("evm-version", po::value(&evmVersionString), "which EVM version to use") // "eof-version" is declared as uint64_t, since uint8_t will be parsed as character by boost. ("eof-version", po::value()->implicit_value(1u), "which EOF version to use") - ("testpath", po::value(&this->testPath)->default_value(solidity::test::testPath()), "path to test files") + ("testpath", po::value(&this->testPath)->default_value(test::testPath()), "path to test files") ("vm", po::value>(&vmPaths), "path to evmc library, can be supplied multiple times.") ("batches", po::value(&this->batches)->default_value(1), "set number of batches to split the tests into") ("selected-batch", po::value(&this->selectedBatch)->default_value(0), "zero-based number of batch to execute") @@ -309,7 +309,7 @@ bool isValidSemanticTestPath(boost::filesystem::path const& _testPath) boost::unit_test::precondition::predicate_t nonEOF() { return [](boost::unit_test::test_unit_id) { - return !solidity::test::CommonOptions::get().eofVersion().has_value(); + return !CommonOptions::get().eofVersion().has_value(); }; } @@ -325,13 +325,13 @@ bool loadVMs(CommonOptions const& _options) if (_options.disableSemanticTests) return true; - bool evmSupported = solidity::test::EVMHost::checkVmPaths(_options.vmPaths); + bool evmSupported = EVMHost::checkVmPaths(_options.vmPaths); if (!_options.disableSemanticTests && !evmSupported) { - std::cerr << "Unable to find " << solidity::test::evmoneFilename; + std::cerr << "Unable to find " << evmoneFilename; std::cerr << ". Please disable semantics tests with --no-semantic-tests or provide a path using --vm ." << std::endl; std::cerr << "You can download it at" << std::endl; - std::cerr << solidity::test::evmoneDownloadLink << std::endl; + std::cerr << evmoneDownloadLink << std::endl; return false; } return true; diff --git a/test/CommonSyntaxTest.cpp b/test/CommonSyntaxTest.cpp index ccf4added41e..9a7d0949cab5 100644 --- a/test/CommonSyntaxTest.cpp +++ b/test/CommonSyntaxTest.cpp @@ -30,8 +30,8 @@ #include #include -#include -#include +#include +#include #include using namespace solidity; @@ -42,7 +42,6 @@ using namespace solidity::frontend; using namespace solidity::frontend::test; using namespace solidity::test; using namespace boost::unit_test; -namespace fs = boost::filesystem; namespace { diff --git a/test/CommonSyntaxTest.h b/test/CommonSyntaxTest.h index fb46c37a6401..28aa27a5f07f 100644 --- a/test/CommonSyntaxTest.h +++ b/test/CommonSyntaxTest.h @@ -27,7 +27,6 @@ #include #include #include -#include namespace solidity::test { diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 44812e230ab0..3dad1fd43114 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -34,8 +34,6 @@ #include #include -#include - #include #include diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 107a6aae0bef..de6d6c5e9b8d 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -19,10 +19,10 @@ #pragma once #include + #include #include #include -#include #include #include #include @@ -30,6 +30,7 @@ #include #include #include + #include #include #include @@ -44,6 +45,8 @@ #include +#include + #include namespace solidity::frontend::test diff --git a/test/TestCase.cpp b/test/TestCase.cpp index bd4c92b7dd8e..30cc4cfc6099 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -31,6 +31,7 @@ using namespace solidity; using namespace solidity::frontend; using namespace solidity::frontend::test; using namespace solidity::util; +using namespace solidity::test; void TestCase::printSettings(std::ostream& _stream, const std::string& _linePrefix, const bool) { @@ -123,7 +124,7 @@ void EVMVersionRestrictedTestCase::processEVMVersionSetting() if (!version) BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid EVM version: \"" + versionString + "\""}); - langutil::EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); + langutil::EVMVersion evmVersion = CommonOptions::get().evmVersion(); bool comparisonResult; if (comparator == ">") comparisonResult = evmVersion > version; @@ -146,9 +147,9 @@ void EVMVersionRestrictedTestCase::processEVMVersionSetting() void EVMVersionRestrictedTestCase::processBytecodeFormatSetting() { - std::optional eofVersion = solidity::test::CommonOptions::get().eofVersion(); + std::optional eofVersion = CommonOptions::get().eofVersion(); // EOF only available since Osaka - solAssert(!eofVersion.has_value() || solidity::test::CommonOptions::get().evmVersion().supportsEOF()); + solAssert(!eofVersion.has_value() || CommonOptions::get().evmVersion().supportsEOF()); std::string bytecodeFormatString = m_reader.stringSetting("bytecodeFormat", "legacy,>=EOFv1"); if (bytecodeFormatString == "legacy,>=EOFv1" || bytecodeFormatString == ">=EOFv1,legacy") diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index f4b7034de544..7a4ef0fac909 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -36,7 +36,6 @@ #include #include -#include #include using namespace solidity::langutil; diff --git a/test/libsolidity/ABIJsonTest.cpp b/test/libsolidity/ABIJsonTest.cpp index 803445b96ea6..5f5e535ef4a5 100644 --- a/test/libsolidity/ABIJsonTest.cpp +++ b/test/libsolidity/ABIJsonTest.cpp @@ -26,12 +26,14 @@ #include #include -#include +#include +#include using namespace solidity; using namespace solidity::util; using namespace solidity::frontend; using namespace solidity::frontend::test; +using namespace solidity::test; ABIJsonTest::ABIJsonTest(std::string const& _filename): TestCase(_filename) @@ -48,8 +50,8 @@ TestCase::TestResult ABIJsonTest::run(std::ostream& _stream, std::string const& "", "pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n" + m_source }}); - compiler.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); - compiler.setOptimiserSettings(solidity::test::CommonOptions::get().optimize); + compiler.setEVMVersion(CommonOptions::get().evmVersion()); + compiler.setOptimiserSettings(CommonOptions::get().optimize); if (!compiler.parseAndAnalyze()) BOOST_THROW_EXCEPTION(std::runtime_error("Parsing contract failed")); diff --git a/test/libsolidity/ASTJSONTest.cpp b/test/libsolidity/ASTJSONTest.cpp index daf4a9ea44e2..e49866241dc1 100644 --- a/test/libsolidity/ASTJSONTest.cpp +++ b/test/libsolidity/ASTJSONTest.cpp @@ -33,16 +33,15 @@ #include #include -#include #include using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; using namespace solidity::frontend::test; +using namespace solidity::test; using namespace solidity::util::formatting; using namespace solidity::util; -namespace fs = boost::filesystem; using namespace boost::unit_test; using namespace std::string_literals; @@ -79,7 +78,7 @@ void replaceVersionWithTag(std::string& _input) { boost::algorithm::replace_all( _input, - "\"" + solidity::test::CommonOptions::get().evmVersion().name() + "\"", + "\"" + CommonOptions::get().evmVersion().name() + "\"", "%EVMVERSION%" ); } @@ -89,7 +88,7 @@ void replaceTagWithVersion(std::string& _input) boost::algorithm::replace_all( _input, "%EVMVERSION%", - "\"" + solidity::test::CommonOptions::get().evmVersion().name() + "\"" + "\"" + CommonOptions::get().evmVersion().name() + "\"" ); } @@ -210,7 +209,7 @@ TestCase::TestResult ASTJSONTest::run(std::ostream& _stream, std::string const& { c.reset(); c.setSources(sources); - c.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); + c.setEVMVersion(CommonOptions::get().evmVersion()); if (!c.parseAndAnalyze(variant.stopAfter)) { diff --git a/test/libsolidity/ASTPropertyTest.cpp b/test/libsolidity/ASTPropertyTest.cpp index 3419d51c577c..94a4f292bc7f 100644 --- a/test/libsolidity/ASTPropertyTest.cpp +++ b/test/libsolidity/ASTPropertyTest.cpp @@ -36,6 +36,7 @@ #include using namespace solidity::util; +using namespace solidity::test; using namespace solidity::langutil; using namespace solidity::frontend; using namespace solidity::frontend::test; @@ -191,8 +192,8 @@ TestCase::TestResult ASTPropertyTest::run(std::ostream& _stream, std::string con "A", "pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n" + m_source }}); - compiler.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); - compiler.setOptimiserSettings(solidity::test::CommonOptions::get().optimize); + compiler.setEVMVersion(CommonOptions::get().evmVersion()); + compiler.setOptimiserSettings(CommonOptions::get().optimize); if (!compiler.parseAndAnalyze()) BOOST_THROW_EXCEPTION(std::runtime_error( "Parsing contract failed" + diff --git a/test/libsolidity/GasTest.cpp b/test/libsolidity/GasTest.cpp index 39b0e6681abe..603878657880 100644 --- a/test/libsolidity/GasTest.cpp +++ b/test/libsolidity/GasTest.cpp @@ -27,7 +27,9 @@ #include #include #include -#include + +#include +#include #include using namespace solidity::langutil; diff --git a/test/libsolidity/NatspecJSONTest.cpp b/test/libsolidity/NatspecJSONTest.cpp index 7dff638f0de5..d1a49a2e95a3 100644 --- a/test/libsolidity/NatspecJSONTest.cpp +++ b/test/libsolidity/NatspecJSONTest.cpp @@ -27,8 +27,6 @@ #include -#include - using namespace solidity::frontend::test; using namespace solidity::util; using namespace std::string_literals; diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 1b66dd60fffb..faad1bad39a8 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -24,12 +24,12 @@ #include #include -#include #include -#include +#include #include -#include #include +#include +#include #include #include #include @@ -37,13 +37,13 @@ using namespace solidity; using namespace solidity::yul; using namespace solidity::langutil; +using namespace solidity::test; using namespace solidity::util; using namespace solidity::util::formatting; using namespace solidity::frontend::test; using namespace boost::algorithm; using namespace boost::unit_test; using namespace std::string_literals; -namespace fs = boost::filesystem; std::ostream& solidity::frontend::test::operator<<(std::ostream& _output, RequiresYulOptimizer _requiresYulOptimizer) { @@ -88,10 +88,10 @@ SemanticTest::SemanticTest( ); m_runWithABIEncoderV1Only = m_reader.boolSetting("ABIEncoderV1Only", false); - if (m_runWithABIEncoderV1Only && !solidity::test::CommonOptions::get().useABIEncoderV1) + if (m_runWithABIEncoderV1Only && !CommonOptions::get().useABIEncoderV1) m_shouldRun = false; - auto const eofEnabled = solidity::test::CommonOptions::get().eofVersion().has_value(); + auto const eofEnabled = CommonOptions::get().eofVersion().has_value(); std::string compileViaYul = m_reader.stringSetting("compileViaYul", eofEnabled ? "true" : "also"); if (compileViaYul == "false" && eofEnabled) @@ -326,14 +326,14 @@ TestCase::TestResult SemanticTest::run(std::ostream& _stream, std::string const& if (m_testCaseWantsYulRun && result == TestResult::Success) { - if (solidity::test::CommonOptions::get().optimize) + if (CommonOptions::get().optimize) result = runTest(_stream, _linePrefix, _formatted, true /* _isYulRun */); else result = tryRunTestWithYulOptimizer(_stream, _linePrefix, _formatted); } if (result != TestResult::Success) - solidity::test::CommonOptions::get().printSelectedOptions( + CommonOptions::get().printSelectedOptions( _stream, _linePrefix, {"evmVersion", "optimize", "useABIEncoderV1", "batch"} @@ -364,7 +364,7 @@ TestCase::TestResult SemanticTest::runTest( for (TestFunctionCall& test: m_tests) test.reset(); - std::map libraries; + std::map libraries; bool constructed = false; @@ -703,7 +703,7 @@ bool SemanticTest::deploy( std::string const& _contractName, u256 const& _value, bytes const& _arguments, - std::map const& _libraries + std::map const& _libraries ) { auto output = compileAndRunWithoutCheck(m_sources.sources, _value, _contractName, _arguments, _libraries, m_sources.mainSourceFile); diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index 8944710542a5..a57407de4563 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -25,7 +25,7 @@ #include #include #include -#include + #include #include @@ -35,8 +35,8 @@ using namespace solidity::util::formatting; using namespace solidity::langutil; using namespace solidity::frontend; using namespace solidity::frontend::test; +using namespace solidity::test; using namespace boost::unit_test; -namespace fs = boost::filesystem; SyntaxTest::SyntaxTest( std::string const& _filename, @@ -48,7 +48,7 @@ SyntaxTest::SyntaxTest( { static std::set const compileViaYulAllowedValues{"true", "false"}; - auto const eofEnabled = solidity::test::CommonOptions::get().eofVersion().has_value(); + auto const eofEnabled = CommonOptions::get().eofVersion().has_value(); m_compileViaYul = m_reader.stringSetting("compileViaYul", eofEnabled ? "true" : "false"); if (!util::contains(compileViaYulAllowedValues, m_compileViaYul)) @@ -133,8 +133,8 @@ void SyntaxTest::filterObtainedErrors() if(m_sources.sources.count(sourceName) == 1) { int preambleSize = - static_cast(compiler().charStream(sourceName).size()) - - static_cast(m_sources.sources[sourceName].size()); + static_cast(compiler().charStream(sourceName).size()) - + static_cast(m_sources.sources[sourceName].size()); solAssert(preambleSize >= 0, ""); // ignore the version & license pragma inserted by the testing tool when calculating locations. diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index a9f4c59a74ac..cf572f8c6676 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -41,8 +41,6 @@ #include -#include - using namespace solidity; using namespace solidity::frontend; using namespace solidity::yul; diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index b7e475c91b1b..2ce9a0789ef0 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -42,9 +42,10 @@ using namespace solidity::yul; using namespace solidity::yul::test; using namespace solidity::frontend; using namespace solidity::frontend::test; +using namespace solidity::test; ObjectCompilerTest::ObjectCompilerTest(std::string const& _filename): - solidity::frontend::test::EVMVersionRestrictedTestCase(_filename) + EVMVersionRestrictedTestCase(_filename) { m_source = m_reader.source(); m_optimisationPreset = m_reader.enumSetting( @@ -97,7 +98,7 @@ TestCase::TestResult ObjectCompilerTest::run(std::ostream& _stream, std::string { m_obtainedResult += (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n') ? "\n" : ""; m_obtainedResult += "Opcodes: " + - boost::trim_copy(evmasm::disassemble(obj.bytecode->bytecode, solidity::test::CommonOptions::get().evmVersion())); + boost::trim_copy(evmasm::disassemble(obj.bytecode->bytecode, CommonOptions::get().evmVersion())); } if (std::find(m_outputSetting.begin(), m_outputSetting.end(), "SourceMappings") != m_outputSetting.end()) { diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 1048fb468854..0ce8617c29d7 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -35,7 +35,7 @@ #include #include -#include +#include using namespace solidity; using namespace solidity::util; diff --git a/test/soltest.cpp b/test/soltest.cpp index 9bc60d1181f8..db647ff9256e 100644 --- a/test/soltest.cpp +++ b/test/soltest.cpp @@ -49,6 +49,7 @@ using namespace boost::unit_test; using namespace solidity::frontend::test; +using namespace solidity::test; namespace fs = boost::filesystem; namespace @@ -68,7 +69,7 @@ void removeTestSuite(std::string const& _name) class BoostBatcher: public test_tree_visitor { public: - BoostBatcher(solidity::test::Batcher& _batcher): + BoostBatcher(Batcher& _batcher): m_batcher(_batcher) {} @@ -91,7 +92,7 @@ class BoostBatcher: public test_tree_visitor } private: - solidity::test::Batcher& m_batcher; + Batcher& m_batcher; std::vector m_path; }; @@ -127,18 +128,18 @@ int registerTests( boost::filesystem::path const& _path, std::vector const& _labels, TestCase::TestCaseCreator _testCaseCreator, - solidity::test::Batcher& _batcher + Batcher& _batcher ) { int numTestsAdded = 0; fs::path fullpath = _basepath / _path; TestCase::Config config{ fullpath.string(), - solidity::test::CommonOptions::get().evmVersion(), - solidity::test::CommonOptions::get().eofVersion(), - solidity::test::CommonOptions::get().vmPaths, - solidity::test::CommonOptions::get().enforceGasTest, - solidity::test::CommonOptions::get().enforceGasTestMinValue, + CommonOptions::get().evmVersion(), + CommonOptions::get().eofVersion(), + CommonOptions::get().vmPaths, + CommonOptions::get().enforceGasTest, + CommonOptions::get().enforceGasTestMinValue, }; if (fs::is_directory(fullpath)) { @@ -148,7 +149,7 @@ int registerTests( fs::directory_iterator() )) if ( - solidity::test::isValidSemanticTestPath(entry) && + isValidSemanticTestPath(entry) && (fs::is_directory(entry.path()) || TestCase::isTestFilename(entry.path().filename())) ) numTestsAdded += registerTests( @@ -195,13 +196,13 @@ bool initializeOptions() { auto const& suite = boost::unit_test::framework::master_test_suite(); - auto options = std::make_unique(); + auto options = std::make_unique(); bool shouldContinue = options->parse(suite.argc, suite.argv); if (!shouldContinue) return false; options->validate(); - solidity::test::CommonOptions::setSingleton(std::move(options)); + CommonOptions::setSingleton(std::move(options)); return true; } @@ -224,10 +225,10 @@ test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[]) if (!shouldContinue) exit(EXIT_SUCCESS); - if (!solidity::test::loadVMs(solidity::test::CommonOptions::get())) + if (!loadVMs(CommonOptions::get())) exit(EXIT_FAILURE); - if (solidity::test::CommonOptions::get().disableSemanticTests) + if (CommonOptions::get().disableSemanticTests) std::cout << std::endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << std::endl << std::endl; Batcher batcher(CommonOptions::get().selectedBatch, CommonOptions::get().batches); @@ -241,12 +242,12 @@ test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[]) // Include the interactive tests in the automatic tests as well for (auto const& ts: g_interactiveTestsuites) { - auto const& options = solidity::test::CommonOptions::get(); + auto const& options = CommonOptions::get(); if (ts.smt && options.disableSMT) continue; - if (ts.needsVM && solidity::test::CommonOptions::get().disableSemanticTests) + if (ts.needsVM && CommonOptions::get().disableSemanticTests) continue; //TODO @@ -262,7 +263,7 @@ test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[]) // > 0, std::string("no ") + ts.title + " tests found"); } - if (solidity::test::CommonOptions::get().disableSemanticTests) + if (CommonOptions::get().disableSemanticTests) { for (auto suite: { "ABIDecoderTest", diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 26efc089020b..e0a7635c60c2 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -43,13 +43,10 @@ using namespace solidity::util; using namespace solidity::frontend; using namespace solidity::frontend::test; using namespace solidity::util::formatting; +using namespace solidity::test; -namespace po = boost::program_options; namespace fs = boost::filesystem; -using TestCreator = TestCase::TestCaseCreator; -using TestOptions = solidity::test::IsolTestOptions; - struct TestStats { int successCount = 0; @@ -80,7 +77,7 @@ class TestFilter bool matches(fs::path const& _path, std::string const& _name) const { - return std::regex_match(_name, m_filterExpression) && solidity::test::isValidSemanticTestPath(_path); + return std::regex_match(_name, m_filterExpression) && isValidSemanticTestPath(_path); } private: @@ -92,8 +89,8 @@ class TestTool { public: TestTool( - TestCreator _testCaseCreator, - TestOptions const& _options, + TestCase::TestCaseCreator _testCaseCreator, + IsolTestOptions const& _options, fs::path _path, std::string _name ): @@ -115,11 +112,11 @@ class TestTool Result process(); static TestStats processPath( - TestCreator _testCaseCreator, - TestOptions const& _options, + TestCase::TestCaseCreator _testCaseCreator, + IsolTestOptions const& _options, fs::path const& _basepath, fs::path const& _path, - solidity::test::Batcher& _batcher + Batcher& _batcher ); private: enum class Request @@ -132,8 +129,8 @@ class TestTool void updateTestCase(); Request handleResponse(bool _exception); - TestCreator m_testCaseCreator; - TestOptions const& m_options; + TestCase::TestCaseCreator m_testCaseCreator; + IsolTestOptions const& m_options; TestFilter m_filter; fs::path const m_path; std::string const m_name; @@ -253,11 +250,11 @@ TestTool::Request TestTool::handleResponse(bool _exception) } TestStats TestTool::processPath( - TestCreator _testCaseCreator, - TestOptions const& _options, + TestCase::TestCaseCreator _testCaseCreator, + IsolTestOptions const& _options, fs::path const& _basepath, fs::path const& _path, - solidity::test::Batcher& _batcher + Batcher& _batcher ) { std::queue paths; @@ -362,12 +359,12 @@ void setupTerminal() } std::optional runTestSuite( - TestCreator _testCaseCreator, - TestOptions const& _options, + TestCase::TestCaseCreator _testCaseCreator, + IsolTestOptions const& _options, fs::path const& _basePath, fs::path const& _subdirectory, std::string const& _name, - solidity::test::Batcher& _batcher + Batcher& _batcher ) { fs::path testPath{_basePath / _subdirectory}; @@ -429,7 +426,7 @@ int main(int argc, char const *argv[]) auto& options = dynamic_cast(CommonOptions::get()); - if (!solidity::test::loadVMs(options)) + if (!loadVMs(options)) return EXIT_FAILURE; if (options.disableSemanticTests) @@ -493,7 +490,7 @@ int main(int argc, char const *argv[]) std::cerr << exception.what() << std::endl; return 2; } - catch (solidity::test::ConfigException const& exception) + catch (ConfigException const& exception) { std::cerr << exception.what() << std::endl; return 2; From 7cd9e42ac05475b2e199be936bc7ea0f18d77b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 14 Apr 2025 14:32:57 +0200 Subject: [PATCH 07/14] Unify the lists of allowed test file extensions --- test/Common.cpp | 11 +++++++++++ test/Common.h | 3 +++ test/TestCase.cpp | 5 ++--- test/tools/isoltest.cpp | 22 +++++++++++++++------- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/test/Common.cpp b/test/Common.cpp index a140b31cc858..b509ec01a46f 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -306,6 +306,17 @@ bool isValidSemanticTestPath(boost::filesystem::path const& _testPath) return true; } +std::set testFileExtensions() +{ + return { + ".sol", + ".yul", + ".asm", + ".asmjson", // Not .json because JSON files that do not represent test cases exist in some test dirs. + ".stack", + }; +} + boost::unit_test::precondition::predicate_t nonEOF() { return [](boost::unit_test::test_unit_id) { diff --git a/test/Common.h b/test/Common.h index 27c2081346fc..ff92ab38b233 100644 --- a/test/Common.h +++ b/test/Common.h @@ -107,6 +107,9 @@ struct CommonOptions /// Note: @p _testPath can be relative but must include at least the `/test/libsolidity/semanticTests/` part bool isValidSemanticTestPath(boost::filesystem::path const& _testPath); +/// Returns a list of file extensions allowed for test files. +std::set testFileExtensions(); + /// Helper that can be used to skip tests when the EVM version selected on the command line /// is older than @p _minEVMVersion. /// @return A predicate (function) that can be passed into @a boost::unit_test::precondition(). diff --git a/test/TestCase.cpp b/test/TestCase.cpp index 30cc4cfc6099..20cbf5878889 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -51,9 +51,8 @@ void TestCase::printUpdatedSettings(std::ostream& _stream, std::string const& _l bool TestCase::isTestFilename(boost::filesystem::path const& _filename) { - std::string extension = _filename.extension().string(); - // NOTE: .asmjson rather than .json because JSON files that do not represent test cases exist in some test dirs. - return (extension == ".sol" || extension == ".yul" || extension == ".asm" || extension == ".asmjson" || extension == ".stack") && + return + testFileExtensions().contains(_filename.extension().string()) && !boost::starts_with(_filename.string(), "~") && !boost::starts_with(_filename.string(), "."); } diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index e0a7635c60c2..2d8f06e5bc29 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -25,9 +25,14 @@ #include #include +#include #include #include +#include + +#include + #include #include #include @@ -65,14 +70,17 @@ struct TestStats class TestFilter { public: - explicit TestFilter(std::string _filter): m_filter(std::move(_filter)) + explicit TestFilter(std::string _filter): + m_filter(std::move(_filter)) { - std::string filter{m_filter}; - - boost::replace_all(filter, "/", "\\/"); - boost::replace_all(filter, "*", ".*"); - - m_filterExpression = std::regex{"(" + filter + "(\\.sol|\\.yul|\\.asm|\\.asmjson|\\.stack))"}; + auto const startsWithDot = [](std::string const& _extension) { return boost::starts_with(_extension, "."); }; + soltestAssert(ranges::all_of(testFileExtensions(), startsWithDot)); + + m_filterExpression = std::regex{fmt::format( + "({}({}))", + boost::replace_all_copy(boost::replace_all_copy(m_filter, "/", "\\/"), "*", ".*"), + boost::replace_all_copy(joinHumanReadable(testFileExtensions(), "|"), ".", "\\.") + )}; } bool matches(fs::path const& _path, std::string const& _name) const From 58795c7813218d6a985e7a921d0c89570a0b6a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Apr 2025 00:33:19 +0200 Subject: [PATCH 08/14] Add missing m_ prefix to CommonOptions::evmVersionString --- test/Common.cpp | 8 ++++---- test/Common.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/Common.cpp b/test/Common.cpp index b509ec01a46f..9dd9df0df0a5 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -110,7 +110,7 @@ CommonOptions::CommonOptions(std::string _caption): void CommonOptions::addOptions() { options.add_options() - ("evm-version", po::value(&evmVersionString), "which EVM version to use") + ("evm-version", po::value(&m_evmVersionString), "which EVM version to use") // "eof-version" is declared as uint64_t, since uint8_t will be parsed as character by boost. ("eof-version", po::value()->implicit_value(1u), "which EOF version to use") ("testpath", po::value(&this->testPath)->default_value(test::testPath()), "path to test files") @@ -259,11 +259,11 @@ void CommonOptions::printSelectedOptions(std::ostream& _stream, std::string cons langutil::EVMVersion CommonOptions::evmVersion() const { - if (!evmVersionString.empty()) + if (!m_evmVersionString.empty()) { - auto version = langutil::EVMVersion::fromString(evmVersionString); + auto version = langutil::EVMVersion::fromString(m_evmVersionString); if (!version) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid EVM version: " + evmVersionString)); + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid EVM version: " + m_evmVersionString)); return *version; } else diff --git a/test/Common.h b/test/Common.h index ff92ab38b233..d67c2ed991c4 100644 --- a/test/Common.h +++ b/test/Common.h @@ -97,7 +97,7 @@ struct CommonOptions boost::program_options::options_description options; private: - std::string evmVersionString; + std::string m_evmVersionString; std::optional m_eofVersion; static std::unique_ptr m_singleton; }; From 6c749871bf54ae7d38c22d7b28feace1fbdda80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Apr 2025 00:30:01 +0200 Subject: [PATCH 09/14] Replace runtime_error in soltest with ConfigException/assertions where appropriate --- test/Common.cpp | 15 +++++---------- test/Common.h | 2 +- test/EVMHost.cpp | 5 +++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/test/Common.cpp b/test/Common.cpp index 9dd9df0df0a5..f8279112a146 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -185,8 +186,7 @@ bool CommonOptions::parse(int argc, char const* const* argv) { // Request as uint64_t, since uint8_t will be parsed as character by boost. uint64_t eofVersion = arguments["eof-version"].as(); - if (eofVersion != 1) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid EOF version: " + std::to_string(eofVersion))); + solRequire(eofVersion == 1, ConfigException, "Invalid EOF version: " + std::to_string(eofVersion)); m_eofVersion = 1; } @@ -198,11 +198,7 @@ bool CommonOptions::parse(int argc, char const* const* argv) (parsedOption.original_tokens.size() == 1 && parsedOption.original_tokens.front().empty()) ) continue; // ignore empty options - std::stringstream errorMessage; - errorMessage << "Unrecognized option: "; - for (auto const& token: parsedOption.original_tokens) - errorMessage << token; - BOOST_THROW_EXCEPTION(std::runtime_error(errorMessage.str())); + solThrow(ConfigException, "Unrecognized option: " + util::joinHumanReadable(parsedOption.original_tokens, "")); } } catch (po::error const& exception) @@ -262,8 +258,7 @@ langutil::EVMVersion CommonOptions::evmVersion() const if (!m_evmVersionString.empty()) { auto version = langutil::EVMVersion::fromString(m_evmVersionString); - if (!version) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid EVM version: " + m_evmVersionString)); + solRequire(version, ConfigException, "Invalid EVM version: " + m_evmVersionString); return *version; } else @@ -279,7 +274,7 @@ yul::EVMDialect const& CommonOptions::evmDialect() const CommonOptions const& CommonOptions::get() { if (!m_singleton) - BOOST_THROW_EXCEPTION(std::runtime_error("Options not yet constructed!")); + soltestAssert(false, "Options not yet constructed!"); return *m_singleton; } diff --git a/test/Common.h b/test/Common.h index d67c2ed991c4..ffb22a0ce09d 100644 --- a/test/Common.h +++ b/test/Common.h @@ -76,7 +76,7 @@ struct CommonOptions virtual void addOptions(); // @returns true if the program should continue, false if it should exit immediately without // reporting an error. - // Throws ConfigException or std::runtime_error if parsing fails. + // Throws ConfigException if parsing fails. virtual bool parse(int argc, char const* const* argv); // Throws a ConfigException on error virtual void validate() const; diff --git a/test/EVMHost.cpp b/test/EVMHost.cpp index 983615312efd..1bfbeec08a5a 100644 --- a/test/EVMHost.cpp +++ b/test/EVMHost.cpp @@ -29,6 +29,8 @@ #endif #include + +#include #include #if defined(__GNUC__) && !defined(__clang__) // GCC-specific pragma @@ -91,8 +93,7 @@ bool EVMHost::checkVmPaths(std::vector const& _vmPaths) if (vm.has_capability(EVMC_CAPABILITY_EVM1)) { - if (evmVmFound) - BOOST_THROW_EXCEPTION(std::runtime_error("Multiple evm1 evmc vms defined. Please only define one evm1 evmc vm.")); + solRequire(!evmVmFound, ConfigException, "Multiple evm1 evmc vms defined. Please only define one evm1 evmc vm."); evmVmFound = true; } } From 6495949558ac7ab9f80c36ca6fc4e998d625221e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Apr 2025 01:48:58 +0200 Subject: [PATCH 10/14] Introduce dedicated exception classes for soltest, derived from util::Exception --- test/Common.cpp | 1 - test/Common.h | 5 +++- test/CommonSyntaxTest.cpp | 6 ++--- test/FilesystemUtils.cpp | 19 ++++++++------ test/TestCase.cpp | 12 ++++----- test/TestCaseReader.cpp | 34 ++++++++++++++------------ test/TestCaseReader.h | 17 ++++++++----- test/libevmasm/EVMAssemblyTest.cpp | 2 +- test/libevmasm/PlainAssemblyParser.cpp | 8 +++--- test/libevmasm/PlainAssemblyParser.h | 2 +- test/libsolidity/ABIJsonTest.cpp | 3 +-- test/libsolidity/ASTJSONTest.cpp | 29 +++++++++------------- test/libsolidity/ASTPropertyTest.cpp | 10 ++++---- test/libsolidity/GasTest.cpp | 9 +++---- test/libsolidity/NatspecJSONTest.cpp | 13 ++++------ test/libsolidity/SMTCheckerTest.cpp | 20 +++++++-------- test/libsolidity/SemanticTest.cpp | 9 ++++--- test/libsolidity/SyntaxTest.cpp | 10 ++++---- test/libyul/Common.cpp | 12 ++++----- test/libyul/ObjectCompilerTest.cpp | 2 +- test/libyul/StackShufflingTest.cpp | 5 ++-- test/libyul/YulOptimizerTest.cpp | 3 ++- test/soltest.cpp | 7 +----- test/tools/isoltest.cpp | 12 ++++----- 24 files changed, 123 insertions(+), 127 deletions(-) diff --git a/test/Common.cpp b/test/Common.cpp index f8279112a146..3a6546d796d3 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -34,7 +34,6 @@ #include #include -#include namespace fs = boost::filesystem; namespace po = boost::program_options; diff --git a/test/Common.h b/test/Common.h index ffb22a0ce09d..01dbf86f6914 100644 --- a/test/Common.h +++ b/test/Common.h @@ -48,7 +48,10 @@ static constexpr auto evmoneFilename = "libevmone.so"; static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.13.0/evmone-0.13.0-linux-x86_64.tar.gz"; #endif -struct ConfigException: public util::Exception {}; +struct SoltestError: public util::Exception {}; +struct ConfigException: public SoltestError {}; +struct ValidationError: public SoltestError {}; +struct ExecutionError: public SoltestError {}; struct CommonOptions { diff --git a/test/CommonSyntaxTest.cpp b/test/CommonSyntaxTest.cpp index 9a7d0949cab5..e53b9d803552 100644 --- a/test/CommonSyntaxTest.cpp +++ b/test/CommonSyntaxTest.cpp @@ -32,7 +32,6 @@ #include #include -#include using namespace solidity; using namespace solidity::util; @@ -49,7 +48,7 @@ namespace int parseUnsignedInteger(std::string::iterator& _it, std::string::iterator _end) { if (_it == _end || !util::isDigit(*_it)) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid test expectation. Source location expected.")); + solThrow(ValidationError, "Invalid test expectation. Source location expected."); int result = 0; while (_it != _end && util::isDigit(*_it)) { @@ -257,8 +256,7 @@ std::vector CommonSyntaxTest::parseExpectations(std::istream& _ std::string errorTypeStr(typeBegin, it); std::optional errorType = Error::parseErrorType(errorTypeStr); - if (!errorType.has_value()) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid error type: " + errorTypeStr)); + solRequire(errorType.has_value(), ValidationError, "Invalid error type: " + errorTypeStr); skipWhitespace(it, line.end()); diff --git a/test/FilesystemUtils.cpp b/test/FilesystemUtils.cpp index edd9a0d6d11d..c0a96474ddb8 100644 --- a/test/FilesystemUtils.cpp +++ b/test/FilesystemUtils.cpp @@ -18,8 +18,12 @@ #include +#include + #include +#include + #include using namespace solidity; @@ -37,19 +41,19 @@ void solidity::test::createFilesWithParentDirs(std::set newFile << _content; if (newFile.fail() || !boost::filesystem::exists(path)) - BOOST_THROW_EXCEPTION(std::runtime_error("Failed to create an empty file: \"" + path.string() + "\".")); + solThrow(ExecutionError, "Failed to create an empty file: \"" + path.string() + "\"."); } } void solidity::test::createFileWithContent(boost::filesystem::path const& _path, std::string const& _content) { if (boost::filesystem::is_regular_file(_path)) - BOOST_THROW_EXCEPTION(std::runtime_error("File already exists: \"" + _path.string() + "\".")); + solThrow(ExecutionError, "File already exists: \"" + _path.string() + "\"."); // Use binary mode to avoid line ending conversion on Windows. std::ofstream newFile(_path.string(), std::ofstream::binary); if (newFile.fail() || !boost::filesystem::is_regular_file(_path)) - BOOST_THROW_EXCEPTION(std::runtime_error("Failed to create a file: \"" + _path.string() + "\".")); + solThrow(ExecutionError, "Failed to create a file: \"" + _path.string() + "\"."); newFile << _content; } @@ -79,9 +83,10 @@ bool solidity::test::createSymlinkIfSupportedByFilesystem( ) return false; else - BOOST_THROW_EXCEPTION(std::runtime_error( - "Failed to create a symbolic link: \"" + _linkName.string() + "\"" - " -> " + _targetPath.string() + "\"." - " " + symlinkCreationError.message() + "." + solThrow(ExecutionError, fmt::format( + "Failed to create a symbolic link: \"{}\" -> {}\". {}.", + _linkName.string(), + _targetPath.string(), + symlinkCreationError.message() )); } diff --git a/test/TestCase.cpp b/test/TestCase.cpp index 20cbf5878889..2fe94afbef79 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -20,13 +20,14 @@ #include #include +#include #include #include #include -#include +using namespace std::string_literals; using namespace solidity; using namespace solidity::frontend; using namespace solidity::frontend::test; @@ -66,7 +67,7 @@ bool TestCase::shouldRun() void TestCase::expect(std::string::iterator& _it, std::string::iterator _end, std::string::value_type _c) { if (_it == _end || *_it != _c) - BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Invalid test expectation. Expected: \"") + _c + "\".")); + solThrow(ValidationError, "Invalid test expectation. Expected: \""s + _c + "\"."); ++_it; } @@ -120,8 +121,7 @@ void EVMVersionRestrictedTestCase::processEVMVersionSetting() version = std::make_optional(); else version = langutil::EVMVersion::fromString(versionString); - if (!version) - BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid EVM version: \"" + versionString + "\""}); + solRequire(version, ValidationError, "Invalid EVM version: \"" + versionString + "\""); langutil::EVMVersion evmVersion = CommonOptions::get().evmVersion(); bool comparisonResult; @@ -138,7 +138,7 @@ void EVMVersionRestrictedTestCase::processEVMVersionSetting() else if (comparator == "!") comparisonResult = !(evmVersion == version); else - BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid EVM comparator: \"" + comparator + "\""}); + solThrow(ValidationError, "Invalid EVM comparator: \"" + comparator + "\""); if (!comparisonResult) m_shouldRun = false; @@ -160,7 +160,7 @@ void EVMVersionRestrictedTestCase::processBytecodeFormatSetting() else if (bytecodeFormatString == ">=EOFv1" && !eofVersion.has_value()) m_shouldRun = false; else if (bytecodeFormatString != "legacy" && bytecodeFormatString != ">=EOFv1" ) - BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid bytecodeFormat flag: \"" + bytecodeFormatString + "\""}); + solThrow(ValidationError, "Invalid bytecodeFormat flag: \"" + bytecodeFormatString + "\""); } EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(std::string const& _filename): diff --git a/test/TestCaseReader.cpp b/test/TestCaseReader.cpp index da6174cbe9bd..95c3aa0147f8 100644 --- a/test/TestCaseReader.cpp +++ b/test/TestCaseReader.cpp @@ -24,13 +24,15 @@ #include using namespace solidity::frontend::test; +using namespace solidity::test; namespace fs = boost::filesystem; -TestCaseReader::TestCaseReader(std::string const& _filename): m_fileStream(_filename), m_fileName(_filename) +TestCaseReader::TestCaseReader(std::string const& _filename): + m_fileStream(_filename), + m_fileName(_filename) { - if (!m_fileStream) - BOOST_THROW_EXCEPTION(std::runtime_error("Cannot open file: \"" + _filename + "\".")); + solRequire(m_fileStream, ValidationError, "Cannot open file: \"" + _filename + "\"."); m_fileStream.exceptions(std::ios::badbit); std::tie(m_sources, m_lineNumber) = parseSourcesAndSettingsWithLineNumber(m_fileStream); @@ -47,7 +49,7 @@ TestCaseReader::TestCaseReader(std::istringstream const& _str) std::string const& TestCaseReader::source() const { if (m_sources.sources.size() != 1) - BOOST_THROW_EXCEPTION(std::runtime_error("Expected single source definition, but got multiple sources.")); + solThrow(ValidationError, "Expected single source definition, but got multiple sources."); return m_sources.sources.at(m_sources.mainSourceFile); } @@ -68,7 +70,7 @@ bool TestCaseReader::boolSetting(std::string const& _name, bool _defaultValue) if (value == "true") return true; - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid Boolean value: " + value + ".")); + solThrow(ValidationError, "Invalid Boolean value: " + value + "."); } size_t TestCaseReader::sizetSetting(std::string const& _name, size_t _defaultValue) @@ -94,10 +96,10 @@ std::string TestCaseReader::stringSetting(std::string const& _name, std::string void TestCaseReader::ensureAllSettingsRead() const { if (!m_unreadSettings.empty()) - BOOST_THROW_EXCEPTION(std::runtime_error( - "Unknown setting(s): " + - util::joinHumanReadable(m_unreadSettings | ranges::views::keys) - )); + solThrow( + ValidationError, + "Unknown setting(s): " + util::joinHumanReadable(m_unreadSettings | ranges::views::keys) + ); } std::pair TestCaseReader::parseSourcesAndSettingsWithLineNumber(std::istream& _stream) @@ -137,7 +139,7 @@ std::pair TestCaseReader::parseSourcesAndSettingsWithLineNumb line.size() - sourceDelimiterEnd.size() - sourceDelimiterStart.size() )); if (sources.count(currentSourceName)) - BOOST_THROW_EXCEPTION(std::runtime_error("Multiple definitions of test source \"" + currentSourceName + "\".")); + solThrow(ValidationError, "Multiple definitions of test source \"" + currentSourceName + "\"."); } else if (boost::algorithm::starts_with(line, externalSourceDelimiterStart) && boost::algorithm::ends_with(line, sourceDelimiterEnd)) { @@ -163,16 +165,16 @@ std::pair TestCaseReader::parseSourcesAndSettingsWithLineNumb if (!externalSourceTarget.is_relative() || !externalSourceTarget.root_path().empty()) // NOTE: UNC paths (ones starting with // or \\) are considered relative by Boost // since they have an empty root directory (but non-empty root name). - BOOST_THROW_EXCEPTION(std::runtime_error("External Source paths need to be relative to the location of the test case.")); + solThrow(ValidationError, "External Source paths need to be relative to the location of the test case."); fs::path externalSourceFullPath = testCaseParentDir / externalSourceTarget; std::string externalSourceContent; if (!fs::exists(externalSourceFullPath)) - BOOST_THROW_EXCEPTION(std::runtime_error("External Source '" + externalSourceTarget.string() + "' not found.")); + solThrow(ValidationError, "External Source '" + externalSourceTarget.string() + "' not found."); else externalSourceContent = util::readFileAsString(externalSourceFullPath); if (sources.count(externalSourceName)) - BOOST_THROW_EXCEPTION(std::runtime_error("Multiple definitions of test source \"" + externalSourceName + "\".")); + solThrow(ValidationError, "Multiple definitions of test source \"" + externalSourceName + "\"."); sources[externalSourceName] = externalSourceContent; externalSources[externalSourceName] = externalSourceTarget; } @@ -183,7 +185,7 @@ std::pair TestCaseReader::parseSourcesAndSettingsWithLineNumb { size_t colon = line.find(':'); if (colon == std::string::npos) - BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Expected \":\" inside setting."))); + solThrow(ValidationError, "Expected \":\" inside setting."); std::string key = line.substr(comment.size(), colon - comment.size()); std::string value = line.substr(colon + 1); boost::algorithm::trim(key); @@ -191,7 +193,7 @@ std::pair TestCaseReader::parseSourcesAndSettingsWithLineNumb m_settings[key] = value; } else - BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Expected \"//\" or \"// ---\" to terminate settings and source."))); + solThrow(ValidationError, "Expected \"//\" or \"// ---\" to terminate settings and source."); } // Register the last source as the main one sources[currentSourceName] = currentSource; @@ -208,6 +210,6 @@ std::string TestCaseReader::parseSimpleExpectations(std::istream& _file) else if (line == "//") result += "\n"; else - BOOST_THROW_EXCEPTION(std::runtime_error("Test expectations must start with \"// \".")); + solThrow(ValidationError, "Test expectations must start with \"// \"."); return result; } diff --git a/test/TestCaseReader.h b/test/TestCaseReader.h index 5830e97f3666..36bc54a4e041 100644 --- a/test/TestCaseReader.h +++ b/test/TestCaseReader.h @@ -18,15 +18,19 @@ #pragma once +#include + #include #include -#include - #include #include +#include + +#include + #include #include #include @@ -91,10 +95,11 @@ E TestCaseReader::enumSetting(std::string const& _name, std::map std::string value = stringSetting(_name, _defaultChoice); - if (_choices.count(value) == 0) - BOOST_THROW_EXCEPTION(std::runtime_error( - "Invalid Enum value: " + value + ". Available choices: " + util::joinHumanReadable(_choices | ranges::views::keys) + "." - )); + solRequire(_choices.count(value) != 0, solidity::test::ValidationError, fmt::format( + "Invalid Enum value: {}. Available choices: {}.", + value, + util::joinHumanReadable(_choices | ranges::views::keys) + )); return _choices.at(value); } diff --git a/test/libevmasm/EVMAssemblyTest.cpp b/test/libevmasm/EVMAssemblyTest.cpp index 6d86592cdfce..7e4db332f7ac 100644 --- a/test/libevmasm/EVMAssemblyTest.cpp +++ b/test/libevmasm/EVMAssemblyTest.cpp @@ -64,7 +64,7 @@ EVMAssemblyTest::EVMAssemblyTest(std::string const& _filename): else if (boost::algorithm::ends_with(_filename, ".asm")) m_assemblyFormat = AssemblyFormat::Plain; else - BOOST_THROW_EXCEPTION(std::runtime_error("Not an assembly test: \"" + _filename + "\". Allowed extensions: .asm, .asmjson.")); + solThrow(ValidationError, "Not an assembly test: \"" + _filename + "\". Allowed extensions: .asm, .asmjson."); m_selectedOutputs = m_reader.stringSetting("outputs", "Assembly,Bytecode,Opcodes,SourceMappings"); OptimisationPreset optimizationPreset = m_reader.enumSetting( diff --git a/test/libevmasm/PlainAssemblyParser.cpp b/test/libevmasm/PlainAssemblyParser.cpp index 5c12a3b02120..9eea133c5b50 100644 --- a/test/libevmasm/PlainAssemblyParser.cpp +++ b/test/libevmasm/PlainAssemblyParser.cpp @@ -68,7 +68,7 @@ Json PlainAssemblyParser::parse(std::string _sourceName, std::string const& _sou expectNoMoreArguments(); if (!immediateArgument.starts_with("0x")) - BOOST_THROW_EXCEPTION(std::runtime_error(formatError("The immediate argument to PUSH must be a hex number prefixed with '0x'."))); + solThrow(ValidationError, formatError("The immediate argument to PUSH must be a hex number prefixed with '0x'.")); immediateArgument.remove_prefix("0x"s.size()); codeJSON.push_back({{"name", "PUSH"}, {"value", immediateArgument}}); @@ -83,7 +83,7 @@ Json PlainAssemblyParser::parse(std::string _sourceName, std::string const& _sou codeJSON.push_back({{"name", "JUMPDEST"}}); } else - BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Unknown instruction."))); + solThrow(ValidationError, formatError("Unknown instruction.")); } return {{".code", codeJSON}}; } @@ -113,7 +113,7 @@ std::string_view PlainAssemblyParser::expectArgument() { bool hasArgument = advanceToken(); if (!hasArgument) - BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Missing argument(s)."))); + solThrow(ValidationError, formatError("Missing argument(s).")); return currentToken().value; } @@ -122,7 +122,7 @@ void PlainAssemblyParser::expectNoMoreArguments() { bool hasArgument = advanceToken(); if (hasArgument) - BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Too many arguments."))); + solThrow(ValidationError, formatError("Too many arguments.")); } void PlainAssemblyParser::advanceLine(std::string_view _line) diff --git a/test/libevmasm/PlainAssemblyParser.h b/test/libevmasm/PlainAssemblyParser.h index 39957f611347..abaf1d1dc78a 100644 --- a/test/libevmasm/PlainAssemblyParser.h +++ b/test/libevmasm/PlainAssemblyParser.h @@ -46,7 +46,7 @@ class PlainAssemblyParser { public: /// Parses plain assembly format and returns the equivalent assembly JSON. - /// Errors are reported by throwing runtime_error. + /// Errors are reported by throwing ValidationError. Json parse(std::string _sourceName, std::string const& _source); protected: diff --git a/test/libsolidity/ABIJsonTest.cpp b/test/libsolidity/ABIJsonTest.cpp index 5f5e535ef4a5..3cc455288cc5 100644 --- a/test/libsolidity/ABIJsonTest.cpp +++ b/test/libsolidity/ABIJsonTest.cpp @@ -52,8 +52,7 @@ TestCase::TestResult ABIJsonTest::run(std::ostream& _stream, std::string const& }}); compiler.setEVMVersion(CommonOptions::get().evmVersion()); compiler.setOptimiserSettings(CommonOptions::get().optimize); - if (!compiler.parseAndAnalyze()) - BOOST_THROW_EXCEPTION(std::runtime_error("Parsing contract failed")); + solRequire(compiler.parseAndAnalyze(), ExecutionError, "Parsing contract failed"); m_obtainedResult.clear(); bool first = true; diff --git a/test/libsolidity/ASTJSONTest.cpp b/test/libsolidity/ASTJSONTest.cpp index e49866241dc1..c38e1d66260f 100644 --- a/test/libsolidity/ASTJSONTest.cpp +++ b/test/libsolidity/ASTJSONTest.cpp @@ -67,11 +67,10 @@ std::string compilerStateToString(CompilerStack::State _state) CompilerStack::State stringToCompilerState(const std::string& _state) { for (unsigned int i = CompilerStack::State::Empty; i <= CompilerStack::State::CompilationSuccessful; ++i) - { if (_state == compilerStateToString(CompilerStack::State(i))) return CompilerStack::State(i); - } - BOOST_THROW_EXCEPTION(std::runtime_error("Unsupported compiler state (" + _state + ") in test contract file")); + + solThrow(ValidationError, "Unsupported compiler state (" + _state + ") in test contract file"); } void replaceVersionWithTag(std::string& _input) @@ -119,8 +118,7 @@ void ASTJSONTest::generateTestVariants(std::string const& _filename) void ASTJSONTest::fillSources(std::string const& _filename) { std::ifstream file(_filename); - if (!file) - BOOST_THROW_EXCEPTION(std::runtime_error("Cannot open test contract: \"" + _filename + "\".")); + solRequire(file, ValidationError, "Cannot open test contract: \"" + _filename + "\"."); file.exceptions(std::ios::badbit); std::string sourceName; @@ -146,7 +144,7 @@ void ASTJSONTest::fillSources(std::string const& _filename) std::string state = line.substr(failMarker.size()); boost::algorithm::trim(state); if (m_expectedFailAfter.has_value()) - BOOST_THROW_EXCEPTION(std::runtime_error("Duplicated \"failAfter\" directive")); + solThrow(ValidationError, "Duplicated \"failAfter\" directive"); m_expectedFailAfter = stringToCompilerState(state); } @@ -159,8 +157,7 @@ void ASTJSONTest::fillSources(std::string const& _filename) void ASTJSONTest::validateTestConfiguration() const { - if (m_variants.empty()) - BOOST_THROW_EXCEPTION(std::runtime_error("No file with expected result found.")); + solRequire(!m_variants.empty(), ValidationError, "No file with expected result found."); if (m_expectedFailAfter.has_value()) { @@ -170,13 +167,11 @@ void ASTJSONTest::validateTestConfiguration() const ); if (unexpectedTestVariant != m_variants.end()) - BOOST_THROW_EXCEPTION( - std::runtime_error( - std::string("Unexpected JSON file: ") + unexpectedTestVariant->astFilename() + - " in \"failAfter: " + - compilerStateToString(m_expectedFailAfter.value()) + "\" scenario." - ) - ); + solThrow(ValidationError, fmt::format( + "Unexpected JSON file: {} in \"failAfter: {}\" scenario.", + unexpectedTestVariant->astFilename(), + compilerStateToString(m_expectedFailAfter.value()) + )); } } @@ -184,7 +179,7 @@ ASTJSONTest::ASTJSONTest(std::string const& _filename): EVMVersionRestrictedTestCase(_filename) { if (!boost::algorithm::ends_with(_filename, ".sol")) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid test contract file name: \"" + _filename + "\".")); + solThrow(ValidationError, "Invalid test contract file name: \"" + _filename + "\"."); generateTestVariants(_filename); fillSources(_filename); @@ -309,7 +304,7 @@ void ASTJSONTest::printUpdatedExpectations(std::ostream&, std::string const&) co void ASTJSONTest::updateExpectation(std::string const& _filename, std::string const& _expectation, std::string const& _variant) const { std::ofstream file(_filename.c_str()); - if (!file) BOOST_THROW_EXCEPTION(std::runtime_error("Cannot write " + _variant + "AST expectation to \"" + _filename + "\".")); + solRequire(file, ExecutionError, "Cannot write " + _variant + "AST expectation to \"" + _filename + "\"."); file.exceptions(std::ios::badbit); std::string replacedResult = _expectation; diff --git a/test/libsolidity/ASTPropertyTest.cpp b/test/libsolidity/ASTPropertyTest.cpp index 94a4f292bc7f..9f198d3b6fa0 100644 --- a/test/libsolidity/ASTPropertyTest.cpp +++ b/test/libsolidity/ASTPropertyTest.cpp @@ -47,7 +47,7 @@ ASTPropertyTest::ASTPropertyTest(std::string const& _filename): EVMVersionRestrictedTestCase(_filename) { if (!boost::algorithm::ends_with(_filename, ".sol")) - BOOST_THROW_EXCEPTION(std::runtime_error("Not a Solidity file: \"" + _filename + "\".")); + solThrow(ValidationError, "Not a Solidity file: \"" + _filename + "\"."); m_source = m_reader.source(); readExpectations(); @@ -195,10 +195,10 @@ TestCase::TestResult ASTPropertyTest::run(std::ostream& _stream, std::string con compiler.setEVMVersion(CommonOptions::get().evmVersion()); compiler.setOptimiserSettings(CommonOptions::get().optimize); if (!compiler.parseAndAnalyze()) - BOOST_THROW_EXCEPTION(std::runtime_error( - "Parsing contract failed" + - SourceReferenceFormatter::formatErrorInformation(compiler.errors(), compiler, _formatted) - )); + solThrow( + ExecutionError, + "Parsing contract failed" + SourceReferenceFormatter::formatErrorInformation(compiler.errors(), compiler, _formatted) + ); Json astJson = ASTJsonExporter(compiler.state()).toJson(compiler.ast("A")); soltestAssert(!astJson.empty()); diff --git a/test/libsolidity/GasTest.cpp b/test/libsolidity/GasTest.cpp index 603878657880..5bccd71a06cf 100644 --- a/test/libsolidity/GasTest.cpp +++ b/test/libsolidity/GasTest.cpp @@ -30,11 +30,11 @@ #include #include -#include using namespace solidity::langutil; using namespace solidity::frontend; using namespace solidity::frontend::test; +using namespace solidity::test; using namespace solidity; using namespace boost::unit_test; @@ -55,7 +55,7 @@ void GasTest::parseExpectations(std::istream& _stream) while (getline(_stream, line)) if (!boost::starts_with(line, "// ")) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid expectation: expected \"// \".")); + solThrow(ValidationError, "Invalid expectation: expected \"// \"."); else if (boost::ends_with(line, ":")) { std::string kind = line.substr(3, line.length() - 4); @@ -63,7 +63,7 @@ void GasTest::parseExpectations(std::istream& _stream) currentKind = &m_expectations[std::move(kind)]; } else if (!currentKind) - BOOST_THROW_EXCEPTION(std::runtime_error("No function kind specified. Expected \"creation:\", \"external:\" or \"internal:\".")); + solThrow(ValidationError, "No function kind specified. Expected \"creation:\", \"external:\" or \"internal:\"."); else { auto it = line.begin() + 3; @@ -76,8 +76,7 @@ void GasTest::parseExpectations(std::istream& _stream) functionName.clear(); expect(it, line.end(), ':'); skipWhitespace(it, line.end()); - if (it == line.end()) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid expectation: expected gas cost.")); + solRequire(it != line.end(), ValidationError, "Invalid expectation: expected gas cost."); (*currentKind)[functionName] = std::string(it, line.end()); } } diff --git a/test/libsolidity/NatspecJSONTest.cpp b/test/libsolidity/NatspecJSONTest.cpp index d1a49a2e95a3..17f7789df626 100644 --- a/test/libsolidity/NatspecJSONTest.cpp +++ b/test/libsolidity/NatspecJSONTest.cpp @@ -28,6 +28,7 @@ #include using namespace solidity::frontend::test; +using namespace solidity::test; using namespace solidity::util; using namespace std::string_literals; @@ -68,7 +69,7 @@ void NatspecJSONTest::parseCustomExpectations(std::istream& _stream) Json parsedJSON; bool jsonParsingSuccessful = jsonParseStrict(rawJSON, parsedJSON, &jsonErrors); if (!jsonParsingSuccessful) - BOOST_THROW_EXCEPTION(std::runtime_error(fmt::format( + solThrow(ValidationError, fmt::format( "Malformed JSON in {} expectation for contract {}.\n" "Note that JSON expectations must be pretty-printed to be split correctly. " "The object is assumed to and at the first unindented closing brace.\n" @@ -76,7 +77,7 @@ void NatspecJSONTest::parseCustomExpectations(std::istream& _stream) toString(kind), contractName, rawJSON - ))); + )); m_expectedNatspecJSON[std::string(contractName)][kind] = parsedJSON; } @@ -126,9 +127,7 @@ std::tuple NatspecJSONTest::parseExpectationH return {_line.substr(0, _line.size() - kindSuffix.size()), kind}; } - BOOST_THROW_EXCEPTION(std::runtime_error( - "Natspec kind (devdoc/userdoc) not present in the expectation: "s.append(_line) - )); + solThrow(ValidationError, "Natspec kind (devdoc/userdoc) not present in the expectation: "s.append(_line)); } std::string NatspecJSONTest::extractExpectationJSON(std::istream& _stream) @@ -152,9 +151,7 @@ std::string_view NatspecJSONTest::expectLinePrefix(std::string_view _line) { size_t startPosition = 0; if (!boost::algorithm::starts_with(_line, "//")) - BOOST_THROW_EXCEPTION(std::runtime_error( - "Expectation line is not a comment: "s.append(_line) - )); + solThrow(ValidationError, "Expectation line is not a comment: "s.append(_line)); startPosition += 2; if (startPosition < _line.size() && _line[startPosition] == ' ') diff --git a/test/libsolidity/SMTCheckerTest.cpp b/test/libsolidity/SMTCheckerTest.cpp index ce099091f69c..39a1225f43fb 100644 --- a/test/libsolidity/SMTCheckerTest.cpp +++ b/test/libsolidity/SMTCheckerTest.cpp @@ -42,13 +42,13 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename): else if (isValidContractName(contract)) m_modelCheckerSettings.contracts.contracts[""] = {contract}; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid contract specified in SMTContract setting.")); + solThrow(ValidationError, "Invalid contract specified in SMTContract setting."); auto extCallsMode = ModelCheckerExtCalls::fromString(m_reader.stringSetting("SMTExtCalls", "untrusted")); if (extCallsMode) m_modelCheckerSettings.externalCalls = *extCallsMode; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT external calls mode.")); + solThrow(ValidationError, "Invalid SMT external calls mode."); auto const& showProvedSafe = m_reader.stringSetting("SMTShowProvedSafe", "no"); if (showProvedSafe == "no") @@ -56,7 +56,7 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename): else if (showProvedSafe == "yes") m_modelCheckerSettings.showProvedSafe = true; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT \"show proved safe\" choice.")); + solThrow(ValidationError, "Invalid SMT \"show proved safe\" choice."); auto const& showUnproved = m_reader.stringSetting("SMTShowUnproved", "yes"); if (showUnproved == "no") @@ -64,7 +64,7 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename): else if (showUnproved == "yes") m_modelCheckerSettings.showUnproved = true; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT \"show unproved\" choice.")); + solThrow(ValidationError, "Invalid SMT \"show unproved\" choice."); auto const& showUnsupported = m_reader.stringSetting("SMTShowUnsupported", "yes"); if (showUnsupported == "no") @@ -72,14 +72,14 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename): else if (showUnsupported == "yes") m_modelCheckerSettings.showUnsupported = true; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT \"show unsupported\" choice.")); + solThrow(ValidationError, "Invalid SMT \"show unsupported\" choice."); m_modelCheckerSettings.solvers = smtutil::SMTSolverChoice::None(); auto const& choice = m_reader.stringSetting("SMTSolvers", "z3"); if (choice == "none") m_modelCheckerSettings.solvers = smtutil::SMTSolverChoice::None(); else if (!m_modelCheckerSettings.solvers.setSolver(choice)) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT solver choice.")); + solThrow(ValidationError, "Invalid SMT solver choice."); m_modelCheckerSettings.solvers &= ModelChecker::availableSolvers(); @@ -90,13 +90,13 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename): if (targets) m_modelCheckerSettings.targets = *targets; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT targets.")); + solThrow(ValidationError, "Invalid SMT targets."); auto engine = ModelCheckerEngine::fromString(m_reader.stringSetting("SMTEngine", "all")); if (engine) m_modelCheckerSettings.engine = *engine; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT engine choice.")); + solThrow(ValidationError, "Invalid SMT engine choice."); if (m_modelCheckerSettings.solvers.none() || m_modelCheckerSettings.engine.none()) m_shouldRun = false; @@ -107,7 +107,7 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename): else if (ignoreCex == "yes") m_ignoreCex = true; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT counterexample choice.")); + solThrow(ValidationError, "Invalid SMT counterexample choice."); static auto removeInv = [](std::vector&& errors) { std::vector filtered; @@ -123,7 +123,7 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename): else if (ignoreInv == "yes") m_modelCheckerSettings.invariants = ModelCheckerInvariants::None(); else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid SMT invariant choice.")); + solThrow(ValidationError, "Invalid SMT invariant choice."); if (m_modelCheckerSettings.invariants.invariants.empty()) m_expectations = removeInv(std::move(m_expectations)); diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index faad1bad39a8..3150798b85e4 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -98,12 +98,13 @@ SemanticTest::SemanticTest( m_shouldRun = false; if (m_runWithABIEncoderV1Only && compileViaYul != "false") - BOOST_THROW_EXCEPTION(std::runtime_error( + solThrow( + ValidationError, "ABIEncoderV1Only tests cannot be run via yul, " - "so they need to also specify ``compileViaYul: false``" - )); + "so they need to also specify `compileViaYul: false`" + ); if (!util::contains(compileViaYulAllowedValues, compileViaYul)) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid compileViaYul value: " + compileViaYul + ".")); + solThrow(ValidationError, "Invalid compileViaYul value: " + compileViaYul + "."); m_testCaseWantsYulRun = util::contains(yulRunTriggers, compileViaYul); m_testCaseWantsLegacyRun = util::contains(legacyRunTriggers, compileViaYul); diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index a57407de4563..2ed2be01a880 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -27,7 +27,6 @@ #include #include -#include using namespace solidity; using namespace solidity::util; @@ -52,7 +51,7 @@ SyntaxTest::SyntaxTest( m_compileViaYul = m_reader.stringSetting("compileViaYul", eofEnabled ? "true" : "false"); if (!util::contains(compileViaYulAllowedValues, m_compileViaYul)) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid compileViaYul value: " + m_compileViaYul + ".")); + solThrow(ValidationError, "Invalid compileViaYul value: " + m_compileViaYul + "."); if (m_compileViaYul == "false" && eofEnabled) m_shouldRun = false; @@ -66,7 +65,7 @@ SyntaxTest::SyntaxTest( }; std::string stopAfter = m_reader.stringSetting("stopAfter", "compilation"); if (!pipelineStages.count(stopAfter)) - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid stopAfter value: " + stopAfter + ".")); + solThrow(ValidationError, "Invalid stopAfter value: " + stopAfter + "."); m_stopAfter = pipelineStages.at(stopAfter); } @@ -105,10 +104,11 @@ void SyntaxTest::parseAndAnalyze() auto error = ranges::find_if(errors, isInternalError); error != ranges::end(errors) ) - BOOST_THROW_EXCEPTION(std::runtime_error( + solThrow( + ExecutionError, "Unexpected " + Error::formatErrorType((*error)->type()) + " at compilation stage." " This error should NOT be encoded as expectation and should be fixed instead." - )); + ); } filterObtainedErrors(); diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index cf572f8c6676..6840a06c487d 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -117,13 +117,11 @@ std::map _eofVersion) { if (!validDialects.count(_name)) - BOOST_THROW_EXCEPTION(std::runtime_error{ - "Invalid Dialect \"" + - _name + - "\". Valid dialects are " + - util::joinHumanReadable(validDialectNames(), ", ", " and ") + - "." - }); + solThrow(ValidationError, fmt::format( + "Invalid Dialect \"{}\". Valid dialects are {}.", + _name, + util::joinHumanReadable(validDialectNames(), ", ", " and ") + )); return validDialects.at(_name)(_evmVersion, _eofVersion); } diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index 2ce9a0789ef0..c5714192ed6e 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -63,7 +63,7 @@ ObjectCompilerTest::ObjectCompilerTest(std::string const& _filename): boost::split(m_outputSetting, m_reader.stringSetting("outputs", "Assembly,Bytecode,Opcodes,SourceMappings"), boost::is_any_of(",")); for (auto const& output: m_outputSetting) if (std::find(allowedOutputs.begin(), allowedOutputs.end(), output) == allowedOutputs.end()) - BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid output type: \"" + output + "\""}); + solThrow(ValidationError, "Invalid output type: \"" + output + "\""); m_expectation = m_reader.simpleExpectations(); } diff --git a/test/libyul/StackShufflingTest.cpp b/test/libyul/StackShufflingTest.cpp index 58ea124fb1df..af76d425ca13 100644 --- a/test/libyul/StackShufflingTest.cpp +++ b/test/libyul/StackShufflingTest.cpp @@ -146,8 +146,7 @@ void StackShufflingTest::processSettings() { std::string depthString = m_reader.stringSetting("maximumStackDepth", "16"); std::optional depth = toUnsignedInt(depthString); - if (!depth.has_value()) - BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid maximum stack depth: \"" + depthString + "\""}); + solRequire(depth.has_value(), ValidationError, "Invalid maximum stack depth: \"" + depthString + "\""); m_maximumStackDepth = *depth; } @@ -182,7 +181,7 @@ TestCase::TestResult StackShufflingTest::run(std::ostream& _stream, std::string if (auto depth = util::findOffset(m_sourceStack | ranges::views::reverse, _slot)) output << "DUP" << *depth + 1 << std::endl; else - BOOST_THROW_EXCEPTION(std::runtime_error("Invalid DUP operation.")); + solThrow(ExecutionError, "Invalid DUP operation."); } }, [&](){ // pop diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 0ce8617c29d7..612d0dfdc5c5 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -44,6 +44,7 @@ using namespace solidity::yul; using namespace solidity::yul::test; using namespace solidity::frontend; using namespace solidity::frontend::test; +using namespace solidity::test; YulOptimizerTest::YulOptimizerTest(std::string const& _filename): EVMVersionRestrictedTestCase(_filename) @@ -51,7 +52,7 @@ YulOptimizerTest::YulOptimizerTest(std::string const& _filename): boost::filesystem::path path(_filename); if (path.empty() || std::next(path.begin()) == path.end() || std::next(std::next(path.begin())) == path.end()) - BOOST_THROW_EXCEPTION(std::runtime_error("Filename path has to contain a directory: \"" + _filename + "\".")); + solThrow(ValidationError, "Filename path has to contain a directory: \"" + _filename + "\"."); m_optimizerStep = std::prev(std::prev(path.end()))->string(); m_source = m_reader.source(); diff --git a/test/soltest.cpp b/test/soltest.cpp index db647ff9256e..ff348c0b3515 100644 --- a/test/soltest.cpp +++ b/test/soltest.cpp @@ -278,12 +278,7 @@ test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[]) removeTestSuite(suite); } } - catch (solidity::test::ConfigException const& exception) - { - std::cerr << exception.what() << std::endl; - exit(EXIT_FAILURE); - } - catch (std::runtime_error const& exception) + catch (SoltestError const& exception) { std::cerr << exception.what() << std::endl; exit(EXIT_FAILURE); diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 2d8f06e5bc29..992731bfb6fa 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -196,6 +196,11 @@ TestTool::Result TestTool::process() else return Result::Skipped; } + catch (SoltestError const& exception) + { + AnsiColorized(std::cout, formatted, {BOLD, RED}) << exception.what() << std::endl; + return Result::Exception; + } catch (...) { AnsiColorized(std::cout, formatted, {BOLD, RED}) << @@ -493,12 +498,7 @@ int main(int argc, char const *argv[]) std::cerr << exception.what() << std::endl; return 2; } - catch (std::runtime_error const& exception) - { - std::cerr << exception.what() << std::endl; - return 2; - } - catch (ConfigException const& exception) + catch (SoltestError const& exception) { std::cerr << exception.what() << std::endl; return 2; From 483da00eef7e5952c0186281bccd64ada92d81a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Apr 2025 21:33:31 +0200 Subject: [PATCH 11/14] Tweak error messages of some SoltestErrors --- test/FilesystemUtils.cpp | 2 +- test/TestCaseReader.h | 3 ++- test/libyul/Common.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/FilesystemUtils.cpp b/test/FilesystemUtils.cpp index c0a96474ddb8..f87a73e43e08 100644 --- a/test/FilesystemUtils.cpp +++ b/test/FilesystemUtils.cpp @@ -84,7 +84,7 @@ bool solidity::test::createSymlinkIfSupportedByFilesystem( return false; else solThrow(ExecutionError, fmt::format( - "Failed to create a symbolic link: \"{}\" -> {}\". {}.", + "Failed to create a symbolic link: \"{}\" -> \"{}\". {}.", _linkName.string(), _targetPath.string(), symlinkCreationError.message() diff --git a/test/TestCaseReader.h b/test/TestCaseReader.h index 36bc54a4e041..5397020c80cb 100644 --- a/test/TestCaseReader.h +++ b/test/TestCaseReader.h @@ -96,7 +96,8 @@ E TestCaseReader::enumSetting(std::string const& _name, std::map std::string value = stringSetting(_name, _defaultChoice); solRequire(_choices.count(value) != 0, solidity::test::ValidationError, fmt::format( - "Invalid Enum value: {}. Available choices: {}.", + "Invalid choice in '{}' setting: {}.\nAvailable choices: {}.", + _name, value, util::joinHumanReadable(_choices | ranges::views::keys) )); diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index 6840a06c487d..6edebc3aa620 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -118,7 +118,7 @@ yul::Dialect const& yul::test::dialect(std::string const& _name, langutil::EVMVe { if (!validDialects.count(_name)) solThrow(ValidationError, fmt::format( - "Invalid Dialect \"{}\". Valid dialects are {}.", + "Invalid Dialect \"{}\". Valid dialects: {}.", _name, util::joinHumanReadable(validDialectNames(), ", ", " and ") )); From 3ee48b6ee2fded1fc612ea52da70152c547f86de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Apr 2025 02:13:41 +0200 Subject: [PATCH 12/14] isoltest: Print exceptions caught during testing to stderr --- test/tools/isoltest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index 992731bfb6fa..833e45150aac 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -198,12 +198,12 @@ TestTool::Result TestTool::process() } catch (SoltestError const& exception) { - AnsiColorized(std::cout, formatted, {BOLD, RED}) << exception.what() << std::endl; + AnsiColorized(std::cerr, formatted, {BOLD, RED}) << exception.what() << std::endl; return Result::Exception; } catch (...) { - AnsiColorized(std::cout, formatted, {BOLD, RED}) << + AnsiColorized(std::cerr, formatted, {BOLD, RED}) << "Unhandled exception during test: " << boost::current_exception_diagnostic_information() << std::endl; return Result::Exception; } From 37864dfe0a7ddc0091d4e056a95121c88a8a6745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Apr 2025 20:34:15 +0200 Subject: [PATCH 13/14] TestCase: Print an extra note when expectations differ only in the amount of whitespace --- test/TestCase.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/TestCase.cpp b/test/TestCase.cpp index 2fe94afbef79..6ff4a16725c2 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -24,6 +24,7 @@ #include #include +#include #include @@ -93,6 +94,13 @@ TestCase::TestResult TestCase::checkResult(std::ostream& _stream, const std::str util::AnsiColorized(_stream, _formatted, {util::formatting::BOLD, util::formatting::CYAN}) << _linePrefix << "Obtained result:" << std::endl; printPrefixed(_stream, m_obtainedResult, nextIndentLevel); + + if (boost::trim_all_copy(m_expectation) == boost::trim_all_copy(m_obtainedResult)) + // NOTE: This will catch `a b` vs `a b` and ` ab ` vs `ab` but not `ab` vs `a b`. + util::AnsiColorized(_stream, _formatted, {util::formatting::BOLD, util::formatting::YELLOW}) + << "\n" + << _linePrefix << "NOTE: Results differ only in the amount of whitespace." << std::endl; + return TestResult::Failure; } return TestResult::Success; From caeaa48e5505171e5f83984387442f3d9ef51682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Apr 2025 20:44:00 +0200 Subject: [PATCH 14/14] TestCase: Treat expectations as a match even if the test case does not add a trailing newline --- test/TestCase.cpp | 4 +++- test/libevmasm/EVMAssemblyTest.cpp | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/TestCase.cpp b/test/TestCase.cpp index 6ff4a16725c2..13a40f5dd78e 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -84,7 +84,9 @@ void TestCase::printUpdatedExpectations(std::ostream& _stream, std::string const TestCase::TestResult TestCase::checkResult(std::ostream& _stream, const std::string& _linePrefix, bool const _formatted) { - if (m_expectation != m_obtainedResult) + // NOTE: Test cases usually use parseSimpleExpectations(), which ensures that m_expectations ends + // with a newline, so count m_obtainedResult as a match even if it does not. + if (m_expectation != m_obtainedResult && m_expectation != m_obtainedResult + '\n') { std::string nextIndentLevel = _linePrefix + " "; util::AnsiColorized(_stream, _formatted, {util::formatting::BOLD, util::formatting::CYAN}) diff --git a/test/libevmasm/EVMAssemblyTest.cpp b/test/libevmasm/EVMAssemblyTest.cpp index 7e4db332f7ac..efb737cc47eb 100644 --- a/test/libevmasm/EVMAssemblyTest.cpp +++ b/test/libevmasm/EVMAssemblyTest.cpp @@ -174,8 +174,6 @@ TestCase::TestResult EVMAssemblyTest::run(std::ostream& _stream, std::string con std::string separator = (content.empty() ? "" : (output == "Assembly" ? "\n" : " ")); m_obtainedResult += output + ":" + separator + content; } - if (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n') - m_obtainedResult += "\n"; return checkResult(_stream, _linePrefix, _formatted); }