[インデックス 14930] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージにおけるバグ修正を目的としています。具体的には、XML属性の値をポインタ型のフィールドにデコードする際に発生していた問題を解決します。これにより、XMLのアンマーシャリング処理がより堅牢になり、ポインタフィールドへの属性値の正確なマッピングが保証されます。
コミット
- コミットハッシュ:
4730a226ca78a192ee0fa39df76cd9b260772f5d
- Author: Kamil Kisiel kamil@kamilkisiel.net
- Date: Fri Jan 18 17:07:34 2013 -0500
- コミットメッセージ:
encoding/xml: fix decoding of attributes in to pointer fields. Fixes #3719. R=anacrolix, rsc CC=golang-dev https://golang.org/cl/7131052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4730a226ca78a192ee0fa39df76cd9b260772f5d
元コミット内容
commit 4730a226ca78a192ee0fa39df76cd9b260772f5d
Author: Kamil Kisiel <kamil@kamilkisiel.net>
Date: Fri Jan 18 17:07:34 2013 -0500
encoding/xml: fix decoding of attributes in to pointer fields.
Fixes #3719.
R=anacrolix, rsc
CC=golang-dev
https://golang.org/cl/7131052
---
src/pkg/encoding/xml/read.go | 7 +++++++
src/pkg/encoding/xml/read_test.go | 44 +++++++++++++++++++++++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/src/pkg/encoding/xml/read.go b/src/pkg/encoding/xml/read.go
index 7a06a29b95..6bc23e1226 100644
--- a/src/pkg/encoding/xml/read.go
+++ b/src/pkg/encoding/xml/read.go
@@ -394,6 +394,13 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
\treturn err == nil
}\n \n+\tif pv := dst; pv.Kind() == reflect.Ptr {\n+\t\tif pv.IsNil() {\n+\t\t\tpv.Set(reflect.New(pv.Type().Elem()))\n+\t\t}\n+\t\tdst = pv.Elem()\n+\t}\n+\n \t// Save accumulated data.\n \tswitch t := dst; t.Kind() {\n \tcase reflect.Invalid:\ndiff --git a/src/pkg/encoding/xml/read_test.go b/src/pkg/encoding/xml/read_test.go
index 8df09b3cce..b45e2f0e61 100644
--- a/src/pkg/encoding/xml/read_test.go
+++ b/src/pkg/encoding/xml/read_test.go
@@ -355,3 +355,47 @@ func TestUnmarshalWithoutNameType(t *testing.T) {\n \t\tt.Fatalf(\"have %v\\nwant %v\", x.Attr, OK)\n \t}\n }\n+\n+func TestUnmarshalAttr(t *testing.T) {\n+\ttype ParamVal struct {\n+\t\tInt int `xml:\"int,attr\"`\n+\t}\n+\n+\ttype ParamPtr struct {\n+\t\tInt *int `xml:\"int,attr\"`\n+\t}\n+\n+\ttype ParamStringPtr struct {\n+\t\tInt *string `xml:\"int,attr\"`\n+\t}\n+\n+\tx := []byte(`<Param int=\"1\" />`)\n+\n+\tp1 := &ParamPtr{}\n+\tif err := Unmarshal(x, p1); err != nil {\n+\t\tt.Fatalf(\"Unmarshal: %s\", err)\n+\t}\n+\tif p1.Int == nil {\n+\t\tt.Fatalf(\"Unmarshal failed in to *int field\")\n+\t} else if *p1.Int != 1 {\n+\t\tt.Fatalf(\"Unmarshal with %s failed:\\nhave %#v,\\n want %#v\", x, p1.Int, 1)\n+\t}\n+\n+\tp2 := &ParamVal{}\n+\tif err := Unmarshal(x, p2); err != nil {\n+\t\tt.Fatalf(\"Unmarshal: %s\", err)\n+\t}\n+\tif p2.Int != 1 {\n+\t\tt.Fatalf(\"Unmarshal with %s failed:\\nhave %#v,\\n want %#v\", x, p2.Int, 1)\n+\t}\n+\n+\tp3 := &ParamStringPtr{}\n+\tif err := Unmarshal(x, p3); err != nil {\n+\t\tt.Fatalf(\"Unmarshal: %s\", err)\n+\t}\n+\tif p3.Int == nil {\n+\t\tt.Fatalf(\"Unmarshal failed in to *string field\")\n+\t} else if *p3.Int != \"1\" {\n+\t\tt.Fatalf(\"Unmarshal with %s failed:\\nhave %#v,\\n want %#v\", x, p3.Int, 1)\n+\t}\n+}\n```
## 変更の背景
このコミットは、Go言語の `encoding/xml` パッケージにおける特定のバグ、Issue 3719「`encoding/xml`: attributes are not decoded into pointer fields」を修正するために行われました。
このバグは、XML要素の属性をGoの構造体のポインタ型フィールドにアンマーシャリングしようとした際に発生していました。具体的には、`xml:"attr"` タグが付けられたフィールドがポインタ型(例: `*int`, `*string`)である場合、`encoding/xml` パッケージは属性値をそのポインタが指す先へ正しくデコードできませんでした。結果として、ポインタフィールドは `nil` のままであったり、期待される値が設定されなかったりする問題がありました。
この問題は、XMLデータをGoの構造体にマッピングする際に、ポインタフィールドを柔軟に扱いたい開発者にとって大きな制約となっていました。例えば、属性が存在しない場合に `nil` を保持し、存在する場合に値を保持するといったセマンティクスを実現できませんでした。この修正により、`encoding/xml` パッケージはより多様なGoの型定義に対応できるようになり、開発者はXML属性をポインタフィールドに安心してデコードできるようになりました。
## 前提知識の解説
### Go言語の `encoding/xml` パッケージ
`encoding/xml` パッケージは、Go言語でXMLデータをエンコード(Goの構造体からXMLへ)およびデコード(XMLからGoの構造体へ)するための機能を提供します。主な関数として `xml.Marshal` と `xml.Unmarshal` があります。
- **`xml.Unmarshal(data []byte, v interface{}) error`**: XMLデータをGoの構造体にデコードする際に使用されます。この関数は、XML要素や属性を、Goの構造体のフィールドにマッピングします。マッピングは、構造体フィールドに付けられたタグ(例: `xml:"name,attr"`, `xml:"element"`)に基づいて行われます。
### 構造体タグとXML属性
Goの構造体フィールドには、`xml` タグを付けることで、XMLデータとのマッピング方法を制御できます。
- `xml:"name"`: XML要素の名前を指定します。
- `xml:"name,attr"`: XML属性の名前を指定します。このタグが付いたフィールドは、親XML要素の属性として扱われます。
- `xml:",chardata"`: XML要素の文字データ(テキストコンテンツ)をフィールドにマッピングします。
- `xml:",innerxml"`: XML要素の内部の生XMLをフィールドにマッピングします。
本コミットでは、特に `xml:"name,attr"` タグが付けられたフィールドがポインタ型である場合の挙動が問題となっていました。
### Go言語の `reflect` パッケージ
`reflect` パッケージは、Goのプログラムが実行時に自身の構造を検査(リフレクション)したり、変更したりする機能を提供します。`encoding/xml` のような汎用的なエンコーディング/デコーディングライブラリは、この `reflect` パッケージを多用して、任意のGoの構造体のフィールドに動的にアクセスし、値を設定します。
- **`reflect.Value`**: Goの任意の型の値を表すリフレクションオブジェクトです。
- **`reflect.Kind()`**: `reflect.Value` が表す値の基本的な種類(例: `reflect.Int`, `reflect.String`, `reflect.Ptr`, `reflect.Struct` など)を返します。
- **`reflect.Ptr`**: ポインタ型を表す `reflect.Kind` です。
- **`reflect.IsNil()`**: ポインタ、インターフェース、マップ、スライス、チャネル、関数などの `reflect.Value` が `nil` であるかどうかを判定します。
- **`reflect.Type()`**: `reflect.Value` が表す値の型を返します。
- **`reflect.Type().Elem()`**: ポインタ型の場合、そのポインタが指す要素の型を返します。
- **`reflect.New(typ reflect.Type)`**: 指定された型 `typ` の新しい値へのポインタを返します。これは、`new(T)` と同等です。
- **`reflect.Value.Set(x reflect.Value)`**: `reflect.Value` が表す値に、別の `reflect.Value` `x` の値を設定します。この操作は、`reflect.Value` が変更可能(settable)である場合にのみ可能です。
- **`reflect.Value.Elem()`**: ポインタ型の場合、そのポインタが指す要素の `reflect.Value` を返します。
`encoding/xml` パッケージは、XML属性の値をGoの構造体フィールドに設定する際に、`reflect` パッケージを使ってフィールドの型を調べ、適切な変換と値の設定を行います。
## 技術的詳細
このバグは、`encoding/xml` パッケージ内部の `copyValue` 関数に起因していました。`copyValue` 関数は、XMLから読み取ったバイトスライス `src` の内容を、Goの構造体フィールドを表す `reflect.Value` `dst` にコピーする役割を担っています。
問題は、`dst` がポインタ型(例: `*int`)であるにもかかわらず、そのポインタが `nil` の場合に、新しいインスタンスを割り当ててポインタが指す先を初期化する処理が欠けていた点にありました。
修正前の `copyValue` 関数は、`dst` がポインタ型であるかどうかを直接チェックせず、`dst` が指す先の値に直接データをコピーしようとしていました。しかし、`dst` が `nil` ポインタの場合、その先に値を書き込むことはできません(パニックを引き起こすか、単に値が設定されない)。
このコミットでは、`copyValue` 関数に以下のロジックが追加されました。
1. `dst` がポインタ型であるか (`pv.Kind() == reflect.Ptr`) をチェックします。
2. もしポインタ型であれば、そのポインタが `nil` であるか (`pv.IsNil()`) をチェックします。
3. `nil` であれば、`reflect.New(pv.Type().Elem())` を使って、ポインタが指す先の型の新しいインスタンスを生成し、そのポインタを `pv.Set()` で `dst` に設定します。これにより、`nil` ポインタが有効なメモリを指すようになります。
4. 最後に、`dst = pv.Elem()` を実行して、`dst` をポインタが指す実際の要素の `reflect.Value` に更新します。これにより、後続の `switch t := dst; t.Kind()` ブロックで、ポインタの先の値に正しくデータがコピーされるようになります。
この変更により、`encoding/xml` はXML属性をポインタフィールドにデコードする際に、必要に応じてポインタの指す先を自動的に初期化し、値を正しく設定できるようになりました。
## コアとなるコードの変更箇所
`src/pkg/encoding/xml/read.go` の `copyValue` 関数に以下の7行が追加されました。
```diff
--- a/src/pkg/encoding/xml/read.go
+++ b/src/pkg/encoding/xml/read.go
@@ -394,6 +394,13 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
\treturn err == nil
}\n \n+\tif pv := dst; pv.Kind() == reflect.Ptr {\n+\t\tif pv.IsNil() {\n+\t\t\tpv.Set(reflect.New(pv.Type().Elem()))\n+\t\t}\n+\t\tdst = pv.Elem()\n+\t}\n+\n \t// Save accumulated data.\n \tswitch t := dst; t.Kind() {\
\tcase reflect.Invalid:\
また、src/pkg/encoding/xml/read_test.go
に TestUnmarshalAttr
という新しいテストケースが追加され、ポインタ型フィールドへの属性デコードが正しく行われることを検証しています。
コアとなるコードの解説
追加されたコードブロックは、copyValue
関数が値のコピーを行う前に、ターゲットの reflect.Value
dst
がポインタ型であるかどうか、そしてそれが nil
であるかどうかをチェックします。
if pv := dst; pv.Kind() == reflect.Ptr {
if pv.IsNil() {
pv.Set(reflect.New(pv.Type().Elem()))
}
dst = pv.Elem()
}
-
if pv := dst; pv.Kind() == reflect.Ptr { ... }
:dst
をpv
という新しいreflect.Value
変数に代入します。pv.Kind() == reflect.Ptr
で、dst
がポインタ型であるかをチェックします。この条件が真の場合のみ、ブロック内の処理が実行されます。
-
if pv.IsNil() { ... }
:pv
がポインタ型であり、かつそのポインタがnil
であるかをチェックします。つまり、まだメモリが割り当てられていない状態です。
-
pv.Set(reflect.New(pv.Type().Elem()))
:pv.Type().Elem()
:pv
がポインタ型なので、このメソッドはポインタが指す先の型(例:*int
ならint
型)を返します。reflect.New(...)
: 指定された型の新しいインスタンスへのポインタを生成します。これはnew(T)
と同じ効果を持ちます。pv.Set(...)
:pv
(元のdst
のポインタ)に、新しく生成されたインスタンスへのポインタを設定します。これにより、nil
だったポインタが有効なメモリを指すようになります。
-
dst = pv.Elem()
:pv.Elem()
:pv
が指す先の値(つまり、新しく初期化されたint
やstring
の値)のreflect.Value
を返します。- この行で、
dst
変数を、ポインタの先の実際の値のreflect.Value
に更新します。これにより、この後のswitch
ステートメントで、dst
が直接その値(例:int
やstring
)として扱われ、src
からのデータが正しくコピーされるようになります。
このロジックにより、encoding/xml
はXML属性をポインタフィールドにデコードする際に、ポインタが nil
であっても適切に初期化し、値を設定できるようになりました。
関連リンク
- Go Issue 3719: https://code.google.com/p/go/issues/detail?id=3719 (古いGoのIssueトラッカーのリンクですが、問題の詳細が記述されています)
- Go CL 7131052: https://golang.org/cl/7131052 (このコミットに対応するGoのコードレビューシステムへのリンク)
参考にした情報源リンク
- Go Issue Tracker (Issue 3719): 上記のリンク
- Go Code Review (CL 7131052): 上記のリンク
- Go言語の
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - Go言語の
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - Go言語におけるリフレクションの基本概念に関する一般的な情報源 (例: Go by Example: Reflection, A Tour of Go: Reflection)