Skip to content

Commit 9e7b85a

Browse files
vmanektallamatheusaaguiar
authored andcommitted
Improve Error Reporting of SemVer Parser
1 parent 10f8142 commit 9e7b85a

20 files changed

+135
-64
lines changed

Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Compiler Features:
1515
* Optimizer: Added optimization rule ``and(shl(X, Y), shl(X, Z)) => shl(X, and(Y, Z))``.
1616
* 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.
1717
* SMTChecker: Make ``z3`` the default solver for the BMC and CHC engines instead of all solvers.
18+
* Parser: More detailed error messages about invalid version pragmas.
1819

1920

2021
Bugfixes:

liblangutil/SemVerHandler.cpp

+24-9
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727

2828
#include <functional>
2929
#include <limits>
30+
#include <fmt/format.h>
3031

3132
using namespace std;
3233
using namespace solidity;
3334
using namespace solidity::langutil;
35+
using namespace solidity::util;
3436

3537
SemVerMatchExpressionParser::SemVerMatchExpressionParser(vector<Token> _tokens, vector<string> _literals):
3638
m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
@@ -52,7 +54,7 @@ SemVerVersion::SemVerVersion(string const& _versionString)
5254
if (level < 2)
5355
{
5456
if (i == end || *i != '.')
55-
BOOST_THROW_EXCEPTION(SemVerError());
57+
solThrow(SemVerError, "Invalid versionString: "s + _versionString);
5658
else
5759
++i;
5860
}
@@ -70,7 +72,7 @@ SemVerVersion::SemVerVersion(string const& _versionString)
7072
build = string(buildStart, i);
7173
}
7274
if (i != end)
73-
BOOST_THROW_EXCEPTION(SemVerError());
75+
solThrow(SemVerError, "Invalid versionString "s + _versionString);
7476
}
7577

7678
bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _version) const
@@ -156,12 +158,12 @@ bool SemVerMatchExpression::matches(SemVerVersion const& _version) const
156158
return false;
157159
}
158160

159-
optional<SemVerMatchExpression> SemVerMatchExpressionParser::parse()
161+
SemVerMatchExpression SemVerMatchExpressionParser::parse()
160162
{
161163
reset();
162164

163165
if (m_tokens.empty())
164-
return nullopt;
166+
solThrow(SemVerError, "Empty version pragma.");
165167

166168
try
167169
{
@@ -171,14 +173,19 @@ optional<SemVerMatchExpression> SemVerMatchExpressionParser::parse()
171173
if (m_pos >= m_tokens.size())
172174
break;
173175
if (currentToken() != Token::Or)
174-
BOOST_THROW_EXCEPTION(SemVerError());
176+
{
177+
solThrow(
178+
SemVerError,
179+
"You can only combine version ranges using the || operator."
180+
);
181+
}
175182
nextToken();
176183
}
177184
}
178-
catch (SemVerError const&)
185+
catch (SemVerError const& e)
179186
{
180187
reset();
181-
return nullopt;
188+
throw e;
182189
}
183190

184191
return m_expression;
@@ -265,14 +272,22 @@ unsigned SemVerMatchExpressionParser::parseVersionPart()
265272
{
266273
c = currentChar();
267274
if (v * 10 < v || v * 10 + static_cast<unsigned>(c - '0') < v * 10)
268-
BOOST_THROW_EXCEPTION(SemVerError());
275+
solThrow(SemVerError, "Integer too large to be used in a version number.");
269276
v = v * 10 + static_cast<unsigned>(c - '0');
270277
nextChar();
271278
}
272279
return v;
273280
}
281+
else if (c == char(-1))
282+
solThrow(SemVerError, "Expected version number but reached end of pragma.");
274283
else
275-
BOOST_THROW_EXCEPTION(SemVerError());
284+
solThrow(
285+
SemVerError, fmt::format(
286+
"Expected the start of a version number but instead found character '{}'. "
287+
"Version number is invalid or the pragma is not terminated with a semicolon.",
288+
c
289+
)
290+
);
276291
}
277292

278293
char SemVerMatchExpressionParser::currentChar() const

liblangutil/SemVerHandler.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,18 @@
2525

2626
#include <liblangutil/Token.h>
2727
#include <libsolutil/Assertions.h>
28+
#include <liblangutil/Exceptions.h>
2829

2930
#include <string>
30-
#include <optional>
3131
#include <utility>
3232
#include <vector>
3333

