Improve Error Reporting of SemVer Parser

This commit is contained in:
Vinay 2022-03-15 18:53:29 +00:00 committed by Matheus Aguiar
parent 10f81425a3
commit 9e7b85ac4b
20 changed files with 135 additions and 64 deletions

View File

@ -15,6 +15,7 @@ Compiler Features:
* Optimizer: Added optimization rule ``and(shl(X, Y), shl(X, Z)) => shl(X, and(Y, Z))``. * 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: 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. * 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: Bugfixes:

View File

@ -27,10 +27,12 @@
#include <functional> #include <functional>
#include <limits> #include <limits>
#include <fmt/format.h>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::langutil; using namespace solidity::langutil;
using namespace solidity::util;
SemVerMatchExpressionParser::SemVerMatchExpressionParser(vector<Token> _tokens, vector<string> _literals): SemVerMatchExpressionParser::SemVerMatchExpressionParser(vector<Token> _tokens, vector<string> _literals):
m_tokens(std::move(_tokens)), m_literals(std::move(_literals)) m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
@ -52,7 +54,7 @@ SemVerVersion::SemVerVersion(string const& _versionString)
if (level < 2) if (level < 2)
{ {
if (i == end || *i != '.') if (i == end || *i != '.')
BOOST_THROW_EXCEPTION(SemVerError()); solThrow(SemVerError, "Invalid versionString: "s + _versionString);
else else
++i; ++i;
} }
@ -70,7 +72,7 @@ SemVerVersion::SemVerVersion(string const& _versionString)
build = string(buildStart, i); build = string(buildStart, i);
} }
if (i != end) if (i != end)
BOOST_THROW_EXCEPTION(SemVerError()); solThrow(SemVerError, "Invalid versionString "s + _versionString);
} }
bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _version) const bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _version) const
@ -156,12 +158,12 @@ bool SemVerMatchExpression::matches(SemVerVersion const& _version) const
return false; return false;
} }
optional<SemVerMatchExpression> SemVerMatchExpressionParser::parse() SemVerMatchExpression SemVerMatchExpressionParser::parse()
{ {
reset(); reset();
if (m_tokens.empty()) if (m_tokens.empty())
return nullopt; solThrow(SemVerError, "Empty version pragma.");
try try
{ {
@ -171,14 +173,19 @@ optional<SemVerMatchExpression> SemVerMatchExpressionParser::parse()
if (m_pos >= m_tokens.size()) if (m_pos >= m_tokens.size())
break; break;
if (currentToken() != Token::Or) if (currentToken() != Token::Or)
BOOST_THROW_EXCEPTION(SemVerError()); {
solThrow(
SemVerError,
"You can only combine version ranges using the || operator."
);
}
nextToken(); nextToken();
} }
} }
catch (SemVerError const&) catch (SemVerError const& e)
{ {
reset(); reset();
return nullopt; throw e;
} }
return m_expression; return m_expression;
@ -265,14 +272,22 @@ unsigned SemVerMatchExpressionParser::parseVersionPart()
{ {
c = currentChar(); c = currentChar();
if (v * 10 < v || v * 10 + static_cast<unsigned>(c - '0') < v * 10) if (v * 10 < v || v * 10 + static_cast<unsigned>(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<unsigned>(c - '0'); v = v * 10 + static_cast<unsigned>(c - '0');
nextChar(); nextChar();
} }
return v; return v;
} }
else if (c == char(-1))
solThrow(SemVerError, "Expected version number but reached end of pragma.");
else 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 char SemVerMatchExpressionParser::currentChar() const

View File

