[インデックス 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 .MSI
XXX}}", "", tVal, true},
- このテストケースは、
MSI
というマップに存在しないキーXXX
でアクセスした場合に、期待される結果が空文字列""
であると定義していました。これは、バグのある実装(キーの型のゼロ値を返す)が空文字列を返していたため、テストがパスしてしまっていました。
- このテストケースは、
- 変更後:
{"map[NO]", "{{index .MSI
XXX}}", "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言語の知識)