Skip to content

Commit 97ecbe6

Browse files
authored
Merge pull request askama-rs#299 from Kijewski/pr-issue-283
derive: keep track of called macros
2 parents 889b2c5 + 9336435 commit 97ecbe6

File tree

8 files changed

+162
-25
lines changed

8 files changed

+162
-25
lines changed

rinja_derive/src/generator.rs

+17-14
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ use std::path::Path;
88
use std::str;
99
use std::sync::Arc;
1010

11-
use parser::node::Whitespace;
11+
use parser::node::{Macro, Whitespace};
1212
use parser::{
1313
CharLit, Expr, FloatKind, IntKind, MAX_RUST_KEYWORD_LEN, Num, RUST_KEYWORDS, StrLit, WithSpan,
1414
};
1515
use rustc_hash::FxBuildHasher;
1616

17-
use crate::CompileError;
1817
use crate::heritage::{Context, Heritage};
1918
use crate::html::write_escaped_str;
2019
use crate::input::{Source, TemplateInput};
2120
use crate::integration::{Buffer, impl_everything, write_header};
21+
use crate::{CompileError, FileInfo};
2222

2323
pub(crate) fn template_to_string(
2424
buf: &mut Buffer,
@@ -46,27 +46,29 @@ pub(crate) fn template_to_string(
4646
}
4747

4848
struct Generator<'a, 'h> {
49-
// The template input state: original struct AST and attributes
49+
/// The template input state: original struct AST and attributes
5050
input: &'a TemplateInput<'a>,
51-
// All contexts, keyed by the package-relative template path
51+
/// All contexts, keyed by the package-relative template path
5252
contexts: &'a HashMap<&'a Arc<Path>, Context<'a>, FxBuildHasher>,
53-
// The heritage contains references to blocks and their ancestry
53+
/// The heritage contains references to blocks and their ancestry
5454
heritage: Option<&'h Heritage<'a, 'h>>,
55-
// Variables accessible directly from the current scope (not redirected to context)
55+
/// Variables accessible directly from the current scope (not redirected to context)
5656
locals: MapChain<'a>,
57-
// Suffix whitespace from the previous literal. Will be flushed to the
58-
// output buffer unless suppressed by whitespace suppression on the next
59-
// non-literal.
57+
/// Suffix whitespace from the previous literal. Will be flushed to the
58+
/// output buffer unless suppressed by whitespace suppression on the next
59+
/// non-literal.
6060
next_ws: Option<&'a str>,
61-
// Whitespace suppression from the previous non-literal. Will be used to
62-
// determine whether to flush prefix whitespace from the next literal.
61+
/// Whitespace suppression from the previous non-literal. Will be used to
62+
/// determine whether to flush prefix whitespace from the next literal.
6363
skip_ws: Whitespace,
64-
// If currently in a block, this will contain the name of a potential parent block
64+
/// If currently in a block, this will contain the name of a potential parent block
6565
super_block: Option<(&'a str, usize)>,
66-
// Buffer for writable
66+
/// Buffer for writable
6767
buf_writable: WritableBuffer<'a>,
68-
// Used in blocks to check if we are inside a filter block.
68+
/// Used in blocks to check if we are inside a filter block.
6969
is_in_filter_block: usize,
70+
/// Set of called macros we are currently in. Used to prevent (indirect) recursions.
71+
seen_macros: Vec<(&'a Macro<'a>, Option<FileInfo<'a>>)>,
7072
}
7173

7274
impl<'a, 'h> Generator<'a, 'h> {
@@ -91,6 +93,7 @@ impl<'a, 'h> Generator<'a, 'h> {
9193
..Default::default()
9294
},
9395
is_in_filter_block,
96+
seen_macros: Vec::new(),
9497
}
9598
}
9699

rinja_derive/src/generator/node.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22
use std::collections::hash_map::{Entry, HashMap};
3+
use std::fmt::Write;
34
use std::mem;
45

56
use parser::node::{
@@ -616,14 +617,28 @@ impl<'a> Generator<'a, '_> {
616617
call.span(),
617618
)
618619
})?;
619-
(def, mctx)
620+
(*def, mctx)
620621
} else {
621622
let def = ctx.macros.get(name).ok_or_else(|| {
622623
ctx.generate_error(format_args!("macro {name:?} not found"), call.span())
623624
})?;
624-
(def, ctx)
625+
(*def, ctx)
625626
};
626627

628+
if self.seen_macros.iter().any(|(s, _)| std::ptr::eq(*s, def)) {
629+
let mut message = "Found recursion in macro calls:".to_owned();
630+
for (m, f) in &self.seen_macros {
631+
if let Some(f) = f {
632+
write!(message, "{f}").unwrap();
633+
} else {
634+
write!(message, "\n`{}`", m.name.escape_debug()).unwrap();
635+
}
636+
}
637+
return Err(ctx.generate_error(message, call.span()));
638+
} else {
639+
self.seen_macros.push((def, ctx.file_info_of(call.span())));
640+
}
641+
627642
self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first
628643
let size_hint = self.push_locals(|this| {
629644
macro_call_ensure_arg_count(call, def, ctx)?;
@@ -746,6 +761,7 @@ impl<'a> Generator<'a, '_> {
746761
Ok(size_hint)
747762
})?;
748763
self.prepare_ws(ws);
764+
self.seen_macros.pop();
749765
Ok(size_hint)
750766
}
751767

rinja_derive/src/heritage.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub(crate) struct Context<'a> {
5151
pub(crate) parsed: &'a Parsed,
5252
}
5353

54-
impl Context<'_> {
54+
impl<'a> Context<'a> {
5555
pub(crate) fn empty(parsed: &Parsed) -> Context<'_> {
5656
Context {
5757
nodes: &[],
@@ -64,11 +64,11 @@ impl Context<'_> {
6464
}
6565
}
6666

