Skip to content

Commit 3d85c69

Browse files
html/template: revert "avoid race when escaping updates template"
This reverts CLs 274450 and 279492, except for the new tests. The new race test is changed to skip, as it now fails. We can try again for 1.17. Original CL descriptions: 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. html/template: avoid race when escaping updates template For #39807 Fixes #43855 Change-Id: I2ce91321ada06ea496a982aefe170eb5af9ba847 Reviewed-on: https://go-review.googlesource.com/c/go/+/285957 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 54514c6 commit 3d85c69

File tree

2 files changed

+47
-84
lines changed

2 files changed

+47
-84
lines changed

src/html/template/exec_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,8 @@ var v = "v";
17201720
`
17211721

17221722
func TestEscapeRace(t *testing.T) {
1723+
t.Skip("this test currently fails with -race; see issue #39807")
1724+
17231725
tmpl := New("")
17241726
_, err := tmpl.New("templ.html").Parse(raceText)
17251727
if err != nil {
@@ -1777,6 +1779,39 @@ func TestRecursiveExecute(t *testing.T) {
17771779
}
17781780
}
17791781

1782+
// recursiveInvoker is for TestRecursiveExecuteViaMethod.
1783+
type recursiveInvoker struct {
1784+
t *testing.T
1785+
tmpl *Template
1786+
}
1787+
1788+
func (r *recursiveInvoker) Recur() (string, error) {
1789+
var sb strings.Builder
1790+
if err := r.tmpl.ExecuteTemplate(&sb, "subroutine", nil); err != nil {
1791+
r.t.Fatal(err)
1792+
}
1793+
return sb.String(), nil
1794+
}
1795+
1796+
func TestRecursiveExecuteViaMethod(t *testing.T) {
1797+
tmpl := New("")
1798+
top, err := tmpl.New("x.html").Parse(`{{.Recur}}`)
1799+
if err != nil {
1800+
t.Fatal(err)
1801+
}
1802+
_, err = tmpl.New("subroutine").Parse(`<a href="/x?p={{"'a<b'"}}">`)
1803+
if err != nil {
1804+
t.Fatal(err)
1805+
}
1806+
r := &recursiveInvoker{
1807+
t: t,
1808+
tmpl: tmpl,
1809+
}
1810+
if err := top.Execute(io.Discard, r); err != nil {
1811+
t.Fatal(err)
1812+
}
1813+
}
1814+
17801815
// Issue 43295.
17811816
func TestTemplateFuncsAfterClone(t *testing.T) {
17821817
s := `{{ f . }}`

src/html/template/template.go

Lines changed: 12 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"os"
1212
"path"
1313
"path/filepath"
14-
"reflect"
1514
"sync"
1615
"text/template"
1716
"text/template/parse"
@@ -36,20 +35,18 @@ var escapeOK = fmt.Errorf("template escaped correctly")
3635

3736
// nameSpace is the data structure shared by all templates in an association.
3837
type nameSpace struct {
39-
mu sync.RWMutex
38+
mu sync.Mutex
4039
set map[string]*Template
4140
escaped bool
4241
esc escaper
43-
// The original functions, before wrapping.
44-
funcMap FuncMap
4542
}
4643

4744
// Templates returns a slice of the templates associated with t, including t
4845
// itself.
4946
func (t *Template) Templates() []*Template {
5047
ns := t.nameSpace
51-
ns.mu.RLock()
52-
defer ns.mu.RUnlock()
48+
ns.mu.Lock()
49+
defer ns.mu.Unlock()
5350
// Return a slice so we don't expose the map.
5451
m := make([]*Template, 0, len(ns.set))
5552
for _, v := range ns.set {
@@ -87,8 +84,8 @@ func (t *Template) checkCanParse() error {
8784
if t == nil {
8885
return nil
8986
}
90-
t.nameSpace.mu.RLock()
91-
defer t.nameSpace.mu.RUnlock()
87+
t.nameSpace.mu.Lock()
88+
defer t.nameSpace.mu.Unlock()
9289
if t.nameSpace.escaped {
9390
return fmt.Errorf("html/template: cannot Parse after Execute")
9491
}
@@ -97,16 +94,6 @@ func (t *Template) checkCanParse() error {
9794

9895
// escape escapes all associated templates.
9996
func (t *Template) escape() error {
100-
t.nameSpace.mu.RLock()
101-
escapeErr := t.escapeErr
102-
t.nameSpace.mu.RUnlock()
103-
if escapeErr != nil {
104-
if escapeErr == escapeOK {
105-
return nil
106-
}
107-
return escapeErr
108-
}
109-
11097
t.nameSpace.mu.Lock()
11198
defer t.nameSpace.mu.Unlock()
11299
t.nameSpace.escaped = true
@@ -134,8 +121,6 @@ func (t *Template) Execute(wr io.Writer, data interface{}) error {
134121
if err := t.escape(); err != nil {
135122
return err
136123
}
137-
t.nameSpace.mu.RLock()
138-
defer t.nameSpace.mu.RUnlock()
139124
return t.text.Execute(wr, data)
140125
}
141126

@@ -151,36 +136,20 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})
151136
if err != nil {
152137
return err
153138
}
154-
t.nameSpace.mu.RLock()
155-
defer t.nameSpace.mu.RUnlock()
156139
return tmpl.text.Execute(wr, data)
157140
}
158141

159142
// lookupAndEscapeTemplate guarantees that the template with the given name
160143
// is escaped, or returns an error if it cannot be. It returns the named
161144
// template.
162145
func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) {
163-
t.nameSpace.mu.RLock()
146+
t.nameSpace.mu.Lock()
147+
defer t.nameSpace.mu.Unlock()
148+
t.nameSpace.escaped = true
164149
tmpl = t.set[name]
165-
var escapeErr error
166-
if tmpl != nil {
167-
escapeErr = tmpl.escapeErr
168-
}
169-
t.nameSpace.mu.RUnlock()
170-
171150
if tmpl == nil {
172151
return nil, fmt.Errorf("html/template: %q is undefined", name)
173152
}
174-
if escapeErr != nil {
175-
if escapeErr != escapeOK {
176-
return nil, escapeErr
177-
}
178-
return tmpl, nil
179-
}
180-
181-
t.nameSpace.mu.Lock()
182-
defer t.nameSpace.mu.Unlock()
183-
t.nameSpace.escaped = true
184153
if tmpl.escapeErr != nil && tmpl.escapeErr != escapeOK {
185154
return nil, tmpl.escapeErr
186155
}
@@ -286,13 +255,6 @@ func (t *Template) Clone() (*Template, error) {
286255
}
287256
ns := &nameSpace{set: make(map[string]*Template)}
288257
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)
296258
ret := &Template{
297259
nil,
298260
textClone,
@@ -307,13 +269,12 @@ func (t *Template) Clone() (*Template, error) {
307269
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
308270
}
309271
x.Tree = x.Tree.Copy()
310-
tc := &Template{
272+
ret.set[name] = &Template{
311273
nil,
312274
x,
313275
x.Tree,
314276
ret.nameSpace,
315277
}
316-
ret.set[name] = tc
317278
}
318279
// Return the template associated with the name of this template.
319280
return ret.set[ret.Name()], nil
@@ -382,43 +343,10 @@ type FuncMap map[string]interface{}
382343
// type. However, it is legal to overwrite elements of the map. The return
383344
// value is the template, so calls can be chained.
384345
func (t *Template) Funcs(funcMap FuncMap) *Template {
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)
346+
t.text.Funcs(template.FuncMap(funcMap))
395347
return t
396348
}
397349

398-
// wrapFuncs records the functions with text/template. We wrap them to
399-
// unlock the nameSpace. See TestRecursiveExecute for a test case.
400-
func wrapFuncs(ns *nameSpace, textTemplate *template.Template, funcMap FuncMap) {
401-
if len(funcMap) == 0 {
402-
return
403-
}
404-
tfuncs := make(template.FuncMap, len(funcMap))
405-
for name, fn := range funcMap {
406-
fnv := reflect.ValueOf(fn)
407-
wrapper := func(args []reflect.Value) []reflect.Value {
408-
ns.mu.RUnlock()
409-
defer ns.mu.RLock()
410-
if fnv.Type().IsVariadic() {
411-
return fnv.CallSlice(args)
412-
} else {
413-
return fnv.Call(args)
414-
}
415-
}
416-
wrapped := reflect.MakeFunc(fnv.Type(), wrapper)
417-
tfuncs[name] = wrapped.Interface()
418-
}
419-
textTemplate.Funcs(tfuncs)
420-
}
421-
422350
// Delims sets the action delimiters to the specified strings, to be used in
423351
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
424352
// definitions will inherit the settings. An empty delimiter stands for the
@@ -432,8 +360,8 @@ func (t *Template) Delims(left, right string) *Template {
432360
// Lookup returns the template with the given name that is associated with t,
433361
// or nil if there is no such template.
434362
func (t *Template) Lookup(name string) *Template {
435-
t.nameSpace.mu.RLock()
436-
defer t.nameSpace.mu.RUnlock()
363+
t.nameSpace.mu.Lock()
364+
defer t.nameSpace.mu.Unlock()
437365
return t.set[name]
438366
}
439367

0 commit comments

Comments
 (0)