about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs4
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-rw-r--r--compiler/rustc_log/src/lib.rs19
-rw-r--r--compiler/rustc_middle/src/middle/limits.rs11
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/check_match.rs8
-rw-r--r--compiler/rustc_pattern_analysis/src/lib.rs7
-rw-r--r--compiler/rustc_pattern_analysis/src/rustc.rs5
-rw-r--r--compiler/rustc_pattern_analysis/src/usefulness.rs24
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--library/std/src/sys/pal/wasm/atomics/thread.rs4
-rw-r--r--tests/ui/feature-gates/feature-gate-pattern-complexity.rs6
-rw-r--r--tests/ui/feature-gates/feature-gate-pattern-complexity.stderr12
-rw-r--r--tests/ui/pattern/complexity_limit.rs106
-rw-r--r--tests/ui/pattern/complexity_limit.stderr14
-rw-r--r--tests/ui/pattern/usefulness/issue-118437-exponential-time-on-diagonal-match.rs3
15 files changed, 219 insertions, 7 deletions
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 327c70ede28..10867ae230a 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 53254d567cc..17c4d81474e 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_log/src/lib.rs b/compiler/rustc_log/src/lib.rs
index 1a78f9f0f86..e4b67cde244 100644
--- a/compiler/rustc_log/src/lib.rs
+++ b/compiler/rustc_log/src/lib.rs
@@ -57,6 +57,7 @@ pub struct LoggerConfig {
     pub verbose_entry_exit: Result<String, VarError>,
     pub verbose_thread_ids: Result<String, VarError>,
     pub backtrace: Result<String, VarError>,
+    pub wraptree: Result<String, VarError>,
 }
 
 impl LoggerConfig {
@@ -67,6 +68,7 @@ impl LoggerConfig {
             verbose_entry_exit: env::var(format!("{env}_ENTRY_EXIT")),
             verbose_thread_ids: env::var(format!("{env}_THREAD_IDS")),
             backtrace: env::var(format!("{env}_BACKTRACE")),
+            wraptree: env::var(format!("{env}_WRAPTREE")),
         }
     }
 }
@@ -99,7 +101,7 @@ pub fn init_logger(cfg: LoggerConfig) -> Result<(), Error> {
         Err(_) => false,
     };
 
-    let layer = tracing_tree::HierarchicalLayer::default()
+    let mut layer = tracing_tree::HierarchicalLayer::default()
         .with_writer(io::stderr)
         .with_indent_lines(true)
         .with_ansi(color_logs)
@@ -110,6 +112,16 @@ pub fn init_logger(cfg: LoggerConfig) -> Result<(), Error> {
         .with_thread_ids(verbose_thread_ids)
         .with_thread_names(verbose_thread_ids);
 
+    match cfg.wraptree {
+        Ok(v) => match v.parse::<usize>() {
+            Ok(v) => {
+                layer = layer.with_wraparound(v);
+            }
+            Err(_) => return Err(Error::InvalidWraptree(v)),
+        },
+        Err(_) => {} // no wraptree
+    }
+
     let subscriber = tracing_subscriber::Registry::default().with(filter).with(layer);
     match cfg.backtrace {
         Ok(str) => {
@@ -164,6 +176,7 @@ pub fn stderr_isatty() -> bool {
 pub enum Error {
     InvalidColorValue(String),
     NonUnicodeColorValue,
+    InvalidWraptree(String),
 }
 
 impl std::error::Error for Error {}
@@ -179,6 +192,10 @@ impl Display for Error {
                 formatter,
                 "non-Unicode log color value: expected one of always, never, or auto",
             ),
+            Error::InvalidWraptree(value) => write!(
+                formatter,
+                "invalid log WRAPTREE value '{value}': expected a non-negative integer",
+            ),
         }
     }
 }
diff --git a/compiler/rustc_middle/src/middle/limits.rs b/compiler/rustc_middle/src/middle/limits.rs
index 344ea38192e..4d698012749 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<usize> {
     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 cf12b4fb912..2685bae4d09 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<UsefulnessReport<'p, 'tcx>, 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 d4b38d260e7..4b0955699fc 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<Self>],
     ) {
     }
+
+    /// 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<usize>,
 ) -> Result<rustc::UsefulnessReport<'p, 'tcx>, 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 f8839b0b590..5f5bfa7154a 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 bbe02f94c0a..0261768d916 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<PatId>,
+    complexity_limit: Option<usize>,
+    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<usize>,
 ) -> Result<UsefulnessReport<'p, Cx>, 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 ee8d9ae9c53..7e31cfc0662 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/library/std/src/sys/pal/wasm/atomics/thread.rs b/library/std/src/sys/pal/wasm/atomics/thread.rs
index 49f936f1449..3923ff821d9 100644
--- a/library/std/src/sys/pal/wasm/atomics/thread.rs
+++ b/library/std/src/sys/pal/wasm/atomics/thread.rs
@@ -1,4 +1,5 @@
 use crate::ffi::CStr;
+use crate::ffi::CString;
 use crate::io;
 use crate::num::NonZero;
 use crate::sys::unsupported;
@@ -17,6 +18,9 @@ impl Thread {
     pub fn yield_now() {}
 
     pub fn set_name(_name: &CStr) {}
+    pub fn get_name() -> Option<CString> {
+        None
+    }
 
     pub fn sleep(dur: Duration) {
         use crate::arch::wasm32;
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 00000000000..43e9a00c9a7
--- /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 00000000000..c05e6abb017
--- /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 00000000000..c9a3f99bccd
--- /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 00000000000..08d9d40fe46
--- /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 984feef5f47..783512d5cca 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,