Commit f1168ac41 for llama.cpp
commit f1168ac417f1b09af0846d042a12c577b0ffc7ca
Author: Hrushikesh Ramilla <ramillahrushikesh@gmail.com>
Date: Sun Oct 25 16:40:10 2026 +0530
grammar: limit recursion depth to prevent stack overflow (#29495) (#29755)
diff --git a/src/llama-grammar.cpp b/src/llama-grammar.cpp
index 0a8a61e7e..ffc7cf27c 100644
--- a/src/llama-grammar.cpp
+++ b/src/llama-grammar.cpp
@@ -11,6 +11,7 @@
#include <stdexcept>
#define MAX_REPETITION_THRESHOLD 2000
+#define MAX_GRAMMAR_RECURSION_DEPTH 256
//
// helpers
//
@@ -440,13 +441,14 @@ const char * llama_grammar_parser::parse_alternates(
const char * src,
const std::string & rule_name,
uint32_t rule_id,
- bool is_nested) {
+ bool is_nested,
+ uint32_t depth) {
llama_grammar_rule rule;
- const char * pos = parse_sequence(src, rule_name, rule, is_nested);
+ const char * pos = parse_sequence(src, rule_name, rule, is_nested, depth);
while (*pos == '|') {
rule.push_back({LLAMA_GRETYPE_ALT, 0});
pos = parse_space(pos + 1, true);
- pos = parse_sequence(pos, rule_name, rule, is_nested);
+ pos = parse_sequence(pos, rule_name, rule, is_nested, depth);
}
rule.push_back({LLAMA_GRETYPE_END, 0});
add_rule(rule_id, rule);
@@ -457,7 +459,8 @@ const char * llama_grammar_parser::parse_sequence(
const char * src,
const std::string & rule_name,
llama_grammar_rule & rule,
- bool is_nested) {
+ bool is_nested,
+ uint32_t depth) {
size_t last_sym_start = rule.size();
const char * pos = src;
uint64_t n_prev_rules = 1;
@@ -595,11 +598,14 @@ const char * llama_grammar_parser::parse_sequence(
n_prev_rules = 1;
rule.push_back({LLAMA_GRETYPE_RULE_REF, ref_rule_id});
} else if (*pos == '(') { // grouping
+ if (depth >= MAX_GRAMMAR_RECURSION_DEPTH) {
+ throw std::runtime_error("grammar recursion depth limit exceeded (max " + std::to_string(MAX_GRAMMAR_RECURSION_DEPTH) + "), please reduce nesting depth");
+ }
// parse nested alternates into synthesized rule
pos = parse_space(pos + 1, true);
uint32_t n_rules_before = symbol_ids.size();
uint32_t sub_rule_id = generate_symbol_id(rule_name);
- pos = parse_alternates(pos, rule_name, sub_rule_id, true);
+ pos = parse_alternates(pos, rule_name, sub_rule_id, true, depth + 1);
n_prev_rules = std::max(1u, (uint32_t)symbol_ids.size() - n_rules_before);
last_sym_start = rule.size();
// output reference to synthesized rule
diff --git a/src/llama-grammar.h b/src/llama-grammar.h
index b5a0e588e..2885f7705 100644
--- a/src/llama-grammar.h
+++ b/src/llama-grammar.h
@@ -102,13 +102,15 @@ struct llama_grammar_parser {
const char * src,
const std::string & rule_name,
uint32_t rule_id,
- bool is_nested);
+ bool is_nested,
+ uint32_t depth = 0);
const char * parse_sequence(
const char * src,
const std::string & rule_name,
llama_grammar_rule & rule,
- bool is_nested);
+ bool is_nested,
+ uint32_t depth = 0);
const char * parse_rule(const char * src);
diff --git a/tests/test-grammar-parser.cpp b/tests/test-grammar-parser.cpp
index 2ddf25bb6..a6a99520c 100644
--- a/tests/test-grammar-parser.cpp
+++ b/tests/test-grammar-parser.cpp
@@ -165,6 +165,20 @@ int main()
root ::= "a"{5000,6000}
)""");
+ // Recursion depth limit exceeded (Issue #29495)
+ {
+ std::string deep_grammar = "root ::= ";
+ int n_depth = 300;
+ for (int i = 0; i < n_depth; ++i) {
+ deep_grammar += "(";
+ }
+ deep_grammar += "\"x\"";
+ for (int i = 0; i < n_depth; ++i) {
+ deep_grammar += ")";
+ }
+ verify_failure(deep_grammar.c_str());
+ }
+
verify_parsing(R"""(
root ::= "a"{0,5000}
)""", {