Skip to content

text/template: Template tree produced by template.Clone is not in a clean state #56409

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
kidlj opened this issue Oct 25, 2022 · 7 comments
Closed
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@kidlj
Copy link

kidlj commented Oct 25, 2022

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

$ go version
go version go1.19.2 linux/amd64

Does this issue reproduce with the latest release?

Yes.

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

go env Output
$ go env
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/lijian/.cache/go-build"
GOENV="/home/lijian/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS="-tags=dev"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/lijian/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/lijian/go"
GOPRIVATE=""
GOPROXY="https://goproxy.cn,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.19.2"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/lijian/Code/blog/go.mod"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3532686189=/tmp/go-build -gno-record-gcc-switches"

What did you do?

To make Go html/template inherit base template properly, every time executing an end template I make a copy via template.Clone of the full template tree, hoping it in a clean state, then add the desired end template to the cloned template tree and execute it. It almost works well, except that when a {{block "FOO" .}}{{end}} is defined in the base template, but the corresponding named block is not {{define}}-ed in the end executing template, the execution result may contain the content of block FOO defined in other templates that should not be executed.

The clone and execution process is as follows:

package templates

import (
	"embed"
	"fmt"
	"html/template"
	"io"

	"github.com/labstack/echo/v4"
)

//go:embed views/*.html
var templateFS embed.FS

type Template struct {
	templates *template.Template
}

func NewTemplate() *Template {
	templates := template.Must(template.New("").Funcs(funcMap()).ParseFS(templateFS, "views/*.html"))
	return &Template{
		templates: templates,
	}
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	tmpl := template.Must(t.templates.Clone())
	tmpl = template.Must(tmpl.ParseFS(templateFS, fmt.Sprintf("views/%s", name)))
	return tmpl.ExecuteTemplate(w, name, data)
}

func funcMap() template.FuncMap {
	return template.FuncMap{
		"dateTime": dateTime,
		"noEscape": noEscape,
	}
}

And the base layout template is as follows:

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width" />
	<title>{{block "title" .}}{{end}}</title>
	<link rel="stylesheet" href="/static/bin/style.css">
	{{block "feed" .}}{{end}}
</head>

<body>
	<div class="container">
		{{block "content" .}}{{end}}
	</div>
</body>

</html>

When the blog.html template that inherits layout.html and defines block feed is executed, it works fine(that produces feed block contents):

{{template "layout.html" .}}

{{define "title"}}{{.Blog.Title}}{{end}}

{{define "feed"}}
<link rel="alternate" type="application/atom+xml" title="Blog Feed" href="{{.Feed}}">
{{end}}

{{ define "content" }}
<article>
	{{with .Blog}}
	<div class="content">
		<div class="header">
			<h2>{{.Title}}</h2>
			<span>{{.Date | dateTime}}</span>
		</div>

		{{.Content | noEscape}}
	</div>
	{{end}}
</article>
{{end}}

But when the index.html template that also inherits layout.html but does not define block feed is executed, the execution result also contains the content of block feed, which seems like come from blog.html.

{{template "layout.html" .}}

{{define "title"}}Home{{end}}

{{ define "content" }}
<article>
	<div class="content">
		<h1 class="header">Home</h1>
		<p>
			<a href="/blog/">Go to Blog →</a>
		</p>
	</div>
</article>
{{end}}

The execution result:

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width" />
	<title>Home</title>
	<link rel="stylesheet" href="/static/bin/style.css">
	
<!-- this line should not be produced here -->
<link rel="alternate" type="application/atom+xml" title="Blog Feed" href="">

</head>

In short, in the static blog project, I want blogs.html and blog.html to have the feed link produced, but not having it on the index.html page.

The full project code can be checked here: https://github.com/kidlj/blog

What did you expect to see?

Template tree produced by template.Clone is in a clean state that doesn't generate contents from blocks that have not been defined.

What did you see instead?

Template tree produced by template.Clone generates block contents that are defined elsewhere not being executed.

@heschi
Copy link
Contributor

heschi commented Oct 25, 2022

cc @robpike

@heschi heschi added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Oct 25, 2022
@heschi heschi added this to the Backlog milestone Oct 25, 2022
@robpike
Copy link
Contributor

robpike commented Oct 26, 2022

Does the bug occur with text/template alone? Here your test uses html/template, which is a large wrapper around text/template. It would be good to know where the problem truly arises.

@kidlj
Copy link
Author

kidlj commented Oct 26, 2022

In the test project, I switched from importing html/template to text/template, the rendering result is same. So this must be a text/template issue.

@robpike
Copy link
Contributor

robpike commented Oct 26, 2022

Please provide a complete executable example.

@kidlj
Copy link
Author

kidlj commented Oct 26, 2022

The full testing project code can be checked here: https://github.com/kidlj/blog

@robpike
Copy link
Contributor

robpike commented Oct 26, 2022

I'm sorry but that's a large program with 55 external dependencies, and runs as a web server.

For best results when asking for help with issues, it's best to provide a short, self-contained source file that compiles into a single binary that demonstrates the issue.

@kidlj
Copy link
Author

kidlj commented Oct 26, 2022

Sure, sorry for the inconvenience. I found that I misunderstood how template parsing works while preparing a new smaller demonstration project for this issue. The truth is if the overlay template(index.html) doesn't define a named block that with the same name are defined in sibling or parent base templates, the block defined elsewhere will be executed and produce contents. This is by design.

Close.

@kidlj kidlj closed this as completed Oct 26, 2022
kidlj added a commit to kidlj/miniblog that referenced this issue Oct 26, 2022
@golang golang locked and limited conversation to collaborators Oct 26, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

4 participants