[インデックス 17992] ファイルの概要
このコミットは、Go言語のunicode/utf16
パッケージにおいて、無効なUTF-16サロゲートペアのデコードに関する明示的なテストを追加するものです。これにより、既存のDecodeRune
関数のテストカバレッジが向上し、特に不正な入力に対する挙動が正しく処理されることを保証します。
コミット
commit 62baae6e57ca9271fc9a4269958d474aa398cc00
Author: Dave Cheney <dave@cheney.net>
Date: Mon Dec 16 12:35:25 2013 +1100
unicode/utf16: add explicit test for decoding invalid runes.
The EncodeRune test exercises DecodeRune, but only for runes that it can encode. Add an explicit test for invalid utf16 surrogate pairs.
Bonus: coverage is now 100%
unicode/utf16/utf16.go: IsSurrogate 100.0%
unicode/utf16/utf16.go: DecodeRune 100.0%
unicode/utf16/utf16.go: EncodeRune 100.0%
unicode/utf16/utf16.go: Encode 100.0%
unicode/utf16/utf16.go: Decode 100.0%
total: (statements) 100.0%
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/39150044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/62baae6e57ca9271fc9a4269958d474aa398cc00
元コミット内容
このコミットの目的は、unicode/utf16
パッケージのDecodeRune
関数に対して、無効なUTF-16サロゲートペアをデコードする際の挙動を明示的にテストすることです。既存のEncodeRune
テストはDecodeRune
も間接的にテストしていましたが、それはエンコード可能な有効なルーンに限定されていました。この変更により、無効なサロゲートペアが入力された場合に、適切にUnicodeの置換文字(U+FFFD)が返されることを確認するテストが追加されました。結果として、unicode/utf16/utf16.go
内の関連関数のテストカバレッジが100%に達しました。
変更の背景
Go言語の標準ライブラリにおけるunicode/utf16
パッケージは、UTF-16エンコーディングとデコーディングを扱うための機能を提供します。UTF-16は可変長エンコーディングであり、基本多言語面(BMP)の文字は16ビットの単一コードユニットで表現されますが、それ以外の文字(サロゲートペア)は2つの16ビットコードユニット(上位サロゲートと下位サロゲート)のペアで表現されます。
このコミットが行われた背景には、DecodeRune
関数が、有効なサロゲートペアだけでなく、不正な組み合わせのサロゲートペア(例えば、上位サロゲートの後に下位サロゲートではない値が続く場合)をどのように処理するかという点に対するテストの不足がありました。堅牢なソフトウェアにおいては、予期せぬ不正な入力に対しても、定義された適切な挙動(この場合はUnicodeの置換文字U+FFFDを返すこと)を示すことが重要です。
既存のテストは、主に有効なエンコード/デコードパスを検証していましたが、不正な入力に対するエッジケースのテストが不足していたため、このコミットでそのギャップを埋めることが目的とされました。テストカバレッジの向上は、コードの品質と信頼性を高める上で重要な指標となります。
前提知識の解説
UnicodeとUTF-16
- Unicode: 世界中の文字を統一的に扱うための文字コード標準です。各文字には一意の「コードポイント」(整数値)が割り当てられています。
- ルーン (Rune): Go言語では、Unicodeのコードポイントを表現するために
rune
型(int32
のエイリアス)を使用します。 - UTF-16: Unicodeの文字をバイト列にエンコードするための方式の一つです。
- 基本多言語面 (BMP): UnicodeのU+0000からU+FFFFまでの範囲の文字を指します。これらの文字はUTF-16では1つの16ビットコードユニットで表現されます。
- サロゲートペア: BMP外の文字(U+10000からU+10FFFFまでの範囲)を表現するために使用されます。これらは2つの16ビットコードユニットのペアで構成されます。
- 上位サロゲート (High Surrogate): U+D800からU+DBFFまでの範囲のコードユニット。
- 下位サロゲート (Low Surrogate): U+DC00からU+DFFFまでの範囲のコードユニット。
- 有効なサロゲートペアは、必ず上位サロゲートの後に下位サロゲートが続く形式を取ります。
Unicodeの置換文字 (U+FFFD)
Unicodeの置換文字(REPLACEMENT CHARACTER, U+FFFD)は、文字コード変換中にエラーが発生した場合や、不正なバイトシーケンスが検出された場合に使用される特殊な文字です。これは、元の文字を正確に表現できない場合に、その場所を占める「不明な文字」を示すために用いられます。UTF-16のデコードにおいて、不正なサロゲートペア(例えば、上位サロゲートの後に下位サロゲートではない値が続く場合や、下位サロゲートが単独で現れる場合など)が検出された際には、このU+FFFDを返すのが一般的な慣習であり、堅牢な実装の証とされます。
テストカバレッジ
テストカバレッジとは、ソフトウェアのテストがどれだけコードを網羅しているかを示す指標です。100%のカバレッジは、すべてのステートメント、ブランチ、関数などがテストによって実行されたことを意味しますが、これは必ずしもすべてのバグがないことを保証するものではありません。しかし、カバレッジが高いほど、コードの品質と信頼性が高い傾向にあります。このコミットでは、特にエッジケース(不正な入力)に対するテストを追加することで、カバレッジを向上させています。
技術的詳細
このコミットは、src/pkg/unicode/utf16/utf16_test.go
ファイルに新しいテストケースを追加することで、DecodeRune
関数の堅牢性を高めています。
DecodeRune(r1, r2 rune) rune
関数は、2つのUTF-16コードユニットr1
とr2
を受け取り、それらが構成するUnicodeルーンを返します。もしr1
とr2
が有効なサロゲートペアを形成しない場合、この関数はUnicodeの置換文字U+FFFDを返すことが期待されます。
追加されたテストケース{0xd800, 'a', 0xfffd}
は、この挙動を明示的に検証します。
0xd800
は上位サロゲートの範囲(U+D800からU+DBFF)に属します。'a'
(ASCII文字 'a' のルーン値、U+0061)は下位サロゲートの範囲(U+DC00からU+DFFF)に属しません。 したがって、0xd800
と'a'
の組み合わせは不正なサロゲートペアであり、DecodeRune
は0xfffd
(置換文字)を返す必要があります。
このテストの追加により、DecodeRune
関数が不正な入力に対して適切にフォールバックし、予期せぬクラッシュや誤った文字の解釈を防ぐことが保証されます。これは、特に外部からの入力(ファイル、ネットワークなど)を扱うシステムにおいて、文字エンコーディングの堅牢性を確保するために非常に重要です。
コアとなるコードの変更箇所
変更はsrc/pkg/unicode/utf16/utf16_test.go
ファイルにのみ行われています。
--- a/src/pkg/unicode/utf16/utf16_test.go
+++ b/src/pkg/unicode/utf16/utf16_test.go
@@ -100,6 +100,26 @@ func TestDecode(t *testing.T) {
}\n
}\n
\n
+var decodeRuneTests = []struct {\n
+\tr1, r2 rune\n
+\twant rune\n
+}{\n
+\t{0xd800, 0xdc00, 0x10000},\n
+\t{0xd800, 0xdc01, 0x10001},\n
+\t{0xd808, 0xdf45, 0x12345},\n
+\t{0xdbff, 0xdfff, 0x10ffff},\n
+\t{0xd800, 'a', 0xfffd}, // illegal, replacement rune substituted\n
+}\n
+\n
+func TestDecodeRune(t *testing.T) {\n
+\tfor i, tt := range decodeRuneTests {\n
+\t\tgot := DecodeRune(tt.r1, tt.r2)\n
+\t\tif got != tt.want {\n
+\t\t\tt.Errorf("%d: DecodeRune(%q, %q) = %v; want %v", i, tt.r1, tt.r2, got, tt.want)\n
+\t\t}\n
+\t}\n
+}\n
+\n
var surrogateTests = []struct {\n
\tr rune\n
\twant bool\n
コアとなるコードの解説
このコミットで追加された主要なコードは、decodeRuneTests
という構造体のスライスと、それを利用するTestDecodeRune
関数です。
-
decodeRuneTests
スライス:- これは、
DecodeRune
関数のテストケースを定義する匿名構造体のスライスです。 - 各要素は、入力となる2つのルーン
r1
,r2
と、期待される結果のルーンwant
を持ちます。 - 最初の4つのテストケースは、有効なサロゲートペアが正しくデコードされることを確認します。例えば、
{0xd800, 0xdc00, 0x10000}
は、上位サロゲート0xd800
と下位サロゲート0xdc00
が組み合わさって、UnicodeコードポイントU+10000(𐀀, Linear B Syllable B008 A)になることをテストしています。 - 重要な追加:
{0xd800, 'a', 0xfffd}
というテストケースが追加されました。0xd800
は上位サロゲートです。'a'
は下位サロゲートの範囲外の文字です。- この組み合わせは不正なサロゲートペアであるため、
DecodeRune
はUnicodeの置換文字0xfffd
を返すことが期待されます。このテストケースが、このコミットの核心的な変更点です。
- これは、
-
TestDecodeRune
関数:- Goのテストフレームワーク(
testing
パッケージ)に準拠したテスト関数です。 decodeRuneTests
スライスをループで反復処理します。- 各テストケースについて、
DecodeRune(tt.r1, tt.r2)
を呼び出し、実際の戻り値got
を取得します。 got
が期待される値tt.want
と異なる場合、t.Errorf
を呼び出してテスト失敗を報告します。これにより、どのテストケースで、どのような入力に対して、どのような結果が得られ、何が期待されていたかが詳細にログに出力されます。
- Goのテストフレームワーク(
この追加により、DecodeRune
関数が有効なサロゲートペアだけでなく、不正なサロゲートペアに対しても、仕様通りにUnicodeの置換文字を返すという堅牢な挙動を保証できるようになりました。
関連リンク
- Go言語の
unicode/utf16
パッケージのドキュメント: https://pkg.go.dev/unicode/utf16 (コミット当時のバージョンとは異なる可能性がありますが、現在のドキュメントも参考になります) - Unicode Consortium: https://www.unicode.org/
- UTF-16に関するWikipediaの記事: https://ja.wikipedia.org/wiki/UTF-16
- Unicodeの置換文字 (U+FFFD) に関するWikipediaの記事: https://ja.wikipedia.org/wiki/%E7%BD%AE%E6%8F%9B%E6%96%87%E5%AD%97
参考にした情報源リンク
- Go言語のコードレビューシステム (Gerrit) の該当コミット: https://golang.org/cl/39150044 (現在はGitHubに移行しているため、直接アクセスしてもリダイレクトされるか、情報が見つからない場合がありますが、コミットメッセージに記載されている元のリンクです)
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- UTF-16に関する一般的な情報源 (例: MDN Web Docs, 各種プログラミング言語のドキュメント)
- Unicodeのサロゲートペアに関する一般的な情報源 (例: Unicode標準の関連セクション)
- テストカバレッジに関する一般的な情報源 (例: Wikipedia, ソフトウェアテストに関する書籍や記事)