67-
pub(crate) fn new<'n>(
67+
pub(crate) fn new(
6868
config: &Config,
69-
path: &'n Path,
70-
parsed: &'n Parsed,
71-
) -> Result<Context<'n>, CompileError> {
69+
path: &'a Path,
70+
parsed: &'a Parsed,
71+
) -> Result<Self, CompileError> {
7272
let mut extends = None;
7373
let mut blocks = HashMap::default();
7474
let mut macros = HashMap::default();
@@ -142,10 +142,11 @@ impl Context<'_> {
142142
}
143143

144144
pub(crate) fn generate_error(&self, msg: impl fmt::Display, node: Span<'_>) -> CompileError {
145-
CompileError::new(
146-
msg,
147-
self.path.map(|path| FileInfo::of(node, path, self.parsed)),
148-
)
145+
CompileError::new(msg, self.file_info_of(node))
146+
}
147+
148+
pub(crate) fn file_info_of(&self, node: Span<'a>) -> Option<FileInfo<'a>> {
149+
self.path.map(|path| FileInfo::of(node, path, self.parsed))
149150
}
150151
}
151152

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% import "macro-recursion-2.html" as next %}
2+
3+
{% macro some_macro %}
4+
{% call next::some_macro %}
5+
{% endmacro %}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% import "macro-recursion-3.html" as next %}
2+
3+
{% macro some_macro %}
4+
{% call next::some_macro %}
5+
{% endmacro %}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% import "macro-recursion-1.html" as next %}
2+
3+
{% macro some_macro %}
4+
{% call next::some_macro %}
5+
{% endmacro %}

testing/tests/ui/macro-recursion.rs

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use rinja::Template;
2+
3+
#[derive(Template)]
4+
#[template(
5+
source = "
6+
{% macro one %}{% call one %}{% endmacro %}
7+
{% call one %}
8+
",
9+
ext = "html"
10+
)]
11+
struct Direct;
12+
13+
#[derive(Template)]
14+
#[template(
15+
source = "
16+
{% macro one %}{% call two %}{% endmacro %}
17+
{% macro two %}{% call three %}{% endmacro %}
18+
{% macro three %}{% call four %}{% endmacro %}
19+
{% macro four %}{% call five %}{% endmacro %}
20+
{% macro five %}{% call one %}{% endmacro %}
21+
{% call one %}
22+
",
23+
ext = "html"
24+
)]
25+
struct Indirect;
26+
27+
#[derive(Template)]
28+
#[template(
29+
source = r#"
30+
{% import "macro-recursion-1.html" as next %}
31+
{% macro some_macro %}
32+
{% call next::some_macro %}
33+
{% endmacro %}
34+
{% call some_macro %}
35+
"#,
36+
ext = "html"
37+
)]
38+
struct AcrossImports;
39+
40+
fn main() {
41+
}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
error: Found recursion in macro calls:
2+
--> Direct.html:3:10
3+
" call one %}\n "
4+
--> Direct.html:2:25
5+
" call one %}{% endmacro %}\n {% call one %}\n "
6+
--> tests/ui/macro-recursion.rs:5:14
7+
|
8+
5 | source = "
9+
| ______________^
10+
6 | | {% macro one %}{% call one %}{% endmacro %}
11+
7 | | {% call one %}
12+
8 | | ",
13+
| |_____^
14+
15+
error: Found recursion in macro calls:
16+
--> Indirect.html:7:10
17+
" call one %}\n "
18+
--> Indirect.html:2:25
19+
" call two %}{% endmacro %}\n {% macro two %}{% call three %}{% endmacro %}"...
20+
--> Indirect.html:3:25
21+
" call three %}{% endmacro %}\n {% macro three %}{% call four %}{% endmacro"...
22+
--> Indirect.html:4:27
23+
" call four %}{% endmacro %}\n {% macro four %}{% call five %}{% endmacro %"...
24+
--> Indirect.html:5:26
25+
" call five %}{% endmacro %}\n {% macro five %}{% call one %}{% endmacro %}"...
26+
--> Indirect.html:6:26
27+
" call one %}{% endmacro %}\n {% call one %}\n "
28+
--> tests/ui/macro-recursion.rs:15:14
29+
|
30+
15 | source = "
31+
| ______________^
32+
16 | | {% macro one %}{% call two %}{% endmacro %}
33+
17 | | {% macro two %}{% call three %}{% endmacro %}
34+
18 | | {% macro three %}{% call four %}{% endmacro %}
35+
... |
36+
21 | | {% call one %}
37+
22 | | ",
38+
| |_____^
39+
40+
error: Found recursion in macro calls:
41+
--> AcrossImports.html:6:10
42+
" call some_macro %}\n "
43+
--> AcrossImports.html:4:14
44+
" call next::some_macro %}\n {% endmacro %}\n {% call some_macro %}\n "...
45+
--> testing/templates/macro-recursion-1.html:4:6
46+
" call next::some_macro %}\n{% endmacro %}"
47+
--> testing/templates/macro-recursion-2.html:4:6
48+
" call next::some_macro %}\n{% endmacro %}"
49+
--> testing/templates/macro-recursion-3.html:4:6
50+
" call next::some_macro %}\n{% endmacro %}"
51+
--> tests/ui/macro-recursion.rs:29:14
52+
|
53+
29 | source = r#"
54+
| ______________^
55+
30 | | {% import "macro-recursion-1.html" as next %}
56+
31 | | {% macro some_macro %}
57+
32 | | {% call next::some_macro %}
58+
33 | | {% endmacro %}
59+
34 | | {% call some_macro %}
60+
35 | | "#,
61+
| |______^

0 commit comments

Comments
 (0)