mirror of
https://github.com/golang/go.git
synced 2025-05-20 23:03:26 +00:00
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 <r@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
parent
d8cbc2c918
commit
cd2c9df761
@ -8,6 +8,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template/parse"
|
"text/template/parse"
|
||||||
)
|
)
|
||||||
@ -194,3 +195,37 @@ func TestFuncMapWorksAfterClone(t *testing.T) {
|
|||||||
t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr)
|
t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://golang.org/issue/16101
|
||||||
|
func TestTemplateCloneExecuteRace(t *testing.T) {
|
||||||
|
const (
|
||||||
|
input = `<title>{{block "a" .}}a{{end}}</title><body>{{block "b" .}}b{{end}}<body>`
|
||||||
|
overlay = `{{define "b"}}A{{end}}`
|
||||||
|
)
|
||||||
|
outer := Must(New("outer").Parse(input))
|
||||||
|
tmpl := Must(Must(outer.Clone()).Parse(overlay))
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
if err := tmpl.Execute(ioutil.Discard, "data"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTemplateCloneLookup(t *testing.T) {
|
||||||
|
// Template.escape makes an assumption that the template associated
|
||||||
|
// with t.Name() is t. Check that this holds.
|
||||||
|
tmpl := Must(New("x").Parse("a"))
|
||||||
|
tmpl = Must(tmpl.Clone())
|
||||||
|
if tmpl.Lookup(tmpl.Name()) != tmpl {
|
||||||
|
t.Error("after Clone, tmpl.Lookup(tmpl.Name()) != tmpl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -240,6 +240,9 @@ func (t *Template) Clone() (*Template, error) {
|
|||||||
ret.set[ret.Name()] = ret
|
ret.set[ret.Name()] = ret
|
||||||
for _, x := range textClone.Templates() {
|
for _, x := range textClone.Templates() {
|
||||||
name := x.Name()
|
name := x.Name()
|
||||||
|
if name == ret.Name() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
src := t.set[name]
|
src := t.set[name]
|
||||||
if src == nil || src.escapeErr != nil {
|
if src == nil || src.escapeErr != nil {
|
||||||
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
|
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user