Skip to content

Commit b1822e1

Browse files
committed
fix: Enhance parser recursion depth handling in macro expansion and mhchem parsing
1 parent e0a0d70 commit b1822e1

5 files changed

Lines changed: 53 additions & 7 deletions

File tree

crates/ratex-parser/src/macro_expander.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct MacroExpansion {
5555
pub struct MacroExpander<'a> {
5656
pub lexer: Lexer<'a>,
5757
pub mode: Mode,
58+
parser_recursion_depth: usize,
5859
stack: Vec<Token>,
5960
macros: MacroNamespace,
6061
expansion_count: usize,
@@ -164,6 +165,7 @@ impl<'a> MacroExpander<'a> {
164165
let mut me = Self {
165166
lexer: Lexer::new(input),
166167
mode,
168+
parser_recursion_depth: 0,
167169
stack: Vec::new(),
168170
macros: MacroNamespace::new(),
169171
expansion_count: 0,
@@ -173,6 +175,10 @@ impl<'a> MacroExpander<'a> {
173175
me
174176
}
175177

178+
pub fn set_parser_recursion_depth(&mut self, depth: usize) {
179+
self.parser_recursion_depth = depth;
180+
}
181+
176182
fn load_builtins(&mut self) {
177183
let builtins: &[(&str, &str)] = &[
178184
// ── Grouping ──
@@ -898,8 +904,12 @@ impl<'a> MacroExpander<'a> {
898904
MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
899905
let args = me.consume_args(1)?;
900906
let s = crate::mhchem::mhchem_arg_tokens_to_string(&args[0]);
901-
let tex = crate::mhchem::chem_parse_str(&s, "ce")
902-
.map_err(|e| ParseError::msg(format!("\\ce: {e}")))?;
907+
let tex = crate::mhchem::chem_parse_str_with_parser_depth(
908+
&s,
909+
"ce",
910+
me.parser_recursion_depth,
911+
)
912+
.map_err(|e| ParseError::msg(format!("\\ce: {e}")))?;
903913
Ok(lex_string_to_stack_tokens(&tex))
904914
}),
905915
);
@@ -908,8 +918,12 @@ impl<'a> MacroExpander<'a> {
908918
MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
909919
let args = me.consume_args(1)?;
910920
let s = crate::mhchem::mhchem_arg_tokens_to_string(&args[0]);
911-
let tex = crate::mhchem::chem_parse_str(&s, "pu")
912-
.map_err(|e| ParseError::msg(format!("\\pu: {e}")))?;
921+
let tex = crate::mhchem::chem_parse_str_with_parser_depth(
922+
&s,
923+
"pu",
924+
me.parser_recursion_depth,
925+
)
926+
.map_err(|e| ParseError::msg(format!("\\pu: {e}")))?;
913927
Ok(lex_string_to_stack_tokens(&tex))
914928
}),
915929
);

crates/ratex-parser/src/mhchem/engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub(crate) fn go_machine(
3838
// mhchem inputs fan out through helper machines before texify. Guard the
3939
// active engine-call depth directly so engine callers fail before building
4040
// an over-budget AST, while preserving the public parser boundary.
41-
if previous_depth >= MAX_RECURSION_DEPTH {
41+
if ctx.parser_recursion_depth + previous_depth >= MAX_RECURSION_DEPTH {
4242
return Err(MhchemError::msg("Recursion limit exceeded"));
4343
}
4444
MHCHEM_RECURSION_DEPTH.with(|depth| depth.set(previous_depth + 1));

crates/ratex-parser/src/mhchem/mod.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use serde_json::Value;
2121
/// Context for recursive `go` (used by actions).
2222
pub struct ParserCtx<'a> {
2323
pub data: &'a MhchemData,
24+
pub parser_recursion_depth: usize,
2425
}
2526

2627
impl ParserCtx<'_> {
@@ -31,8 +32,19 @@ impl ParserCtx<'_> {
3132

3233
/// Parse `\ce` / `\pu` argument to TeX fragment (wrap `\mathrm` etc. is done here).
3334
pub fn chem_parse_str(input: &str, mode: &str) -> MhchemResult<String> {
35+
chem_parse_str_with_parser_depth(input, mode, 0)
36+
}
37+
38+
pub(crate) fn chem_parse_str_with_parser_depth(
39+
input: &str,
40+
mode: &str,
41+
parser_recursion_depth: usize,
42+
) -> MhchemResult<String> {
3443
let d = data();
35-
let ctx = ParserCtx { data: d };
44+
let ctx = ParserCtx {
45+
data: d,
46+
parser_recursion_depth,
47+
};
3648
let sm = match mode {
3749
"ce" => "ce",
3850
"pu" => "pu",
@@ -108,7 +120,10 @@ mod tests {
108120
let nested_empty_ce =
109121
|depth: usize| format!("{}{}", r"\ce{".repeat(depth), "}".repeat(depth));
110122
let d = data();
111-
let ctx = ParserCtx { data: d };
123+
let ctx = ParserCtx {
124+
data: d,
125+
parser_recursion_depth: 0,
126+
};
112127

113128
assert!(ctx.go(&nested_ce(30), "ce").is_ok());
114129
let error = ctx.go(&nested_ce(31), "ce").unwrap_err();

crates/ratex-parser/src/parser.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ impl<'a> Parser<'a> {
5252
/// Return the current lookahead token (fetching from gullet if needed).
5353
pub fn fetch(&mut self) -> ParseResult<Token> {
5454
if self.next_token.is_none() {
55+
self.gullet.set_parser_recursion_depth(self.recursion_depth);
5556
self.next_token = Some(self.gullet.expand_next_token()?);
5657
}
5758
Ok(self.next_token.clone().unwrap())

crates/ratex-parser/src/tests.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,16 @@ mod recursion_limit {
12351235
format!("{}H{}", r"\ce{".repeat(depth), "}".repeat(depth))
12361236
}
12371237

1238+
fn macro_expanded_nested_ce(enclosing_depth: usize, ce_depth: usize) -> String {
1239+
format!(
1240+
r"\def\foo{{{}}}{}{}{}",
1241+
nested_ce(ce_depth),
1242+
"{".repeat(enclosing_depth),
1243+
r"\foo",
1244+
"}".repeat(enclosing_depth)
1245+
)
1246+
}
1247+
12381248
#[test]
12391249
fn prooftree_depth_is_bounded() {
12401250
assert!(parse(&unary_prooftree(31)).is_ok());
@@ -1264,6 +1274,12 @@ mod recursion_limit {
12641274
assert_recursion_limit_err(&nested_ce(32));
12651275
}
12661276

1277+
#[test]
1278+
fn mhchem_depth_accounts_for_macro_expanded_enclosing_groups() {
1279+
assert!(parse(&macro_expanded_nested_ce(10, 10)).is_ok());
1280+
assert_recursion_limit_err(&macro_expanded_nested_ce(20, 20));
1281+
}
1282+
12671283
#[test]
12681284
fn source_preflight_ignores_latex_macro_definition_bodies() {
12691285
let deep_body = braced_body(40);

0 commit comments

Comments
 (0)