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

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

このコミットは、Goランタイム内のgostringW関数のテストを修正するものです。具体的には、テストがgostringWに渡すワイド文字列の形式を改善し、ヌル終端を保証し、エンディアンに依存しないようにすることで、テストの正確性と堅牢性を向上させています。

コミット

runtime: fix stringw test.

Null terminate string. Make it endian-agnostic.

TBR=bradfitz R=golang-codereviews CC=golang-codereviews https://golang.org/cl/106060044

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

https://github.com/golang/go/commit/2b309c6e225a908132ddf34316286ec0cdfdb98f

元コミット内容

commit 2b309c6e225a908132ddf34316286ec0cdfdb98f
Author: Keith Randall <khr@golang.org>
Date:   Tue Jun 17 09:17:33 2014 -0700

    runtime: fix stringw test.
    
    Null terminate string.  Make it endian-agnostic.
    
    TBR=bradfitz
    R=golang-codereviews
    CC=golang-codereviews
    https://golang.org/cl/106060044

変更の背景

Go言語のランタイムには、外部システム(特にWindows APIなど)との連携において、ワイド文字列(通常はUTF-16でエンコードされた16ビット単位の文字データ)をGoの内部文字列形式(UTF-8)に変換するためのgostringWという内部関数が存在します。このコミットが行われる前、gostringWのテストコードにはいくつかの問題がありました。

  1. ヌル終端の欠如: gostringW関数は、C言語スタイルのワイド文字列と同様に、ヌル終端(値が0の要素で文字列の終わりを示す)を期待して動作します。しかし、既存のテストコードでは、このヌル終端が明示的に追加されていませんでした。これにより、gostringWが期待通りに動作しない、あるいはテストが不安定になる可能性がありました。
  2. エンディアン依存性: 以前のテストコードでは、rune(GoにおけるUnicodeコードポイント)をbyteスライスに変換する際に、手動でバイト分割を行っていました(例: byte(c&255)byte(c>>8))。この方法は、システムがリトルエンディアンかビッグエンディアンかによってバイトの並び順が異なるため、エンディアンに依存する挙動を引き起こす可能性がありました。gostringW自体はエンディアンを考慮して設計されているべきですが、テストコードがエンディアン依存であると、異なるアーキテクチャでテストが失敗する原因となります。
  3. 不正確な型変換: gostringWuint16のシーケンスを処理することを意図していますが、テストコードでは[]byteスライスを構築し、それをgostringWに渡していました。これは、gostringWの本来のインターフェースと乖離しており、テストの意図を不明瞭にしていました。

これらの問題を解決し、gostringW関数の正確な動作を保証するために、テストコードの修正が必要とされました。

前提知識の解説

Go言語における文字列とrune

Go言語の文字列は、不変のバイトスライスであり、通常はUTF-8でエンコードされたテキストを表します。Goの文字列は、内部的にはバイト列として扱われますが、rangeループで文字列をイテレートすると、各文字はrune型として取得されます。runeはGoにおけるUnicodeコードポイントを表すint32のエイリアスです。これにより、Goは多言語対応を容易に実現しています。

UTF-8とUTF-16

  • UTF-8: Unicode文字を可変長のバイトシーケンスでエンコードする方式です。ASCII文字は1バイトで表現され、他の文字は2バイトから4バイトで表現されます。WebやUnix系システムで広く使われています。Goの内部文字列はUTF-8です。
  • UTF-16: Unicode文字を16ビット(2バイト)または32ビット(4バイト)の単位でエンコードする方式です。基本多言語面(BMP)の文字は16ビットで表現され、それ以外の文字はサロゲートペアと呼ばれる2つの16ビット単位で表現されます。Windows APIなどでよく使われます。

エンディアン(Endianness)

エンディアンとは、マルチバイトのデータをメモリに格納する際のバイトの順序付けの方式を指します。

  • リトルエンディアン (Little-endian): 最下位バイト(least significant byte)が最も小さいアドレスに格納されます。Intel x86アーキテクチャで一般的です。
  • ビッグエンディアン (Big-endian): 最上位バイト(most significant byte)が最も小さいアドレスに格納されます。ネットワークプロトコルや一部のプロセッサ(例: PowerPC)で一般的です。

異なるエンディアンのシステム間でデータをやり取りする際には、バイト順序の変換が必要になることがあります。

