Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
- Updated third party library [fmt](https://github.com/fmtlib/fmt) to version
11.1.2
- Fixed MSVC compiling issues on Windows
- Hardened the OpenQASM 2.0 parser against malformed input: out-of-range
numeric literals, deeply nested expressions, unterminated string literals at
end of file, and token kind/value mismatches now yield a parse error instead
of aborting or hanging the process

# Version 3.5 - 8 March 2024

Expand Down
18 changes: 12 additions & 6 deletions qasmtools/include/qasmtools/parser/lexer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,17 @@ class Lexer {
}
}

if (integral) {
return Token(tok_start, Token::Kind::nninteger, str,
std::stoi(str));
} else {
return Token(tok_start, Token::Kind::real, str, std::stof(str));
try {
if (integral) {
return Token(tok_start, Token::Kind::nninteger, str,
std::stoi(str));
} else {
return Token(tok_start, Token::Kind::real, str, std::stof(str));
}
} catch (const std::out_of_range&) {
std::cerr << "Lexical error at " << tok_start
<< ": numeric literal out of range\n";
return Token(tok_start, Token::Kind::error, str);
}
}

Expand Down Expand Up @@ -213,7 +219,7 @@ class Lexer {
str.reserve(64); // Reserve space to avoid reallocation

while (buf_->peek() != '"' && buf_->peek() != '\n' &&
buf_->peek() != '\r') {
buf_->peek() != '\r' && buf_->peek() != EOF) {
str.push_back(buf_->peek());
skip_char();
}
Expand Down
18 changes: 18 additions & 0 deletions qasmtools/include/qasmtools/parser/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ class Parser {
Token current_token_; ///< current token
int bits_ = 0; ///< number of bits
int qubits_ = 0; ///< number of qubits
int expr_depth_ = 0; ///< current expression-parsing recursion depth
/// Cap on expression nesting; bounds recursion to prevent stack overflow
/// on adversarial input (e.g. deeply nested parentheses).
static constexpr int MAX_EXPR_DEPTH = 1000;
#ifdef EXPR_GMP
bool use_gmp_ = false; ///< whether to use gmp to parse reals
#endif /* EXPR_GMP */
Expand Down Expand Up @@ -650,6 +654,20 @@ class Parser {
* \return Unique pointer to an expression object
*/
ast::ptr<ast::Expr> parse_exp(int min_precedence = 1) {
struct DepthGuard {
int& d;
explicit DepthGuard(int& d_) : d(d_) { ++d; }
~DepthGuard() { --d; }
} depth_guard{expr_depth_};
if (expr_depth_ > MAX_EXPR_DEPTH) {
error_ = true;
if (!supress_errors_) {
std::cerr << current_token_.position()
<< ": expression nesting exceeds maximum depth\n";
}
throw ParseError();
}

auto pos = current_token_.position();

auto lexp = parse_atom();
Expand Down
18 changes: 15 additions & 3 deletions qasmtools/include/qasmtools/parser/token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,13 @@ class Token {
*
* \return The value of the token as an integer
*/
int as_int() const { return std::get<int>(value_); }
int as_int() const {
// Total accessor: a kind/value mismatch (e.g. a token returned by a
// failed expect_and_consume_token) must not throw std::bad_variant_access
// and abort the host; the parser's error_ flag already records the error.
auto* p = std::get_if<int>(&value_);
return p ? *p : 0;
}

/**
* \brief Get the floating point value
Expand All @@ -303,7 +309,10 @@ class Token {
*
* \return The value of the token as a floating point number
*/
double as_real() const { return std::get<double>(value_); }
double as_real() const {
auto* p = std::get_if<double>(&value_);
return p ? *p : 0.0;
}

/**
* \brief Get the string value
Expand All @@ -312,7 +321,10 @@ class Token {
*
* \return The value of the token as a string
*/
std::string as_string() const { return std::get<std::string>(value_); }
std::string as_string() const {
auto* p = std::get_if<std::string>(&value_);
return p ? *p : std::string();
}

/**
* \brief Return the position of the token
Expand Down