[インデックス 13494] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template パッケージにおけるマップ(map)のインデックス処理に関するバグ修正です。具体的には、テンプレート内でマップに存在しないキーでアクセスした場合に、誤った型のゼロ値が返される問題を修正しています。また、このバグを検出できなかったテストケースも修正されています。
コミット
Author: Rob Pike r@golang.org Date: Mon Jul 23 16:19:12 2012 -0700
Commit Message:
text/template: fix bug in map indexing
If the key is not present, return value of the type of the element
not the type of the key. Also fix a test that should have caught this case.
Fixes #3850.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/6405078
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ce274339a1ed46356f5322288f4ef878a06f0aab
元コミット内容
text/template: fix bug in map indexing
If the key is not present, return value of the type of the element
not the type of the key. Also fix a test that should have caught this case.
Fixes #3850.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/6405078
変更の背景
text/template パッケージは、Goアプリケーションでテキストベースの出力を生成するためのテンプレートエンジンを提供します。このパッケージでは、データ構造(例えばマップ)にアクセスするために index 関数が提供されています。
このコミットが行われる前、text/template の index 関数がマップに対して使用され、指定されたキーがマップ内に存在しない場合、reflect.Zero(v.Type().Key()) を呼び出してキーの型のゼロ値を返していました。しかし、テンプレートの期待される動作としては、キーが存在しない場合は要素の型のゼロ値を返すのが正しい挙動でした。例えば、map[string]int のようなマップで存在しないキーにアクセスした場合、int 型のゼロ値(0)が返されるべきですが、バグのある実装では string 型のゼロ値(空文字列 "")が返されていました。
この誤った挙動は、テンプレートの出力が期待と異なる結果になる原因となっていました。また、この問題は Issue 3850 として報告されており、このコミットはその問題を修正するために作成されました。さらに、このバグを本来検出するはずだったテストケースも不適切であったため、同時に修正されています。
前提知識の解説
Go言語の text/template パッケージ
text/template パッケージは、Go言語でテキスト出力を生成するためのデータ駆動型テンプレートエンジンです。HTMLテンプレートを扱う html/template パッケージと似ていますが、text/template は任意のテキスト形式の生成に使用されます。テンプレートは、Goのデータ構造(構造体、マップ、スライスなど)をデータソースとして利用し、そのデータに基づいて動的にコンテンツを生成します。
Go言語の map
map はGo言語におけるキーと値のペアのコレクションです。他の言語のハッシュマップや辞書に相当します。マップから存在しないキーを読み取ろうとすると、その値の型のゼロ値が返されます。例えば、map[string]int で存在しないキーにアクセスすると 0 が返されます。
Go言語の reflect パッケージ
reflect パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)することを可能にします。これにより、変数の型、値、メソッドなどを動的に調べたり、操作したりできます。
reflect.Value: Goの任意の型の値を表します。reflect.Type: Goの任意の型の情報を表します。v.Type():reflect.Valueからその値のreflect.Typeを取得します。v.Type().Key(): マップのreflect.Typeに対して呼び出されると、そのマップのキーのreflect.Typeを返します。v.Type().Elem(): マップのreflect.Typeに対して呼び出されると、そのマップの要素(値)のreflect.Typeを返します。reflect.Zero(Type): 指定されたreflect.Typeのゼロ値を表すreflect.Valueを返します。例えば、reflect.Zero(reflect.TypeOf(0))はint型のゼロ値(0)を返します。
技術的詳細
このバグは、text/template パッケージの内部でマップのインデックス処理を行う index 関数(src/pkg/text/template/funcs.go 内)に存在していました。
テンプレート内で {{index .MSI "XXX"}} のようにマップ MSI に対して存在しないキー "XXX" でアクセスした場合、index 関数はマップの reflect.Value を取得し、v.MapIndex(index) を呼び出してキーの存在を確認します。キーが存在しない場合、x.IsValid() は false を返します。
バグのあるコードでは、この else ブロック内で v = reflect.Zero(v.Type().Key()) が実行されていました。これは、マップのキーの型のゼロ値を返すことを意味します。例えば、map[string]int の場合、キーの型は string なので、reflect.Zero(string)、つまり空文字列 "" が返されていました。
しかし、Goのマップのセマンティクスでは、存在しないキーにアクセスした場合、その要素の型のゼロ値が返されます。したがって、map[string]int の場合は int 型のゼロ値である 0 が返されるべきでした。
このコミットでは、この行を v = reflect.Zero(v.Type().Elem()) に変更することで、マップの要素(値)の型のゼロ値を返すように修正しました。これにより、map[string]int の例では、正しく int 型のゼロ値 0 が返されるようになります。
また、src/pkg/text/template/exec_test.go にある関連するテストケースも修正されました。以前のテストでは、"map[NO]" というテストケースで、存在しないキーに対するマップのインデックスが空文字列 "" を返すことを期待していました。これはバグのある挙動を期待していたため、テストがバグを検出できませんでした。修正後は、このテストケースが 0 を返すことを期待するように変更され、正しい挙動を検証できるようになりました。
コアとなるコードの変更箇所
diff --git a/src/pkg/text/template/exec_test.go b/src/pkg/text/template/exec_test.go
index c8a3013977..4efe2d1b38 100644
--- a/src/pkg/text/template/exec_test.go
+++ b/src/pkg/text/template/exec_test.go
@@ -390,7 +390,7 @@ var execTests = []execTest{
{"slice[WRONG]\", \"{{index .SI `hello`}}\", \"\", tVal, false},
{\"map[one]\", \"{{index .MSI `one`}}\", \"1\", tVal, true},
{\"map[two]\", \"{{index .MSI `two`}}\", \"2\", tVal, true},
-\t{\"map[NO]\", \"{{index .MSI `XXX`}}\", \"\", tVal, true},
+\t{\"map[NO]\", \"{{index .MSI `XXX`}}\", \"0\", tVal, true},
{\"map[WRONG]\", \"{{index .MSI 10}}\", \"\", tVal, false},
{\"double index\", \"{{index .SMSI 1 `eleven`}}\", \"11\", tVal, true},
diff --git a/src/pkg/text/template/funcs.go b/src/pkg/text/template/funcs.go
index 90fb9c52c0..e6fa0fb5f2 100644
--- a/src/pkg/text/template/funcs.go
+++ b/src/pkg/text/template/funcs.go
@@ -128,7 +128,7 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
if x := v.MapIndex(index); x.IsValid() {
v = x
} else {
-\t\t\t\tv = reflect.Zero(v.Type().Key())
+\t\t\t\tv = reflect.Zero(v.Type().Elem())
}
default:
return nil, fmt.Errorf(\"can't index item of type %s\", index.Type())
コアとなるコードの解説
src/pkg/text/template/exec_test.go の変更
- 変更前:
{"map[NO]", "{{index .MSIXXX}}", "", tVal, true},- このテストケースは、
MSIというマップに存在しないキーXXXでアクセスした場合に、期待される結果が空文字列""であると定義していました。これは、バグのある実装(キーの型のゼロ値を返す)が空文字列を返していたため、テストがパスしてしまっていました。
- このテストケースは、
- 変更後:
{"map[NO]", "{{index .MSIXXX}}", "0", tVal, true},- テストケースの期待値を
"0"に変更しました。これにより、マップの要素の型が数値型(例:int)である場合に、存在しないキーへのアクセスが正しく0を返すことを検証できるようになりました。
- テストケースの期待値を
src/pkg/text/template/funcs.go の変更
- 変更前:
v = reflect.Zero(v.Type().Key())- マップに存在しないキーでアクセスした場合、
reflect.Zero関数にマップのキーの型(v.Type().Key())を渡していました。これにより、例えばmap[string]intの場合、キーの型であるstringのゼロ値(空文字列"")が返されていました。
- マップに存在しないキーでアクセスした場合、
- 変更後:
v = reflect.Zero(v.Type().Elem())- マップに存在しないキーでアクセスした場合、
reflect.Zero関数にマップの要素(値)の型(v.Type().Elem())を渡すように変更しました。これにより、map[string]intの場合、要素の型であるintのゼロ値(0)が正しく返されるようになります。
- マップに存在しないキーでアクセスした場合、
この変更により、text/template がマップのインデックス処理において、Go言語のマップのセマンティクスに沿った正しいゼロ値を返すようになりました。
関連リンク
- Go Issue 3850: https://github.com/golang/go/issues/3850
- Gerrit Change 6405078: https://golang.org/cl/6405078
参考にした情報源リンク
- Go言語の
text/templateパッケージ公式ドキュメント: https://pkg.go.dev/text/template - Go言語の
reflectパッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語のマップに関するドキュメントやチュートリアル (一般的なGo言語の知識)
- Go言語のゼロ値に関するドキュメントやチュートリアル (一般的なGo言語の知識)