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

[インデックス 15710] ファイルの概要

このコミットは、Go言語の標準ライブラリであるencoding/xmlパッケージ内のバグ修正に関するものです。encoding/xmlパッケージは、XMLドキュメントのエンコード(Goのデータ構造からXMLへの変換)およびデコード(XMLからGoのデータ構造への変換)機能を提供します。

このコミットで変更された主要なファイルは以下の通りです。

  • src/pkg/encoding/xml/xml.go: encoding/xmlパッケージのXMLデコーダのコアロジックが含まれています。エンティティ解析に関連する部分が修正されました。
  • src/pkg/encoding/xml/xml_test.go: encoding/xmlパッケージのテストファイルです。今回の修正によって、エンティティ解析に関するテストケースが更新され、より正確なエラーメッセージが期待されるようになりました。

コミット

commit 019754ed4036f9ddc7514c78548fe77a606464db
Author: Russ Cox <rsc@golang.org>
Date:   Tue Mar 12 00:29:36 2013 -0400

    encoding/xml: fix spurious "no semicolon" in error
    
    Noticed while doing other XML investigations.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/7550045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/019754ed4036f9ddc7514c78548fe77a606464db

元コミット内容

encoding/xml: fix spurious "no semicolon" in error

Noticed while doing other XML investigations.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7550045

変更の背景

このコミットは、Go言語のencoding/xmlパッケージにおいて、XMLエンティティの解析時に発生する誤ったエラーメッセージ「no semicolon」(セミコロンがない)を修正するために行われました。

XMLドキュメントでは、特殊文字や予約語を表現するために「エンティティ」が使用されます。例えば、<&lt;と記述されます。これらのエンティティは通常、アンパサンド(&)で始まり、セミコロン(;)で終わる必要があります。

しかし、このバグが存在するバージョンでは、XMLデコーダが不正なエンティティを検出した際に、本来の原因とは異なる「セミコロンがない」という誤ったエラーメッセージを報告することがありました。これは、デコーダがエンティティ文字列を正しく抽出できていなかったために発生していました。具体的には、エンティティの開始位置から1バイトしか読み取っていなかったため、エンティティ全体を評価できず、結果として常にセミコロンの有無だけをチェックしてしまうという問題がありました。

この問題は、他のXML関連の調査中に発見され、開発者にとって誤解を招くエラーメッセージを修正する必要があるという認識から、このコミットが作成されました。

前提知識の解説

XMLエンティティ

