diff options
author | Ian Lance Taylor <iant@golang.org> | 2020-12-21 11:21:59 -0800 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2021-01-07 17:51:29 +0000 |
commit | e60cffa4ca9ae726d96b53817d82d98402017772 (patch) | |
tree | 9afa0d0d1d68e952a7c8f74d3fb6623864f3af80 /src/html | |
parent | 6da2d3b7d7f9c0063bc4128c2453db65c96f5299 (diff) | |
download | go-e60cffa4ca9ae726d96b53817d82d98402017772.tar.gz go-e60cffa4ca9ae726d96b53817d82d98402017772.zip |
html/template: attach functions to namespace
The text/template functions are stored in a data structure shared by
all related templates, so do the same with the original, unwrapped,
functions on the html/template side.
For #39807
Fixes #43295
Change-Id: I9f64a0a601f1151c863a2833b5be2baf649b6cef
Reviewed-on: https://go-review.googlesource.com/c/go/+/279492
Trust: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
Diffstat (limited to 'src/html')
-rw-r--r-- | src/html/template/exec_test.go | 20 | ||||
-rw-r--r-- | src/html/template/template.go | 46 |
2 files changed, 47 insertions, 19 deletions
diff --git a/src/html/template/exec_test.go b/src/html/template/exec_test.go index eb00824260..cd6b78a1a9 100644 --- a/src/html/template/exec_test.go +++ b/src/html/template/exec_test.go @@ -1776,3 +1776,23 @@ func TestRecursiveExecute(t *testing.T) { t.Fatal(err) } } + +// Issue 43295. +func TestTemplateFuncsAfterClone(t *testing.T) { + s := `{{ f . }}` + want := "test" + orig := New("orig").Funcs(map[string]interface{}{ + "f": func(in string) string { + return in + }, + }).New("child") + + overviewTmpl := Must(Must(orig.Clone()).Parse(s)) + var out strings.Builder + if err := overviewTmpl.Execute(&out, want); err != nil { + t.Fatal(err) + } + if got := out.String(); got != want { + t.Fatalf("got %q; want %q", got, want) + } +} diff --git a/src/html/template/template.go b/src/html/template/template.go index 09d71d43e2..1ff7e1f7a0 100644 --- a/src/html/template/template.go +++ b/src/html/template/template.go @@ -27,9 +27,7 @@ type Template struct { // template's in sync. text *template.Template // The underlying template's parse tree, updated to be HTML-safe. - Tree *parse.Tree - // The original functions, before wrapping. - funcMap FuncMap + Tree *parse.Tree *nameSpace // common to all associated templates } @@ -42,6 +40,8 @@ type nameSpace struct { set map[string]*Template escaped bool esc escaper + // The original functions, before wrapping. + funcMap FuncMap } // Templates returns a slice of the templates associated with t, including t @@ -260,7 +260,6 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error nil, text, text.Tree, - nil, t.nameSpace, } t.set[name] = ret @@ -287,14 +286,19 @@ func (t *Template) Clone() (*Template, error) { } ns := &nameSpace{set: make(map[string]*Template)} ns.esc = makeEscaper(ns) + if t.nameSpace.funcMap != nil { + ns.funcMap = make(FuncMap, len(t.nameSpace.funcMap)) + for name, fn := range t.nameSpace.funcMap { + ns.funcMap[name] = fn + } + } + wrapFuncs(ns, textClone, ns.funcMap) ret := &Template{ nil, textClone, textClone.Tree, - t.funcMap, ns, } - ret.wrapFuncs() ret.set[ret.Name()] = ret for _, x := range textClone.Templates() { name := x.Name() @@ -307,10 +311,8 @@ func (t *Template) Clone() (*Template, error) { nil, x, x.Tree, - src.funcMap, ret.nameSpace, } - tc.wrapFuncs() ret.set[name] = tc } // Return the template associated with the name of this template. @@ -325,7 +327,6 @@ func New(name string) *Template { nil, template.New(name), nil, - nil, ns, } tmpl.set[name] = tmpl @@ -351,7 +352,6 @@ func (t *Template) new(name string) *Template { nil, t.text.New(name), nil, - nil, t.nameSpace, } if existing, ok := tmpl.set[name]; ok { @@ -382,23 +382,31 @@ type FuncMap map[string]interface{} // type. However, it is legal to overwrite elements of the map. The return // value is the template, so calls can be chained. func (t *Template) Funcs(funcMap FuncMap) *Template { - t.funcMap = funcMap - t.wrapFuncs() + t.nameSpace.mu.Lock() + if t.nameSpace.funcMap == nil { + t.nameSpace.funcMap = make(FuncMap, len(funcMap)) + } + for name, fn := range funcMap { + t.nameSpace.funcMap[name] = fn + } + t.nameSpace.mu.Unlock() + + wrapFuncs(t.nameSpace, t.text, funcMap) return t } // wrapFuncs records the functions with text/template. We wrap them to // unlock the nameSpace. See TestRecursiveExecute for a test case. -func (t *Template) wrapFuncs() { - if len(t.funcMap) == 0 { +func wrapFuncs(ns *nameSpace, textTemplate *template.Template, funcMap FuncMap) { + if len(funcMap) == 0 { return } - tfuncs := make(template.FuncMap, len(t.funcMap)) - for name, fn := range t.funcMap { + tfuncs := make(template.FuncMap, len(funcMap)) + for name, fn := range funcMap { fnv := reflect.ValueOf(fn) wrapper := func(args []reflect.Value) []reflect.Value { - t.nameSpace.mu.RUnlock() - defer t.nameSpace.mu.RLock() + ns.mu.RUnlock() + defer ns.mu.RLock() if fnv.Type().IsVariadic() { return fnv.CallSlice(args) } else { @@ -408,7 +416,7 @@ func (t *Template) wrapFuncs() { wrapped := reflect.MakeFunc(fnv.Type(), wrapper) tfuncs[name] = wrapped.Interface() } - t.text.Funcs(tfuncs) + textTemplate.Funcs(tfuncs) } // Delims sets the action delimiters to the specified strings, to be used in |