[インデックス 19421] ファイルの概要
このコミットは、Go言語のreflect
パッケージにおいて、nil
マップからの要素削除操作がパニックを引き起こさないように修正するものです。具体的には、reflect.Value.SetMapIndex
を通じてマップ要素を削除しようとした際に、対象のマップがnil
であった場合にランタイムパニックが発生する問題を解決します。この変更により、nil
マップに対するdelete
操作がGo言語の組み込み関数delete
と同様に、何もしない(no-op)で正常に終了するようになります。
コミット
commit cb6cb42ede03d6a35fbe6603f22e8855910f9f51
Author: Keith Randall <khr@golang.org>
Date: Tue May 20 16:26:04 2014 -0700
reflect: don't panic on delete from nil map.
Fixes #8051
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/95560046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cb6cb42ede03d6a35fbe6603f22e8855910f9f51
元コミット内容
reflect: don't panic on delete from nil map.
このコミットは、nil
マップからの要素削除時にパニックを発生させないようにするものです。
バグ #8051 を修正します。
変更の背景
Go言語において、組み込みのdelete
関数を使ってnil
マップから要素を削除しようとした場合、それはパニックを起こさずに何もしない(no-op)という挙動が仕様として定められています。これは、マップがまだ初期化されていない場合でも安全にdelete
操作を行えるようにするための設計判断です。
しかし、reflect
パッケージを介してマップ操作を行う場合、特にreflect.Value.SetMapIndex
メソッドを使用して要素を削除しようとした際に、対象のマップがnil
であるとランタイムパニックが発生するという不整合がありました。この挙動は、組み込みのdelete
関数との一貫性を欠き、開発者がreflect
パッケージを使用する際に予期せぬパニックに遭遇する可能性がありました。
このコミットは、この不整合を解消し、reflect
パッケージを介したnil
マップからの削除操作も、組み込みのdelete
関数と同様にパニックを起こさず、何もしないように変更することを目的としています。これにより、Go言語のマップ操作全体における挙動の一貫性が保たれ、より堅牢なコードの記述が可能になります。コミットメッセージにある Fixes #8051
は、この問題がGoのIssueトラッカーで報告されていたことを示しています。
前提知識の解説
Go言語のマップ (map)
Go言語のマップは、キーと値のペアを格納するハッシュテーブルの実装です。マップは参照型であり、make
関数で初期化するか、マップリテラルを使用して作成します。
// マップの宣言と初期化
var m map[string]int // nilマップ
m2 := make(map[string]int) // 空のマップ
m3 := map[string]int{"a": 1, "b": 2} // 初期値を持つマップ
nilマップ
マップを宣言しただけでmake
関数やマップリテラルで初期化しなかった場合、そのマップはnil
値になります。nil
マップは、キーと値のペアを保持するためのメモリが割り当てられていない状態です。
nil
マップに対する操作には以下の特性があります。
- 読み取り:
nil
マップから要素を読み取ろうとすると、その型のゼロ値が返されます。パニックは発生しません。 - 書き込み:
nil
マップに要素を書き込もうとすると、ランタイムパニックが発生します。これは、メモリが割り当てられていないためです。 - 削除 (
delete
組み込み関数):delete(nilMap, key)
のようにnil
マップから要素を削除しようとすると、パニックは発生せず、何もしません(no-op)。これがGoの仕様です。
delete
組み込み関数
delete(m, key)
は、マップm
から指定されたkey
に対応する要素を削除するためのGoの組み込み関数です。マップにkey
が存在しない場合や、マップがnil
である場合でも、パニックは発生しません。
reflect
パッケージ
reflect
パッケージは、Goプログラムが実行時に自身の構造を検査し、変更することを可能にする機能(リフレクション)を提供します。これにより、型情報や値の操作を動的に行うことができます。
reflect.Value
: Goのあらゆる値のランタイム表現です。reflect.ValueOf(i interface{})
関数を使って、任意のGoの値をreflect.Value
に変換できます。reflect.Value.SetMapIndex(key, elem Value)
: このメソッドは、マップのreflect.Value
に対して、指定されたkey
とelem
のペアを設定します。マップから要素を削除する際には、elem
にその要素型のゼロ値(reflect.Value{}
)を渡します。
このコミットの文脈では、reflect
パッケージを介してnil
マップに対するdelete
操作が行われた際に、組み込みのdelete
関数との挙動の不整合があったことが問題でした。
技術的詳細
このコミットの技術的な核心は、Goランタイムのマップ操作を司る内部関数reflect·mapdelete
の挙動変更にあります。
Goのreflect
パッケージは、内部的にはランタイムのCGO(GoとCの混合コード)関数を呼び出して、マップのような組み込み型の操作を行います。reflect·mapdelete
は、reflect.Value.SetMapIndex
がマップからの要素削除のために最終的に呼び出すランタイム関数の一つです。
変更前は、reflect·mapdelete
関数内で、引数として渡されたマップのハッシュマップ構造体ポインタh
がnil
である場合、runtime·panicstring("delete from nil map")
を呼び出してパニックを発生させていました。これは、reflect
パッケージを介した操作が、組み込みのdelete
関数とは異なる挙動を示す原因となっていました。
このコミットでは、このif(h == nil)
の条件分岐において、パニックを発生させる代わりにreturn;
(関数から即座に抜ける)ように変更されました。これにより、nil
マップに対する削除操作は、パニックを起こさずに何もしない(no-op)で終了するようになります。これは、Go言語の組み込みdelete
関数のセマンティクスと完全に一致する挙動です。
また、この変更の正しさを検証するために、src/pkg/reflect/all_test.go
に新しいテストケースが追加されました。このテストケースは、reflect.Value.SetMapIndex
を使用してnil
マップから要素を削除しようとしたときに、パニックが発生しないことを確認します。
コアとなるコードの変更箇所
src/pkg/reflect/all_test.go
--- a/src/pkg/reflect/all_test.go
+++ b/src/pkg/reflect/all_test.go
@@ -993,6 +993,9 @@ func TestNilMap(t *testing.T) {
if x.Kind() != Invalid {
t.Errorf("mbig.MapIndex(\"hello\") for nil map = %v, want Invalid Value", x)
}
+
+ // Test that deletes from a nil map succeed.
+ mv.SetMapIndex(ValueOf("hi"), Value{})
}
func TestChan(t *testing.T) {
src/pkg/runtime/hashmap.goc
--- a/src/pkg/runtime/hashmap.goc
+++ b/src/pkg/runtime/hashmap.goc
@@ -990,7 +990,7 @@ func reflect·mapassign(t *MapType, h *Hmap, key *byte, val *byte) {\n #pragma textflag NOSPLIT\n func reflect·mapdelete(t *MapType, h *Hmap, key *byte) {\n if(h == nil)\n-\t\truntime·panicstring(\"delete from nil map\");
+\t\treturn; // see bug 8051
if(raceenabled) {
runtime·racewritepc(h, runtime·getcallerpc(&t), reflect·mapdelete);
runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), reflect·mapdelete);
コアとなるコードの解説
src/pkg/runtime/hashmap.goc
の変更
このファイルはGoランタイムのハッシュマップ(マップ)の実装に関連するCGOコードを含んでいます。
変更されたのはreflect·mapdelete
関数です。この関数は、reflect
パッケージがマップから要素を削除する際に内部的に呼び出すランタイム関数です。
func reflect·mapdelete(t *MapType, h *Hmap, key *byte) {
if(h == nil)
- runtime·panicstring("delete from nil map");
+ return; // see bug 8051
if(raceenabled) {
runtime·racewritepc(h, runtime·getcallerpc(&t), reflect·mapdelete);
runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), reflect·mapdelete);
if(h == nil)
: ここで、削除対象のマップを表すハッシュマップ構造体ポインタh
がnil
であるかどうかをチェックしています。h
がnil
であるということは、Goのnil
マップに対応します。- 変更前:
runtime·panicstring("delete from nil map");
:h
がnil
の場合、runtime·panicstring
関数を呼び出して「delete from nil map」というメッセージと共にランタイムパニックを発生させていました。 - 変更後:
return; // see bug 8051
:h
がnil
の場合、パニックを発生させる代わりに、関数から即座にreturn
(戻る)するように変更されました。これにより、nil
マップからの削除操作は何も行われず、エラーも発生しません。コメントにある「see bug 8051」は、この変更がIssue #8051の修正であることを示しています。
この変更により、reflect
パッケージを介したマップ削除操作が、Goの組み込みdelete
関数と同じセマンティクスを持つようになり、nil
マップに対する削除が安全に行えるようになりました。
src/pkg/reflect/all_test.go
の変更
このファイルはreflect
パッケージのテストコードを含んでいます。
TestNilMap
関数に新しいテストケースが追加されました。
// Test that deletes from a nil map succeed.
mv.SetMapIndex(ValueOf("hi"), Value{})
mv
: このテスト関数内で定義されているreflect.Value
型の変数で、nil
マップを表しています。mv.SetMapIndex(ValueOf("hi"), Value{})
:nil
マップmv
に対して、キー"hi"
と、その型のゼロ値であるValue{}
を渡してSetMapIndex
を呼び出しています。SetMapIndex
に要素型のゼロ値を渡すことは、そのキーに対応する要素をマップから削除する操作に相当します。- テストの意図: この行がパニックを起こさずに正常に実行されることを確認することで、
nil
マップからの削除が期待通りに機能するようになったことを検証しています。もし変更前のコードであれば、この行でパニックが発生し、テストは失敗していました。
このテストケースの追加は、変更が正しく機能し、意図した挙動(パニックしないこと)が保証されることを確認するための重要なステップです。
関連リンク
- Go CL (Code Review): https://golang.org/cl/95560046
- Go Issue #8051: (直接的なリンクは見つかりませんでしたが、コミットメッセージで参照されています)
参考にした情報源リンク
- Go言語の
delete
組み込み関数の挙動に関する議論:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH_EHrOTscNvSzSQc30FnYU8XBoRrDa0KIXofmhxSEQnF2XIHAIr8QqjWpaQrGpZal-6tNHu56mcyDIbrUU8rwXsz-NgMY1NoGIUzg5scIxIXMB26ErMw7bo8NSdQg26QMF9Vk=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFBsusC8nc4TKjzpvuwLBYXdLtUb6ndJyyBqGpwNpTLGTq1K946ZUclYlZ93_QdaSNhCQyzNqkbvjHTIZXjFdmdqK28QnBnYmf3IT8GGzNeBgPQ6GUWRs-tyFDKihb9eat_Ox4P7MMg70pWp2fd-HeQuuSe0LRwexSouR3LfL6Y
reflect.Value.SetMapIndex
によるマップ要素削除の挙動に関する情報:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHNPwabH826n9TAlBvCx5dPEG4iaz9g9q3dNILsQO_YUmHPmlU_vb1zUIXoAVNyKZPq6P2PN_nzhZ9ixBuI2nHurh14q9JW3T8fqet2HwkpVgdouNm3ARlPIPG92uCaucCE65OuEOIxOQKoh0Lr2V8xuoCLOi7WgnIFJrQ23sMSBKir13gf3UUcm4oPP7q9ZQlNvBnjyH5FzjpEV1fppaeLHJr-fnZ7bM=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHH9YJJaxtXcDJulbNhL-T_U-C9B1_1_GlA1Bf-_DD1xLjBe4OE43V2J4CYjO_buv-KsbxGmQ_owyR9hzmrMLiqhagrmE0LMoFtp7OcQTKJcllOP_x8DnzTOvbQciP_5nmE2REs6BA3HPD9rzaeXjAyRox7s-gw7yx6SYSFdVFHuuUucYQR3KLmKB4nhTK3D62C7iAwxEgFhwfwZCAOP7_r_r5GBA==