XML(Extensible Markup Language)において、エンティティは特定の文字や文字列を表現するためのメカニズムです。これらは主に以下の目的で使用されます。

  1. 特殊文字の表現: XMLのマークアップ(例: <>&"')として解釈される文字を、データとして記述するために使用されます。例えば、<&lt;&&amp;と記述します。
  2. 数値文字参照: Unicode文字をその数値コードで直接参照する方法です。&#xNNNN;(16進数)または&#NNNN;(10進数)の形式で記述されます。例えば、ユーロ記号(€)は&#x20AC;または&#8364;と記述できます。
  3. 一般エンティティ: DTD(Document Type Definition)で定義された名前付きのエンティティです。例えば、&copy;のような著作権記号など。

エンティティは常にアンパサンド(&)で始まり、セミコロン(;)で終わるという構文規則があります。このコミットのバグは、このセミコロンのチェックに関連していました。

Go言語のencoding/xmlパッケージ

encoding/xmlは、Go言語の標準ライブラリの一部であり、XMLドキュメントとGoのデータ構造(構造体など)間の変換を扱うためのパッケージです。

  • xml.Decoder: XMLドキュメントを読み込み、Goのデータ構造にデコードするための主要な型です。XMLストリームを解析し、要素、属性、文字データ、コメント、処理命令などをトークンとして読み取ります。
  • xml.SyntaxError: XMLの構文エラーが発生した場合にxml.Decoderが返すエラー型です。このエラー型には、エラーメッセージ(Msgフィールド)やエラーが発生した位置(LineColumnフィールド)などの情報が含まれます。今回のコミットは、このMsgフィールドの内容が誤っていた問題を修正しています。

Go言語のスライス操作

Go言語では、スライスは配列の一部を参照するための強力な機能です。このコミットの修正では、スライスの基本的な操作が関わっています。

  • d.buf.Bytes(): これは[]byte型のバイトスライスを返します。
  • [before]: これはスライスd.buf.Bytes()beforeインデックスにある単一のバイトを返します。
  • [before:]: これはスライスd.buf.Bytes()beforeインデックスからスライスの最後までの新しいスライスを返します。

この違いが、エンティティ文字列を正しく抽出できるかどうかの鍵となります。

技術的詳細

このコミットの技術的な核心は、src/pkg/encoding/xml/xml.goファイル内のXMLデコーダがエンティティを解析する際のバイトスライスの取り扱いを修正した点にあります。

xml.goの修正

修正前のコードでは、エンティティの開始位置を示すbeforeインデックスを使用して、エンティティ文字列をent := string(d.buf.Bytes()[before])のように抽出していました。ここでd.buf.Bytes()[before]は、d.buf.Bytes()スライスのbeforeインデックスにある単一のバイトを意味します。これをstring()にキャストすると、その単一バイトに対応する文字(UTF-8エンコーディングの場合)が文字列としてentに格納されます。

このため、デコーダはエンティティ全体(例: &amp;)ではなく、エンティティの最初の文字(例: &)しかent変数に格納していませんでした。その結果、続くif ent[len(ent)-1] != ';'というセミコロンのチェックは、常に1文字のentに対して行われることになり、エンティティがセミコロンで終わっていなくても、entが1文字であるためent[len(ent)-1]は常にその1文字目を指し、それがセミコロンでない限り「セミコロンがない」という誤った結論を導き出してしまっていました。

修正後のコードでは、ent := string(d.buf.Bytes()[before:])に変更されました。ここでd.buf.Bytes()[before:]は、d.buf.Bytes()スライスのbeforeインデックスからスライスの最後までのサブスライスを意味します。これにより、ent変数にはエンティティの開始位置から現在読み込んでいるバッファの末尾までの完全なエンティティ文字列が格納されるようになります。

この修正により、if ent[len(ent)-1] != ';'というチェックが、エンティティ全体に対して正しく行われるようになり、エンティティが実際にセミコロンで終わっていない場合にのみ「セミコロンがない」というエラーが報告されるようになりました。また、エンティティの構文が根本的に間違っている場合(例: &abc\x01;のように不正な文字が含まれる場合)には、より適切なエラーメッセージが生成されるようになります。

xml_test.goの修正

テストファイルxml_test.goでは、主に以下の変更が行われました。

  1. 不要なコメントの削除: 以前のコミット(issue 1259)に関連すると思われる、もはや適切ではないコメントブロックが削除されました。これはコードのクリーンアップと、現在の動作を正確に反映させるためのものです。
  2. characterTestsの更新:
    • 新しいテストケースが追加されました。例えば、{"<doc>&abc\x01;</doc>", "invalid character entity &abc (no semicolon)"}は、不正な文字を含むエンティティがどのように扱われるかを示しています。
    • 既存のテストケースのエラーメッセージが修正されました。特に、{"<doc>&\xef\xbf\xbe;</doc>", "invalid character entity & (no semicolon)"}{"<doc>&\xef\xbf\xbe;</doc>", "invalid character entity &\uFFFE;"}に変更されています。これは、修正によってデコーダがより正確なエンティティ文字列を認識し、それに応じたエラーメッセージを生成できるようになったことを反映しています。\uFFFEはUnicodeのU+FFFE文字を表し、これは通常、不正な文字として扱われます。
    • {"<doc>&hello;</doc>", "invalid character entity &hello;"}のような、セミコロンがないが、エンティティ名自体が不正なケースも追加され、より堅牢なテストカバレッジが提供されています。
  3. t.Fatalfのフォーマット変更: TestDisallowedCharacters関数内で、テスト失敗時のメッセージ出力フォーマットがwant '%s', got '%s'からwant %q, got %qに変更されました。%qはGoのフォーマット動詞で、文字列を引用符で囲んで出力するため、特に空白文字や非表示文字を含む文字列のエラーメッセージをデバッグする際に、より視覚的に分かりやすくなります。これは機能的な変更ではなく、テストの可読性とデバッグのしやすさを向上させるための改善です。

これらのテストの変更は、xml.goの修正が意図した通りに動作し、XMLエンティティの解析におけるエラー報告が正確になったことを検証するために不可欠です。

コアとなるコードの変更箇所

src/pkg/encoding/xml/xml.go

--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -956,7 +956,7 @@ Input:
 			b0, b1 = 0, 0
 			continue Input
 		}
