From 7fd7cc1e765e7e907604dd96fde85be22bde4f80 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 28 May 2019 11:24:54 -0400 Subject: [PATCH] Produce AST even when there are parser errors --- Changelog.md | 3 +- libsolidity/ast/AST.h | 6 +- libsolidity/interface/CompilerStack.cpp | 126 ++++++++++-------- libsolidity/interface/CompilerStack.h | 13 +- libsolidity/interface/StandardCompiler.cpp | 11 +- scripts/check_style.sh | 2 +- solc/CommandLineInterface.cpp | 47 ++++--- .../recovery_ast_constructor/args | 1 + .../cmdlineTests/recovery_ast_constructor/err | 8 ++ .../recovery_ast_constructor/input.sol | 17 +++ .../recovery_ast_constructor/output | 34 +++++ .../recovery_standard_json/input.json | 18 +++ .../recovery_standard_json/output.json | 7 + 13 files changed, 209 insertions(+), 84 deletions(-) create mode 100644 test/cmdlineTests/recovery_ast_constructor/args create mode 100644 test/cmdlineTests/recovery_ast_constructor/err create mode 100644 test/cmdlineTests/recovery_ast_constructor/input.sol create mode 100644 test/cmdlineTests/recovery_ast_constructor/output create mode 100644 test/cmdlineTests/recovery_standard_json/input.json create mode 100644 test/cmdlineTests/recovery_standard_json/output.json diff --git a/Changelog.md b/Changelog.md index 1e47eca2a..bdf79b29d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,6 +14,7 @@ Compiler Features: * Standard JSON Interface: Compile only selected sources and contracts. * Standard JSON Interface: Provide secondary error locations (e.g. the source position of other conflicting declarations). * SMTChecker: Do not erase knowledge about storage pointers if another storage pointer is assigned. + * Standard JSON Interface: Provide AST even on errors if ``--error-recovery`` commandline switch or StandardCompiler `settings.parserErrorRecovery` is true. * Yul Optimizer: Do not inline function if it would result in expressions being duplicated that are not cheap. @@ -39,7 +40,7 @@ Important Bugfixes: Compiler Features: - * Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch. + * Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch or StandardCompiler `settings.parserErrorRecovery` boolean. * Optimizer: Add rule to simplify ``SUB(~0, X)`` to ``NOT(X)``. * Yul Optimizer: Make the optimizer work for all dialects of Yul including eWasm. diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index ffdb2a574..1cd41ac10 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -79,13 +79,15 @@ public: static void listAccept(std::vector const& _list, ASTVisitor& _visitor) { for (T const& element: _list) - element->accept(_visitor); + if (element) + element->accept(_visitor); } template static void listAccept(std::vector const& _list, ASTConstVisitor& _visitor) { for (T const& element: _list) - element->accept(_visitor); + if (element) + element->accept(_visitor); } /// @returns a copy of the vector containing only the nodes which derive from T. diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 4e662464f..3884e13f6 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -117,7 +117,7 @@ boost::optional CompilerStack::parseRemapping(string c void CompilerStack::setRemappings(vector const& _remappings) { - if (m_stackState >= ParsingSuccessful) + if (m_stackState >= ParsingPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set remappings before parsing.")); for (auto const& remapping: _remappings) solAssert(!remapping.prefix.empty(), ""); @@ -126,14 +126,14 @@ void CompilerStack::setRemappings(vector const& _remappings) void CompilerStack::setEVMVersion(langutil::EVMVersion _version) { - if (m_stackState >= ParsingSuccessful) + if (m_stackState >= ParsingPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set EVM version before parsing.")); m_evmVersion = _version; } void CompilerStack::setLibraries(std::map const& _libraries) { - if (m_stackState >= ParsingSuccessful) + if (m_stackState >= ParsingPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set libraries before parsing.")); m_libraries = _libraries; } @@ -147,21 +147,21 @@ void CompilerStack::setOptimiserSettings(bool _optimize, unsigned _runs) void CompilerStack::setOptimiserSettings(OptimiserSettings _settings) { - if (m_stackState >= ParsingSuccessful) + if (m_stackState >= ParsingPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set optimiser settings before parsing.")); m_optimiserSettings = std::move(_settings); } void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources) { - if (m_stackState >= ParsingSuccessful) + if (m_stackState >= ParsingPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set use literal sources before parsing.")); m_metadataLiteralSources = _metadataLiteralSources; } void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _response) { - if (m_stackState >= ParsingSuccessful) + if (m_stackState >= ParsingPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must add SMTLib2 responses before parsing.")); m_smtlib2Responses[_hash] = _response; } @@ -169,6 +169,7 @@ void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _respons void CompilerStack::reset(bool _keepSettings) { m_stackState = Empty; + m_hasError = false; m_sources.clear(); m_smtlib2Responses.clear(); m_unhandledSMTLib2Queries.clear(); @@ -240,24 +241,23 @@ bool CompilerStack::parse() } } } - if (Error::containsOnlyWarnings(m_errorReporter.errors())) - { - m_stackState = ParsingSuccessful; - return true; - } - else - return false; + + m_stackState = ParsingPerformed; + if (!Error::containsOnlyWarnings(m_errorReporter.errors())) + m_hasError = true; + return !m_hasError; } bool CompilerStack::analyze() { - if (m_stackState != ParsingSuccessful || m_stackState >= AnalysisSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call analyze only after parsing was successful.")); + if (m_stackState != ParsingPerformed || m_stackState >= AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call analyze only after parsing was performed.")); resolveImports(); bool noErrors = true; - try { + try + { SyntaxChecker syntaxChecker(m_errorReporter, m_optimiserSettings.runYulOptimiser); for (Source const* source: m_sourceOrder) if (!syntaxChecker.checkSyntax(*source->ast)) @@ -377,25 +377,26 @@ bool CompilerStack::analyze() m_unhandledSMTLib2Queries += modelChecker.unhandledQueries(); } } - catch(FatalError const&) + catch (FatalError const&) { if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. noErrors = false; } - if (noErrors) - { - m_stackState = AnalysisSuccessful; - return true; - } - else - return false; + m_stackState = AnalysisPerformed; + if (!noErrors) + m_hasError = true; + + return !m_hasError; } bool CompilerStack::parseAndAnalyze() { - return parse() && analyze(); + bool success = parse(); + if (success || m_parserErrorRecovery) + success = analyze(); + return success; } bool CompilerStack::isRequestedSource(string const& _sourceName) const @@ -425,10 +426,13 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con bool CompilerStack::compile() { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) if (!parseAndAnalyze()) return false; + if (m_hasError) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors.")); + // Only compile contracts individually which have been requested. map> otherCompilers; for (Source const* source: m_sourceOrder) @@ -459,7 +463,7 @@ void CompilerStack::link() vector CompilerStack::contractNames() const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); vector contractNames; for (auto const& contract: m_contracts) @@ -469,7 +473,7 @@ vector CompilerStack::contractNames() const string const CompilerStack::lastContractName() const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); // try to find some user-supplied contract string contractName; @@ -528,7 +532,7 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); // Look up the contract (by its fully-qualified name) @@ -635,7 +639,7 @@ map CompilerStack::sourceIndices() const Json::Value const& CompilerStack::contractABI(string const& _contractName) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); return contractABI(contract(_contractName)); @@ -643,7 +647,7 @@ Json::Value const& CompilerStack::contractABI(string const& _contractName) const Json::Value const& CompilerStack::contractABI(Contract const& _contract) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -657,7 +661,7 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const Json::Value const& CompilerStack::natspecUser(string const& _contractName) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); return natspecUser(contract(_contractName)); @@ -665,7 +669,7 @@ Json::Value const& CompilerStack::natspecUser(string const& _contractName) const Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -679,7 +683,7 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const Json::Value const& CompilerStack::natspecDev(string const& _contractName) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); return natspecDev(contract(_contractName)); @@ -687,7 +691,7 @@ Json::Value const& CompilerStack::natspecDev(string const& _contractName) const Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -701,7 +705,7 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); Json::Value methodIdentifiers(Json::objectValue); @@ -712,7 +716,7 @@ Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const string const& CompilerStack::metadata(string const& _contractName) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); return metadata(contract(_contractName)); @@ -720,7 +724,7 @@ string const& CompilerStack::metadata(string const& _contractName) const string const& CompilerStack::metadata(Contract const& _contract) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -742,7 +746,9 @@ Scanner const& CompilerStack::scanner(string const& _sourceName) const SourceUnit const& CompilerStack::ast(string const& _sourceName) const { - if (m_stackState < ParsingSuccessful) + if (m_stackState < ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing not yet performed.")); + if (!source(_sourceName).ast && !m_parserErrorRecovery) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); return *source(_sourceName).ast; @@ -750,7 +756,7 @@ SourceUnit const& CompilerStack::ast(string const& _sourceName) const ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); return *contract(_contractName).contract; @@ -814,7 +820,7 @@ string const& CompilerStack::Source::ipfsUrl() const StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath) { - solAssert(m_stackState < ParsingSuccessful, ""); + solAssert(m_stackState < ParsingPerformed, ""); StringMap newSources; for (auto const& node: _ast.nodes()) if (ImportDirective const* import = dynamic_cast(node.get())) @@ -850,7 +856,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string string CompilerStack::applyRemapping(string const& _path, string const& _context) { - solAssert(m_stackState < ParsingSuccessful, ""); + solAssert(m_stackState < ParsingPerformed, ""); // Try to find the longest prefix match in all remappings that are active in the current context. auto isPrefixOf = [](string const& _a, string const& _b) { @@ -892,7 +898,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context void CompilerStack::resolveImports() { - solAssert(m_stackState == ParsingSuccessful, ""); + solAssert(m_stackState == ParsingPerformed, ""); // topological sorting (depth first search) of the import graph, cutting potential cycles vector sourceOrder; @@ -903,15 +909,16 @@ void CompilerStack::resolveImports() if (sourcesSeen.count(_source)) return; sourcesSeen.insert(_source); - for (ASTPointer const& node: _source->ast->nodes()) - if (ImportDirective const* import = dynamic_cast(node.get())) - { - string const& path = import->annotation().absolutePath; - solAssert(!path.empty(), ""); - solAssert(m_sources.count(path), ""); - import->annotation().sourceUnit = m_sources[path].ast.get(); - toposort(&m_sources[path]); - } + if (_source->ast) + for (ASTPointer const& node: _source->ast->nodes()) + if (ImportDirective const* import = dynamic_cast(node.get())) + { + string const& path = import->annotation().absolutePath; + solAssert(!path.empty(), ""); + solAssert(m_sources.count(path), ""); + import->annotation().sourceUnit = m_sources[path].ast.get(); + toposort(&m_sources[path]); + } sourceOrder.push_back(_source); }; @@ -938,7 +945,9 @@ void CompilerStack::compileContract( map>& _otherCompilers ) { - solAssert(m_stackState >= AnalysisSuccessful, ""); + solAssert(m_stackState >= AnalysisPerformed, ""); + if (m_hasError) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors.")); if (_otherCompilers.count(&_contract) || !_contract.canBeDeployed()) return; @@ -990,7 +999,9 @@ void CompilerStack::compileContract( void CompilerStack::generateIR(ContractDefinition const& _contract) { - solAssert(m_stackState >= AnalysisSuccessful, ""); + solAssert(m_stackState >= AnalysisPerformed, ""); + if (m_hasError) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateIR with errors.")); if (!_contract.canBeDeployed()) return; @@ -1008,7 +1019,10 @@ void CompilerStack::generateIR(ContractDefinition const& _contract) void CompilerStack::generateEWasm(ContractDefinition const& _contract) { - solAssert(m_stackState >= AnalysisSuccessful, ""); + solAssert(m_stackState >= AnalysisPerformed, ""); + if (m_hasError) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEWasm with errors.")); + Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); solAssert(!compiledContract.yulIROptimized.empty(), ""); if (!compiledContract.eWasm.empty()) @@ -1038,7 +1052,7 @@ void CompilerStack::generateEWasm(ContractDefinition const& _contract) CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const { - solAssert(m_stackState >= AnalysisSuccessful, ""); + solAssert(m_stackState >= AnalysisPerformed, ""); auto it = m_contracts.find(_contractName); if (it != m_contracts.end()) diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 55df5d8f2..72a81b437 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -78,6 +78,8 @@ class DeclarationContainer; * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. * It holds state and can be used to either step through the compilation stages (and abort e.g. * before compilation to bytecode) or run the whole compilation in one call. + * If error recovery is active, it is possible to progress through the stages even when + * there are errors. In any case, producing code is only possible without errors. */ class CompilerStack: boost::noncopyable { @@ -85,8 +87,8 @@ public: enum State { Empty, SourcesSet, - ParsingSuccessful, - AnalysisSuccessful, + ParsingPerformed, + AnalysisPerformed, CompilationSuccessful }; @@ -110,6 +112,10 @@ public: /// @returns the current state. State state() const { return m_stackState; } + bool hasError() const { return m_hasError; } + + bool compilationSuccessful() const { return m_stackState >= CompilationSuccessful; } + /// Resets the compiler to an empty state. Unless @a _keepSettings is set to true, /// all settings are reset as well. void reset(bool _keepSettings = false); @@ -414,6 +420,9 @@ private: bool m_metadataLiteralSources = false; bool m_parserErrorRecovery = false; State m_stackState = Empty; + /// Whether or not there has been an error during processing. + /// If this is true, the stack will refuse to generate code. + bool m_hasError = false; bool m_release = VersionIsRelease; }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index f61e2af21..ca27deac2 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -844,11 +844,14 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting )); } - bool const analysisSuccess = compilerStack.state() >= CompilerStack::State::AnalysisSuccessful; + bool analysisPerformed = compilerStack.state() >= CompilerStack::State::AnalysisPerformed; bool const compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful; + if (compilerStack.hasError() && !_inputsAndSettings.parserErrorRecovery) + analysisPerformed = false; + /// Inconsistent state - stop here to receive error reports from users - if (((binariesRequested && !compilationSuccess) || !analysisSuccess) && errors.empty()) + if (((binariesRequested && !compilationSuccess) || !analysisPerformed) && errors.empty()) return formatFatalError("InternalCompilerError", "No error reported, but compilation failed."); Json::Value output = Json::objectValue; @@ -864,7 +867,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting output["sources"] = Json::objectValue; unsigned sourceIndex = 0; - for (string const& sourceName: analysisSuccess ? compilerStack.sourceNames() : vector()) + for (string const& sourceName: analysisPerformed ? compilerStack.sourceNames() : vector()) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; @@ -876,7 +879,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting } Json::Value contractsOutput = Json::objectValue; - for (string const& contractName: analysisSuccess ? compilerStack.contractNames() : vector()) + for (string const& contractName: analysisPerformed ? compilerStack.contractNames() : vector()) { size_t colon = contractName.rfind(':'); solAssert(colon != string::npos, ""); diff --git a/scripts/check_style.sh b/scripts/check_style.sh index afecc06bc..0f9a8096e 100755 --- a/scripts/check_style.sh +++ b/scripts/check_style.sh @@ -6,7 +6,7 @@ REPO_ROOT="$(dirname "$0")"/.. cd $REPO_ROOT -WHITESPACE=$(git grep -n -I -E "^.*[[:space:]]+$" | grep -v "test/libsolidity/ASTJSON\|test/compilationTests/zeppelin/LICENSE") +WHITESPACE=$(git grep -n -I -E "^.*[[:space:]]+$" | grep -v "test/libsolidity/ASTJSON\|test/libsolidity/ASTRecoveryTests\|test/compilationTests/zeppelin/LICENSE") if [[ "$WHITESPACE" != "" ]] then diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 5a1287001..34d4e8894 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -943,8 +943,7 @@ bool CommandLineInterface::processInput() m_compiler->setSources(m_sourceCodes); if (m_args.count(g_argLibraries)) m_compiler->setLibraries(m_libraries); - if (m_args.count(g_argErrorRecovery)) - m_compiler->setParserErrorRecovery(true); + m_compiler->setParserErrorRecovery(m_args.count(g_argErrorRecovery)); m_compiler->setEVMVersion(m_evmVersion); // TODO: Perhaps we should not compile unless requested @@ -966,7 +965,12 @@ bool CommandLineInterface::processInput() } if (!successful) - return false; + { + if (m_args.count(g_argErrorRecovery)) + return true; + else + return false; + } } catch (CompilerError const& _exception) { @@ -1044,20 +1048,20 @@ void CommandLineInterface::handleCombinedJSON() contractData[g_strAbi] = dev::jsonCompactPrint(m_compiler->contractABI(contractName)); if (requests.count("metadata")) contractData["metadata"] = m_compiler->metadata(contractName); - if (requests.count(g_strBinary)) + if (requests.count(g_strBinary) && m_compiler->compilationSuccessful()) contractData[g_strBinary] = m_compiler->object(contractName).toHex(); - if (requests.count(g_strBinaryRuntime)) + if (requests.count(g_strBinaryRuntime) && m_compiler->compilationSuccessful()) contractData[g_strBinaryRuntime] = m_compiler->runtimeObject(contractName).toHex(); - if (requests.count(g_strOpcodes)) + if (requests.count(g_strOpcodes) && m_compiler->compilationSuccessful()) contractData[g_strOpcodes] = dev::eth::disassemble(m_compiler->object(contractName).bytecode); - if (requests.count(g_strAsm)) + if (requests.count(g_strAsm) && m_compiler->compilationSuccessful()) contractData[g_strAsm] = m_compiler->assemblyJSON(contractName, m_sourceCodes); - if (requests.count(g_strSrcMap)) + if (requests.count(g_strSrcMap) && m_compiler->compilationSuccessful()) { auto map = m_compiler->sourceMapping(contractName); contractData[g_strSrcMap] = map ? *map : ""; } - if (requests.count(g_strSrcMapRuntime)) + if (requests.count(g_strSrcMapRuntime) && m_compiler->compilationSuccessful()) { auto map = m_compiler->runtimeSourceMapping(contractName); contractData[g_strSrcMapRuntime] = map ? *map : ""; @@ -1121,15 +1125,16 @@ void CommandLineInterface::handleAst(string const& _argStr) asts.push_back(&m_compiler->ast(sourceCode.first)); map gasCosts; for (auto const& contract: m_compiler->contractNames()) - if (auto const* assemblyItems = m_compiler->runtimeAssemblyItems(contract)) - { - auto ret = GasEstimator::breakToStatementLevel( - GasEstimator(m_evmVersion).structuralEstimation(*assemblyItems, asts), - asts - ); - for (auto const& it: ret) - gasCosts[it.first] += it.second; - } + if (m_compiler->compilationSuccessful()) + if (auto const* assemblyItems = m_compiler->runtimeAssemblyItems(contract)) + { + auto ret = GasEstimator::breakToStatementLevel( + GasEstimator(m_evmVersion).structuralEstimation(*assemblyItems, asts), + asts + ); + for (auto const& it: ret) + gasCosts[it.first] += it.second; + } bool legacyFormat = !m_args.count(g_argAstCompactJson); if (m_args.count(g_argOutputDir)) @@ -1397,6 +1402,12 @@ void CommandLineInterface::outputCompilationResults() handleAst(g_argAstJson); handleAst(g_argAstCompactJson); + if (!m_compiler->compilationSuccessful()) + { + serr() << endl << "Compilation halted after AST generation due to errors." << endl; + return; + } + vector contracts = m_compiler->contractNames(); for (string const& contract: contracts) { diff --git a/test/cmdlineTests/recovery_ast_constructor/args b/test/cmdlineTests/recovery_ast_constructor/args new file mode 100644 index 000000000..bd7064049 --- /dev/null +++ b/test/cmdlineTests/recovery_ast_constructor/args @@ -0,0 +1 @@ +--error-recovery --ast --hashes diff --git a/test/cmdlineTests/recovery_ast_constructor/err b/test/cmdlineTests/recovery_ast_constructor/err new file mode 100644 index 000000000..1cdca6323 --- /dev/null +++ b/test/cmdlineTests/recovery_ast_constructor/err @@ -0,0 +1,8 @@ +recovery_ast_constructor/input.sol:5:27: Error: Expected primary expression. + balances[tx.origin] = ; // missing RHS. + ^ +recovery_ast_constructor/input.sol:5:27: Warning: Recovered in Statement at ';'. + balances[tx.origin] = ; // missing RHS. + ^ + +Compilation halted after AST generation due to errors. diff --git a/test/cmdlineTests/recovery_ast_constructor/input.sol b/test/cmdlineTests/recovery_ast_constructor/input.sol new file mode 100644 index 000000000..a6b55ab7f --- /dev/null +++ b/test/cmdlineTests/recovery_ast_constructor/input.sol @@ -0,0 +1,17 @@ +pragma solidity >=0.0.0; + +contract Error1 { + constructor() public { + balances[tx.origin] = ; // missing RHS. + } + + // Without error recovery we stop due to the above error. + // Error recovery however recovers at the above ';' + // There should be an AST for the above, albeit with error + // nodes. + + // This function parses properly and should give AST info. + function five() public view returns(uint) { + return 5; + } +} diff --git a/test/cmdlineTests/recovery_ast_constructor/output b/test/cmdlineTests/recovery_ast_constructor/output new file mode 100644 index 000000000..98a17ceaf --- /dev/null +++ b/test/cmdlineTests/recovery_ast_constructor/output @@ -0,0 +1,34 @@ +Syntax trees: + + +======= recovery_ast_constructor/input.sol ======= +PragmaDirective + Source: "pragma solidity >=0.0.0;" +ContractDefinition "Error1" + Source: "contract Error1 {\n constructor() public {\n balances[tx.origin] = ; // missing RHS.\n }\n\n // Without error recovery we stop due to the above error.\n // Error recovery however recovers at the above ';'\n // There should be an AST for the above, albeit with error\n // nodes.\n\n // This function parses properly and should give AST info.\n function five() public view returns(uint) {\n return 5;\n }\n}" + FunctionDefinition "" - public + Source: "constructor() public {\n balances[tx.origin] = ; // missing RHS.\n }" + ParameterList + Source: "()" + ParameterList + Source: "" + Block + Source: "{\n balances[tx.origin] = ; // missing RHS.\n }" + FunctionDefinition "five" - public - const + Source: "function five() public view returns(uint) {\n return 5;\n }" + ParameterList + Source: "()" + ParameterList + Source: "(uint)" + VariableDeclaration "" + Type: uint256 + Source: "uint" + ElementaryTypeName uint + Source: "uint" + Block + Source: "{\n return 5;\n }" + Return + Source: "return 5" + Literal, token: [no token] value: 5 + Type: int_const 5 + Source: "5" diff --git a/test/cmdlineTests/recovery_standard_json/input.json b/test/cmdlineTests/recovery_standard_json/input.json new file mode 100644 index 000000000..bc0d019d2 --- /dev/null +++ b/test/cmdlineTests/recovery_standard_json/input.json @@ -0,0 +1,18 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract Errort6 { using foo for ; /* missing type name */ }" + } + }, + "settings": + { + "parserErrorRecovery": true, + "outputSelection": + { + "*": { "": ["ast"] } + } + } +} diff --git a/test/cmdlineTests/recovery_standard_json/output.json b/test/cmdlineTests/recovery_standard_json/output.json new file mode 100644 index 000000000..8c98a2cc6 --- /dev/null +++ b/test/cmdlineTests/recovery_standard_json/output.json @@ -0,0 +1,7 @@ +{"errors":[{"component":"general","formattedMessage":"A:1:58: ParserError: Expected type name +pragma solidity >=0.0; contract Errort6 { using foo for ; /* missing type name */ } + ^ +","message":"Expected type name","severity":"error","sourceLocation":{"end":58,"file":"A","start":57},"type":"ParserError"},{"component":"general","formattedMessage":"A:1:84: Warning: Recovered in ContractDefinition at '}'. +pragma solidity >=0.0; contract Errort6 { using foo for ; /* missing type name */ } + ^ +","message":"Recovered in ContractDefinition at '}'.","severity":"warning","sourceLocation":{"end":84,"file":"A","start":83},"type":"Warning"}],"sources":{"A":{"ast":{"absolutePath":"A","exportedSymbols":{"Errort6":[3]},"id":4,"nodeType":"SourceUnit","nodes":[{"id":1,"literals":["solidity",">=","0.0"],"nodeType":"PragmaDirective","src":"0:22:0"},{"baseContracts":[],"contractDependencies":[],"contractKind":"contract","documentation":null,"fullyImplemented":true,"id":3,"linearizedBaseContracts":[3],"name":"Errort6","nodeType":"ContractDefinition","nodes":[],"scope":4,"src":"23:35:0"}],"src":"0:84:0"},"id":0}}}