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

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

このコミットは、Go言語のテストスイート test/string_lit.go に、[]rune スライスから string への変換に関する新しいテストケースを追加するものです。特に、無効なUnicodeコードポイントや範囲外のコードポイントを含む []rune スライスが string に変換された際の挙動を検証することを目的としています。

コミット

commit 936665f6414f0fa0e416f734bbe46571f1fc65ac
Author: Ian Lance Taylor <iant@golang.org>
Date:   Thu Dec 12 17:17:02 2013 -0800

    test: add []rune case to string_lit.go
    
    Gccgo managed to get this case wrong.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/41490044

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

https://github.com/golang/go/commit/936665f6414f0fa0e416f734bbe46571f1fc65ac

元コミット内容

test: add []rune case to string_lit.go

Gccgo managed to get this case wrong.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/41490044

変更の背景

このコミットの背景には、Go言語のコンパイラの一つである gccgo が、特定の []rune から string への変換ケースで誤った挙動を示したという問題があります。Go言語において、string 型はUTF-8エンコードされたバイト列として内部的に表現されますが、rune 型はUnicodeコードポイントを表す int32 のエイリアスです。[]rune から string への変換は、これらのUnicodeコードポイントをUTF-8バイト列にエンコードする処理を含みます。

gccgo は、Go言語のフロントエンドをGCCに統合したものであり、Goの標準コンパイラである gc とは異なる実装を持っています。異なるコンパイラ実装が存在する場合、言語仕様の解釈や最適化の過程で、標準コンパイラとは異なるバグや挙動の差異が生じることがあります。

この特定のケースでは、0x10ffff を超える値(Unicodeの最大コードポイントはU+10FFFF)、サロゲートペアの範囲(U+D800からU+DFFF)、および負の値といった、無効または特殊な rune 値を含むスライスを string に変換する際に、gccgo が期待されるUTF-8エンコーディングを生成できなかったと考えられます。

このバグを特定し、将来的な回帰を防ぐために、このコミットでは test/string_lit.go に新しいテストケースが追加されました。これにより、gccgo だけでなく、他のGoコンパイラやランタイムが []rune から string への変換を正しく処理することを確認できます。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念とUnicodeに関する知識が必要です。

  1. Go言語の string:

    • Goの string は、不変なバイトスライスであり、通常はUTF-8エンコードされたテキストを表します。
    • string はバイトのシーケンスであり、文字のシーケンスではありません。
    • 文字列リテラルはデフォルトでUTF-8エンコードされます。
  2. Go言語の rune:

    • runeint32 のエイリアスであり、単一のUnicodeコードポイントを表します。
    • Goのソースコードでは、シングルクォートで囲まれた文字リテラル(例: 'A', '世')は rune 型です。
    • stringrange ループでイテレートすると、各要素は rune 型として取得されます。
  3. []rune から string への変換:

    • []rune 型の値を string() にキャストすると、スライス内の各 rune(Unicodeコードポイント)がUTF-8バイト列にエンコードされ、それらが連結されて新しい string が生成されます。
    • Go言語の仕様では、無効なUnicodeコードポイント(例: U+10FFFFを超える値、サロゲートペアの範囲内の値、負の値)が string に変換される場合、それらはU+FFFD (REPLACEMENT CHARACTER) に置き換えられ、そのUTF-8エンコーディングである \xef\xbf\xbd が生成されることになっています。
  4. UnicodeとUTF-8:

    • Unicode: 世界中の文字を統一的に扱うための文字コード体系です。各文字には一意の「コードポイント」(数値)が割り当てられています。
    • UTF-8: Unicodeコードポイントをバイト列にエンコードするための可変長エンコーディング方式です。ASCII文字は1バイト、他の文字は2〜4バイトで表現されます。UTF-8は自己同期性があり、前方互換性も高いため、Webや多くのシステムで広く利用されています。
    • サロゲートペア: UTF-16エンコーディングにおいて、U+FFFFを超えるコードポイントを表現するために2つの16ビット値(上位サロゲートと下位サロゲート)を組み合わせる仕組みです。Unicodeの仕様では、U+D800からU+DFFFの範囲はサロゲートペアのために予約されており、単独で有効なコードポイントとしては扱われません。
  5. gccgo:

    • Go言語のコンパイラの一つで、GCC (GNU Compiler Collection) のフロントエンドとして実装されています。Goの標準コンパイラである gc とは異なるコード生成パスを持ちます。

