diff --git a/Changelog.md b/Changelog.md index 6f220005e138..eb2f99fd8746 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Language Features: Compiler Features: + * Parser: Remove the experimental error recovery mode (``--error-recovery`` / ``settings.parserErrorRecovery``). * Yul Optimizer: If ``PUSH0`` is supported, favor zero literals over storing zero values in variables. diff --git a/liblangutil/ParserBase.cpp b/liblangutil/ParserBase.cpp index 12a00d04f121..fde0f27ecc5a 100644 --- a/liblangutil/ParserBase.cpp +++ b/liblangutil/ParserBase.cpp @@ -74,56 +74,10 @@ void ParserBase::expectToken(Token _value, bool _advance) { Token tok = m_scanner->currentToken(); if (tok != _value) - { - std::string const expectedToken = ParserBase::tokenName(_value); - if (m_parserErrorRecovery) - parserError(6635_error, "Expected " + expectedToken + " but got " + tokenName(tok)); - else - fatalParserError(2314_error, "Expected " + expectedToken + " but got " + tokenName(tok)); - // Do not advance so that recovery can sync or make use of the current token. - // This is especially useful if the expected token - // is the only one that is missing and is at the end of a construct. - // "{ ... ; }" is such an example. - // ^ - _advance = false; - } - if (_advance) - advance(); -} - -void ParserBase::expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance) -{ - solAssert(m_inParserRecovery, "The function is supposed to be called during parser recovery only."); - - Token tok = m_scanner->currentToken(); - if (tok != _value) - { - SourceLocation errorLoc = currentLocation(); - int startPosition = errorLoc.start; - while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS) - advance(); - - std::string const expectedToken = ParserBase::tokenName(_value); - if (m_scanner->currentToken() == Token::EOS) - { - // rollback to where the token started, and raise exception to be caught at a higher level. - m_scanner->setPosition(static_cast(startPosition)); - std::string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + " instead."; - fatalParserError(1957_error, errorLoc, msg); - } - else - { - parserWarning(3796_error, "Recovered in " + _currentNodeName + " at " + expectedToken + "."); - m_inParserRecovery = false; - } - } - else - { - std::string expectedToken = ParserBase::tokenName(_value); - parserWarning(3347_error, "Recovered in " + _currentNodeName + " at " + expectedToken + "."); - m_inParserRecovery = false; - } - + fatalParserError( + 2314_error, + "Expected " + ParserBase::tokenName(_value) + " but got " + tokenName(tok) + ); if (_advance) advance(); } diff --git a/liblangutil/ParserBase.h b/liblangutil/ParserBase.h index f72400f6c34e..88fc76a9c307 100644 --- a/liblangutil/ParserBase.h +++ b/liblangutil/ParserBase.h @@ -38,14 +38,9 @@ struct ErrorId; class ParserBase { public: - /// Set @a _parserErrorRecovery to true for additional error - /// recovery. This is experimental and intended for use - /// by front-end tools that need partial AST information even - /// when errors occur. - explicit ParserBase(ErrorReporter& errorReporter, bool _parserErrorRecovery = false): m_errorReporter(errorReporter) - { - m_parserErrorRecovery = _parserErrorRecovery; - } + explicit ParserBase(ErrorReporter& errorReporter): + m_errorReporter(errorReporter) + {} virtual ~ParserBase() = default; @@ -70,13 +65,9 @@ class ParserBase ///@{ ///@name Helper functions /// If current token value is not @a _value, throw exception otherwise advance token - // @a if _advance is true and error recovery is in effect. + // if @a _advance is true void expectToken(Token _value, bool _advance = true); - /// Like expectToken but if there is an error ignores tokens until - /// the expected token or EOS is seen. If EOS is encountered, back up to the error point, - /// and throw an exception so that a higher grammar rule has an opportunity to recover. - void expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance = true); Token currentToken() const; Token peekNextToken() const; std::string tokenName(Token _token); @@ -108,10 +99,6 @@ class ParserBase ErrorReporter& m_errorReporter; /// Current recursion depth during parsing. size_t m_recursionDepth = 0; - /// True if we are in parser error recovery. Usually this means we are scanning for - /// a synchronization token like ';', or '}'. We use this to reduce cascaded error messages. - bool m_inParserRecovery = false; - bool m_parserErrorRecovery = false; }; } diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 2546cfdda6bc..b2dd73cc244c 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -159,14 +159,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) SemVerMatchExpressionParser parser(tokens, literals); SemVerMatchExpression matchExpression = parser.parse(); static SemVerVersion const currentVersion{std::string(VersionString)}; - if (!matchExpression.matches(currentVersion)) - m_errorReporter.syntaxError( - 3997_error, - _pragma.location(), - "Source file requires different compiler version (current compiler is " + - std::string(VersionString) + ") - note that nightly builds are considered to be " - "strictly less than the released version" - ); + solAssert(matchExpression.matches(currentVersion)); m_versionPragmaFound = true; } catch (SemVerError const&) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 9d754202da59..ca23b0d61975 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -87,15 +87,19 @@ class ASTNode static void listAccept(std::vector const& _list, ASTVisitor& _visitor) { for (T const& element: _list) - if (element) - element->accept(_visitor); + { + solAssert(element); + element->accept(_visitor); + } } template static void listAccept(std::vector const& _list, ASTConstVisitor& _visitor) { for (T const& element: _list) - if (element) - element->accept(_visitor); + { + solAssert(element); + element->accept(_visitor); + } } /// @returns a copy of the vector containing only the nodes which derive from T. diff --git a/libsolidity/ast/ASTJsonExporter.cpp b/libsolidity/ast/ASTJsonExporter.cpp index ac847a339432..b1591780952a 100644 --- a/libsolidity/ast/ASTJsonExporter.cpp +++ b/libsolidity/ast/ASTJsonExporter.cpp @@ -560,7 +560,7 @@ bool ASTJsonExporter::visit(EventDefinition const& _node) std::make_pair("parameters", toJson(_node.parameterList())), std::make_pair("anonymous", _node.isAnonymous()) }; - if (m_stackState >= CompilerStack::State::AnalysisPerformed) + if (m_stackState >= CompilerStack::State::AnalysisSuccessful) _attributes.emplace_back( std::make_pair( "eventSelector", @@ -579,7 +579,7 @@ bool ASTJsonExporter::visit(ErrorDefinition const& _node) std::make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), std::make_pair("parameters", toJson(_node.parameterList())) }; - if (m_stackState >= CompilerStack::State::AnalysisPerformed) + if (m_stackState >= CompilerStack::State::AnalysisSuccessful) _attributes.emplace_back(std::make_pair("errorSelector", _node.functionType(true)->externalIdentifierHex())); setJsonNode(_node, "ErrorDefinition", std::move(_attributes)); diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 234ae5d009e3..f646ff014633 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -307,7 +307,6 @@ void CompilerStack::addSMTLib2Response(h256 const& _hash, std::string const& _re void CompilerStack::reset(bool _keepSettings) { m_stackState = Empty; - m_hasError = false; m_sources.clear(); m_smtlib2Responses.clear(); m_unhandledSMTLib2Queries.clear(); @@ -353,7 +352,7 @@ bool CompilerStack::parse() if (SemVerVersion{std::string(VersionString)}.isPrerelease()) m_errorReporter.warning(3805_error, "This is a pre-release compiler version, please do not use it in production."); - Parser parser{m_errorReporter, m_evmVersion, m_parserErrorRecovery}; + Parser parser{m_errorReporter, m_evmVersion}; std::vector sourcesToParse; for (auto const& s: m_sources) @@ -403,16 +402,12 @@ bool CompilerStack::parse() } } - if (m_stopAfter <= Parsed) - m_stackState = Parsed; - else - m_stackState = ParsedAndImported; if (Error::containsErrors(m_errorReporter.errors())) - m_hasError = true; + return false; + m_stackState = (m_stopAfter <= Parsed ? Parsed : ParsedAndImported); storeContractDefinitions(); - - return !m_hasError; + return true; } void CompilerStack::importASTs(std::map const& _sources) @@ -440,8 +435,8 @@ void CompilerStack::importASTs(std::map const& _source bool CompilerStack::analyze() { - if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed) - solThrow(CompilerError, "Must call analyze only after parsing was performed."); + if (m_stackState != ParsedAndImported) + solThrow(CompilerError, "Must call analyze only after parsing was successful."); if (!resolveImports()) return false; @@ -628,11 +623,11 @@ bool CompilerStack::analyze() noErrors = false; } - m_stackState = AnalysisPerformed; if (!noErrors) - m_hasError = true; + return false; - return !m_hasError; + m_stackState = AnalysisSuccessful; + return true; } bool CompilerStack::parseAndAnalyze(State _stopAfter) @@ -642,7 +637,7 @@ bool CompilerStack::parseAndAnalyze(State _stopAfter) bool success = parse(); if (m_stackState >= m_stopAfter) return success; - if (success || m_parserErrorRecovery) + if (success) success = analyze(); return success; } @@ -675,16 +670,13 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con bool CompilerStack::compile(State _stopAfter) { m_stopAfter = _stopAfter; - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) if (!parseAndAnalyze(_stopAfter)) return false; if (m_stackState >= m_stopAfter) return true; - if (m_hasError) - solThrow(CompilerError, "Called compile with errors."); - // Only compile contracts individually which have been requested. std::map> otherCompilers; @@ -763,7 +755,7 @@ std::vector CompilerStack::contractNames() const std::string const CompilerStack::lastContractName(std::optional const& _sourceName) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Parsing was not successful."); // try to find some user-supplied contract std::string contractName; @@ -866,7 +858,7 @@ std::string const* CompilerStack::runtimeSourceMapping(std::string const& _contr std::string const CompilerStack::filesystemFriendlyName(std::string const& _contractName) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "No compiled contracts found."); // Look up the contract (by its fully-qualified name) @@ -980,7 +972,7 @@ std::map CompilerStack::sourceIndices() const Json::Value const& CompilerStack::contractABI(std::string const& _contractName) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); return contractABI(contract(_contractName)); @@ -988,7 +980,7 @@ Json::Value const& CompilerStack::contractABI(std::string const& _contractName) Json::Value const& CompilerStack::contractABI(Contract const& _contract) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); solAssert(_contract.contract, ""); @@ -998,7 +990,7 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const Json::Value const& CompilerStack::storageLayout(std::string const& _contractName) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); return storageLayout(contract(_contractName)); @@ -1006,7 +998,7 @@ Json::Value const& CompilerStack::storageLayout(std::string const& _contractName Json::Value const& CompilerStack::storageLayout(Contract const& _contract) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); solAssert(_contract.contract, ""); @@ -1016,7 +1008,7 @@ Json::Value const& CompilerStack::storageLayout(Contract const& _contract) const Json::Value const& CompilerStack::natspecUser(std::string const& _contractName) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); return natspecUser(contract(_contractName)); @@ -1024,7 +1016,7 @@ Json::Value const& CompilerStack::natspecUser(std::string const& _contractName) Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); solAssert(_contract.contract, ""); @@ -1034,7 +1026,7 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const Json::Value const& CompilerStack::natspecDev(std::string const& _contractName) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); return natspecDev(contract(_contractName)); @@ -1042,7 +1034,7 @@ Json::Value const& CompilerStack::natspecDev(std::string const& _contractName) c Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); solAssert(_contract.contract, ""); @@ -1052,7 +1044,7 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const Json::Value CompilerStack::interfaceSymbols(std::string const& _contractName) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); Json::Value interfaceSymbols(Json::objectValue); @@ -1082,7 +1074,7 @@ Json::Value CompilerStack::interfaceSymbols(std::string const& _contractName) co bytes CompilerStack::cborMetadata(std::string const& _contractName, bool _forIR) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); return createCBORMetadata(contract(_contractName), _forIR); @@ -1090,7 +1082,7 @@ bytes CompilerStack::cborMetadata(std::string const& _contractName, bool _forIR) std::string const& CompilerStack::metadata(Contract const& _contract) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); solAssert(_contract.contract, ""); @@ -1112,7 +1104,7 @@ SourceUnit const& CompilerStack::ast(std::string const& _sourceName) const { if (m_stackState < Parsed) solThrow(CompilerError, "Parsing not yet performed."); - if (!source(_sourceName).ast && !m_parserErrorRecovery) + if (!source(_sourceName).ast) solThrow(CompilerError, "Parsing was not successful."); return *source(_sourceName).ast; @@ -1120,7 +1112,7 @@ SourceUnit const& CompilerStack::ast(std::string const& _sourceName) const ContractDefinition const& CompilerStack::contractDefinition(std::string const& _contractName) const { - if (m_stackState < AnalysisPerformed) + if (m_stackState < AnalysisSuccessful) solThrow(CompilerError, "Analysis was not successful."); return *contract(_contractName).contract; @@ -1219,15 +1211,15 @@ bool CompilerStack::resolveImports() if (sourcesSeen.count(_source)) return; sourcesSeen.insert(_source); - if (_source->ast) - for (ASTPointer const& node: _source->ast->nodes()) - if (ImportDirective const* import = dynamic_cast(node.get())) - { - std::string const& path = *import->annotation().absolutePath; - solAssert(m_sources.count(path), ""); - import->annotation().sourceUnit = m_sources[path].ast.get(); - toposort(&m_sources[path]); - } + solAssert(_source->ast); + for (ASTPointer const& node: _source->ast->nodes()) + if (ImportDirective const* import = dynamic_cast(node.get())) + { + std::string const& path = *import->annotation().absolutePath; + solAssert(m_sources.count(path), ""); + import->annotation().sourceUnit = m_sources[path].ast.get(); + toposort(&m_sources[path]); + } sourceOrder.push_back(_source); }; @@ -1326,8 +1318,7 @@ void CompilerStack::assembleYul( std::shared_ptr _runtimeAssembly ) { - solAssert(m_stackState >= AnalysisPerformed, ""); - solAssert(!m_hasError, ""); + solAssert(m_stackState >= AnalysisSuccessful, ""); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); @@ -1400,9 +1391,7 @@ void CompilerStack::compileContract( { solAssert(!m_viaIR, ""); solUnimplementedAssert(!m_eofVersion.has_value(), "Experimental EOF support is only available for via-IR compilation."); - solAssert(m_stackState >= AnalysisPerformed, ""); - if (m_hasError) - solThrow(CompilerError, "Called compile with errors."); + solAssert(m_stackState >= AnalysisSuccessful, ""); if (_otherCompilers.count(&_contract)) return; @@ -1438,9 +1427,7 @@ void CompilerStack::compileContract( void CompilerStack::generateIR(ContractDefinition const& _contract) { - solAssert(m_stackState >= AnalysisPerformed, ""); - if (m_hasError) - solThrow(CompilerError, "Called generateIR with errors."); + solAssert(m_stackState >= AnalysisSuccessful, ""); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); if (!compiledContract.yulIR.empty()) @@ -1502,9 +1489,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract) void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract) { - solAssert(m_stackState >= AnalysisPerformed, ""); - if (m_hasError) - solThrow(CompilerError, "Called generateEVMFromIR with errors."); + solAssert(m_stackState >= AnalysisSuccessful, ""); if (!_contract.canBeDeployed()) return; @@ -1535,7 +1520,7 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract) CompilerStack::Contract const& CompilerStack::contract(std::string const& _contractName) const { - solAssert(m_stackState >= AnalysisPerformed, ""); + solAssert(m_stackState >= AnalysisSuccessful, ""); auto it = m_contracts.find(_contractName); if (it != m_contracts.end()) diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 8880770b8225..0227896ae063 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -86,8 +86,6 @@ 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: public langutil::CharStreamProvider { @@ -101,7 +99,7 @@ class CompilerStack: public langutil::CharStreamProvider SourcesSet, Parsed, ParsedAndImported, - AnalysisPerformed, + AnalysisSuccessful, CompilationSuccessful }; @@ -137,10 +135,6 @@ class CompilerStack: public langutil::CharStreamProvider /// @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); @@ -164,14 +158,6 @@ class CompilerStack: public langutil::CharStreamProvider /// Sets whether to strip revert strings, add additional strings or do nothing at all. void setRevertStringBehaviour(RevertStrings _revertStrings); - /// Set whether or not parser error is desired. - /// When called without an argument it will revert to the default. - /// Must be set before parsing. - void setParserErrorRecovery(bool _wantErrorRecovery = false) - { - m_parserErrorRecovery = _wantErrorRecovery; - } - /// Sets the pipeline to go through the Yul IR or not. /// Must be set before parsing. void setViaIR(bool _viaIR); @@ -517,12 +503,8 @@ class CompilerStack: public langutil::CharStreamProvider bool m_metadataLiteralSources = false; MetadataHash m_metadataHash = MetadataHash::IPFS; langutil::DebugInfoSelection m_debugInfoSelection = langutil::DebugInfoSelection::Default(); - bool m_parserErrorRecovery = false; State m_stackState = Empty; CompilationSourceType m_compilationSourceType = CompilationSourceType::Solidity; - /// 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; MetadataFormat m_metadataFormat = defaultMetadataFormat(); }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 20cec68ae04b..c46be3b22ac1 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -423,7 +423,7 @@ std::optional checkAuxiliaryInputKeys(Json::Value const& _input) std::optional checkSettingsKeys(Json::Value const& _input) { - static std::set keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; + static std::set keys{"debug", "evmVersion", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; return checkKeys(_input, keys, "settings"); } @@ -773,13 +773,6 @@ std::variant StandardCompiler: ret.stopAfter = CompilerStack::State::Parsed; } - if (settings.isMember("parserErrorRecovery")) - { - if (!settings["parserErrorRecovery"].isBool()) - return formatFatalError(Error::Type::JSONError, "\"settings.parserErrorRecovery\" must be a Boolean."); - ret.parserErrorRecovery = settings["parserErrorRecovery"].asBool(); - } - if (settings.isMember("viaIR")) { if (!settings["viaIR"].isBool()) @@ -1166,7 +1159,6 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); compilerStack.setViaIR(_inputsAndSettings.viaIR); compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); - compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery); compilerStack.setRemappings(std::move(_inputsAndSettings.remappings)); compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); compilerStack.setRevertStringBehaviour(_inputsAndSettings.revertStrings); @@ -1317,21 +1309,18 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting } bool parsingSuccess = compilerStack.state() >= CompilerStack::State::Parsed; - bool analysisPerformed = compilerStack.state() >= CompilerStack::State::AnalysisPerformed; + bool analysisSuccess = compilerStack.state() >= CompilerStack::State::AnalysisSuccessful; bool compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful; - if (compilerStack.hasError() && !_inputsAndSettings.parserErrorRecovery) - analysisPerformed = false; - // If analysis fails, the artifacts inside CompilerStack are potentially incomplete and must not be returned. // Note that not completing analysis due to stopAfter does not count as a failure. It's neither failure nor success. - bool analysisFailed = !analysisPerformed && _inputsAndSettings.stopAfter >= CompilerStack::State::AnalysisPerformed; + bool analysisFailed = !analysisSuccess && _inputsAndSettings.stopAfter >= CompilerStack::State::AnalysisSuccessful; bool compilationFailed = !compilationSuccess && binariesRequested; /// Inconsistent state - stop here to receive error reports from users if ( - (compilationFailed || !analysisPerformed) && - (errors.empty() && _inputsAndSettings.stopAfter >= CompilerStack::State::AnalysisPerformed) + (compilationFailed || analysisFailed || !parsingSuccess) && + errors.empty() ) return formatFatalError(Error::Type::InternalCompilerError, "No error reported, but compilation failed."); @@ -1348,7 +1337,9 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting output["sources"] = Json::objectValue; unsigned sourceIndex = 0; - if (parsingSuccess && !analysisFailed && (!compilerStack.hasError() || _inputsAndSettings.parserErrorRecovery)) + // NOTE: A case that will pass `parsingSuccess && !analysisFailed` but not `analysisSuccess` is + // stopAfter: parsing with no parsing errors. + if (parsingSuccess && !analysisFailed) for (std::string const& sourceName: compilerStack.sourceNames()) { Json::Value sourceResult = Json::objectValue; @@ -1359,7 +1350,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting } Json::Value contractsOutput = Json::objectValue; - for (std::string const& contractName: analysisPerformed ? compilerStack.contractNames() : std::vector()) + for (std::string const& contractName: analysisSuccess ? compilerStack.contractNames() : std::vector()) { size_t colon = contractName.rfind(':'); solAssert(colon != std::string::npos, ""); diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index 588813d76d9b..523c09d94e49 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -72,7 +72,6 @@ class StandardCompiler { std::string language; Json::Value errors; - bool parserErrorRecovery = false; CompilerStack::State stopAfter = CompilerStack::State::CompilationSuccessful; std::map sources; std::map smtLib2Responses; diff --git a/libsolidity/lsp/LanguageServer.cpp b/libsolidity/lsp/LanguageServer.cpp index 81b95e05a168..2b8b62706d50 100644 --- a/libsolidity/lsp/LanguageServer.cpp +++ b/libsolidity/lsp/LanguageServer.cpp @@ -265,7 +265,7 @@ void LanguageServer::compile() m_compilerStack.reset(false); m_compilerStack.setSources(m_fileRepository.sourceUnits()); - m_compilerStack.compile(CompilerStack::State::AnalysisPerformed); + m_compilerStack.compile(CompilerStack::State::AnalysisSuccessful); } void LanguageServer::compileAndUpdateDiagnostics() @@ -554,7 +554,7 @@ ASTNode const* LanguageServer::astNodeAtSourceLocation(std::string const& _sourc std::tuple LanguageServer::astNodeAndOffsetAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos) { - if (m_compilerStack.state() < CompilerStack::AnalysisPerformed) + if (m_compilerStack.state() < CompilerStack::AnalysisSuccessful) return {nullptr, -1}; if (!m_fileRepository.sourceUnits().count(_sourceUnitName)) return {nullptr, -1}; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 127ff402b4a4..1f5611a2f3d9 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -173,16 +173,13 @@ void Parser::parsePragmaVersion(SourceLocation const& _location, std::vector Parser::parseContractDefinition() std::vector> baseContracts; std::vector> subNodes; std::pair contractKind{}; - try - { - documentation = parseStructuredDocumentation(); - contractKind = parseContractKind(); - tie(name, nameLocation) = expectIdentifierWithLocation(); - if (m_scanner->currentToken() == Token::Is) - do - { - advance(); - baseContracts.push_back(parseInheritanceSpecifier()); - } - while (m_scanner->currentToken() == Token::Comma); - expectToken(Token::LBrace); - while (true) + documentation = parseStructuredDocumentation(); + contractKind = parseContractKind(); + std::tie(name, nameLocation) = expectIdentifierWithLocation(); + if (m_scanner->currentToken() == Token::Is) + do { - Token currentTokenValue = m_scanner->currentToken(); - if (currentTokenValue == Token::RBrace) - break; - else if ( - (currentTokenValue == Token::Function && m_scanner->peekNextToken() != Token::LParen) || - currentTokenValue == Token::Constructor || - currentTokenValue == Token::Receive || - currentTokenValue == Token::Fallback - ) - subNodes.push_back(parseFunctionDefinition()); - else if (currentTokenValue == Token::Struct) - subNodes.push_back(parseStructDefinition()); - else if (currentTokenValue == Token::Enum) - subNodes.push_back(parseEnumDefinition()); - else if (currentTokenValue == Token::Type) - subNodes.push_back(parseUserDefinedValueTypeDefinition()); - else if ( - // Workaround because `error` is not a keyword. - currentTokenValue == Token::Identifier && - currentLiteral() == "error" && - m_scanner->peekNextToken() == Token::Identifier && - m_scanner->peekNextNextToken() == Token::LParen - ) - subNodes.push_back(parseErrorDefinition()); - else if (variableDeclarationStart()) - { - VarDeclParserOptions options; - options.kind = VarDeclKind::State; - options.allowInitialValue = true; - subNodes.push_back(parseVariableDeclaration(options)); - expectToken(Token::Semicolon); - } - else if (currentTokenValue == Token::Modifier) - subNodes.push_back(parseModifierDefinition()); - else if (currentTokenValue == Token::Event) - subNodes.push_back(parseEventDefinition()); - else if (currentTokenValue == Token::Using) - subNodes.push_back(parseUsingDirective()); - else - fatalParserError(9182_error, "Function, variable, struct or modifier declaration expected."); + advance(); + baseContracts.push_back(parseInheritanceSpecifier()); } - } - catch (FatalError const&) + while (m_scanner->currentToken() == Token::Comma); + expectToken(Token::LBrace); + while (true) { - if ( - !m_errorReporter.hasErrors() || - !m_parserErrorRecovery || - m_errorReporter.hasExcessiveErrors() + Token currentTokenValue = m_scanner->currentToken(); + if (currentTokenValue == Token::RBrace) + break; + else if ( + (currentTokenValue == Token::Function && m_scanner->peekNextToken() != Token::LParen) || + currentTokenValue == Token::Constructor || + currentTokenValue == Token::Receive || + currentTokenValue == Token::Fallback ) - BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ - m_inParserRecovery = true; + subNodes.push_back(parseFunctionDefinition()); + else if (currentTokenValue == Token::Struct) + subNodes.push_back(parseStructDefinition()); + else if (currentTokenValue == Token::Enum) + subNodes.push_back(parseEnumDefinition()); + else if (currentTokenValue == Token::Type) + subNodes.push_back(parseUserDefinedValueTypeDefinition()); + else if ( + // Workaround because `error` is not a keyword. + currentTokenValue == Token::Identifier && + currentLiteral() == "error" && + m_scanner->peekNextToken() == Token::Identifier && + m_scanner->peekNextNextToken() == Token::LParen + ) + subNodes.push_back(parseErrorDefinition()); + else if (variableDeclarationStart()) + { + VarDeclParserOptions options; + options.kind = VarDeclKind::State; + options.allowInitialValue = true; + subNodes.push_back(parseVariableDeclaration(options)); + expectToken(Token::Semicolon); + } + else if (currentTokenValue == Token::Modifier) + subNodes.push_back(parseModifierDefinition()); + else if (currentTokenValue == Token::Event) + subNodes.push_back(parseEventDefinition()); + else if (currentTokenValue == Token::Using) + subNodes.push_back(parseUsingDirective()); + else + fatalParserError(9182_error, "Function, variable, struct or modifier declaration expected."); } nodeFactory.markEndPosition(); - if (m_inParserRecovery) - expectTokenOrConsumeUntil(Token::RBrace, "ContractDefinition"); - else - expectToken(Token::RBrace); + expectToken(Token::RBrace); return nodeFactory.createNode( name, nameLocation, @@ -1295,26 +1276,10 @@ ASTPointer Parser::parseBlock(bool _allowUnchecked, ASTPointer } expectToken(Token::LBrace); std::vector> statements; - try - { - while (m_scanner->currentToken() != Token::RBrace) - statements.push_back(parseStatement(true)); - nodeFactory.markEndPosition(); - } - catch (FatalError const&) - { - if ( - !m_errorReporter.hasErrors() || - !m_parserErrorRecovery || - m_errorReporter.hasExcessiveErrors() - ) - BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ - m_inParserRecovery = true; - } - if (m_inParserRecovery) - expectTokenOrConsumeUntil(Token::RBrace, "Block"); - else - expectToken(Token::RBrace); + while (m_scanner->currentToken() != Token::RBrace) + statements.push_back(parseStatement(true)); + nodeFactory.markEndPosition(); + expectToken(Token::RBrace); return nodeFactory.createNode(_docString, unchecked, statements); } @@ -1323,86 +1288,70 @@ ASTPointer Parser::parseStatement(bool _allowUnchecked) RecursionGuard recursionGuard(*this); ASTPointer docString; ASTPointer statement; - try + if (m_scanner->currentCommentLiteral() != "") + docString = std::make_shared(m_scanner->currentCommentLiteral()); + switch (m_scanner->currentToken()) { - if (m_scanner->currentCommentLiteral() != "") - docString = std::make_shared(m_scanner->currentCommentLiteral()); - switch (m_scanner->currentToken()) - { - case Token::If: - return parseIfStatement(docString); - case Token::While: - return parseWhileStatement(docString); - case Token::Do: - return parseDoWhileStatement(docString); - case Token::For: - return parseForStatement(docString); - case Token::Unchecked: - case Token::LBrace: - return parseBlock(_allowUnchecked, docString); - case Token::Continue: - statement = ASTNodeFactory(*this).createNode(docString); - advance(); - break; - case Token::Break: - statement = ASTNodeFactory(*this).createNode(docString); - advance(); - break; - case Token::Return: - { - ASTNodeFactory nodeFactory(*this); - ASTPointer expression; - if (advance() != Token::Semicolon) - { - expression = parseExpression(); - nodeFactory.setEndPositionFromNode(expression); - } - statement = nodeFactory.createNode(docString, expression); - break; - } - case Token::Throw: - { - statement = ASTNodeFactory(*this).createNode(docString); - advance(); - break; - } - case Token::Try: - return parseTryStatement(docString); - case Token::Assembly: - return parseInlineAssembly(docString); - case Token::Emit: - statement = parseEmitStatement(docString); - break; - case Token::Identifier: - if (m_scanner->currentLiteral() == "revert" && m_scanner->peekNextToken() == Token::Identifier) - statement = parseRevertStatement(docString); - else if (m_insideModifier && m_scanner->currentLiteral() == "_") + case Token::If: + return parseIfStatement(docString); + case Token::While: + return parseWhileStatement(docString); + case Token::Do: + return parseDoWhileStatement(docString); + case Token::For: + return parseForStatement(docString); + case Token::Unchecked: + case Token::LBrace: + return parseBlock(_allowUnchecked, docString); + case Token::Continue: + statement = ASTNodeFactory(*this).createNode(docString); + advance(); + break; + case Token::Break: + statement = ASTNodeFactory(*this).createNode(docString); + advance(); + break; + case Token::Return: + { + ASTNodeFactory nodeFactory(*this); + ASTPointer expression; + if (advance() != Token::Semicolon) { - statement = ASTNodeFactory(*this).createNode(docString); - advance(); + expression = parseExpression(); + nodeFactory.setEndPositionFromNode(expression); } - else - statement = parseSimpleStatement(docString); - break; - default: - statement = parseSimpleStatement(docString); + statement = nodeFactory.createNode(docString, expression); break; - } } - catch (FatalError const&) + case Token::Throw: { - if ( - !m_errorReporter.hasErrors() || - !m_parserErrorRecovery || - m_errorReporter.hasExcessiveErrors() - ) - BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ - m_inParserRecovery = true; + statement = ASTNodeFactory(*this).createNode(docString); + advance(); + break; } - if (m_inParserRecovery) - expectTokenOrConsumeUntil(Token::Semicolon, "Statement"); - else - expectToken(Token::Semicolon); + case Token::Try: + return parseTryStatement(docString); + case Token::Assembly: + return parseInlineAssembly(docString); + case Token::Emit: + statement = parseEmitStatement(docString); + break; + case Token::Identifier: + if (m_scanner->currentLiteral() == "revert" && m_scanner->peekNextToken() == Token::Identifier) + statement = parseRevertStatement(docString); + else if (m_insideModifier && m_scanner->currentLiteral() == "_") + { + statement = ASTNodeFactory(*this).createNode(docString); + advance(); + } + else + statement = parseSimpleStatement(docString); + break; + default: + statement = parseSimpleStatement(docString); + break; + } + expectToken(Token::Semicolon); return statement; } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 65e8f61f311d..5f2a1213d5ae 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -40,10 +40,9 @@ class Parser: public langutil::ParserBase public: explicit Parser( langutil::ErrorReporter& _errorReporter, - langutil::EVMVersion _evmVersion, - bool _errorRecovery = false + langutil::EVMVersion _evmVersion ): - ParserBase(_errorReporter, _errorRecovery), + ParserBase(_errorReporter), m_evmVersion(_evmVersion) {} diff --git a/libsolutil/JSON.cpp b/libsolutil/JSON.cpp index 27dcf677f299..ea75ada0f79f 100644 --- a/libsolutil/JSON.cpp +++ b/libsolutil/JSON.cpp @@ -144,4 +144,19 @@ bool jsonParseStrict(std::string const& _input, Json::Value& _json, std::string* return parse(readerBuilder, _input, _json, _errs); } +std::optional jsonValueByPath(Json::Value const& _node, std::string_view _jsonPath) +{ + if (!_node.isObject() || _jsonPath.empty()) + return {}; + + std::string memberName = std::string(_jsonPath.substr(0, _jsonPath.find_first_of('.'))); + if (!_node.isMember(memberName)) + return {}; + + if (memberName == _jsonPath) + return _node[memberName]; + + return jsonValueByPath(_node[memberName], _jsonPath.substr(memberName.size() + 1)); +} + } // namespace solidity::util diff --git a/libsolutil/JSON.h b/libsolutil/JSON.h index 905859f6db0c..f368739e1a99 100644 --- a/libsolutil/JSON.h +++ b/libsolutil/JSON.h @@ -26,6 +26,8 @@ #include #include +#include +#include namespace solidity::util { @@ -67,6 +69,13 @@ std::string jsonPrint(Json::Value const& _input, JsonFormat const& _format); /// \return \c true if the document was successfully parsed, \c false if an error occurred. bool jsonParseStrict(std::string const& _input, Json::Value& _json, std::string* _errs = nullptr); +/// Retrieves the value specified by @p _jsonPath by from a series of nested JSON dictionaries. +/// @param _jsonPath A dot-separated series of dictionary keys. +/// @param _node The node representing the start of the path. +/// @returns The value of the last key on the path. @a nullptr if any node on the path descends +/// into something that is not a dictionary or the key is not present. +std::optional jsonValueByPath(Json::Value const& _node, std::string_view _jsonPath); + namespace detail { diff --git a/scripts/check_style.sh b/scripts/check_style.sh index 5288cb9a5c6a..3a268e777b34 100755 --- a/scripts/check_style.sh +++ b/scripts/check_style.sh @@ -41,7 +41,7 @@ REPO_ROOT="$(dirname "$0")"/.. cd "$REPO_ROOT" || exit 1 WHITESPACE=$(git grep -n -I -E "^.*[[:space:]]+$" | - grep -v "test/libsolidity/ASTJSON\|test/libsolidity/ASTRecoveryTests\|test/compilationTests/zeppelin/LICENSE\|${EXCLUDE_FILES_JOINED}" || true + grep -v "test/libsolidity/ASTJSON\|test/compilationTests/zeppelin/LICENSE\|${EXCLUDE_FILES_JOINED}" || true ) if [[ "$WHITESPACE" != "" ]] diff --git a/scripts/error_codes.py b/scripts/error_codes.py index d5b7ed76e279..33a475214b81 100755 --- a/scripts/error_codes.py +++ b/scripts/error_codes.py @@ -171,7 +171,6 @@ def print_ids_per_file(ids, id_to_file_names, top_dir): def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False): test_sub_dirs = [ - path.join("test", "libsolidity", "errorRecoveryTests"), path.join("test", "libsolidity", "smtCheckerTests"), path.join("test", "libsolidity", "syntaxTests"), path.join("test", "libyul", "yulSyntaxTests") diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 509279a71542..5fd15115982d 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -792,10 +792,7 @@ void CommandLineInterface::compile() } } else - { m_compiler->setSources(m_fileReader.sourceUnits()); - m_compiler->setParserErrorRecovery(m_options.input.errorRecovery); - } bool successful = m_compiler->compile(m_options.output.stopAfter); @@ -805,7 +802,7 @@ void CommandLineInterface::compile() formatter.printErrorInformation(*error); } - if (!successful && !m_options.input.errorRecovery) + if (!successful) solThrow(CommandLineExecutionError, ""); } catch (CompilerError const& _exception) @@ -845,52 +842,56 @@ void CommandLineInterface::handleCombinedJSON() output[g_strVersion] = frontend::VersionString; vector contracts = m_compiler->contractNames(); + // NOTE: The state checks here are more strict that in Standard JSON. There we allow + // requesting certain outputs even if compilation fails as long as analysis went ok. + bool compilationSuccess = m_compiler->state() >= CompilerStack::State::CompilationSuccessful; + if (!contracts.empty()) output[g_strContracts] = Json::Value(Json::objectValue); for (string const& contractName: contracts) { Json::Value& contractData = output[g_strContracts][contractName] = Json::objectValue; - if (m_options.compiler.combinedJsonRequests->abi) + if (m_options.compiler.combinedJsonRequests->abi && compilationSuccess) contractData[g_strAbi] = m_compiler->contractABI(contractName); - if (m_options.compiler.combinedJsonRequests->metadata) + if (m_options.compiler.combinedJsonRequests->metadata && compilationSuccess) contractData["metadata"] = m_compiler->metadata(contractName); - if (m_options.compiler.combinedJsonRequests->binary && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->binary && compilationSuccess) contractData[g_strBinary] = m_compiler->object(contractName).toHex(); - if (m_options.compiler.combinedJsonRequests->binaryRuntime && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->binaryRuntime && compilationSuccess) contractData[g_strBinaryRuntime] = m_compiler->runtimeObject(contractName).toHex(); - if (m_options.compiler.combinedJsonRequests->opcodes && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->opcodes && compilationSuccess) contractData[g_strOpcodes] = evmasm::disassemble(m_compiler->object(contractName).bytecode, m_options.output.evmVersion); - if (m_options.compiler.combinedJsonRequests->asm_ && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->asm_ && compilationSuccess) contractData[g_strAsm] = m_compiler->assemblyJSON(contractName); - if (m_options.compiler.combinedJsonRequests->storageLayout && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->storageLayout && compilationSuccess) contractData[g_strStorageLayout] = m_compiler->storageLayout(contractName); - if (m_options.compiler.combinedJsonRequests->generatedSources && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->generatedSources && compilationSuccess) contractData[g_strGeneratedSources] = m_compiler->generatedSources(contractName, false); - if (m_options.compiler.combinedJsonRequests->generatedSourcesRuntime && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->generatedSourcesRuntime && compilationSuccess) contractData[g_strGeneratedSourcesRuntime] = m_compiler->generatedSources(contractName, true); - if (m_options.compiler.combinedJsonRequests->srcMap && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->srcMap && compilationSuccess) { auto map = m_compiler->sourceMapping(contractName); contractData[g_strSrcMap] = map ? *map : ""; } - if (m_options.compiler.combinedJsonRequests->srcMapRuntime && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->srcMapRuntime && compilationSuccess) { auto map = m_compiler->runtimeSourceMapping(contractName); contractData[g_strSrcMapRuntime] = map ? *map : ""; } - if (m_options.compiler.combinedJsonRequests->funDebug && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->funDebug && compilationSuccess) contractData[g_strFunDebug] = StandardCompiler::formatFunctionDebugData( m_compiler->object(contractName).functionDebugData ); - if (m_options.compiler.combinedJsonRequests->funDebugRuntime && m_compiler->compilationSuccessful()) + if (m_options.compiler.combinedJsonRequests->funDebugRuntime && compilationSuccess) contractData[g_strFunDebugRuntime] = StandardCompiler::formatFunctionDebugData( m_compiler->runtimeObject(contractName).functionDebugData ); - if (m_options.compiler.combinedJsonRequests->signatureHashes) + if (m_options.compiler.combinedJsonRequests->signatureHashes && compilationSuccess) contractData[g_strSignatureHashes] = m_compiler->interfaceSymbols(contractName)["methods"]; - if (m_options.compiler.combinedJsonRequests->natspecDev) + if (m_options.compiler.combinedJsonRequests->natspecDev && compilationSuccess) contractData[g_strNatspecDev] = m_compiler->natspecDev(contractName); - if (m_options.compiler.combinedJsonRequests->natspecUser) + if (m_options.compiler.combinedJsonRequests->natspecUser && compilationSuccess) contractData[g_strNatspecUser] = m_compiler->natspecUser(contractName); } @@ -1164,57 +1165,56 @@ void CommandLineInterface::outputCompilationResults() // do we need AST output? handleAst(); - if ( - !m_compiler->compilationSuccessful() && - m_options.output.stopAfter == CompilerStack::State::CompilationSuccessful - ) - { - serr() << endl << "Compilation halted after AST generation due to errors." << endl; - return; - } - - vector contracts = m_compiler->contractNames(); - for (string const& contract: contracts) + CompilerOutputs astOutputSelection; + astOutputSelection.astCompactJson = true; + if (m_options.compiler.outputs != CompilerOutputs() && m_options.compiler.outputs != astOutputSelection) { - if (needsHumanTargetedStdout(m_options)) - sout() << endl << "======= " << contract << " =======" << endl; + // Currently AST is the only output allowed with --stop-after parsing. For all of the others + // we can safely assume that full compilation was performed and successful. + solAssert(m_options.output.stopAfter >= CompilerStack::State::CompilationSuccessful); - // do we need EVM assembly? - if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson) + for (string const& contract: m_compiler->contractNames()) { - string ret; - if (m_options.compiler.outputs.asmJson) - ret = util::jsonPrint(removeNullMembers(m_compiler->assemblyJSON(contract)), m_options.formatting.json); - else - ret = m_compiler->assemblyString(contract, m_fileReader.sourceUnits()); + if (needsHumanTargetedStdout(m_options)) + sout() << endl << "======= " << contract << " =======" << endl; - if (!m_options.output.dir.empty()) - createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret); - else - sout() << "EVM assembly:" << endl << ret << endl; - } + // do we need EVM assembly? + if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson) + { + string ret; + if (m_options.compiler.outputs.asmJson) + ret = util::jsonPrint(removeNullMembers(m_compiler->assemblyJSON(contract)), m_options.formatting.json); + else + ret = m_compiler->assemblyString(contract, m_fileReader.sourceUnits()); + + if (!m_options.output.dir.empty()) + createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret); + else + sout() << "EVM assembly:" << endl << ret << endl; + } - if (m_options.compiler.estimateGas) - handleGasEstimation(contract); - - handleBytecode(contract); - handleIR(contract); - handleIRAst(contract); - handleIROptimized(contract); - handleIROptimizedAst(contract); - handleSignatureHashes(contract); - handleMetadata(contract); - handleABI(contract); - handleStorageLayout(contract); - handleNatspec(true, contract); - handleNatspec(false, contract); - } // end of contracts iteration + if (m_options.compiler.estimateGas) + handleGasEstimation(contract); + + handleBytecode(contract); + handleIR(contract); + handleIRAst(contract); + handleIROptimized(contract); + handleIROptimizedAst(contract); + handleSignatureHashes(contract); + handleMetadata(contract); + handleABI(contract); + handleStorageLayout(contract); + handleNatspec(true, contract); + handleNatspec(false, contract); + } // end of contracts iteration + } if (!m_hasOutput) { if (!m_options.output.dir.empty()) sout() << "Compiler run successful. Artifact(s) can be found in directory " << m_options.output.dir << "." << endl; - else if (contracts.empty()) + else if (m_compiler->contractNames().empty()) sout() << "Compiler run successful. No contracts to compile." << endl; else sout() << "Compiler run successful. No output generated." << endl; diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index 060c1ad8fe0e..8ac4f969c8c9 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -43,7 +43,6 @@ static string const g_strBasePath = "base-path"; static string const g_strIncludePath = "include-path"; static string const g_strAssemble = "assemble"; static string const g_strCombinedJson = "combined-json"; -static string const g_strErrorRecovery = "error-recovery"; static string const g_strEVM = "evm"; static string const g_strEVMVersion = "evm-version"; static string const g_strEOFVersion = "experimental-eof-version"; @@ -226,7 +225,6 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex input.includePaths == _other.input.includePaths && input.allowedDirectories == _other.input.allowedDirectories && input.ignoreMissingFiles == _other.input.ignoreMissingFiles && - input.errorRecovery == _other.input.errorRecovery && output.dir == _other.output.dir && output.overwriteFiles == _other.output.overwriteFiles && output.evmVersion == _other.output.evmVersion && @@ -570,10 +568,6 @@ General Information)").c_str(), g_strIgnoreMissingFiles.c_str(), "Ignore missing files." ) - ( - g_strErrorRecovery.c_str(), - "Enables additional parser error recovery." - ) ; desc.add(inputOptions); @@ -962,7 +956,6 @@ void CommandLineParser::processArgs() map> validOptionInputModeCombinations = { // TODO: This should eventually contain all options. - {g_strErrorRecovery, {InputMode::Compiler, InputMode::CompilerWithASTImport}}, {g_strExperimentalViaIR, {InputMode::Compiler, InputMode::CompilerWithASTImport}}, {g_strViaIR, {InputMode::Compiler, InputMode::CompilerWithASTImport}}, {g_strMetadataLiteral, {InputMode::Compiler, InputMode::CompilerWithASTImport}}, @@ -1369,8 +1362,6 @@ void CommandLineParser::processArgs() m_args.count(g_strModelCheckerTargets) || m_args.count(g_strModelCheckerTimeout); m_options.output.viaIR = (m_args.count(g_strExperimentalViaIR) > 0 || m_args.count(g_strViaIR) > 0); - if (m_options.input.mode == InputMode::Compiler) - m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0); solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport); } diff --git a/solc/CommandLineParser.h b/solc/CommandLineParser.h index 6b391e724096..33cb7bb094ef 100644 --- a/solc/CommandLineParser.h +++ b/solc/CommandLineParser.h @@ -174,7 +174,6 @@ struct CommandLineOptions std::vector includePaths; FileReader::FileSystemPathSet allowedDirectories; bool ignoreMissingFiles = false; - bool errorRecovery = false; } input; struct diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1ea70fe7a1d0..119104aca630 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -105,6 +105,8 @@ set(libsolidity_sources libsolidity/ViewPureChecker.cpp libsolidity/analysis/FunctionCallGraph.cpp libsolidity/interface/FileReader.cpp + libsolidity/ASTPropertyTest.h + libsolidity/ASTPropertyTest.cpp ) detect_stray_source_files("${libsolidity_sources}" "libsolidity/") diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 9e054c109c5d..01e47afe004d 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -70,13 +71,13 @@ Testsuite const g_interactiveTestsuites[] = { {"Yul Syntax", "libyul", "yulSyntaxTests", false, false, &yul::test::SyntaxTest::create}, {"EVM Code Transform", "libyul", "evmCodeTransform", false, false, &yul::test::EVMCodeTransformTest::create, {"nooptions"}}, {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, - {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, {"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create}, {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create}, {"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create}, - {"Memory Guard Tests", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create}, + {"Memory Guard", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create}, + {"AST Properties", "libsolidity", "astPropertyTests", false, false, &ASTPropertyTest::create}, }; } diff --git a/test/cmdlineTests/combined_json_stop_after_parsing/args b/test/cmdlineTests/combined_json_stop_after_parsing/args new file mode 100644 index 000000000000..692bfd79eaeb --- /dev/null +++ b/test/cmdlineTests/combined_json_stop_after_parsing/args @@ -0,0 +1 @@ +--pretty-json --json-indent 4 --stop-after parsing --combined-json abi,asm,ast,bin,bin-runtime,devdoc,function-debug,function-debug-runtime,generated-sources,generated-sources-runtime,hashes,metadata,opcodes,srcmap,srcmap-runtime,storage-layout,userdoc diff --git a/test/cmdlineTests/recovery_ast_empty_contract/input.sol b/test/cmdlineTests/combined_json_stop_after_parsing/input.sol similarity index 51% rename from test/cmdlineTests/recovery_ast_empty_contract/input.sol rename to test/cmdlineTests/combined_json_stop_after_parsing/input.sol index bdc83a2c9013..a3a86cc8d317 100644 --- a/test/cmdlineTests/recovery_ast_empty_contract/input.sol +++ b/test/cmdlineTests/combined_json_stop_after_parsing/input.sol @@ -1,3 +1,4 @@ // SPDX-License-Identifier: GPL-3.0 -pragma 0.5.11; -c \ No newline at end of file +pragma solidity *; + +contract C {} diff --git a/test/cmdlineTests/combined_json_stop_after_parsing/output b/test/cmdlineTests/combined_json_stop_after_parsing/output new file mode 100644 index 000000000000..32cdd019b859 --- /dev/null +++ b/test/cmdlineTests/combined_json_stop_after_parsing/output @@ -0,0 +1,53 @@ +{ + "contracts": + { + "combined_json_stop_after_parsing/input.sol:C": {} + }, + "sourceList": + [ + "combined_json_stop_after_parsing/input.sol" + ], + "sources": + { + "combined_json_stop_after_parsing/input.sol": + { + "AST": + { + "absolutePath": "combined_json_stop_after_parsing/input.sol", + "id": 3, + "license": "GPL-3.0", + "nodeType": "SourceUnit", + "nodes": + [ + { + "id": 1, + "literals": + [ + "solidity", + "*" + ], + "nodeType": "PragmaDirective", + "src": "36:18:0" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "id": 2, + "name": "C", + "nameLocation": "65:1:0", + "nodeType": "ContractDefinition", + "nodes": [], + "src": "56:13:0", + "usedErrors": [], + "usedEvents": [] + } + ], + "src": "36:34:0" + }, + "id": 0 + } + }, + "version": "" +} diff --git a/test/cmdlineTests/recovery_ast_constructor/args b/test/cmdlineTests/recovery_ast_constructor/args deleted file mode 100644 index 8b7c575db012..000000000000 --- a/test/cmdlineTests/recovery_ast_constructor/args +++ /dev/null @@ -1 +0,0 @@ ---error-recovery --ast-compact-json --pretty-json --hashes diff --git a/test/cmdlineTests/recovery_ast_constructor/err b/test/cmdlineTests/recovery_ast_constructor/err deleted file mode 100644 index ecff59f7a73c..000000000000 --- a/test/cmdlineTests/recovery_ast_constructor/err +++ /dev/null @@ -1,14 +0,0 @@ -Error: Expected primary expression. - --> recovery_ast_constructor/input.sol:6:27: - | -6 | balances[tx.origin] = ; // missing RHS. - | ^ - -Warning: Recovered in Statement at ';'. - --> recovery_ast_constructor/input.sol:6:27: - | -6 | 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 deleted file mode 100644 index 8da09638ae18..000000000000 --- a/test/cmdlineTests/recovery_ast_constructor/input.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.0.0; - -contract Error1 { - constructor() { - 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 deleted file mode 100644 index fd92fb7fa630..000000000000 --- a/test/cmdlineTests/recovery_ast_constructor/output +++ /dev/null @@ -1,188 +0,0 @@ -JSON AST (compact format): - - -======= recovery_ast_constructor/input.sol ======= -{ - "absolutePath": "recovery_ast_constructor/input.sol", - "exportedSymbols": - { - "Error1": - [ - 18 - ] - }, - "id": 19, - "license": "GPL-3.0", - "nodeType": "SourceUnit", - "nodes": - [ - { - "id": 1, - "literals": - [ - "solidity", - ">=", - "0.0", - ".0" - ], - "nodeType": "PragmaDirective", - "src": "36:24:0" - }, - { - "abstract": false, - "baseContracts": [], - "canonicalName": "Error1", - "contractDependencies": [], - "contractKind": "contract", - "fullyImplemented": true, - "id": 18, - "linearizedBaseContracts": - [ - 18 - ], - "name": "Error1", - "nameLocation": "71:6:0", - "nodeType": "ContractDefinition", - "nodes": - [ - { - "body": - { - "id": 8, - "nodeType": "Block", - "src": "96:49:0", - "statements": - [ - null - ] - }, - "id": 9, - "implemented": true, - "kind": "constructor", - "modifiers": [], - "name": "", - "nameLocation": "-1:-1:-1", - "nodeType": "FunctionDefinition", - "parameters": - { - "id": 2, - "nodeType": "ParameterList", - "parameters": [], - "src": "93:2:0" - }, - "returnParameters": - { - "id": 3, - "nodeType": "ParameterList", - "parameters": [], - "src": "96:0:0" - }, - "scope": 18, - "src": "82:63:0", - "stateMutability": "nonpayable", - "virtual": false, - "visibility": "public" - }, - { - "body": - { - "id": 16, - "nodeType": "Block", - "src": "440:19:0", - "statements": - [ - { - "expression": - { - "hexValue": "35", - "id": 14, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "453:1:0", - "typeDescriptions": - { - "typeIdentifier": "t_rational_5_by_1", - "typeString": "int_const 5" - }, - "value": "5" - }, - "functionReturnParameters": 13, - "id": 15, - "nodeType": "Return", - "src": "446:8:0" - } - ] - }, - "functionSelector": "af11c34c", - "id": 17, - "implemented": true, - "kind": "function", - "modifiers": [], - "name": "five", - "nameLocation": "407:4:0", - "nodeType": "FunctionDefinition", - "parameters": - { - "id": 10, - "nodeType": "ParameterList", - "parameters": [], - "src": "411:2:0" - }, - "returnParameters": - { - "id": 13, - "nodeType": "ParameterList", - "parameters": - [ - { - "constant": false, - "id": 12, - "mutability": "mutable", - "name": "", - "nameLocation": "-1:-1:-1", - "nodeType": "VariableDeclaration", - "scope": 17, - "src": "434:4:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": - { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": - { - "id": 11, - "name": "uint", - "nodeType": "ElementaryTypeName", - "src": "434:4:0", - "typeDescriptions": - { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "visibility": "internal" - } - ], - "src": "433:6:0" - }, - "scope": 18, - "src": "398:61:0", - "stateMutability": "view", - "virtual": false, - "visibility": "public" - } - ], - "scope": 19, - "src": "62:399:0", - "usedErrors": [], - "usedEvents": [] - } - ], - "src": "36:426:0" -} diff --git a/test/cmdlineTests/recovery_ast_empty_contract/args b/test/cmdlineTests/recovery_ast_empty_contract/args deleted file mode 100644 index 2ad0232ec105..000000000000 --- a/test/cmdlineTests/recovery_ast_empty_contract/args +++ /dev/null @@ -1 +0,0 @@ ---error-recovery diff --git a/test/cmdlineTests/recovery_ast_empty_contract/err b/test/cmdlineTests/recovery_ast_empty_contract/err deleted file mode 100644 index fed05447091e..000000000000 --- a/test/cmdlineTests/recovery_ast_empty_contract/err +++ /dev/null @@ -1,8 +0,0 @@ -Error: Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. - --> recovery_ast_empty_contract/input.sol:3:1: - | -3 | c - | ^ - - -Compilation halted after AST generation due to errors. diff --git a/test/cmdlineTests/recovery_standard_json/input.json b/test/cmdlineTests/recovery_standard_json/input.json deleted file mode 100644 index 9c8b15066877..000000000000 --- a/test/cmdlineTests/recovery_standard_json/input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "language": "Solidity", - "sources": - { - "A": - { - "content": "// SPDX-License-Identifier: GPL-3.0\npragma 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 deleted file mode 100644 index 110b70616907..000000000000 --- a/test/cmdlineTests/recovery_standard_json/output.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "errors": - [ - { - "component": "general", - "errorCode": "3546", - "formattedMessage": "ParserError: Expected type name - --> A:2:58: - | -2 | pragma solidity >=0.0; contract Errort6 { using foo for ; /* missing type name */ } - | ^ - -", - "message": "Expected type name", - "severity": "error", - "sourceLocation": - { - "end": 94, - "file": "A", - "start": 93 - }, - "type": "ParserError" - }, - { - "component": "general", - "errorCode": "3796", - "formattedMessage": "Warning: Recovered in ContractDefinition at '}'. - --> A:2:84: - | -2 | pragma solidity >=0.0; contract Errort6 { using foo for ; /* missing type name */ } - | ^ - -", - "message": "Recovered in ContractDefinition at '}'.", - "severity": "warning", - "sourceLocation": - { - "end": 120, - "file": "A", - "start": 119 - }, - "type": "Warning" - } - ], - "sources": - { - "A": - { - "ast": - { - "absolutePath": "A", - "exportedSymbols": - { - "Errort6": - [ - 3 - ] - }, - "id": 4, - "license": "GPL-3.0", - "nodeType": "SourceUnit", - "nodes": - [ - { - "id": 1, - "literals": - [ - "solidity", - ">=", - "0.0" - ], - "nodeType": "PragmaDirective", - "src": "36:22:0" - }, - { - "abstract": false, - "baseContracts": [], - "canonicalName": "Errort6", - "contractDependencies": [], - "contractKind": "contract", - "fullyImplemented": true, - "id": 3, - "linearizedBaseContracts": - [ - 3 - ], - "name": "Errort6", - "nameLocation": "68:7:0", - "nodeType": "ContractDefinition", - "nodes": [], - "scope": 4, - "src": "59:35:0", - "usedErrors": [], - "usedEvents": [] - } - ], - "src": "36:84:0" - }, - "id": 0 - } - } -} diff --git a/test/libsolidity/ASTJSONTest.cpp b/test/libsolidity/ASTJSONTest.cpp index 6f264fc131ef..8018d71b3619 100644 --- a/test/libsolidity/ASTJSONTest.cpp +++ b/test/libsolidity/ASTJSONTest.cpp @@ -59,7 +59,7 @@ string compilerStateToString(CompilerStack::State _state) case CompilerStack::State::SourcesSet: return "SourcesSet"; case CompilerStack::State::Parsed: return "Parsed"; case CompilerStack::State::ParsedAndImported: return "ParsedAndImported"; - case CompilerStack::State::AnalysisPerformed: return "AnalysisPerformed"; + case CompilerStack::State::AnalysisSuccessful: return "AnalysisSuccessful"; case CompilerStack::State::CompilationSuccessful: return "CompilationSuccessful"; } soltestAssert(false, "Unexpected value of state parameter"); @@ -102,7 +102,7 @@ void ASTJSONTest::generateTestVariants(string const& _filename) const std::vector variantCompileStates = { CompilerStack::State::Parsed, - CompilerStack::State::AnalysisPerformed + CompilerStack::State::AnalysisSuccessful, }; for (const auto state: variantCompileStates) diff --git a/test/libsolidity/ASTPropertyTest.cpp b/test/libsolidity/ASTPropertyTest.cpp new file mode 100644 index 000000000000..194b2c92b259 --- /dev/null +++ b/test/libsolidity/ASTPropertyTest.cpp @@ -0,0 +1,204 @@ +/* + 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 + +#include +#include +#include + +#include + +using namespace solidity::util; +using namespace solidity::langutil; +using namespace solidity::frontend; +using namespace solidity::frontend::test; +using namespace solidity; +using namespace std; + +ASTPropertyTest::ASTPropertyTest(string const& _filename): + TestCase(_filename) +{ + if (!boost::algorithm::ends_with(_filename, ".sol")) + BOOST_THROW_EXCEPTION(runtime_error("Not a Solidity file: \"" + _filename + "\".")); + + m_source = m_reader.source(); + readExpectations(); + soltestAssert(m_tests.size() > 0, "No tests specified in " + _filename); +} + +string ASTPropertyTest::formatExpectations(bool _obtainedResult) +{ + string expectations; + for (string const& testId: m_testOrder) + { + soltestAssert(m_tests.count(testId) > 0); + expectations += + testId + + ": " + + (_obtainedResult ? m_tests[testId].obtainedValue : m_tests[testId].expectedValue) + + "\n"; + } + return expectations; +} + +vector ASTPropertyTest::readKeyValuePairs(string const& _input) +{ + vector result; + for (string line: _input | ranges::views::split('\n') | ranges::to>) + { + boost::trim(line); + if (line.empty()) + continue; + + soltestAssert( + ranges::all_of(line, [](char c) { return isprint(c); }), + "Non-printable character(s) found in property test: " + line + ); + + auto colonPosition = line.find_first_of(':'); + soltestAssert(colonPosition != string::npos, "Property test is missing a colon: " + line); + + StringPair pair{ + boost::trim_copy(line.substr(0, colonPosition)), + boost::trim_copy(line.substr(colonPosition + 1)) + }; + soltestAssert(!get<0>(pair).empty() != false, "Empty key in property test: " + line); + soltestAssert(!get<1>(pair).empty() != false, "Empty value in property test: " + line); + + result.push_back(pair); + } + return result; +} + +void ASTPropertyTest::readExpectations() +{ + for (auto const& [testId, testExpectation]: readKeyValuePairs(m_reader.simpleExpectations())) + { + soltestAssert(m_tests.count(testId) == 0, "More than one expectation for test \"" + testId + "\""); + m_tests.emplace(testId, Test{"", testExpectation, ""}); + m_testOrder.push_back(testId); + } + m_expectation = formatExpectations(false /* _obtainedResult */); +} + +void ASTPropertyTest::extractTestsFromAST(Json::Value const& _astJson) +{ + queue nodesToVisit; + nodesToVisit.push(_astJson); + + while (!nodesToVisit.empty()) + { + Json::Value& node = nodesToVisit.front(); + + if (node.isArray()) + for (auto&& member: node) + nodesToVisit.push(member); + else if (node.isObject()) + for (string const& memberName: node.getMemberNames()) + { + if (memberName != "documentation") + { + nodesToVisit.push(node[memberName]); + continue; + } + + string nodeDocstring = node["documentation"].isObject() ? + node["documentation"]["text"].asString() : + node["documentation"].asString(); + soltestAssert(!nodeDocstring.empty()); + + vector pairs = readKeyValuePairs(nodeDocstring); + if (pairs.empty()) + continue; + + for (auto const& [testId, testedProperty]: pairs) + { + soltestAssert( + m_tests.count(testId) > 0, + "Test \"" + testId + "\" does not have a corresponding expected value." + ); + soltestAssert( + m_tests[testId].property.empty(), + "Test \"" + testId + "\" was already defined before." + ); + m_tests[testId].property = testedProperty; + + soltestAssert(node.isMember("nodeType")); + optional propertyNode = jsonValueByPath(node, testedProperty); + soltestAssert( + propertyNode.has_value(), + node["nodeType"].asString() + " node does not have a property named \""s + testedProperty + "\"" + ); + soltestAssert( + !propertyNode->isObject() && !propertyNode->isArray(), + "Property \"" + testedProperty + "\" is an object or an array." + ); + m_tests[testId].obtainedValue = propertyNode->asString(); + } + } + + nodesToVisit.pop(); + } + + auto firstTestWithoutProperty = ranges::find_if( + m_tests, + [&](auto const& _testCase) { return _testCase.second.property.empty(); } + ); + soltestAssert( + firstTestWithoutProperty == ranges::end(m_tests), + "AST property not defined for test \"" + firstTestWithoutProperty->first + "\"" + ); + + m_obtainedResult = formatExpectations(true /* _obtainedResult */); +} + +TestCase::TestResult ASTPropertyTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + CompilerStack compiler; + + compiler.setSources({{ + "A", + "pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n" + m_source + }}); + compiler.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); + compiler.setOptimiserSettings(solidity::test::CommonOptions::get().optimize); + if (!compiler.parseAndAnalyze()) + BOOST_THROW_EXCEPTION(runtime_error( + "Parsing contract failed" + + SourceReferenceFormatter::formatErrorInformation(compiler.errors(), compiler, _formatted) + )); + + Json::Value astJson = ASTJsonExporter(compiler.state()).toJson(compiler.ast("A")); + soltestAssert(astJson); + + extractTestsFromAST(astJson); + + return checkResult(_stream, _linePrefix, _formatted); +} diff --git a/test/libsolidity/ASTPropertyTest.h b/test/libsolidity/ASTPropertyTest.h new file mode 100644 index 000000000000..e4cec05280b5 --- /dev/null +++ b/test/libsolidity/ASTPropertyTest.h @@ -0,0 +1,66 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include + +#include +#include + +namespace solidity::frontend +{ +class CompilerStack; +} + +namespace solidity::frontend::test +{ + +using StringPair = std::pair; + +class ASTPropertyTest: public TestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::make_unique(_config.filename); + } + ASTPropertyTest(std::string const& _filename); + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + +private: + struct Test + { + std::string property; + std::string expectedValue; + std::string obtainedValue; + }; + + void readExpectations(); + std::vector readKeyValuePairs(std::string const& _input); + void extractTestsFromAST(Json::Value const& _astJson); + std::string formatExpectations(bool _obtainedResult = true); + + std::vector m_testOrder; + std::map m_tests; +}; + +} diff --git a/test/libsolidity/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp index cb462b91d6dc..199afe95af4f 100644 --- a/test/libsolidity/AnalysisFramework.cpp +++ b/test/libsolidity/AnalysisFramework.cpp @@ -47,15 +47,12 @@ AnalysisFramework::parseAnalyseAndReturnError( string const& _source, bool _reportWarnings, bool _insertLicenseAndVersionPragma, - bool _allowMultipleErrors, - bool _allowRecoveryErrors + bool _allowMultipleErrors ) { compiler().reset(); compiler().setSources({{"", _insertLicenseAndVersionPragma ? withPreamble(_source) : _source}}); compiler().setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); - compiler().setParserErrorRecovery(_allowRecoveryErrors); - _allowMultipleErrors = _allowMultipleErrors || _allowRecoveryErrors; if (!compiler().parse()) { BOOST_FAIL("Parsing contract failed in analysis test suite:" + formatErrors(compiler().errors())); diff --git a/test/libsolidity/AnalysisFramework.h b/test/libsolidity/AnalysisFramework.h index d36884390ade..65b6940f6571 100644 --- a/test/libsolidity/AnalysisFramework.h +++ b/test/libsolidity/AnalysisFramework.h @@ -48,8 +48,7 @@ class AnalysisFramework std::string const& _source, bool _reportWarnings = false, bool _insertLicenseAndVersionPragma = true, - bool _allowMultipleErrors = false, - bool _allowRecoveryErrors = false + bool _allowMultipleErrors = false ); virtual ~AnalysisFramework() = default; diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 77c01f1eaf48..14ccbba46de1 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -39,14 +39,13 @@ namespace solidity::frontend::test namespace { -ASTPointer parseText(std::string const& _source, ErrorList& _errors, bool errorRecovery = false) +ASTPointer parseText(std::string const& _source, ErrorList& _errors) { ErrorReporter errorReporter(_errors); auto charStream = CharStream(_source, ""); ASTPointer sourceUnit = Parser( errorReporter, - solidity::test::CommonOptions::get().evmVersion(), - errorRecovery + solidity::test::CommonOptions::get().evmVersion() ).parse(charStream); if (!sourceUnit) return ASTPointer(); @@ -78,12 +77,12 @@ bool successParse(std::string const& _source) return true; } -Error getError(std::string const& _source, bool errorRecovery = false) +Error getError(std::string const& _source) { ErrorList errors; try { - parseText(_source, errors, errorRecovery); + parseText(_source, errors); } catch (FatalError const& /*_exception*/) { diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 785f14284249..7d02e0a9e5fe 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -282,43 +282,6 @@ BOOST_AUTO_TEST_CASE(smoke_test) BOOST_CHECK(containsAtMostWarnings(result)); } -BOOST_AUTO_TEST_CASE(error_recovery_field) -{ - auto input = R"( - { - "language": "Solidity", - "settings": { - "parserErrorRecovery": "1" - }, - "sources": { - "empty": { - "content": "" - } - } - } - )"; - - Json::Value result = compile(input); - BOOST_CHECK(containsError(result, "JSONError", "\"settings.parserErrorRecovery\" must be a Boolean.")); - - input = R"( - { - "language": "Solidity", - "settings": { - "parserErrorRecovery": true - }, - "sources": { - "empty": { - "content": "" - } - } - } - )"; - - result = compile(input); - BOOST_CHECK(containsAtMostWarnings(result)); -} - BOOST_AUTO_TEST_CASE(optimizer_enabled_not_boolean) { char const* input = R"( diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index 0a4793d30a57..e0d7a17e925a 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -38,10 +38,9 @@ using namespace solidity::frontend::test; using namespace boost::unit_test; namespace fs = boost::filesystem; -SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion, bool _parserErrorRecovery): CommonSyntaxTest(_filename, _evmVersion) +SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion): CommonSyntaxTest(_filename, _evmVersion) { m_optimiseYul = m_reader.boolSetting("optimize-yul", true); - m_parserErrorRecovery = _parserErrorRecovery; } void SyntaxTest::setupCompiler() @@ -49,7 +48,6 @@ void SyntaxTest::setupCompiler() compiler().reset(); compiler().setSources(withPreamble(m_sources.sources)); compiler().setEVMVersion(m_evmVersion); - compiler().setParserErrorRecovery(m_parserErrorRecovery); compiler().setOptimiserSettings( m_optimiseYul ? OptimiserSettings::full() : diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index 6b3be8a28a78..f4ca6d9bc955 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -39,13 +39,9 @@ class SyntaxTest: public AnalysisFramework, public solidity::test::CommonSyntaxT public: static std::unique_ptr create(Config const& _config) { - return std::make_unique(_config.filename, _config.evmVersion, false); + return std::make_unique(_config.filename, _config.evmVersion); } - static std::unique_ptr createErrorRecovery(Config const& _config) - { - return std::make_unique(_config.filename, _config.evmVersion, true); - } - SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion, bool _parserErrorRecovery = false); + SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion); protected: virtual void setupCompiler(); @@ -53,7 +49,6 @@ class SyntaxTest: public AnalysisFramework, public solidity::test::CommonSyntaxT virtual void filterObtainedErrors(); bool m_optimiseYul = true; - bool m_parserErrorRecovery = false; }; } diff --git a/test/libsolidity/analysis/FunctionCallGraph.cpp b/test/libsolidity/analysis/FunctionCallGraph.cpp index da17fc433214..9de3441e6cf5 100644 --- a/test/libsolidity/analysis/FunctionCallGraph.cpp +++ b/test/libsolidity/analysis/FunctionCallGraph.cpp @@ -102,7 +102,7 @@ EdgeNames edgeNames(EdgeMap const& _edgeMap) tuple collectGraphs(CompilerStack const& _compilerStack) { - soltestAssert(!_compilerStack.hasError(), ""); + soltestAssert(_compilerStack.state() >= CompilerStack::State::AnalysisSuccessful); tuple graphs; diff --git a/test/libsolidity/astPropertyTests/blank_test_case.sol b/test/libsolidity/astPropertyTests/blank_test_case.sol new file mode 100644 index 000000000000..fc2fa5809212 --- /dev/null +++ b/test/libsolidity/astPropertyTests/blank_test_case.sol @@ -0,0 +1,9 @@ +/// TestCase1: name +contract C { + /// + /// + function f() public pure { } +} +// ---- +// TestCase1: C +// diff --git a/test/libsolidity/astPropertyTests/multiple_nested_properties_per_node.sol b/test/libsolidity/astPropertyTests/multiple_nested_properties_per_node.sol new file mode 100644 index 000000000000..09841828f911 --- /dev/null +++ b/test/libsolidity/astPropertyTests/multiple_nested_properties_per_node.sol @@ -0,0 +1,13 @@ +contract C { + function f() public pure { + /// TestCase1: condition.operator + /// TestCase2: initializationExpression.initialValue.value + /// TestCase3: loopExpression.expression.subExpression.name + for(uint i = 1; i < 42; i++) { + } + } +} +// ---- +// TestCase1: < +// TestCase2: 1 +// TestCase3: i diff --git a/test/libsolidity/astPropertyTests/multiple_properties_per_node.sol b/test/libsolidity/astPropertyTests/multiple_properties_per_node.sol new file mode 100644 index 000000000000..c62aa6ac126a --- /dev/null +++ b/test/libsolidity/astPropertyTests/multiple_properties_per_node.sol @@ -0,0 +1,11 @@ +contract C { + /// TestCase1: name + /// TestCase2: functionSelector + /// TestCase3: visibility + function singleFunction() public pure { + } +} +// ---- +// TestCase1: singleFunction +// TestCase2: 3d33252c +// TestCase3: public diff --git a/test/libsolidity/astPropertyTests/nested_properties.sol b/test/libsolidity/astPropertyTests/nested_properties.sol new file mode 100644 index 000000000000..324fe64c9ac8 --- /dev/null +++ b/test/libsolidity/astPropertyTests/nested_properties.sol @@ -0,0 +1,17 @@ +contract C { + function f() public pure { + /// TestCase1: condition.operator + for(uint i = 0; i < 42; ++i) { + } + /// TestCase2: initializationExpression.initialValue.value + for(uint i = 1; i < 42; i = i * 2) { + } + /// TestCase3: loopExpression.expression.subExpression.name + for(uint i = 0; i < 42; i++) { + } + } +} +// ---- +// TestCase1: < +// TestCase2: 1 +// TestCase3: i diff --git a/test/libsolidity/astPropertyTests/property_expectation_with_colon.sol b/test/libsolidity/astPropertyTests/property_expectation_with_colon.sol new file mode 100644 index 000000000000..7e228a56f428 --- /dev/null +++ b/test/libsolidity/astPropertyTests/property_expectation_with_colon.sol @@ -0,0 +1,9 @@ +/// TestCase1: nameLocation +/// TestCase2: src +contract C { + function f() public pure { + } +} +// ---- +// TestCase1: 115:1:-1 +// TestCase2: 106:51:-1 diff --git a/test/libsolidity/astPropertyTests/simple_properties.sol b/test/libsolidity/astPropertyTests/simple_properties.sol new file mode 100644 index 000000000000..d7485fbecba8 --- /dev/null +++ b/test/libsolidity/astPropertyTests/simple_properties.sol @@ -0,0 +1,12 @@ +/// TestContractC: name +contract C { + /// TestStateVarX: stateVariable + uint x; + /// TestFunctionF: visibility + function f() public pure { + } +} +// ---- +// TestContractC: C +// TestStateVarX: true +// TestFunctionF: public diff --git a/test/libsolidity/errorRecoveryTests/constructor_recovers.sol b/test/libsolidity/errorRecoveryTests/constructor_recovers.sol deleted file mode 100644 index cc03f1e89f1d..000000000000 --- a/test/libsolidity/errorRecoveryTests/constructor_recovers.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity >=0.0.0; - -contract Error1 { - constructor() { - 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; - } -} -// ---- -// ParserError 6933: (88-89): Expected primary expression. -// Warning 3347: (88-89): Recovered in Statement at ';'. diff --git a/test/libsolidity/errorRecoveryTests/contract_recovery.sol b/test/libsolidity/errorRecoveryTests/contract_recovery.sol deleted file mode 100644 index 082d91be6c76..000000000000 --- a/test/libsolidity/errorRecoveryTests/contract_recovery.sol +++ /dev/null @@ -1,6 +0,0 @@ -contract Errort6 { - using foo for ; // missing type name -} -// ---- -// ParserError 3546: (36-37): Expected type name -// Warning 3796: (59-60): Recovered in ContractDefinition at '}'. diff --git a/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol b/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol deleted file mode 100644 index ad228a7e13e9..000000000000 --- a/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity >=0.0.0; - -// Example to show why deleting the token at the -// is bad when error recovery is in effect. Here, ")" is missing -// and there is a ";" instead. That causes us to -// not be able to synchronize to ';'. Advance again and -// '}' is deleted and then we can't synchronize the contract. -// There should be an an AST created this contract (with errors). -contract Error2 { - mapping (address => uint balances; // missing ) before "balances" -} -// ---- -// ParserError 6635: (425-426): Expected ')' but got ';' -// ParserError 6635: (425-426): Expected identifier but got ';' -// ParserError 6635: (458-459): Expected ';' but got '}' diff --git a/test/libsolidity/errorRecoveryTests/error_to_eos.sol b/test/libsolidity/errorRecoveryTests/error_to_eos.sol deleted file mode 100644 index 5d09898601cc..000000000000 --- a/test/libsolidity/errorRecoveryTests/error_to_eos.sol +++ /dev/null @@ -1,23 +0,0 @@ -// Example which where scanning hits EOS, so we reset. -// Here we recover in the contractDefinition. -// There should be an an AST created this contract (with errors). -contract Error2 { - mapping (address => uint balances) // missing ; -} - -// There is no error in this contract -contract SendCoin { - function sendCoin(address receiver, uint amount) public returns(bool sufficient) { - if (balances[msg.sender] < amount) return false; - balances[msg.sender] -= amount; - balances[receiver] += amount; - emit Transfer(msg.sender, receiver, amount); - return true; - } -} -// ---- -// ParserError 6635: (235-236): Expected identifier but got '}' -// ParserError 6635: (276-284): Expected ';' but got 'contract' -// ParserError 9182: (276-284): Function, variable, struct or modifier declaration expected. -// Warning 3796: (572-573): Recovered in ContractDefinition at '}'. -// ParserError 7858: (574-575): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. diff --git a/test/libsolidity/errorRecoveryTests/missing_rhs.sol b/test/libsolidity/errorRecoveryTests/missing_rhs.sol deleted file mode 100644 index aaf7a7509e95..000000000000 --- a/test/libsolidity/errorRecoveryTests/missing_rhs.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity >=0.0.0; - -contract Error3 { - constructor() { - balances[tx.origin] = ; // missing RHS. - } - -} -// ---- -// ParserError 6933: (88-89): Expected primary expression. -// Warning 3347: (88-89): Recovered in Statement at ';'. diff --git a/test/libsolidity/errorRecoveryTests/multiple_errors.sol b/test/libsolidity/errorRecoveryTests/multiple_errors.sol deleted file mode 100644 index c0a0bd32c54c..000000000000 --- a/test/libsolidity/errorRecoveryTests/multiple_errors.sol +++ /dev/null @@ -1,29 +0,0 @@ -// An example with multiple errors. -// Most are caught by inserting an expected token. -// However some us S C Johnson recovery to -// skip over tokens. - -pragma solidity >=0.0.0; - -contract Error4 { - constructor() { - balances[tx.origin] = 1 2; // missing operator - } - - function sendCoin(address receiver, uint amount) public returns(bool sufficient) { - if (balances[msg.sender] < amount) return false; - balances[msg.sender] -= amount // Missing ";" - balances[receiver] += amount // Another missing ";" - emit Transfer(msg.sender // truncated line - return true; - } - - -} -// ---- -// ParserError 6635: (242-243): Expected ';' but got 'Number' -// ParserError 6635: (464-472): Expected ';' but got identifier -// ParserError 6635: (522-526): Expected ';' but got 'emit' -// ParserError 6635: (570-576): Expected ',' but got 'return' -// ParserError 6933: (570-576): Expected primary expression. -// Warning 3796: (581-582): Recovered in Statement at ';'. diff --git a/test/libsolidity/errorRecoveryTests/recovery_failed_eos.sol b/test/libsolidity/errorRecoveryTests/recovery_failed_eos.sol deleted file mode 100644 index 67c4cf7b4c00..000000000000 --- a/test/libsolidity/errorRecoveryTests/recovery_failed_eos.sol +++ /dev/null @@ -1,10 +0,0 @@ -pragma solidity >=0.0.0; - -contract Error7 { - constructor() { - a = -// ---- -// ParserError 6933: (76-76): Expected primary expression. -// ParserError 1957: (76-76): In Statement, ';'is expected; got end of source instead. -// ParserError 1957: (76-76): In Block, '}'is expected; got end of source instead. -// ParserError 1957: (76-76): In ContractDefinition, '}'is expected; got end of source instead. diff --git a/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_1.sol b/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_1.sol deleted file mode 100644 index 9baa5638ecd1..000000000000 --- a/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_1.sol +++ /dev/null @@ -1,3 +0,0 @@ -pragma solidity ^99.99.0; -// ---- -// SyntaxError 3997: (0-25): Source file requires different compiler version (current compiler is .... diff --git a/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_2.sol b/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_2.sol deleted file mode 100644 index 1dcc4bc79ae2..000000000000 --- a/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_2.sol +++ /dev/null @@ -1,6 +0,0 @@ -pragma solidity ^99.99.0; -this is surely invalid -// ---- -// ParserError 6635: (31-33): Expected identifier but got 'is' -// ParserError 6635: (34-40): Expected ';' but got identifier -// ParserError 6635: (49-49): Expected ';' but got end of source diff --git a/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_3.sol b/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_3.sol deleted file mode 100644 index 40fae8116270..000000000000 --- a/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_3.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^99.99.0; -contract C { - uint ; -} -// ---- -// ParserError 6635: (48-49): Expected identifier but got ';' -// ParserError 6635: (50-51): Expected ';' but got '}' diff --git a/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_4.sol b/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_4.sol deleted file mode 100644 index fadf021c7bde..000000000000 --- a/test/libsolidity/errorRecoveryTests/wrong_compiler_recovers_4.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^99.99.0; -contract C { - function f() {} -} -// ---- -// SyntaxError 3997: (0-25): Source file requires different compiler version (current compiler is .... -// SyntaxError 4937: (43-58): No visibility specified. Did you intend to add "public"? diff --git a/test/libsolidity/util/BytesUtils.cpp b/test/libsolidity/util/BytesUtils.cpp index f2a5db7af29a..58d1baacd681 100644 --- a/test/libsolidity/util/BytesUtils.cpp +++ b/test/libsolidity/util/BytesUtils.cpp @@ -26,6 +26,8 @@ #include +#include +#include #include #include #include @@ -253,26 +255,46 @@ std::string BytesUtils::formatFixedPoint(bytes const& _bytes, bool _signed, size string BytesUtils::formatRawBytes( bytes const& _bytes, solidity::frontend::test::ParameterList const& _parameters, - string _linePrefix) + string _linePrefix +) { stringstream os; ParameterList parameters; auto it = _bytes.begin(); if (_bytes.size() != ContractABIUtils::encodingSize(_parameters)) - parameters = ContractABIUtils::defaultParameters((_bytes.size() + 31) / 32); + { + // Interpret all full 32-byte values as integers. + parameters = ContractABIUtils::defaultParameters(_bytes.size() / 32); + + // We'd introduce trailing zero bytes if we interpreted the final bit as an integer. + // We want a right-aligned sequence of bytes instead. + if (_bytes.size() % 32 != 0) + parameters.push_back({ + bytes(), + "", + ABIType{ABIType::HexString, ABIType::AlignRight, _bytes.size() % 32}, + FormatInfo{}, + }); + } else parameters = _parameters; + soltestAssert(ContractABIUtils::encodingSize(parameters) >= _bytes.size()); for (auto const& parameter: parameters) { - bytes byteRange{it, it + static_cast(parameter.abiType.size)}; + long actualSize = min( + distance(it, _bytes.end()), + static_cast(parameter.abiType.size) + ); + bytes byteRange(parameter.abiType.size, 0); + copy(it, it + actualSize, byteRange.begin()); os << _linePrefix << byteRange; if (¶meter != ¶meters.back()) os << endl; - it += static_cast(parameter.abiType.size); + it += actualSize; } return os.str(); @@ -365,14 +387,32 @@ string BytesUtils::formatBytesRange( auto it = _bytes.begin(); if (_bytes.size() != ContractABIUtils::encodingSize(_parameters)) - parameters = ContractABIUtils::defaultParameters((_bytes.size() + 31) / 32); + { + // Interpret all full 32-byte values as integers. + parameters = ContractABIUtils::defaultParameters(_bytes.size() / 32); + + // We'd introduce trailing zero bytes if we interpreted the final bit as an integer. + // We want a right-aligned sequence of bytes instead. + if (_bytes.size() % 32 != 0) + parameters.push_back({ + bytes(), + "", + ABIType{ABIType::HexString, ABIType::AlignRight, _bytes.size() % 32}, + FormatInfo{}, + }); + } else parameters = _parameters; - + soltestAssert(ContractABIUtils::encodingSize(parameters) >= _bytes.size()); for (auto const& parameter: parameters) { - bytes byteRange{it, it + static_cast(parameter.abiType.size)}; + long actualSize = min( + distance(it, _bytes.end()), + static_cast(parameter.abiType.size) + ); + bytes byteRange(parameter.abiType.size, 0); + copy(it, it + actualSize, byteRange.begin()); if (!parameter.matchesBytes(byteRange)) AnsiColorized( @@ -386,7 +426,7 @@ string BytesUtils::formatBytesRange( if (¶meter != ¶meters.back()) os << ", "; - it += static_cast(parameter.abiType.size); + it += actualSize; } return os.str(); diff --git a/test/libyul/SyntaxTest.h b/test/libyul/SyntaxTest.h index 7234e1f2c6bf..27e59ee61439 100644 --- a/test/libyul/SyntaxTest.h +++ b/test/libyul/SyntaxTest.h @@ -33,10 +33,6 @@ class SyntaxTest: public solidity::test::CommonSyntaxTest { return std::make_unique(_config.filename, _config.evmVersion); } - static std::unique_ptr createErrorRecovery(Config const& _config) - { - return std::make_unique(_config.filename, _config.evmVersion); - } SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion); ~SyntaxTest() override {} protected: diff --git a/test/solc/CommandLineParser.cpp b/test/solc/CommandLineParser.cpp index 4a76ce4f1593..43cef9d27ada 100644 --- a/test/solc/CommandLineParser.cpp +++ b/test/solc/CommandLineParser.cpp @@ -116,7 +116,6 @@ BOOST_AUTO_TEST_CASE(cli_mode_options) "--include-path=/home/user/include", "--allow-paths=/tmp,/home,project,../contracts", "--ignore-missing", - "--error-recovery", "--output-dir=/tmp/out", "--overwrite", "--evm-version=spuriousDragon", @@ -177,7 +176,6 @@ BOOST_AUTO_TEST_CASE(cli_mode_options) expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "c", "/usr/lib"}; expectedOptions.input.ignoreMissingFiles = true; - expectedOptions.input.errorRecovery = (inputMode == InputMode::Compiler); expectedOptions.output.dir = "/tmp/out"; expectedOptions.output.overwriteFiles = true; expectedOptions.output.evmVersion = EVMVersion::spuriousDragon(); @@ -405,7 +403,6 @@ BOOST_AUTO_TEST_CASE(invalid_options_input_modes_combinations) { map> invalidOptionInputModeCombinations = { // TODO: This should eventually contain all options. - {"--error-recovery", {"--assemble", "--yul", "--strict-assembly", "--standard-json", "--link"}}, {"--experimental-via-ir", {"--assemble", "--yul", "--strict-assembly", "--standard-json", "--link"}}, {"--via-ir", {"--assemble", "--yul", "--strict-assembly", "--standard-json", "--link"}}, {"--metadata-literal", {"--assemble", "--yul", "--strict-assembly", "--standard-json", "--link"}}, diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index d5a95676d379..2e57718bdada 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(isoltest ../ExecutionFramework.cpp ../libsolidity/ABIJsonTest.cpp ../libsolidity/ASTJSONTest.cpp + ../libsolidity/ASTPropertyTest.cpp ../libsolidity/SMTCheckerTest.cpp ../libyul/Common.cpp ../libyul/ControlFlowGraphTest.cpp