From 485324fba537e025030f5341fe11655dd9a141a5 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Thu, 30 Jan 2025 14:47:55 +0100 Subject: [PATCH 1/5] Test: Add common types for compilers under test --- test/CMakeLists.txt | 2 + test/libsolidity/util/compiler/Compiler.cpp | 70 ++++++ test/libsolidity/util/compiler/Compiler.h | 238 ++++++++++++++++++++ test/tools/CMakeLists.txt | 2 + 4 files changed, 312 insertions(+) create mode 100644 test/libsolidity/util/compiler/Compiler.cpp create mode 100644 test/libsolidity/util/compiler/Compiler.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index da115f0d4a5b..9c8b7bdb7e6e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -117,6 +117,8 @@ set(libsolidity_sources detect_stray_source_files("${libsolidity_sources}" "libsolidity/") set(libsolidity_util_sources + libsolidity/util/compiler/Compiler.h + libsolidity/util/compiler/Compiler.cpp libsolidity/util/BytesUtils.cpp libsolidity/util/BytesUtilsTests.cpp libsolidity/util/BytesUtils.h diff --git a/test/libsolidity/util/compiler/Compiler.cpp b/test/libsolidity/util/compiler/Compiler.cpp new file mode 100644 index 000000000000..8833e11afe8a --- /dev/null +++ b/test/libsolidity/util/compiler/Compiler.cpp @@ -0,0 +1,70 @@ +#include + +using namespace solidity; +using namespace solidity::frontend::test; + +std::optional CompilerOutput::contract( + ContractName const& _name +) const +{ + std::string sourceName = _name.source(); + std::vector contracts; + + if (auto source = m_sourceUnits.find(sourceName); source != m_sourceUnits.end()) + contracts = source->second; + + if (contracts.empty()) + return std::nullopt; + + // Return specific contract if name was set, or last in the list. + if (!_name.contract().empty()) + { + std::string contractName = _name.contract(); + for (auto const& contract: contracts) + if ( + contractName == contract.name || + sourceName + ":" + contractName == contract.name + ) + return std::make_optional(contract); + return std::nullopt; + } + else + { + auto contract = contracts.back(); + return std::make_optional(contract); + } +} + +std::optional CompilerOutput::matchEvent( + util::h256 const& _hash +) const +{ + for (auto const& [name, contracts]: m_sourceUnits) + for (auto const& contract: contracts) + for (auto const& event: contract.eventSignatures) + if (!event.isAnonymous && keccak256(event.signature) == _hash) + return std::make_optional(event); + + return std::nullopt; +} + +bool CompilerOutput::success() const +{ + return m_success; +} + +std::optional CompilerOutput::findError( + langutil::Error::Type _type +) const +{ + for (auto const& error: m_errors) + if (error->type() == _type) + return std::make_optional(*error); + + return std::nullopt; +} + +std::string CompilerOutput::errorInformation() const +{ + return m_errorInformation; +} diff --git a/test/libsolidity/util/compiler/Compiler.h b/test/libsolidity/util/compiler/Compiler.h new file mode 100644 index 000000000000..b64d44c2c34a --- /dev/null +++ b/test/libsolidity/util/compiler/Compiler.h @@ -0,0 +1,238 @@ +/* + 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 + +using namespace solidity; +using namespace solidity::util; + +namespace solidity::frontend::test +{ + +struct AnnotatedEventSignature +{ + std::string signature; + std::vector indexedTypes; + std::vector nonIndexedTypes; + bool isAnonymous; +}; + +enum class MetadataFormat { + WithReleaseVersionTag, + WithPrereleaseVersionTag, + NoMetadata +}; + +enum class MetadataHash { + IPFS, + Bzzr1, + None +}; + +/** + * Helper for contract lookups. A `ContractName` can be initialized from + * either an unqualified contract name like "C" or a qualified name + * like ":C" or "lib.sol:C". + */ +class ContractName +{ +public: + /// Construct an empty, unqualified contract name + ContractName(): m_isUnqualified(true) {} + + /// Construct a new contract name by parsing the name given. + /// @param _name the contract name, e.g. "C", ":C" or "lib.sol:C" + ContractName(std::string const& _name) + { + auto pos = _name.find(":"); + if (pos != std::string::npos) + { + m_isUnqualified = false; + m_source = _name.substr(0, pos); + m_contract = _name.substr(pos + 1); + } + else + { + m_isUnqualified = true; + m_contract = _name; + } + } + + /// Support implicit conversion from string literals. + ContractName(const char* _name): ContractName(std::string{_name}) {} + + /// Construct a fully-qualified contract name. + /// @param _source the source name + /// @param _contract the contract name + ContractName(std::string const& _source, std::string const& _contract): + m_source(_source), + m_contract(_contract), + m_isUnqualified(false) + {} + + /// @returns the source name. + std::string const& source() const + { + return m_source; + } + + /// @returns the contractname. + std::string const& contract() const + { + return m_contract; + } + + /// @returns false if this was initialized from an unqualified contract + /// name like "C". + bool isUnqualified() const + { + return m_isUnqualified; + } + +private: + /// The source name. Can be empty. + std::string m_source; + /// The contract name. Can be empty. + std::string m_contract; + /// True, if this is an unqualified contract name. + bool m_isUnqualified; +}; + +struct CompilerInput +{ + CompilerInput() = default; + + /// Source code to be compiled + std::map sourceCode; + /// Information on which library is deployed where + std::map libraryAddresses; + /// Contract name without a colon prefix + std::optional contractName; + /// EVM target version + std::optional evmVersion; + /// EOF version + std::optional eofVersion; + /// If optimisation should be enabled. + std::optional optimise; + /// Optimiser setting to be used during compilation + std::optional optimiserSettings; + /// Revert string behaviour + std::optional revertStrings; + /// Format of the metadata appended at the end of the bytecode + std::optional metadataFormat; + /// Hash should be used to store the metadata in the bytecode + std::optional metadataHash; + /// Flag used for debugging + std::optional debugFailure; + /// Flag to enable new code generator + std::optional viaIR; +}; + +/** + * A compiled contract. + */ +struct CompiledContract +{ + /// The name of the contract + std::string name; + /// Bytecode of the assembled object for a contract. + bytes object; + /// Bytecode of the assembled runtime object for a contract. + bytes runtimeObject; + /// If object has unlinked references. + bool hasUnlinkedReferences; + /// CBOR-encoded metadata matching the pipeline selected using the viaIR + /// setting. + bytes cborMetadata; + /// Normal contract assembly items. + std::optional assemblyItems; + /// Normal contract runtime assembly items. + std::optional runtimeAssemblyItems; + /// Contract Metadata matching the pipeline selected using the viaIR setting. + std::optional metadata; + /// The contract ABI as a JSON object. + std::optional contractABI; + /// JSON object with the three members ``methods``, ``events``, ``errors``. + /// Each is a map, mapping identifiers (hashes) to function names. + std::optional interfaceSymbols; + /// Event signatures + std::vector eventSignatures; +}; + +using SourceUnits = std::map>; + +/** + * Output generated by the compiler. Provides convenient access method to the + * internal data. + */ +class CompilerOutput +{ +public: + CompilerOutput( + SourceUnits _sourceUnits, + bool _success, + langutil::ErrorList _errors, + std::string _errorInformation + ): + m_sourceUnits(_sourceUnits), + m_success(_success), + m_errors(_errors), + m_errorInformation(_errorInformation) + {} + + /// @returns the compiled contract, if any was found. + /// @param _name find specific contract, if set. Default: return last contract + /// from source with empty name. + std::optional contract(ContractName const& _name = {}) const; + + /// @returns the annotated event sig for the hash given, if any. + /// @param hash + std::optional matchEvent( + util::h256 const& _hash + ) const; + + /// @returns true if no errors occurred during compilation. + bool success() const; + + /// @returns the first error that matches the type specified, or none if no + /// error for this type was found. + /// @param _type the error type to look for. + std::optional findError(langutil::Error::Type _type) const; + + /// @returns a formatted output of all errors that occurred during + /// compilation. + std::string errorInformation() const; + +private: + /// All compiled contracts, indexed by source name. + SourceUnits m_sourceUnits; + /// If compilation was successful. + bool m_success; + /// All errors, if any. + langutil::ErrorList m_errors; + /// Formatted error information. + std::string m_errorInformation; +}; + +} diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 5c1d41b7f595..a540ba4a906c 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -23,6 +23,8 @@ add_executable(isoltest ../libsolidity/util/ContractABIUtils.cpp ../libsolidity/util/TestFileParser.cpp ../libsolidity/util/TestFunctionCall.cpp + ../libsolidity/util/compiler/Compiler.h + ../libsolidity/util/compiler/Compiler.cpp ../libsolidity/GasTest.cpp ../libsolidity/MemoryGuardTest.cpp ../libsolidity/NatspecJSONTest.cpp From 6e8cb9a6e28c4cda00b03cbabde5ce434a6c7b3f Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 26 Mar 2025 16:02:27 +0100 Subject: [PATCH 2/5] CompilerStack: Expose contracts per source unit --- libsolidity/interface/CompilerStack.cpp | 12 ++++++++++++ libsolidity/interface/CompilerStack.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1b0a299f7291..84eb9108a610 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1256,6 +1256,18 @@ ContractDefinition const& CompilerStack::contractDefinition(std::string const& _ return *contract(_contractName).contract; } +std::vector CompilerStack::contractDefinitions( + std::string const& _sourceName +) const +{ + std::vector contracts; + if (auto source = m_sources.find(_sourceName); source != m_sources.end()) + for (auto const* contract: ASTNode::filteredNodes(source->second.ast->nodes())) + contracts.emplace_back(contract); + + return contracts; +} + size_t CompilerStack::functionEntryPoint( std::string const& _contractName, FunctionDefinition const& _function diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 2731787a4b88..382382acab2e 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -307,6 +307,9 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac /// does not exist. ContractDefinition const& contractDefinition(std::string const& _contractName) const; + /// @returns a list of parsed contracts per source unit with the supplied name. + std::vector contractDefinitions(std::string const& _sourceName) const; + /// @returns a list of unhandled queries to the SMT solver (has to be supplied in a second run /// by calling @a addSMTLib2Response). std::vector const& unhandledSMTLib2Queries() const { return m_unhandledSMTLib2Queries; } From ecf5c1b98df690e406730571a941cc1d98a59d7d Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Thu, 30 Jan 2025 14:49:42 +0100 Subject: [PATCH 3/5] Test: Add abstraction for internal compiler --- test/CMakeLists.txt | 2 + .../util/compiler/InternalCompiler.cpp | 180 ++++++++++++++++++ .../util/compiler/InternalCompiler.h | 69 +++++++ test/tools/CMakeLists.txt | 2 + 4 files changed, 253 insertions(+) create mode 100644 test/libsolidity/util/compiler/InternalCompiler.cpp create mode 100644 test/libsolidity/util/compiler/InternalCompiler.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9c8b7bdb7e6e..8cd77ab6ae70 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -119,6 +119,8 @@ detect_stray_source_files("${libsolidity_sources}" "libsolidity/") set(libsolidity_util_sources libsolidity/util/compiler/Compiler.h libsolidity/util/compiler/Compiler.cpp + libsolidity/util/compiler/InternalCompiler.h + libsolidity/util/compiler/InternalCompiler.cpp libsolidity/util/BytesUtils.cpp libsolidity/util/BytesUtilsTests.cpp libsolidity/util/BytesUtils.h diff --git a/test/libsolidity/util/compiler/InternalCompiler.cpp b/test/libsolidity/util/compiler/InternalCompiler.cpp new file mode 100644 index 000000000000..62280ecaa76c --- /dev/null +++ b/test/libsolidity/util/compiler/InternalCompiler.cpp @@ -0,0 +1,180 @@ +#include + +using namespace solidity; +using namespace solidity::evmasm; +using namespace solidity::frontend::test; + +CompilerOutput InternalCompiler::compile(CompilerInput const& _input) +{ + configure(_input); + + auto [contracts, errors] = internalCompile(_input); + bool success = m_stack.compilationSuccessful(); + + return CompilerOutput{ + contracts, + success, + errors, + formatErrorInformation() + }; +} + +void InternalCompiler::configure(CompilerInput const& _input) +{ + m_stack.reset(); + + m_stack.setSources(_input.sourceCode); + m_stack.setLibraries(_input.libraryAddresses); + + if (_input.evmVersion.has_value()) + m_stack.setEVMVersion(_input.evmVersion.value()); + if (_input.optimise.has_value()) + m_stack.setOptimiserSettings(_input.optimise.value()); + if (_input.optimiserSettings.has_value()) + m_stack.setOptimiserSettings(_input.optimiserSettings.value()); + if (_input.revertStrings.has_value()) + m_stack.setRevertStringBehaviour(_input.revertStrings.value()); + if (_input.metadataFormat.has_value()) + { + CompilerStack::MetadataFormat metadata = CompilerStack::MetadataFormat::NoMetadata; + switch (_input.metadataFormat.value()) + { + case MetadataFormat::WithReleaseVersionTag: + metadata = CompilerStack::MetadataFormat::WithReleaseVersionTag; + break; + case MetadataFormat::WithPrereleaseVersionTag: + metadata = CompilerStack::MetadataFormat::WithPrereleaseVersionTag; + break; + default: + break; + } + m_stack.setMetadataFormat(metadata); + } + if (_input.metadataHash.has_value()) + { + CompilerStack::MetadataHash hash = CompilerStack::MetadataHash::None; + switch (_input.metadataHash.value()) + { + case MetadataHash::IPFS: + hash = CompilerStack::MetadataHash::IPFS; + break; + case MetadataHash::Bzzr1: + hash = CompilerStack::MetadataHash::Bzzr1; + break; + default: + break; + } + m_stack.setMetadataHash(hash); + } + if (_input.viaIR.has_value()) + m_stack.setViaIR(_input.viaIR.value()); + m_stack.setEOFVersion(_input.eofVersion); +} + +std::pair InternalCompiler::internalCompile( + CompilerInput const& _input +) +{ + m_stack.compile(); + + MappedContracts mappedContracts{}; + for (auto sourceName: m_stack.sourceNames()) + { + std::vector compiled; + for (auto contract: m_stack.contractDefinitions(sourceName)) + { + std::string contractName = contract->fullyQualifiedName(); + LinkerObject object = m_stack.object(contractName); + LinkerObject runtimeObject = m_stack.runtimeObject(contractName); + bool hasUnlinkedReferences = !object.linkReferences.empty(); + bytes cborMetadata = m_stack.cborMetadata(contractName); + + std::optional assemblyItems; + std::optional runtimeAssemblyItems; + + if (!_input.eofVersion.has_value()) + { + if (auto items = m_stack.assemblyItems(contractName); items != nullptr) + assemblyItems = std::make_optional(*items); + + if (auto items = m_stack.runtimeAssemblyItems(contractName); items != nullptr) + runtimeAssemblyItems = std::make_optional(*items); + } + + if (!m_stack.isExperimentalSolidity()) + compiled.emplace_back(CompiledContract{ + contractName, + object.bytecode, + runtimeObject.bytecode, + hasUnlinkedReferences, + cborMetadata, + assemblyItems, + runtimeAssemblyItems, + m_stack.metadata(contractName), + m_stack.contractABI(contractName), + m_stack.interfaceSymbols(contractName), + generateEventSignatures(contractName) + }); + else + compiled.emplace_back(CompiledContract{ + contractName, + object.bytecode, + runtimeObject.bytecode, + hasUnlinkedReferences, + cborMetadata, + assemblyItems, + runtimeAssemblyItems, + std::nullopt, + std::nullopt, + std::nullopt, + std::vector{} + }); + } + mappedContracts.insert(std::make_pair(sourceName, compiled)); + } + + return std::make_pair(mappedContracts, m_stack.errors()); +} + +std::vector InternalCompiler::generateEventSignatures( + std::string const& _contractName +) const +{ + std::vector signatures; + ContractDefinition const& contract = m_stack.contractDefinition(_contractName); + + for (EventDefinition const* event: contract.events() + contract.usedInterfaceEvents()) + { + AnnotatedEventSignature eventInfo; + auto eventFunctionType = event->functionType(true); + + eventInfo.signature = eventFunctionType->externalSignature(); + for (auto const& param: event->parameters()) + if (param->isIndexed()) + eventInfo.indexedTypes.emplace_back(param->type()->toString(true)); + else + eventInfo.nonIndexedTypes.emplace_back(param->type()->toString(true)); + eventInfo.isAnonymous = event->isAnonymous(); + + signatures.push_back(eventInfo); + } + + return signatures; +} + +std::string InternalCompiler::formatErrorInformation() const +{ + std::string errorInformation; + for (auto const& error: m_stack.errors()) + { + auto formatted = SourceReferenceFormatter::formatErrorInformation( + *error.get(), + m_stack, + true, + false + ); + errorInformation.append(formatted); + } + + return errorInformation; +} diff --git a/test/libsolidity/util/compiler/InternalCompiler.h b/test/libsolidity/util/compiler/InternalCompiler.h new file mode 100644 index 000000000000..6020d10014e2 --- /dev/null +++ b/test/libsolidity/util/compiler/InternalCompiler.h @@ -0,0 +1,69 @@ +/* + 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 + +using namespace solidity::langutil; + +namespace solidity::frontend::test +{ + +using MappedContracts = std::map>; +using Errors = ErrorList; + +/** + * Abstraction of the internal compiler aka. `CompilerStack`. + */ +class InternalCompiler +{ +public: + /// Configures and compiles using the internal compiler stack. + /// @param _input the compiler input with sources, options etc. optionally + /// set. + /// @returns the aggregated compiler output. + CompilerOutput compile(CompilerInput const& _input); + +private: + /// Configures the compiler stack. Needs to be called before compilation. + /// @param _input is mapped to the compiler stacks' setters. + void configure(CompilerInput const& _input); + + /// Compile with current input. + /// @returns compiled contracts per source unit and all errors. + std::pair internalCompile(CompilerInput const& _input); + + /// @returns annotated event signatures per contract. + /// @param _contractName defines the contract to be used. + std::vector generateEventSignatures( + std::string const& _contractName + ) const; + + /// @returns a formatted output of all errors that occurred during + /// compilation. + std::string formatErrorInformation() const; + + /// The compiler stack + CompilerStack m_stack; +}; + +} diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index a540ba4a906c..cca5efa90848 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -25,6 +25,8 @@ add_executable(isoltest ../libsolidity/util/TestFunctionCall.cpp ../libsolidity/util/compiler/Compiler.h ../libsolidity/util/compiler/Compiler.cpp + ../libsolidity/util/compiler/InternalCompiler.h + ../libsolidity/util/compiler/InternalCompiler.cpp ../libsolidity/GasTest.cpp ../libsolidity/MemoryGuardTest.cpp ../libsolidity/NatspecJSONTest.cpp From f776c460b60d910984e2c5c4faf1d40cff1ebdb1 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Thu, 30 Jan 2025 14:50:42 +0100 Subject: [PATCH 4/5] Test: Add universal host for compilers under test --- test/CMakeLists.txt | 2 + .../util/compiler/CompilerHost.cpp | 25 ++++++++++ test/libsolidity/util/compiler/CompilerHost.h | 48 +++++++++++++++++++ test/tools/CMakeLists.txt | 2 + 4 files changed, 77 insertions(+) create mode 100644 test/libsolidity/util/compiler/CompilerHost.cpp create mode 100644 test/libsolidity/util/compiler/CompilerHost.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8cd77ab6ae70..1a50b4584c5e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -121,6 +121,8 @@ set(libsolidity_util_sources libsolidity/util/compiler/Compiler.cpp libsolidity/util/compiler/InternalCompiler.h libsolidity/util/compiler/InternalCompiler.cpp + libsolidity/util/compiler/CompilerHost.h + libsolidity/util/compiler/CompilerHost.cpp libsolidity/util/BytesUtils.cpp libsolidity/util/BytesUtilsTests.cpp libsolidity/util/BytesUtils.h diff --git a/test/libsolidity/util/compiler/CompilerHost.cpp b/test/libsolidity/util/compiler/CompilerHost.cpp new file mode 100644 index 000000000000..3d5dcc1909c8 --- /dev/null +++ b/test/libsolidity/util/compiler/CompilerHost.cpp @@ -0,0 +1,25 @@ +#include + +using namespace solidity; +using namespace solidity::frontend::test; + +CompilerOutput const& CompilerHost::compile(CompilerInput _input) +{ + auto compile = [=](auto& compiler) { return compiler.compile(_input); }; + auto output = std::visit(compile, m_compiler); + + m_output.emplace(output); + + return this->output(); +} + +void CompilerHost::reset() +{ + m_output.reset(); +} + +CompilerOutput const& CompilerHost::output() const +{ + solAssert(m_output.has_value(), "No output found. Please compile first."); + return m_output.value(); +} diff --git a/test/libsolidity/util/compiler/CompilerHost.h b/test/libsolidity/util/compiler/CompilerHost.h new file mode 100644 index 000000000000..1380c1adcee7 --- /dev/null +++ b/test/libsolidity/util/compiler/CompilerHost.h @@ -0,0 +1,48 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +namespace solidity::frontend::test +{ + +class CompilerHost +{ +public: + /// Takes the current compiler input, requests the hosted compiler to compile + /// and updates the output accordingly. + CompilerOutput const& compile(CompilerInput _input); + + /// Resets the output generated by the previous compilation. + void reset(); + + /// @returns the stored output generated by the previous compilation. + CompilerOutput const& output() const; + +private: + /// Instance of an invocable compiler. + std::variant m_compiler; + /// Last generated output. Will be none before initial compilation + /// and after reset. + std::optional m_output; +}; + +} diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index cca5efa90848..d94cfa281593 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -25,6 +25,8 @@ add_executable(isoltest ../libsolidity/util/TestFunctionCall.cpp ../libsolidity/util/compiler/Compiler.h ../libsolidity/util/compiler/Compiler.cpp + ../libsolidity/util/compiler/CompilerHost.h + ../libsolidity/util/compiler/CompilerHost.cpp ../libsolidity/util/compiler/InternalCompiler.h ../libsolidity/util/compiler/InternalCompiler.cpp ../libsolidity/GasTest.cpp From 132c6f49d82ea78d8976fc4c40bb4a1643fb88dd Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Thu, 30 Jan 2025 14:51:48 +0100 Subject: [PATCH 5/5] Test: Use new universal compiler host --- test/ExecutionFramework.h | 2 +- test/libsolidity/GasCosts.cpp | 10 +-- test/libsolidity/GasMeter.cpp | 43 +++++++++--- test/libsolidity/SemanticTest.cpp | 51 +++++--------- test/libsolidity/SemanticTest.h | 7 -- test/libsolidity/SolidityEndToEndTest.cpp | 14 +++- .../SolidityExecutionFramework.cpp | 69 +++++++++++-------- test/libsolidity/SolidityExecutionFramework.h | 18 +++-- 8 files changed, 125 insertions(+), 89 deletions(-) diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 44812e230ab0..02700a2935e6 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -65,7 +65,7 @@ class ExecutionFramework std::string const& _contractName = "", bytes const& _arguments = {}, std::map const& _libraryAddresses = {}, - std::optional const& _sourceName = std::nullopt + std::optional const& _mainSourceName = std::nullopt ) = 0; bytes const& compileAndRun( diff --git a/test/libsolidity/GasCosts.cpp b/test/libsolidity/GasCosts.cpp index d8dad9e02705..a991ad2bd030 100644 --- a/test/libsolidity/GasCosts.cpp +++ b/test/libsolidity/GasCosts.cpp @@ -38,7 +38,8 @@ namespace solidity::frontend::test #define CHECK_DEPLOY_GAS(_gasNoOpt, _gasOpt, _evmVersion) \ do \ { \ - u256 metaCost = GasMeter::dataGas(m_compiler.cborMetadata(m_compiler.lastContractName()), true, _evmVersion); \ + auto compiledContract = m_compiler.output().contract().value(); \ + u256 metaCost = GasMeter::dataGas(compiledContract.cborMetadata, true, _evmVersion); \ u256 gasOpt{_gasOpt}; \ u256 gasNoOpt{_gasNoOpt}; \ u256 gas = m_optimiserSettings == OptimiserSettings::minimal() ? gasNoOpt : gasOpt; \ @@ -90,7 +91,7 @@ BOOST_AUTO_TEST_CASE(string_storage) } } )"; - m_compiler.setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); + m_appendCBORMetadata = false; compileAndRun(sourceCode); @@ -202,8 +203,9 @@ BOOST_AUTO_TEST_CASE(single_callvaluecheck) } )"; compileAndRun(sourceCode); - size_t bytecodeSizeNonpayable = m_compiler.object("Nonpayable").bytecode.size(); - size_t bytecodeSizePayable = m_compiler.object("Payable").bytecode.size(); + auto compilerOutput = m_compiler.output(); + size_t bytecodeSizeNonpayable = compilerOutput.contract("Nonpayable").value().object.size(); + size_t bytecodeSizePayable = compilerOutput.contract("Payable").value().object.size(); auto evmVersion = solidity::test::CommonOptions::get().evmVersion(); if (evmVersion < EVMVersion::shanghai()) diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 9012ad083c02..827ad54ce38e 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -22,6 +22,7 @@ */ #include +#include #include #include #include @@ -43,24 +44,40 @@ class GasMeterTestFramework: public SolidityExecutionFramework void compile(std::string const& _sourceCode) { m_compiler.reset(); - m_compiler.setSources({{"", "pragma solidity >=0.0;\n" - "// SPDX-License-Identifier: GPL-3.0\n" + _sourceCode}}); - m_compiler.setOptimiserSettings(solidity::test::CommonOptions::get().optimize); - m_compiler.setEVMVersion(m_evmVersion); - BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); + m_compilerInput = CompilerInput{}; + + m_compilerInput.sourceCode = {{"", "pragma solidity >=0.0;\n" + "// SPDX-License-Identifier: GPL-3.0\n" + _sourceCode}}; + m_compilerInput.optimise = solidity::test::CommonOptions::get().optimize; + m_compilerInput.evmVersion = std::make_optional(m_evmVersion); + + m_compiler.compile(m_compilerInput); + + BOOST_REQUIRE_MESSAGE(m_compiler.output().success(), "Compiling contract failed"); } void testCreationTimeGas(std::string const& _sourceCode, u256 const& _tolerance = u256(0)) { compileAndRun(_sourceCode); + auto state = std::make_shared(); - PathGasMeter meter(*m_compiler.assemblyItems(m_compiler.lastContractName()), solidity::test::CommonOptions::get().evmVersion()); + auto output = m_compiler.output(); + auto contract = output.contract(); + + soltestAssert(contract.has_value()); + soltestAssert(contract.value().assemblyItems.has_value()); + + auto object = contract.value().object; + auto runtimeObject = contract.value().runtimeObject; + auto assemblyItems = contract.value().assemblyItems.value(); + + PathGasMeter meter(assemblyItems, solidity::test::CommonOptions::get().evmVersion()); GasMeter::GasConsumption gas = meter.estimateMax(0, state); - u256 bytecodeSize(m_compiler.runtimeObject(m_compiler.lastContractName()).bytecode.size()); + u256 bytecodeSize(runtimeObject.size()); // costs for deployment gas += bytecodeSize * GasCosts::createDataGas; // costs for transaction - gas += gasForTransaction(m_compiler.object(m_compiler.lastContractName()).bytecode, true); + gas += gasForTransaction(object, true); BOOST_REQUIRE(!gas.isInfinite); BOOST_CHECK_LE(m_gasUsed, gas.value); @@ -71,6 +88,14 @@ class GasMeterTestFramework: public SolidityExecutionFramework /// against the actual gas usage computed by the VM on the given set of argument variants. void testRunTimeGas(std::string const& _sig, std::vector _argumentVariants, u256 const& _tolerance = u256(0)) { + auto output = m_compiler.output(); + auto contract = output.contract(); + + soltestAssert(contract.has_value()); + soltestAssert(contract.value().runtimeAssemblyItems.has_value()); + + auto runtimeAssemblyItems = contract.value().runtimeAssemblyItems.value(); + u256 gasUsed = 0; GasMeter::GasConsumption gas; util::FixedHash<4> hash = util::selectorFromSignatureH32(_sig); @@ -83,7 +108,7 @@ class GasMeterTestFramework: public SolidityExecutionFramework } gas += GasEstimator(solidity::test::CommonOptions::get().evmVersion()).functionalEstimation( - *m_compiler.runtimeAssemblyItems(m_compiler.lastContractName()), + runtimeAssemblyItems, _sig ); BOOST_REQUIRE(!gas.isInfinite); diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 1b66dd60fffb..9f31234447a6 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -118,8 +118,8 @@ SemanticTest::SemanticTest( if (m_enforceGasCost) { - m_compiler.setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); - m_compiler.setMetadataHash(CompilerStack::MetadataHash::None); + m_compilerInput.metadataFormat = MetadataFormat::NoMetadata; + m_compilerInput.metadataHash = MetadataHash::None; } } @@ -231,13 +231,15 @@ std::string SemanticTest::formatEventParameter(std::optional SemanticTest::eventSideEffectHook(FunctionCall const&) const { + auto output = m_compiler.output(); + std::vector sideEffects; std::vector recordedLogs = ExecutionFramework::recordedLogs(); for (LogRecord const& log: recordedLogs) { std::optional eventSignature; if (!log.topics.empty()) - eventSignature = matchEvent(log.topics[0]); + eventSignature = output.matchEvent(log.topics[0]); std::stringstream sideEffect; sideEffect << "emit "; if (eventSignature.has_value()) @@ -273,31 +275,6 @@ std::vector SemanticTest::eventSideEffectHook(FunctionCall const&) return sideEffects; } -std::optional SemanticTest::matchEvent(util::h256 const& hash) const -{ - std::optional result; - for (std::string& contractName: m_compiler.contractNames()) - { - ContractDefinition const& contract = m_compiler.contractDefinition(contractName); - for (EventDefinition const* event: contract.events() + contract.usedInterfaceEvents()) - { - FunctionTypePointer eventFunctionType = event->functionType(true); - if (!event->isAnonymous() && keccak256(eventFunctionType->externalSignature()) == hash) - { - AnnotatedEventSignature eventInfo; - eventInfo.signature = eventFunctionType->externalSignature(); - for (auto const& param: event->parameters()) - if (param->isIndexed()) - eventInfo.indexedTypes.emplace_back(param->type()->toString(true)); - else - eventInfo.nonIndexedTypes.emplace_back(param->type()->toString(true)); - result = eventInfo; - } - } - } - return result; -} - frontend::OptimiserSettings SemanticTest::optimizerSettingsFor(RequiresYulOptimizer _requiresYulOptimizer) { switch (_requiresYulOptimizer) @@ -383,13 +360,14 @@ TestCase::TestResult SemanticTest::runTest( } else if (test.call().kind == FunctionCall::Kind::Library) { + std::string name = test.call().libraryFile + ":" + test.call().signature; soltestAssert( - deploy(test.call().signature, 0, {}, libraries) && m_transactionSuccessful, + deploy(name, 0, {}, libraries) && m_transactionSuccessful, "Failed to deploy library " + test.call().signature); // For convenience, in semantic tests we assume that an unqualified name like `L` is equivalent to one // with an empty source unit name (`:L`). This is fine because the compiler never uses unqualified // names in the Yul code it produces and does not allow `linkersymbol()` at all in inline assembly. - libraries[test.call().libraryFile + ":" + test.call().signature] = m_contractAddress; + libraries[name] = m_contractAddress; continue; } else @@ -416,7 +394,14 @@ TestCase::TestResult SemanticTest::runTest( } else { + ContractName contractName{m_sources.mainSourceFile, ""}; + + auto compiledContract = m_compiler.output().contract(contractName); + solAssert(compiledContract.has_value()); + + CompiledContract contract = compiledContract.value(); bytes output; + if (test.call().kind == FunctionCall::Kind::LowLevel) output = callLowLevel(test.call().arguments.rawBytes(), test.call().value.value); else if (test.call().kind == FunctionCall::Kind::Builtin) @@ -432,9 +417,10 @@ TestCase::TestResult SemanticTest::runTest( } else { + soltestAssert(contract.interfaceSymbols.has_value()); soltestAssert( m_allowNonExistingFunctions || - m_compiler.interfaceSymbols(m_compiler.lastContractName(m_sources.mainSourceFile))["methods"].contains(test.call().signature), + contract.interfaceSymbols.value()["methods"].contains(test.call().signature), "The function " + test.call().signature + " is not known to the compiler" ); @@ -462,7 +448,8 @@ TestCase::TestResult SemanticTest::runTest( test.setFailure(!m_transactionSuccessful); test.setRawBytes(std::move(output)); if (test.call().kind != FunctionCall::Kind::LowLevel) - test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName(m_sources.mainSourceFile))); + if (contract.contractABI.has_value()) + test.setContractABI(contract.contractABI.value()); } std::vector effects; diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index efc2b59030e6..f01d286595d6 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -30,13 +30,6 @@ namespace solidity::frontend::test { -struct AnnotatedEventSignature -{ - std::string signature; - std::vector indexedTypes; - std::vector nonIndexedTypes; -}; - enum class RequiresYulOptimizer { False, diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 76699c2e3b6c..4c35ff2afcac 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(creation_code_optimizer) } )"; - m_metadataHash = CompilerStack::MetadataHash::None; + m_metadataHash = MetadataHash::None; ALSO_VIA_YUL({ bytes bytecodeC = compileContract(codeC); reset(); @@ -2815,9 +2815,17 @@ BOOST_AUTO_TEST_CASE(include_creation_bytecode_only_once) } )"; compileAndRun(sourceCode); + + auto output = m_compiler.output(); + auto contractDouble = output.contract("Double"); + auto contractSingle = output.contract("Single"); + + solAssert(contractDouble); + solAssert(contractSingle); + BOOST_CHECK_LE( - double(m_compiler.object("Double").bytecode.size()), - 1.2 * double(m_compiler.object("Single").bytecode.size()) + double(contractDouble.value().object.size()), + 1.2 * double(contractSingle.value().object.size()) ); } diff --git a/test/libsolidity/SolidityExecutionFramework.cpp b/test/libsolidity/SolidityExecutionFramework.cpp index 57d4d9d9b75b..31643541c4a7 100644 --- a/test/libsolidity/SolidityExecutionFramework.cpp +++ b/test/libsolidity/SolidityExecutionFramework.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -42,49 +43,63 @@ using namespace solidity::test; bytes SolidityExecutionFramework::multiSourceCompileContract( std::map const& _sourceCode, - std::optional const& _mainSourceName, std::string const& _contractName, - std::map const& _libraryAddresses + std::map const& _libraryAddresses, + std::optional const& _mainSourceName ) { if (_mainSourceName.has_value()) solAssert(_sourceCode.find(_mainSourceName.value()) != _sourceCode.end(), ""); - m_compiler.reset(); - m_compiler.setSources(withPreamble( + m_compilerInput = CompilerInput{}; + m_compilerInput.sourceCode = withPreamble( _sourceCode, solidity::test::CommonOptions::get().useABIEncoderV1 // _addAbicoderV1Pragma - )); - m_compiler.setLibraries(_libraryAddresses); - m_compiler.setRevertStringBehaviour(m_revertStrings); - m_compiler.setEVMVersion(m_evmVersion); - m_compiler.setEOFVersion(m_eofVersion); - m_compiler.setOptimiserSettings(m_optimiserSettings); - m_compiler.setViaIR(m_compileViaYul); - m_compiler.setRevertStringBehaviour(m_revertStrings); + ); + m_compilerInput.libraryAddresses = _libraryAddresses; + m_compilerInput.evmVersion = std::make_optional(m_evmVersion); + m_compilerInput.eofVersion = m_eofVersion; + m_compilerInput.optimiserSettings = std::make_optional(m_optimiserSettings); + m_compilerInput.revertStrings = std::make_optional(m_revertStrings); + m_compilerInput.metadataHash = std::make_optional(m_metadataHash); + m_compilerInput.viaIR = std::make_optional(m_compileViaYul); if (!m_appendCBORMetadata) { - m_compiler.setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); + m_compilerInput.metadataFormat = std::make_optional(MetadataFormat::NoMetadata); } - m_compiler.setMetadataHash(m_metadataHash); - if (!m_compiler.compile()) + CompilerOutput const& output = m_compiler.compile(m_compilerInput); + if (!output.success()) { // The testing framework expects an exception for // "unimplemented" yul IR generation. if (m_compileViaYul) - for (auto const& error: m_compiler.errors()) - if (error->type() == langutil::Error::Type::CodeGenerationError) - BOOST_THROW_EXCEPTION(*error); - langutil::SourceReferenceFormatter{std::cerr, m_compiler, true, false} - .printErrorInformation(m_compiler.errors()); + { + auto error = output.findError(langutil::Error::Type::CodeGenerationError); + if (error.has_value()) + BOOST_THROW_EXCEPTION(error.value()); + } + std::cout << output.errorInformation() << std::endl; BOOST_ERROR("Compiling contract failed"); } - std::string contractName(_contractName.empty() ? m_compiler.lastContractName(_mainSourceName) : _contractName); - evmasm::LinkerObject obj = m_compiler.object(contractName); - BOOST_REQUIRE(obj.linkReferences.empty()); + + // Construct `ContractName` with the contract name given, and use `_mainSourceName` + // if the contract's name source prefix is empty. + ContractName name{_contractName}; + ContractName lookupName = name.source().empty() ? + ContractName{_mainSourceName.value_or(""), name.contract()} : + name; + + auto contract = output.contract(lookupName); + soltestAssert(contract.has_value()); + soltestAssert(!contract.value().hasUnlinkedReferences); + if (m_showMetadata) - std::cout << "metadata: " << m_compiler.metadata(contractName) << std::endl; - return obj.bytecode; + { + auto metadata = contract.value().metadata.value_or(""); + std::cout << "metadata: " << metadata << std::endl; + } + + return contract.value().object; } bytes SolidityExecutionFramework::compileContract( @@ -95,8 +110,8 @@ bytes SolidityExecutionFramework::compileContract( { return multiSourceCompileContract( {{"", _sourceCode}}, - std::nullopt, _contractName, - _libraryAddresses + _libraryAddresses, + std::nullopt ); } diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 4caa4aa1e1a4..35be89b544a3 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -27,6 +27,9 @@ #include +#include +#include + #include #include @@ -35,6 +38,8 @@ namespace solidity::frontend::test { +using namespace solidity::test; + class SolidityExecutionFramework: public solidity::test::ExecutionFramework { @@ -58,10 +63,10 @@ class SolidityExecutionFramework: public solidity::test::ExecutionFramework std::string const& _contractName = "", bytes const& _arguments = {}, std::map const& _libraryAddresses = {}, - std::optional const& _sourceName = std::nullopt + std::optional const& _mainSourceName = std::nullopt ) override { - bytes bytecode = multiSourceCompileContract(_sourceCode, _sourceName, _contractName, _libraryAddresses); + bytes bytecode = multiSourceCompileContract(_sourceCode, _contractName, _libraryAddresses, _mainSourceName); sendMessage(bytecode, _arguments, true, _value); return m_output; } @@ -74,19 +79,20 @@ class SolidityExecutionFramework: public solidity::test::ExecutionFramework bytes multiSourceCompileContract( std::map const& _sources, - std::optional const& _mainSourceName = std::nullopt, std::string const& _contractName = "", - std::map const& _libraryAddresses = {} + std::map const& _libraryAddresses = {}, + std::optional const& _mainSourceName = std::nullopt ); protected: using CompilerStack = solidity::frontend::CompilerStack; std::optional m_eofVersion; - CompilerStack m_compiler; + CompilerInput m_compilerInput; + CompilerHost m_compiler; bool m_compileViaYul = false; bool m_showMetadata = false; bool m_appendCBORMetadata = true; - CompilerStack::MetadataHash m_metadataHash = CompilerStack::MetadataHash::IPFS; + MetadataHash m_metadataHash = MetadataHash::IPFS; RevertStrings m_revertStrings = RevertStrings::Default; };