Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 インスタンスを作成します。この新しいインスタンスは、元のインスタンスと同じテンプレート定義、名前付きテンプレートのセット、および設定(デリミタなど)を持ちますが、元のインスタンスとは独立しています。これにより、新しいインスタンスに対して ParseAddParseTree などの操作を行っても、元のインスタンスには影響を与えません。

この独立性を確保するためには、Clone メソッドが、ルートテンプレートだけでなく、関連するすべての名前付きテンプレート、そしてそれらが参照する内部的な共通データ構造(common)も正しく複製し、新しいクローンされたインスタンスに紐付ける必要があります。

技術的詳細

このコミットの技術的な核心は、Template.Clone() メソッドと、その内部で利用される Template.copy() メソッドの修正にあります。

修正前の問題点

修正前の Template.Clone() メソッドは、以下のような問題がありました。

  1. ルートテンプレートの不適切なコピー: Clone メソッドは、まず t.copy() を呼び出して新しい Template インスタンス nt を作成していました。しかし、この copy メソッドは common フィールドを nil に設定していました。その後、nt.init() が呼び出されますが、この時点では nttmpl マップにはまだルートテンプレートが正しく設定されていませんでした。
  2. 関連テンプレートの common フィールドの不整合: Clone メソッドは、元のテンプレート ttmpl マップをイテレートし、各関連テンプレート v に対して v.copy() を呼び出して新しいテンプレート tmpl を作成していました。そして、tmpl.common = nt.common と設定していました。しかし、nt.commonnt.init() の呼び出しによって初期化された新しい common 構造体であるべきですが、ルートテンプレートの tmpl マップへの登録が不完全なため、この関連付けが正しく機能しない可能性がありました。特に、v.copy()commonnil に設定してしまうため、関連テンプレートが新しいクローンされたルートテンプレートの common を参照するように明示的に設定する必要がありました。

結果として、クローンされた Template インスタンス nttmpl マップ内のルートテンプレート(nt.tmpl[t.name])が、クローンされた nt 自身ではなく、元の t を参照してしまう、あるいは common 構造体の参照が正しくないという問題が発生していました。これにより、クローンされたテンプレートが自己完結的でなく、元のテンプレートに依存してしまう状態になっていました。

修正内容

このコミットでは、以下の変更によって上記のバグが修正されました。

  1. Template.copy() メソッドの変更:

    • 修正前: func (t *Template) copy() *Template (引数なし)
    • 修正後: func (t *Template) copy(c *common) *Template (引数 c *common を追加)
    • この変更により、copy メソッドは、新しい Template インスタンスを作成する際に、引数として渡された common 構造体を直接 nt.common に設定できるようになりました。これにより、copy されたテンプレートが、どの common 構造体を共有すべきかを明示的に指定できるようになります。
  2. Template.Clone() メソッドの変更:

    • nt := t.copy()nt := t.copy(nil) に変更: まず、新しいルートテンプレート nt を作成しますが、この時点では commonnil に設定されます。これは後で 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 をコピーする際に、新しい ntcommon 構造体を引数として v.copy() に渡します。これにより、コピーされた関連テンプレート tmpl が、クローンされたルートテンプレート nt と同じ common 構造体を共有するようになります。

追加されたテスト

src/pkg/text/template/multi_test.goTestClone 関数内で、クローンされたテンプレートの自己整合性を検証する新しいテストが追加されました。

	// 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)
		}
	}

このテストは、クローンされたテンプレート clonetmpl マップをイテレートし、以下の2つの条件を検証します。

  1. ルートテンプレートの自己参照: k == clone.name (つまり、現在のテンプレートがルートテンプレートである場合) かつ v.tmpl[k] != clone (つまり、ルートテンプレートが自分自身を指していない場合) にエラーを報告します。これは、クローンされたルートテンプレートが、その tmpl マップ内で自分自身を正しく参照していることを確認します。
  2. 関連テンプレートの自己整合性: 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 の変更点

  1. 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 をコピーする際に、新しい ntcommon 構造体 (nt.common) を v.copy() メソッドに引数として渡しています。
        • これにより、コピーされた関連テンプレート tmpl は、クローンされたルートテンプレート nt と同じ common 構造体を共有するようになります。これは、クローンされたテンプレートセット全体が、単一の独立した共通データセットを持つことを保証するために不可欠です。修正前は、関連テンプレートが新しい common を正しく参照していなかったため、問題が発生していました。
  2. Template.copy() メソッド:

    • func (t *Template) copy() *Template から func (t *Template) copy(c *common) *Template への変更:
      • このメソッドは、テンプレートの浅いコピーを作成するヘルパー関数です。引数 c *common が追加されたことで、この関数が新しいテンプレートインスタンスを作成する際に、その common フィールドを引数 c の値に設定できるようになりました。
    • nt.common = c の追加:
      • 新しいテンプレート ntcommon フィールドを、引数として渡された 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 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 インスタンスを作成します。この新しいインスタンスは、元のインスタンスと同じテンプレート定義、名前付きテンプレートのセット、および設定(デリミタなど)を持ちますが、元のインスタンスとは独立しています。これにより、新しいインスタンスに対して ParseAddParseTree などの操作を行っても、元のインスタンスには影響を与えません。