3434
namespace solidity::langutil
3535
{
3636

37-
class SemVerError: public util::Exception
37+
struct SemVerError: public util::Exception
3838
{
39+
3940
};
4041

4142
#undef major
@@ -87,8 +88,8 @@ class SemVerMatchExpressionParser
8788
public:
8889
SemVerMatchExpressionParser(std::vector<Token> _tokens, std::vector<std::string> _literals);
8990

90-
/// Returns an expression if it was parseable, or nothing otherwise.
91-
std::optional<SemVerMatchExpression> parse();
91+
/// Returns an expression if it was parsable, or throws a SemVerError otherwise.
92+
SemVerMatchExpression parse();
9293

9394
private:
9495
void reset();

libsolidity/analysis/SyntaxChecker.cpp

+22-16
Original file line numberDiff line numberDiff line change
@@ -153,22 +153,28 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
153153
}
154154
else if (_pragma.literals()[0] == "solidity")
155155
{
156-
vector<Token> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end());
157-
vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end());
158-
SemVerMatchExpressionParser parser(tokens, literals);
159-
auto matchExpression = parser.parse();
160-
// An unparsable version pragma is an unrecoverable fatal error in the parser.
161-
solAssert(matchExpression.has_value(), "");
162-
static SemVerVersion const currentVersion{string(VersionString)};
163-
if (!matchExpression->matches(currentVersion))
164-
m_errorReporter.syntaxError(
165-
3997_error,
166-
_pragma.location(),
167-
"Source file requires different compiler version (current compiler is " +
168-
string(VersionString) + ") - note that nightly builds are considered to be "
169-
"strictly less than the released version"
170-
);
171-
m_versionPragmaFound = true;
156+
try
157+
{
158+
vector<Token> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end());
159+
vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end());
160+
SemVerMatchExpressionParser parser(tokens, literals);
161+
SemVerMatchExpression matchExpression = parser.parse();
162+
static SemVerVersion const currentVersion{string(VersionString)};
163+
if (!matchExpression.matches(currentVersion))
164+
m_errorReporter.syntaxError(
165+
3997_error,
166+
_pragma.location(),
167+
"Source file requires different compiler version (current compiler is " +
168+
string(VersionString) + ") - note that nightly builds are considered to be "
169+
"strictly less than the released version"
170+
);
171+
m_versionPragmaFound = true;
172+
}
173+
catch (SemVerError const&)
174+
{
175+
// An unparsable version pragma is an unrecoverable fatal error in the parser.
176+
solAssert(false);
177+
}
172178
}
173179
else
174180
m_errorReporter.syntaxError(4936_error, _pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");

libsolidity/parsing/Parser.cpp

+21-17
Original file line numberDiff line numberDiff line change
@@ -160,27 +160,31 @@ ASTPointer<SourceUnit> Parser::parse(CharStream& _charStream)
160160
void Parser::parsePragmaVersion(SourceLocation const& _location, vector<Token> const& _tokens, vector<string> const& _literals)
161161
{
162162
SemVerMatchExpressionParser parser(_tokens, _literals);
163-
auto matchExpression = parser.parse();
164-
if (!matchExpression.has_value())
163+
try
164+
{
165+
SemVerMatchExpression matchExpression = parser.parse();
166+
static SemVerVersion const currentVersion{string(VersionString)};
167+
// FIXME: only match for major version incompatibility
168+
if (!matchExpression.matches(currentVersion))
169+
// If m_parserErrorRecovery is true, the same message will appear from SyntaxChecker::visit(),
170+
// so we don't need to report anything here.
171+
if (!m_parserErrorRecovery)
172+
m_errorReporter.fatalParserError(
173+
5333_error,
174+
_location,
175+
"Source file requires different compiler version (current compiler is " +
176+
string(VersionString) + ") - note that nightly builds are considered to be "
177+
"strictly less than the released version"
178+
);
179+
}
180+
catch (SemVerError const& matchError)
181+
{
165182
m_errorReporter.fatalParserError(
166183
1684_error,
167184
_location,
168-
"Found version pragma, but failed to parse it. "
169-
"Please ensure there is a trailing semicolon."
185+
"Invalid version pragma. "s + matchError.what()
170186
);
171-
static SemVerVersion const currentVersion{string(VersionString)};
172-
// FIXME: only match for major version incompatibility
173-
if (!matchExpression->matches(currentVersion))
174-
// If m_parserErrorRecovery is true, the same message will appear from SyntaxChecker::visit(),
175-
// so we don't need to report anything here.
176-
if (!m_parserErrorRecovery)
177-
m_errorReporter.fatalParserError(
178-
5333_error,
179-
_location,
180-
"Source file requires different compiler version (current compiler is " +
181-
string(VersionString) + ") - note that nightly builds are considered to be "
182-
"strictly less than the released version"
183-
);
187+
}
184188
}
185189

186190
ASTPointer<StructuredDocumentation> Parser::parseStructuredDocumentation()

test/libsolidity/SemVerMatcher.cpp

+36-11
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <liblangutil/Scanner.h>
2828
#include <liblangutil/SemVerHandler.h>
2929
#include <test/Common.h>
30+
#include <test/libsolidity/util/SoltestErrors.h>
3031

3132
#include <boost/test/unit_test.hpp>
3233

@@ -58,15 +59,39 @@ SemVerMatchExpression parseExpression(string const& _input)
5859
scanner.next();
5960
}
6061

