From 5b01b76410e63ab704d518e86848a8b29c665db6 Mon Sep 17 00:00:00 2001 From: Konstantin Vyatkin Date: Mon, 20 Jul 2026 21:41:48 +0200 Subject: [PATCH] fix(parser): prevent adaptive-set stack overflow --- src/atn/parser_atn.rs | 3 +- src/parser.rs | 70 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/atn/parser_atn.rs b/src/atn/parser_atn.rs index d2e14812..ae9effb4 100644 --- a/src/atn/parser_atn.rs +++ b/src/atn/parser_atn.rs @@ -983,7 +983,8 @@ impl<'a> ParserIntervalSet<'a> { self.len == 0 } - #[inline(always)] + // Forced inlining bloats recursive recognizer frames in unoptimized builds. + #[inline] pub fn contains(self, value: i32) -> bool { let hit = match self.kind { ParserTokenSetKind::Inline128 | ParserTokenSetKind::Dense => { diff --git a/src/parser.rs b/src/parser.rs index c3ee0a44..176fa055 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -12417,6 +12417,53 @@ mod tests { builder.finish().expect("valid packed parser ATN") } + fn nested_rule_chain_atn(depth: usize) -> Atn { + assert!(depth > 0); + let mut atn = ParserAtnBuilder::new(1); + let mut starts = Vec::with_capacity(depth); + let mut stops = Vec::with_capacity(depth); + for rule_index in 0..depth { + starts.push( + atn.add_state(AtnStateKind::RuleStart, Some(rule_index)) + .expect("rule start") + .index(), + ); + } + for rule_index in 0..depth { + stops.push( + atn.add_state(AtnStateKind::RuleStop, Some(rule_index)) + .expect("rule stop") + .index(), + ); + } + atn.set_rule_to_start_state(starts.clone()) + .expect("rule start states"); + atn.set_rule_to_stop_state(stops.clone()) + .expect("rule stop states"); + for rule_index in 0..depth - 1 { + atn.add_transition( + starts[rule_index], + ParserTransitionSpec::Rule { + target: starts[rule_index + 1], + rule_index: rule_index + 1, + follow_state: stops[rule_index], + precedence: 0, + }, + ) + .expect("nested rule transition"); + } + let token_set = atn.add_interval_set([(1, 1)]).expect("token set"); + atn.add_transition( + starts[depth - 1], + ParserTransitionSpec::Set { + target: stops[depth - 1], + set: token_set, + }, + ) + .expect("terminal set transition"); + finish_atn(atn) + } + fn ordinary_star_loop_atn() -> Atn { let mut atn = ParserAtnBuilder::new(2); for (state_number, kind, rule_index) in [ @@ -15330,6 +15377,29 @@ mod tests { .expect("deferred rules should materialize without recursion"); } + #[test] + fn nested_rule_chain_with_adaptive_set_fits_default_stack() { + // This depth separates the corrected frame from v0.14.0 on a 2 MiB stack. + const DEPTH: usize = 130; + const STACK_SIZE: usize = 2 * 1024 * 1024; + + std::thread::Builder::new() + .name("nested-adaptive-set-rules".to_owned()) + .stack_size(STACK_SIZE) + .spawn(|| { + let atn = nested_rule_chain_atn(DEPTH); + let mut parser = mini_parser(vec![TestToken::new(1).with_text("x")]); + parser.set_build_parse_trees(false); + parser + .parse_atn_rule(&atn, 0) + .expect("nested rule chain should parse on the default-sized stack"); + assert_eq!(parser.input.index(), 1); + }) + .expect("small-stack thread should start") + .join() + .expect("nested rule chain should not overflow its stack"); + } + #[test] fn ambiguous_ordinary_repetition_merges_equivalent_coordinates() { const REPETITIONS: usize = 64;