-		ent := string(d.buf.Bytes()[before])
+		ent := string(d.buf.Bytes()[before:])
 		if ent[len(ent)-1] != ';' {
 			ent += " (no semicolon)"
 		}

src/pkg/encoding/xml/xml_test.go

--- a/src/pkg/encoding/xml/xml_test.go
+++ b/src/pkg/encoding/xml/xml_test.go
@@ -595,13 +595,6 @@ func TestEntityInsideCDATA(t *testing.T) {
 	}
 }
 
-// The last three tests (respectively one for characters in attribute
-// names and two for character entities) pass not because of code
-// changed for issue 1259, but instead pass with the given messages
-// from other parts of xml.Decoder.  I provide these to note the
-// current behavior of situations where one might think that character
-// range checking would detect the error, but it does not in fact.
-//
 var characterTests = []struct {
 	in  string
 	err string
@@ -611,8 +604,10 @@ var characterTests = []struct {
 	{"\xef\xbf\xbe<doc/>", "illegal character code U+FFFE"},
 	{"<?xml version=\"1.0\"?><doc>\r\n<hiya/>\x07<toots/></doc>", "illegal character code U+0007"},
 	{"<?xml version=\"1.0\"?><doc \x12='value'>what's up</doc>", "expected attribute name in element"},
+	{"<doc>&abc\x01;</doc>", "invalid character entity &abc (no semicolon)"},
 	{"<doc>&\x01;</doc>", "invalid character entity & (no semicolon)"},
-	{"<doc>&\xef\xbf\xbe;</doc>", "invalid character entity & (no semicolon)"},
+	{"<doc>&\xef\xbf\xbe;</doc>", "invalid character entity &\uFFFE;"},
+	{"<doc>&hello;</doc>", "invalid character entity &hello;"},
 }
 
 func TestDisallowedCharacters(t *testing.T) {
@@ -629,7 +624,7 @@ func TestDisallowedCharacters(t *testing.T) {
 			t.Fatalf("input %d d.Token() = _, %v, want _, *SyntaxError", i, err)
 		}
 		if synerr.Msg != tt.err {
-			t.Fatalf("input %d synerr.Msg wrong: want '%s', got '%s'", i, tt.err, synerr.Msg)
+			t.Fatalf("input %d synerr.Msg wrong: want %q, got %q", i, tt.err, synerr.Msg)
 		}
 	}
 }

コアとなるコードの解説

src/pkg/encoding/xml/xml.goの変更

-		ent := string(d.buf.Bytes()[before])
+		ent := string(d.buf.Bytes()[before:])

この変更がこのコミットの最も重要な部分です。

  • 修正前 (d.buf.Bytes()[before]): d.buf.Bytes()は、XMLデコーダが現在処理しているバイトバッファ全体を表す[]byteスライスです。[before]は、このスライス内のbeforeというインデックス位置にある単一のバイトを指します。したがって、string(d.buf.Bytes()[before])は、エンティティの開始位置にある最初の1バイトのみを文字列としてent変数に格納していました。 例えば、XML入力が<doc>&amp;</doc>で、before&の開始位置を指している場合、entには"&"という1文字の文字列しか入りませんでした。

  • 修正後 (d.buf.Bytes()[before:]): [before:]は、d.buf.Bytes()スライスのbeforeインデックスからスライスの最後までのすべてのバイトを含む新しいサブスライスを作成します。したがって、string(d.buf.Bytes()[before:])は、エンティティの開始位置から現在のバッファの末尾までの完全なエンティティ文字列ent変数に格納します。 上記の例では、entには"&amp;"という文字列全体が格納されるようになります。

