about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2023-10-24 22:22:52 +0000
committerEsteban Küber <esteban@kuber.com.ar>2023-10-25 19:07:34 +0000
commit2dec1bc685c58747c81391b1c5a53ba91400c50d (patch)
treec53dd4a55d587f3ae8627a0bc28494d3a4a17ca4
parent855444ec54d1a745c5c53375792957170244e7c3 (diff)
downloadrust-2dec1bc685c58747c81391b1c5a53ba91400c50d.tar.gz
rust-2dec1bc685c58747c81391b1c5a53ba91400c50d.zip
Avoid unbounded O(n^2) when parsing nested type args
When encountering code like `f::<f::<f::<f::<f::<f::<f::<f::<...` with
unmatched closing angle brackets, add a linear check that avoids the
exponential behavior of the parse recovery mechanism.

Fix #117080.
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs6
-rw-r--r--compiler/rustc_parse/src/parser/path.rs25
-rw-r--r--tests/ui/parser/deep-unmatched-angle-brackets.rs17
-rw-r--r--tests/ui/parser/deep-unmatched-angle-brackets.stderr13
4 files changed, 55 insertions, 6 deletions
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 597303cae73..41b19ecb63a 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -159,8 +159,9 @@ pub struct Parser<'a> {
     /// appropriately.
     ///
     /// See the comments in the `parse_path_segment` function for more details.
-    unmatched_angle_bracket_count: u32,
-    max_angle_bracket_count: u32,
+    unmatched_angle_bracket_count: u16,
+    max_angle_bracket_count: u16,
+    angle_bracket_nesting: u16,
 
     last_unexpected_token_span: Option<Span>,
     /// If present, this `Parser` is not parsing Rust code but rather a macro call.
@@ -394,6 +395,7 @@ impl<'a> Parser<'a> {
             break_last_token: false,
             unmatched_angle_bracket_count: 0,
             max_angle_bracket_count: 0,
+            angle_bracket_nesting: 0,
             last_unexpected_token_span: None,
             subparser_name,
             capture_state: CaptureState {
diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs
index 2fcb9a78cfd..4969e672a72 100644
--- a/compiler/rustc_parse/src/parser/path.rs
+++ b/compiler/rustc_parse/src/parser/path.rs
@@ -487,10 +487,24 @@ impl<'a> Parser<'a> {
         // Take a snapshot before attempting to parse - we can restore this later.
         let snapshot = is_first_invocation.then(|| self.clone());
 
+        self.angle_bracket_nesting += 1;
         debug!("parse_generic_args_with_leading_angle_bracket_recovery: (snapshotting)");
         match self.parse_angle_args(ty_generics) {
-            Ok(args) => Ok(args),
+            Ok(args) => {
+                self.angle_bracket_nesting -= 1;
+                Ok(args)
+            }
+            Err(mut e) if self.angle_bracket_nesting > 10 => {
+                self.angle_bracket_nesting -= 1;
+                // When encountering severely malformed code where there are several levels of
+                // nested unclosed angle args (`f::<f::<f::<f::<...`), we avoid severe O(n^2)
+                // behavior by bailing out earlier (#117080).
+                e.emit();
+                rustc_errors::FatalError.raise();
+            }
             Err(e) if is_first_invocation && self.unmatched_angle_bracket_count > 0 => {
+                self.angle_bracket_nesting -= 1;
+
                 // Swap `self` with our backup of the parser state before attempting to parse
                 // generic arguments.
                 let snapshot = mem::replace(self, snapshot.unwrap());
@@ -520,8 +534,8 @@ impl<'a> Parser<'a> {
                     // Make a span over ${unmatched angle bracket count} characters.
                     // This is safe because `all_angle_brackets` ensures that there are only `<`s,
                     // i.e. no multibyte characters, in this range.
-                    let span =
-                        lo.with_hi(lo.lo() + BytePos(snapshot.unmatched_angle_bracket_count));
+                    let span = lo
+                        .with_hi(lo.lo() + BytePos(snapshot.unmatched_angle_bracket_count.into()));
                     self.sess.emit_err(errors::UnmatchedAngle {
                         span,
                         plural: snapshot.unmatched_angle_bracket_count > 1,
@@ -531,7 +545,10 @@ impl<'a> Parser<'a> {
                     self.parse_angle_args(ty_generics)
                 }
             }
-            Err(e) => Err(e),
+            Err(e) => {
+                self.angle_bracket_nesting -= 1;
+                Err(e)
+            }
         }
     }
 
diff --git a/tests/ui/parser/deep-unmatched-angle-brackets.rs b/tests/ui/parser/deep-unmatched-angle-brackets.rs
new file mode 100644
index 00000000000..f8d490e1c5e
--- /dev/null
+++ b/tests/ui/parser/deep-unmatched-angle-brackets.rs
@@ -0,0 +1,17 @@
+trait Mul<T> {
+    type Output;
+}
+trait Matrix: Mul<<Self as Matrix>::Row, Output = ()> {
+    type Row;
+    type Transpose: Matrix<Row = Self::Row>;
+}
+fn is_mul<S, T: Mul<S, Output = ()>>() {}
+fn f<T: Matrix>() {
+    is_mul::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<
+        f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<
+        f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<
+        f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::
+        <f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<>();
+    //~^ ERROR expected one of `!`, `+`, `,`, `::`, or `>`, found `(`
+}
+fn main() {}
diff --git a/tests/ui/parser/deep-unmatched-angle-brackets.stderr b/tests/ui/parser/deep-unmatched-angle-brackets.stderr
new file mode 100644
index 00000000000..1f285037482
--- /dev/null
+++ b/tests/ui/parser/deep-unmatched-angle-brackets.stderr
@@ -0,0 +1,13 @@
+error: expected one of `!`, `+`, `,`, `::`, or `>`, found `(`
+  --> $DIR/deep-unmatched-angle-brackets.rs:14:63
+   |
+LL |         <f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<>();
+   |                                                               ^ expected one of `!`, `+`, `,`, `::`, or `>`
+   |
+help: you might have meant to end the type parameters here
+   |
+LL |         <f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<f::<>>();
+   |                                                               +
+
+error: aborting due to previous error
+