[インデックス 17885] ファイルの概要
このコミットは、Go言語の標準ライブラリであるencoding/gobパッケージにおける、net.IP型のエンコーディングに関する互換性の問題を修正するものです。具体的には、gobがencoding.TextMarshalerおよびencoding.TextUnmarshalerインターフェースを処理する際の優先順位を変更し、net.IP型がこれらのインターフェースを実装していることによる予期せぬ挙動を回避しています。
変更されたファイルは以下の通りです。
doc/go1.2.html: Go 1.2のリリースノートに関するドキュメント。encoding/gobがサポートするインターフェースに関する記述が更新されています。src/pkg/encoding/gob/doc.go:encoding/gobパッケージのドキュメント。gobがエンコード/デコード時に考慮するインターフェースの優先順位に関する説明が修正されています。src/pkg/encoding/gob/gobencdec_test.go:encoding/gobパッケージのテストファイル。net.IP型のエンコーディング/デコーディングが正しく行われることを確認するための新しいテストケースが追加されています。src/pkg/encoding/gob/type.go:encoding/gobパッケージの型情報処理に関するコアロジック。MarshalTextおよびUnmarshalTextインターフェースの検出と利用に関するコードが変更されています。
コミット
commit 7dd086e52d237eaf46e88c723ba61d6a835ef1d0
Author: Russ Cox <rsc@golang.org>
Date: Wed Nov 13 21:29:19 2013 -0500
encoding/gob: do not use MarshalText, UnmarshalText
This seems to be the best of a long list of bad ways to fix this issue.
Fixes #6760.
R=r
CC=golang-dev
https://golang.org/cl/22770044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7dd086e52d237eaf46e88c723ba61d6a835ef1d0
元コミット内容
encoding/gob: do not use MarshalText, UnmarshalText
This seems to be the best of a long list of bad ways to fix this issue.
Fixes #6760.
R=r
CC=golang-dev
https://golang.org/cl/22770044
変更の背景
このコミットは、Goのencoding/gobパッケージがnet.IP型を正しくエンコード/デコードできないという問題(Go issue #6760)を解決するために行われました。
encoding/gobはGoのネイティブなデータシリアライゼーションフォーマットであり、Goプログラム間で構造化されたデータを効率的にやり取りするために設計されています。gobは、カスタムのエンコーディング/デコーディングロジックを提供するために、特定のインターフェース(GobEncoder, GobDecoder, encoding.BinaryMarshaler, encoding.BinaryUnmarshaler, encoding.TextMarshaler, encoding.TextUnmarshaler)を型が実装しているかどうかをチェックします。
問題は、net.IP型がencoding.TextMarshalerおよびencoding.TextUnmarshalerインターフェースを実装していたことにありました。gobパッケージは、これらのインターフェースをBinaryMarshaler/BinaryUnmarshalerよりも優先して使用しようとしていました。しかし、net.IPのMarshalText実装は、IPアドレスをテキスト形式(例: "192.168.1.1")で表現するものであり、これはgobが期待するバイナリ形式とは異なりました。このミスマッチにより、net.IP型を含むデータをgobでエンコード/デコードしようとすると、データが破損したり、予期せぬエラーが発生したりする可能性がありました。
コミットメッセージにある「This seems to be the best of a long list of bad ways to fix this issue.」という記述は、この問題に対する解決策が複数検討されたものの、gobの既存の設計とnet.IPの特性を考慮すると、TextMarshaler/TextUnmarshalerの優先順位を下げる(あるいは無視する)ことが最も現実的で影響の少ない修正であったことを示唆しています。
前提知識の解説
encoding/gobパッケージ: Go言語の標準ライブラリで、Goのデータ構造をバイナリ形式にシリアライズ(エンコード)およびデシリアライズ(デコード)するためのパッケージです。ネットワーク経由でのデータ転送や、ファイルへの永続化などに利用されます。gobは、データ型情報も一緒にエンコードするため、受信側で型定義がなくてもデータを正しくデコードできるという特徴があります。encoding.BinaryMarshaler/encoding.BinaryUnmarshalerインターフェース:type BinaryMarshaler interface { MarshalBinary() (data []byte, err error) }type BinaryUnmarshaler interface { UnmarshalBinary(data []byte) error }これらのインターフェースを実装する型は、自身をバイナリ形式に変換する方法(MarshalBinary)と、バイナリデータから自身を再構築する方法(UnmarshalBinary)を定義できます。gobを含む多くのGoのエンコーディングパッケージで、カスタムのバイナリシリアライゼーションロジックを提供するために利用されます。
encoding.TextMarshaler/encoding.TextUnmarshalerインターフェース:type TextMarshaler interface { MarshalText() (text []byte, err error) }type TextUnmarshaler interface { UnmarshalText(text []byte) error }これらのインターフェースを実装する型は、自身をテキスト形式に変換する方法(MarshalText)と、テキストデータから自身を再構築する方法(UnmarshalText)を定義できます。JSONやYAMLなど、テキストベースのエンコーディングでよく利用されます。例えば、time.Time型はこれらのインターフェースを実装しており、日付時刻をRFC3339形式の文字列としてエンコード/デコードします。
net.IP型: Goの標準ライブラリnetパッケージで定義されている、IPアドレスを表す型です。[]byteのスライスとして実装されており、IPv4アドレス(4バイト)やIPv6アドレス(16バイト)を格納します。net.IP型は、IPアドレスの文字列表現(例: "192.168.1.1")との変換のためにMarshalTextおよびUnmarshalTextを実装しています。
gobパッケージは、エンコード/デコードを行う際に、与えられた型がこれらのインターフェースのいずれかを実装しているかをチェックし、実装していればそのカスタムロジックを優先的に利用します。従来のgobの実装では、TextMarshaler/TextUnmarshalerがBinaryMarshaler/BinaryUnmarshalerよりも優先される可能性があり、これがnet.IPとの間で問題を引き起こしていました。
技術的詳細
このコミットの技術的詳細は、encoding/gobパッケージがカスタムエンコーディングインターフェースを検出するロジックの変更にあります。
encoding/gobは、Goの型をシリアライズする際に、以下の優先順位でカスタムエンコーディングロジックを探します(変更前):
GobEncoder/GobDecoder(gob固有のインターフェース)encoding.BinaryMarshaler/encoding.BinaryUnmarshalerencoding.TextMarshaler/encoding.TextUnmarshaler
net.IP型は、IPアドレスをテキスト形式(例: "192.168.1.1")で表現するためにMarshalTextとUnmarshalTextを実装しています。しかし、gobは本質的にバイナリエンコーディングを目的としており、net.IPのMarshalTextが生成するテキスト形式のデータは、gobのバイナリストリームに直接適合しませんでした。
このコミットでは、src/pkg/encoding/gob/type.go内のvalidUserType関数において、TextMarshalerおよびTextUnmarshalerインターフェースの検出ロジックをコメントアウトすることで、gobがこれらのインターフェースをエンコーディング/デコーディングの候補から除外するように変更しています。これにより、net.IPのような型がTextMarshalerを実装していても、gobはそれを無視し、代わりにその型の基盤となるバイナリ表現(この場合は[]byte)を直接エンコード/デコードするようになります。
コミットメッセージのコメントアウトされたコードブロックには、NOTE(rsc): Would like to allow MarshalText here, but results in incompatibility with older encodings for net.IP. See golang.org/issue/6760.と記載されており、これはMarshalTextをサポートしたいという意図はあったものの、既存のnet.IPの古いエンコーディングとの互換性を維持するためには、MarshalTextの使用を避けるしかなかったという背景を明確に示しています。
結果として、gobはnet.IPをテキストとしてではなく、その基盤となるバイトスライスとして扱い、Go 1.1以前のgobでエンコードされたnet.IPデータとの互換性を保ちつつ、正しくシリアライズ/デシリアライズできるようになりました。
コアとなるコードの変更箇所
src/pkg/encoding/gob/doc.go
--- a/src/pkg/encoding/gob/doc.go
+++ b/src/pkg/encoding/gob/doc.go
@@ -86,13 +86,13 @@ Functions and channels will not be sent in a gob. Attempting to encode such a va
at top the level will fail. A struct field of chan or func type is treated exactly
like an unexported field and is ignored.
-Gob can encode a value of any type implementing the GobEncoder,
-encoding.BinaryMarshaler, or encoding.TextMarshaler interfaces by calling the
-corresponding method, in that order of preference.
+Gob can encode a value of any type implementing the GobEncoder or
+encoding.BinaryMarshaler interfaces by calling the corresponding method,
+in that order of preference.
-Gob can decode a value of any type implementing the GobDecoder,
-encoding.BinaryUnmarshaler, or encoding.TextUnmarshaler interfaces by calling
-the corresponding method, again in that order of preference.
+Gob can decode a value of any type implementing the GobDecoder or
+encoding.BinaryUnmarshaler interfaces by calling the corresponding method,
+again in that order of preference.
Encoding Details
src/pkg/encoding/gob/gobencdec_test.go
--- a/src/pkg/encoding/gob/gobencdec_test.go
+++ b/src/pkg/encoding/gob/gobencdec_test.go
@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
+ "net"
"strings"
"testing"
"time"
@@ -767,3 +768,17 @@ func TestGobEncodePtrError(t *testing.T) {
}
}
+func TestNetIP(t *testing.T) {
+ // Encoding of net.IP{1,2,3,4} in Go 1.1.
+ enc := []byte{0x07, 0x0a, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04}
+
+ var ip net.IP
+ err := NewDecoder(bytes.NewReader(enc)).Decode(&ip)
+ if err != nil {
+ t.Fatalf("decode: %v", err)
+ }
+ if ip.String() != "1.2.3.4" {
+ t.Errorf("decoded to %v, want 1.2.3.4", ip.String())
+ }
+}
src/pkg/encoding/gob/type.go
--- a/src/pkg/encoding/gob/type.go
+++ b/src/pkg/encoding/gob/type.go
@@ -88,18 +88,25 @@ func validUserType(rt reflect.Type) (ut *userTypeInfo, err error) {
ut.externalEnc, ut.encIndir = xGob, indir
} else if ok, indir := implementsInterface(ut.user, binaryMarshalerInterfaceType); ok {
ut.externalEnc, ut.encIndir = xBinary, indir
- } else if ok, indir := implementsInterface(ut.user, textMarshalerInterfaceType); ok {
- ut.externalEnc, ut.encIndir = xText, indir
}
+ // NOTE(rsc): Would like to allow MarshalText here, but results in incompatibility
+ // with older encodings for net.IP. See golang.org/issue/6760.
+ // } else if ok, indir := implementsInterface(ut.user, textMarshalerInterfaceType); ok {
+ // ut.externalEnc, ut.encIndir = xText, indir
+ // }
+
if ok, indir := implementsInterface(ut.user, gobDecoderInterfaceType); ok {
ut.externalDec, ut.decIndir = xGob, indir
} else if ok, indir := implementsInterface(ut.user, binaryUnmarshalerInterfaceType); ok {
ut.externalDec, ut.decIndir = xBinary, indir
- } else if ok, indir := implementsInterface(ut.user, textUnmarshalerInterfaceType); ok {
- ut.externalDec, ut.decIndir = xText, indir
}
+ // See note above.
+ // } else if ok, indir := implementsInterface(ut.user, textUnmarshalerInterfaceType); ok {
+ // ut.externalDec, ut.decIndir = xText, indir
+ // }
+
userTypeCache[rt] = ut
return
}
doc/go1.2.html
--- a/doc/go1.2.html
+++ b/doc/go1.2.html
@@ -736,7 +736,8 @@ now treats channel and function fields of structures as if they were unexported,
even if they are not. That is, it ignores them completely. Previously they would
trigger an error, which could cause unexpected compatibility problems if an
embedded structure added such a field.
-The package also now supports the generic encoding interfaces of the
+The package also now supports the generic <code>BinaryMarshaler</code> and
+<code>BinaryUnmarshaler</code> interfaces of the
<a href="/pkg/encoding/"><code>encoding</code></a> package
described above.
</li>
コアとなるコードの解説
src/pkg/encoding/gob/doc.go の変更
この変更は、encoding/gobパッケージの公式ドキュメントを更新するものです。以前の記述では、gobがencoding.TextMarshalerおよびencoding.TextUnmarshalerインターフェースも優先的に利用すると説明されていましたが、今回の修正によりこれらのインターフェースの優先度が実質的に削除されたため、ドキュメントもそれに合わせて修正されました。これにより、ユーザーはgobがどのようにエンコーディングインターフェースを扱うかについて、正確な情報を得られるようになります。
src/pkg/encoding/gob/gobencdec_test.go の変更
TestNetIPという新しいテスト関数が追加されました。このテストは、Go 1.1でエンコードされたnet.IP{1,2,3,4}のバイナリ表現(encバイトスライス)をデコードし、その結果が期待通り1.2.3.4というIPアドレスになることを検証します。
enc := []byte{0x07, 0x0a, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04}: これは、Go 1.1のgobでnet.IP{1,2,3,4}がエンコードされた際の具体的なバイト列です。このバイト列をデコードすることで、新しいgobの実装が古い形式のデータと互換性があることを確認します。NewDecoder(bytes.NewReader(enc)).Decode(&ip): エンコードされたバイト列からgob.Decoderを作成し、net.IP型の変数ipにデコードします。if ip.String() != "1.2.3.4": デコードされたnet.IPが期待される文字列表現("1.2.3.4")と一致するかを検証します。
このテストの追加は、TextMarshaler/TextUnmarshalerの扱いを変更したことによって、既存のnet.IPのエンコーディング/デコーディングが壊れていないことを保証するために非常に重要です。
src/pkg/encoding/gob/type.go の変更
このファイルは、gobがGoの型を分析し、エンコード/デコードのための内部表現を構築する際の中心的なロジックを含んでいます。validUserType関数は、与えられた型がGobEncoder、BinaryMarshaler、TextMarshalerなどのカスタムエンコーディングインターフェースを実装しているかどうかをチェックし、その情報をuserTypeInfo構造体に格納します。
変更点としては、以下の行がコメントアウトされました。
// } else if ok, indir := implementsInterface(ut.user, textMarshalerInterfaceType); ok {
// ut.externalEnc, ut.encIndir = xText, indir
// }
// } else if ok, indir := implementsInterface(ut.user, textUnmarshalerInterfaceType); ok {
// ut.externalDec, ut.decIndir = xText, indir
// }
これらの変更により、gobはTextMarshalerおよびTextUnmarshalerインターフェースを実装している型であっても、それらをカスタムエンコーディングの候補として認識しなくなりました。結果として、net.IP型がMarshalText/UnmarshalTextを実装していても、gobはそれらを無視し、net.IPの基盤となる[]byteとしてのバイナリ表現を直接エンコード/デコードするようになります。
コメントアウトされたコードの上のNOTE(rsc)コメントは、この変更の理由を明確にしています。MarshalTextを許可したいという意図はあったものの、net.IPの古いエンコーディングとの互換性の問題(issue #6760)を解決するためには、この方法が最善であると判断されたことを示しています。
doc/go1.2.html の変更
Go 1.2のリリースノートのドキュメントも更新され、encoding/gobパッケージがBinaryMarshalerとBinaryUnmarshalerインターフェースをサポートすることが明記されました。これは、TextMarshalerのサポートが実質的に削除されたことを反映し、ユーザーにGo 1.2でのgobの挙動に関する正確な情報を提供します。
関連リンク
- Go issue #6760: https://golang.org/issue/6760
- Go Change List (CL) 22770044: https://golang.org/cl/22770044
参考にした情報源リンク
- コミットデータ:
./commit_data/17885.txt - Web検索結果 (Go issue 6760): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE9-dYQ86nz7suTyslls_Jx4hl2Gralf7drmEnh98_DV7pNBqVlYxwclET-Oqhsjvhc1XDad0Z41Vhnkq8MubG3aL_nGYcsbFLZN3aMa-gF6cKqIf8P5CVxnMbij4uxGSR70gFMAWvEkLA81yTI5cuhOTP8Q_dKm4rVurKr4WHD5tOyoA_o8Khz5LZW (Google SearchによるGo issue 6760の要約)
- Go言語の公式ドキュメント (
encoding/gob,encodingパッケージ): https://pkg.go.dev/encoding/gob, https://pkg.go.dev/encoding (一般的なGoのエンコーディングインターフェースに関する情報) - Go言語の公式ドキュメント (
netパッケージ): https://pkg.go.dev/net (net.IPに関する情報)