about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libsyntax/ext/tt/macro_rules.rs39
-rw-r--r--src/test/compile-fail/issue-5067.rs62
2 files changed, 100 insertions, 1 deletions
diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs
index d222de2dd36..9f4c0b5eb80 100644
--- a/src/libsyntax/ext/tt/macro_rules.rs
+++ b/src/libsyntax/ext/tt/macro_rules.rs
@@ -243,7 +243,7 @@ pub fn compile(sess: &ParseSess, def: &ast::MacroDef) -> SyntaxExtension {
                     (**tt).clone()
                 }
                 _ => sess.span_diagnostic.span_bug(def.span, "wrong-structured lhs")
-            }).collect()
+            }).collect::<Vec<TokenTree>>()
         }
         _ => sess.span_diagnostic.span_bug(def.span, "wrong-structured lhs")
     };
@@ -262,6 +262,11 @@ pub fn compile(sess: &ParseSess, def: &ast::MacroDef) -> SyntaxExtension {
         valid &= check_rhs(sess, rhs);
     }
 
+    // don't abort iteration early, so that errors for multiple lhses can be reported
+    for lhs in &lhses {
+        valid &= check_lhs_no_empty_seq(sess, &[lhs.clone()])
+    }
+
     let exp: Box<_> = Box::new(MacroRulesMacroExpander {
         name: def.ident,
         imported_from: def.imported_from,
@@ -288,6 +293,38 @@ fn check_lhs_nt_follows(sess: &ParseSess, lhs: &TokenTree) -> bool {
     // after parsing/expansion. we can report every error in every macro this way.
 }
 
+/// Check that the lhs contains no repetition which could match an empty token
+/// tree, because then the matcher would hang indefinitely.
+fn check_lhs_no_empty_seq(sess: &ParseSess, tts: &[TokenTree]) -> bool {
+    for tt in tts {
+        match *tt {
+            TokenTree::Token(_, _) => (),
+            TokenTree::Delimited(_, ref del) => if !check_lhs_no_empty_seq(sess, &del.tts) {
+                return false;
+            },
+            TokenTree::Sequence(span, ref seq) => {
+                if seq.separator.is_none() {
+                    if seq.tts.iter().all(|seq_tt| {
+                        match *seq_tt {
+                            TokenTree::Sequence(_, ref sub_seq) =>
+                                sub_seq.op == tokenstream::KleeneOp::ZeroOrMore,
+                            _ => false,
+                        }
+                    }) {
+                        sess.span_diagnostic.span_err(span, "repetition matches empty token tree");
+                        return false;
+                    }
+                }
+                if !check_lhs_no_empty_seq(sess, &seq.tts) {
+                    return false;
+                }
+            }
+        }
+    }
+
+    true
+}
+
 fn check_rhs(sess: &ParseSess, rhs: &TokenTree) -> bool {
     match *rhs {
         TokenTree::Delimited(..) => return true,
diff --git a/src/test/compile-fail/issue-5067.rs b/src/test/compile-fail/issue-5067.rs
new file mode 100644
index 00000000000..b7b5553dc74
--- /dev/null
+++ b/src/test/compile-fail/issue-5067.rs
@@ -0,0 +1,62 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+macro_rules! foo {
+    ( $()* ) => {};
+    //~^ ERROR repetition matches empty token tree
+    ( $()+ ) => {};
+    //~^ ERROR repetition matches empty token tree
+
+    ( $(),* ) => {}; // PASS
+    ( $(),+ ) => {}; // PASS
+
+    ( [$()*] ) => {};
+    //~^ ERROR repetition matches empty token tree
+    ( [$()+] ) => {};
+    //~^ ERROR repetition matches empty token tree
+
+    ( [$(),*] ) => {}; // PASS
+    ( [$(),+] ) => {}; // PASS
+
+    ( $($()* $(),* $(a)* $(a),* )* ) => {};
+    //~^ ERROR repetition matches empty token tree
+    ( $($()* $(),* $(a)* $(a),* )+ ) => {};
+    //~^ ERROR repetition matches empty token tree
+
+    ( $(a     $(),* $(a)* $(a),* )* ) => {}; // PASS
+    ( $($(a)+ $(),* $(a)* $(a),* )+ ) => {}; // PASS
+
+    ( $(a $()+)* ) => {};
+    //~^ ERROR repetition matches empty token tree
+    ( $(a $()*)+ ) => {};
+    //~^ ERROR repetition matches empty token tree
+}
+
+
+// --- Original Issue --- //
+
+macro_rules! make_vec {
+    (a $e1:expr $($(, a $e2:expr)*)*) => ([$e1 $($(, $e2)*)*]);
+    //~^ ERROR repetition matches empty token tree
+}
+
+fn main() {
+    let _ = make_vec!(a 1, a 2, a 3);
+}
+
+
+// --- Minified Issue --- //
+
+macro_rules! m {
+    ( $()* ) => {}
+    //~^ ERROR repetition matches empty token tree
+}
+
+m!();