[インデックス 16760] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/json
パッケージにおけるUTF-8強制(coercion)に関するテストを追加するものです。具体的には、不正なUTF-8シーケンスを含む文字列がJSONエンコードされる際に、どのように処理されるかを確認するためのテストケースが拡充されています。
コミット
commit 4274d074dcf06fc67318d11962994ea19b2aff6b
Author: Russ Cox <rsc@golang.org>
Date: Fri Jul 12 20:40:50 2013 -0400
encoding/json: add more tests for UTF-8 coercion
Suggested by Rob in CL 11211045, but the mail arrived
moments after hg submit completed.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/11138045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4274d074dcf06fc67318d11962994ea19b2aff6b
元コミット内容
encoding/json: add more tests for UTF-8 coercion
Suggested by Rob in CL 11211045, but the mail arrived
moments after hg submit completed.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/11138045
変更の背景
この変更は、encoding/json
パッケージが不正なUTF-8シーケンスを含む文字列をJSONとしてエンコードする際の挙動をより堅牢にテストするために行われました。元のコミットメッセージにあるように、Rob Pike氏(Go言語の共同開発者の一人)からの提案が背景にあります。提案はコミットが完了した直後に届いたため、この追加のコミットで対応されました。
JSONは文字列をUnicodeで表現することを要求しており、通常はUTF-8エンコーディングが使用されます。しかし、入力データが厳密なUTF-8形式に従っていない場合、JSONエンコーダがどのように振る舞うかは重要な問題です。Goの encoding/json
パッケージは、不正なUTF-8バイトシーケンスをUnicodeの「Replacement Character」(U+FFFD)に置き換えることで、常に有効なUTF-8文字列を出力するように設計されています。このコミットは、この「UTF-8強制(coercion)」の挙動を、より多様な不正な入力パターンで検証するためのテストを追加しています。
前提知識の解説
UTF-8
UTF-8(Unicode Transformation Format - 8-bit)は、Unicode文字を可変長バイトシーケンスでエンコードする文字エンコーディング方式です。ASCII互換性があり、世界中のほとんどの文字を表現できます。UTF-8の大きな特徴は、1バイトから4バイトまでの可変長で文字を表現する点と、不正なバイトシーケンスを検出できる自己同期性を持つ点です。
JSON (JavaScript Object Notation)
JSONは、人間が読み書きしやすく、機械が解析しやすいデータ交換フォーマットです。ECMA-404 JSON Data Interchange Format Standardで定義されており、文字列はUnicodeで表現されることが規定されています。JSON文字列は通常、UTF-8でエンコードされます。
Unicode Replacement Character (U+FFFD)
UnicodeのU+FFFDは「Replacement Character」と呼ばれ、エンコーディングエラーや、受信したデータが有効な文字として解釈できない場合に、その不正な文字の代わりに表示される特殊な文字です。Goの encoding/json
パッケージでは、入力文字列に不正なUTF-8シーケンスが含まれている場合、このU+FFFDに置き換えてJSON文字列を生成します。これにより、出力されるJSONは常に有効なUTF-8文字列となり、JSONの仕様に準拠します。
Go言語の encoding/json
パッケージ
Go言語の標準ライブラリである encoding/json
パッケージは、Goのデータ構造とJSONデータの間でエンコード(Marshal)およびデコード(Unmarshal)を行う機能を提供します。このパッケージは、JSONの仕様に厳密に従うように設計されており、特に文字列のエンコーディングに関しては、前述のU+FFFDによる不正なUTF-8の強制処理が行われます。
技術的詳細
このコミットは、src/pkg/encoding/json/decode_test.go
ファイルに TestMarshalBadUTF8
という新しいテストケースを追加しています。このテストは、badUTF8
という構造体のスライスを定義し、様々な不正なUTF-8シーケンスを含む入力文字列 (in
) と、それらが json.Marshal
によってエンコードされた場合の期待される出力文字列 (out
) のペアを格納しています。
テストケースの例:
{"hello\\xffworld",
"hello\ufffdworld"}
:\xff
(不正なバイト) が\ufffd
に置き換えられる。{"\\xff",
"\ufffd"}
: 単一の不正なバイト。{"\\xff\\xff",
"\ufffd\ufffd"}
: 連続する不正なバイト。{"a\\xffb",
"a\ufffdb"}
: 文字列の途中の不正なバイト。{"\\xe6\\x97\\xa5\\xe6\\x9c\\xac\\xff\\xaa\\x9e",
"日本\ufffd\ufffd\ufffd"}
: 有効な日本語文字(日本)の後に不正なバイトが続くケース。この例では、\xff\xaa\x9e
の3バイトがそれぞれ\ufffd
に置き換えられています。これは、UTF-8のマルチバイト文字の途中で不正なバイトが挿入された場合でも、残りの有効な部分が正しく処理され、不正な部分のみが置換されることを示しています。
TestMarshalBadUTF8
関数は、この badUTF8
スライスをイテレートし、各入力文字列に対して json.Marshal
を呼び出し、その結果が期待される出力と一致するかどうかを検証します。これにより、encoding/json
パッケージが不正なUTF-8入力をどのように処理し、常に有効なJSON文字列(かつ有効なUTF-8文字列)を生成するかを保証します。
コアとなるコードの変更箇所
src/pkg/encoding/json/decode_test.go
ファイルの以下の部分が変更されました。
--- a/src/pkg/encoding/json/decode_test.go
+++ b/src/pkg/encoding/json/decode_test.go
@@ -391,12 +391,23 @@ func TestMarshal(t *testing.T) {
}
}
+var badUTF8 = []struct {
+\tin, out string
+}{
+\t{"hello\\xffworld", `\"hello\\ufffdworld\"`},\n\t{\"\", `\"\"`},\
+\t{\"\\xff\", `\"\\ufffd\"`},\
+\t{\"\\xff\\xff\", `\"\\ufffd\\ufffd\"`},\
+\t{\"a\\xffb\", `\"a\\ufffdb\"`},\
+\t{\"\\xe6\\x97\\xa5\\xe6\\x9c\\xac\\xff\\xaa\\x9e\", `\"日本\\ufffd\\ufffd\\ufffd\"`},\
+}\n+\n func TestMarshalBadUTF8(t *testing.T) {
-\ts := "hello\\xffworld"\n-\tconst enc = `\"hello\\ufffdworld\"`\n-\tb, err := Marshal(s)\n-\tif string(b) != enc || err != nil {\n-\t\tt.Errorf(\"Marshal(%q) = %#q, %v, want %#q, nil\", s, b, err, enc)\n+\tfor _, tt := range badUTF8 {\n+\t\tb, err := Marshal(tt.in)\n+\t\tif string(b) != tt.out || err != nil {\n+\t\t\tt.Errorf(\"Marshal(%q) = %#q, %v, want %#q, nil\", tt.in, b, err, tt.out)\n+\t\t}\n \t}\n }
コアとなるコードの解説
badUTF8
変数
var badUTF8 = []struct {
in, out string
}{
{"hello\\xffworld", `\"hello\\ufffdworld\"`},
{"", `\"\"`},
{"\\xff", `\"\\ufffd\"`},
{"\\xff\\xff", `\"\\ufffd\\ufffd\"`},
{"a\\xffb", `\"a\\ufffdb\"`},
{"\\xe6\\x97\\xa5\\xe6\\x9c\\xac\\xff\\xaa\\x9e\", `\"日本\\ufffd\\ufffd\\ufffd\"`},
}
この構造体のスライスは、テストケースのデータを提供します。各要素は、入力文字列 (in
) と、その入力が json.Marshal
によって処理された場合に期待されるJSONエンコードされた出力文字列 (out
) のペアです。
\xff
: これは単一の不正なバイト(UTF-8の範囲外)を表します。\xe6\x97\xa5\xe6\x9c\xac
: これはUTF-8でエンコードされた「日本」という文字です。\ufffd
: これはUnicodeのReplacement Character(U+FFFD)のエスケープシーケンスです。JSON文字列内で不正なUTF-8バイトが検出された場合、Goのencoding/json
パッケージはこれを\ufffd
に置き換えます。
TestMarshalBadUTF8
関数
func TestMarshalBadUTF8(t *testing.T) {
for _, tt := range badUTF8 {
b, err := Marshal(tt.in)
if string(b) != tt.out || err != nil {
t.Errorf("Marshal(%q) = %#q, %v, want %#q, nil", tt.in, b, err, tt.out)
}
}
}
このテスト関数は、badUTF8
スライス内の各テストケースをループ処理します。
Marshal(tt.in)
: 各入力文字列tt.in
をjson.Marshal
関数に渡してJSONエンコードします。string(b) != tt.out || err != nil
: エンコード結果のバイトスライスb
を文字列に変換し、それが期待される出力tt.out
と一致するか、またはエラーが発生していないかをチェックします。t.Errorf(...)
: もし結果が期待と異なるか、エラーが発生した場合は、t.Errorf
を使ってテスト失敗を報告します。これにより、どの入力でどのような不一致があったかが詳細に表示されます。
このテストの追加により、encoding/json
パッケージが不正なUTF-8シーケンスを適切に処理し、常に有効なJSON(かつUTF-8)文字列を生成するという保証が強化されました。
関連リンク
- Go言語の
encoding/json
パッケージのドキュメント: https://pkg.go.dev/encoding/json - Unicode Replacement Character (U+FFFD) について: https://en.wikipedia.org/wiki/Replacement_character
- UTF-8について: https://ja.wikipedia.org/wiki/UTF-8
- JSONについて: https://www.json.org/json-ja.html
参考にした情報源リンク
- 上記の関連リンクに加えて、Go言語のソースコード内の
encoding/json
パッケージの実装(特にencode.go
やdecode.go
)を参照し、UTF-8の処理ロジックについて理解を深めました。 - JSONの仕様(ECMA-404)も参照し、文字列エンコーディングに関する要件を確認しました。
- 一般的なUTF-8エンコーディングの挙動と、不正なバイトシーケンスの処理に関する情報も参考にしました。