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

[インデックス 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() メソッドは、その型が structintslicearray など、どの基本的な種類であるかを返します。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.Slicecase ブロックにおいて、要素の型がバイト(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.Arrayreflect.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 } というチェックが重要です。

  • typreflect.Type であり、現在の値の型情報を持っています。
  • typ.Elem() は、配列やスライスの場合はその要素の型を返します。
  • Kind() は、その要素の型がプリミティブな種類(例: reflect.Uint8byte)であるかを返します。

このチェックにより、もし配列やスライスがバイト型(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.Arrayreflect.Slicecase ブロックに、それぞれ以下の行が追加されました。

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 の変更

新しいテストケース BadAttrmarshalErrorTests スライスに追加されました。

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 が返されることを検証します。これにより、将来の回帰を防ぎ、修正が正しく適用されたことを保証します。

関連リンク

参考にした情報源リンク