[インデックス 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/templatepackage documentation: https://pkg.go.dev/text/template - Go
parsepackage documentation: https://pkg.go.dev/text/template/parse - Goにおけるパニックとエラーハンドリングに関する一般的な情報 (Web検索結果):
- https://mdaverde.com/
- https://stackoverflow.com/
(具体的なURLはWeb検索結果のキャッシュのため変動する可能性がありますが、
text/templateのパニック、AddParseTreeの使用法、Goにおけるnilポインタ参照とパニックに関する一般的な情報源として参照しました。)