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

[インデックス 11277] ファイルの概要

このコミットは、Goコンパイラのガベージコレクタ(gc)が、error型に対して誤ってパッケージパス(pkgpath)を出力する問題を修正するものです。これにより、reflectパッケージのPkgPath()メソッドがerror型に対して常に空文字列を返すように、そのセマンティクスが正しくなります。

コミット

commit ee09a8cd9fee2f38fd100bd27451c4284f7e9d96
Author: David Symonds <dsymonds@golang.org>
Date:   Fri Jan 20 09:26:17 2012 +1100

    gc: don't emit pkgpath for error type.
    
    Fixes #2660.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5557060

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ee09a8cd9fee2f38fd100bd27451c4284f7e9d96

元コミット内容

gc: don't emit pkgpath for error type.

Fixes #2660.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5557060

変更の背景

Go言語のreflectパッケージは、実行時に型情報を検査するための機能を提供します。reflect.TypeインターフェースにはPkgPath()メソッドがあり、これは型のパッケージパス(例: "encoding/base64")を返します。しかし、Goの組み込み型やプリデクレアされた型(int, string, errorなど)は、特定のパッケージに属しているわけではないため、PkgPath()は空文字列を返すことが期待されます。

このコミット以前は、Goコンパイラのガベージコレクタ(gc)が、error型に対して誤ってパッケージパスを出力してしまうバグが存在していました。これにより、reflect.TypeOf(someError).PkgPath()が予期せぬ値を返す可能性があり、reflectパッケージのセマンティクスに一貫性がありませんでした。この問題はGoのIssue #2660として報告されており、このコミットはその修正を目的としています。

特に、errorはインターフェース型であり、そのゼロ値(nil)に対してreflect.TypeOf()を呼び出すとnilreflect.Typeが返されるため、その後にPkgPath()などのメソッドを呼び出すとランタイムパニックを引き起こす可能性があります。このコミットは、error型がPkgPath()に対して空文字列を返すという期待される動作を保証することで、このような潜在的な問題を回避し、reflectパッケージの堅牢性を向上させます。

前提知識の解説

  • Go言語のreflectパッケージ: Goのreflectパッケージは、プログラムの実行時に変数や関数の型情報を動的に検査・操作するための機能を提供します。これにより、ジェネリックなコードや、型に依存しない処理を記述することが可能になります。
  • reflect.Typeインターフェース: reflectパッケージの中心的なインターフェースで、Goの型の情報を表現します。型の名前、サイズ、メソッド、フィールドなどの情報にアクセスできます。
  • PkgPath()メソッド: reflect.Typeインターフェースのメソッドの一つで、その型が定義されているパッケージのインポートパスを文字列で返します。例えば、encoding/base64パッケージの型であれば"encoding/base64"を返します。組み込み型やプリデクレアされた型(int, string, bool, errorなど)の場合、PkgPath()は空文字列を返すことになっています。
  • error: Go言語におけるエラーを表す組み込みインターフェース型です。type error interface { Error() string }と定義されており、エラーを返す関数は通常この型を返します。error型は特定のパッケージに属するものではなく、言語仕様によって定義されたプリデクレアされた型です。
  • Goコンパイラ(gc: Go言語の公式コンパイラです。ソースコードを機械語に変換する過程で、型情報なども処理します。
  • ガベージコレクタ(GC): プログラムが使用しなくなったメモリを自動的に解放する仕組みです。Goのコンパイラの一部として、型情報やオブジェクトのレイアウトに関する情報もGCに渡されます。

技術的詳細

このコミットの核心は、Goコンパイラのgc部分、特に型情報を処理するreflect.cファイルにおける変更です。

以前のコードでは、dextratype関数内で型のシンボル(t->sym)が存在し、かつその型が基本型(t != types[t->etype])でない場合に、その型のパッケージパス(t->sym->pkg)をガベージコレクタに渡していました。しかし、error型は基本型ではないものの、プリデクレアされた型であるため、パッケージパスを持つべきではありません。

このコミットでは、dextratype関数内の条件式に&& t != errortypeという条件が追加されました。これにより、terrortypeerror型を表す内部的な型)である場合には、パッケージパスの出力がスキップされるようになります。

この変更により、reflect.TypeOf(err).PkgPath()が常に空文字列を返すというreflectパッケージの仕様に準拠するようになります。

また、src/pkg/reflect/all_test.goには、PkgPath()の動作を検証するための新しいテストケースが追加されました。このテストでは、base64.Encodingのような特定のパッケージに属する型、uintmapのような組み込み型、そしてerror型に対してPkgPath()が期待通りの値を返すか(空文字列を含む)を確認しています。

