[インデックス 10501] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージにおける Clone
メソッドのバグを修正するものです。具体的には、テンプレートをクローン(複製)する際に、ルートテンプレートのコピーが正しく行われない問題を解決し、その自己整合性を検証するためのテストが追加されています。
コミット
- コミットハッシュ:
0197cc49ae3bfabc0edbeb0ae7534036d130dd71
- 作者: Rob Pike r@golang.org
- 日付: Thu Nov 24 16:07:19 2011 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0197cc49ae3bfabc0edbeb0ae7534036d130dd71
元コミット内容
text/template: fix bug in Clone
Cloned template copied the root template incorrectly.
Add test of self-consistency.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5436063
変更の背景
text/template
パッケージの Template
型には、既存のテンプレートを複製するための Clone
メソッドが提供されています。このメソッドは、元のテンプレートの構造や定義を保持しつつ、新しい独立したテンプレートインスタンスを作成するために使用されます。しかし、以前の実装では、クローンされたテンプレートが元のテンプレートの「ルート」を正しくコピーしていませんでした。
テンプレートシステムにおいて、複数の名前付きテンプレートが関連付けられている場合、それらは通常、単一の「ルート」テンプレートに属しています。Clone
メソッドの目的は、このルートテンプレートとその関連テンプレートの完全なコピーを作成することです。バグのある実装では、クローンされたテンプレートが、その内部で参照するルートテンプレートが、クローンされた自分自身ではなく、元のテンプレートを参照してしまう可能性がありました。これにより、クローンされたテンプレートが期待通りに動作しない、あるいは予期せぬ副作用を引き起こす可能性がありました。
この問題を解決し、クローンされたテンプレートが完全に自己完結的で、元のテンプレートから独立して機能するようにするために、このコミットが作成されました。また、このようなバグが将来的に再発しないよう、クローンされたテンプレートの自己整合性を検証する新しいテストケースも追加されています。
前提知識の解説
Go言語の text/template
パッケージ
text/template
パッケージは、Go言語でテキストベースの出力を生成するためのデータ駆動型テンプレートエンジンを提供します。これは、HTML、XML、プレーンテキストなどの動的なコンテンツを生成するのに非常に役立ちます。
Template
型: テンプレートの定義と実行をカプセル化する主要な型です。- 名前付きテンプレート:
text/template
は、複数の名前付きテンプレートを管理できます。例えば、{{template "header"}}
のように、別のテンプレートを呼び出すことができます。 - ルートテンプレートと関連テンプレート: 複数の名前付きテンプレートが定義されている場合、それらは通常、単一の
Template
インスタンス(ルートテンプレート)に属しています。他の名前付きテンプレートは、このルートテンプレートに関連付けられた「関連テンプレート」として扱われます。 Parse
メソッド: テンプレート文字列を解析し、Template
オブジェクトを構築します。Execute
/ExecuteTemplate
メソッド: テンプレートを実行し、指定されたデータ構造を適用して出力を生成します。Clone
メソッド: 既存のTemplate
インスタンスのディープコピーを作成します。これにより、元のテンプレートを変更せずに、その構造を再利用して新しいテンプレートを作成できます。これは、例えば、共通のテンプレート定義をベースに、特定の用途に合わせて一部をカスタマイズしたテンプレートを作成する場合などに有用です。
テンプレートの内部構造 (tmpl
マップと common
構造体)
text/template
パッケージの Template
型の内部には、以下のような重要なフィールドが存在します(コミット当時の実装に基づく推測):
tmpl
(map[string]*Template): このマップは、現在のTemplate
インスタンスに関連付けられているすべての名前付きテンプレートを保持します。キーはテンプレート名、値は対応するTemplate
オブジェクトです。このマップには、自分自身(ルートテンプレート)も含まれます。common
(*common): これは、複数のテンプレートインスタンス間で共有される共通のデータや設定を保持するための内部構造へのポインタです。例えば、デリミタの設定や、テンプレートの解析ツリーなどが含まれる可能性があります。Clone
操作では、このcommon
構造体を適切に処理することが重要です。
Clone
メソッドの役割
Clone
メソッドは、既存の Template
インスタンスから新しい Template
インスタンスを作成します。この新しいインスタンスは、元のインスタンスと同じテンプレート定義、名前付きテンプレートのセット、および設定(デリミタなど)を持ちますが、元のインスタンスとは独立しています。これにより、新しいインスタンスに対して Parse
や AddParseTree
などの操作を行っても、元のインスタンスには影響を与えません。
この独立性を確保するためには、Clone
メソッドが、ルートテンプレートだけでなく、関連するすべての名前付きテンプレート、そしてそれらが参照する内部的な共通データ構造(common
)も正しく複製し、新しいクローンされたインスタンスに紐付ける必要があります。
技術的詳細
このコミットの技術的な核心は、Template.Clone()
メソッドと、その内部で利用される Template.copy()
メソッドの修正にあります。
修正前の問題点
修正前の Template.Clone()
メソッドは、以下のような問題がありました。
- ルートテンプレートの不適切なコピー:
Clone
メソッドは、まずt.copy()
を呼び出して新しいTemplate
インスタンスnt
を作成していました。しかし、このcopy
メソッドはcommon
フィールドをnil
に設定していました。その後、nt.init()
が呼び出されますが、この時点ではnt
のtmpl
マップにはまだルートテンプレートが正しく設定されていませんでした。 - 関連テンプレートの
common
フィールドの不整合:Clone
メソッドは、元のテンプレートt
のtmpl
マップをイテレートし、各関連テンプレートv
に対してv.copy()
を呼び出して新しいテンプレートtmpl
を作成していました。そして、tmpl.common = nt.common
と設定していました。しかし、nt.common
はnt.init()
の呼び出しによって初期化された新しいcommon
構造体であるべきですが、ルートテンプレートのtmpl
マップへの登録が不完全なため、この関連付けが正しく機能しない可能性がありました。特に、v.copy()
がcommon
をnil
に設定してしまうため、関連テンプレートが新しいクローンされたルートテンプレートのcommon
を参照するように明示的に設定する必要がありました。
結果として、クローンされた Template
インスタンス nt
の tmpl
マップ内のルートテンプレート(nt.tmpl[t.name]
)が、クローンされた nt
自身ではなく、元の t
を参照してしまう、あるいは common
構造体の参照が正しくないという問題が発生していました。これにより、クローンされたテンプレートが自己完結的でなく、元のテンプレートに依存してしまう状態になっていました。
修正内容
このコミットでは、以下の変更によって上記のバグが修正されました。
-
Template.copy()
メソッドの変更:- 修正前:
func (t *Template) copy() *Template
(引数なし) - 修正後:
func (t *Template) copy(c *common) *Template
(引数c *common
を追加) - この変更により、
copy
メソッドは、新しいTemplate
インスタンスを作成する際に、引数として渡されたcommon
構造体を直接nt.common
に設定できるようになりました。これにより、copy
されたテンプレートが、どのcommon
構造体を共有すべきかを明示的に指定できるようになります。
- 修正前:
-
Template.Clone()
メソッドの変更:nt := t.copy()
をnt := t.copy(nil)
に変更: まず、新しいルートテンプレートnt
を作成しますが、この時点ではcommon
はnil
に設定されます。これは後でnt.init()
で適切に初期化されます。nt.tmpl[t.name] = nt
の追加:nt.init()
の直後に、クローンされたルートテンプレートnt
自身を、その名前 (t.name
) でnt.tmpl
マップに明示的に登録します。これにより、クローンされたテンプレートが、自身のルートテンプレートとして自分自身を正しく参照するようになります。- 関連テンプレートのコピーロジックの変更:
if k == t.name { continue }
の追加: ルートテンプレートは既にnt.tmpl[t.name] = nt
で処理されているため、ループ内で再度処理する必要はありません。tmpl := v.copy()
をtmpl := v.copy(nt.common)
に変更: 各関連テンプレートv
をコピーする際に、新しいnt
のcommon
構造体を引数としてv.copy()
に渡します。これにより、コピーされた関連テンプレートtmpl
が、クローンされたルートテンプレートnt
と同じcommon
構造体を共有するようになります。
追加されたテスト
src/pkg/text/template/multi_test.go
に TestClone
関数内で、クローンされたテンプレートの自己整合性を検証する新しいテストが追加されました。
// Verify that the clone is self-consistent.
for k, v := range clone.tmpl {
if k == clone.name && v.tmpl[k] != clone {
t.Error("clone does not contain root")
}
if v != v.tmpl[v.name] {
t.Errorf("clone does not contain self for %q", k)
}
}
このテストは、クローンされたテンプレート clone
の tmpl
マップをイテレートし、以下の2つの条件を検証します。
- ルートテンプレートの自己参照:
k == clone.name
(つまり、現在のテンプレートがルートテンプレートである場合) かつv.tmpl[k] != clone
(つまり、ルートテンプレートが自分自身を指していない場合) にエラーを報告します。これは、クローンされたルートテンプレートが、そのtmpl
マップ内で自分自身を正しく参照していることを確認します。 - 関連テンプレートの自己整合性:
v != v.tmpl[v.name]
(つまり、関連テンプレートv
が、そのtmpl
マップ内で自分自身を正しく参照していない場合) にエラーを報告します。これは、クローンされた各関連テンプレートが、そのtmpl
マップ内で自分自身を正しく参照していることを確認します。
これらのテストは、Clone
メソッドがテンプレートの内部構造を正しく複製し、クローンされたテンプレートが完全に独立して機能することを保証するために非常に重要です。
コアとなるコードの変更箇所
src/pkg/text/template/multi_test.go
--- a/src/pkg/text/template/multi_test.go
+++ b/src/pkg/text/template/multi_test.go
@@ -230,6 +230,15 @@ func TestClone(t *testing.T) {
if err != nil {
t.Fatal(err)
}
+ // Verify that the clone is self-consistent.
+ for k, v := range clone.tmpl {
+ if k == clone.name && v.tmpl[k] != clone {
+ t.Error("clone does not contain root")
+ }
+ if v != v.tmpl[v.name] {
+ t.Errorf("clone does not contain self for %q", k)
+ }
+ }
// Execute root.
var b bytes.Buffer
err = root.ExecuteTemplate(&b, "a", 0)
src/pkg/text/template/template.go
--- a/src/pkg/text/template/template.go
+++ b/src/pkg/text/template/template.go
@@ -73,12 +73,15 @@ func (t *Template) init() {
// common templates and use them with variant definitions for other templates by
// adding the variants after the clone is made.
func (t *Template) Clone() *Template {
- nt := t.copy()
+ nt := t.copy(nil)
nt.init()
+ nt.tmpl[t.name] = nt
for k, v := range t.tmpl {
+ if k == t.name { // Already installed.
+ continue
+ }
// The associated templates share nt's common structure.
- tmpl := v.copy()
- tmpl.common = nt.common
+ tmpl := v.copy(nt.common)
nt.tmpl[k] = tmpl
}
for k, v := range t.parseFuncs {
@@ -90,9 +93,10 @@ func (t *Template) Clone() *Template {
return nt
}
-// copy returns a shallow copy of t, with common set to nil.
-func (t *Template) copy() *Template {
+// copy returns a shallow copy of t, with common set to the argument.
+func (t *Template) copy(c *common) *Template {
nt := New(t.name)
nt.Tree = t.Tree
+ nt.common = c
nt.leftDelim = t.leftDelim
nt.rightDelim = t.rightDelim
return nt
コアとなるコードの解説
src/pkg/text/template/template.go
の変更点
-
Template.Clone()
メソッド:nt := t.copy()
からnt := t.copy(nil)
への変更:- これは、新しいテンプレート
nt
を作成する際に、初期状態ではcommon
フィールドをnil
に設定することを意味します。common
フィールドは、nt.init()
の呼び出しによって適切に初期化されます。
- これは、新しいテンプレート
nt.tmpl[t.name] = nt
の追加:nt.init()
の直後にこの行が追加されました。これは非常に重要です。クローンされたテンプレートnt
が、そのtmpl
マップ内で、自身の名前 (t.name
) に対応するエントリとして、自分自身 (nt
) を明示的に登録します。これにより、クローンされたテンプレートが、その内部でルートテンプレートを参照する際に、正しくクローンされた自分自身を参照するようになります。修正前は、この自己参照が正しく設定されていなかったため、元のテンプレートを参照してしまう可能性がありました。
- 関連テンプレートのループ内の変更:
if k == t.name { continue }
の追加: ループの最初にこのチェックが追加されました。これは、ルートテンプレート (k == t.name
) は既にnt.tmpl[t.name] = nt
で処理されているため、このループ内で再度処理する必要がないことを意味します。これにより、冗長な処理を避け、ロジックを明確にしています。tmpl := v.copy()
からtmpl := v.copy(nt.common)
への変更:- これがバグ修正の主要な部分です。元のテンプレート
t
に関連付けられている各名前付きテンプレートv
をコピーする際に、新しいnt
のcommon
構造体 (nt.common
) をv.copy()
メソッドに引数として渡しています。 - これにより、コピーされた関連テンプレート
tmpl
は、クローンされたルートテンプレートnt
と同じcommon
構造体を共有するようになります。これは、クローンされたテンプレートセット全体が、単一の独立した共通データセットを持つことを保証するために不可欠です。修正前は、関連テンプレートが新しいcommon
を正しく参照していなかったため、問題が発生していました。
- これがバグ修正の主要な部分です。元のテンプレート
-
Template.copy()
メソッド:func (t *Template) copy() *Template
からfunc (t *Template) copy(c *common) *Template
への変更:- このメソッドは、テンプレートの浅いコピーを作成するヘルパー関数です。引数
c *common
が追加されたことで、この関数が新しいテンプレートインスタンスを作成する際に、そのcommon
フィールドを引数c
の値に設定できるようになりました。
- このメソッドは、テンプレートの浅いコピーを作成するヘルパー関数です。引数
nt.common = c
の追加:- 新しいテンプレート
nt
のcommon
フィールドを、引数として渡されたc
に設定します。これにより、Clone
メソッドからcopy
を呼び出す際に、どのcommon
構造体を共有すべきかを明示的に指定できるようになり、テンプレート間のcommon
構造体の参照関係が正しく確立されます。
- 新しいテンプレート
src/pkg/text/template/multi_test.go
の変更点
TestClone
関数内の新しいテストブロック:- このテストは、
Clone
メソッドが正しく機能していることを検証するためのものです。 for k, v := range clone.tmpl
ループを使用して、クローンされたテンプレートclone
に含まれるすべての名前付きテンプレートをイテレートします。if k == clone.name && v.tmpl[k] != clone
のチェックは、クローンされたルートテンプレートが、そのtmpl
マップ内で自分自身を正しく参照していることを確認します。もし参照が間違っていれば、"clone does not contain root"
というエラーが報告されます。if v != v.tmpl[v.name]
のチェックは、クローンされた各関連テンプレートv
が、そのtmpl
マップ内で自分自身を正しく参照していることを確認します。もし参照が間違っていれば、"clone does not contain self for %q"
というエラーが報告されます。- これらのテストは、
Clone
メソッドがテンプレートの内部構造(特にtmpl
マップ内の参照)を正しく複製し、クローンされたテンプレートが完全に自己完結的で、元のテンプレートから独立して機能することを保証するために不可欠です。
- このテストは、
これらの変更により、text/template
パッケージの Clone
メソッドは、テンプレートのディープコピーをより正確に実行できるようになり、クローンされたテンプレートが期待通りに動作することが保証されます。
関連リンク
- Go CL 5436063: https://golang.org/cl/5436063
参考にした情報源リンク
- Go text/template package documentation (GoDoc): https://pkg.go.dev/text/template
- Go text/template package source code (GitHub): https://github.com/golang/go/tree/master/src/text/template
- Go text/template: Understanding the Clone method (Stack Overflow / Blog posts, if found during search) - Note: Specific links would be added here if a relevant and helpful resource was found during the web search for "Go text/template Clone bug" or "Go text/template package explanation".```markdown
[インデックス 10501] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージにおける Clone
メソッドのバグを修正するものです。具体的には、テンプレートをクローン(複製)する際に、ルートテンプレートのコピーが正しく行われない問題を解決し、その自己整合性を検証するためのテストが追加されています。
コミット
- コミットハッシュ:
0197cc49ae3bfabc0edbeb0ae7534036d130dd71
- 作者: Rob Pike r@golang.org
- 日付: Thu Nov 24 16:07:19 2011 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0197cc49ae3bfabc0edbeb0ae7534036d130dd71
元コミット内容
text/template: fix bug in Clone
Cloned template copied the root template incorrectly.
Add test of self-consistency.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5436063
変更の背景
text/template
パッケージの Template
型には、既存のテンプレートを複製するための Clone
メソッドが提供されています。このメソッドは、元のテンプレートの構造や定義を保持しつつ、新しい独立したテンプレートインスタンスを作成するために使用されます。しかし、以前の実装では、クローンされたテンプレートが元のテンプレートの「ルート」を正しくコピーしていませんでした。
テンプレートシステムにおいて、複数の名前付きテンプレートが関連付けられている場合、それらは通常、単一の「ルート」テンプレートに属しています。Clone
メソッドの目的は、このルートテンプレートとその関連テンプレートの完全なコピーを作成することです。バグのある実装では、クローンされたテンプレートが、その内部で参照するルートテンプレートが、クローンされた自分自身ではなく、元のテンプレートを参照してしまう可能性がありました。これにより、クローンされたテンプレートが期待通りに動作しない、あるいは予期せぬ副作用を引き起こす可能性がありました。
この問題を解決し、クローンされたテンプレートが完全に自己完結的で、元のテンプレートから独立して機能するようにするために、このコミットが作成されました。また、このようなバグが将来的に再発しないよう、クローンされたテンプレートの自己整合性を検証する新しいテストケースも追加されています。
前提知識の解説
Go言語の text/template
パッケージ
text/template
パッケージは、Go言語でテキストベースの出力を生成するためのデータ駆動型テンプレートエンジンを提供します。これは、HTML、XML、プレーンテキストなどの動的なコンテンツを生成するのに非常に役立ちます。
Template
型: テンプレートの定義と実行をカプセル化する主要な型です。- 名前付きテンプレート:
text/template
は、複数の名前付きテンプレートを管理できます。例えば、{{template "header"}}
のように、別のテンプレートを呼び出すことができます。 - ルートテンプレートと関連テンプレート: 複数の名前付きテンプレートが定義されている場合、それらは通常、単一の
Template
インスタンス(ルートテンプレート)に属しています。他の名前付きテンプレートは、このルートテンプレートに関連付けられた「関連テンプレート」として扱われます。 Parse
メソッド: テンプレート文字列を解析し、Template
オブジェクトを構築します。Execute
/ExecuteTemplate
メソッド: テンプレートを実行し、指定されたデータ構造を適用して出力を生成します。Clone
メソッド: 既存のTemplate
インスタンスのディープコピーを作成します。これにより、元のテンプレートを変更せずに、その構造を再利用して新しいテンプレートを作成できます。これは、例えば、共通のテンプレート定義をベースに、特定の用途に合わせて一部をカスタマイズしたテンプレートを作成する場合などに有用です。
テンプレートの内部構造 (tmpl
マップと common
構造体)
text/template
パッケージの Template
型の内部には、以下のような重要なフィールドが存在します(コミット当時の実装に基づく推測):
tmpl
(map[string]*Template): このマップは、現在のTemplate
インスタンスに関連付けられているすべての名前付きテンプレートを保持します。キーはテンプレート名、値は対応するTemplate
オブジェクトです。このマップには、自分自身(ルートテンプレート)も含まれます。common
(*common): これは、複数のテンプレートインスタンス間で共有される共通のデータや設定を保持するための内部構造へのポインタです。例えば、デリミタの設定や、テンプレートの解析ツリーなどが含まれる可能性があります。Clone
操作では、このcommon
構造体を適切に処理することが重要です。
Clone
メソッドの役割
Clone
メソッドは、既存の Template
インスタンスから新しい Template
インスタンスを作成します。この新しいインスタンスは、元のインスタンスと同じテンプレート定義、名前付きテンプレートのセット、および設定(デリミタなど)を持ちますが、元のインスタンスとは独立しています。これにより、新しいインスタンスに対して Parse
や AddParseTree
などの操作を行っても、元のインスタンスには影響を与えません。
この独立性を確保するためには、Clone
メソッドが、ルートテンプレートだけでなく、関連するすべての名前付きテンプレート、そしてそれらが参照する内部的な共通データ構造(common
)も正しく複製し、新しいクローンされたインスタンスに紐付ける必要があります。
技術的詳細
このコミットの技術的な核心は、Template.Clone()
メソッドと、その内部で利用される Template.copy()
メソッドの修正にあります。
修正前の問題点
修正前の Template.Clone()
メソッドは、以下のような問題がありました。
- ルートテンプレートの不適切なコピー:
Clone
メソッドは、まずt.copy()
を呼び出して新しいTemplate
インスタンスnt
を作成していました。しかし、このcopy
メソッドはcommon
フィールドをnil
に設定していました。その後、nt.init()
が呼び出されますが、この時点ではnt
のtmpl
マップにはまだルートテンプレートが正しく設定されていませんでした。 - 関連テンプレートの
common
フィールドの不整合:Clone
メソッドは、元のテンプレートt
のtmpl
マップをイテレートし、各関連テンプレートv
に対してv.copy()
を呼び出して新しいテンプレートtmpl
を作成していました。そして、tmpl.common = nt.common
と設定していました。しかし、nt.common
はnt.init()
の呼び出しによって初期化された新しいcommon
構造体であるべきですが、ルートテンプレートのtmpl
マップへの登録が不完全なため、この関連付けが正しく機能しない可能性がありました。特に、v.copy()
がcommon
をnil
に設定してしまうため、関連テンプレートが新しいクローンされたルートテンプレートのcommon
を参照するように明示的に設定する必要がありました。
結果として、クローンされた Template
インスタンス nt
の tmpl
マップ内のルートテンプレート(nt.tmpl[t.name]
)が、クローンされた nt
自身ではなく、元の t
を参照してしまう、あるいは common
構造体の参照が正しくないという問題が発生していました。これにより、クローンされたテンプレートが自己完結的でなく、元のテンプレートに依存してしまう状態になっていました。
修正内容
このコミットでは、以下の変更によって上記のバグが修正されました。
-
Template.copy()
メソッドの変更:- 修正前:
func (t *Template) copy() *Template
(引数なし) - 修正後:
func (t *Template) copy(c *common) *Template
(引数c *common
を追加) - この変更により、
copy
メソッドは、新しいTemplate
インスタンスを作成する際に、引数として渡されたcommon
構造体を直接nt.common
に設定できるようになりました。これにより、copy
されたテンプレートが、どのcommon
構造体を共有すべきかを明示的に指定できるようになります。
- 修正前:
-
Template.Clone()
メソッドの変更:nt := t.copy()
をnt := t.copy(nil)
に変更: まず、新しいルートテンプレートnt
を作成しますが、この時点ではcommon
はnil
に設定されます。これは後でnt.init()
で適切に初期化されます。nt.tmpl[t.name] = nt
の追加:nt.init()
の直後に、クローンされたルートテンプレートnt
自身を、その名前 (t.name
) でnt.tmpl
マップに明示的に登録します。これにより、クローンされたテンプレートが、自身のルートテンプレートとして自分自身を正しく参照するようになります。- 関連テンプレートのコピーロジックの変更:
if k == t.name { continue }
の追加: ルートテンプレートは既にnt.tmpl[t.name] = nt
で処理されているため、ループ内で再度処理する必要はありません。tmpl := v.copy()
からtmpl := v.copy(nt.common)
に変更: 各関連テンプレートv
をコピーする際に、新しいnt
のcommon
構造体を引数としてv.copy()
に渡します。これにより、コピーされた関連テンプレートtmpl
が、クローンされたルートテンプレートnt
と同じcommon
構造体を共有するようになります。
追加されたテスト
src/pkg/text/template/multi_test.go
に TestClone
関数内で、クローンされたテンプレートの自己整合性を検証する新しいテストが追加されました。
// Verify that the clone is self-consistent.
for k, v := range clone.tmpl {
if k == clone.name && v.tmpl[k] != clone {
t.Error("clone does not contain root")
}
if v != v.tmpl[v.name] {
t.Errorf("clone does not contain self for %q", k)
}
}
このテストは、クローンされたテンプレート clone
の tmpl
マップをイテレートし、以下の2つの条件を検証します。
- ルートテンプレートの自己参照:
k == clone.name
(つまり、現在のテンプレートがルートテンプレートである場合) かつv.tmpl[k] != clone
(つまり、ルートテンプレートが自分自身を指していない場合) にエラーを報告します。これは、クローンされたルートテンプレートが、そのtmpl
マップ内で自分自身を正しく参照していることを確認します。 - 関連テンプレートの自己整合性:
v != v.tmpl[v.name]
(つまり、関連テンプレートv
が、そのtmpl
マップ内で自分自身を正しく参照していない場合) にエラーを報告します。これは、クローンされた各関連テンプレートが、そのtmpl
マップ内で自分自身を正しく参照していることを確認します。
これらのテストは、Clone
メソッドがテンプレートの内部構造を正しく複製し、クローンされたテンプレートが完全に独立して機能することを保証するために非常に重要です。
コアとなるコードの変更箇所
src/pkg/text/template/multi_test.go
--- a/src/pkg/text/template/multi_test.go
+++ b/src/pkg/text/template/multi_test.go
@@ -230,6 +230,15 @@ func TestClone(t *testing.T) {
if err != nil {
t.Fatal(err)
}
+ // Verify that the clone is self-consistent.
+ for k, v := range clone.tmpl {
+ if k == clone.name && v.tmpl[k] != clone {
+ t.Error("clone does not contain root")
+ }
+ if v != v.tmpl[v.name] {
+ t.Errorf("clone does not contain self for %q", k)
+ }
+ }
// Execute root.
var b bytes.Buffer
err = root.ExecuteTemplate(&b, "a", 0)
src/pkg/text/template/template.go
--- a/src/pkg/text/template/template.go
+++ b/src/pkg/text/template/template.go
@@ -73,12 +73,15 @@ func (t *Template) init() {
// common templates and use them with variant definitions for other templates by
// adding the variants after the clone is made.
func (t *Template) Clone() *Template {
- nt := t.copy()
+ nt := t.copy(nil)
nt.init()
+ nt.tmpl[t.name] = nt
for k, v := range t.tmpl {
+ if k == t.name { // Already installed.
+ continue
+ }
// The associated templates share nt's common structure.
- tmpl := v.copy()
- tmpl.common = nt.common
+ tmpl := v.copy(nt.common)
nt.tmpl[k] = tmpl
}
for k, v := range t.parseFuncs {
@@ -90,9 +93,10 @@ func (t *Template) Clone() *Template {
return nt
}
-// copy returns a shallow copy of t, with common set to nil.
-func (t *Template) copy() *Template {
+// copy returns a shallow copy of t, with common set to the argument.
+func (t *Template) copy(c *common) *Template {
nt := New(t.name)
nt.Tree = t.Tree
+ nt.common = c
nt.leftDelim = t.leftDelim
nt.rightDelim = t.rightDelim
return nt
コアとなるコードの解説
src/pkg/text/template/template.go
の変更点
-
Template.Clone()
メソッド:nt := t.copy()
からnt := t.copy(nil)
への変更:- これは、新しいテンプレート
nt
を作成する際に、初期状態ではcommon
フィールドをnil
に設定することを意味します。common
フィールドは、nt.init()
の呼び出しによって適切に初期化されます。
- これは、新しいテンプレート
nt.tmpl[t.name] = nt
の追加:nt.init()
の直後にこの行が追加されました。これは非常に重要です。クローンされたテンプレートnt
が、そのtmpl
マップ内で、自身の名前 (t.name
) に対応するエントリとして、自分自身 (nt
) を明示的に登録します。これにより、クローンされたテンプレートが、その内部でルートテンプレートを参照する際に、正しくクローンされた自分自身を参照するようになります。修正前は、この自己参照が正しく設定されていなかったため、元のテンプレートを参照してしまう可能性がありました。
- 関連テンプレートのループ内の変更:
if k == t.name { continue }
の追加: ループの最初にこのチェックが追加されました。これは、ルートテンプレート (k == t.name
) は既にnt.tmpl[t.name] = nt
で処理されているため、このループ内で再度処理する必要がないことを意味します。これにより、冗長な処理を避け、ロジックを明確にしています。tmpl := v.copy()
からtmpl := v.copy(nt.common)
へ変更:- これがバグ修正の主要な部分です。元のテンプレート
t
に関連付けられている各名前付きテンプレートv
をコピーする際に、新しいnt
のcommon
構造体 (nt.common
) をv.copy()
メソッドに引数として渡しています。 - これにより、コピーされた関連テンプレート
tmpl
は、クローンされたルートテンプレートnt
と同じcommon
構造体を共有するようになります。これは、クローンされたテンプレートセット全体が、単一の独立した共通データセットを持つことを保証するために不可欠です。修正前は、関連テンプレートが新しいcommon
を正しく参照していなかったため、問題が発生していました。
- これがバグ修正の主要な部分です。元のテンプレート
-
Template.copy()
メソッド:func (t *Template) copy() *Template
からfunc (t *Template) copy(c *common) *Template
へ変更:- このメソッドは、テンプレートの浅いコピーを作成するヘルパー関数です。引数
c *common
が追加されたことで、この関数が新しいテンプレートインスタンスを作成する際に、そのcommon
フィールドを引数c
の値に設定できるようになりました。
- このメソッドは、テンプレートの浅いコピーを作成するヘルパー関数です。引数
nt.common = c
の追加:- 新しいテンプレート
nt
のcommon
フィールドを、引数として渡されたc
に設定します。これにより、Clone
メソッドからcopy
を呼び出す際に、どのcommon
構造体を共有すべきかを明示的に指定できるようになり、テンプレート間のcommon
構造体の参照関係が正しく確立されます。
- 新しいテンプレート
src/pkg/text/template/multi_test.go
の変更点
TestClone
関数内の新しいテストブロック:- このテストは、
Clone
メソッドが正しく機能していることを検証するためのものです。 for k, v := range clone.tmpl
ループを使用して、クローンされたテンプレートclone
に含まれるすべての名前付きテンプレートをイテレートします。if k == clone.name && v.tmpl[k] != clone
のチェックは、クローンされたルートテンプレートが、そのtmpl
マップ内で自分自身を正しく参照していることを確認します。もし参照が間違っていれば、"clone does not contain root"
というエラーが報告されます。if v != v.tmpl[v.name]
のチェックは、クローンされた各関連テンプレートv
が、そのtmpl
マップ内で自分自身を正しく参照していることを確認します。もし参照が間違っていれば、"clone does not contain self for %q"
というエラーが報告されます。- これらのテストは、
Clone
メソッドがテンプレートの内部構造(特にtmpl
マップ内の参照)を正しく複製し、クローンされたテンプレートが完全に自己完結的で、元のテンプレートから独立して機能することを保証するために不可欠です。
- このテストは、
これらの変更により、text/template
パッケージの Clone
メソッドは、テンプレートのディープコピーをより正確に実行できるようになり、クローンされたテンプレートが期待通りに動作することが保証されます。
関連リンク
- Go CL 5436063: https://golang.org/cl/5436063
参考にした情報源リンク
- Go text/template package documentation (GoDoc): https://pkg.go.dev/text/template
- Go text/template package source code (GitHub): https://github.com/golang/go/tree/master/src/text/template