Skip to content

Commit 7d8f74f

Browse files
Rollup merge of #121917 - GuillaumeGomez:pattern-complexity_limit.rs, r=Nadrieril
Add new `pattern_complexity` attribute to add possibility to limit and check recursion in pattern matching Needed for rust-lang/rust-analyzer#9528. This PR adds a new attribute only available when running rust testsuite called `pattern_complexity` which allows to set the maximum recursion for the pattern matching. It is quite useful to ensure the complexity doesn't grow, like in `tests/ui/pattern/usefulness/issue-118437-exponential-time-on-diagonal-match.rs`. r? `@Nadrieril`
2 parents 1090205 + f04c5c5 commit 7d8f74f

File tree

13 files changed

+197
-6
lines changed

13 files changed

+197
-6
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
929929
omit_gdb_pretty_printer_section, Normal, template!(Word), WarnFollowing,
930930
"the `#[omit_gdb_pretty_printer_section]` attribute is just used for the Rust test suite",
931931
),
932+
rustc_attr!(
933+
TEST, pattern_complexity, CrateLevel, template!(NameValueStr: "N"),
934+
ErrorFollowing, @only_local: true,
935+
),
932936
];
933937

934938
pub fn deprecated_attributes() -> Vec<&'static BuiltinAttribute> {

compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ declare_features! (
213213
(internal, negative_bounds, "1.71.0", None),
214214
/// Allows using `#[omit_gdb_pretty_printer_section]`.
215215
(internal, omit_gdb_pretty_printer_section, "1.5.0", None),
216+
/// Set the maximum pattern complexity allowed (not limited by default).
217+
(internal, pattern_complexity, "CURRENT_RUSTC_VERSION", None),
216218
/// Allows using `#[prelude_import]` on glob `use` items.
217219
(internal, prelude_import, "1.2.0", None),
218220
/// Used to identify crates that contain the profiler runtime.

compiler/rustc_middle/src/middle/limits.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,21 @@ pub fn get_recursion_limit(krate_attrs: &[Attribute], sess: &Session) -> Limit {
4040
}
4141

4242
fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: usize) -> Limit {
43+
match get_limit_size(krate_attrs, sess, name) {
44+
Some(size) => Limit::new(size),
45+
None => Limit::new(default),
46+
}
47+
}
48+
49+
pub fn get_limit_size(krate_attrs: &[Attribute], sess: &Session, name: Symbol) -> Option<usize> {
4350
for attr in krate_attrs {
4451
if !attr.has_name(name) {
4552
continue;
4653
}
4754

4855
if let Some(s) = attr.value_str() {
4956
match s.as_str().parse() {
50-
Ok(n) => return Limit::new(n),
57+
Ok(n) => return Some(n),
5158
Err(e) => {
5259
let value_span = attr
5360
.meta()
@@ -69,5 +76,5 @@ fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: u
6976
}
7077
}
7178
}
72-
return Limit::new(default);
79+
None
7380
}

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rustc_hir as hir;
1717
use rustc_hir::def::*;
1818
use rustc_hir::def_id::LocalDefId;
1919
use rustc_hir::HirId;
20+
use rustc_middle::middle::limits::get_limit_size;
2021
use rustc_middle::thir::visit::Visitor;
2122
use rustc_middle::thir::*;
2223
use rustc_middle::ty::print::with_no_trimmed_paths;
@@ -26,7 +27,7 @@ use rustc_session::lint::builtin::{
2627
};
2728
use rustc_session::Session;
2829
use rustc_span::hygiene::DesugaringKind;
29-
use rustc_span::Span;
30+
use rustc_span::{sym, Span};
3031

3132
pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
3233
let typeck_results = tcx.typeck(def_id);
@@ -403,8 +404,11 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
403404
arms: &[MatchArm<'p, 'tcx>],
404405
scrut_ty: Ty<'tcx>,
405406
) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
407+
let pattern_complexity_limit =
408+
get_limit_size(cx.tcx.hir().krate_attrs(), cx.tcx.sess, sym::pattern_complexity);
406409
let report =
407-
rustc_pattern_analysis::analyze_match(&cx, &arms, scrut_ty).map_err(|err| {
410+
rustc_pattern_analysis::analyze_match(&cx, &arms, scrut_ty, pattern_complexity_limit)
411+
.map_err(|err| {
408412
self.error = Err(err);
409413
err
410414
})?;

compiler/rustc_pattern_analysis/src/lib.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ pub trait TypeCx: Sized + fmt::Debug {
142142
_overlaps_with: &[&DeconstructedPat<Self>],
143143
) {
144144
}
145+
146+
/// The maximum pattern complexity limit was reached.
147+
fn complexity_exceeded(&self) -> Result<(), Self::Error>;
145148
}
146149

147150
/// The arm of a match expression.
@@ -167,10 +170,12 @@ pub fn analyze_match<'p, 'tcx>(
167170
tycx: &RustcMatchCheckCtxt<'p, 'tcx>,
168171
arms: &[rustc::MatchArm<'p, 'tcx>],
169172
scrut_ty: Ty<'tcx>,
173+
pattern_complexity_limit: Option<usize>,
170174
) -> Result<rustc::UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
171175
let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
172176
let scrut_validity = ValidityConstraint::from_bool(tycx.known_valid_scrutinee);
173-
let report = compute_match_usefulness(tycx, arms, scrut_ty, scrut_validity)?;
177+
let report =
178+
compute_match_usefulness(tycx, arms, scrut_ty, scrut_validity, pattern_complexity_limit)?;
174179

175180
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
176181
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.

compiler/rustc_pattern_analysis/src/rustc.rs

+5
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,11 @@ impl<'p, 'tcx: 'p> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> {
895895
errors::OverlappingRangeEndpoints { overlap: overlaps, range: pat_span },
896896
);
897897
}
898+
899+
fn complexity_exceeded(&self) -> Result<(), Self::Error> {
900+
let span = self.whole_match_span.unwrap_or(self.scrut_span);
901+
Err(self.tcx.dcx().span_err(span, "reached pattern complexity limit"))
902+
}
898903
}
899904