さらに、src/pkg/reflect/type.goPkgPath()メソッドのドキュメントが更新され、「unnamed types」に加えて「predeclared types」も空文字列を返すことが明記されました。これは、この変更によってerror型のようなプリデクレアされた型が正式にPkgPath()で空文字列を返すようになったことを反映しています。

コアとなるコードの変更箇所

このコミットによる主要なコード変更は以下の3つのファイルにわたります。

  1. src/cmd/gc/reflect.c:

    • dextratype関数内の条件式が変更されました。
    • 変更前: if(t != types[t->etype])
    • 変更後: if(t != types[t->etype] && t != errortype)
  2. src/pkg/reflect/all_test.go:

    • TestImportPath関数が大幅に拡張されました。
    • base64.Encodingのテストに加え、uint(0)map[string]int{}(*error)(nil).Elem()といった様々な型に対するPkgPath()のテストケースが追加されました。
  3. src/pkg/reflect/type.go:

    • PkgPath()メソッドのコメントが更新されました。
    • 変更前: // PkgPath returns an empty string for unnamed types.
    • 変更後: // PkgPath returns an empty string for unnamed or predeclared types.

コアとなるコードの解説

src/cmd/gc/reflect.c の変更

// 変更前
// 	if(t != types[t->etype])
// 		ot = dgopkgpath(s, ot, t->sym->pkg);

// 変更後
 	if(t != types[t->etype] && t != errortype)
 		ot = dgopkgpath(s, ot, t->sym->pkg);

この変更は、Goコンパイラの内部で型情報を処理する部分にあります。dextratype関数は、Goの型システムがガベージコレクタやランタイムに型情報を渡す際に使用される可能性があります。 t != types[t->etype]という条件は、型tがその基本型(t->etypeで示される)と異なる場合に真となります。これは、例えばtype MyInt intのようなエイリアス型や構造体型などが該当します。 しかし、error型はプリデクレアされたインターフェース型であり、types[t->etype]とは異なるものの、パッケージパスを持つべきではありません。 そこで、&& t != errortypeという条件を追加することで、明示的にerror型の場合にはパッケージパスの出力処理(dgopkgpathの呼び出し)をスキップするようにしました。これにより、error型がreflect.PkgPath()で空文字列を返すという期待される動作が保証されます。

src/pkg/reflect/all_test.go の変更

// 変更前
// func TestImportPath(t *testing.T) {
// 	if path := TypeOf(&base64.Encoding{}).Elem().PkgPath(); path != "encoding/base64" {
// 		t.Errorf(`TypeOf(&base64.Encoding{}).Elem().PkgPath() = %q, want "encoding/base64"`, path)
// 	}
// }

// 変更後
func TestImportPath(t *testing.T) {
	tests := []struct {
		t    Type
		path string
	}{
		{TypeOf(&base64.Encoding{}).Elem(), "encoding/base64"},
		{TypeOf(uint(0)), ""},
		{TypeOf(map[string]int{}), ""},
		{TypeOf((*error)(nil)).Elem(), ""},
	}
	for _, test := range tests {
		if path := test.t.PkgPath(); path != test.path {
			t.Errorf("%v.PkgPath() = %q, want %q", test.t, path, test.path)
		}
	}
}

このテストの変更は、PkgPath()メソッドの動作が様々な型で正しく機能することを確認するために重要です。

  • TypeOf(&base64.Encoding{}).Elem(): encoding/base64パッケージに属する構造体型で、期待されるパッケージパスは"encoding/base64"です。
  • TypeOf(uint(0)): 組み込みの数値型(uint)で、パッケージパスは空文字列""が期待されます。
  • TypeOf(map[string]int{}): 組み込みのマップ型で、パッケージパスは空文字列""が期待されます。
  • TypeOf((*error)(nil)).Elem(): errorインターフェース型で、パッケージパスは空文字列""が期待されます。(*error)(nil)nilerrorインターフェース値のポインタ型を生成し、.Elem()でその要素型(つまりerrorインターフェース型自体)を取得します。

これらのテストケースを追加することで、PkgPath()がプリデクレアされた型や組み込み型に対して正しく空文字列を返すことを保証し、gcの変更が意図した通りに機能していることを検証しています。

src/pkg/reflect/type.go の変更

// 変更前
// 	// PkgPath returns an empty string for unnamed types.

// 変更後
// 	// PkgPath returns an empty string for unnamed or predeclared types.

このドキュメントの変更は、PkgPath()のセマンティクスに関する公式な説明を更新するものです。以前は「unnamed types」(匿名型、例えばstruct{}[]intなど)に対して空文字列を返すことが述べられていましたが、この変更により「predeclared types」(プリデクレアされた型、例えばint, string, bool, errorなど)も同様に空文字列を返すことが明記されました。これは、gcの変更によってerror型がこのカテゴリに正式に含まれるようになったことを反映しています。

関連リンク

参考にした情報源リンク