@ -25,17 +25,18 @@
#include <liblangutil/Token.h> #include <liblangutil/Token.h>
#include <libsolutil/Assertions.h> #include <libsolutil/Assertions.h>
#include <liblangutil/Exceptions.h>
#include <string> #include <string>
#include <optional>
#include <utility> #include <utility>
#include <vector> #include <vector>
namespace solidity::langutil namespace solidity::langutil
{ {
class SemVerError: public util::Exception struct SemVerError: public util::Exception
{ {
}; };
#undef major #undef major
@ -87,8 +88,8 @@ class SemVerMatchExpressionParser
public: public:
SemVerMatchExpressionParser(std::vector<Token> _tokens, std::vector<std::string> _literals); SemVerMatchExpressionParser(std::vector<Token> _tokens, std::vector<std::string> _literals);
/// Returns an expression if it was parseable, or nothing otherwise. /// Returns an expression if it was parsable, or throws a SemVerError otherwise.
std::optional<SemVerMatchExpression> parse(); SemVerMatchExpression parse();
private: private:
void reset(); void reset();

View File

@ -153,22 +153,28 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
} }
else if (_pragma.literals()[0] == "solidity") else if (_pragma.literals()[0] == "solidity")
{ {
vector<Token> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end()); try
vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end()); {
SemVerMatchExpressionParser parser(tokens, literals); vector<Token> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end());
auto matchExpression = parser.parse(); vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end());
// An unparsable version pragma is an unrecoverable fatal error in the parser. SemVerMatchExpressionParser parser(tokens, literals);
solAssert(matchExpression.has_value(), ""); SemVerMatchExpression matchExpression = parser.parse();
static SemVerVersion const currentVersion{string(VersionString)}; static SemVerVersion const currentVersion{string(VersionString)};
if (!matchExpression->matches(currentVersion)) if (!matchExpression.matches(currentVersion))
m_errorReporter.syntaxError( m_errorReporter.syntaxError(
3997_error, 3997_error,
_pragma.location(), _pragma.location(),
"Source file requires different compiler version (current compiler is " + "Source file requires different compiler version (current compiler is " +
string(VersionString) + ") - note that nightly builds are considered to be " string(VersionString) + ") - note that nightly builds are considered to be "
"strictly less than the released version" "strictly less than the released version"
); );
m_versionPragmaFound = true; m_versionPragmaFound = true;
}
catch (SemVerError const&)
{
// An unparsable version pragma is an unrecoverable fatal error in the parser.
solAssert(false);
}
} }
else else
m_errorReporter.syntaxError(4936_error, _pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); m_errorReporter.syntaxError(4936_error, _pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");

View File

@ -160,27 +160,31 @@ ASTPointer<SourceUnit> Parser::parse(CharStream& _charStream)
void Parser::parsePragmaVersion(SourceLocation const& _location, vector<Token> const& _tokens, vector<string> const& _literals) void Parser::parsePragmaVersion(SourceLocation const& _location, vector<Token> const& _tokens, vector<string> const& _literals)
{ {
SemVerMatchExpressionParser parser(_tokens, _literals); SemVerMatchExpressionParser parser(_tokens, _literals);
auto matchExpression = parser.parse(); try
if (!matchExpression.has_value()) {
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( m_errorReporter.fatalParserError(
1684_error, 1684_error,
_location, _location,
"Found version pragma, but failed to parse it. " "Invalid version pragma. "s + matchError.what()
"Please ensure there is a trailing semicolon."
); );
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<StructuredDocumentation> Parser::parseStructuredDocumentation() ASTPointer<StructuredDocumentation> Parser::parseStructuredDocumentation()

View File

@ -27,6 +27,7 @@
#include <liblangutil/Scanner.h> #include <liblangutil/Scanner.h>
#include <liblangutil/SemVerHandler.h> #include <liblangutil/SemVerHandler.h>
#include <test/Common.h> #include <test/Common.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
@ -58,17 +59,41 @@ SemVerMatchExpression parseExpression(string const& _input)
scanner.next(); scanner.next();
} }
auto expression = SemVerMatchExpressionParser(tokens, literals).parse(); try
BOOST_REQUIRE(expression.has_value()); {
BOOST_CHECK_MESSAGE( auto matchExpression = SemVerMatchExpressionParser(tokens, literals).parse();
expression->isValid(),
"Expression \"" + _input + "\" did not parse properly." BOOST_CHECK_MESSAGE(
); matchExpression.isValid(),
return *expression; "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) BOOST_AUTO_TEST_CASE(positive_range)
{ {
// Positive range tests // Positive range tests
@ -159,9 +184,9 @@ BOOST_AUTO_TEST_CASE(positive_range)
for (auto const& t: tests) for (auto const& t: tests)
{ {
SemVerVersion version(t.second); SemVerVersion version(t.second);
SemVerMatchExpression expression = parseExpression(t.first); SemVerMatchExpression matchExpression = parseExpression(t.first);
BOOST_CHECK_MESSAGE( BOOST_CHECK_MESSAGE(
expression.matches(version), matchExpression.matches(version),
"Version \"" + t.second + "\" did not satisfy expression \"" + t.first + "\"" "Version \"" + t.second + "\" did not satisfy expression \"" + t.first + "\""
); );
} }
@ -235,9 +260,9 @@ BOOST_AUTO_TEST_CASE(negative_range)
for (auto const& t: tests) for (auto const& t: tests)
{ {
SemVerVersion version(t.second); SemVerVersion version(t.second);
SemVerMatchExpression expression = parseExpression(t.first); auto matchExpression = parseExpression(t.first);
BOOST_CHECK_MESSAGE( BOOST_CHECK_MESSAGE(
!expression.matches(version), !matchExpression.matches(version),
"Version \"" + t.second + "\" did satisfy expression \"" + t.first + "\" " + "Version \"" + t.second + "\" did satisfy expression \"" + t.first + "\" " +
"(although it should not)" "(although it should not)"
); );

View File

@ -0,0 +1,3 @@
pragma solidity ;
// ----
// ParserError 1684: (0-17): Invalid version pragma. Empty version pragma.

View File

@ -1,3 +1,3 @@
pragma solidity pragma; 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.

View File

@ -1,3 +1,3 @@
pragma solidity (8.0.0); 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.

View File

@ -1,3 +1,3 @@
pragma solidity 88_; 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.

View File

@ -1,3 +1,3 @@
pragma solidity v1.2.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.

View File

@ -1,3 +1,3 @@
pragma solidity >0.5.0<; 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.

View File

@ -1,3 +1,3 @@
pragma solidity; 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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -1,4 +1,4 @@
pragma solidity 0.4.3 pragma solidity 0.4.3
pragma abicoder v2; 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.

View File

@ -0,0 +1,2 @@
pragma solidity 0.8.17 || 0.4.1 - 0.9.0;
// ----

View File

@ -0,0 +1,2 @@
pragma solidity 0.4.0 - 0.9.0 || 0.4.1 - 0.6.2;
// ----

View File

@ -0,0 +1,3 @@
pragma solidity 4294967296;
// ----
// ParserError 1684: (0-27): Invalid version pragma. Integer too large to be used in a version number.