grammar: limit recursion depth to prevent stack overflow (#29495) (#29755)

This commit is contained in:
Hrushikesh Ramilla authored and GitHub committed 2026-10-25 12:10:10 +01:00
1 parent fa3645feb5
commit f1168ac417
3 files changed
+29 -7

No files matched your search

+11 -5
View File
@@ -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
+4 -2
View File
@@ -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);
+14
View File
@@ -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}
)""", {