Goランタイムの役割

Goランタイムは、Goプログラムの実行を管理する低レベルのコンポーネントです。ガベージコレクション、スケジューリング、メモリ管理、システムコールインターフェースなど、Goプログラムが動作するために必要な多くの機能を提供します。gostringWのような関数は、GoプログラムがOSや外部ライブラリと効率的に連携できるように、ランタイムの一部として実装されています。

gostringWの概要

gostringWは、Goランタイム内部の関数で、uint16のシーケンス(ワイド文字列)をGoのstring型(UTF-8エンコードされたバイトスライス)に変換する役割を担います。この関数は、主にcgoを介してC言語のライブラリやOSのAPI(特にWindows API)と連携する際に使用されます。これらのAPIがUTF-16のようなワイド文字列を扱うため、Go側で適切に変換する必要があるためです。gostringWは、入力されたuint16シーケンスをUnicodeコードポイントとして解釈し、それをUTF-8にエンコードしてGo文字列を生成します。

技術的詳細

このコミットは、gostringW関数のテストの堅牢性を高めるために、以下の技術的な変更を導入しました。

  1. gostringWの引数型の変更(テスト側):

    • src/pkg/runtime/export_test.goにおいて、gostringWのテスト用エクスポート宣言がfunc gostringW([]byte) stringからfunc gostringW([]uint16) stringに変更されました。これは、gostringWが本来uint16のシーケンスを処理することを意図しているため、テストコードがそのインターフェースに合致するように修正されたことを意味します。これにより、テストの意図がより明確になり、実際のgostringWの動作を正確に模倣できるようになりました。
  2. ヌル終端の明示的な追加:

    • src/pkg/runtime/string_test.goTestStringW関数内で、gostringWに渡すuint16スライス(b)の最後にb = append(b, 0)という行が追加されました。これは、ワイド文字列がヌル終端であることを明示的に示すための変更です。gostringWがヌル終端を期待しているため、この変更によりテストがより現実的なシナリオをシミュレートし、関数の正しい動作を検証できるようになります。
  3. エンディアン非依存性の実現:

    • 以前のテストコードでは、runebyteスライスに変換する際に、byte(c&255)byte(c>>8)のように手動でバイトを分割していました。この方法は、システムのエンディアンに依存する可能性がありました。
    • 新しいコードでは、b = append(b, uint16(c))と直接runeuint16にキャストして追加しています。Goのコンパイラとランタイムは、uint16のような固定幅の整数型を扱う際に、システムのエンディアンを適切に処理します。これにより、テストコード自体がエンディアンに依存することなく、gostringWが異なるエンディアンのシステムでも正しく動作するかを検証できるようになりました。gostringW関数自体が内部でエンディアンを考慮した変換を行うため、テスト側でuint16として値を渡すことで、そのエンディアン処理の正確性を間接的に検証することになります。
    • また、if c != rune(uint16(c))というチェックが追加され、runeが16ビットで表現できる範囲に収まっているかを確認しています。これは、gostringWが16ビットのワイド文字列を扱うことを前提としているため、テストデータがその前提を満たしていることを保証するためのものです。

これらの変更により、gostringWのテストはより正確で、堅牢で、異なる環境(特にエンディアンが異なるシステム)でも信頼性の高いものとなりました。

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

このコミットによる主要なコード変更は、以下の2つのファイルに集中しています。

src/pkg/runtime/export_test.go

--- a/src/pkg/runtime/export_test.go
+++ b/src/pkg/runtime/export_test.go
@@ -91,6 +91,6 @@ func gogoBytes() int32
 
 var GogoBytes = gogoBytes
 
-func gostringW([]byte) string
+func gostringW([]uint16) string
 
 var GostringW = gostringW

src/pkg/runtime/string_test.go