これらの知識が、[]rune から string への変換における gccgo のバグと、それを修正・検証するためのテストの重要性を理解する上で不可欠です。

技術的詳細

このコミットで追加されたテストケースは、[]rune から string への変換におけるGo言語の厳密な仕様準拠を検証します。特に、以下の3種類の「不正な」rune 値がどのように処理されるかに焦点を当てています。

  1. 0x10ffff (U+10FFFF): これはUnicodeの最大コードポイントです。この値自体は有効なUnicodeコードポイントであり、UTF-8では4バイトでエンコードされます (\xf4\x8f\xbf\xbf)。テストでは、この値が正しくエンコードされることを確認します。

  2. 0x10ffff + 1 (U+110000): これはUnicodeの有効な範囲(U+0000からU+10FFFF)を超えるコードポイントです。Go言語の仕様では、このような無効なコードポイントはU+FFFD (REPLACEMENT CHARACTER) に置き換えられ、そのUTF-8エンコーディングである \xef\xbf\xbd が生成されるべきです。

  3. 0xD800 および 0xDFFF: これらはUnicodeのサロゲートペアの範囲(U+D800からU+DFFF)に属するコードポイントです。これらの値は単独では有効なUnicodeコードポイントではなく、UTF-16エンコーディングで補助平面の文字を表すために使用されます。Go言語の string() 変換においては、これらも無効なコードポイントとして扱われ、U+FFFDに置き換えられるべきです。

  4. -1: 負の値も有効なUnicodeコードポイントではありません。これもU+FFFDに置き換えられるべきです。

テストケース rs := []rune{0x10ffff, 0x10ffff + 1, 0xD800, 0xDFFF, -1} は、これらの特殊な rune 値を含むスライスを定義しています。 そして、s = string(rs) でこの []rune スライスを string に変換し、その結果が期待されるUTF-8バイト列と一致するかを assert 関数で検証しています。

期待される結果 \xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd の内訳は以下の通りです。

  • 0x10ffff -> \xf4\x8f\xbf\xbf (有効なUnicodeコードポイントのUTF-8エンコーディング)
  • 0x10ffff + 1 -> \xef\xbf\xbd (U+FFFDのUTF-8エンコーディング)
  • 0xD800 -> \xef\xbf\xbd (U+FFFDのUTF-8エンコーディング)
  • 0xDFFF -> \xef\xbf\xbd (U+FFFDのUTF-8エンコーディング)
  • -1 -> \xef\xbf\xbd (U+FFFDのUTF-8エンコーディング)

このテストの追加は、Goコンパイラ(特に gccgo のような代替実装)が、Unicodeの複雑な側面、特に無効なコードポイントの扱いについて、Go言語の仕様に厳密に準拠していることを保証するために重要です。これにより、異なるコンパイラ間での挙動の一貫性が保たれ、Goプログラムの移植性と信頼性が向上します。

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

変更は test/string_lit.go ファイルに集中しており、以下の5行が追加されています。

--- a/test/string_lit.go
+++ b/test/string_lit.go
@@ -125,6 +125,11 @@ func main() {
 	s = string(-1)
 	assert(s, "\xef\xbf\xbd", "negative rune")
 
+	// the large rune tests yet again, with a slice.
+	rs := []rune{0x10ffff, 0x10ffff + 1, 0xD800, 0xDFFF, -1}
+	s = string(rs)
+	assert(s, "\xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd", "large rune slice")
+
 	assert(string(gr1), gx1, "global ->[]rune")
 	assert(string(gr2), gx2fix, "global invalid ->[]rune")
 	assert(string(gb1), gx1, "->[]byte")

コアとなるコードの解説

追加されたコードは、main 関数内の既存のテスト群に新しいテストケースを挿入しています。

// the large rune tests yet again, with a slice.
rs := []rune{0x10ffff, 0x10ffff + 1, 0xD800, 0xDFFF, -1}
s = string(rs)
assert(s, "\xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd", "large rune slice")
  1. // the large rune tests yet again, with a slice.

    • これはコメントであり、このテストケースが以前の rune 関連のテスト(おそらく単一の rune から string への変換など)の拡張であり、今回は []rune スライスを対象としていることを示唆しています。
  2. rs := []rune{0x10ffff, 0x10ffff + 1, 0xD800, 0xDFFF, -1}

    • rs という名前の []rune 型のスライスを宣言し、初期化しています。
    • スライスに含まれる各要素は runeint32)型の値です。
      • 0x10ffff: Unicodeの最大コードポイント (U+10FFFF)。有効な rune
      • 0x10ffff + 1: Unicodeの範囲外のコードポイント (U+110000)。無効な rune
      • 0xD800: サロゲートペアの開始範囲。単独では無効な rune
      • 0xDFFF: サロゲートペアの終了範囲。単独では無効な rune
      • -1: 負の値。無効な rune
  3. s = string(rs)

    • rs スライスを string 型に変換し、その結果を s 変数に代入しています。
    • この変換プロセスで、Goランタイムは rs 内の各 rune をUTF-8バイト列にエンコードします。無効な rune はU+FFFD (REPLACEMENT CHARACTER) のUTF-8表現 \xef\xbf\xbd に置き換えられます。
  4. assert(s, "\xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd", "large rune slice")

    • assert 関数は、第一引数(変換された文字列 s)が第二引数(期待される文字列リテラル)と一致するかを検証するテストユーティリティです。
    • 期待される文字列リテラル "\xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd" は、上記の各 rune がGoの仕様に従ってUTF-8エンコードされた結果を連結したものです。
    • 第三引数 "large rune slice" は、テストが失敗した場合に表示される説明メッセージです。

