[インデックス 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
:Clone
panics if template not parsed: https://golang.org/issue/3281
参考にした情報源リンク
- 上記の関連リンクに記載されたGoのコードレビューとIssueトラッカー。
- Go言語の
html/template
パッケージのドキュメント。 - Go言語の
text/template
パッケージのドキュメント(html/template
はtext/template
をベースにしているため)。 - Go言語における
nil
ポインタとパニックに関する一般的な知識。