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

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

このコミットは、Go言語の標準ライブラリであるencoding/gobパッケージにおける、net.IP型のエンコーディングに関する互換性の問題を修正するものです。具体的には、gobencoding.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.IPMarshalText実装は、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/TextUnmarshalerBinaryMarshaler/BinaryUnmarshalerよりも優先される可能性があり、これがnet.IPとの間で問題を引き起こしていました。

技術的詳細

このコミットの技術的詳細は、encoding/gobパッケージがカスタムエンコーディングインターフェースを検出するロジックの変更にあります。

encoding/gobは、Goの型をシリアライズする際に、以下の優先順位でカスタムエンコーディングロジックを探します(変更前):

  1. GobEncoder / GobDecoder (gob固有のインターフェース)
  2. encoding.BinaryMarshaler / encoding.BinaryUnmarshaler
  3. encoding.TextMarshaler / encoding.TextUnmarshaler

net.IP型は、IPアドレスをテキスト形式(例: "192.168.1.1")で表現するためにMarshalTextUnmarshalTextを実装しています。しかし、gobは本質的にバイナリエンコーディングを目的としており、net.IPMarshalTextが生成するテキスト形式のデータは、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の使用を避けるしかなかったという背景を明確に示しています。

結果として、gobnet.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パッケージの公式ドキュメントを更新するものです。以前の記述では、gobencoding.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のgobnet.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関数は、与えられた型がGobEncoderBinaryMarshalerTextMarshalerなどのカスタムエンコーディングインターフェースを実装しているかどうかをチェックし、その情報を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
// }

これらの変更により、gobTextMarshalerおよび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パッケージがBinaryMarshalerBinaryUnmarshalerインターフェースをサポートすることが明記されました。これは、TextMarshalerのサポートが実質的に削除されたことを反映し、ユーザーにGo 1.2でのgobの挙動に関する正確な情報を提供します。

関連リンク

参考にした情報源リンク