[インデックス 18138] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージにおけるバグ修正を含んでいます。具体的には、src/pkg/text/template/multi_test.go
に新しいテストケースが追加され、src/pkg/text/template/template.go
の AddParseTree
メソッドに1行の変更が加えられています。
コミット
- コミットハッシュ:
ff006982c355bef55fb7b36944a1cc661bacf287
- Author: Josh Bleecher Snyder josharian@gmail.com
- Date: Mon Dec 30 17:17:19 2013 -0800
- コミットメッセージ:
text/template: don't panic when using AddParseTree with an unparsed template Fixes #7032. R=golang-codereviews, r CC=golang-codereviews https://golang.org/cl/43960045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ff006982c355bef55fb7b36944a1cc661bacf287
元コミット内容
text/template: don't panic when using AddParseTree with an unparsed template
このコミットは、text/template
パッケージにおいて、パースされていないテンプレートに対して AddParseTree
メソッドを使用した場合に発生していたパニック(panic)を修正するものです。
変更の背景
この変更は、Go Issue #7032 に対応するものです。元の実装では、text/template
パッケージの Template
型のインスタンスがまだパースされていない状態(つまり、Parse
メソッドなどが呼び出されていない状態)で AddParseTree
メソッドを呼び出すと、内部で nil
ポインタ参照が発生し、プログラムがパニックを起こしていました。
AddParseTree
は、既存のテンプレートに別のパース済みテンプレートツリーを追加するためのメソッドです。通常、このメソッドは既に初期化され、場合によってはパース済みの Template
インスタンスに対して使用されることを想定しています。しかし、ユーザーが New
で作成しただけの、まだ Parse
されていない Template
インスタンスに対して AddParseTree
を呼び出すシナリオが考慮されておらず、その結果として予期せぬパニックが発生していました。
このパニックは、Go言語における「パニックは回復不能なエラーのために予約されている」という原則に反しており、より穏やかなエラーハンドリング(エラーの返却)が望ましい状況でした。このコミットは、この予期せぬパニックを防ぎ、より堅牢な動作を保証することを目的としています。
前提知識の解説
Go言語の text/template
パッケージ
text/template
パッケージは、Go言語でテキストベースの出力を生成するためのデータ駆動型テンプレートエンジンを提供します。HTML、XML、プレーンテキストなど、様々な形式のテキストを生成するのに使用されます。
Template
型: テンプレートのコンパイル済み表現を表す構造体です。New(name string) *Template
: 指定された名前で新しい空のTemplate
インスタンスを作成します。この時点では、テンプレートはまだパースされていません。Parse(text string) (*Template, error)
: テンプレート文字列をパースし、Template
インスタンスにコンパイルします。AddParseTree(name string, tree *parse.Tree) (*Template, error)
: 既存のTemplate
インスタンスに、指定された名前とパースツリー (*parse.Tree
) を持つ新しいテンプレートを追加します。これにより、複数のテンプレートを一つのTemplate
インスタンス内で管理し、{{template "name"}}
のように相互に参照できるようになります。
panic
と nil
ポインタ参照
Go言語における panic
は、プログラムの実行を停止させる回復不能なエラーを示します。これは通常、プログラマーの論理的な誤りや、予期せぬ深刻な状態(例: メモリ不足)が発生した場合に用いられます。
nil
ポインタ参照は、nil
値を持つポインタが指すメモリ領域にアクセスしようとしたときに発生するランタイムエラーです。これはGo言語で最も一般的なパニックの原因の一つであり、プログラムのクラッシュにつながります。
このコミットの文脈では、Template
インスタンスが適切に初期化されていない状態で内部フィールドにアクセスしようとした結果、nil
ポインタ参照によるパニックが発生していました。
技術的詳細
問題の根源は、Template
型の内部構造にあります。Template
型は、テンプレートの共通状態を保持する common
フィールド(*common
型)と、名前でテンプレートをマップする tmpl
フィールド(map[string]*Template
型)を持っています。
New
関数で Template
インスタンスを作成した直後、common
フィールドは nil
のままです。Parse
メソッドが呼び出されると、common
フィールドが適切に初期化されます。
AddParseTree
メソッドの元の実装では、テンプレート名の再定義をチェックするために t.tmpl[name] != nil
という条件を使用していました。しかし、t.tmpl
は t.common
が初期化された後にのみ有効なマップとして機能します。New
で作成されたばかりの Template
インスタンスでは、t.common
が nil
であるため、t.tmpl
も実質的に nil
と同等な状態です。この状態で t.tmpl[name]
のようなマップアクセスを行うと、nil
マップへのアクセスとなり、ランタイムパニックが発生していました。
このコミットの修正は、t.tmpl[name] != nil
のチェックの前に、t.common != nil
というガード条件を追加することで、この問題を解決しています。これにより、common
フィールドが初期化されていない場合には、t.tmpl
へのアクセスを避け、パニックを防ぐことができます。
コアとなるコードの変更箇所
diff --git a/src/pkg/text/template/multi_test.go b/src/pkg/text/template/multi_test.go
index 1f6ed5d8e2..e4e804880a 100644
--- a/src/pkg/text/template/multi_test.go
+++ b/src/pkg/text/template/multi_test.go
@@ -259,6 +259,18 @@ func TestAddParseTree(t *testing.T) {
}\n }\n \n+// Issue 7032
+func TestAddParseTreeToUnparsedTemplate(t *testing.T) {
+ master := "{{define \"master\"}}{{end}}"\n+ tmpl := New("master")\n+ tree, err := parse.Parse("master", master, "", "", nil)\n+ if err != nil {\n+ t.Fatalf("unexpected parse err: %v", err)\n+ }\n+ masterTree := tree["master"]\n+ tmpl.AddParseTree("master", masterTree) // used to panic\n+}\n+\n func TestRedefinition(t *testing.T) {
var tmpl *Template
var err error
diff --git a/src/pkg/text/template/template.go b/src/pkg/text/template/template.go
index a2b9062ad1..249d0cbfb9 100644
--- a/src/pkg/text/template/template.go
+++ b/src/pkg/text/template/template.go
@@ -105,7 +105,7 @@ func (t *Template) copy(c *common) *Template {
// AddParseTree creates a new template with the name and parse tree
// and associates it with t.
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
-\tif t.tmpl[name] != nil {\n+\tif t.common != nil && t.tmpl[name] != nil {\n \t\treturn nil, fmt.Errorf("template: redefinition of template %q", name)\n \t}\n \tnt := t.New(name)\n```
## コアとなるコードの解説
### `src/pkg/text/template/template.go` の変更
```go
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
- if t.tmpl[name] != nil {
+ if t.common != nil && t.tmpl[name] != nil {
return nil, fmt.Errorf("template: redefinition of template %q", name)
}
nt := t.New(name)
この変更は、AddParseTree
メソッドの冒頭にあるテンプレート名の再定義チェックの条件を修正しています。
- 変更前:
if t.tmpl[name] != nil { ... }
t.tmpl
はTemplate
インスタンスがNew
で作成された直後には、その内部のcommon
フィールドがnil
であるため、nil
マップとして扱われます。nil
マップに対して要素アクセス(t.tmpl[name]
)を行うと、Goランタイムはパニックを発生させます。
- 変更後:
if t.common != nil && t.tmpl[name] != nil { ... }
t.common != nil
という条件が追加されました。これにより、t.common
がnil
でない場合にのみ、t.tmpl[name]
のチェックが実行されます。- Go言語の論理AND (
&&
) 演算子はショートサーキット評価を行うため、t.common
がnil
であれば、t.tmpl[name]
の評価は行われず、nil
マップへのアクセスによるパニックが回避されます。 - この修正により、パースされていない(
common
がnil
の)Template
インスタンスに対してAddParseTree
が呼び出されても、パニックすることなく処理が続行され、新しいテンプレートが追加されるようになります。
src/pkg/text/template/multi_test.go
の変更
// Issue 7032
func TestAddParseTreeToUnparsedTemplate(t *testing.T) {
master := "{{define \"master\"}}{{end}}"
tmpl := New("master")
tree, err := parse.Parse("master", master, "", "", nil)
if err != nil {
t.Fatalf("unexpected parse err: %v", err)
}
masterTree := tree["master"]
tmpl.AddParseTree("master", masterTree) // used to panic
}
このテストケースは、Issue #7032 で報告されたパニックを再現し、修正が正しく機能することを確認するために追加されました。
master := "{{define \"master\"}}{{end}}"
:master
という名前の定義済みテンプレートを含む文字列を定義します。tmpl := New("master")
:New
関数を使って、まだパースされていない新しいTemplate
インスタンスtmpl
を作成します。この時点では、tmpl
の内部common
フィールドはnil
です。tree, err := parse.Parse("master", master, "", "", nil)
:parse
パッケージを使用して、master
テンプレート文字列をパースし、そのパースツリーを取得します。masterTree := tree["master"]
: パースツリーからmaster
テンプレートに対応するツリーを取得します。tmpl.AddParseTree("master", masterTree)
: ここが重要なポイントです。まだパースされていないtmpl
インスタンスに対して、パース済みのmasterTree
をAddParseTree
で追加しようとします。- 修正前: この行で
nil
マップへのアクセスが発生し、パニックしていました。 - 修正後:
t.common != nil
のチェックによりパニックが回避され、テストは正常に完了します。
- 修正前: この行で
このテストケースは、修正が意図した通りにパニックを抑制し、AddParseTree
がパースされていないテンプレートに対しても安全に呼び出せるようになったことを検証しています。
関連リンク
- Go Issue #7032: https://github.com/golang/go/issues/7032 (このコミットの修正対象となったIssue)
- Go CL 43960045: https://golang.org/cl/43960045 (このコミットに対応するGo Code Reviewの変更リスト)
参考にした情報源リンク
- Go
text/template
package documentation: https://pkg.go.dev/text/template - Go
parse
package documentation: https://pkg.go.dev/text/template/parse - Goにおけるパニックとエラーハンドリングに関する一般的な情報 (Web検索結果):
- https://mdaverde.com/
- https://stackoverflow.com/
(具体的なURLはWeb検索結果のキャッシュのため変動する可能性がありますが、
text/template
のパニック、AddParseTree
の使用法、Goにおけるnil
ポインタ参照とパニックに関する一般的な情報源として参照しました。)