61-
auto expression = SemVerMatchExpressionParser(tokens, literals).parse();
62-
BOOST_REQUIRE(expression.has_value());
63-
BOOST_CHECK_MESSAGE(
64-
expression->isValid(),
65-
"Expression \"" + _input + "\" did not parse properly."
66-
);
67-
return *expression;
62+
try
63+
{
64+
auto matchExpression = SemVerMatchExpressionParser(tokens, literals).parse();
65+
66+
BOOST_CHECK_MESSAGE(
67+
matchExpression.isValid(),
68+
"Expression \"" + _input + "\" did not parse properly."
69+
);
70+
71+
return matchExpression;
72+
}
73+
catch (SemVerError const&)
74+
{
75+
// Ignored, since a test case should have a parsable version
76+
soltestAssert(false);
77+
}
78+
}
79+
6880
}
6981

82+
BOOST_AUTO_TEST_CASE(exception_on_invalid_version_in_semverversion_constructor)
83+
{
84+
BOOST_CHECK_EXCEPTION(
85+
SemVerVersion version("1.2"),
86+
SemVerError,
87+
[&](auto const& _exception) { BOOST_TEST(_exception.what() == "Invalid versionString: 1.2"); return true; }
88+
);
89+
90+
BOOST_CHECK_EXCEPTION(
91+
SemVerVersion version("-1.2.0"),
92+
SemVerError,
93+
[&](auto const& _exception) { BOOST_TEST(_exception.what() == "Invalid versionString: -1.2.0"); return true; }
94+
);
7095
}
7196

7297
BOOST_AUTO_TEST_CASE(positive_range)
@@ -159,9 +184,9 @@ BOOST_AUTO_TEST_CASE(positive_range)
159184
for (auto const& t: tests)
160185
{
161186
SemVerVersion version(t.second);
162-
SemVerMatchExpression expression = parseExpression(t.first);
187+
SemVerMatchExpression matchExpression = parseExpression(t.first);
163188
BOOST_CHECK_MESSAGE(
164-
expression.matches(version),
189+
matchExpression.matches(version),
165190
"Version \"" + t.second + "\" did not satisfy expression \"" + t.first + "\""
166191
);
167192
}
@@ -235,9 +260,9 @@ BOOST_AUTO_TEST_CASE(negative_range)
235260
for (auto const& t: tests)
236261
{
237262
SemVerVersion version(t.second);
238-
SemVerMatchExpression expression = parseExpression(t.first);
263+
auto matchExpression = parseExpression(t.first);
239264
BOOST_CHECK_MESSAGE(
240-
!expression.matches(version),
265+
!matchExpression.matches(version),
241266
"Version \"" + t.second + "\" did satisfy expression \"" + t.first + "\" " +
242267
"(although it should not)"
243268
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pragma solidity ;
2+
// ----
3+
// ParserError 1684: (0-17): Invalid version pragma. Empty version pragma.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pragma solidity pragma;
22
// ----
3-
// ParserError 1684: (0-23): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon.
3+
// 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.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pragma solidity (8.0.0);
22
// ----
3-
// ParserError 1684: (0-24): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon.
3+
// 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.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pragma solidity 88_;
22
// ----
3-
// ParserError 1684: (0-20): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon.
3+
// 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.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pragma solidity v1.2.3;
22
// ----
3-
// ParserError 1684: (0-23): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon.
3+
// 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.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pragma solidity >0.5.0<;
22
// ----
3-
// ParserError 1684: (0-24): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon.
3+
// ParserError 1684: (0-24): Invalid version pragma. Expected version number but reached end of pragma.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pragma solidity;
22
// ----
3-
// ParserError 1684: (0-16): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon.
3+
// ParserError 1684: (0-16): Invalid version pragma. Empty version pragma.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pragma solidity 0.4.1 - 0.6.2 0.8.17;
2+
// ----
3+
// ParserError 1684: (0-37): Invalid version pragma. You can only combine version ranges using the || operator.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pragma solidity 0.8.17 0.4.1 - 0.6.2;
2+
// ----
3+
// 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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pragma solidity 0.4.0 - 0.4.1 0.4.1 - 0.6.2;
2+
// ----
3+
// ParserError 1684: (0-44): Invalid version pragma. You can only combine version ranges using the || operator.
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
pragma solidity 0.4.3
22
pragma abicoder v2;
33
// ----
4-
// ParserError 1684: (0-41): Found version pragma, but failed to parse it. Please ensure there is a trailing semicolon.
4+
// 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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pragma solidity 0.8.17 || 0.4.1 - 0.9.0;
2+
// ----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pragma solidity 0.4.0 - 0.9.0 || 0.4.1 - 0.6.2;
2+
// ----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pragma solidity 4294967296;
2+
// ----
3+
// ParserError 1684: (0-27): Invalid version pragma. Integer too large to be used in a version number.

0 commit comments

Comments
 (0)