[インデックス 15338] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/xml
パッケージにおける、XMLエンコーダのエラーハンドリングに関する修正です。具体的には、Encoder.Encode
メソッドが内部の io.Writer
への書き込みエラーを適切に報告しない問題を解決しています。
変更されたファイルは以下の通りです。
src/pkg/encoding/xml/marshal.go
: XMLマーシャリング(Goのデータ構造からXMLへの変換)ロジックが含まれるファイル。主にprinter
型のメソッドが修正されています。src/pkg/encoding/xml/marshal_test.go
:marshal.go
のテストファイル。書き込みエラーをテストする新しいテストケースが追加されています。src/pkg/encoding/xml/xml.go
: XMLエンコーディングの低レベルなユーティリティ関数が含まれるファイル。特にEscape
関数がEscapeText
にリファクタリングされ、エラーを返すようになりました。src/pkg/encoding/xml/xml_test.go
:xml.go
のテストファイル。EscapeText
のI/Oエラーをテストする新しいテストケースが追加されています。
コミット
commit afde71cfbdb1032cce8fefc6bc665b816b3276e6
Author: Olivier Saingre <osaingre@gmail.com>
Date: Wed Feb 20 14:41:23 2013 -0800
encoding/xml: make sure Encoder.Encode reports Write errors.
Fixes #4112.
R=remyoudompheng, daniel.morsing, dave, rsc
CC=golang-dev
https://golang.org/cl/7085053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/afde71cfbdb1032cce8fefc6bc665b816b3276e6
元コミット内容
encoding/xml: make sure Encoder.Encode reports Write errors.
Fixes #4112.
R=remyoudompheng, daniel.morsing, dave, rsc
CC=golang-dev
https://golang.org/cl/7085053
変更の背景
このコミットの主な背景は、Go言語の encoding/xml
パッケージにおける Encoder.Encode
メソッドが、XMLデータを書き込む際に発生する io.Writer
からの書き込みエラーを適切に捕捉し、呼び出し元に報告していなかったという問題です。コミットメッセージにある Fixes #4112
は、Goプロジェクトの内部イシュートラッカーにおける特定のバグ報告に対応していることを示しています。
従来の Encoder.Encode
は、内部で io.Writer
インターフェースを介してXMLデータを書き出しますが、この書き込み操作が失敗した場合(例えば、ディスク容量不足、ネットワークエラー、権限問題など)、そのエラーが Encode
メソッドの戻り値として伝播されませんでした。これにより、アプリケーションはXMLエンコーディング中に発生した重要なI/Oエラーを検知できず、データの破損や予期せぬ動作につながる可能性がありました。
この修正は、堅牢なエラーハンドリングを保証し、encoding/xml
パッケージの信頼性を向上させることを目的としています。特に、ストリーム処理やネットワーク経由でのXML送信など、書き込みエラーが発生しうるシナリオにおいて、この修正は重要です。
前提知識の解説
Go言語の io.Writer
インターフェース
Go言語において、io.Writer
はデータを書き込むための基本的なインターフェースです。その定義は以下の通りです。
type Writer interface {
Write(p []byte) (n int, err error)
}
Write
メソッドは、バイトスライス p
のデータを書き込み、書き込まれたバイト数 n
と、発生したエラー err
を返します。エラーが発生しなかった場合は nil
を返します。このインターフェースは、ファイル、ネットワーク接続、メモリバッファなど、様々な出力先に対して統一された書き込みメカニズムを提供します。
XMLエンコーディングとエスケープ処理
XML(Extensible Markup Language)は、データを構造化するためのマークアップ言語です。XMLドキュメント内では、特定の文字(<
、>
、&
、'
、"
)は特別な意味を持つため、これらをデータとして表現する場合には「エスケープ」する必要があります。例えば、<
は <
と、&
は &
といったエンティティ参照に変換されます。
encoding/xml
パッケージは、Goのデータ構造をXML形式に変換(マーシャリング)する機能を提供します。この変換プロセスには、データ内の特殊文字を適切にエスケープする処理が含まれます。
Go言語のエラーハンドリング
Go言語では、エラーは戻り値として明示的に扱われます。関数がエラーを返す可能性がある場合、通常は最後の戻り値として error
型の値を返します。呼び出し元は、この error
値が nil
でないかどうかをチェックすることで、エラーが発生したかどうかを判断し、適切に処理します。
このコミットの文脈では、io.Writer
の Write
メソッドが返すエラーを、encoding/xml
パッケージのエンコーディング関数が捕捉し、さらに上位の Encoder.Encode
メソッドまで伝播させる必要があります。
技術的詳細
このコミットの技術的な核心は、encoding/xml
パッケージ内のXMLエスケープ処理関数 Escape
をリファクタリングし、書き込みエラーを報告するように変更した点にあります。
-
Escape
からEscapeText
へのリファクタリングとエラー伝播:- 既存の
func Escape(w io.Writer, s []byte)
関数は、書き込みエラーを無視していました。 - この関数は
func EscapeText(w io.Writer, s []byte) error
という新しいシグネチャを持つ関数にリファクタリングされました。この新しい関数は、内部のw.Write
呼び出しが返すエラーを捕捉し、それを自身の戻り値として返します。 EscapeText
の実装では、w.Write
の結果をチェックし、エラーが発生した場合は即座にそのエラーを返します。- 後方互換性のために、元の
Escape
関数は残されましたが、その実装は単にEscapeText
を呼び出し、返されるエラーを破棄する形に変更されました。これにより、Go 1.0 以前のコードは引き続きコンパイルされますが、Go 1.1 以降をターゲットとするコードはEscapeText
を使用してエラーハンドリングを行うことが推奨されます。
- 既存の
-
marshal.go
におけるエラーハンドリングの導入:src/pkg/encoding/xml/marshal.go
内のprinter
型のメソッド(marshalValue
,marshalSimple
,marshalStruct
など)は、XMLデータを実際に書き出す役割を担っています。- これらのメソッド内で
Escape
関数が呼び出されていた箇所が、新しくエラーを返すようになったEscapeText
に置き換えられました。 EscapeText
の呼び出し結果として返されるエラーは、if err := ...; err != nil { return err }
のパターンでチェックされ、即座に呼び出し元に伝播されるようになりました。これにより、XMLエンコーディングの途中で書き込みエラーが発生した場合、そのエラーがEncoder.Encode
メソッドの呼び出し元まで適切に報告されるようになります。
-
テストケースの追加:
src/pkg/encoding/xml/marshal_test.go
にTestMarshalWriteIOErrors
という新しいテスト関数が追加されました。このテストは、io.Writer
インターフェースを実装し、常にエラーを返すerrWriter
というダミーの型を使用します。このerrWriter
をEncoder
に渡してエンコードを試みることで、書き込みエラーが適切に報告されることを検証します。src/pkg/encoding/xml/xml_test.go
にもTestEscapeTextIOErrors
という同様のテスト関数が追加され、EscapeText
関数自体が書き込みエラーを正しく伝播するかどうかを検証します。
これらの変更により、encoding/xml
パッケージは、XMLデータの生成中に発生する可能性のあるI/Oエラーに対して、より堅牢なエラーハンドリングを提供するようになりました。
コアとなるコードの変更箇所
src/pkg/encoding/xml/marshal.go
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -193,7 +193,9 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error {
if xmlns != "" {
p.WriteString(` xmlns="`)
// TODO: EscapeString, to avoid the allocation.
- Escape(p, []byte(xmlns))
+ if err := EscapeText(p, []byte(xmlns)); err != nil {
+ return err
+ }
p.WriteByte('"')
}
@@ -252,19 +254,22 @@ func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) error {
p.WriteString(strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()))
case reflect.String:
// TODO: Add EscapeString.
- Escape(p, []byte(val.String()))
+ EscapeText(p, []byte(val.String()))
case reflect.Bool:
p.WriteString(strconv.FormatBool(val.Bool()))
case reflect.Array:
// will be [...]byte
- bytes := make([]byte, val.Len())
- for i := range bytes {
- bytes[i] = val.Index(i).Interface().(byte)
+ var bytes []byte
+ if val.CanAddr() {
+ bytes = val.Slice(0, val.Len()).Bytes()
+ } else {
+ bytes = make([]byte, val.Len())
+ reflect.Copy(reflect.ValueOf(bytes), val)
}
- Escape(p, bytes)
+ EscapeText(p, bytes)
case reflect.Slice:
// will be []byte
- Escape(p, val.Bytes())
+ EscapeText(p, val.Bytes())
default:
return &UnsupportedTypeError{typ}
}
@@ -298,10 +303,14 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
case reflect.Bool:
Escape(p, strconv.AppendBool(scratch[:0], vf.Bool()))
case reflect.String:
- Escape(p, []byte(vf.String()))
+ if err := EscapeText(p, []byte(vf.String())); err != nil {
+ return err
+ }
case reflect.Slice:
if elem, ok := vf.Interface().([]byte); ok {
- Escape(p, elem)
+ if err := EscapeText(p, elem); err != nil {
+ return err
+ }
}
case reflect.Struct:
if vf.Type() == timeType {
src/pkg/encoding/xml/xml.go
--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -1720,9 +1720,9 @@ var (
esc_cr = []byte("
")
)
-// Escape writes to w the properly escaped XML equivalent
+// EscapeText writes to w the properly escaped XML equivalent
// of the plain text data s.
-func Escape(w io.Writer, s []byte) {
+func EscapeText(w io.Writer, s []byte) error {
var esc []byte
last := 0
for i, c := range s {
@@ -1746,11 +1746,25 @@ func Escape(w io.Writer, s []byte) {
default:
continue
}
- w.Write(s[last:i])
- w.Write(esc)
+ if _, err := w.Write(s[last:i]); err != nil {
+ return err
+ }
+ if _, err := w.Write(esc); err != nil {
+ return err
+ }
last = i + 1
}
- w.Write(s[last:])
+ if _, err := w.Write(s[last:]); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Escape is like EscapeText but omits the error return value.
+// It is provided for backwards compatibility with Go 1.0.
+// Code targeting Go 1.1 or later should use EscapeText.
+func Escape(w io.Writer, s []byte) {
+ EscapeText(w, s)
}
// procInstEncoding parses the `encoding="..."` or `encoding='...'`
コアとなるコードの解説
src/pkg/encoding/xml/xml.go
の変更
Escape
からEscapeText
へのリファクタリング:- 元の
Escape
関数はfunc Escape(w io.Writer, s []byte)
というシグネチャで、戻り値がありませんでした。これは、内部でw.Write
がエラーを返しても、それを呼び出し元に報告しないことを意味します。 - 新しい
EscapeText
関数はfunc EscapeText(w io.Writer, s []byte) error
というシグネチャに変更され、error
を返すようになりました。 EscapeText
の実装内部では、w.Write
の呼び出しごとにif _, err := w.Write(...); err != nil { return err }
という形でエラーチェックが追加されています。これにより、書き込み中にエラーが発生した場合、そのエラーが即座にEscapeText
の呼び出し元に伝播されます。- 元の
Escape
関数は、後方互換性のために残され、EscapeText
を呼び出してエラーを破棄するラッパー関数として機能します。これは、Go 1.0 以前のコードがこの変更によって壊れないようにするための配慮です。
- 元の
src/pkg/encoding/xml/marshal.go
の変更
EscapeText
の呼び出しとエラーハンドリングの導入:marshal.go
内のprinter
型のメソッド(例:marshalValue
,marshalSimple
,marshalStruct
)は、XML要素の属性値やテキストコンテンツを書き出す際にEscape
関数を呼び出していました。- これらの箇所で
Escape
の呼び出しがEscapeText
に変更されました。 - さらに重要なのは、
EscapeText
の呼び出し結果をif err := EscapeText(...); err != nil { return err }
の形式でチェックし、エラーが発生した場合はそのエラーを現在のマーシャリング関数の戻り値として返すようになった点です。 - これにより、XMLデータの生成中に
EscapeText
が内部で書き込みエラーを検出した場合、そのエラーがmarshal.go
の関数を通じて最終的にEncoder.Encode
メソッドの呼び出し元まで適切に伝播されるようになります。例えば、xmlns
属性の書き込み、文字列、バイトスライス、ブール値などのエスケープ処理中に発生した書き込みエラーが捕捉されるようになります。
これらの変更は、encoding/xml
パッケージがより堅牢になり、I/Oエラーを透過的に報告することで、開発者がXMLエンコーディングの失敗をより確実に検知し、対応できるようになることを意味します。
関連リンク
- Go CL (Change List): https://golang.org/cl/7085053
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/afde71cfbdb1032cce8fefc6bc665b816b3276e6
- Go言語の
io.Writer
インターフェースに関する一般的な情報 - XMLのエスケープ処理に関する一般的な情報
- Go言語のエラーハンドリングに関する一般的な情報
(注: コミットメッセージに記載されている Fixes #4112
についてWeb検索を行いましたが、GoLand IDEの関連性の低いイシューがヒットしました。このイシュー番号は、Goプロジェクトの公式イシュートラッカーにおける内部的な参照である可能性が高いです。)