diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1b0a299f7291..29ab15c98926 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1256,6 +1256,16 @@ ContractDefinition const& CompilerStack::contractDefinition(std::string const& _ return *contract(_contractName).contract; } +std::vector CompilerStack::contractDefinitions( + std::string const& _sourceName +) const +{ + solAssert(m_sources.find(_sourceName) != m_sources.end(), "No source found."); + auto const& source = m_sources.find(_sourceName); + + return ASTNode::filteredNodes(source->second.ast->nodes()); +} + 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; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c66963c190d4..5875cd841c08 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -121,6 +121,12 @@ 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/compiler/CompilerHost.h + libsolidity/util/compiler/CompilerHost.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/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..ce7de118f4b9 100644 --- a/test/libsolidity/GasCosts.cpp +++ b/test/libsolidity/GasCosts.cpp @@ -19,7 +19,9 @@ * Tests that check that the cost of certain operations stay within range. */ +#include #include + #include #include #include @@ -38,7 +40,10 @@ 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 const& output = m_compiler.output(); \ + auto const* contract = output.contract(); \ + soltestAssert(contract); \ + u256 metaCost = GasMeter::dataGas(contract->cborMetadata, true, _evmVersion); \ u256 gasOpt{_gasOpt}; \ u256 gasNoOpt{_gasNoOpt}; \ u256 gas = m_optimiserSettings == OptimiserSettings::minimal() ? gasNoOpt : gasOpt; \ @@ -90,7 +95,7 @@ BOOST_AUTO_TEST_CASE(string_storage) } } )"; - m_compiler.setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); + m_appendCBORMetadata = false; compileAndRun(sourceCode); @@ -202,8 +207,15 @@ 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 const& output = m_compiler.output(); + auto const* nonpayable = output.contract("Nonpayable"); + auto const* payable = output.contract("Payable"); + + soltestAssert(nonpayable); + soltestAssert(payable); + + size_t bytecodeSizeNonpayable = nonpayable->object.size(); + size_t bytecodeSizePayable = payable->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..d4fbfcd0f056 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -22,9 +22,14 @@ */ #include + +#include +#include + #include #include #include + #include #include @@ -43,24 +48,39 @@ 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 = {{"", withPreamble(_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 const& output = m_compiler.output(); + auto const* contract = output.contract(); + + soltestAssert(contract); + soltestAssert(contract->assemblyItems.has_value()); + + auto object = contract->object; + auto runtimeObject = contract->runtimeObject; + auto assemblyItems = contract->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 +91,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 const& output = m_compiler.output(); + auto const* contract = output.contract(); + + soltestAssert(contract); + soltestAssert(contract->runtimeAssemblyItems.has_value()); + + auto runtimeAssemblyItems = contract->runtimeAssemblyItems.value(); + u256 gasUsed = 0; GasMeter::GasConsumption gas; util::FixedHash<4> hash = util::selectorFromSignatureH32(_sig); @@ -83,7 +111,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..16b365c87e1d 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; } } @@ -196,7 +196,7 @@ std::vector SemanticTest::makeSideEffectHooks() const }; } -std::string SemanticTest::formatEventParameter(std::optional _signature, bool _indexed, size_t _index, bytes const& _data) +std::string SemanticTest::formatEventParameter(EventSignature const* _signature, bool _indexed, size_t _index, bytes const& _data) { auto isPrintableASCII = [](bytes const& s) { @@ -217,7 +217,7 @@ std::string SemanticTest::formatEventParameter(std::optional const& types = _indexed ? _signature->indexedTypes : _signature->nonIndexedTypes; if (_index < types.size()) @@ -231,17 +231,19 @@ std::string SemanticTest::formatEventParameter(std::optional SemanticTest::eventSideEffectHook(FunctionCall const&) const { + auto const& output = m_compiler.output(); + std::vector sideEffects; std::vector recordedLogs = ExecutionFramework::recordedLogs(); for (LogRecord const& log: recordedLogs) { - std::optional eventSignature; + EventSignature const* eventSignature = nullptr; if (!log.topics.empty()) - eventSignature = matchEvent(log.topics[0]); + eventSignature = output.matchEvent(log.topics[0]); std::stringstream sideEffect; sideEffect << "emit "; - if (eventSignature.has_value()) - sideEffect << eventSignature.value().signature; + if (eventSignature) + sideEffect << eventSignature->signature; else sideEffect << ""; @@ -252,7 +254,7 @@ std::vector SemanticTest::eventSideEffectHook(FunctionCall const&) size_t index{0}; for (h256 const& topic: log.topics) { - if (!eventSignature.has_value() || index != 0) + if (!eventSignature || index != 0) eventStrings.push_back("#" + formatEventParameter(eventSignature, true, index, topic.asBytes())); ++index; } @@ -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,13 @@ TestCase::TestResult SemanticTest::runTest( } else { + ContractName contractName{m_sources.mainSourceFile, ""}; + + auto contract = m_compiler.output().contract(contractName); + soltestAssert(contract); + 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 +416,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 +447,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..87a703a7e5f4 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, @@ -107,8 +100,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict std::map makeBuiltins(); std::vector makeSideEffectHooks() const; std::vector eventSideEffectHook(FunctionCall const&) const; - std::optional matchEvent(util::h256 const& hash) const; - static std::string formatEventParameter(std::optional _signature, bool _indexed, size_t _index, bytes const& _data); + static std::string formatEventParameter(EventSignature const* _signature, bool _indexed, size_t _index, bytes const& _data); OptimiserSettings optimizerSettingsFor(RequiresYulOptimizer _requiresYulOptimizer); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 76699c2e3b6c..c5753ca8788e 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 const& output = m_compiler.output(); + auto const* contractDouble = output.contract("Double"); + auto const* 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->object.size()), + 1.2 * double(contractSingle->object.size()) ); } diff --git a/test/libsolidity/SolidityExecutionFramework.cpp b/test/libsolidity/SolidityExecutionFramework.cpp index 57d4d9d9b75b..aed98c329c75 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 const* contract = output.contract(lookupName); + soltestAssert(contract); + soltestAssert(!contract->hasUnlinkedReferences); + if (m_showMetadata) - std::cout << "metadata: " << m_compiler.metadata(contractName) << std::endl; - return obj.bytecode; + { + auto metadata = contract->metadata.value_or(""); + std::cout << "metadata: " << metadata << std::endl; + } + + return contract->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; }; diff --git a/test/libsolidity/util/compiler/Compiler.cpp b/test/libsolidity/util/compiler/Compiler.cpp new file mode 100644 index 000000000000..8ea31fef3fbe --- /dev/null +++ b/test/libsolidity/util/compiler/Compiler.cpp @@ -0,0 +1,62 @@ +#include + +using namespace solidity; +using namespace solidity::frontend::test; + +CompiledContract const* CompilerOutput::contract(ContractName const& _name) const +{ + auto const& sourceName = _name.source(); + auto const& contractName = _name.contract(); + + if (auto source = m_sourceUnits.find(sourceName); source != m_sourceUnits.end()) + { + if (!contractName.empty()) + { + for (auto const& contract: source->second) + if ( + contractName == contract.name || + sourceName + ":" + contractName == contract.name + ) + return &contract; + } + else + { + if (!source->second.empty()) + return &source->second.back(); + } + } + + return nullptr; +} + +EventSignature const* 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 &event; + + return nullptr; +} + +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_view 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..534beecc30ca --- /dev/null +++ b/test/libsolidity/util/compiler/Compiler.h @@ -0,0 +1,257 @@ +/* + 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 +{ + +using MetadataFormat = CompilerStack::MetadataFormat; +using MetadataHash = CompilerStack::MetadataHash; + +struct EventSignature +{ + static EventSignature fromEvent(EventDefinition const& _event) + { + auto externalSignature = _event.functionType(true)->externalSignature(); + bool isAnonymous = _event.isAnonymous(); + + auto toString = [](auto const& _param) { return _param->type()->toString(true); }; + + auto indexedTypes = + _event.parameters() | + ranges::views::filter([](auto const& _param) { return _param->isIndexed(); }) | + ranges::views::transform(toString) | + ranges::to(); + auto nonIndexedTypes = + _event.parameters() | + ranges::views::filter([](auto const& _param) { return !_param->isIndexed(); }) | + ranges::views::transform(toString) | + ranges::to(); + + return EventSignature{ + externalSignature, + indexedTypes, + nonIndexedTypes, + isAnonymous + }; + } + + std::string signature; + std::vector indexedTypes; + std::vector nonIndexedTypes; + bool isAnonymous; +}; + +template +concept ToStringView = std::is_convertible_v; + +/** + * 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" + explicit ContractName(std::string_view const _name) + { + auto const 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 e.g. string literals. + template + ContractName(T const& _name): ContractName(std::string_view{_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. + CompiledContract const* contract(ContractName const& _name = {}) const; + + /// @returns the annotated event sig for the hash given, if any. + /// @param hash + EventSignature const* 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_view 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/libsolidity/util/compiler/CompilerHost.cpp b/test/libsolidity/util/compiler/CompilerHost.cpp new file mode 100644 index 000000000000..6d49544a8d10 --- /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/libsolidity/util/compiler/InternalCompiler.cpp b/test/libsolidity/util/compiler/InternalCompiler.cpp new file mode 100644 index 000000000000..08a39dcfd64e --- /dev/null +++ b/test/libsolidity/util/compiler/InternalCompiler.cpp @@ -0,0 +1,150 @@ +#include + +#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()) + m_stack.setMetadataFormat(_input.metadataFormat.value()); + if (_input.metadataHash.has_value()) + m_stack.setMetadataHash(_input.metadataHash.value()); + 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 const& contract: m_stack.contractDefinitions(sourceName)) + { + std::string const& contractName = contract->fullyQualifiedName(); + LinkerObject const& object = m_stack.object(contractName); + LinkerObject const& 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 +{ + auto const& contract = m_stack.contractDefinition(_contractName); + auto const& events = contract.events(); + auto const& interfaceEvents = contract.usedInterfaceEvents(); + + auto toSignature = [](EventDefinition const* _event) + { + soltestAssert(_event); + return EventSignature::fromEvent(*_event); + }; + + auto signatures = + ranges::views::concat(events, interfaceEvents) | + ranges::views::transform(toSignature) | + ranges::to(); + + return signatures; +} + +std::string InternalCompiler::formatErrorInformation() const +{ + std::string errorInformation; + for (auto const& error: m_stack.errors()) + { + auto formatted = SourceReferenceFormatter::formatErrorInformation( + *error, + 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..15996779bccf --- /dev/null +++ b/test/libsolidity/util/compiler/InternalCompiler.h @@ -0,0 +1,70 @@ +/* + 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 289f2f2def35..0ebf8a0d24bc 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -25,6 +25,12 @@ 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/util/compiler/CompilerHost.h + ../libsolidity/util/compiler/CompilerHost.cpp + ../libsolidity/util/compiler/InternalCompiler.h + ../libsolidity/util/compiler/InternalCompiler.cpp ../libsolidity/GasTest.cpp ../libsolidity/MemoryGuardTest.cpp ../libsolidity/NatspecJSONTest.cpp