diff --git a/Changelog.md b/Changelog.md index 7276df4d7..f01abd6ab 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,6 +15,7 @@ Compiler Features: * Optimizer: Added optimization rule ``and(shl(X, Y), shl(X, Z)) => shl(X, and(Y, Z))``. * SMTChecker: Support Eldarica as a Horn solver for the CHC engine when using the CLI option ``--model-checker-solvers eld``. The binary `eld` must be available in the system. * SMTChecker: Make ``z3`` the default solver for the BMC and CHC engines instead of all solvers. + * Parser: More detailed error messages about invalid version pragmas. Bugfixes: diff --git a/liblangutil/SemVerHandler.cpp b/liblangutil/SemVerHandler.cpp index ceb562aed..6e85e2609 100644 --- a/liblangutil/SemVerHandler.cpp +++ b/liblangutil/SemVerHandler.cpp @@ -27,10 +27,12 @@ #include #include +#include using namespace std; using namespace solidity; using namespace solidity::langutil; +using namespace solidity::util; SemVerMatchExpressionParser::SemVerMatchExpressionParser(vector _tokens, vector _literals): m_tokens(std::move(_tokens)), m_literals(std::move(_literals)) @@ -52,7 +54,7 @@ SemVerVersion::SemVerVersion(string const& _versionString) if (level < 2) { if (i == end || *i != '.') - BOOST_THROW_EXCEPTION(SemVerError()); + solThrow(SemVerError, "Invalid versionString: "s + _versionString); else ++i; } @@ -70,7 +72,7 @@ SemVerVersion::SemVerVersion(string const& _versionString) build = string(buildStart, i); } if (i != end) - BOOST_THROW_EXCEPTION(SemVerError()); + solThrow(SemVerError, "Invalid versionString "s + _versionString); } bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _version) const @@ -156,12 +158,12 @@ bool SemVerMatchExpression::matches(SemVerVersion const& _version) const return false; } -optional SemVerMatchExpressionParser::parse() +SemVerMatchExpression SemVerMatchExpressionParser::parse() { reset(); if (m_tokens.empty()) - return nullopt; + solThrow(SemVerError, "Empty version pragma."); try { @@ -171,14 +173,19 @@ optional SemVerMatchExpressionParser::parse() if (m_pos >= m_tokens.size()) break; if (currentToken() != Token::Or) - BOOST_THROW_EXCEPTION(SemVerError()); + { + solThrow( + SemVerError, + "You can only combine version ranges using the || operator." + ); + } nextToken(); } } - catch (SemVerError const&) + catch (SemVerError const& e) { reset(); - return nullopt; + throw e; } return m_expression; @@ -265,14 +272,22 @@ unsigned SemVerMatchExpressionParser::parseVersionPart() { c = currentChar(); if (v * 10 < v || v * 10 + static_cast(c - '0') < v * 10) - BOOST_THROW_EXCEPTION(SemVerError()); + solThrow(SemVerError, "Integer too large to be used in a version number."); v = v * 10 + static_cast(c - '0'); nextChar(); } return v; } + else if (c == char(-1)) + solThrow(SemVerError, "Expected version number but reached end of pragma."); else - BOOST_THROW_EXCEPTION(SemVerError()); + solThrow( + SemVerError, fmt::format( + "Expected the start of a version number but instead found character '{}'. " + "Version number is invalid or the pragma is not terminated with a semicolon.", + c + ) + ); } char SemVerMatchExpressionParser::currentChar() const diff --git a/liblangutil/SemVerHandler.h b/liblangutil/SemVerHandler.h index bce4d736b..cf344502e 100644 --- a/liblangutil/SemVerHandler.h +++ b/liblangutil/SemVerHandler.h @@ -25,17 +25,18 @@ #include #include +#include #include -#include #include #include namespace solidity::langutil { -class SemVerError: public util::Exception +struct SemVerError: public util::Exception { + }; #undef major @@ -87,8 +88,8 @@ class SemVerMatchExpressionParser public: SemVerMatchExpressionParser(std::vector _tokens, std::vector _literals); - /// Returns an expression if it was parseable, or nothing otherwise. - std::optional parse(); + /// Returns an expression if it was parsable, or throws a SemVerError otherwise. + SemVerMatchExpression parse(); private: void reset(); diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index bdf138e20..167242b2c 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -153,22 +153,28 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) } else if (_pragma.literals()[0] == "solidity") { - vector tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end()); - vector literals(_pragma.literals().begin() + 1, _pragma.literals().end()); - SemVerMatchExpressionParser parser(tokens, literals); - auto matchExpression = parser.parse(); - // An unparsable version pragma is an unrecoverable fatal error in the parser. - solAssert(matchExpression.has_value(), ""); - static SemVerVersion const currentVersion{string(VersionString)}; - if (!matchExpression->matches(currentVersion)) - m_errorReporter.syntaxError( - 3997_error, - _pragma.location(), - "Source file requires different compiler version (current compiler is " + - string(VersionString) + ") - note that nightly builds are considered to be " - "strictly less than the released version" - ); - m_versionPragmaFound = true; + try + { + vector tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end()); + vector literals(_pragma.literals().begin() + 1, _pragma.literals().end()); + SemVerMatchExpressionParser parser(tokens, literals); + SemVerMatchExpression matchExpression = parser.parse(); + static SemVerVersion const currentVersion{string(VersionString)}; + if (!matchExpression.matches(currentVersion)) + m_errorReporter.syntaxError( + 3997_error, + _pragma.location(), + "Source file requires different compiler version (current compiler is " + + string(VersionString) + ") - note that nightly builds are considered to be " + "strictly less than the released version" + ); + m_versionPragmaFound = true; + } + catch (SemVerError const&) + { + // An unparsable version pragma is an unrecoverable fatal error in the parser. + solAssert(false); + } } else m_errorReporter.syntaxError(4936_error, _pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index afa70609d..8075e63e4 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -160,27 +160,31 @@ ASTPointer Parser::parse(CharStream& _charStream) void Parser::parsePragmaVersion(SourceLocation const& _location, vector const& _tokens, vector const& _literals) { SemVerMatchExpressionParser parser(_tokens, _literals); - auto matchExpression = parser.parse(); - if (!matchExpression.has_value()) + try + { + SemVerMatchExpression matchExpression = parser.parse(); + static SemVerVersion const currentVersion{string(VersionString)}; + // FIXME: only match for major version incompatibility + if (!matchExpression.matches(currentVersion)) + // If m_parserErrorRecovery is true, the same message will appear from SyntaxChecker::visit(), + // so we don't need to report anything here. + if (!m_parserErrorRecovery) + m_errorReporter.fatalParserError( + 5333_error, + _location, + "Source file requires different compiler version (current compiler is " + + string(VersionString) + ") - note that nightly builds are considered to be " + "strictly less than the released version" + ); + } + catch (SemVerError const& matchError) + { m_errorReporter.fatalParserError( 1684_error, _location, - "Found version pragma, but failed to parse it. " - "Please ensure there is a trailing semicolon." + "Invalid version pragma. "s + matchError.what() ); - static SemVerVersion const currentVersion{string(VersionString)}; - // FIXME: only match for major version incompatibility - if (!matchExpression->matches(currentVersion)) - // If m_parserErrorRecovery is true, the same message will appear from SyntaxChecker::visit(), - // so we don't need to report anything here. - if (!m_parserErrorRecovery) - m_errorReporter.fatalParserError( - 5333_error, - _location, - "Source file requires different compiler version (current compiler is " + - string(VersionString) + ") - note that nightly builds are considered to be " - "strictly less than the released version" - ); + } } ASTPointer Parser::parseStructuredDocumentation() diff --git a/test/libsolidity/SemVerMatcher.cpp b/test/libsolidity/SemVerMatcher.cpp index a69b2a39d..e1ad89445 100644 --- a/test/libsolidity/SemVerMatcher.cpp +++ b/test/libsolidity/SemVerMatcher.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -58,17 +59,41 @@ SemVerMatchExpression parseExpression(string const& _input) scanner.next(); } - auto expression = SemVerMatchExpressionParser(tokens, literals).parse(); - BOOST_REQUIRE(expression.has_value()); - BOOST_CHECK_MESSAGE( - expression->isValid(), - "Expression \"" + _input + "\" did not parse properly." - ); - return *expression; + try + { + auto matchExpression = SemVerMatchExpressionParser(tokens, literals).parse(); + + BOOST_CHECK_MESSAGE( + matchExpression.isValid(), + "Expression \"" + _input + "\" did not parse properly." + ); + + return matchExpression; + } + catch (SemVerError const&) + { + // Ignored, since a test case should have a parsable version + soltestAssert(false); + } } } +BOOST_AUTO_TEST_CASE(exception_on_invalid_version_in_semverversion_constructor) +{ + BOOST_CHECK_EXCEPTION( + SemVerVersion version("1.2"), + SemVerError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == "Invalid versionString: 1.2"); return true; } + ); + + BOOST_CHECK_EXCEPTION( + SemVerVersion version("-1.2.0"), + SemVerError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == "Invalid versionString: -1.2.0"); return true; } + ); +} + BOOST_AUTO_TEST_CASE(positive_range) { // Positive range tests @@ -159,9 +184,9 @@ BOOST_AUTO_TEST_CASE(positive_range) for (auto const& t: tests) { SemVerVersion version(t.second); - SemVerMatchExpression expression = parseExpression(t.first); + SemVerMatchExpression matchExpression = parseExpression(t.first); BOOST_CHECK_MESSAGE( - expression.matches(version), + matchExpression.matches(version), "Version \"" + t.second + "\" did not satisfy expression \"" + t.first + "\"" ); } @@ -235,9 +260,9 @@ BOOST_AUTO_TEST_CASE(negative_range) for (auto const& t: tests) { SemVerVersion version(t.second); - SemVerMatchExpression expression = parseExpression(t.first); + auto matchExpression = parseExpression(t.first); BOOST_CHECK_MESSAGE( - !expression.matches(version), + !matchExpression.matches(version), "Version \"" + t.second + "\" did satisfy expression \"" + t.first + "\" " + "(although it should not)" ); diff --git a/test/libsolidity/syntaxTests/pragma/blank_space_version.sol b/test/libsolidity/syntaxTests/pragma/blank_space_version.sol new file mode 100644 index 000000000..d4c6eadcf --- /dev/null +++ b/test/libsolidity/syntaxTests/pragma/blank_space_version.sol @@ -0,0 +1,3 @@ +pragma solidity ; +// ---- +// ParserError 1684: (0-17): Invalid version pragma. Empty version pragma. diff --git a/test/libsolidity/syntaxTests/pragma/broken_version_2.sol b/test/libsolidity/syntaxTests/pragma/broken_version_2.sol index 903ac4cd3..67c37fe64 100644 --- a/test/libsolidity/syntaxTests/pragma/broken_version_2.sol +++ b/test/libsolidity/syntaxTests/pragma/broken_version_2.sol @@ -1,3 +1,3 @@ pragma solidity pragma; // ---- -// ParserError 1684: (0-23): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon. +// ParserError 1684: (0-23): Invalid version pragma. Expected the start of a version number but instead found character 'p'. Version number is invalid or the pragma is not terminated with a semicolon. diff --git a/test/libsolidity/syntaxTests/pragma/broken_version_4.sol b/test/libsolidity/syntaxTests/pragma/broken_version_4.sol index 76560fac6..ab9faa173 100644 --- a/test/libsolidity/syntaxTests/pragma/broken_version_4.sol +++ b/test/libsolidity/syntaxTests/pragma/broken_version_4.sol @@ -1,3 +1,3 @@ pragma solidity (8.0.0); // ---- -// ParserError 1684: (0-24): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon. +// ParserError 1684: (0-24): Invalid version pragma. Expected the start of a version number but instead found character '('. Version number is invalid or the pragma is not terminated with a semicolon. diff --git a/test/libsolidity/syntaxTests/pragma/broken_version_5.sol b/test/libsolidity/syntaxTests/pragma/broken_version_5.sol index 3c284be74..d858fdfa5 100644 --- a/test/libsolidity/syntaxTests/pragma/broken_version_5.sol +++ b/test/libsolidity/syntaxTests/pragma/broken_version_5.sol @@ -1,3 +1,3 @@ pragma solidity 88_; // ---- -// ParserError 1684: (0-20): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon. +// ParserError 1684: (0-20): Invalid version pragma. Expected the start of a version number but instead found character '_'. Version number is invalid or the pragma is not terminated with a semicolon. diff --git a/test/libsolidity/syntaxTests/pragma/broken_version_6.sol b/test/libsolidity/syntaxTests/pragma/broken_version_6.sol index ab98e4f2a..ee47523a3 100644 --- a/test/libsolidity/syntaxTests/pragma/broken_version_6.sol +++ b/test/libsolidity/syntaxTests/pragma/broken_version_6.sol @@ -1,3 +1,3 @@ pragma solidity v1.2.3; // ---- -// ParserError 1684: (0-23): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon. +// ParserError 1684: (0-23): Invalid version pragma. Expected the start of a version number but instead found character 'v'. Version number is invalid or the pragma is not terminated with a semicolon. diff --git a/test/libsolidity/syntaxTests/pragma/broken_version_7.sol b/test/libsolidity/syntaxTests/pragma/broken_version_7.sol index b4b6fea7a..88fad9c99 100644 --- a/test/libsolidity/syntaxTests/pragma/broken_version_7.sol +++ b/test/libsolidity/syntaxTests/pragma/broken_version_7.sol @@ -1,3 +1,3 @@ pragma solidity >0.5.0<; // ---- -// ParserError 1684: (0-24): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon. +// ParserError 1684: (0-24): Invalid version pragma. Expected version number but reached end of pragma. diff --git a/test/libsolidity/syntaxTests/pragma/empty_version.sol b/test/libsolidity/syntaxTests/pragma/empty_version.sol index cb8cc47d3..b30428386 100644 --- a/test/libsolidity/syntaxTests/pragma/empty_version.sol +++ b/test/libsolidity/syntaxTests/pragma/empty_version.sol @@ -1,3 +1,3 @@ pragma solidity; // ---- -// ParserError 1684: (0-16): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon. +// ParserError 1684: (0-16): Invalid version pragma. Empty version pragma. diff --git a/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_range_first.sol b/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_range_first.sol new file mode 100644 index 000000000..235549c4c --- /dev/null +++ b/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_range_first.sol @@ -0,0 +1,3 @@ +pragma solidity 0.4.1 - 0.6.2 0.8.17; +// ---- +// ParserError 1684: (0-37): Invalid version pragma. You can only combine version ranges using the || operator. diff --git a/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_range_second.sol b/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_range_second.sol new file mode 100644 index 000000000..00c6aff60 --- /dev/null +++ b/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_range_second.sol @@ -0,0 +1,3 @@ +pragma solidity 0.8.17 0.4.1 - 0.6.2; +// ---- +// ParserError 1684: (0-37): Invalid version pragma. Expected the start of a version number but instead found character '-'. Version number is invalid or the pragma is not terminated with a semicolon. diff --git a/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_two_ranges.sol b/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_two_ranges.sol new file mode 100644 index 000000000..a5cb07a18 --- /dev/null +++ b/test/libsolidity/syntaxTests/pragma/invalid_range_conjunction_two_ranges.sol @@ -0,0 +1,3 @@ +pragma solidity 0.4.0 - 0.4.1 0.4.1 - 0.6.2; +// ---- +// ParserError 1684: (0-44): Invalid version pragma. You can only combine version ranges using the || operator. diff --git a/test/libsolidity/syntaxTests/pragma/unterminated_version.sol b/test/libsolidity/syntaxTests/pragma/unterminated_version.sol index 6dc7373c0..2515a9679 100644 --- a/test/libsolidity/syntaxTests/pragma/unterminated_version.sol +++ b/test/libsolidity/syntaxTests/pragma/unterminated_version.sol @@ -1,4 +1,4 @@ pragma solidity 0.4.3 pragma abicoder v2; // ---- -// ParserError 1684: (0-41): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon. +// ParserError 1684: (0-41): Invalid version pragma. Expected the start of a version number but instead found character 'p'. Version number is invalid or the pragma is not terminated with a semicolon. diff --git a/test/libsolidity/syntaxTests/pragma/valid_range_disjunction_range_second.sol b/test/libsolidity/syntaxTests/pragma/valid_range_disjunction_range_second.sol new file mode 100644 index 000000000..f318ec6f9 --- /dev/null +++ b/test/libsolidity/syntaxTests/pragma/valid_range_disjunction_range_second.sol @@ -0,0 +1,2 @@ +pragma solidity 0.8.17 || 0.4.1 - 0.9.0; +// ---- diff --git a/test/libsolidity/syntaxTests/pragma/valid_range_disjunction_two_ranges.sol b/test/libsolidity/syntaxTests/pragma/valid_range_disjunction_two_ranges.sol new file mode 100644 index 000000000..6bf3a243e --- /dev/null +++ b/test/libsolidity/syntaxTests/pragma/valid_range_disjunction_two_ranges.sol @@ -0,0 +1,2 @@ +pragma solidity 0.4.0 - 0.9.0 || 0.4.1 - 0.6.2; +// ---- diff --git a/test/libsolidity/syntaxTests/pragma/version_number_too_large.sol b/test/libsolidity/syntaxTests/pragma/version_number_too_large.sol new file mode 100644 index 000000000..bde5a9aae --- /dev/null +++ b/test/libsolidity/syntaxTests/pragma/version_number_too_large.sol @@ -0,0 +1,3 @@ +pragma solidity 4294967296; +// ---- +// ParserError 1684: (0-27): Invalid version pragma. Integer too large to be used in a version number.