この独立性を確保するためには、Clone メソッドが、ルートテンプレートだけでなく、関連するすべての名前付きテンプレート、そしてそれらが参照する内部的な共通データ構造(common)も正しく複製し、新しいクローンされたインスタンスに紐付ける必要があります。

技術的詳細

このコミットの技術的な核心は、Template.Clone() メソッドと、その内部で利用される Template.copy() メソッドの修正にあります。

修正前の問題点

修正前の Template.Clone() メソッドは、以下のような問題がありました。

  1. ルートテンプレートの不適切なコピー: Clone メソッドは、まず t.copy() を呼び出して新しい Template インスタンス nt を作成していました。しかし、この copy メソッドは common フィールドを nil に設定していました。その後、nt.init() が呼び出されますが、この時点では nttmpl マップにはまだルートテンプレートが正しく設定されていませんでした。
  2. 関連テンプレートの common フィールドの不整合: Clone メソッドは、元のテンプレート ttmpl マップをイテレートし、各関連テンプレート v に対して v.copy() を呼び出して新しいテンプレート tmpl を作成していました。そして、tmpl.common = nt.common と設定していました。しかし、nt.commonnt.init() の呼び出しによって初期化された新しい common 構造体であるべきですが、ルートテンプレートの tmpl マップへの登録が不完全なため、この関連付けが正しく機能しない可能性がありました。特に、v.copy()commonnil に設定してしまうため、関連テンプレートが新しいクローンされたルートテンプレートの common を参照するように明示的に設定する必要がありました。

結果として、クローンされた Template インスタンス nttmpl マップ内のルートテンプレート(nt.tmpl[t.name])が、クローンされた nt 自身ではなく、元の t を参照してしまう、あるいは common 構造体の参照が正しくないという問題が発生していました。これにより、クローンされたテンプレートが自己完結的でなく、元のテンプレートに依存してしまう状態になっていました。

修正内容

このコミットでは、以下の変更によって上記のバグが修正されました。

  1. Template.copy() メソッドの変更:

    • 修正前: func (t *Template) copy() *Template (引数なし)
    • 修正後: func (t *Template) copy(c *common) *Template (引数 c *common を追加)
    • この変更により、copy メソッドは、新しい Template インスタンスを作成する際に、引数として渡された common 構造体を直接 nt.common に設定できるようになりました。これにより、copy されたテンプレートが、どの common 構造体を共有すべきかを明示的に指定できるようになります。
  2. Template.Clone() メソッドの変更:

    • nt := t.copy()nt := t.copy(nil) に変更: まず、新しいルートテンプレート nt を作成しますが、この時点では commonnil に設定されます。これは後で 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 をコピーする際に、新しい ntcommon 構造体を引数として v.copy() に渡します。これにより、コピーされた関連テンプレート tmpl が、クローンされたルートテンプレート nt と同じ common 構造体を共有するようになります。

追加されたテスト

src/pkg/text/template/multi_test.goTestClone 関数内で、クローンされたテンプレートの自己整合性を検証する新しいテストが追加されました。

	// 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)
		}
	}

このテストは、クローンされたテンプレート clonetmpl マップをイテレートし、以下の2つの条件を検証します。

  1. ルートテンプレートの自己参照: k == clone.name (つまり、現在のテンプレートがルートテンプレートである場合) かつ v.tmpl[k] != clone (つまり、ルートテンプレートが自分自身を指していない場合) にエラーを報告します。これは、クローンされたルートテンプレートが、その tmpl マップ内で自分自身を正しく参照していることを確認します。
  2. 関連テンプレートの自己整合性: 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 の変更点

  1. 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 をコピーする際に、新しい ntcommon 構造体 (nt.common) を v.copy() メソッドに引数として渡しています。
        • これにより、コピーされた関連テンプレート tmpl は、クローンされたルートテンプレート nt と同じ common 構造体を共有するようになります。これは、クローンされたテンプレートセット全体が、単一の独立した共通データセットを持つことを保証するために不可欠です。修正前は、関連テンプレートが新しい common を正しく参照していなかったため、問題が発生していました。
  2. Template.copy() メソッド:

    • func (t *Template) copy() *Template から func (t *Template) copy(c *common) *Template へ変更:
      • このメソッドは、テンプレートの浅いコピーを作成するヘルパー関数です。引数 c *common が追加されたことで、この関数が新しいテンプレートインスタンスを作成する際に、その common フィールドを引数 c の値に設定できるようになりました。
    • nt.common = c の追加:
      • 新しいテンプレート ntcommon フィールドを、引数として渡された 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 メソッドは、テンプレートのディープコピーをより正確に実行できるようになり、クローンされたテンプレートが期待通りに動作することが保証されます。

関連リンク

参考にした情報源リンク