diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index 6c7e68246ea5f..ab5434f7945e1 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -23,6 +23,10 @@ expand_duplicate_matcher_binding = duplicate matcher binding .label = duplicate binding .label2 = previous binding +expand_expansion_growth_limit_reached = + expansion grow limit reached while expanding `{$descr}` + .help = consider increasing the expansion grow limit by adding a `#![expansion_growth_limit = "{$suggested_limit}"]` attribute to your crate (`{$crate_name}`) + expand_expected_comma_in_list = expected token: `,` diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index c4d2a374f0c67..0a36291982682 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -1011,6 +1011,7 @@ pub struct ExtCtxt<'a> { pub ecfg: expand::ExpansionConfig<'a>, pub num_standard_library_imports: usize, pub reduced_recursion_limit: Option, + pub reduced_expansion_growth_limit: Option, pub root_path: PathBuf, pub resolver: &'a mut dyn ResolverExpand, pub current_expansion: ExpansionData, @@ -1040,6 +1041,7 @@ impl<'a> ExtCtxt<'a> { ecfg, num_standard_library_imports: 0, reduced_recursion_limit: None, + reduced_expansion_growth_limit: None, resolver, lint_store, root_path: PathBuf::new(), diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index e3a0ae3570eb0..0db9b539ae43d 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -212,6 +212,17 @@ pub(crate) struct RecursionLimitReached<'a> { pub crate_name: &'a str, } +#[derive(Diagnostic)] +#[diag(expand_expansion_growth_limit_reached)] +#[help] +pub(crate) struct ExpansionGrowthLimitReached<'a> { + #[primary_span] + pub span: Span, + pub descr: String, + pub suggested_limit: Limit, + pub crate_name: &'a str, +} + #[derive(Diagnostic)] #[diag(expand_malformed_feature_attribute, code = "E0556")] pub(crate) struct MalformedFeatureAttribute { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index f87f4aba2b9ea..ef1edec9fdb36 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1,8 +1,8 @@ use crate::base::*; use crate::config::StripUnconfigured; use crate::errors::{ - IncompleteParse, RecursionLimitReached, RemoveExprNotSupported, RemoveNodeNotSupported, - UnsupportedKeyValue, WrongFragmentKind, + ExpansionGrowthLimitReached, IncompleteParse, RecursionLimitReached, RemoveExprNotSupported, + RemoveNodeNotSupported, UnsupportedKeyValue, WrongFragmentKind, }; use crate::hygiene::SyntaxContext; use crate::mbe::diagnostics::annotate_err_with_kind; @@ -621,6 +621,22 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.cx.trace_macros_diag(); } + fn error_expansion_growth_limit(&mut self) { + let expn_data = self.cx.current_expansion.id.expn_data(); + let suggested_limit = match self.cx.ecfg.expansion_growth_limit { + Limit(0) => Limit(2), + limit => limit * 2, + }; + + self.cx.emit_err(ExpansionGrowthLimitReached { + span: expn_data.call_site, + descr: expn_data.kind.descr(), + suggested_limit, + crate_name: &self.cx.ecfg.crate_name, + }); + self.cx.trace_macros_diag(); + } + /// A macro's expansion does not fit in this fragment kind. /// For example, a non-type macro in a type position. fn error_wrong_fragment_kind(&mut self, kind: AstFragmentKind, mac: &ast::MacCall, span: Span) { @@ -629,6 +645,21 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.cx.trace_macros_diag(); } + fn reduce_expansion_growth_limit(&mut self, tokens_solved_size: usize) -> Result<(), ()> { + let expansion_limit = + self.cx.reduced_expansion_growth_limit.unwrap_or(self.cx.ecfg.expansion_growth_limit); + if !expansion_limit.value_within_limit(tokens_solved_size) { + if self.cx.reduced_expansion_growth_limit.is_none() { + self.error_expansion_growth_limit(); + } + + // Reduce the recursion limit by half each time it triggers. + self.cx.reduced_recursion_limit = Some(expansion_limit / 2); + return Err(()); + } + Ok(()) + } + fn expand_invoc( &mut self, invoc: Invocation, @@ -658,6 +689,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span) } SyntaxExtensionKind::LegacyBang(expander) => { + if self.reduce_expansion_growth_limit(mac.args.tokens.len()).is_err() { + return ExpandResult::Ready(fragment_kind.dummy(span)); + } let tok_result = expander.expand(self.cx, span, mac.args.tokens.clone()); let result = if let Some(result) = fragment_kind.make_from(tok_result) { result @@ -1979,6 +2013,7 @@ pub struct ExpansionConfig<'feat> { pub crate_name: String, pub features: &'feat Features, pub recursion_limit: Limit, + pub expansion_growth_limit: Limit, pub trace_mac: bool, /// If false, strip `#[test]` nodes pub should_test: bool, @@ -1994,6 +2029,7 @@ impl ExpansionConfig<'_> { crate_name, features, recursion_limit: Limit::new(1024), + expansion_growth_limit: Limit::new(1000000), trace_mac: false, should_test: false, span_debug: false, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 04ebe22a9eb8a..943a1c507d87c 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -355,6 +355,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Limits: ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), + ungated!(expansion_growth_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), ungated!(type_length_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), gated!( move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index e5ae6d5b5d6fb..8da224bb8a322 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -212,10 +212,12 @@ fn configure_and_expand( // Create the config for macro expansion let recursion_limit = get_recursion_limit(pre_configured_attrs, sess); + let expansion_growth_limit = get_expansion_growth_limit(&krate.attrs, sess); let cfg = rustc_expand::expand::ExpansionConfig { crate_name: crate_name.to_string(), features, recursion_limit, + expansion_growth_limit, trace_mac: sess.opts.unstable_opts.trace_macros, should_test: sess.is_test_crate(), span_debug: sess.opts.unstable_opts.span_debug, @@ -1007,3 +1009,17 @@ fn get_recursion_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit } rustc_middle::middle::limits::get_recursion_limit(krate_attrs, sess) } + +fn get_expansion_growth_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit { + if let Some(attr) = krate_attrs + .iter() + .find(|attr| attr.has_name(sym::expansion_growth_limit) && attr.value_str().is_none()) + { + validate_attr::emit_fatal_malformed_builtin_attribute( + &sess.parse_sess, + attr, + sym::expansion_growth_limit, + ); + } + rustc_middle::middle::limits::get_expansion_growth_limit(krate_attrs, sess) +} diff --git a/compiler/rustc_middle/src/middle/limits.rs b/compiler/rustc_middle/src/middle/limits.rs index d4f023958d6fd..dd640f4c314bb 100644 --- a/compiler/rustc_middle/src/middle/limits.rs +++ b/compiler/rustc_middle/src/middle/limits.rs @@ -21,6 +21,7 @@ use std::num::IntErrorKind; pub fn provide(providers: &mut Providers) { providers.limits = |tcx, ()| Limits { recursion_limit: get_recursion_limit(tcx.hir().krate_attrs(), tcx.sess), + expansion_growth_limit: get_expansion_growth_limit(tcx.hir().krate_attrs(), tcx.sess), move_size_limit: get_limit( tcx.hir().krate_attrs(), tcx.sess, @@ -40,6 +41,10 @@ pub fn get_recursion_limit(krate_attrs: &[Attribute], sess: &Session) -> Limit { get_limit(krate_attrs, sess, sym::recursion_limit, 128) } +pub fn get_expansion_growth_limit(krate_attrs: &[Attribute], sess: &Session) -> Limit { + get_limit(krate_attrs, sess, sym::expansion_growth_limit, 1000000) +} + fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: usize) -> Limit { for attr in krate_attrs { if !attr.has_name(name) { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index f748404875759..d3e97e24e8dbd 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1165,6 +1165,10 @@ impl<'tcx> TyCtxt<'tcx> { self.limits(()).recursion_limit } + pub fn expansion_growth_limit(self) -> Limit { + self.limits(()).expansion_growth_limit + } + pub fn move_size_limit(self) -> Limit { self.limits(()).move_size_limit } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 9bff9017881a4..504ff640baaec 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -130,6 +130,9 @@ pub struct Limits { pub move_size_limit: Limit, /// The maximum length of types during monomorphization. pub type_length_limit: Limit, + /// The maximum tokens limit for potentially infinitely resolving + /// a macros that add infinite tokens inside the buffer. + pub expansion_growth_limit: Limit, } pub struct CompilerIO { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 382754be2ca83..6216e5a533351 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -700,6 +700,7 @@ symbols! { existential_type, exp2f32, exp2f64, + expansion_growth_limit, expect, expected, expf32, diff --git a/tests/ui/limits/issue-95698.rs b/tests/ui/limits/issue-95698.rs new file mode 100644 index 0000000000000..8aef8f1ea9f6b --- /dev/null +++ b/tests/ui/limits/issue-95698.rs @@ -0,0 +1,25 @@ +// check-fail + +// issue #95698 +macro_rules! from_cow_impls { + ($( $from: ty ),+ $(,)? ) => { + // recursion call + from_cow_impls!( + $( $from, Cow::from ),+ + ); + //~^^^ ERROR expansion grow limit reached while expanding `from_cow_impls!` + }; + + ($( $from: ty, $normalizer: expr ),+ $(,)? ) => { + $( impl<'a> From<$from> for LhsValue<'a> { + fn from(val: $from) -> Self { + LhsValue::Bytes($normalizer(val)) + } + } )+ + }; +} + +from_cow_impls!( + &'a [u8], /*callback,*/ + Vec, /*callback,*/ +); diff --git a/tests/ui/limits/issue-95698.stderr b/tests/ui/limits/issue-95698.stderr new file mode 100644 index 0000000000000..2391780284ed6 --- /dev/null +++ b/tests/ui/limits/issue-95698.stderr @@ -0,0 +1,19 @@ +error: expansion grow limit reached while expanding `from_cow_impls!` + --> $DIR/issue-95698.rs:7:9 + | +LL | / from_cow_impls!( +LL | | $( $from, Cow::from ),+ +LL | | ); + | |_________^ +... +LL | / from_cow_impls!( +LL | | &'a [u8], /*callback,*/ +LL | | Vec, /*callback,*/ +LL | | ); + | |_- in this macro invocation + | + = help: consider increasing the expansion grow limit by adding a `#![expansion_growth_limit = "2000000"]` attribute to your crate (`issue_95698`) + = note: this error originates in the macro `from_cow_impls` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error +