[インデックス 10589] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/gob
パッケージにおける挙動の修正に関するものです。具体的には、構造体のエクスポートされていない(unexported)フィールドの型情報が、gob
エンコーダによって誤って送信される問題を解決しています。これにより、gob
エンコーディングの堅牢性と、エクスポートされていないフィールドを持つ構造体の扱いが改善されます。
コミット
commit 30775f67e7d5e897d4d9aafe8ab84a5f65550ce4
Author: Rob Pike <r@golang.org>
Date: Fri Dec 2 00:02:24 2011 -0800
encoding/gob: don't send type info for unexported fields
Fixes #2517.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5440079
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/30775f67e7d5e897d4d9aafe8ab84a5f65550ce4
元コミット内容
encoding/gob: don't send type info for unexported fields
Fixes #2517.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5440079
変更の背景
この変更は、Go言語の encoding/gob
パッケージが、構造体のエクスポートされていないフィールドの型情報までエンコードしようとしていたバグ(Issue #2517)を修正するために行われました。Go言語では、構造体のフィールドが小文字で始まる場合、それはエクスポートされていない(unexported)フィールドとみなされ、パッケージ外からは直接アクセスできません。gob
エンコーダは、データ構造をシリアライズする際に、その型情報も一緒に送信します。しかし、エクスポートされていないフィールドは、通常、シリアライズの対象外とすべきであり、その型情報まで送信することは不適切でした。
特に、chan
(チャネル) や func
(関数) のような型は、gob
でのエンコードがサポートされていません。もしエクスポートされていないこれらの型のフィールドが構造体に含まれていた場合、gob
エンコーダがその型情報を送信しようとすると、エンコードエラーが発生していました。このバグは、開発者が意図せずエクスポートされていないチャネルや関数フィールドを持つ構造体を gob
でエンコードしようとした際に、予期せぬエラーを引き起こす可能性がありました。
このコミットは、gob
エンコーダがエクスポートされていないフィールドの型情報を送信しないようにすることで、この問題を解決し、gob
の堅牢性を向上させ、より柔軟なデータ構造のシリアライズを可能にすることを目的としています。
前提知識の解説
Go言語におけるエクスポートされた(Exported)フィールドとエクスポートされていない(Unexported)フィールド
Go言語では、識別子(変数名、関数名、型名、構造体フィールド名など)の最初の文字が大文字であるか小文字であるかによって、その可視性(visibility)が決定されます。
- エクスポートされた(Exported)識別子: 最初の文字が大文字の場合、その識別子はパッケージ外からアクセス可能です。これは、他の言語における
public
に相当します。 - エクスポートされていない(Unexported)識別子: 最初の文字が小文字の場合、その識別子は定義されたパッケージ内からのみアクセス可能です。これは、他の言語における
private
やinternal
に相当します。
encoding/gob
のようなシリアライズメカニズムでは、通常、エクスポートされたフィールドのみがシリアライズの対象となります。これは、エクスポートされていないフィールドが内部的な状態を表すことが多く、外部に公開すべきではないためです。
encoding/gob
パッケージ
encoding/gob
は、Go言語のデータ構造をバイナリ形式でエンコード(シリアライズ)およびデコード(デシリアライズ)するための標準パッケージです。主にGoプログラム間でのデータ交換や、永続化のために使用されます。gob
は、データだけでなく、その型情報も一緒にエンコードするため、受信側は事前に型を知らなくてもデータをデコードできるという特徴があります。
gob
の主な特徴は以下の通りです。
- 自己記述的: データストリームには型情報が含まれるため、受信側は送信側と同じ型定義を持っていなくてもデコードできます。
- 効率的: バイナリ形式であり、JSONやXMLに比べてコンパクトで高速です。
- Go固有: Goの型システムに特化しており、構造体、スライス、マップなどのGoのデータ型を直接エンコードできます。
- エクスポートされたフィールドのみ: デフォルトでは、構造体のエクスポートされたフィールドのみがエンコード/デコードの対象となります。
reflect
パッケージ
reflect
パッケージは、Go言語の実行時リフレクション機能を提供します。これにより、プログラムは自身の構造を検査し、実行時に変数の型や値を操作することができます。
reflect.Type
: Goの型の情報を表します。Kind()
メソッドでその型が構造体、配列、スライスなど、どのような種類であるかを取得できます。reflect.Value
: Goの変数の値を表します。Type.NumField()
: 構造体のフィールド数を返します。Type.Field(i)
: 構造体のi
番目のフィールドのStructField
を返します。StructField.Name
: フィールド名を返します。StructField.Type
: フィールドの型をreflect.Type
として返します。
このコミットでは、reflect
パッケージを使用して構造体のフィールドを走査し、そのフィールドがエクスポートされているかどうかを isExported
関数で判定しています。
技術的詳細
このコミットの核心は、encoding/gob
パッケージの Encoder
型の sendActualType
メソッドにおける変更です。このメソッドは、gob
ストリームに実際の型情報を送信する役割を担っています。
変更前は、reflect.Struct
型の処理において、構造体のすべてのフィールドを無条件に走査し、それぞれのフィールドの型情報を enc.sendType
メソッドで送信していました。
// 変更前
case reflect.Struct:
for i := 0; i < st.NumField(); i++ {
enc.sendType(w, state, st.Field(i).Type)
}
この挙動が問題でした。Goの慣習として、エクスポートされていないフィールド(小文字で始まるフィールド)は、パッケージ内部でのみ使用されるべきであり、外部に公開されるシリアライズデータには含まれるべきではありません。しかし、gob
はこれらのフィールドの型情報まで送信しようとしていました。特に、chan
や func
のような gob
でエンコードできない型がエクスポートされていないフィールドとして存在する場合、型情報の送信自体がエラーを引き起こしていました。
このコミットでは、sendActualType
メソッド内のループに条件分岐が追加されました。st.Field(i).Name
を isExported
関数でチェックし、そのフィールドがエクスポートされている場合にのみ enc.sendType
を呼び出すように変更されました。
// 変更後
case reflect.Struct:
for i := 0; i < st.NumField(); i++ {
if isExported(st.Field(i).Name) { // ここが追加された条件
enc.sendType(w, state, st.Field(i).Type)
}
}
isExported
関数は、Go言語の reflect
パッケージの内部で定義されているヘルパー関数で、識別子の最初の文字が大文字であるかどうかをチェックすることで、その識別子がエクスポートされているかどうかを判定します。この変更により、gob
エンコーダは、エクスポートされていないフィールドの型情報をストリームに含めなくなり、chan
や func
のようなエンコード不可能な型がエクスポートされていないフィールドとして存在しても、エンコードエラーが発生しなくなりました。
テストケース TestUnexportedChan
が追加され、この修正が正しく機能することを確認しています。このテストでは、エクスポートされていないチャネルフィールドを持つ構造体を gob
でエンコードし、エラーが発生しないことを検証しています。
コアとなるコードの変更箇所
変更は src/pkg/encoding/gob/encoder.go
ファイルの Encoder
型の sendActualType
メソッド内で行われています。
--- a/src/pkg/encoding/gob/encoder.go
+++ b/src/pkg/encoding/gob/encoder.go
@@ -119,7 +119,9 @@ func (enc *Encoder) sendActualType(w io.Writer, state *encoderState, ut *userTyp
switch st := actual; st.Kind() {
case reflect.Struct:
for i := 0; i < st.NumField(); i++ {
- enc.sendType(w, state, st.Field(i).Type)
+ if isExported(st.Field(i).Name) {
+ enc.sendType(w, state, st.Field(i).Type)
+ }
}
case reflect.Array, reflect.Slice:
enc.sendType(w, state, st.Elem())
また、src/pkg/encoding/gob/encoder_test.go
に新しいテストケースが追加されています。
--- a/src/pkg/encoding/gob/encoder_test.go
+++ b/src/pkg/encoding/gob/encoder_test.go
@@ -662,3 +662,19 @@ func TestSequentialDecoder(t *testing.T) {
}
}
}
+
+// Should be able to have unrepresentable fields (chan, func) as long as they
+// are unexported.
+type Bug2 struct {
+ A int
+ b chan int
+}
+
+func TestUnexportedChan(t *testing.T) {
+ b := Bug2{23, make(chan int)}
+ var stream bytes.Buffer
+ enc := NewEncoder(&stream)
+ if err := enc.Encode(b); err != nil {
+ t.Fatalf("error encoding unexported channel: %s", err)
+ }
+}
コアとなるコードの解説
src/pkg/encoding/gob/encoder.go
の変更
sendActualType
関数は、gob
エンコーダが型情報をストリームに書き込む際に呼び出されます。reflect.Struct
のケースでは、構造体の各フィールドを反復処理します。
変更前は、for i := 0; i < st.NumField(); i++
ループ内で、st.Field(i).Type
を直接 enc.sendType
に渡していました。これは、エクスポートされているかどうかにかかわらず、すべてのフィールドの型情報を送信することを意味していました。
変更後は、if isExported(st.Field(i).Name)
という条件が追加されました。
st.Field(i).Name
は、現在のフィールドの名前(文字列)を返します。isExported
関数は、Go言語のreflect
パッケージの内部で定義されている関数で、与えられた識別子名がエクスポートされている(つまり、最初の文字が大文字である)場合にtrue
を返します。
この条件により、enc.sendType
は、エクスポートされたフィールドの型情報に対してのみ呼び出されるようになります。これにより、エクスポートされていないフィールド(特に chan
や func
のようなエンコード不可能な型)の型情報が gob
ストリームに誤って含まれることがなくなり、関連するエンコードエラーが回避されます。
src/pkg/encoding/gob/encoder_test.go
の追加テスト
TestUnexportedChan
という新しいテスト関数が追加されました。
Bug2
という構造体が定義されています。この構造体は、エクスポートされたint
型のフィールドA
と、エクスポートされていないchan int
型のフィールドb
を持ちます。Bug2
のインスタンスb
が作成され、b.b
には新しいチャネルが割り当てられます。bytes.Buffer
を使用してgob
エンコードの出力先となるメモリバッファを作成します。NewEncoder
を使用してEncoder
インスタンスを作成します。enc.Encode(b)
を呼び出して、Bug2
インスタンスをエンコードします。if err := enc.Encode(b); err != nil
でエンコード中にエラーが発生しなかったことを確認します。もしエラーが発生した場合、t.Fatalf
でテストを失敗させます。
このテストは、エクスポートされていないチャネルフィールドを持つ構造体が、エラーなく gob
エンコードできることを検証しています。これは、コミットによって修正された問題が実際に解決されたことを示す重要なテストです。
関連リンク
- Go Issue #2517:
encoding/gob: don't send type info for unexported fields
- https://github.com/golang/go/issues/2517 - Go Code Review:
https://golang.org/cl/5440079
- このコミットのコードレビューページ。
参考にした情報源リンク
- Go言語の公式ドキュメント:
encoding/gob
パッケージ - https://pkg.go.dev/encoding/gob - Go言語の公式ドキュメント:
reflect
パッケージ - https://pkg.go.dev/reflect - Go言語の可視性ルール(Exported/Unexported identifiers)に関する一般的な情報源。