このテストケースは、Go言語の string() 変換が、有効なUnicodeコードポイントだけでなく、範囲外の値、サロゲートペア、負の値といった無効な rune 値に対しても、期待される(U+FFFDへの置換を含む)UTF-8エンコーディングを生成することを保証します。これにより、Goプログラムが様々な入力に対して堅牢に動作することが確認されます。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/
  • Go言語のIssue Tracker: https://github.com/golang/go/issues
  • gccgo について: https://gcc.gnu.org/onlinedocs/gccgo/
  • UnicodeのU+FFFD (REPLACEMENT CHARACTER) について: https://www.compart.com/en/unicode/U+FFFD
  • Unicodeのサロゲートペアについて: https://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%AD%E3%82%B2%E3%83%BC%E3%83%88%E3%83%9A%E3%82%A2
  • Go言語の string[]byte[]rune の変換に関する議論やドキュメント。
    • 特に、Goの文字列処理に関する公式ブログ記事やGo言語の仕様書(The Go Programming Language Specification)の「Conversions」セクション。
    • Goのソースコード内の unicode/utf8 パッケージのドキュメントも関連情報源となり得る。
    • go doc unicode/utf8go doc builtin string などで確認できる。
    • Goのテストファイル自体も、言語の挙動を理解するための重要な情報源となる。
      • test/string_lit.go の他のテストケースも参照することで、Goの文字列リテラルや変換に関する期待される挙動をより深く理解できる。
      • test/run.gotest/fixedbugs ディレクトリ内のテストも、過去のバグ修正と関連するテストケースの例として役立つ。```markdown

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

このコミットは、Go言語のテストスイート test/string_lit.go に、[]rune スライスから string への変換に関する新しいテストケースを追加するものです。特に、無効なUnicodeコードポイントや範囲外のコードポイントを含む []rune スライスが string に変換された際の挙動を検証することを目的としています。

コミット

commit 936665f6414f0fa0e416f734bbe46571f1fc65ac
Author: Ian Lance Taylor <iant@golang.org>
Date:   Thu Dec 12 17:17:02 2013 -0800

    test: add []rune case to string_lit.go
    
    Gccgo managed to get this case wrong.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/41490044

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

https://github.com/golang/go/commit/936665f6414f0fa0e416f734bbe46571f1fc65ac

元コミット内容

test: add []rune case to string_lit.go

Gccgo managed to get this case wrong.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/41490044

変更の背景

このコミットの背景には、Go言語のコンパイラの一つである gccgo が、特定の []rune から string への変換ケースで誤った挙動を示したという問題があります。Go言語において、string 型はUTF-8エンコードされたバイト列として内部的に表現されますが、rune 型はUnicodeコードポイントを表す int32 のエイリアスです。[]rune から string への変換は、これらのUnicodeコードポイントをUTF-8バイト列にエンコードする処理を含みます。

gccgo は、Go言語のフロントエンドをGCCに統合したものであり、Goの標準コンパイラである gc とは異なる実装を持っています。異なるコンパイラ実装が存在する場合、言語仕様の解釈や最適化の過程で、標準コンパイラとは異なるバグや挙動の差異が生じることがあります。

この特定のケースでは、0x10ffff を超える値(Unicodeの最大コードポイントはU+10FFFF)、サロゲートペアの範囲(U+D800からU+DFFF)、および負の値といった、無効または特殊な rune 値を含むスライスを string に変換する際に、gccgo が期待されるUTF-8エンコーディングを生成できなかったと考えられます。

このバグを特定し、将来的な回帰を防ぐために、このコミットでは test/string_lit.go に新しいテストケースが追加されました。これにより、gccgo だけでなく、他のGoコンパイラやランタイムが []rune から string への変換を正しく処理することを確認できます。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念とUnicodeに関する知識が必要です。

  1. Go言語の string:

    • Goの string は、不変なバイトスライスであり、通常はUTF-8エンコードされたテキストを表します。
    • string はバイトのシーケンスであり、文字のシーケンスではありません。
    • 文字列リテラルはデフォルトでUTF-8エンコードされます。
  2. Go言語の rune:

    • runeint32 のエイリアスであり、単一のUnicodeコードポイントを表します。
    • Goのソースコードでは、シングルクォートで囲まれた文字リテラル(例: 'A', '世')は rune 型です。
    • stringrange ループでイテレートすると、各要素は rune 型として取得されます。
  3. []rune から string への変換:

    • []rune 型の値を string() にキャストすると、スライス内の各 rune(Unicodeコードポイント)がUTF-8バイト列にエンコードされ、それらが連結されて新しい string が生成されます。
    • Go言語の仕様では、無効なUnicodeコードポイント(例: U+10FFFFを超える値、サロゲートペアの範囲内の値、負の値)が string に変換される場合、それらはU+FFFD (REPLACEMENT CHARACTER) に置き換えられ、そのUTF-8エンコーディングである \xef\xbf\xbd が生成されることになっています。
  4. UnicodeとUTF-8:

    • Unicode: 世界中の文字を統一的に扱うための文字コード体系です。各文字には一意の「コードポイント」(数値)が割り当てられています。
    • UTF-8: Unicodeコードポイントをバイト列にエンコードするための可変長エンコーディング方式です。ASCII文字は1バイト、他の文字は2〜4バイトで表現されます。UTF-8は自己同期性があり、前方互換性も高いため、Webや多くのシステムで広く利用されています。
    • サロゲートペア: UTF-16エンコーディングにおいて、U+FFFFを超えるコードポイントを表現するために2つの16ビット値(上位サロゲートと下位サロゲート)を組み合わせる仕組みです。Unicodeの仕様では、U+D800からU+DFFFの範囲はサロゲートペアのために予約されており、単独で有効なコードポイントとしては扱われません。
  5. gccgo:

    • Go言語のコンパイラの一つで、GCC (GNU Compiler Collection) のフロントエンドとして実装されています。Goの標準コンパイラである gc とは異なるコード生成パスを持ちます。

これらの知識が、[]rune から string への変換における gccgo のバグと、それを修正・検証するためのテストの重要性を理解する上で不可欠です。

技術的詳細

このコミットで追加されたテストケースは、[]rune から string への変換におけるGo言語の厳密な仕様準拠を検証します。特に、以下の3種類の「不正な」rune 値がどのように処理されるかに焦点を当てています。

  1. 0x10ffff (U+10FFFF): これはUnicodeの最大コードポイントです。この値自体は有効なUnicodeコードポイントであり、UTF-8では4バイトでエンコードされます (\xf4\x8f\xbf\xbf)。テストでは、この値が正しくエンコードされることを確認します。

  2. 0x10ffff + 1 (U+110000): これはUnicodeの有効な範囲(U+0000からU+10FFFF)を超えるコードポイントです。Go言語の仕様では、このような無効なコードポイントはU+FFFD (REPLACEMENT CHARACTER) に置き換えられ、そのUTF-8エンコーディングである \xef\xbf\xbd が生成されるべきです。

  3. 0xD800 および 0xDFFF: これらはUnicodeのサロゲートペアの範囲(U+D800からU+DFFF)に属するコードポイントです。これらの値は単独では有効なUnicodeコードポイントではなく、UTF-16エンコーディングで補助平面の文字を表すために使用されます。Go言語の string() 変換においては、これらも無効なコードポイントとして扱われ、U+FFFDに置き換えられるべきです。

  4. -1: 負の値も有効なUnicodeコードポイントではありません。これもU+FFFDに置き換えられるべきです。

テストケース rs := []rune{0x10ffff, 0x10ffff + 1, 0xD800, 0xDFFF, -1} は、これらの特殊な rune 値を含むスライスを定義しています。 そして、s = string(rs) でこの []rune スライスを string に変換し、その結果が期待されるUTF-8バイト列と一致するかを assert 関数で検証しています。

期待される結果 \xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd の内訳は以下の通りです。

  • 0x10ffff -> \xf4\x8f\xbf\xbf (有効なUnicodeコードポイントのUTF-8エンコーディング)
  • 0x10ffff + 1 -> \xef\xbf\xbd (U+FFFDのUTF-8エンコーディング)
  • 0xD800 -> \xef\xbf\xbd (U+FFFDのUTF-8エンコーディング)
  • 0xDFFF -> \xef\xbf\xbd (U+FFFDのUTF-8エンコーディング)
  • -1 -> \xef\xbf\xbd (U+FFFDのUTF-8エンコーディング)

このテストの追加は、Goコンパイラ(特に gccgo のような代替実装)が、Unicodeの複雑な側面、特に無効なコードポイントの扱いについて、Go言語の仕様に厳密に準拠していることを保証するために重要です。これにより、異なるコンパイラ間での挙動の一貫性が保たれ、Goプログラムの移植性と信頼性が向上します。

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

変更は test/string_lit.go ファイルに集中しており、以下の5行が追加されています。

--- a/test/string_lit.go
+++ b/test/string_lit.go
@@ -125,6 +125,11 @@ func main() {
 	s = string(-1)
 	assert(s, "\xef\xbf\xbd", "negative rune")
 
+	// the large rune tests yet again, with a slice.
+	rs := []rune{0x10ffff, 0x10ffff + 1, 0xD800, 0xDFFF, -1}
+	s = string(rs)
+	assert(s, "\xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd", "large rune slice")
+
 	assert(string(gr1), gx1, "global ->[]rune")
 	assert(string(gr2), gx2fix, "global invalid ->[]rune")
 	assert(string(gb1), gx1, "->[]byte")

コアとなるコードの解説

追加されたコードは、main 関数内の既存のテスト群に新しいテストケースを挿入しています。

// the large rune tests yet again, with a slice.
rs := []rune{0x10ffff, 0x10ffff + 1, 0xD800, 0xDFFF, -1}
s = string(rs)
assert(s, "\xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd", "large rune slice")
  1. // the large rune tests yet again, with a slice.

    • これはコメントであり、このテストケースが以前の rune 関連のテスト(おそらく単一の rune から string への変換など)の拡張であり、今回は []rune スライスを対象としていることを示唆しています。
  2. rs := []rune{0x10ffff, 0x10ffff + 1, 0xD800, 0xDFFF, -1}

    • rs という名前の []rune 型のスライスを宣言し、初期化しています。
    • スライスに含まれる各要素は runeint32)型の値です。
      • 0x10ffff: Unicodeの最大コードポイント (U+10FFFF)。有効な rune
      • 0x10ffff + 1: Unicodeの範囲外のコードポイント (U+110000)。無効な rune
      • 0xD800: サロゲートペアの開始範囲。単独では無効な rune
      • 0xDFFF: サロゲートペアの終了範囲。単独では無効な rune
      • -1: 負の値。無効な rune
  3. s = string(rs)

    • rs スライスを string 型に変換し、その結果を s 変数に代入しています。
    • この変換プロセスで、Goランタイムは rs 内の各 rune をUTF-8バイト列にエンコードします。無効な rune はU+FFFD (REPLACEMENT CHARACTER) のUTF-8表現 \xef\xbf\xbd に置き換えられます。
  4. assert(s, "\xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd", "large rune slice")

    • assert 関数は、第一引数(変換された文字列 s)が第二引数(期待される文字列リテラル)と一致するかを検証するテストユーティリティです。
    • 期待される文字列リテラル "\xf4\x8f\xbf\xbf\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd" は、上記の各 rune がGoの仕様に従ってUTF-8エンコードされた結果を連結したものです。
    • 第三引数 "large rune slice" は、テストが失敗した場合に表示される説明メッセージです。

このテストケースは、Go言語の string() 変換が、有効なUnicodeコードポイントだけでなく、範囲外の値、サロゲートペア、負の値といった無効な rune 値に対しても、期待される(U+FFFDへの置換を含む)UTF-8エンコーディングを生成することを保証します。これにより、Goプログラムが様々な入力に対して堅牢に動作することが確認されます。

関連リンク

参考にした情報源リンク