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

[インデックス 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/templateindex 関数がマップに対して使用され、指定されたキーがマップ内に存在しない場合、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言語の text/template パッケージ公式ドキュメント: https://pkg.go.dev/text/template
  • Go言語の reflect パッケージ公式ドキュメント: https://pkg.go.dev/reflect
  • Go言語のマップに関するドキュメントやチュートリアル (一般的なGo言語の知識)
  • Go言語のゼロ値に関するドキュメントやチュートリアル (一般的なGo言語の知識)