diff --git a/src/parser/event_parser/grammar/attributes.rs b/src/parser/event_parser/grammar/attributes.rs
index 52210ccad2b..d774f88273c 100644
--- a/src/parser/event_parser/grammar/attributes.rs
+++ b/src/parser/event_parser/grammar/attributes.rs
@@ -1,22 +1,26 @@
 use super::*;
 
+enum AttrKind {
+    Inner, Outer
+}
+
 pub(super) fn inner_attributes(p: &mut Parser) {
-    many(p, |p| attribute(p, true))
+    many(p, |p| attribute(p, AttrKind::Inner))
 }
 
-pub(super) fn outer_attributes(_: &mut Parser) {
+pub(super) fn outer_attributes(p: &mut Parser) {
+    many(p, |p| attribute(p, AttrKind::Outer))
 }
 
 
-fn attribute(p: &mut Parser, inner: bool) -> bool {
+fn attribute(p: &mut Parser, kind: AttrKind) -> bool {
     fn attr_tail(p: &mut Parser) {
         meta_item(p) && p.expect(R_BRACK);
     }
 
-    if inner {
-        node_if(p, [POUND, EXCL, L_BRACK], ATTR, attr_tail)
-    } else {
-        node_if(p, [POUND, L_BRACK], ATTR, attr_tail)
+    match kind {
+        AttrKind::Inner => node_if(p, [POUND, EXCL, L_BRACK], ATTR, attr_tail),
+        AttrKind::Outer => node_if(p, [POUND, L_BRACK], ATTR, attr_tail),
     }
 }
 
diff --git a/src/parser/event_parser/grammar/items.rs b/src/parser/event_parser/grammar/items.rs
index 950e02a4d48..522986ed014 100644
--- a/src/parser/event_parser/grammar/items.rs
+++ b/src/parser/event_parser/grammar/items.rs
@@ -12,7 +12,7 @@ pub(super) fn mod_contents(p: &mut Parser) {
 
 fn item_first(p: &Parser) -> bool {
     match p.current() {
-        STRUCT_KW | FN_KW | EXTERN_KW | MOD_KW | USE_KW => true,
+        STRUCT_KW | FN_KW | EXTERN_KW | MOD_KW | USE_KW | POUND => true,
         _ => false,
     }
 }
diff --git a/src/parser/event_parser/grammar/mod.rs b/src/parser/event_parser/grammar/mod.rs
index 60458ce7090..76f62b714ff 100644
--- a/src/parser/event_parser/grammar/mod.rs
+++ b/src/parser/event_parser/grammar/mod.rs
@@ -41,7 +41,15 @@ fn node<F: FnOnce(&mut Parser)>(p: &mut Parser, node_kind: SyntaxKind, rest: F)
 }
 
 fn many<F: Fn(&mut Parser) -> bool>(p: &mut Parser, f: F) {
-    while f(p) { }
+    loop {
+        let pos = p.pos();
+        if !f(p) {
+            return
+        }
+        if pos == p.pos() {
+            panic!("Infinite loop in parser")
+        }
+    }
 }
 
 fn comma_list<F: Fn(&mut Parser) -> bool>(p: &mut Parser, end: SyntaxKind, f: F) {
diff --git a/src/parser/event_parser/parser.rs b/src/parser/event_parser/parser.rs
index a1a0ebfea21..d7d24fa2733 100644
--- a/src/parser/event_parser/parser.rs
+++ b/src/parser/event_parser/parser.rs
@@ -44,6 +44,10 @@ impl<'t> Parser<'t> {
         }
     }
 
+    pub(crate) fn pos(&self) -> usize {
+        self.pos
+    }
+
     pub(crate) fn into_events(self) -> Vec<Event> {
         assert!(self.curly_limit.is_none());
         assert!(self.current() == EOF);
diff --git a/tests/data/parser/ok/0011_outer_attribute.rs b/tests/data/parser/ok/0011_outer_attribute.rs
new file mode 100644
index 00000000000..8b80c0d90c0
--- /dev/null
+++ b/tests/data/parser/ok/0011_outer_attribute.rs
@@ -0,0 +1,3 @@
+#[cfg(test)]
+#[ignore]
+fn foo() {}
diff --git a/tests/data/parser/ok/0011_outer_attribute.txt b/tests/data/parser/ok/0011_outer_attribute.txt
new file mode 100644
index 00000000000..a790f4095d3
--- /dev/null
+++ b/tests/data/parser/ok/0011_outer_attribute.txt
@@ -0,0 +1,29 @@
+FILE@[0; 35)
+  ATTR@[0; 13)
+    POUND@[0; 1)
+    L_BRACK@[1; 2)
+    META_ITEM@[2; 11)
+      IDENT@[2; 5)
+      L_PAREN@[5; 6)
+      META_ITEM@[6; 10)
+        IDENT@[6; 10)
+      R_PAREN@[10; 11)
+    R_BRACK@[11; 12)
+    WHITESPACE@[12; 13)
+  ATTR@[13; 23)
+    POUND@[13; 14)
+    L_BRACK@[14; 15)
+    META_ITEM@[15; 21)
+      IDENT@[15; 21)
+    R_BRACK@[21; 22)
+    WHITESPACE@[22; 23)
+  FN_ITEM@[23; 35)
+    FN_KW@[23; 25)
+    WHITESPACE@[25; 26)
+    IDENT@[26; 29)
+    L_PAREN@[29; 30)
+    R_PAREN@[30; 31)
+    WHITESPACE@[31; 32)
+    L_CURLY@[32; 33)
+    R_CURLY@[33; 34)
+    WHITESPACE@[34; 35)