この修正により、その後のif ent[len(ent)-1] != ';'というセミコロンのチェックが、エンティティ全体に対して正しく行われるようになり、誤った「no semicolon」エラーが解消されました。エンティティが実際にセミコロンで終わっていない場合や、エンティティ内に不正な文字が含まれる場合に、より正確なエラーメッセージが報告されるようになります。

src/pkg/encoding/xml/xml_test.goの変更

  1. コメントの削除: 以前のコードには、特定のテストがなぜパスするのか、そして文字範囲チェックがなぜエラーを検出しないのかについての説明コメントがありました。このコメントは、おそらく過去のバグ(issue 1259)に関連するものでしたが、今回の修正によってデコーダの動作が改善されたため、もはや適切ではないと判断され削除されました。これにより、コードベースの関連性が保たれ、誤解を招く情報がなくなりました。

  2. characterTestsスライスの更新:

    • {"<doc>&abc\x01;</doc>", "invalid character entity &abc (no semicolon)"}, この新しいテストケースは、エンティティ名に不正な文字(\x01は制御文字)が含まれている場合の挙動をテストします。修正前は誤ったエラーが出た可能性がありましたが、修正後は&abcが正しく認識され、その後にセミコロンがないため「no semicolon」というエラーが期待されます。
    • {"<doc>&\xef\xbf\xbe;</doc>", "invalid character entity & (no semicolon)"}, から {"<doc>&\xef\xbf\xbe;</doc>", "invalid character entity &\uFFFE;"} への変更 \xef\xbf\xbeはUTF-8でU+FFFE文字を表します。これはXMLで許可されていない文字です。修正前は、エンティティが1文字しか認識されなかったため、&の後にセミコロンがないという誤ったエラーが出ていました。修正後は、デコーダが&\xef\xbf\xbe;全体を認識し、その中に不正な文字\uFFFEが含まれていることを正確に報告するようになりました。これにより、より具体的で有用なエラーメッセージが提供されます。
    • {"<doc>&hello;</doc>", "invalid character entity &hello;"} このテストケースは、セミコロンがないが、エンティティ名自体がXMLの定義済みエンティティではない場合の挙動をテストします。修正前は「no semicolon」が誤って付加されたかもしれませんが、修正後はエンティティ名が正しく認識され、それが不正なエンティティであることを示すエラーメッセージが期待されます。
  3. t.Fatalfのフォーマット変更:

    -			t.Fatalf("input %d synerr.Msg wrong: want '%s', got '%s'", i, tt.err, synerr.Msg)
    +			t.Fatalf("input %d synerr.Msg wrong: want %q, got %q", i, tt.err, synerr.Msg)
    

    これは、テストが失敗した際に表示されるエラーメッセージのフォーマットを変更するものです。%sは文字列をそのまま出力しますが、%qは文字列をGoの文字列リテラル形式(引用符で囲み、特殊文字をエスケープ)で出力します。これにより、エラーメッセージに空白文字や制御文字が含まれる場合でも、その内容がより明確に表示され、デバッグが容易になります。機能的な変更ではなく、テストの診断性を向上させるための改善です。

これらのテストの変更は、xml.goのコアロジックの修正が、XMLエンティティの解析とエラー報告の正確性をどのように向上させたかを具体的に示しています。

関連リンク

  • Go言語のencoding/xmlパッケージのドキュメント: https://pkg.go.dev/encoding/xml
  • Go言語のIssueトラッカー (関連する可能性のあるIssue): https://github.com/golang/go/issues (このコミットメッセージに記載されているhttps://golang.org/cl/7550045は、GoプロジェクトのGerritコードレビューシステムへのリンクであり、現在はGitHubのコミットページにリダイレクトされるか、関連するIssueに紐付けられている可能性があります。)

参考にした情報源リンク