Skip to content

Commit e60cffa

Browse files
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 <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]>
1 parent 6da2d3b commit e60cffa

File tree

2 files changed

+47
-19
lines changed

2 files changed

+47
-19
lines changed

src/html/template/exec_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,3 +1776,23 @@ func TestRecursiveExecute(t *testing.T) {
17761776
t.Fatal(err)
17771777
}
17781778
}
1779+
1780+
// Issue 43295.
1781+
func TestTemplateFuncsAfterClone(t *testing.T) {
1782+
s := `{{ f . }}`
1783+
want := "test"
1784+
orig := New("orig").Funcs(map[string]interface{}{
1785+
"f": func(in string) string {
1786+
return in
1787+
},
1788+
}).New("child")
1789+
1790+
overviewTmpl := Must(Must(orig.Clone()).Parse(s))
1791+
var out strings.Builder
1792+
if err := overviewTmpl.Execute(&out, want); err != nil {
1793+
t.Fatal(err)
1794+
}
1795+
if got := out.String(); got != want {
1796+
t.Fatalf("got %q; want %q", got, want)
1797+
}
1798+
}

src/html/template/template.go

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ type Template struct {
2727
// template's in sync.
2828
text *template.Template
2929
// The underlying template's parse tree, updated to be HTML-safe.
30-
Tree *parse.Tree
31-
// The original functions, before wrapping.
32-
funcMap FuncMap
30+
Tree *parse.Tree
3331
*nameSpace // common to all associated templates
3432
}
3533

@@ -42,6 +40,8 @@ type nameSpace struct {
4240
set map[string]*Template
4341
escaped bool
4442
esc escaper
43+
// The original functions, before wrapping.
44+
funcMap FuncMap
4545
}
4646

4747
// 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
260260
nil,
261261
text,
262262
text.Tree,
263-
nil,
264263
t.nameSpace,
265264
}
266265
t.set[name] = ret
@@ -287,14 +286,19 @@ func (t *Template) Clone() (*Template, error) {
287286
}
288287
ns := &nameSpace{set: make(map[string]*Template)}
289288
ns.esc = makeEscaper(ns)
289+
if t.nameSpace.funcMap != nil {
290+
ns.funcMap = make(FuncMap, len(t.nameSpace.funcMap))
291+
for name, fn := range t.nameSpace.funcMap {
292+
ns.funcMap[name] = fn
293+
}
294+
}
295+
wrapFuncs(ns, textClone, ns.funcMap)
290296
ret := &Template{
291297
nil,
292298
textClone,
293299
textClone.Tree,
294-
t.funcMap,
295300
ns,
296301
}
297-
ret.wrapFuncs()
298302
ret.set[ret.Name()] = ret
299303
for _, x := range textClone.Templates() {
300304
name := x.Name()
@@ -307,10 +311,8 @@ func (t *Template) Clone() (*Template, error) {
307311
nil,
308312
x,
309313
x.Tree,
310-
src.funcMap,
311314
ret.nameSpace,
312315
}
313-
tc.wrapFuncs()
314316
ret.set[name] = tc
315317
}
316318
// Return the template associated with the name of this template.
@@ -325,7 +327,6 @@ func New(name string) *Template {
325327
nil,
326328
template.New(name),
327329
nil,
328-
nil,
329330
ns,
330331
}
331332
tmpl.set[name] = tmpl
@@ -351,7 +352,6 @@ func (t *Template) new(name string) *Template {
351352
nil,
352353
t.text.New(name),
353354
nil,
354-
nil,
355355
t.nameSpace,
356356
}
357357
if existing, ok := tmpl.set[name]; ok {
@@ -382,23 +382,31 @@ type FuncMap map[string]interface{}
382382
// type. However, it is legal to overwrite elements of the map. The return
383383
// value is the template, so calls can be chained.
384384
func (t *Template) Funcs(funcMap FuncMap) *Template {
385-
t.funcMap = funcMap
386-
t.wrapFuncs()
385+
t.nameSpace.mu.Lock()
386+
if t.nameSpace.funcMap == nil {
387+
t.nameSpace.funcMap = make(FuncMap, len(funcMap))
388+
}
389+
for name, fn := range funcMap {
390+
t.nameSpace.funcMap[name] = fn
391+
}
392+
t.nameSpace.mu.Unlock()
393+
394+
wrapFuncs(t.nameSpace, t.text, funcMap)
387395
return t
388396
}
389397

390398
// wrapFuncs records the functions with text/template. We wrap them to
391399
// unlock the nameSpace. See TestRecursiveExecute for a test case.
392-
func (t *Template) wrapFuncs() {
393-
if len(t.funcMap) == 0 {
400+
func wrapFuncs(ns *nameSpace, textTemplate *template.Template, funcMap FuncMap) {
401+
if len(funcMap) == 0 {
394402
return
395403
}
396-
tfuncs := make(template.FuncMap, len(t.funcMap))
397-
for name, fn := range t.funcMap {
404+
tfuncs := make(template.FuncMap, len(funcMap))
405+
for name, fn := range funcMap {
398406
fnv := reflect.ValueOf(fn)
399407
wrapper := func(args []reflect.Value) []reflect.Value {
400-
t.nameSpace.mu.RUnlock()
401-
defer t.nameSpace.mu.RLock()
408+
ns.mu.RUnlock()
409+
defer ns.mu.RLock()
402410
if fnv.Type().IsVariadic() {
403411
return fnv.CallSlice(args)
404412
} else {
@@ -408,7 +416,7 @@ func (t *Template) wrapFuncs() {
408416
wrapped := reflect.MakeFunc(fnv.Type(), wrapper)
409417
tfuncs[name] = wrapped.Interface()
410418
}
411-
t.text.Funcs(tfuncs)
419+
textTemplate.Funcs(tfuncs)
412420
}
413421

414422
// Delims sets the action delimiters to the specified strings, to be used in

0 commit comments

Comments
 (0)