[インデックス 12619] ファイルの概要
このコミットは、Go言語の html/template パッケージにおける Clone メソッドがパニック(クラッシュ)を引き起こすバグを修正するものです。具体的には、テンプレートがまだパースされていない状態で Clone メソッドが呼び出された際に発生する nil ポインタ参照によるパニックを防ぎます。
コミット
commit 5f32c8b88bfe5f6e2ba32bb444dbda88ec741024
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Mar 13 16:55:43 2012 -0700
html/template: fix panic on Clone
Fixes #3281
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5819044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5f32c8b88bfe5f6e2ba32bb444dbda88ec741024
元コミット内容
html/template: fix panic on Clone
このコミットは、html/template パッケージの Clone メソッドがパニックを引き起こす問題を修正します。
関連するIssue: #3281
変更の背景
この変更は、Go言語のIssue 3281「html/template: Clone panics if template not parsed」を修正するために行われました。
元の問題は、html/template パッケージの Template 型の Clone メソッドが、クローン対象のテンプレートがまだパースされていない(つまり、Parse メソッドが呼び出されていない)状態で呼び出された場合に、nil ポインタ参照によるパニックを引き起こすというものでした。
html/template パッケージは、HTMLコンテンツを安全に生成するためのテンプレートエンジンを提供します。これには、クロスサイトスクリプティング(XSS)などの脆弱性を防ぐための自動エスケープ機能が含まれています。Template オブジェクトは、Parse メソッドによってテンプレート文字列を解析し、内部的に parse.Tree 構造体にその解析結果(構文木)を保持します。
Clone メソッドは、既存のテンプレートオブジェクトのコピーを作成するために使用されます。これにより、元のテンプレートを変更せずに、新しいテンプレートオブジェクトで追加の操作(例えば、新しいテンプレートの定義や既存のテンプレートの上書き)を行うことができます。しかし、Clone メソッドの実装が、Template オブジェクトの Tree フィールドが nil である可能性を考慮していなかったため、パースされていないテンプレートをクローンしようとすると、x.Tree.Root.CopyList() の部分で nil ポインタデリファレンスが発生し、プログラムがクラッシュしていました。
このコミットは、この特定のシナリオを適切に処理し、Clone メソッドが安全に動作するようにするための修正です。
前提知識の解説
- Go言語の
html/templateパッケージ: Go言語標準ライブラリの一部で、HTMLテンプレートを処理するためのパッケージです。セキュリティを重視しており、デフォルトでHTMLエスケープを行うことで、XSS攻撃などのウェブ脆弱性を防ぎます。テンプレートは、{{.FieldName}}のようなプレースホルダーや、{{range .Items}}...{{end}}のような制御構造を含めることができます。 Template型:html/templateパッケージの中心となる型で、個々のテンプレートを表します。テンプレートのパース、実行、および関連するテンプレートの管理を行います。Parseメソッド:Template型のメソッドで、テンプレート文字列を解析し、内部的な構文木(parse.Tree)を構築します。この構文木が、テンプレートの実行時に使用されます。Cloneメソッド:Template型のメソッドで、現在のテンプレートオブジェクトのコピーを作成します。このコピーは、元のテンプレートと同じ名前空間と定義されたテンプレートを共有しますが、独立したオブジェクトとして扱われます。これにより、元のテンプレートに影響を与えることなく、コピーに対して変更を加えることができます。parse.Tree構造体:html/templateパッケージの内部で使用される構造体で、パースされたテンプレートの抽象構文木(AST)を表します。Rootフィールドは、テンプレートのルートノードを指します。nilポインタ参照 (nil pointer dereference): プログラミングにおいて、nil(またはnull)値を持つポインタが指すメモリ領域にアクセスしようとしたときに発生するエラーです。Go言語では、これはランタイムパニックを引き起こし、プログラムが異常終了します。Must関数:html/templateパッケージで提供されるヘルパー関数で、テンプレート操作の結果としてエラーが発生した場合にパニックを引き起こします。これは、初期化時など、エラーが発生してはならない状況でテンプレートを扱う際に便利です。
技術的詳細
html/template パッケージの Template 型には、Tree というフィールドがあります。この Tree フィールドは *parse.Tree 型であり、テンプレートが Parse メソッドによって正常に解析された場合に、そのテンプレートの抽象構文木(AST)を保持します。もしテンプレートがまだパースされていない場合、またはパースに失敗した場合は、この Tree フィールドは nil になります。
Template 型の Clone メソッドは、既存のテンプレートオブジェクトを複製する際に、その内部状態、特に Tree フィールドが指す構文木も複製しようとします。元の実装では、Clone メソッド内で以下のようなコードがありました。
x.Tree = &parse.Tree{
Name: x.Tree.Name,
Root: x.Tree.Root.CopyList(),
}
このコードは、x.Tree が nil でないことを前提としていました。しかし、t1 := New("all") のようにテンプレートを新規作成した直後で、まだ Parse メソッドが呼び出されていない場合、t1 の Tree フィールドは nil です。この状態で t1.Clone() を呼び出すと、x.Tree.Name や x.Tree.Root.CopyList() の部分で nil ポインタデリファレンスが発生し、ランタイムパニックを引き起こしていました。
このコミットによる修正は、x.Tree が nil でない場合にのみ、構文木の複製処理を行うように条件分岐を追加することで、この問題を解決しています。
コアとなるコードの変更箇所
変更は主に src/pkg/html/template/template.go ファイルの Clone メソッドにあります。
--- a/src/pkg/html/template/template.go
+++ b/src/pkg/html/template/template.go
@@ -160,9 +160,11 @@ func (t *Template) Clone() (*Template, error) {
if src == nil || src.escaped {
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
}
-\t\tx.Tree = &parse.Tree{
-\t\t\tName: x.Tree.Name,
-\t\t\tRoot: x.Tree.Root.CopyList(),
+\t\tif x.Tree != nil {
+\t\t\tx.Tree = &parse.Tree{
+\t\t\t\tName: x.Tree.Name,
+\t\t\t\tRoot: x.Tree.Root.CopyList(),
+\t\t\t}
\t\t}
\t\tret.set[name] = &Template{
\t\t\tfalse,
また、この修正を検証するためのテストケースが src/pkg/html/template/clone_test.go に追加されています。
--- a/src/pkg/html/template/clone_test.go
+++ b/src/pkg/html/template/clone_test.go
@@ -113,3 +113,10 @@ func TestClone(t *testing.T) {
t.Errorf("t3: got %q want %q", got, want)
}
}
+
+// This used to crash; http://golang.org/issue/3281
+func TestCloneCrash(t *testing.T) {
+ t1 := New("all")
+ Must(t1.New("t1").Parse(`{{define "foo"}}foo{{end}}`))
+ t1.Clone()
+}
コアとなるコードの解説
template.go の変更点では、Clone メソッド内で x.Tree が nil でないことを確認する if x.Tree != nil という条件文が追加されました。これにより、テンプレートがまだパースされておらず x.Tree が nil の場合でも、nil ポインタデリファレンスが発生することなく、安全に Clone メソッドが実行されるようになります。x.Tree が nil の場合は、構文木の複製は行われず、Tree フィールドは nil のままコピーされます。これは、パースされていないテンプレートのクローンが、やはりパースされていない状態であることを意味するため、正しい挙動です。
clone_test.go に追加された TestCloneCrash テストケースは、このバグが修正されたことを検証します。
t1 := New("all")で新しいテンプレートセットを作成します。この時点では、t1自体はまだパースされていません。Must(t1.New("t1").Parse({{define "foo"}}foo{{end}}))で、t1の名前空間に新しいテンプレートt1を定義し、パースします。しかし、これはt1自体ではなく、そのサブテンプレートt1をパースしている点に注意が必要です。t1(親テンプレート) のTreeフィールドは依然としてnilのままです。t1.Clone()を呼び出します。修正前は、この行でパニックが発生していました。修正後は、x.Tree != nilのチェックにより、パニックが回避され、テストが正常に完了します。
このテストは、Clone メソッドが、親テンプレートがパースされていない状態でも安全に動作することを確認しています。
関連リンク
- Go Code Review 5819044: https://golang.org/cl/5819044
- Go Issue 3281:
html/template:Clonepanics if template not parsed: https://golang.org/issue/3281
参考にした情報源リンク
- 上記の関連リンクに記載されたGoのコードレビューとIssueトラッカー。
- Go言語の
html/templateパッケージのドキュメント。 - Go言語の
text/templateパッケージのドキュメント(html/templateはtext/templateをベースにしているため)。 - Go言語における
nilポインタとパニックに関する一般的な知識。