Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DSLX:FE] Add expression depth guard for Term grammar production, factor out helper. #1817

Merged
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
47 changes: 27 additions & 20 deletions xls/dslx/frontend/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -645,12 +645,7 @@ absl::StatusOr<Expr*> Parser::ParseExpression(Bindings& bindings,
ExprRestrictions restrictions) {
XLS_ASSIGN_OR_RETURN(const Token* peek, PeekToken());

if (++approximate_expression_depth_ >= kApproximateExpressionDepthLimit) {
return ParseErrorStatus(peek->span(),
"Expression is too deeply nested, please break "
"into multiple statements");
}
auto bump_down = absl::Cleanup([this] { approximate_expression_depth_--; });
XLS_ASSIGN_OR_RETURN(ExpressionDepthGuard expr_depth, BumpExpressionDepth());

VLOG(5) << "ParseExpression @ " << GetPos() << " peek: `" << peek->ToString()
<< "`";
Expand Down Expand Up @@ -819,6 +814,8 @@ absl::StatusOr<TypeRef*> Parser::ParseTypeRef(Bindings& bindings,

absl::StatusOr<TypeAnnotation*> Parser::ParseTypeAnnotation(
Bindings& bindings, std::optional<Token> first) {
XLS_ASSIGN_OR_RETURN(ExpressionDepthGuard expr_depth, BumpExpressionDepth());

VLOG(5) << "ParseTypeAnnotation @ " << GetPos();
if (!first.has_value()) {
XLS_ASSIGN_OR_RETURN(first, PopToken());
Expand Down Expand Up @@ -1135,14 +1132,8 @@ absl::StatusOr<NameDefTree*> Parser::ParseNameDefTree(Bindings& bindings) {
this]() -> absl::StatusOr<NameDefTree*> {
XLS_ASSIGN_OR_RETURN(const Token* peek, PeekToken());
if (peek->kind() == TokenKind::kOParen) {
if (++approximate_expression_depth_ >= kApproximateExpressionDepthLimit) {
return ParseErrorStatus(
peek->span(),
"Name definition tree is too deeply nested, please break "
"into multiple statements");
}
auto bump_down =
absl::Cleanup([this] { approximate_expression_depth_--; });
XLS_ASSIGN_OR_RETURN(ExpressionDepthGuard expr_depth,
BumpExpressionDepth());
return ParseNameDefTree(bindings);
}
XLS_ASSIGN_OR_RETURN(auto name_def, ParseNameDefOrWildcard(bindings));
Expand Down Expand Up @@ -1427,6 +1418,8 @@ absl::StatusOr<Expr*> Parser::ParseComparisonExpression(
}

absl::StatusOr<NameDefTree*> Parser::ParsePattern(Bindings& bindings) {
XLS_ASSIGN_OR_RETURN(ExpressionDepthGuard depth_guard, BumpExpressionDepth());

XLS_ASSIGN_OR_RETURN(std::optional<Token> oparen,
TryPopToken(TokenKind::kOParen));
if (oparen.has_value()) {
Expand Down Expand Up @@ -1812,12 +1805,7 @@ absl::StatusOr<Expr*> Parser::ParseTermLhs(Bindings& outer_bindings,
ExprRestrictions restrictions) {
XLS_ASSIGN_OR_RETURN(const Token* peek, PeekToken());

if (++approximate_expression_depth_ >= kApproximateExpressionDepthLimit) {
return ParseErrorStatus(peek->span(),
"Expression is too deeply nested, please break "
"into multiple statements");
}
auto bump_down = absl::Cleanup([this] { approximate_expression_depth_--; });
XLS_ASSIGN_OR_RETURN(ExpressionDepthGuard expr_depth, BumpExpressionDepth());

const Pos start_pos = peek->span().start();
VLOG(5) << "ParseTerm @ " << start_pos << " peek: `" << peek->ToString()
Expand Down Expand Up @@ -2161,8 +2149,27 @@ absl::StatusOr<Expr*> Parser::ParseTermRhs(Expr* lhs, Bindings& outer_bindings,
return lhs;
}

ExpressionDepthGuard::~ExpressionDepthGuard() {
if (parser_ != nullptr) {
parser_->approximate_expression_depth_--;
CHECK_GE(parser_->approximate_expression_depth_, 0);
parser_ = nullptr;
}
}

absl::StatusOr<ExpressionDepthGuard> Parser::BumpExpressionDepth() {
if (++approximate_expression_depth_ >= kApproximateExpressionDepthLimit) {
return ParseErrorStatus(Span(GetPos(), GetPos()),
"Extremely deep nesting detected -- please break "
"into multiple statements");
}
return ExpressionDepthGuard(this);
}

absl::StatusOr<Expr*> Parser::ParseTerm(Bindings& outer_bindings,
ExprRestrictions restrictions) {
XLS_ASSIGN_OR_RETURN(ExpressionDepthGuard expr_depth, BumpExpressionDepth());

XLS_ASSIGN_OR_RETURN(Expr * lhs, ParseTermLhs(outer_bindings, restrictions));

while (true) {
Expand Down
36 changes: 36 additions & 0 deletions xls/dslx/frontend/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,35 @@ XLS_DEFINE_STRONG_INT_TYPE(ExprRestrictions, uint32_t);

constexpr ExprRestrictions kNoRestrictions = ExprRestrictions(0);

// forward declaration
class Parser;

// RAII guard used to ensure the expression nesting depth does not get
// unreasonably deep (which can cause stack overflows and "erroneously" flag
// fuzzing issues in that dimension).
class ABSL_MUST_USE_RESULT ExpressionDepthGuard final {
public:
explicit ExpressionDepthGuard(Parser* parser) : parser_(parser) {}
~ExpressionDepthGuard();

// move-only type, and we use the parser pointer to track which instance is
// performing the side effect in the destructor if the original is moved
ExpressionDepthGuard(ExpressionDepthGuard&& other) : parser_(other.parser_) {
other.parser_ = nullptr;
}
ExpressionDepthGuard& operator=(ExpressionDepthGuard&& other) {
parser_ = other.parser_;
other.parser_ = nullptr;
return *this;
}

ExpressionDepthGuard(const ExpressionDepthGuard&) = delete;
ExpressionDepthGuard& operator=(const ExpressionDepthGuard&) = delete;

private:
Parser* parser_;
};

class Parser : public TokenParser {
public:
Parser(std::string module_name, Scanner* scanner)
Expand Down Expand Up @@ -128,6 +157,7 @@ class Parser : public TokenParser {

private:
friend class ParserTest;
friend class ExpressionDepthGuard;

// Simple helper class to wrap the operations necessary to evaluate [parser]
// productions as transactions - with "Commit" or "Rollback" operations.
Expand Down Expand Up @@ -657,6 +687,12 @@ class Parser : public TokenParser {
Bindings& outer_bindings,
Keyword keyword);

// Bumps the internally-tracked expression depth so we can provide a useful
// error if we over-recurse on expression depth past the point a user would
// reasonably be expected to do. This (e.g.) helps avoid stack overflows
// during fuzzing.
absl::StatusOr<ExpressionDepthGuard> BumpExpressionDepth();

std::unique_ptr<Module> module_;

// `Let` nodes are created _after_ those that use their namedefs (due to the
Expand Down
36 changes: 35 additions & 1 deletion xls/dslx/frontend/parser_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3181,7 +3181,41 @@ TEST_F(ParserTest, UnreasonablyDeepExpr) {
Parser parser{"test", &s};
absl::StatusOr<std::unique_ptr<Module>> module = parser.ParseModule();
EXPECT_THAT(module, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Expression is too deeply nested")));
HasSubstr("Extremely deep nesting detected")));
}

// Previously we could avoid the nesting detector by entering an interior
// production in the grammar like a Term.
TEST_F(ParserTest, UnreasonablyDeepTermExprNesting) {
constexpr std::string_view kProgram = R"(const E=
0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0()";
Scanner s{file_table_, Fileno(0), std::string(kProgram)};
Parser parser{"test", &s};
absl::StatusOr<std::unique_ptr<Module>> module = parser.ParseModule();
EXPECT_THAT(module, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Extremely deep nesting detected")));
}

// As above but guards the TypeAnnotation production in the grammar.
TEST_F(ParserTest, UnreasonablyDeepTypeDefinition) {
constexpr std::string_view kProgram = R"(type T=
((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((()";
Scanner s{file_table_, Fileno(0), std::string(kProgram)};
Parser parser{"test", &s};
absl::StatusOr<std::unique_ptr<Module>> module = parser.ParseModule();
EXPECT_THAT(module, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Extremely deep nesting detected")));
}

// As above but guards the pattern-match production in the grammar.
TEST_F(ParserTest, UnreasonablyDeepPatternMatch) {
constexpr std::string_view kProgram = R"(fn f(x: u32) { match x {
((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((()";
Scanner s{file_table_, Fileno(0), std::string(kProgram)};
Parser parser{"test", &s};
absl::StatusOr<std::unique_ptr<Module>> module = parser.ParseModule();
EXPECT_THAT(module, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Extremely deep nesting detected")));
}

TEST_F(ParserTest, NonTypeDefinitionBeforeArrayLiteralColon) {
Expand Down
2 changes: 1 addition & 1 deletion xls/fuzzer/crashers/crasher_2020-01-08_2173.x
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn main(x0: s57, x1: s36, x2: s24, x3: u12) -> (u62, u45, u62) {
;
let x20: s29 = (s29:0x40000);
let x21: uN[57] = (x0 as u57)[:];
let x22: uN[1320] = (((((((((((((((((((((((x10) ++ (x18)) ++ (x10)) ++ (x10)) ++ (x18)) ++ (x10)) ++ (x19)) ++ (x6)) ++ (x10)) ++ (x6)) ++ (x18)) ++ (x10)) ++ (x19)) ++ (x19)) ++ (x6)) ++ (x6)) ++ (x10)) ++ (x19)) ++ (x18)) ++ (x6)) ++ (x3)) ++ (x3)) ++ (x10)) ++ (x6);
let x22: uN[1320] = x10 ++ x18 ++ x10 ++ x10 ++ x18 ++ x10 ++ x19 ++ x6 ++ x10 ++ x6 ++ x18 ++ x10 ++ x19 ++ x19 ++ x6 ++ x6 ++ x10 ++ x19 ++ x18 ++ x6 ++ x3 ++ x3 ++ x10 ++ x6;
let x23: s24 = for (i, x): (u4, s24) in range((u4:0x0), (u4:0x7)) {
x
}(x15)
Expand Down