diff --git a/parser/internal/pratt_parser_test.cc b/parser/internal/pratt_parser_test.cc index fc6477745..0a1b251f5 100644 --- a/parser/internal/pratt_parser_test.cc +++ b/parser/internal/pratt_parser_test.cc @@ -1742,6 +1742,15 @@ TEST(PrattParserRecursionDepthTest, SequentialScopesDoNotAccumulateDepth) { EXPECT_THAT(Parse("[1] + [2] + [3]", options), IsOkAndHolds(NotNull())); } +TEST(PrattParserRecursionDepthTest, DeeplyNestedTernary) { + cel::ParserOptions options; + options.max_recursion_depth = 4; + EXPECT_THAT(Parse("a ? b : a ? b : a ? b : a ? b : c", options), + IsOkAndHolds(NotNull())); + EXPECT_THAT(Parse("a ? b : a ? b : a ? b : a ? b : a ? b : c", options), + StatusIs(absl::StatusCode::kCancelled)); +} + class TestParserWorker : public ParserWorker { // Expose the protected constructor and methods for testing. public: diff --git a/parser/internal/pratt_parser_worker.h b/parser/internal/pratt_parser_worker.h index 2cfe84b79..f6d0fc825 100644 --- a/parser/internal/pratt_parser_worker.h +++ b/parser/internal/pratt_parser_worker.h @@ -358,18 +358,21 @@ ExprNode PrattParserWorker::ParseExpr() { template void PrattParserWorker::ParseTernary(ExprNode& lhs) { - Token op_tok = NextToken(); - int64_t op_id = NextId(op_tok); - ExprNode true_expr = ParseBinaryAndTernary(1); - if (!Expect(TokenType::kColon, "expected ':' in conditional expression")) { + if (recursion_depth_ > options_.max_recursion_depth) { + recursion_limit_exceeded_ = true; return; } - ExprNode false_expr = ParseBinaryAndTernary(0); + recursion_depth_++; + absl::Cleanup depth_cleanup = [this] { recursion_depth_--; }; + int64_t op_id = NextId(NextToken()); std::vector args; args.reserve(3); args.push_back(std::move(lhs)); - args.push_back(std::move(true_expr)); - args.push_back(std::move(false_expr)); + args.push_back(ParseBinaryAndTernary(1)); + if (!Expect(TokenType::kColon, "expected ':' in conditional expression")) { + return; + } + args.push_back(ParseBinaryAndTernary(0)); lhs = ast_factory_.NewCall(op_id, CelOperator::CONDITIONAL, std::move(args)); } @@ -391,7 +394,7 @@ void PrattParserWorker::BuildBinaryCall(int64_t op_id, template ExprNode PrattParserWorker::ParseBinaryAndTernary(int min_prec) { ExprNode lhs = ParseSelectorChain(); - while (true) { + while (!recursion_limit_exceeded_ && !is_recovery_limit_exceeded()) { TokenType tok = peek_token_.type; if (tok == TokenType::kQuestion && min_prec <= 0) { ParseTernary(lhs); diff --git a/parser/parser_test.cc b/parser/parser_test.cc index 922968d3e..399ea1403 100644 --- a/parser/parser_test.cc +++ b/parser/parser_test.cc @@ -1086,6 +1086,12 @@ std::vector test_cases = { "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" "]]]]]]", "", "Expression recursion limit exceeded. limit: 32", "", "", ""}, + {"a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : " + "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : " + "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : " + "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : " + "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : c", + "", "Expression recursion limit exceeded. limit: 32", "", "", ""}, { // Note, the ANTLR parse stack may recurse much more deeply and permit // more detailed expressions than the visitor can recurse over in