Repository: djc/askama
Pull request #869 (2023-09-28 15:16:53)

Limit nesting in parser nodes, too (2)
See #868.


Patch:
diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs
index ad0c19e..3b6bced 100644
--- a/askama_parser/src/expr.rs
+++ b/askama_parser/src/expr.rs
@@ -10,7 +10,7 @@ use nom::sequence::{pair, preceded, terminated, tuple};
 use nom::{error_position, IResult};
 
 use super::{
-    char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, PathOrIdentifier,
+    char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, PathOrIdentifier,
 };
 
 macro_rules! expr_prec_layer {
@@ -76,17 +76,13 @@ impl<'a> Expr<'a> {
         preceded(
             ws(char('(')),
             cut(terminated(
-                separated_list0(char(','), ws(move |i| Self::nested(i, level))),
+                separated_list0(char(','), ws(move |i| Self::parse(i, level))),
                 char(')'),
             )),
         )(i)
     }
 
-    pub(super) fn parse(i: &'a str) -> IResult<&'a str, Self> {
-        Self::nested(i, Level::default())
-    }
-
-    fn nested(i: &'a str, level: Level) -> IResult<&'a str, Self> {
+    pub(super) fn parse(i: &'a str, level: Level) -> IResult<&'a str, Self> {
         let level = level.nest(i)?;
         let range_right = move |i| {
             pair(
@@ -172,7 +168,7 @@ impl<'a> Expr<'a> {
 
     fn group(i: &'a str, level: Level) -> IResult<&'a str, Self> {
         let level = level.nest(i)?;
-        let (i, expr) = preceded(ws(char('(')), opt(|i| Self::nested(i, level)))(i)?;
+        let (i, expr) = preceded(ws(char('(')), opt(|i| Self::parse(i, level)))(i)?;
         let expr = match expr {
             Some(expr) => expr,
             None => {
@@ -189,7 +185,7 @@ impl<'a> Expr<'a> {
 
         let mut exprs = vec![expr];
         let (i, _) = fold_many0(
-            preceded(char(','), ws(|i| Self::nested(i, level))),
+            preceded(char(','), ws(|i| Self::parse(i, level))),
             || (),
             |_, expr| {
                 exprs.push(expr);
@@ -205,7 +201,7 @@ impl<'a> Expr<'a> {
             ws(char('[')),
             cut(terminated(
                 map(
-                    separated_list0(char(','), ws(move |i| Self::nested(i, level))),
+                    separated_list0(char(','), ws(move |i| Self::parse(i, level))),
                     Self::Array,
                 ),
                 char(']'),
@@ -348,7 +344,7 @@ impl<'a> Suffix<'a> {
         map(
             preceded(
                 ws(char('[')),
-                cut(terminated(ws(move |i| Expr::nested(i, level)), char(']'))),
+                cut(terminated(ws(move |i| Expr::parse(i, level)), char(']'))),
             ),
             Self::Index,
         )(i)
@@ -363,18 +359,3 @@ impl<'a> Suffix<'a> {
         map(preceded(take_till(not_ws), char('?')), |_| Self::Try)(i)
     }
 }
-
-#[derive(Clone, Copy, Default)]
-pub(crate) struct Level(u8);
-
-impl Level {
-    fn nest(self, i: &str) -> Result<Level, nom::Err<nom::error::Error<&str>>> {
-        if self.0 >= Self::MAX_EXPR_DEPTH {
-            return Err(nom::Err::Failure(error_position!(i, ErrorKind::TooLarge)));
-        }
-
-        Ok(Level(self.0 + 1))
-    }
-
-    const MAX_EXPR_DEPTH: u8 = 64;
-}
diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs
index 1e48262..78b0807 100644
--- a/askama_parser/src/lib.rs
+++ b/askama_parser/src/lib.rs
@@ -255,6 +255,7 @@ fn path_or_identifier(i: &str) -> IResult<&str, PathOrIdentifier<'_>> {
 struct State<'a> {
     syntax: &'a Syntax<'a>,
     loop_depth: Cell<usize>,
+    level: Cell<Level>,
 }
 
 impl<'a> State<'a> {
@@ -262,9 +263,19 @@ impl<'a> State<'a> {
         State {
             syntax,
             loop_depth: Cell::new(0),
+            level: Cell::new(Level::default()),
         }
     }
 
+    fn nest<'b>(&self, i: &'b str) -> Result<(), nom::Err<nom::error::Error<&'b str>>> {
+        self.level.set(self.level.get().nest(i)?);
+        Ok(())
+    }
+
+    fn leave(&self) {
+        self.level.set(self.level.get().leave());
+    }
+
     fn tag_block_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> {
         tag(self.syntax.block_start)(i)
     }
@@ -324,3 +335,22 @@ impl Default for Syntax<'static> {
         }
     }
 }
+
+#[derive(Clone, Copy, Default)]
+pub(crate) struct Level(u8);
+
+impl Level {
+    fn nest(self, i: &str) -> Result<Level, nom::Err<nom::error::Error<&str>>> {
+        if self.0 >= Self::MAX_DEPTH {
+            return Err(nom::Err::Failure(error_position!(i, ErrorKind::TooLarge)));
+        }
+
+        Ok(Level(self.0 + 1))
+    }
+
+    fn leave(&self) -> Self {
+        Level(self.0 - 1)
+    }
+
+    const MAX_DEPTH: u8 = 128;
+}
diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs
index a72c2fb..ba4d09e 100644
--- a/askama_parser/src/node.rs
+++ b/askama_parser/src/node.rs
@@ -15,7 +15,6 @@ use super::{
     bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till,
     str_lit, ws, Expr, PathOrIdentifier, State,
 };
-use crate::expr::Level;
 
 #[derive(Debug, PartialEq)]
 pub enum Node<'a> {
@@ -48,11 +47,11 @@ impl<'a> Node<'a> {
     }
 
     fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
-        let mut p = tuple((
+        let mut p = delimited(
             |i| s.tag_block_start(i),
             alt((
-                map(Call::parse, Self::Call),
-                map(Let::parse, Self::Let),
+                map(|i| Call::parse(i, s), Self::Call),
+                map(|i| Let::parse(i, s), Self::Let),
                 map(|i| If::parse(i, s), Self::If),
                 map(|i| Loop::parse(i, s), |l| Self::Loop(Box::new(l))),
                 map(|i| Match::parse(i, s), Self::Match),
@@ -66,9 +65,13 @@ impl<'a> Node<'a> {
                 |i| Self::r#continue(i, s),
             )),
             cut(|i| s.tag_block_end(i)),
-        ));
-        let (i, (_, contents, _)) = p(i)?;
-        Ok((i, contents))
+        );
+
+        s.nest(i)?;
+        let result = p(i);
+        s.leave();
+
+        result
     }
 
     fn r#break(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
@@ -102,7 +105,7 @@ impl<'a> Node<'a> {
             |i| s.tag_expr_start(i),
             cut(tuple((
                 opt(Whitespace::parse),
-                ws(Expr::parse),
+                ws(|i| Expr::parse(i, s.level.get())),
                 opt(Whitespace::parse),
                 |i| s.tag_expr_end(i),
             ))),
@@ -292,7 +295,7 @@ impl<'a> Cond<'a> {
             opt(Whitespace::parse),
             ws(keyword("else")),
             cut(tuple((
-                opt(CondTest::parse),
+                opt(|i| CondTest::parse(i, s)),
                 opt(Whitespace::parse),
                 |i| s.tag_block_end(i),
                 cut(|i| Node::many(i, s)),
@@ -317,7 +320,7 @@ pub struct CondTest<'a> {
 }
 
 impl<'a> CondTest<'a> {
-    fn parse(i: &'a str) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
         let mut p = preceded(
             ws(keyword("if")),
             cut(tuple((
@@ -326,7 +329,7 @@ impl<'a> CondTest<'a> {
                     ws(Target::parse),
                     ws(char('=')),
                 )),
-                ws(Expr::parse),
+                ws(|i| Expr::parse(i, s.level.get())),
             ))),
         );
         let (i, (target, expr)) = p(i)?;
@@ -372,7 +375,10 @@ impl<'a> Loop<'a> {
             result
         }
 
-        let if_cond = preceded(ws(keyword("if")), cut(ws(Expr::parse)));
+        let if_cond = preceded(
+            ws(keyword("if")),
+            cut(ws(|i| Expr::parse(i, s.level.get()))),
+        );
         let else_block = |i| {
             let mut p = preceded(
                 ws(keyword("else")),
@@ -396,7 +402,7 @@ impl<'a> Loop<'a> {
                 ws(Target::parse),
                 ws(keyword("in")),
                 cut(tuple((
-                    ws(Expr::parse),
+                    ws(|i| Expr::parse(i, s.level.get())),
                     opt(if_cond),
                     opt(Whitespace::parse),
                     |i| s.tag_block_end(i),
@@ -531,14 +537,14 @@ pub struct Call<'a> {
 }
 
 impl<'a> Call<'a> {
-    fn parse(i: &'a str) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(keyword("call")),
             cut(tuple((
                 opt(tuple((ws(identifier), ws(tag("::"))))),
                 ws(identifier),
-                opt(ws(|nested| Expr::arguments(nested, Level::default()))),
+                opt(ws(|nested| Expr::arguments(nested, s.level.get()))),
                 opt(Whitespace::parse),
             ))),
         ));
@@ -571,7 +577,7 @@ impl<'a> Match<'a> {
             opt(Whitespace::parse),
             ws(keyword("match")),
             cut(tuple((
-                ws(Expr::parse),
+                ws(|i| Expr::parse(i, s.level.get())),
                 opt(Whitespace::parse),
                 |i| s.tag_block_end(i),
                 cut(tuple((
@@ -733,13 +739,16 @@ pub struct Let<'a> {
 }
 
 impl<'a> Let<'a> {
-    fn parse(i: &'a str) -> IResult<&'a str, Self> {
+    fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
             ws(alt((keyword("let"), keyword("set")))),
             cut(tuple((
                 ws(Target::parse),
-                opt(preceded(ws(char('=')), ws(Expr::parse))),
+                opt(preceded(
+                    ws(char('=')),
+                    ws(|i| Expr::parse(i, s.level.get())),
+                )),
                 opt(Whitespace::parse),
             ))),
         ));
@@ -766,7 +775,7 @@ impl<'a> If<'a> {
     fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> {
         let mut p = tuple((
             opt(Whitespace::parse),
-            CondTest::parse,
+            |i| CondTest::parse(i, s),
             cut(tuple((
                 opt(Whitespace::parse),
                 |i| s.tag_block_end(i),


Test patch:
diff --git a/testing/tests/ui/excessive_nesting.rs b/testing/tests/ui/excessive_nesting.rs
new file mode 100644
index 0000000..bc72b7d
--- /dev/null
+++ b/testing/tests/ui/excessive_nesting.rs
@@ -0,0 +1,121 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(
+    source = "
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 100
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 200
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 300
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 400
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 500
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 600
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 700
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 800
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 900
+
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}
+    {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 1000
+    ",
+    ext = "txt"
+)]
+struct IfTimes1000;
+
+fn main() {
+}
diff --git a/testing/tests/ui/excessive_nesting.stderr b/testing/tests/ui/excessive_nesting.stderr
new file mode 100644
index 0000000..f8f48ec
--- /dev/null
+++ b/testing/tests/ui/excessive_nesting.stderr
@@ -0,0 +1,8 @@
+error: problems parsing template source at row 14, column 34 near:
+       "%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1"...
+ --> tests/ui/excessive_nesting.rs:3:10
+  |
+3 | #[derive(Template)]
+  |          ^^^^^^^^
+  |
+  = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)