Skip to content

text/template: should t.init() be executed before t.muTmpl.Lock() in AddParseTree() method? #48436

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
And-ZJ opened this issue Sep 17, 2021 · 6 comments
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@And-ZJ
Copy link
Contributor

And-ZJ commented Sep 17, 2021

What version of Go are you using (go version)?

$ go version
go version go1.16.8 windows/amd64

Does this issue reproduce with the latest release?

yes for go1.17.1 and 1.16.8, but not in 1.16.7

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
(not relevant)

What did you do?

I read the CL "add lock for Template.tmpl to fix data race" https://golang.org/cl/348580. I'm a little confused about the following function fix:

// AddParseTree associates the argument parse tree with the template t, giving
// it the specified name. If the template has not been defined, this tree becomes
// its definition. If it has been defined and already has that name, the existing
// definition is replaced; otherwise a new template is created, defined, and returned.
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
	t.muTmpl.Lock()
	defer t.muTmpl.Unlock()
	t.init()
	nt := t
	if name != t.name {
		nt = t.New(name)
	}
	// Even if nt == t, we need to install it in the common.tmpl map.
	if t.associate(nt, tree) || nt.Tree == nil {
		nt.Tree = tree
	}
	return nt, nil
}

As you can see, it executes t.muTmpl.Lock() before t.init().

The init() method and the common structure are listed below.

// init guarantees that t has a valid common structure.
func (t *Template) init() {
   if t.common == nil {
      c := new(common)
      c.tmpl = make(map[string]*Template)
      c.parseFuncs = make(FuncMap)
      c.execFuncs = make(map[string]reflect.Value)
      t.common = c
   }
}

// common holds the information shared by related templates.
type common struct {
	tmpl   map[string]*Template // Map from name to defined templates.
	muTmpl sync.RWMutex         // protects tmpl
	option option
	// We use two maps, one for parsing and one for execution.
	// This separation makes the API cleaner since it doesn't
	// expose reflection to the client.
	muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
	parseFuncs FuncMap
	execFuncs  map[string]reflect.Value
}

In the init() method, if t.common is nil, the t.common will be initialized, including the default initialization of muTmpl.

Now return to the AddParseTree() method.

Because it executes t.init(), which means that t.common may be nil, executing t.muTmpl.Lock() first may produce nil panic. So in this method, should t.init() be executed before t.muTmpl.Lock()?

A simple reproduction procedure:

package main

import (
   "testing"
   "text/template"
)

func TestAddParseTreeInitAndLock(t *testing.T) {
	template.Must(new(template.Template).AddParseTree("go", nil))
}

What did you expect to see?

normal exit

What did you see instead?

panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
@ianlancetaylor
Copy link
Contributor

Thanks. I don't know how much sense that makes, but it used to work, so I guess we should let it keep working.

@ianlancetaylor
Copy link
Contributor

@gopherbot Please open backports to 1.16 and 1.17

Fix regression in AddParseTree due to earlier fix for #39807.

@gopherbot
Copy link
Contributor

Backport issue(s) opened: #48443 (for 1.16), #48444 (for 1.17).

Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://golang.org/wiki/MinorReleases.

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/350730 mentions this issue: text/template: initialize template before locking it

@cagedmantis cagedmantis added this to the Go1.18 milestone Sep 20, 2021
@cagedmantis cagedmantis added the NeedsFix The path to resolution is known, but the work has not been done. label Sep 20, 2021
@gopherbot
Copy link
Contributor

Change https://golang.org/cl/351115 mentions this issue: [release-branch.go1.17] text/template: initialize template before locking it

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/351116 mentions this issue: [release-branch.go1.16] text/template: initialize template before locking it

gopherbot pushed a commit that referenced this issue Sep 23, 2021
…king it

For #39807
For #48436
Fixes #48444

Change-Id: I75f82fd8738dd2f11f0c69b1230e1be1abc36024
Reviewed-on: https://go-review.googlesource.com/c/go/+/350730
Trust: Ian Lance Taylor <iant@golang.org>
Trust: Daniel Martí <mvdan@mvdan.cc>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
(cherry picked from commit ba1c52d)
Reviewed-on: https://go-review.googlesource.com/c/go/+/351115
gopherbot pushed a commit that referenced this issue Sep 23, 2021
…king it

For #39807
For #48436
Fixes #48443

Change-Id: I75f82fd8738dd2f11f0c69b1230e1be1abc36024
Reviewed-on: https://go-review.googlesource.com/c/go/+/350730
Trust: Ian Lance Taylor <iant@golang.org>
Trust: Daniel Martí <mvdan@mvdan.cc>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
(cherry picked from commit ba1c52d)
Reviewed-on: https://go-review.googlesource.com/c/go/+/351116
@golang golang locked and limited conversation to collaborators Sep 20, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

4 participants