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

[インデックス 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に対して、指定されたkeyelemのペアを設定します。マップから要素を削除する際には、elemにその要素型のゼロ値(reflect.Value{})を渡します。

このコミットの文脈では、reflectパッケージを介してnilマップに対するdelete操作が行われた際に、組み込みのdelete関数との挙動の不整合があったことが問題でした。

技術的詳細

このコミットの技術的な核心は、Goランタイムのマップ操作を司る内部関数reflect·mapdeleteの挙動変更にあります。

Goのreflectパッケージは、内部的にはランタイムのCGO(GoとCの混合コード)関数を呼び出して、マップのような組み込み型の操作を行います。reflect·mapdeleteは、reflect.Value.SetMapIndexがマップからの要素削除のために最終的に呼び出すランタイム関数の一つです。

変更前は、reflect·mapdelete関数内で、引数として渡されたマップのハッシュマップ構造体ポインタhnilである場合、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): ここで、削除対象のマップを表すハッシュマップ構造体ポインタhnilであるかどうかをチェックしています。hnilであるということは、Goのnilマップに対応します。
  • 変更前: runtime·panicstring("delete from nil map");: hnilの場合、runtime·panicstring関数を呼び出して「delete from nil map」というメッセージと共にランタイムパニックを発生させていました。
  • 変更後: return; // see bug 8051: hnilの場合、パニックを発生させる代わりに、関数から即座に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: (直接的なリンクは見つかりませんでしたが、コミットメッセージで参照されています)

参考にした情報源リンク