diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 327c70ede28ee..10867ae230a8c 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -929,6 +929,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ omit_gdb_pretty_printer_section, Normal, template!(Word), WarnFollowing, "the `#[omit_gdb_pretty_printer_section]` attribute is just used for the Rust test suite", ), + rustc_attr!( + TEST, pattern_complexity, CrateLevel, template!(NameValueStr: "N"), + ErrorFollowing, @only_local: true, + ), ]; pub fn deprecated_attributes() -> Vec<&'static BuiltinAttribute> { diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 53254d567ccf2..17c4d81474e27 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -213,6 +213,8 @@ declare_features! ( (internal, negative_bounds, "1.71.0", None), /// Allows using `#[omit_gdb_pretty_printer_section]`. (internal, omit_gdb_pretty_printer_section, "1.5.0", None), + /// Set the maximum pattern complexity allowed (not limited by default). + (internal, pattern_complexity, "CURRENT_RUSTC_VERSION", None), /// Allows using `#[prelude_import]` on glob `use` items. (internal, prelude_import, "1.2.0", None), /// Used to identify crates that contain the profiler runtime. diff --git a/compiler/rustc_middle/src/middle/limits.rs b/compiler/rustc_middle/src/middle/limits.rs index 344ea38192e9a..4d69801274956 100644 --- a/compiler/rustc_middle/src/middle/limits.rs +++ b/compiler/rustc_middle/src/middle/limits.rs @@ -40,6 +40,13 @@ pub fn get_recursion_limit(krate_attrs: &[Attribute], sess: &Session) -> Limit { } fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: usize) -> Limit { + match get_limit_size(krate_attrs, sess, name) { + Some(size) => Limit::new(size), + None => Limit::new(default), + } +} + +pub fn get_limit_size(krate_attrs: &[Attribute], sess: &Session, name: Symbol) -> Option { for attr in krate_attrs { if !attr.has_name(name) { continue; @@ -47,7 +54,7 @@ fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: u if let Some(s) = attr.value_str() { match s.as_str().parse() { - Ok(n) => return Limit::new(n), + Ok(n) => return Some(n), Err(e) => { let value_span = attr .meta() @@ -69,5 +76,5 @@ fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: u } } } - return Limit::new(default); + None } diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index cf12b4fb912cd..2685bae4d09c8 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -17,6 +17,7 @@ use rustc_hir as hir; use rustc_hir::def::*; use rustc_hir::def_id::LocalDefId; use rustc_hir::HirId; +use rustc_middle::middle::limits::get_limit_size; use rustc_middle::thir::visit::Visitor; use rustc_middle::thir::*; use rustc_middle::ty::print::with_no_trimmed_paths; @@ -26,7 +27,7 @@ use rustc_session::lint::builtin::{ }; use rustc_session::Session; use rustc_span::hygiene::DesugaringKind; -use rustc_span::Span; +use rustc_span::{sym, Span}; pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> { let typeck_results = tcx.typeck(def_id); @@ -403,8 +404,11 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { arms: &[MatchArm<'p, 'tcx>], scrut_ty: Ty<'tcx>, ) -> Result, ErrorGuaranteed> { + let pattern_complexity_limit = + get_limit_size(cx.tcx.hir().krate_attrs(), cx.tcx.sess, sym::pattern_complexity); let report = - rustc_pattern_analysis::analyze_match(&cx, &arms, scrut_ty).map_err(|err| { + rustc_pattern_analysis::analyze_match(&cx, &arms, scrut_ty, pattern_complexity_limit) + .map_err(|err| { self.error = Err(err); err })?; diff --git a/compiler/rustc_pattern_analysis/src/lib.rs b/compiler/rustc_pattern_analysis/src/lib.rs index d4b38d260e717..4b0955699fc39 100644 --- a/compiler/rustc_pattern_analysis/src/lib.rs +++ b/compiler/rustc_pattern_analysis/src/lib.rs @@ -142,6 +142,9 @@ pub trait TypeCx: Sized + fmt::Debug { _overlaps_with: &[&DeconstructedPat], ) { } + + /// The maximum pattern complexity limit was reached. + fn complexity_exceeded(&self) -> Result<(), Self::Error>; } /// The arm of a match expression. @@ -167,10 +170,12 @@ pub fn analyze_match<'p, 'tcx>( tycx: &RustcMatchCheckCtxt<'p, 'tcx>, arms: &[rustc::MatchArm<'p, 'tcx>], scrut_ty: Ty<'tcx>, + pattern_complexity_limit: Option, ) -> Result, ErrorGuaranteed> { let scrut_ty = tycx.reveal_opaque_ty(scrut_ty); let scrut_validity = ValidityConstraint::from_bool(tycx.known_valid_scrutinee); - let report = compute_match_usefulness(tycx, arms, scrut_ty, scrut_validity)?; + let report = + compute_match_usefulness(tycx, arms, scrut_ty, scrut_validity, pattern_complexity_limit)?; // Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting // `if let`s. Only run if the match is exhaustive otherwise the error is redundant. diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs index f8839b0b59023..5f5bfa7154a74 100644 --- a/compiler/rustc_pattern_analysis/src/rustc.rs +++ b/compiler/rustc_pattern_analysis/src/rustc.rs @@ -895,6 +895,11 @@ impl<'p, 'tcx: 'p> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> { errors::OverlappingRangeEndpoints { overlap: overlaps, range: pat_span }, ); } + + fn complexity_exceeded(&self) -> Result<(), Self::Error> { + let span = self.whole_match_span.unwrap_or(self.scrut_span); + Err(self.tcx.dcx().span_err(span, "reached pattern complexity limit")) + } } /// Recursively expand this pattern into its subpatterns. Only useful for or-patterns. diff --git a/compiler/rustc_pattern_analysis/src/usefulness.rs b/compiler/rustc_pattern_analysis/src/usefulness.rs index bbe02f94c0a02..0261768d91616 100644 --- a/compiler/rustc_pattern_analysis/src/usefulness.rs +++ b/compiler/rustc_pattern_analysis/src/usefulness.rs @@ -734,6 +734,21 @@ struct UsefulnessCtxt<'a, Cx: TypeCx> { /// Collect the patterns found useful during usefulness checking. This is used to lint /// unreachable (sub)patterns. useful_subpatterns: FxHashSet, + complexity_limit: Option, + complexity_level: usize, +} + +impl<'a, Cx: TypeCx> UsefulnessCtxt<'a, Cx> { + fn increase_complexity_level(&mut self, complexity_add: usize) -> Result<(), Cx::Error> { + self.complexity_level += complexity_add; + if self + .complexity_limit + .is_some_and(|complexity_limit| complexity_limit < self.complexity_level) + { + return self.tycx.complexity_exceeded(); + } + Ok(()) + } } /// Context that provides information local to a place under investigation. @@ -1552,6 +1567,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>( } let Some(place) = matrix.head_place() else { + mcx.increase_complexity_level(matrix.rows().len())?; // The base case: there are no columns in the matrix. We are morally pattern-matching on (). // A row is useful iff it has no (unguarded) rows above it. let mut useful = true; // Whether the next row is useful. @@ -1690,8 +1706,14 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>( arms: &[MatchArm<'p, Cx>], scrut_ty: Cx::Ty, scrut_validity: ValidityConstraint, + complexity_limit: Option, ) -> Result, Cx::Error> { - let mut cx = UsefulnessCtxt { tycx, useful_subpatterns: FxHashSet::default() }; + let mut cx = UsefulnessCtxt { + tycx, + useful_subpatterns: FxHashSet::default(), + complexity_limit, + complexity_level: 0, + }; let mut matrix = Matrix::new(arms, scrut_ty, scrut_validity); let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(&mut cx, &mut matrix)?; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ee8d9ae9c5388..7e31cfc066284 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1303,6 +1303,7 @@ symbols! { pat, pat_param, path, + pattern_complexity, pattern_parentheses, phantom_data, pic, diff --git a/tests/ui/feature-gates/feature-gate-pattern-complexity.rs b/tests/ui/feature-gates/feature-gate-pattern-complexity.rs new file mode 100644 index 0000000000000..43e9a00c9a7ef --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-pattern-complexity.rs @@ -0,0 +1,6 @@ +// check that `pattern_complexity` is feature-gated + +#![pattern_complexity = "42"] +//~^ ERROR: the `#[pattern_complexity]` attribute is just used for rustc unit tests + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-pattern-complexity.stderr b/tests/ui/feature-gates/feature-gate-pattern-complexity.stderr new file mode 100644 index 0000000000000..c05e6abb01769 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-pattern-complexity.stderr @@ -0,0 +1,12 @@ +error[E0658]: the `#[pattern_complexity]` attribute is just used for rustc unit tests and will never be stable + --> $DIR/feature-gate-pattern-complexity.rs:3:1 + | +LL | #![pattern_complexity = "42"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/pattern/complexity_limit.rs b/tests/ui/pattern/complexity_limit.rs new file mode 100644 index 0000000000000..c9a3f99bccd14 --- /dev/null +++ b/tests/ui/pattern/complexity_limit.rs @@ -0,0 +1,106 @@ +#![feature(rustc_attrs)] +#![pattern_complexity = "10000"] + +#[derive(Default)] +struct BaseCommand { + field01: bool, + field02: bool, + field03: bool, + field04: bool, + field05: bool, + field06: bool, + field07: bool, + field08: bool, + field09: bool, + field10: bool, + field11: bool, + field12: bool, + field13: bool, + field14: bool, + field15: bool, + field16: bool, + field17: bool, + field18: bool, + field19: bool, + field20: bool, + field21: bool, + field22: bool, + field23: bool, + field24: bool, + field25: bool, + field26: bool, + field27: bool, + field28: bool, + field29: bool, + field30: bool, +} + +fn request_key(command: BaseCommand) { + match command { //~ ERROR: reached pattern complexity limit + BaseCommand { field01: true, .. } => {} + BaseCommand { field02: true, .. } => {} + BaseCommand { field03: true, .. } => {} + BaseCommand { field04: true, .. } => {} + BaseCommand { field05: true, .. } => {} + BaseCommand { field06: true, .. } => {} + BaseCommand { field07: true, .. } => {} + BaseCommand { field08: true, .. } => {} + BaseCommand { field09: true, .. } => {} + BaseCommand { field10: true, .. } => {} + BaseCommand { field11: true, .. } => {} + BaseCommand { field12: true, .. } => {} + BaseCommand { field13: true, .. } => {} + BaseCommand { field14: true, .. } => {} + BaseCommand { field15: true, .. } => {} + BaseCommand { field16: true, .. } => {} + BaseCommand { field17: true, .. } => {} + BaseCommand { field18: true, .. } => {} + BaseCommand { field19: true, .. } => {} + BaseCommand { field20: true, .. } => {} + BaseCommand { field21: true, .. } => {} + BaseCommand { field22: true, .. } => {} + BaseCommand { field23: true, .. } => {} + BaseCommand { field24: true, .. } => {} + BaseCommand { field25: true, .. } => {} + BaseCommand { field26: true, .. } => {} + BaseCommand { field27: true, .. } => {} + BaseCommand { field28: true, .. } => {} + BaseCommand { field29: true, .. } => {} + BaseCommand { field30: true, .. } => {} + + BaseCommand { field01: false, .. } => {} + BaseCommand { field02: false, .. } => {} + BaseCommand { field03: false, .. } => {} + BaseCommand { field04: false, .. } => {} + BaseCommand { field05: false, .. } => {} + BaseCommand { field06: false, .. } => {} + BaseCommand { field07: false, .. } => {} + BaseCommand { field08: false, .. } => {} + BaseCommand { field09: false, .. } => {} + BaseCommand { field10: false, .. } => {} + BaseCommand { field11: false, .. } => {} + BaseCommand { field12: false, .. } => {} + BaseCommand { field13: false, .. } => {} + BaseCommand { field14: false, .. } => {} + BaseCommand { field15: false, .. } => {} + BaseCommand { field16: false, .. } => {} + BaseCommand { field17: false, .. } => {} + BaseCommand { field18: false, .. } => {} + BaseCommand { field19: false, .. } => {} + BaseCommand { field20: false, .. } => {} + BaseCommand { field21: false, .. } => {} + BaseCommand { field22: false, .. } => {} + BaseCommand { field23: false, .. } => {} + BaseCommand { field24: false, .. } => {} + BaseCommand { field25: false, .. } => {} + BaseCommand { field26: false, .. } => {} + BaseCommand { field27: false, .. } => {} + BaseCommand { field28: false, .. } => {} + BaseCommand { field29: false, .. } => {} + BaseCommand { field30: false, .. } => {} + } +} + +fn main() { + request_key(BaseCommand::default()); +} diff --git a/tests/ui/pattern/complexity_limit.stderr b/tests/ui/pattern/complexity_limit.stderr new file mode 100644 index 0000000000000..08d9d40fe4626 --- /dev/null +++ b/tests/ui/pattern/complexity_limit.stderr @@ -0,0 +1,14 @@ +error: reached pattern complexity limit + --> $DIR/complexity_limit.rs:39:5 + | +LL | / match command { +LL | | BaseCommand { field01: true, .. } => {} +LL | | BaseCommand { field02: true, .. } => {} +LL | | BaseCommand { field03: true, .. } => {} +... | +LL | | BaseCommand { field30: false, .. } => {} +LL | | } + | |_____^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/pattern/usefulness/issue-118437-exponential-time-on-diagonal-match.rs b/tests/ui/pattern/usefulness/issue-118437-exponential-time-on-diagonal-match.rs index 984feef5f47e4..783512d5cca3d 100644 --- a/tests/ui/pattern/usefulness/issue-118437-exponential-time-on-diagonal-match.rs +++ b/tests/ui/pattern/usefulness/issue-118437-exponential-time-on-diagonal-match.rs @@ -1,3 +1,6 @@ +#![feature(rustc_attrs)] +#![pattern_complexity = "61"] + //@ check-pass struct BaseCommand { field01: bool,