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

[インデックス 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 スライス内の各テストケースをループ処理します。

  1. Marshal(tt.in): 各入力文字列 tt.injson.Marshal 関数に渡してJSONエンコードします。
  2. string(b) != tt.out || err != nil: エンコード結果のバイトスライス b を文字列に変換し、それが期待される出力 tt.out と一致するか、またはエラーが発生していないかをチェックします。
  3. t.Errorf(...): もし結果が期待と異なるか、エラーが発生した場合は、t.Errorf を使ってテスト失敗を報告します。これにより、どの入力でどのような不一致があったかが詳細に表示されます。

このテストの追加により、encoding/json パッケージが不正なUTF-8シーケンスを適切に処理し、常に有効なJSON(かつUTF-8)文字列を生成するという保証が強化されました。

関連リンク

参考にした情報源リンク

  • 上記の関連リンクに加えて、Go言語のソースコード内の encoding/json パッケージの実装(特に encode.godecode.go)を参照し、UTF-8の処理ロジックについて理解を深めました。
  • JSONの仕様(ECMA-404)も参照し、文字列エンコーディングに関する要件を確認しました。
  • 一般的なUTF-8エンコーディングの挙動と、不正なバイトシーケンスの処理に関する情報も参考にしました。