[インデックス 14038] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージにおける、Templates
メソッドが nil
の Template
オブジェクトに対して呼び出された際に発生するクラッシュ(パニック)を修正するものです。具体的には、Template
構造体の common
フィールドが nil
である場合に Templates
メソッドが安全に nil
を返すように変更されています。
コミット
commit bcccad40202ba895d237d9d0a921b33bc2c5601f
Author: Rob Pike <r@golang.org>
Date: Sun Oct 7 09:26:59 2012 +1100
text/template: fix nil crash on Templates
Fixes #3872.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6612060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bcccad40202ba895d237d9d0a921b33bc2c5601f
元コミット内容
text/template: fix nil crash on Templates
Fixes #3872.
このコミットは、text/template
パッケージの Templates
メソッドが nil
の Template
オブジェクトに対して呼び出された際に発生するクラッシュを修正します。これは Issue 3872 に対応するものです。
変更の背景
この変更は、Go言語の Issue トラッカーで報告された Issue 3872: text/template: Templates() crashes on nil Template に対応するものです。
報告された問題は、text/template
パッケージにおいて、template.New("Name").Templates()
のように、まだパースされていない(つまり、Parse
メソッドなどが呼び出されていない)新しい Template
オブジェクトに対して Templates()
メソッドを呼び出すと、nil
ポインタ参照によるパニックが発生するというものでした。
New
関数で作成された Template
オブジェクトは、初期状態では内部の common
フィールドが nil
になっています。Templates()
メソッドは、この common
フィールドを通じてテンプレートのマップにアクセスしようとしますが、common
が nil
の場合、その参照がパニックを引き起こしていました。
この修正は、このような不正な状態での Templates()
メソッドの呼び出しを安全に処理し、クラッシュを防ぐことを目的としています。
前提知識の解説
Go言語の text/template
パッケージ
text/template
パッケージは、Go言語の標準ライブラリの一部であり、テキストベースのテンプレートを生成するための機能を提供します。これは、HTML、XML、プレーンテキストなどの動的なコンテンツを生成する際に非常に便利です。
主要な概念は以下の通りです。
Template
構造体: テンプレートのインスタンスを表します。テンプレートの定義、パース、実行(データとの結合)を行います。New
関数: 新しいTemplate
オブジェクトを作成します。この時点ではテンプレートの内容は空です。Parse
メソッド: テンプレート文字列をパースし、Template
オブジェクトにその構造をロードします。Execute
メソッド: パースされたテンプレートにデータを適用し、結果をio.Writer
に書き出します。Templates
メソッド: 現在のTemplate
オブジェクトに関連付けられているすべてのテンプレート(自身を含む)のスライスを返します。text/template
パッケージでは、テンプレートは名前空間を持ち、{{template "name"}}
のように他のテンプレートを呼び出すことができます。Templates
メソッドは、これらの関連するテンプレートを一覧表示するために使用されます。
nil
ポインタとパニック
Go言語では、ポインタが何も指していない状態を nil
と表現します。nil
ポインタに対してメソッドを呼び出したり、フィールドにアクセスしようとすると、ランタイムパニック(panic
)が発生します。これは、プログラムの異常終了を引き起こす深刻なエラーです。
このコミットの背景にある問題は、Template
オブジェクトが完全に初期化されていない(特に内部の common
フィールドが nil
のまま)状態で Templates()
メソッドが呼び出されたときに、nil
ポインタ参照が発生し、プログラムがクラッシュするというものでした。
Template.common
フィールド
Template
構造体には、common
という内部フィールドが存在します。これは、複数のテンプレート間で共有される共通の状態(例えば、名前付きテンプレートのマップなど)を保持するために使用されます。New
関数でテンプレートが作成された直後や、エラーが発生してテンプレートが適切に初期化されなかった場合などには、この common
フィールドが nil
になる可能性があります。
Templates()
メソッドは、この common
フィールドを通じて、関連するテンプレートのマップ (t.tmpl
) にアクセスします。したがって、t.common
が nil
の場合、t.common.tmpl
のようなアクセスはパニックを引き起こします。
技術的詳細
この修正は、text/template
パッケージの Template
構造体における Templates()
メソッドの堅牢性を向上させるものです。
修正前は、Templates()
メソッドは Template
オブジェクトの内部状態(特に t.common
フィールド)が適切に初期化されていることを前提としていました。しかし、template.New("Name")
のように、テンプレートが作成されただけでまだパースされていない場合、t.common
フィールドは nil
のままです。この状態で Templates()
メソッドが呼び出されると、t.common.tmpl
のような内部マップへのアクセスが nil
ポインタデリファレンスを引き起こし、ランタイムパニックが発生していました。
このコミットでは、Templates()
メソッドの冒頭に nil
チェックを追加することでこの問題を解決しています。
func (t *Template) Templates() []*Template {
if t.common == nil { // 追加されたnilチェック
return nil
}
// 既存のロジック: common.tmpl マップからテンプレートを収集
m := make([]*Template, 0, len(t.tmpl))
for _, v := range t.tmpl {
m = append(m, v)
}
return m
}
この変更により、t.common
が nil
の場合、Templates()
メソッドは安全に nil
スライスを返します。これにより、nil
ポインタパニックが回避され、プログラムの安定性が向上します。Templates()
メソッドのドキュメントには、Templates
が t
自身を含む t
に関連付けられたテンプレートのスライスを返すと記載されていますが、nil
の Template
オブジェクトに対しては、関連付けられたテンプレートが存在しないため、nil
スライスを返すのが適切な振る舞いと判断されたと考えられます。
また、この修正にはテストケース TestExecuteOnNewTemplate
が追加されています。このテストは、New("Name").Templates()
を呼び出すことで、以前パニックを引き起こしていたシナリオを再現し、修正が正しく機能することを確認します。テストは単にこの呼び出しを実行し、パニックが発生しないことを暗黙的に検証します。
コアとなるコードの変更箇所
src/pkg/text/template/exec_test.go
--- a/src/pkg/text/template/exec_test.go
+++ b/src/pkg/text/template/exec_test.go
@@ -811,3 +811,8 @@ func TestTree(t *testing.T) {
t.Errorf("expected %q got %q", expect, result)
}
}
+
+func TestExecuteOnNewTemplate(t *testing.T) {
+ // This is issue 3872.
+ _ = New("Name").Templates()
+}
src/pkg/text/template/template.go
--- a/src/pkg/text/template/template.go
+++ b/src/pkg/text/template/template.go
@@ -117,6 +117,9 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error
// Templates returns a slice of the templates associated with t, including t
// itself.
func (t *Template) Templates() []*Template {
+\tif t.common == nil {
+\t\treturn nil
+\t}
// Return a slice so we don't expose the map.
m := make([]*Template, 0, len(t.tmpl))
for _, v := range t.tmpl {
コアとなるコードの解説
このコミットの核心的な変更は、src/pkg/text/template/template.go
ファイル内の Template
構造体の Templates()
メソッドに追加された nil
チェックです。
func (t *Template) Templates() []*Template {
if t.common == nil { // ここが追加された行
return nil
}
// ... 既存のロジック ...
}
func (t *Template) Templates() []*Template
: これはTemplate
型のメソッドTemplates
のシグネチャです。このメソッドはTemplate
オブジェクトに関連付けられたすべてのテンプレートのスライス ([]*Template
) を返します。if t.common == nil { ... }
: この条件文が追加された修正の肝です。t.common
はTemplate
構造体の内部フィールドで、複数のテンプレート間で共有される共通のデータ(例えば、名前付きテンプレートのマップなど)を保持します。template.New("Name")
のように、テンプレートが作成されたばかりでまだパースされていない場合、このt.common
フィールドはnil
になります。return nil
:t.common
がnil
である場合、つまりテンプレートがまだ完全に初期化されていないか、有効な内部状態を持っていない場合、このメソッドはnil
の[]*Template
スライスを返します。これにより、後続のt.common.tmpl
へのアクセスがnil
ポインタデリファレンスを引き起こすのを防ぎ、パニックを回避します。
このシンプルな nil
チェックの追加により、Templates()
メソッドはより堅牢になり、不正な状態の Template
オブジェクトに対しても安全に呼び出すことができるようになりました。
また、src/pkg/text/template/exec_test.go
に追加されたテストケース TestExecuteOnNewTemplate
は、この修正が意図通りに機能することを確認するためのものです。
func TestExecuteOnNewTemplate(t *testing.T) {
// This is issue 3872.
_ = New("Name").Templates()
}
このテストは、New("Name").Templates()
を呼び出すだけで、以前パニックが発生していたシナリオを再現します。Goのテストフレームワークでは、テスト関数内でパニックが発生するとテストが失敗するため、このテストが成功するということは、Templates()
メソッドが nil
の Template
に対して呼び出されてもパニックが発生しないことを意味します。
関連リンク
- Go Issue 3872: text/template: Templates() crashes on nil Template
- Go CL 6612060: text/template: fix nil crash on Templates