diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 32869ba13..7feb7b294 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -46,6 +46,8 @@ set(sources analysis/TypeChecker.h analysis/ViewPureChecker.cpp analysis/ViewPureChecker.h + analysis/experimental/Analysis.cpp + analysis/experimental/Analysis.h ast/AST.cpp ast/AST.h ast/AST_accept.h @@ -90,6 +92,8 @@ set(sources codegen/ReturnInfo.cpp codegen/YulUtilFunctions.h codegen/YulUtilFunctions.cpp + codegen/experimental/IRGenerator.cpp + codegen/experimental/IRGenerator.h codegen/ir/Common.cpp codegen/ir/Common.h codegen/ir/IRGenerator.cpp diff --git a/libsolidity/analysis/experimental/Analysis.cpp b/libsolidity/analysis/experimental/Analysis.cpp new file mode 100644 index 000000000..5138ff280 --- /dev/null +++ b/libsolidity/analysis/experimental/Analysis.cpp @@ -0,0 +1,26 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#include + +using namespace solidity::langutil; +using namespace solidity::frontend::experimental; + +bool Analysis::check(ASTNode const&) +{ + return true; +} diff --git a/libsolidity/analysis/experimental/Analysis.h b/libsolidity/analysis/experimental/Analysis.h new file mode 100644 index 000000000..9433508e4 --- /dev/null +++ b/libsolidity/analysis/experimental/Analysis.h @@ -0,0 +1,43 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +namespace solidity::frontend +{ +class ASTNode; +} + +namespace solidity::langutil +{ +class ErrorReporter; +} + +namespace solidity::frontend::experimental +{ + +class Analysis +{ +public: + Analysis(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) + {} + bool check(ASTNode const& _ast); +private: + langutil::ErrorReporter& m_errorReporter; +}; + +} diff --git a/libsolidity/codegen/experimental/IRGenerator.cpp b/libsolidity/codegen/experimental/IRGenerator.cpp new file mode 100644 index 000000000..0db02a163 --- /dev/null +++ b/libsolidity/codegen/experimental/IRGenerator.cpp @@ -0,0 +1,160 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; +using namespace solidity::util; + +string IRGenerator::run( + ContractDefinition const& _contract, + bytes const& /*_cborMetadata*/, + map const& /*_otherYulSources*/ +) const +{ + + Whiskers t(R"( + object "" { + code { + codecopy(0, dataoffset(""), datasize("")) + return(0, datasize("")) + } + object "" { + code { + + } + } + } + )"); + t("CreationObject", IRNames::creationObject(_contract)); + t("DeployedObject", IRNames::deployedObject(_contract)); + t("code", generate(_contract)); + + return t.render(); +} + +string IRGenerator::generate(ContractDefinition const& _contract) const +{ + std::stringstream code; + code << "{\n"; + if (_contract.fallbackFunction()) + { + code << IRNames::function(*_contract.fallbackFunction()) << "()\n"; + } + code << "revert(0,0)\n"; + code << "}\n"; + + for (FunctionDefinition const* f: _contract.definedFunctions()) + code << generate(*f); + + return code.str(); +} + +string IRGenerator::generate(FunctionDefinition const& _function) const +{ + std::stringstream code; + code << "function " << IRNames::function(_function) << "() {\n"; + for (auto _statement: _function.body().statements()) + { + if (auto assembly = dynamic_cast(_statement.get())) + code << generate(*assembly) << "\n"; + else + solUnimplemented("Unsupported statement type."); + } + code << "}\n"; + return code.str(); +} + +namespace { + +struct CopyTranslate: public yul::ASTCopier +{ + CopyTranslate( + yul::Dialect const& _dialect, + map _references + ): m_dialect(_dialect), m_references(std::move(_references)) {} + + using ASTCopier::operator(); + + yul::Expression operator()(yul::Identifier const& _identifier) override + { + // The operator() function is only called in lvalue context. In rvalue context, + // only translate(yul::Identifier) is called. + if (m_references.count(&_identifier)) + return translateReference(_identifier); + else + return ASTCopier::operator()(_identifier); + } + + yul::YulString translateIdentifier(yul::YulString _name) override + { + if (m_dialect.builtin(_name)) + return _name; + else + return yul::YulString{"usr$" + _name.str()}; + } + + yul::Identifier translate(yul::Identifier const& _identifier) override + { + if (!m_references.count(&_identifier)) + return ASTCopier::translate(_identifier); + + yul::Expression translated = translateReference(_identifier); + solAssert(holds_alternative(translated)); + return get(std::move(translated)); + } + +private: + + /// Translates a reference to a local variable, potentially including + /// a suffix. Might return a literal, which causes this to be invalid in + /// lvalue-context. + yul::Expression translateReference(yul::Identifier const&) + { + solUnimplemented("External references in inline assembly not implemented."); + } + + yul::Dialect const& m_dialect; + map m_references; +}; + +} + +string IRGenerator::generate(InlineAssembly const& _assembly) const +{ + CopyTranslate bodyCopier{_assembly.dialect(), {}}; + yul::Statement modified = bodyCopier(_assembly.operations()); + solAssert(holds_alternative(modified)); + return yul::AsmPrinter()(std::get(modified)); +} \ No newline at end of file diff --git a/libsolidity/codegen/experimental/IRGenerator.h b/libsolidity/codegen/experimental/IRGenerator.h new file mode 100644 index 000000000..b2d1a7247 --- /dev/null +++ b/libsolidity/codegen/experimental/IRGenerator.h @@ -0,0 +1,73 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace solidity::frontend::experimental +{ + +class SourceUnit; + +class IRGenerator +{ +public: + IRGenerator( + langutil::EVMVersion _evmVersion, + std::optional _eofVersion, + RevertStrings /*_revertStrings*/, + std::map /*_sourceIndices*/, + langutil::DebugInfoSelection const& _debugInfoSelection, + langutil::CharStreamProvider const* _soliditySourceProvider + ): + m_evmVersion(_evmVersion), + m_eofVersion(_eofVersion), + m_debugInfoSelection(_debugInfoSelection), + m_soliditySourceProvider(_soliditySourceProvider) + {} + + std::string run( + ContractDefinition const& _contract, + bytes const& _cborMetadata, + std::map const& _otherYulSources + ) const; + + std::string generate(ContractDefinition const& _contract) const; + std::string generate(FunctionDefinition const& _function) const; + std::string generate(InlineAssembly const& _assembly) const; +private: + langutil::EVMVersion const m_evmVersion; + std::optional const m_eofVersion; + OptimiserSettings const m_optimiserSettings; + langutil::DebugInfoSelection m_debugInfoSelection = {}; + langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr; +}; + +} diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index dd160807b..875b6b34f 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -44,6 +44,8 @@ #include #include +#include + #include #include #include @@ -56,6 +58,8 @@ #include #include +#include + #include #include @@ -326,6 +330,7 @@ void CompilerStack::reset(bool _keepSettings) m_metadataHash = MetadataHash::IPFS; m_stopAfter = State::CompilationSuccessful; } + m_experimentalAnalysis.reset(); m_globalContext.reset(); m_sourceOrder.clear(); m_contracts.clear(); @@ -487,144 +492,13 @@ bool CompilerStack::analyze() if (source->ast && !resolver.resolveNamesAndTypes(*source->ast)) return false; - DeclarationTypeChecker declarationTypeChecker(m_errorReporter, m_evmVersion); - for (Source const* source: m_sourceOrder) - if (source->ast && !declarationTypeChecker.check(*source->ast)) - return false; - + if (!m_sourceOrder.empty() && m_sourceOrder.front()->ast->experimentalSolidity()) { - // Requires DeclarationTypeChecker to have run - DocStringTagParser docStringTagParser(m_errorReporter); - for (Source const* source: m_sourceOrder) - if (source->ast && !docStringTagParser.validateDocStringsUsingTypes(*source->ast)) - noErrors = false; - } - - // Next, we check inheritance, overrides, function collisions and other things at - // contract or function level. - // This also calculates whether a contract is abstract, which is needed by the - // type checker. - ContractLevelChecker contractLevelChecker(m_errorReporter); - - for (Source const* source: m_sourceOrder) - if (auto sourceAst = source->ast) - noErrors = contractLevelChecker.check(*sourceAst); - - // Now we run full type checks that go down to the expression level. This - // cannot be done earlier, because we need cross-contract types and information - // about whether a contract is abstract for the `new` expression. - // This populates the `type` annotation for all expressions. - // - // Note: this does not resolve overloaded functions. In order to do that, types of arguments are needed, - // which is only done one step later. - TypeChecker typeChecker(m_evmVersion, m_errorReporter); - for (Source const* source: m_sourceOrder) - if (source->ast && !typeChecker.checkTypeRequirements(*source->ast)) - noErrors = false; - - if (noErrors) - { - // Requires ContractLevelChecker and TypeChecker - DocStringAnalyser docStringAnalyser(m_errorReporter); - for (Source const* source: m_sourceOrder) - if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast)) - noErrors = false; - } - - if (noErrors) - { - // Checks that can only be done when all types of all AST nodes are known. - PostTypeChecker postTypeChecker(m_errorReporter); - for (Source const* source: m_sourceOrder) - if (source->ast && !postTypeChecker.check(*source->ast)) - noErrors = false; - if (!postTypeChecker.finalize()) + if (!analyzeExperimental()) noErrors = false; } - - // Create & assign callgraphs and check for contract dependency cycles - if (noErrors) - { - createAndAssignCallGraphs(); - annotateInternalFunctionIDs(); - findAndReportCyclicContractDependencies(); - } - - if (noErrors) - for (Source const* source: m_sourceOrder) - if (source->ast && !PostTypeContractLevelChecker{m_errorReporter}.check(*source->ast)) - noErrors = false; - - // Check that immutable variables are never read in c'tors and assigned - // exactly once - if (noErrors) - for (Source const* source: m_sourceOrder) - if (source->ast) - for (ASTPointer const& node: source->ast->nodes()) - if (ContractDefinition* contract = dynamic_cast(node.get())) - ImmutableValidator(m_errorReporter, *contract).analyze(); - - if (noErrors) - { - // Control flow graph generator and analyzer. It can check for issues such as - // variable is used before it is assigned to. - CFG cfg(m_errorReporter); - for (Source const* source: m_sourceOrder) - if (source->ast && !cfg.constructFlow(*source->ast)) - noErrors = false; - - if (noErrors) - { - ControlFlowRevertPruner pruner(cfg); - pruner.run(); - - ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter); - if (!controlFlowAnalyzer.run()) - noErrors = false; - } - } - - if (noErrors) - { - // Checks for common mistakes. Only generates warnings. - StaticAnalyzer staticAnalyzer(m_errorReporter); - for (Source const* source: m_sourceOrder) - if (source->ast && !staticAnalyzer.analyze(*source->ast)) - noErrors = false; - } - - if (noErrors) - { - // Check for state mutability in every function. - vector> ast; - for (Source const* source: m_sourceOrder) - if (source->ast) - ast.push_back(source->ast); - - if (!ViewPureChecker(ast, m_errorReporter).check()) - noErrors = false; - } - - if (noErrors) - { - // Run SMTChecker - - auto allSources = util::applyMap(m_sourceOrder, [](Source const* _source) { return _source->ast; }); - if (ModelChecker::isPragmaPresent(allSources)) - m_modelCheckerSettings.engine = ModelCheckerEngine::All(); - - // m_modelCheckerSettings is spread to engines and solver interfaces, - // so we need to check whether the enabled ones are available before building the classes. - if (m_modelCheckerSettings.engine.any()) - m_modelCheckerSettings.solvers = ModelChecker::checkRequestedSolvers(m_modelCheckerSettings.solvers, m_errorReporter); - - ModelChecker modelChecker(m_errorReporter, *this, m_smtlib2Responses, m_modelCheckerSettings, m_readFile); - modelChecker.checkRequestedSourcesAndContracts(allSources); - for (Source const* source: m_sourceOrder) - if (source->ast) - modelChecker.analyze(*source->ast); - m_unhandledSMTLib2Queries += modelChecker.unhandledQueries(); - } + else if (!analyzeLegacy(noErrors)) + noErrors = false; m_stackState = AnalysisPerformed; } @@ -641,6 +515,163 @@ bool CompilerStack::analyze() return !m_hasError; } +bool CompilerStack::analyzeLegacy(bool _noErrorsSoFar) +{ + bool noErrors = _noErrorsSoFar; + + DeclarationTypeChecker declarationTypeChecker(m_errorReporter, m_evmVersion); + for (Source const* source: m_sourceOrder) + if (source->ast && !declarationTypeChecker.check(*source->ast)) + return false; + + { + // Requires DeclarationTypeChecker to have run + DocStringTagParser docStringTagParser(m_errorReporter); + for (Source const* source: m_sourceOrder) + if (source->ast && !docStringTagParser.validateDocStringsUsingTypes(*source->ast)) + noErrors = false; + } + + // Next, we check inheritance, overrides, function collisions and other things at + // contract or function level. + // This also calculates whether a contract is abstract, which is needed by the + // type checker. + ContractLevelChecker contractLevelChecker(m_errorReporter); + + for (Source const* source: m_sourceOrder) + if (auto sourceAst = source->ast) + noErrors = contractLevelChecker.check(*sourceAst); + + // Now we run full type checks that go down to the expression level. This + // cannot be done earlier, because we need cross-contract types and information + // about whether a contract is abstract for the `new` expression. + // This populates the `type` annotation for all expressions. + // + // Note: this does not resolve overloaded functions. In order to do that, types of arguments are needed, + // which is only done one step later. + TypeChecker typeChecker(m_evmVersion, m_errorReporter); + for (Source const* source: m_sourceOrder) + if (source->ast && !typeChecker.checkTypeRequirements(*source->ast)) + noErrors = false; + + if (noErrors) + { + // Requires ContractLevelChecker and TypeChecker + DocStringAnalyser docStringAnalyser(m_errorReporter); + for (Source const* source: m_sourceOrder) + if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast)) + noErrors = false; + } + + if (noErrors) + { + // Checks that can only be done when all types of all AST nodes are known. + PostTypeChecker postTypeChecker(m_errorReporter); + for (Source const* source: m_sourceOrder) + if (source->ast && !postTypeChecker.check(*source->ast)) + noErrors = false; + if (!postTypeChecker.finalize()) + noErrors = false; + } + + // Create & assign callgraphs and check for contract dependency cycles + if (noErrors) + { + createAndAssignCallGraphs(); + annotateInternalFunctionIDs(); + findAndReportCyclicContractDependencies(); + } + + if (noErrors) + for (Source const* source: m_sourceOrder) + if (source->ast && !PostTypeContractLevelChecker{m_errorReporter}.check(*source->ast)) + noErrors = false; + + // Check that immutable variables are never read in c'tors and assigned + // exactly once + if (noErrors) + for (Source const* source: m_sourceOrder) + if (source->ast) + for (ASTPointer const& node: source->ast->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + ImmutableValidator(m_errorReporter, *contract).analyze(); + + if (noErrors) + { + // Control flow graph generator and analyzer. It can check for issues such as + // variable is used before it is assigned to. + CFG cfg(m_errorReporter); + for (Source const* source: m_sourceOrder) + if (source->ast && !cfg.constructFlow(*source->ast)) + noErrors = false; + + if (noErrors) + { + ControlFlowRevertPruner pruner(cfg); + pruner.run(); + + ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter); + if (!controlFlowAnalyzer.run()) + noErrors = false; + } + } + + if (noErrors) + { + // Checks for common mistakes. Only generates warnings. + StaticAnalyzer staticAnalyzer(m_errorReporter); + for (Source const* source: m_sourceOrder) + if (source->ast && !staticAnalyzer.analyze(*source->ast)) + noErrors = false; + } + + if (noErrors) + { + // Check for state mutability in every function. + vector> ast; + for (Source const* source: m_sourceOrder) + if (source->ast) + ast.push_back(source->ast); + + if (!ViewPureChecker(ast, m_errorReporter).check()) + noErrors = false; + } + + if (noErrors) + { + // Run SMTChecker + + auto allSources = util::applyMap(m_sourceOrder, [](Source const* _source) { return _source->ast; }); + if (ModelChecker::isPragmaPresent(allSources)) + m_modelCheckerSettings.engine = ModelCheckerEngine::All(); + + // m_modelCheckerSettings is spread to engines and solver interfaces, + // so we need to check whether the enabled ones are available before building the classes. + if (m_modelCheckerSettings.engine.any()) + m_modelCheckerSettings.solvers = ModelChecker::checkRequestedSolvers(m_modelCheckerSettings.solvers, m_errorReporter); + + ModelChecker modelChecker(m_errorReporter, *this, m_smtlib2Responses, m_modelCheckerSettings, m_readFile); + modelChecker.checkRequestedSourcesAndContracts(allSources); + for (Source const* source: m_sourceOrder) + if (source->ast) + modelChecker.analyze(*source->ast); + m_unhandledSMTLib2Queries += modelChecker.unhandledQueries(); + } + + return noErrors; +} + +bool CompilerStack::analyzeExperimental() +{ + bool noErrors = true; + m_experimentalAnalysis = make_unique(m_errorReporter); + for (Source const* source: m_sourceOrder) + if (source->ast) + if (!m_experimentalAnalysis->check(*source->ast)) + noErrors = false; + return noErrors; +} + bool CompilerStack::parseAndAnalyze(State _stopAfter) { m_stopAfter = _stopAfter; @@ -708,7 +739,11 @@ bool CompilerStack::compile(State _stopAfter) if (m_viaIR) generateEVMFromIR(*contract); else + { + if (m_experimentalAnalysis) + solThrow(CompilerError, "Legacy codegen after experimental analysis is unsupported."); compileContract(*contract, otherCompilers); + } } } catch (Error const& _error) @@ -1471,19 +1506,38 @@ void CompilerStack::generateIR(ContractDefinition const& _contract) for (auto const& pair: m_contracts) otherYulSources.emplace(pair.second.contract, pair.second.yulIR); - IRGenerator generator( - m_evmVersion, - m_eofVersion, - m_revertStrings, - sourceIndices(), - m_debugInfoSelection, - this - ); - compiledContract.yulIR = generator.run( - _contract, - createCBORMetadata(compiledContract, /* _forIR */ true), - otherYulSources - ); + if (m_experimentalAnalysis) + { + experimental::IRGenerator generator( + m_evmVersion, + m_eofVersion, + m_revertStrings, + sourceIndices(), + m_debugInfoSelection, + this + ); + compiledContract.yulIR = generator.run( + _contract, + {}, // TODO: createCBORMetadata(compiledContract, /* _forIR */ true), + otherYulSources + ); + } + else + { + IRGenerator generator( + m_evmVersion, + m_eofVersion, + m_revertStrings, + sourceIndices(), + m_debugInfoSelection, + this + ); + compiledContract.yulIR = generator.run( + _contract, + createCBORMetadata(compiledContract, /* _forIR */ true), + otherYulSources + ); + } yul::YulStack stack( m_evmVersion, diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 8880770b8..02dac1655 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -81,6 +81,10 @@ class Compiler; class GlobalContext; class Natspec; class DeclarationContainer; +namespace experimental +{ +class Analysis; +} /** * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. @@ -235,6 +239,13 @@ public: /// @returns false on error. bool analyze(); + /// Perform the analysis steps of legacy language mode. + /// @returns false on error. + bool analyzeLegacy(bool _noErrorsSoFar); + /// Perform the analysis steps of experimental language mode. + /// @returns false on error. + bool analyzeExperimental(); + /// Parses and analyzes all source units that were added /// @returns false on error. bool parseAndAnalyze(State _stopAfter = State::CompilationSuccessful); @@ -514,6 +525,7 @@ private: langutil::ErrorList m_errorList; langutil::ErrorReporter m_errorReporter; + std::unique_ptr m_experimentalAnalysis; bool m_metadataLiteralSources = false; MetadataHash m_metadataHash = MetadataHash::IPFS; langutil::DebugInfoSelection m_debugInfoSelection = langutil::DebugInfoSelection::Default();