Prevent macro expansion hang from exponential token growth#154968
Prevent macro expansion hang from exponential token growth#154968vincenzopalazzo wants to merge 2 commits intorust-lang:mainfrom
Conversation
Add a configurable `macro_token_limit` that caps the number of tokens allowed as input to a single `macro_rules!` expansion. This prevents recursive macros that double their output on each expansion from hanging the compiler — the token count can grow exponentially (e.g. 2^128) long before the recursion depth limit is reached. The default limit is 2^20 (~1 million) top-level tokens. Users can override it with `#![macro_token_limit = "N"]`, following the same pattern as `#![recursion_limit = "N"]`. Fixes rust-lang#95698
|
Some changes occurred in compiler/rustc_hir/src/attrs cc @jdonszelmann, @JonathanBrouwer Some changes occurred in compiler/rustc_attr_parsing cc @jdonszelmann, @JonathanBrouwer Some changes occurred in compiler/rustc_passes/src/check_attr.rs |
|
r? @jackh726 rustbot has assigned @jackh726. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
This comment has been minimized.
This comment has been minimized.
|
The job Click to see the possible cause of the failure (guessed by this bot) |
|
☔ The latest upstream changes (presumably #154958) made this pull request unmergeable. Please resolve the merge conflicts. |
|
☔ The latest upstream changes (presumably #155416) made this pull request unmergeable. Please resolve the merge conflicts. |
Summary
Fixes #95698
Recursive
macro_rules!macros can produce exponentially growing token streams that hang the compiler. While the recursion depth limit (default 128) eventually catches infinite recursion, a macro that doubles its output per expansion would produce 2^128 tokens before the depth limit fires — the compiler hangs trying to parse this astronomical input throughparse_tt.This PR adds a new configurable
macro_token_limitthat caps the number of top-level tokens allowed as input to a singlemacro_rules!expansion:#![macro_token_limit = "N"], following the same pattern as#![recursion_limit = "N"]macro_rules!(not proc macros)Why 2^20 as the default?
Based on research across real-world Rust codebases:
The 2^20 default provides ~10x headroom over typical large macros. In the pathological case (exponential doubling from 3 tokens), the limit fires at recursion depth ~19 — producing an error in ~200ms instead of hanging indefinitely. The
#![macro_token_limit]attribute provides an escape hatch for the rare legitimate case that needs more.Reproducer (previously hung the compiler)
Now produces:
Changes
rustc_span/src/symbol.rsmacro_token_limitsymbolrustc_feature/src/builtin_attrs.rsrustc_hir/src/attrs/data_structures.rsMacroTokenLimittoAttributeKindrustc_hir/src/attrs/encode_cross_crate.rsNocross-crate encodingrustc_attr_parsing/src/attributes/crate_level.rsMacroTokenLimitParserrustc_attr_parsing/src/context.rsrustc_interface/src/limits.rsget_macro_token_limit()with defaultrustc_interface/src/passes.rsrustc_expand/src/expand.rsmacro_token_limitfield toExpansionConfigrustc_expand/src/mbe/macro_rules.rsexpand_macrorustc_expand/src/errors.rsMacroInputTooLargediagnosticrustc_passes/src/check_attr.rsTest plan
tests/ui/macros/issue-95698-exponential-token-growth.rsx.py test tests/ui/macros/issue-95698-exponential-token-growth.rspasses (blessed)x.py check compiler/rustc_expandpasses