diff --git a/macro_import_depth_test.go b/macro_import_depth_test.go new file mode 100644 index 0000000..d70b782 --- /dev/null +++ b/macro_import_depth_test.go @@ -0,0 +1,38 @@ +package pongo2_test + +import ( + "strings" + "testing" + "testing/fstest" + + "github.com/flosch/pongo2/v7" +) + +func TestMacroDepthLimitCoversDefinitionsAndImports(t *testing.T) { + t.Parallel() + tests := map[string]fstest.MapFS{ + "definition": { + "main.tpl": {Data: []byte( + `{% macro rec(n) %}{% if n > 0 %}{{ rec(n-1) }}{% endif %}{% endmacro %}{{ rec(1100) }}`)}, + }, + "import": { + "main.tpl": {Data: []byte(`{% import "macros.tpl" rec %}{{ rec(1100) }}`)}, + "macros.tpl": {Data: []byte( + `{% macro rec(n) export %}{% if n > 0 %}{{ rec(n-1) }}{% endif %}{% endmacro %}`)}, + }, + } + for name, files := range tests { + t.Run(name, func(t *testing.T) { + set := pongo2.NewSet(name, pongo2.NewFSLoader(files)) + tpl, err := set.FromFile("main.tpl") + if err != nil { + t.Fatalf("FromFile: %v", err) + } + _, err = tpl.Execute(nil) + if err == nil || !strings.Contains(err.Error(), + "maximum recursive macro call depth reached (max is 1000)") { + t.Fatalf("Execute error = %v, want macro depth limit", err) + } + }) + } +} diff --git a/tags_import.go b/tags_import.go index 97fc92f..449f511 100644 --- a/tags_import.go +++ b/tags_import.go @@ -46,9 +46,7 @@ type tagImportNode struct { func (node *tagImportNode) Execute(ctx *ExecutionContext, writer TemplateWriter) error { for name, macro := range node.macros { func(name string, macro *tagMacroNode) { - ctx.Private[name] = func(args ...*Value) (*Value, error) { - return macro.call(ctx, args...) - } + ctx.Private[name] = macro.callable(ctx) }(name, macro) } return nil diff --git a/tags_macro.go b/tags_macro.go index 59c75ea..07fc404 100644 --- a/tags_macro.go +++ b/tags_macro.go @@ -69,7 +69,14 @@ type tagMacroNode struct { // Execute registers the macro as a callable function in the private context. // The macro can then be called like {{ macro_name(args) }}. func (node *tagMacroNode) Execute(ctx *ExecutionContext, writer TemplateWriter) error { - ctx.Private[node.name] = func(args ...*Value) (*Value, error) { + ctx.Private[node.name] = node.callable(ctx) + return nil +} + +// callable applies the same recursion bound to a macro registered at its +// definition site and to one registered by an import. +func (node *tagMacroNode) callable(ctx *ExecutionContext) func(args ...*Value) (*Value, error) { + return func(args ...*Value) (*Value, error) { ctx.macroDepth++ defer func() { ctx.macroDepth-- @@ -81,8 +88,6 @@ func (node *tagMacroNode) Execute(ctx *ExecutionContext, writer TemplateWriter) return node.call(ctx, args...) } - - return nil } // call executes the macro body with the provided arguments and returns the