[インデックス 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