[インデックス 15729] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージにおけるバグ修正です。具体的には、XMLマーシャリング(Goの構造体をXMLに変換する処理)において、構造体タグの指定が不正な場合に発生する問題を解決します。特に、XML属性 (attr
フラグ) と要素の階層構造 (>
) を同時に指定した場合の不正な挙動を修正し、そのような無効なタグ指定を適切に拒否するように変更されています。
コミット
commit bfe80e21e4c0076fb75b14b04b9c9a1c3c4ee419
Author: Russ Cox <rsc@golang.org>
Date: Tue Mar 12 16:42:25 2013 -0400
encoding/xml: reject > chain with non-element
Fixes #5033.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7764044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bfe80e21e4c0076fb75b14b04b9c9a1c3c4ee419
元コミット内容
このコミットの元の内容は以下の通りです。
encoding/xml
: 非要素に対する>
チェーンを拒否する- Issue #5033 を修正。
- レビュー担当者: golang-dev, r
- CC: golang-dev
- 変更リスト: https://golang.org/cl/7764044
変更の背景
このコミットは、Goの encoding/xml
パッケージにおける特定のバグ、Issue #5033 を修正するために行われました。このバグは、Goの構造体フィールドにXMLタグを付与する際に、要素の階層構造を示す >
(例: xml:"A>B"
) と、そのフィールドがXML属性であることを示す attr
フラグ (例: xml:"name,attr"
) を誤って組み合わせて使用した場合に発生していました。
具体的には、xml:"X>Y,attr"
のようなタグが指定された場合、encoding/xml
パッケージはこれを正しく処理できず、予期せぬ動作やパニックを引き起こす可能性がありました。XMLの仕様上、属性は常に単一の要素に直接関連付けられるものであり、階層構造を持つことはありません。したがって、このようなタグの組み合わせは論理的に矛盾しており、GoのXMLマーシャリングライブラリはこれを無効な指定として認識し、エラーを返す必要がありました。
このコミット以前は、このような不正なタグ指定が適切に検出されず、開発者が意図しないXML出力やランタイムエラーに遭遇する可能性がありました。この修正により、encoding/xml
パッケージはより堅牢になり、開発者がXMLタグを誤って使用した場合に早期にエラーを通知できるようになります。
前提知識の解説
XML (Extensible Markup Language)
XMLは、情報を構造化するためのマークアップ言語です。HTMLがウェブページの表示に特化しているのに対し、XMLはデータの記述と転送に重点を置いています。XML文書は、要素、属性、テキストコンテンツなどから構成され、ツリー構造を形成します。
- 要素 (Element):
<tag>content</tag>
のように、開始タグと終了タグで囲まれた構造。XML文書の主要な構成要素です。 - 属性 (Attribute):
<tag name="value">
のように、要素の開始タグ内に記述され、その要素に関する追加情報を提供します。属性は常にキーと値のペアで構成されます。
Go言語の encoding/xml
パッケージ
encoding/xml
は、Go言語の標準ライブラリの一部で、Goの構造体とXML文書の間でデータを変換(マーシャリングとアンマーシャリング)するための機能を提供します。
- マーシャリング (Marshalling): Goの構造体のデータをXML文書に変換するプロセスです。
xml.Marshal
関数がこれを行います。 - アンマーシャリング (Unmarshalling): XML文書のデータをGoの構造体に変換するプロセスです。
xml.Unmarshal
関数がこれを行います。
Goの構造体タグ (Struct Tags)
Goでは、構造体のフィールドに「タグ」と呼ばれる文字列を付与することができます。これらのタグは、リフレクションAPIを通じて実行時にアクセス可能であり、encoding/xml
のようなパッケージが構造体フィールドをXML要素や属性にマッピングする方法を制御するために使用されます。
encoding/xml
パッケージで使用される主なタグの形式は以下の通りです。
xml:"element_name"
: フィールドがXML要素としてマーシャリングされる際の要素名を指定します。xml:"element_name,attr"
: フィールドがXML属性としてマーシャリングされる際の属性名を指定します。xml:"A>B"
: ネストされたXML要素の階層構造を指定します。例えば、xml:"Root>Child"
は<Root><Child>...</Child></Root>
のような構造を生成します。xml:",omitempty"
: フィールドがGoのゼロ値(数値の0、文字列の""、スライスのnilなど)である場合に、XML出力からその要素または属性を省略します。
XML要素と属性の区別
XMLにおいて、要素と属性は異なる役割を持ちます。
- 要素は階層構造を形成し、子要素やテキストコンテンツを持つことができます。
- 属性は要素のメタデータを提供し、通常は単一の値を持ち、階層構造を持つことはありません。
この区別は重要であり、encoding/xml
パッケージがGoの構造体をXMLに変換する際に、フィールドが要素として扱われるべきか、属性として扱われるべきかを正しく判断する必要があります。
fAttr
と fElement
フラグ (内部的な概念)
encoding/xml
パッケージの内部では、構造体タグの解析結果を表現するために、fieldInfo
構造体の中にフラグが使用されています。
fAttr
: フィールドがXML属性として扱われるべきであることを示すフラグ。fElement
: フィールドがXML要素として扱われるべきであることを示すフラグ。
これらのフラグは、タグの解析時に設定され、マーシャリング処理のロジックを制御します。
技術的詳細
このコミットの核心は、encoding/xml
パッケージが構造体タグを解析し、Goの構造体フィールドをXML要素または属性にマッピングするロジックの改善にあります。
Goの encoding/xml
パッケージは、構造体フィールドのタグ文字列を解析する際に、strings.Split(tag, ">")
を使用して >
区切りで親要素のチェーンを抽出します。例えば、xml:"A>B>C"
というタグは ["A", "B", "C"]
というトークンに分割されます。このとき、最後のトークン (C
) が実際の要素名または属性名となり、それより前のトークン (A
, B
) が親要素のチェーンを構成します。
問題となっていたのは、この >
チェーンが指定されているにもかかわらず、フィールドがXML属性 (attr
フラグが設定されている場合) として扱われるケースでした。XMLのセマンティクスでは、属性は常に単一の要素に直接関連付けられ、階層構造を持つことはありません。したがって、xml:"X>Y,attr"
のようなタグは論理的に矛盾しています。
このコミットでは、src/pkg/encoding/xml/typeinfo.go
内の structFieldInfo
関数に新しいバリデーションロジックが追加されました。この関数は、構造体フィールドの型情報とタグを解析し、fieldInfo
構造体を構築する役割を担っています。
変更前は、>
チェーンが存在する場合 (len(tokens) > 1
)、単純に finfo.parents
に親要素のリストを設定していました。しかし、変更後は、>
チェーンが存在する場合に加えて、そのフィールドが要素として扱われるべきであるか ((finfo.flags & fElement) == 0
) をチェックするようになりました。
もし >
チェーンが存在するにもかかわらず、そのフィールドが要素ではない(つまり、属性である)場合、fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
というエラーを返します。これにより、xml:"X>Y,attr"
のような不正なタグ指定がコンパイル時ではなく、実行時にマーシャリング処理が開始される前に検出され、明確なエラーメッセージが提供されるようになります。
また、src/pkg/encoding/xml/marshal_test.go
には、この修正を検証するための新しいテストケースが追加されました。
Strings
構造体とExpectXML: <Strings><A></A></Strings>
のテストケースは、omitempty
と親チェーンの組み合わせが正しく機能することを確認します。これはIssue #4168 (関連するが異なる問題) に関連するコメントがありますが、このコミットの直接の修正対象ではありません。AttrParent
構造体 (X string xml:"X>Y,attr"
) と、それに対するエラーテストケース (Err: xml: X>Y chain not valid with attr flag
) が追加されました。これは、まさにこのコミットが修正しようとしている不正なタグ指定をテストし、期待通りにエラーが返されることを確認します。
src/pkg/encoding/xml/marshal.go
の変更は、finfo.flags&(fAttr) != 0
から finfo.flags&fAttr != 0
への変更であり、これは括弧の削除によるスタイルの統一であり、機能的な変更ではありません。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の3つのファイルにわたります。
-
src/pkg/encoding/xml/marshal.go
--- a/src/pkg/encoding/xml/marshal.go +++ b/src/pkg/encoding/xml/marshal.go @@ -301,7 +301,7 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { s := parentStack{printer: p} for i := range tinfo.fields { finfo := &tinfo.fields[i] - if finfo.flags&(fAttr) != 0 { + if finfo.flags&fAttr != 0 { continue } vf := finfo.value(val)
finfo.flags&(fAttr) != 0
の括弧が削除され、finfo.flags&fAttr != 0
に変更されました。これは機能的な変更ではなく、コードスタイルの統一です。
-
src/pkg/encoding/xml/marshal_test.go
--- a/src/pkg/encoding/xml/marshal_test.go +++ b/src/pkg/encoding/xml/marshal_test.go @@ -272,6 +272,10 @@ type EmbedInt struct { MyInt }\n +type Strings struct { + X []string `xml:"A>B,omitempty"` +}\n +\n // Unless explicitly stated as such (or *Plain), all of the // tests below are two-way tests. When introducing new tests,\n // please try to make them two-way as well to ensure that @@ -802,6 +806,11 @@ var marshalTests = []struct { MyInt: 42, }, },\n + // Test omitempty with parent chain; see golang.org/issue/4168. + {\n + ExpectXML: `<Strings><A></A></Strings>`,\n + Value: &Strings{},\n + },\n }\n \n func TestMarshal(t *testing.T) {\n @@ -824,6 +833,10 @@ func TestMarshal(t *testing.T) { }\n }\n \n+type AttrParent struct {\n + X string `xml:"X>Y,attr"`\n +}\n +\n var marshalErrorTests = []struct {\n Value interface{}\n Err string\n @@ -851,6 +864,11 @@ var marshalErrorTests = []struct { Value: &Domain{Comment: []byte("f--bar")},\n Err: `xml: comments must not contain "--"`, },\n + // Reject parent chain with attr, never worked; see golang.org/issue/5033. + {\n + Value: &AttrParent{},\n + Err: `xml: X>Y chain not valid with attr flag`,\n + },\n }\n \n var marshalIndentTests = []struct {\n @@ -873,8 +891,12 @@ var marshalIndentTests = []struct { \n func TestMarshalErrors(t *testing.T) {\n for idx, test := range marshalErrorTests {\n - _, err := Marshal(test.Value)\n - if err == nil || err.Error() != test.Err {\n + data, err := Marshal(test.Value)\n + if err == nil {\n + t.Errorf("#%d: marshal(%#v) = [success] %q, want error %v", idx, test.Value, data, test.Err)\n + continue\n + }\n + if err.Error() != test.Err {\n t.Errorf("#%d: marshal(%#v) = [error] %v, want %v", idx, test.Value, err, test.Err)\n }\n if test.Kind != reflect.Invalid {\n
Strings
構造体とそれを使用するテストケースが追加されました。これはomitempty
と親チェーンの組み合わせのテストです。AttrParent
構造体 (X string xml:"X>Y,attr"
) が追加されました。marshalErrorTests
に、AttrParent
を使用した新しいテストケースが追加され、xml: X>Y chain not valid with attr flag
というエラーが期待されるようになりました。これはIssue #5033の修正を直接検証するものです。TestMarshalErrors
関数内で、エラーが返されなかった場合のテスト失敗ロジックが改善されました。
-
src/pkg/encoding/xml/typeinfo.go
--- a/src/pkg/encoding/xml/typeinfo.go +++ b/src/pkg/encoding/xml/typeinfo.go @@ -192,16 +192,19 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro }\n \n // Prepare field name and parents.\n - tokens = strings.Split(tag, ">")\n - if tokens[0] == "" {\n - tokens[0] = f.Name\n + parents := strings.Split(tag, ">")\n + if parents[0] == "" {\n + parents[0] = f.Name\n }\n - if tokens[len(tokens)-1] == "" {\n + if parents[len(parents)-1] == "" {\n return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)\n }\n - finfo.name = tokens[len(tokens)-1]\n - if len(tokens) > 1 {\n - finfo.parents = tokens[:len(tokens)-1]\n + finfo.name = parents[len(parents)-1]\n + if len(parents) > 1 {\n + if (finfo.flags & fElement) == 0 {\n + return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))\n + }\n + finfo.parents = parents[:len(parents)-1]\n }\n \n // If the field type has an XMLName field, the names must match\n
tokens
変数がparents
にリネームされました。- 最も重要な変更は、
if len(parents) > 1 { ... }
ブロック内に追加された新しい条件分岐です。if (finfo.flags & fElement) == 0 { ... }
- この条件は、「もし親チェーンが存在するにもかかわらず、そのフィールドが要素として扱われるべきではない場合(つまり、属性である場合)」をチェックします。
- この条件が真の場合、
xml: %s chain not valid with %s flag
というエラーを返します。これにより、xml:"X>Y,attr"
のような不正なタグ指定が検出され、エラーがスローされるようになります。
コアとなるコードの解説
このコミットの核心的な修正は、src/pkg/encoding/xml/typeinfo.go
ファイルの structFieldInfo
関数にあります。この関数は、Goの構造体フィールドに付与されたXMLタグを解析し、そのフィールドがXMLマーシャリングにおいてどのように扱われるべきか(要素名、属性名、親要素の階層など)を決定する fieldInfo
構造体を生成します。
変更前は、タグ文字列に >
が含まれている場合、単純にそれを親要素のチェーンとして解釈していました。しかし、これは xml:"X>Y,attr"
のように、階層構造を示す >
と、フィールドがXML属性であることを示す attr
フラグが同時に指定された場合に問題を引き起こしました。XMLのセマンティクスでは、属性は階層構造を持つことができないため、このようなタグは不正です。
新しいコードでは、この論理的な矛盾を検出するためのバリデーションが追加されました。
finfo.name = parents[len(parents)-1]
if len(parents) > 1 { // 親チェーンが存在する場合
if (finfo.flags & fElement) == 0 { // かつ、そのフィールドが要素ではない場合 (つまり属性である場合)
return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
}
finfo.parents = parents[:len(parents)-1]
}
len(parents) > 1
: これは、タグ文字列に>
が含まれており、親要素のチェーンが指定されていることを意味します。例えば、xml:"A>B"
の場合、parents
は["A", "B"]
となり、len(parents)
は2
です。(finfo.flags & fElement) == 0
: これは、fieldInfo
構造体のflags
フィールドにfElement
フラグが設定されていないことをチェックしています。fElement
フラグは、そのフィールドがXML要素として扱われるべきであることを示します。この条件が真であるということは、フィールドが要素ではない(例えば、attr
フラグが設定されているため属性として扱われる)ことを意味します。
この二つの条件が同時に真である場合、つまり「親チェーンが指定されているにもかかわらず、そのフィールドが要素ではない(属性である)」という矛盾した状態であれば、fmt.Errorf
を使用して明確なエラーメッセージを生成し、関数からエラーを返します。このエラーメッセージは、どのタグが不正であるか、そしてなぜそれが無効であるかを開発者に伝えます。
この修正により、encoding/xml
パッケージは、不正なXMLタグの組み合わせを早期に検出し、ランタイムエラーや予期せぬXML出力ではなく、明確なエラーメッセージを通じて開発者に問題を通知できるようになりました。これにより、ライブラリの堅牢性が向上し、開発者はより安全にXMLマーシャリングを利用できるようになります。
関連リンク
- Go Issue #5033: https://github.com/golang/go/issues/5033
- Go Change-list 7764044: https://golang.org/cl/7764044
参考にした情報源リンク
- Go言語
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - XMLの基本概念 (要素と属性): https://www.w3.org/TR/REC-xml/#sec-elements
- Go言語の構造体タグに関する一般的な情報: https://go.dev/blog/json (JSONに関する記事ですが、構造体タグの概念は共通です)
- Go言語のリフレクション: https://go.dev/blog/laws-of-reflection
[インデックス 15729] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージにおけるバグ修正です。具体的には、XMLマーシャリング(Goの構造体をXMLに変換する処理)において、構造体タグの指定が不正な場合に発生する問題を解決します。特に、XML属性 (attr
フラグ) と要素の階層構造 (>
) を同時に指定した場合の不正な挙動を修正し、そのような無効なタグ指定を適切に拒否するように変更されています。
コミット
commit bfe80e21e4c0076fb75b14b04b9c9a1c3c4ee419
Author: Russ Cox <rsc@golang.org>
Date: Tue Mar 12 16:42:25 2013 -0400
encoding/xml: reject > chain with non-element
Fixes #5033.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7764044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bfe80e21e4c0076fb75b14b04b9c9a1c3c4ee419
元コミット内容
このコミットの元の内容は以下の通りです。
encoding/xml
: 非要素に対する>
チェーンを拒否する- Issue #5033 を修正。
- レビュー担当者: golang-dev, r
- CC: golang-dev
- 変更リスト: https://golang.org/cl/7764044
変更の背景
このコミットは、Goの encoding/xml
パッケージにおける特定のバグ、Issue #5033 を修正するために行われました。このバグは、Goの構造体フィールドにXMLタグを付与する際に、要素の階層構造を示す >
(例: xml:"A>B"
) と、そのフィールドがXML属性であることを示す attr
フラグ (例: xml:"name,attr"
) を誤って組み合わせて使用した場合に発生していました。
具体的には、xml:"X>Y,attr"
のようなタグが指定された場合、encoding/xml
パッケージはこれを正しく処理できず、予期せぬ動作やパニックを引き起こす可能性がありました。XMLの仕様上、属性は常に単一の要素に直接関連付けられるものであり、階層構造を持つことはありません。したがって、このようなタグの組み合わせは論理的に矛盾しており、GoのXMLマーシャリングライブラリはこれを無効な指定として認識し、エラーを返す必要がありました。
このコミット以前は、このような不正なタグ指定が適切に検出されず、開発者が意図しないXML出力やランタイムエラーに遭遇する可能性がありました。この修正により、encoding/xml
パッケージはより堅牢になり、開発者がXMLタグを誤って使用した場合に早期にエラーを通知できるようになります。
前提知識の解説
XML (Extensible Markup Language)
XMLは、情報を構造化するためのマークアップ言語です。HTMLがウェブページの表示に特化しているのに対し、XMLはデータの記述と転送に重点を置いています。XML文書は、要素、属性、テキストコンテンツなどから構成され、ツリー構造を形成します。
- 要素 (Element):
<tag>content</tag>
のように、開始タグと終了タグで囲まれた構造。XML文書の主要な構成要素です。 - 属性 (Attribute):
<tag name="value">
のように、要素の開始タグ内に記述され、その要素に関する追加情報を提供します。属性は常にキーと値のペアで構成されます。
Go言語の encoding/xml
パッケージ
encoding/xml
は、Go言語の標準ライブラリの一部で、Goの構造体とXML文書の間でデータを変換(マーシャリングとアンマーシャリング)するための機能を提供します。
- マーシャリング (Marshalling): Goの構造体のデータをXML文書に変換するプロセスです。
xml.Marshal
関数がこれを行います。 - アンマーシャリング (Unmarshalling): XML文書のデータをGoの構造体に変換するプロセスです。
xml.Unmarshal
関数がこれを行います。
Goの構造体タグ (Struct Tags)
Goでは、構造体のフィールドに「タグ」と呼ばれる文字列を付与することができます。これらのタグは、リフレクションAPIを通じて実行時にアクセス可能であり、encoding/xml
のようなパッケージが構造体フィールドをXML要素や属性にマッピングする方法を制御するために使用されます。
encoding/xml
パッケージで使用される主なタグの形式は以下の通りです。
xml:"element_name"
: フィールドがXML要素としてマーシャリングされる際の要素名を指定します。xml:"element_name,attr"
: フィールドがXML属性としてマーシャリングされる際の属性名を指定します。xml:"A>B"
: ネストされたXML要素の階層構造を指定します。例えば、xml:"Root>Child"
は<Root><Child>...</Child></Root>
のような構造を生成します。xml:",omitempty"
: フィールドがGoのゼロ値(数値の0、文字列の""、スライスのnilなど)である場合に、XML出力からその要素または属性を省略します。
XML要素と属性の区別
XMLにおいて、要素と属性は異なる役割を持ちます。
- 要素は階層構造を形成し、子要素やテキストコンテンツを持つことができます。
- 属性は要素のメタデータを提供し、通常は単一の値を持ち、階層構造を持つことはありません。
この区別は重要であり、encoding/xml
パッケージがGoの構造体をXMLに変換する際に、フィールドが要素として扱われるべきか、属性として扱われるべきかを正しく判断する必要があります。
fAttr
と fElement
フラグ (内部的な概念)
encoding/xml
パッケージの内部では、構造体タグの解析結果を表現するために、fieldInfo
構造体の中にフラグが使用されています。
fAttr
: フィールドがXML属性として扱われるべきであることを示すフラグ。fElement
: フィールドがXML要素として扱われるべきであることを示すフラグ。
これらのフラグは、タグの解析時に設定され、マーシャリング処理のロジックを制御します。
技術的詳細
このコミットの核心は、encoding/xml
パッケージが構造体タグを解析し、Goの構造体フィールドをXML要素または属性にマッピングするロジックの改善にあります。
Goの encoding/xml
パッケージは、構造体フィールドのタグ文字列を解析する際に、strings.Split(tag, ">")
を使用して >
区切りで親要素のチェーンを抽出します。例えば、xml:"A>B>C"
というタグは ["A", "B", "C"]
というトークンに分割されます。このとき、最後のトークン (C
) が実際の要素名または属性名となり、それより前のトークン (A
, B
) が親要素のチェーンを構成します。
問題となっていたのは、この >
チェーンが指定されているにもかかわらず、フィールドがXML属性 (attr
フラグが設定されている場合) として扱われるケースでした。XMLのセマンティクスでは、属性は常に単一の要素に直接関連付けられ、階層構造を持つことはありません。したがって、xml:"X>Y,attr"
のようなタグは論理的に矛盾しています。
このコミットでは、src/pkg/encoding/xml/typeinfo.go
内の structFieldInfo
関数に新しいバリデーションロジックが追加されました。この関数は、構造体フィールドの型情報とタグを解析し、fieldInfo
構造体を構築する役割を担っています。
変更前は、>
チェーンが存在する場合 (len(tokens) > 1
)、単純に finfo.parents
に親要素のリストを設定していました。しかし、変更後は、>
チェーンが存在する場合に加えて、そのフィールドが要素として扱われるべきであるか ((finfo.flags & fElement) == 0
) をチェックするようになりました。
もし >
チェーンが存在するにもかかわらず、そのフィールドが要素ではない(つまり、属性である)場合、fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
というエラーを返します。これにより、xml:"X>Y,attr"
のような不正なタグ指定がコンパイル時ではなく、実行時にマーシャリング処理が開始される前に検出され、明確なエラーメッセージが提供されるようになります。
また、src/pkg/encoding/xml/marshal_test.go
には、この修正を検証するための新しいテストケースが追加されました。
Strings
構造体とExpectXML: <Strings><A></A></Strings>
のテストケースは、omitempty
と親チェーンの組み合わせが正しく機能することを確認します。これはIssue #4168 (関連するが異なる問題) に関連するコメントがありますが、このコミットの直接の修正対象ではありません。AttrParent
構造体 (X string xml:"X>Y,attr"
) と、それに対するエラーテストケース (Err: xml: X>Y chain not valid with attr flag
) が追加されました。これは、まさにこのコミットが修正しようとしている不正なタグ指定をテストし、期待通りにエラーが返されることを確認します。
src/pkg/encoding/xml/marshal.go
の変更は、finfo.flags&(fAttr) != 0
から finfo.flags&fAttr != 0
への変更であり、これは括弧の削除によるスタイルの統一であり、機能的な変更ではありません。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の3つのファイルにわたります。
-
src/pkg/encoding/xml/marshal.go
--- a/src/pkg/encoding/xml/marshal.go +++ b/src/pkg/encoding/xml/marshal.go @@ -301,7 +301,7 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { s := parentStack{printer: p} for i := range tinfo.fields { finfo := &tinfo.fields[i] - if finfo.flags&(fAttr) != 0 { + if finfo.flags&fAttr != 0 { continue } vf := finfo.value(val)
finfo.flags&(fAttr) != 0
の括弧が削除され、finfo.flags&fAttr != 0
に変更されました。これは機能的な変更ではなく、コードスタイルの統一です。
-
src/pkg/encoding/xml/marshal_test.go
--- a/src/pkg/encoding/xml/marshal_test.go +++ b/src/pkg/encoding/xml/marshal_test.go @@ -272,6 +272,10 @@ type EmbedInt struct { MyInt }\n +type Strings struct { + X []string `xml:"A>B,omitempty"` +}\n +\n // Unless explicitly stated as such (or *Plain), all of the // tests below are two-way tests. When introducing new tests,\n // please try to make them two-way as well to ensure that @@ -802,6 +806,11 @@ var marshalTests = []struct { MyInt: 42, }, },\n + // Test omitempty with parent chain; see golang.org/issue/4168. + {\n + ExpectXML: `<Strings><A></A></Strings>`,\n + Value: &Strings{},\n + },\n }\n \n func TestMarshal(t *testing.T) {\n @@ -824,6 +833,10 @@ func TestMarshal(t *testing.T) { }\n }\n \n+type AttrParent struct {\n + X string `xml:"X>Y,attr"`\n +}\n +\n var marshalErrorTests = []struct {\n Value interface{}\n Err string\n @@ -851,6 +864,11 @@ var marshalErrorTests = []struct { Value: &Domain{Comment: []byte("f--bar")},\n Err: `xml: comments must not contain "--"`, },\n + // Reject parent chain with attr, never worked; see golang.org/issue/5033. + {\n + Value: &AttrParent{},\n + Err: `xml: X>Y chain not valid with attr flag`,\n + },\n }\n \n var marshalIndentTests = []struct {\n @@ -873,8 +891,12 @@ var marshalIndentTests = []struct { \n func TestMarshalErrors(t *testing.T) {\n for idx, test := range marshalErrorTests {\n - _, err := Marshal(test.Value)\n - if err == nil || err.Error() != test.Err {\n + data, err := Marshal(test.Value)\n + if err == nil {\n + t.Errorf("#%d: marshal(%#v) = [success] %q, want error %v", idx, test.Value, data, test.Err)\n + continue\n + }\n + if err.Error() != test.Err {\n t.Errorf("#%d: marshal(%#v) = [error] %v, want %v\", idx, test.Value, err, test.Err)\n }\n if test.Kind != reflect.Invalid {\n
Strings
構造体とそれを使用するテストケースが追加されました。これはomitempty
と親チェーンの組み合わせのテストです。AttrParent
構造体 (X string xml:"X>Y,attr"
) が追加されました。marshalErrorTests
に、AttrParent
を使用した新しいテストケースが追加され、xml: X>Y chain not valid with attr flag
というエラーが期待されるようになりました。これはIssue #5033の修正を直接検証するものです。TestMarshalErrors
関数内で、エラーが返されなかった場合のテスト失敗ロジックが改善されました。
-
src/pkg/encoding/xml/typeinfo.go
--- a/src/pkg/encoding/xml/typeinfo.go +++ b/src/pkg/encoding/xml/typeinfo.go @@ -192,16 +192,19 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro }\n \n // Prepare field name and parents.\n - tokens = strings.Split(tag, ">")\n - if tokens[0] == "" {\n - tokens[0] = f.Name\n + parents := strings.Split(tag, ">")\n + if parents[0] == "" {\n + parents[0] = f.Name\n }\n - if tokens[len(tokens)-1] == "" {\n + if parents[len(parents)-1] == "" {\n return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)\n }\n - finfo.name = tokens[len(tokens)-1]\n - if len(tokens) > 1 {\n - finfo.parents = tokens[:len(tokens)-1]\n + finfo.name = parents[len(parents)-1]\n + if len(parents) > 1 {\n + if (finfo.flags & fElement) == 0 {\n + return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))\n + }\n + finfo.parents = parents[:len(parents)-1]\n }\n \n // If the field type has an XMLName field, the names must match\n
tokens
変数がparents
にリネームされました。- 最も重要な変更は、
if len(parents) > 1 { ... }
ブロック内に追加された新しい条件分岐です。if (finfo.flags & fElement) == 0 { ... }
- この条件は、「もし親チェーンが存在するにもかかわらず、そのフィールドが要素として扱われるべきではない場合(つまり、属性である場合)」をチェックします。
- この条件が真の場合、
xml: %s chain not valid with %s flag
というエラーを返します。これにより、xml:"X>Y,attr"
のような不正なタグ指定が検出され、エラーがスローされるようになります。
コアとなるコードの解説
このコミットの核心的な修正は、src/pkg/encoding/xml/typeinfo.go
ファイルの structFieldInfo
関数にあります。この関数は、Goの構造体フィールドに付与されたXMLタグを解析し、そのフィールドがXMLマーシャリングにおいてどのように扱われるべきか(要素名、属性名、親要素の階層など)を決定する fieldInfo
構造体を生成します。
変更前は、タグ文字列に >
が含まれている場合、単純にそれを親要素のチェーンとして解釈していました。しかし、これは xml:"X>Y,attr"
のように、階層構造を示す >
と、フィールドがXML属性であることを示す attr
フラグが同時に指定された場合に問題を引き起こしました。XMLのセマンティクスでは、属性は階層構造を持つことができないため、このようなタグは不正です。
新しいコードでは、この論理的な矛盾を検出するためのバリデーションが追加されました。
finfo.name = parents[len(parents)-1]
if len(parents) > 1 { // 親チェーンが存在する場合
if (finfo.flags & fElement) == 0 { // かつ、そのフィールドが要素ではない場合 (つまり属性である場合)
return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
}
finfo.parents = parents[:len(parents)-1]
}
len(parents) > 1
: これは、タグ文字列に>
が含まれており、親要素のチェーンが指定されていることを意味します。例えば、xml:"A>B"
の場合、parents
は["A", "B"]
となり、len(parents)
は2
です。(finfo.flags & fElement) == 0
: これは、fieldInfo
構造体のflags
フィールドにfElement
フラグが設定されていないことをチェックしています。fElement
フラグは、そのフィールドがXML要素として扱われるべきであることを示します。この条件が真であるということは、フィールドが要素ではない(例えば、attr
フラグが設定されているため属性として扱われる)ことを意味します。
この二つの条件が同時に真である場合、つまり「親チェーンが指定されているにもかかわらず、そのフィールドが要素ではない(属性である)」という矛盾した状態であれば、fmt.Errorf
を使用して明確なエラーメッセージを生成し、関数からエラーを返します。このエラーメッセージは、どのタグが不正であるか、そしてなぜそれが無効であるかを開発者に伝えます。
この修正により、encoding/xml
パッケージは、不正なXMLタグの組み合わせを早期に検出し、ランタイムエラーや予期せぬXML出力ではなく、明確なエラーメッセージを通じて開発者に問題を通知できるようになりました。これにより、ライブラリの堅牢性が向上し、開発者はより安全にXMLマーシャリングを利用できるようになります。
関連リンク
- Go Issue #5033: https://github.com/golang/go/issues/5033
- Go Change-list 7764044: https://golang.org/cl/7764044
参考にした情報源リンク
- Go言語
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - XMLの基本概念 (要素と属性): https://www.w3.org/TR/REC-xml/#sec-elements
- Go言語の構造体タグに関する一般的な情報: https://go.dev/blog/json (JSONに関する記事ですが、構造体タグの概念は共通です)
- Go言語のリフレクション: https://go.dev/blog/laws-of-reflection