900905
/// Recursively expand this pattern into its subpatterns. Only useful for or-patterns.

compiler/rustc_pattern_analysis/src/usefulness.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,21 @@ struct UsefulnessCtxt<'a, Cx: TypeCx> {
734734
/// Collect the patterns found useful during usefulness checking. This is used to lint
735735
/// unreachable (sub)patterns.
736736
useful_subpatterns: FxHashSet<PatId>,
737+
complexity_limit: Option<usize>,
738+
complexity_level: usize,
739+
}
740+
741+
impl<'a, Cx: TypeCx> UsefulnessCtxt<'a, Cx> {
742+
fn increase_complexity_level(&mut self, complexity_add: usize) -> Result<(), Cx::Error> {
743+
self.complexity_level += complexity_add;
744+
if self
745+
.complexity_limit
746+
.is_some_and(|complexity_limit| complexity_limit < self.complexity_level)
747+
{
748+
return self.tycx.complexity_exceeded();
749+
}
750+
Ok(())
751+
}
737752
}
738753

739754
/// Context that provides information local to a place under investigation.
@@ -1552,6 +1567,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
15521567
}
15531568

15541569
let Some(place) = matrix.head_place() else {
1570+
mcx.increase_complexity_level(matrix.rows().len())?;
15551571
// The base case: there are no columns in the matrix. We are morally pattern-matching on ().
15561572
// A row is useful iff it has no (unguarded) rows above it.
15571573
let mut useful = true; // Whether the next row is useful.
@@ -1690,8 +1706,14 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
16901706
arms: &[MatchArm<'p, Cx>],
16911707
scrut_ty: Cx::Ty,
16921708
scrut_validity: ValidityConstraint,
1709+
complexity_limit: Option<usize>,
16931710
) -> Result<UsefulnessReport<'p, Cx>, Cx::Error> {
1694-
let mut cx = UsefulnessCtxt { tycx, useful_subpatterns: FxHashSet::default() };
1711+
let mut cx = UsefulnessCtxt {
1712+
tycx,
1713+
useful_subpatterns: FxHashSet::default(),
1714+
complexity_limit,
1715+
complexity_level: 0,
1716+
};
16951717
let mut matrix = Matrix::new(arms, scrut_ty, scrut_validity);
16961718
let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(&mut cx, &mut matrix)?;
16971719

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,7 @@ symbols! {
13031303
pat,
13041304
pat_param,
13051305
path,
1306+
pattern_complexity,
13061307
pattern_parentheses,
13071308
phantom_data,
13081309
pic,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// check that `pattern_complexity` is feature-gated
2+
3+
#![pattern_complexity = "42"]
4+
//~^ ERROR: the `#[pattern_complexity]` attribute is just used for rustc unit tests
5+
6+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0658]: the `#[pattern_complexity]` attribute is just used for rustc unit tests and will never be stable
2+
--> $DIR/feature-gate-pattern-complexity.rs:3:1
3+
|
4+
LL | #![pattern_complexity = "42"]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: add `#![feature(rustc_attrs)]` to the crate attributes to enable
8+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
9+
10+
error: aborting due to 1 previous error
11+
12+
For more information about this error, try `rustc --explain E0658`.

tests/ui/pattern/complexity_limit.rs

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#![feature(rustc_attrs)]
2+
#![pattern_complexity = "10000"]
3+
4+
#[derive(Default)]
5+
struct BaseCommand {
6+
field01: bool,
7+
field02: bool,
8+
field03: bool,
9+
field04: bool,
10+
field05: bool,
11+
field06: bool,
12+
field07: bool,
13+
field08: bool,
14+
field09: bool,
15+
field10: bool,
16+
field11: bool,
17+
field12: bool,
18+
field13: bool,
19+
field14: bool,
20+
field15: bool,
21+
field16: bool,
22+
field17: bool,
23+
field18: bool,
24+
field19: bool,
25+
field20: bool,
26+
field21: bool,
27+
field22: bool,
28+
field23: bool,
29+
field24: bool,
30+
field25: bool,
31+
field26: bool,
32+
field27: bool,
33+
field28: bool,
34+
field29: bool,
35+
field30: bool,
36+
}
37+
38+
fn request_key(command: BaseCommand) {
39+
match command { //~ ERROR: reached pattern complexity limit
40+
BaseCommand { field01: true, .. } => {}
41+
BaseCommand { field02: true, .. } => {}
42+
BaseCommand { field03: true, .. } => {}
43+
BaseCommand { field04: true, .. } => {}
44+
BaseCommand { field05: true, .. } => {}
45+
BaseCommand { field06: true, .. } => {}
46+
BaseCommand { field07: true, .. } => {}
47+
BaseCommand { field08: true, .. } => {}
48+
BaseCommand { field09: true, .. } => {}
49+
BaseCommand { field10: true, .. } => {}
50+
BaseCommand { field11: true, .. } => {}
51+
BaseCommand { field12: true, .. } => {}
52+
BaseCommand { field13: true, .. } => {}
53+
BaseCommand { field14: true, .. } => {}
54+
BaseCommand { field15: true, .. } => {}
55+
BaseCommand { field16: true, .. } => {}
56+
BaseCommand { field17: true, .. } => {}
57+
BaseCommand { field18: true, .. } => {}
58+
BaseCommand { field19: true, .. } => {}
59+
BaseCommand { field20: true, .. } => {}
60+
BaseCommand { field21: true, .. } => {}
61+
BaseCommand { field22: true, .. } => {}
62+
BaseCommand { field23: true, .. } => {}
63+
BaseCommand { field24: true, .. } => {}
64+
BaseCommand { field25: true, .. } => {}
65+
BaseCommand { field26: true, .. } => {}
66+
BaseCommand { field27: true, .. } => {}
67+
BaseCommand { field28: true, .. } => {}
68+
BaseCommand { field29: true, .. } => {}
69+
BaseCommand { field30: true, .. } => {}
70+
71+
BaseCommand { field01: false, .. } => {}
72+
BaseCommand { field02: false, .. } => {}
73+
BaseCommand { field03: false, .. } => {}
74+
BaseCommand { field04: false, .. } => {}
75+
BaseCommand { field05: false, .. } => {}
76+
BaseCommand { field06: false, .. } => {}
77+
BaseCommand { field07: false, .. } => {}
78+
BaseCommand { field08: false, .. } => {}
79+
BaseCommand { field09: false, .. } => {}
80+
BaseCommand { field10: false, .. } => {}
81+
BaseCommand { field11: false, .. } => {}
82+
BaseCommand { field12: false, .. } => {}
83+
BaseCommand { field13: false, .. } => {}
84+
BaseCommand { field14: false, .. } => {}
85+
BaseCommand { field15: false, .. } => {}
86+
BaseCommand { field16: false, .. } => {}
87+
BaseCommand { field17: false, .. } => {}
88+
BaseCommand { field18: false, .. } => {}
89+
BaseCommand { field19: false, .. } => {}
90+
BaseCommand { field20: false, .. } => {}
91+
BaseCommand { field21: false, .. } => {}
92+
BaseCommand { field22: false, .. } => {}
93+
BaseCommand { field23: false, .. } => {}
94+
BaseCommand { field24: false, .. } => {}
95+
BaseCommand { field25: false, .. } => {}
96+
BaseCommand { field26: false, .. } => {}
97+
BaseCommand { field27: false, .. } => {}
98+
BaseCommand { field28: false, .. } => {}
99+
BaseCommand { field29: false, .. } => {}
100+
BaseCommand { field30: false, .. } => {}
101+
}
102+
}
103+
104+
fn main() {
105+
request_key(BaseCommand::default());
106+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: reached pattern complexity limit
2+
--> $DIR/complexity_limit.rs:39:5
3+
|
4+
LL | / match command {
5+
LL | | BaseCommand { field01: true, .. } => {}
6+
LL | | BaseCommand { field02: true, .. } => {}
7+
LL | | BaseCommand { field03: true, .. } => {}
8+
... |
9+
LL | | BaseCommand { field30: false, .. } => {}
10+
LL | | }
11+
| |_____^
12+
13+
error: aborting due to 1 previous error
14+

tests/ui/pattern/usefulness/issue-118437-exponential-time-on-diagonal-match.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#![feature(rustc_attrs)]
2+
#![pattern_complexity = "61"]
3+
14
//@ check-pass
25
struct BaseCommand {
36
field01: bool,

0 commit comments

Comments
 (0)