--- a/src/pkg/runtime/string_test.go
+++ b/src/pkg/runtime/string_test.go
@@ -104,18 +104,18 @@ func BenchmarkRuneIterate2(b *testing.B) {
 func TestStringW(t *testing.T) {
 	strings := []string{
 		"hello",
-		//\"a\\u5566\\u7788b\",
+		\"a\\u5566\\u7788b\",
 	}
 
 	for _, s := range strings {
-		var b []byte
+		var b []uint16
 		for _, c := range s {
-			b = append(b, byte(c&255))
-			b = append(b, byte(c>>8))
-			if c>>16 != 0 {
+			b = append(b, uint16(c))
+			if c != rune(uint16(c)) {
 				t.Errorf("bad test: stringW can't handle >16 bit runes")
 			}
 		}
+		b = append(b, 0)
 		r := runtime.GostringW(b)
 		if r != s {
 			t.Errorf("gostringW(%v) = %s, want %s", b, r, s)

コアとなるコードの解説

src/pkg/runtime/export_test.goの変更

  • func gostringW([]byte) string から func gostringW([]uint16) string への変更:
    • このファイルは、ランタイムの内部関数をテスト目的でエクスポートするためのものです。この変更は、gostringW関数が本来[]uint16型の引数を取ることを明確に示しています。以前は[]byteとしてエクスポートされていましたが、これはgostringWがワイド文字列(16ビット単位)を処理するという本来の意図と異なっていました。この修正により、テストコードがgostringWの正しいインターフェースに準拠するようになり、テストの正確性が向上しました。

src/pkg/runtime/string_test.goの変更

  1. //\"a\\u5566\\u7788b\", のコメント解除:

    • この行は、マルチバイトのUnicode文字を含む文字列(例: 中国語の文字)のテストケースを有効にしています。以前はコメントアウトされていたため、gostringWが非ASCIIのワイド文字列を正しく処理できるかの検証が不十分でした。この有効化により、より包括的なテストが可能になりました。
  2. var b []byte から var b []uint16 への変更:

    • テストで使用されるスライスbの型がbyteからuint16に変更されました。これは、gostringWuint16のシーケンスを期待しているため、テストデータもそれに合わせてuint16のシーケンスとして構築されるべきであることを反映しています。これにより、テストコードとgostringW関数の間の型の不一致が解消されました。
  3. for _, c := range s ループ内の変更:

    • 変更前:
      b = append(b, byte(c&255))
      b = append(b, byte(c>>8))
      if c>>16 != 0 {
          t.Errorf("bad test: stringW can't handle >16 bit runes")
      }
      
      このコードは、runeint32)を2つのbyteに手動で分割していました。c&255は下位8ビット、c>>8は次の8ビットを取得します。これは、16ビットの文字をバイト単位で表現しようとする試みですが、システムのエンディアンに依存する可能性があり、またgostringWuint16を期待していることと整合性がありませんでした。c>>16 != 0は、runeが16ビットを超える場合にエラーを報告するものでした。
    • 変更後:
      b = append(b, uint16(c))
      if c != rune(uint16(c)) {
          t.Errorf("bad test: stringW can't handle >16 bit runes")
      }
      
      この変更により、runeが直接uint16にキャストされてスライスbに追加されます。Goの型システムがこの変換を適切に処理するため、手動でのバイト分割が不要になり、コードが簡潔かつエンディアン非依存になりました。if c != rune(uint16(c))という新しいチェックは、元のruneuint16に収まるかどうかをより正確に検証します。もしruneが16ビットを超える場合(例: サロゲートペアを必要とする文字)、このテストは失敗し、gostringWがそのような文字を処理できないことを示します。これは、gostringWが主にUTF-16の基本多言語面(BMP)の文字を扱うことを想定しているため、適切な検証です。
  4. b = append(b, 0) の追加:

    • forループの直後にこの行が追加されました。これは、gostringWに渡されるuint16スライスの最後に明示的にヌル終端(値が0のuint16)を追加するものです。gostringWはヌル終端のワイド文字列を期待しているため、この変更によりテストが関数の期待する入力形式に完全に合致するようになり、テストの信頼性が向上しました。

これらの変更は、gostringW関数のテストをより正確で、堅牢で、そして異なるシステムアーキテクチャ(特にエンディアン)に対してより耐性のあるものにすることを目的としています。

関連リンク

参考にした情報源リンク

  • Go言語の文字列とruneに関する公式ドキュメントやブログ記事
  • UTF-8とUTF-16のエンコーディングに関する一般的な情報源
  • エンディアンに関するコンピュータアーキテクチャの資料
  • GoランタイムのgostringW関数の役割に関するGoコミュニティの議論やソースコードコメント
  • Google検索: "Go runtime gostringW"