[インデックス 17521] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/xml
パッケージにおける Marshal
関数が、特定の条件下でパニック(実行時エラー)を引き起こす問題を修正するものです。具体的には、XML属性としてバイト配列ではないスライスや配列が指定された場合に発生するパニックを防ぎます。
コミット
commit 10c36fbc9d413062de4a1ecd59b9c5f7dc82b0c9
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 9 16:42:07 2013 -0400
encoding/xml: fix panic in Marshal
Fixes #6341.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/13512048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/10c36fbc9d413062de4a1ecd59b9c5f7dc82b0c9
元コミット内容
encoding/xml: fix panic in Marshal
Fixes #6341.
変更の背景
このコミットは、Go言語の encoding/xml
パッケージの Marshal
関数が、XML属性として非バイト型の配列やスライス(例: []string
)を処理しようとした際に発生するパニックを修正するために行われました。
encoding/xml
パッケージは、Goの構造体とXMLドキュメント間のマーシャリング(Goのデータ構造をXMLに変換すること)およびアンマーシャリング(XMLをGoのデータ構造に変換すること)を提供します。Marshal
関数は、Goの値をXML要素に変換する主要な関数です。
以前の実装では、marshalSimple
関数内で reflect.Array
および reflect.Slice
型を処理する際に、それらが常にバイト配列([]byte
または [...]byte
)であると仮定していました。XML属性は通常、単純な文字列値を持つため、encoding/xml
はバイト配列をXML属性のテキストコンテンツとして効率的に扱うように設計されていました。
しかし、もしユーザーが []string
のような非バイト型のスライスや配列を構造体のフィールドに持ち、それを xml:",attr"
タグを使ってXML属性としてマーシャリングしようとすると、marshalSimple
関数は val.Bytes()
や val.Slice(...).Bytes()
といったバイト型に特化したメソッドを呼び出そうとします。これらのメソッドは、レシーバがバイト型でない場合にはパニックを引き起こします。
この問題は、Go Issue #6341 として報告されており、このコミットはその報告されたパニックを修正することを目的としています。
前提知識の解説
Go言語の reflect
パッケージ
Go言語の reflect
パッケージは、実行時にプログラムの構造を検査し、操作するための機能を提供します。これにより、型情報(reflect.Type
)や値情報(reflect.Value
)を動的に取得できます。
reflect.Type
: Goの型の情報を表します。Kind()
メソッドは、その型がstruct
、int
、slice
、array
など、どの基本的な種類であるかを返します。Elem()
メソッドは、ポインタ、配列、スライス、マップ、チャネルの要素型を返します。reflect.Value
: Goの値の情報を表します。Bytes()
メソッドは、[]byte
型のreflect.Value
から基になるバイトスライスを返します。Slice()
メソッドは、配列やスライスから新しいスライスを作成します。
encoding/xml
のような汎用的なマーシャリングライブラリでは、ユーザーが定義した任意の構造体を処理する必要があるため、reflect
パッケージが不可欠です。これにより、構造体のフィールドの型を動的に調べ、それに応じてXMLへの変換ロジックを適用できます。
XMLマーシャリングと属性
XML(Extensible Markup Language)は、データを構造化するためのマークアップ言語です。XMLドキュメントは要素、属性、テキストコンテンツなどで構成されます。
- 要素:
<tag>content</tag>
のように、開始タグと終了タグで囲まれた構造。 - 属性: 開始タグ内に
name="value"
の形式で記述される、要素に関する追加情報。
Goの encoding/xml
パッケージでは、構造体のフィールドにタグを付けることで、そのフィールドがXML要素になるか、属性になるか、あるいはテキストコンテンツになるかを制御できます。例えば、xml:"name,attr"
のような構造体タグは、そのフィールドがXML属性としてマーシャリングされることを示します。
XML属性は通常、単一の文字列値を持つことが期待されます。そのため、encoding/xml
が属性としてマーシャリングできる型には制限があります。バイト配列([]byte
)は、XMLではテキストデータとして扱われるため、属性値として適切に変換できます。しかし、[]string
のような複雑な型は、単一のXML属性値としては直接表現できません。
パニック(Panic)
Goにおけるパニックは、プログラムの実行を停止させる回復不可能なエラーの一種です。通常、プログラマーの論理的な誤りや、予期せぬ不正な状態(例: nilポインタのデリファレンス、インデックス範囲外アクセス、型アサーションの失敗)によって引き起こされます。このコミットで修正されたパニックは、reflect.Value
のメソッドが、その値がサポートしていない型に対して呼び出された場合に発生する典型的な例です。
技術的詳細
このコミットの技術的な核心は、encoding/xml
パッケージ内の marshalSimple
関数における型チェックの強化です。
marshalSimple
関数は、Goの基本的な型(プリミティブ型、配列、スライスなど)をXMLの単純な値(主に属性値やテキストコンテンツ)に変換する役割を担っています。
変更前のコードでは、reflect.Array
および reflect.Slice
の case
ブロックにおいて、要素の型がバイト(reflect.Uint8
)であるかどうかのチェックがありませんでした。
// 変更前 (簡略化)
case reflect.Array:
// ... バイト配列として処理 ...
var bytes []byte
// val.Slice(0, val.Len()).Bytes() または reflect.Copy を使用
return "", bytes, nil
case reflect.Slice:
// ... バイトスライスとして処理 ...
return "", val.Bytes(), nil
この実装では、val.Bytes()
や val.Slice(...).Bytes()
が、[]byte
や [...]byte
ではない reflect.Value
に対して呼び出されると、Goのランタイムがパニックを引き起こします。例えば、[]string
型の reflect.Value
に対して Bytes()
を呼び出すことはできません。
コミットによる修正は、この問題に対処するために、reflect.Array
と reflect.Slice
の各 case
に以下の条件分岐を追加しました。
// 変更後 (簡略化)
case reflect.Array:
if typ.Elem().Kind() != reflect.Uint8 { // ここが追加された
break // バイト配列でなければ、このcaseを抜ける
}
// ... バイト配列として処理 ...
case reflect.Slice:
if typ.Elem().Kind() != reflect.Uint8 { // ここが追加された
break // バイトスライスでなければ、このcaseを抜ける
}
// ... バイトスライスとして処理 ...
この if typ.Elem().Kind() != reflect.Uint8 { break }
というチェックが重要です。
typ
はreflect.Type
であり、現在の値の型情報を持っています。typ.Elem()
は、配列やスライスの場合はその要素の型を返します。Kind()
は、その要素の型がプリミティブな種類(例:reflect.Uint8
はbyte
)であるかを返します。
このチェックにより、もし配列やスライスがバイト型(uint8
)でなければ、break
ステートメントが実行され、現在の switch
文から抜け出します。marshalSimple
関数の末尾には、どの case
にもマッチしなかった場合に UnsupportedTypeError
を返すロジックがあります。
// marshalSimple 関数の末尾 (簡略化)
// ...
return "", nil, &UnsupportedTypeError{typ} // どのcaseにもマッチしなかった場合
したがって、非バイト型の配列やスライスが渡された場合、新しいチェックによって break
が実行され、最終的に UnsupportedTypeError
が返されるようになります。これにより、不正な型に対してバイト操作を行おうとしてパニックが発生する代わりに、適切なエラーが返されるようになり、プログラムの堅牢性が向上します。
テストケース BadAttr
の追加もこの修正の意図を明確にしています。BadAttr
構造体は []string
型のフィールドをXML属性としてマーシャリングしようとします。このテストは、修正後のコードが xml: unsupported type: []string
というエラーを正しく返すことを検証しており、パニックがエラーに置き換えられたことを示しています。
コアとなるコードの変更箇所
src/pkg/encoding/xml/marshal.go
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -655,7 +655,10 @@ func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) (string, []
case reflect.Bool:
return strconv.FormatBool(val.Bool()), nil, nil
case reflect.Array:
- // will be [...]byte
+ if typ.Elem().Kind() != reflect.Uint8 {
+ break
+ }
+ // [...]byte
var bytes []byte
if val.CanAddr() {
bytes = val.Slice(0, val.Len()).Bytes()
@@ -665,7 +668,10 @@ func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) (string, []
}
return "", bytes, nil
case reflect.Slice:
- // will be []byte
+ if typ.Elem().Kind() != reflect.Uint8 {
+ break
+ }
+ // []byte
return "", val.Bytes(), nil
}
return "", nil, &UnsupportedTypeError{typ}
src/pkg/encoding/xml/marshal_test.go
--- a/src/pkg/encoding/xml/marshal_test.go
+++ b/src/pkg/encoding/xml/marshal_test.go
@@ -904,6 +904,10 @@ type AttrParent struct {
X string `xml:"X>Y,attr"`
}
+type BadAttr struct {
+ Name []string `xml:"name,attr"`
+}
+
var marshalErrorTests = []struct {
Value interface{}
Err string
@@ -936,6 +940,10 @@ var marshalErrorTests = []struct {
\t\tValue: &AttrParent{},
\t\tErr: `xml: X>Y chain not valid with attr flag`,
},\n+\t{\n+\t\tValue: BadAttr{[]string{\"X\", \"Y\"}},\n+\t\tErr: `xml: unsupported type: []string`,\n+\t},\n }
var marshalIndentTests = []struct {
コアとなるコードの解説
src/pkg/encoding/xml/marshal.go
の変更
marshalSimple
関数内の reflect.Array
と reflect.Slice
の case
ブロックに、それぞれ以下の行が追加されました。
if typ.Elem().Kind() != reflect.Uint8 {
break
}
typ.Elem().Kind()
: これは、現在のreflect.Type
(typ
) が配列またはスライスである場合に、その要素のKind
(種類)を取得します。例えば、[]string
の場合、typ.Elem()
はstring
型を表し、Kind()
はreflect.String
を返します。[]byte
の場合、typ.Elem()
はbyte
(つまりuint8
) 型を表し、Kind()
はreflect.Uint8
を返します。!= reflect.Uint8
: この条件は、要素の型がuint8
(バイト)ではない場合に真となります。break
: 条件が真の場合、break
ステートメントが実行され、現在のswitch
文から抜け出します。これにより、非バイト型の配列やスライスが、バイト型として処理されることを防ぎます。switch
文を抜けた後、marshalSimple
関数の最後の行にあるreturn "", nil, &UnsupportedTypeError{typ}
が実行され、適切なエラーが呼び出し元に返されます。
この変更により、encoding/xml
は、XML属性としてバイト配列以外の配列やスライスが指定された場合に、パニックを起こす代わりに、明確な「サポートされていない型」エラーを返すようになります。これは、ライブラリの堅牢性とエラーハンドリングの改善に大きく貢献します。
src/pkg/encoding/xml/marshal_test.go
の変更
新しいテストケース BadAttr
が marshalErrorTests
スライスに追加されました。
type BadAttr struct {
Name []string `xml:"name,attr"`
}
// ...
{
Value: BadAttr{[]string{"X", "Y"}},
Err: `xml: unsupported type: []string`,
},
type BadAttr struct { Name []string
xml:"name,attr"}
: この構造体は、Name
という[]string
型のフィールドを持ち、それがxml:"name,attr"
タグによってXML属性としてマーシャリングされることを意図しています。- テストケースの追加:
marshalErrorTests
は、マーシャリング時に特定のエラーが期待されるシナリオをテストするためのものです。追加されたテストケースは、BadAttr
のインスタンスをマーシャリングしようとしたときに、xml: unsupported type: []string
というエラー文字列が返されることをアサートしています。
このテストケースは、marshal.go
で行われた変更が意図通りに機能し、非バイト型のスライスがXML属性として渡された場合にパニックではなく、適切な UnsupportedTypeError
が返されることを検証します。これにより、将来の回帰を防ぎ、修正が正しく適用されたことを保証します。
関連リンク
- Go Issue #6341: https://github.com/golang/go/issues/6341
- Go CL 13512048: https://golang.org/cl/13512048 (Gerrit Code Review)
参考にした情報源リンク
- Go言語
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - Go言語
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - XML属性に関するW3Cの仕様 (一般的な情報): https://www.w3.org/TR/xml/#sec-attdecls (これは一般的なXMLの知識源であり、特定のコミットに直接関連するものではありませんが、背景知識として有用です。)
- Go言語におけるパニックとリカバリー: https://go.dev/blog/defer-panic-and-recover (一般的なGoのエラーハンドリングに関する情報)