Skip to content

Commit cd2c9df

Browse files
cesparerobpike
authored andcommitted
html/template: fix Clone so that t.Lookup(t.Name()) yields t
Template.escape makes the assumption that t.Lookup(t.Name()) is t (escapeTemplate looks up the associated template by name and sets escapeErr appropriately). This assumption did not hold for a Cloned template, because the template associated with t.Name() was a second copy of the original. Add a test for the assumption that t.Lookup(t.Name()) == t. One effect of this broken assumption was #16101: parallel Executes racily accessed the template namespace because each Execute call saw t.escapeErr == nil and re-escaped the template concurrently with read accesses occurring outside the namespace mutex. Add a test for this race. Related to #12996 and CL 16104. Fixes #16101 Change-Id: I59831d0847abbabb4ef9135f2912c6ce982f9837 Reviewed-on: https://go-review.googlesource.com/31092 Run-TryBot: Rob Pike <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rob Pike <[email protected]>
1 parent d8cbc2c commit cd2c9df

File tree

2 files changed

+38
-0
lines changed

2 files changed

+38
-0
lines changed

src/html/template/clone_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"errors"
1010
"io/ioutil"
11+
"sync"
1112
"testing"
1213
"text/template/parse"
1314
)
@@ -194,3 +195,37 @@ func TestFuncMapWorksAfterClone(t *testing.T) {
194195
t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr)
195196
}
196197
}
198+
199+
// https://golang.org/issue/16101
200+
func TestTemplateCloneExecuteRace(t *testing.T) {
201+
const (
202+
input = `<title>{{block "a" .}}a{{end}}</title><body>{{block "b" .}}b{{end}}<body>`
203+
overlay = `{{define "b"}}A{{end}}`
204+
)
205+
outer := Must(New("outer").Parse(input))
206+
tmpl := Must(Must(outer.Clone()).Parse(overlay))
207+
208+
var wg sync.WaitGroup
209+
for i := 0; i < 10; i++ {
210+
wg.Add(1)
211+
go func() {
212+
defer wg.Done()
213+
for i := 0; i < 100; i++ {
214+
if err := tmpl.Execute(ioutil.Discard, "data"); err != nil {
215+
panic(err)
216+
}
217+
}
218+
}()
219+
}
220+
wg.Wait()
221+
}
222+
223+
func TestTemplateCloneLookup(t *testing.T) {
224+
// Template.escape makes an assumption that the template associated
225+
// with t.Name() is t. Check that this holds.
226+
tmpl := Must(New("x").Parse("a"))
227+
tmpl = Must(tmpl.Clone())
228+
if tmpl.Lookup(tmpl.Name()) != tmpl {
229+
t.Error("after Clone, tmpl.Lookup(tmpl.Name()) != tmpl")
230+
}
231+
}

src/html/template/template.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ func (t *Template) Clone() (*Template, error) {
240240
ret.set[ret.Name()] = ret
241241
for _, x := range textClone.Templates() {
242242
name := x.Name()
243+
if name == ret.Name() {
244+
continue
245+
}
243246
src := t.set[name]
244247
if src == nil || src.escapeErr != nil {
245248
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())

0 commit comments

Comments
 (0)