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

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

このコミットは、Go言語のreflectパッケージにおける文字列リテラル内のヌル文字(NUL character)の扱いを修正するものです。具体的には、\0エスケープシーケンスを\x00に統一し、reflectパッケージが文字列を正しく解釈・表現できるように変更しています。これにより、ヌル文字を含む文字列の反射(reflection)がより正確に行われるようになります。

コミット

  • コミットハッシュ: 613a5c8bc6f766269a1073511b88f3e517e8aa4d
  • Author: Rob Pike r@golang.org
  • Date: Fri Oct 31 15:26:14 2008 -0700
  • コミットメッセージ:
    \x00 for NUL in type string.
    
    R=rsc
    DELTA=14  (9 added, 0 deleted, 5 changed)
    OCL=18281
    CL=18281
    

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

https://github.com/golang/go/commit/613a5c8bc6f766269a1073511b88f3e517e8aa4d

元コミット内容

\x00 for NUL in type string.

R=rsc
DELTA=14  (9 added, 0 deleted, 5 changed)
OCL=18281
CL=18281

変更の背景

Go言語において、文字列はC言語のようにヌル終端ではありません。Goの文字列は、バイトのスライスと長さを保持する構造であり、ヌル文字(\x00)は文字列内の有効なバイトとして扱われます。しかし、初期のGo言語のreflectパッケージでは、文字列リテラル内のヌル文字のエスケープシーケンスとして\0(8進数エスケープ)と\x00(16進数エスケープ)が混在しており、特にreflectパッケージが文字列の型情報を解析する際に、この不整合が問題となる可能性がありました。

このコミットは、reflectパッケージが文字列リテラルを解析する際のヌル文字の表現を\x00に統一し、より堅牢で正確な型情報のリフレクションを保証することを目的としています。これにより、ヌル文字を含む文字列がreflectパッケージによって正しく処理され、予期せぬ挙動やエラーを防ぐことができます。

前提知識の解説

ヌル文字(NUL character, \0または\x00

ヌル文字は、ASCIIコードで0x00(10進数で0)に相当する制御文字です。C言語などでは文字列の終端を示すために使われますが、Go言語では文字列の終端を示す役割はありません。Goの文字列は長さを保持するため、ヌル文字は文字列内の単なる1バイトとして扱われます。

Go言語における文字列とエスケープシーケンス

Go言語の文字列はUTF-8エンコードされたバイトのシーケンスであり、不変です。文字列リテラル内では、特殊文字を表現するためにエスケープシーケンスが使用されます。

  • \n: 改行
  • \t: タブ
  • \": 二重引用符
  • \\: バックスラッシュ
  • \xNN: 16進数エスケープ(NNは2桁の16進数)
  • \uNNNN: Unicodeコードポイントエスケープ(NNNNは4桁の16進数)
  • \UNNNNNNNN: Unicodeコードポイントエスケープ(NNNNNNNNは8桁の16進数)
  • \0NNN: 8進数エスケープ(NNNは3桁の8進数) - このコミットの文脈では、ヌル文字を表現するために使われていたが、\x00に統一される。

reflectパッケージ

reflectパッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)するための機能を提供します。これにより、プログラムは型情報、フィールド、メソッドなどを動的に調べたり、値を操作したりすることができます。例えば、構造体のフィールド名を取得したり、インターフェースの具体的な型を調べたりする際に利用されます。

reflectパッケージは、Goの型システムと密接に連携しており、型の定義や値の表現を正確に反映する必要があります。文字列リテラル内のエスケープシーケンスの解釈も、この正確性を保つ上で重要です。

技術的詳細

このコミットの核心は、reflectパッケージが文字列リテラルを解析する際のヌル文字の扱いを\0から\x00に統一することです。

Go言語の仕様では、文字列リテラル内でヌル文字を表現する方法として\x00が推奨されます。\0は8進数エスケープの一部としてヌル文字を表現できますが、これはより一般的な8進数エスケープ(例: \077)と混同される可能性があり、また16進数エスケープである\x00の方がヌル文字を明確に表現できるため、より推奨される形式です。

reflectパッケージは、Goのソースコードを解析して型情報を構築する際に、文字列リテラルを正しく解釈する必要があります。以前の実装では、reflect/type.go内のunescape関数が\0をヌル文字として特別に扱っていましたが、これはGoの文字列リテラルの一般的なエスケープルールと完全に一致しているわけではありませんでした。

この変更により、reflectパッケージはヌル文字を\x00として一貫して処理するようになります。具体的には、以下の点が変更されています。

  1. src/lib/reflect/test.go: テストケースが\0から\x00に更新され、新しいエスケープシーケンスの解釈が正しく行われることを確認します。
  2. src/lib/reflect/tostring.go: DoubleQuote関数が、ヌル文字を\0ではなく\x00として出力するように変更されます。これは、文字列を引用符で囲んで表現する際に、一貫したエスケープ形式を使用するためです。
  3. src/lib/reflect/type.go:
    • 型定義のコメントが\0 (NUL)から\x00 (NUL)に更新され、ドキュメントと実装の一貫性が保たれます。
    • unescape関数内で、\0をヌル文字として特別に扱うロジックが削除され、代わりに\xエスケープシーケンスの処理が強化されます。特に、\x00が検出された場合に正しくヌル文字として解釈されるように、hex00ヘルパー関数が導入されています。これにより、\xの後に00が続く場合にヌル文字として処理され、それ以外の場合はxがそのまま残るという、より一般的な16進数エスケープのルールに沿った挙動になります。

この変更は、Go言語の文字列リテラルのエスケープルールに対するreflectパッケージの準拠性を高め、ヌル文字を含む文字列の型情報が正確にリフレクションされることを保証します。

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

src/lib/reflect/test.go

--- a/src/lib/reflect/test.go
+++ b/src/lib/reflect/test.go
@@ -119,7 +119,7 @@ func main() {
 	typedump("struct {a int8; b int8; c int8; d int8; b int32}", "struct{a int8; b int8; c int8; d int8; b int32}");
 	typedump("struct {a int8; b int8; c int8; d int8; e int8; b int32}", "struct{a int8; b int8; c int8; d int8; e int8; b int32}");
 	typedump("struct {a int8 \"hi there\"; }", "struct{a int8 \"hi there\"}");
-	typedump("struct {a int8 \"hi \\0there\\t\\n\\\"\\\\\"; }", "struct{a int8 \"hi \\0there\\t\\n\\\"\\\\\"}");
+	typedump("struct {a int8 \"hi \\x00there\\t\\n\\\"\\\\\"; }", "struct{a int8 \"hi \\x00there\\t\\n\\\"\\\\\"}");

 	valuedump("int8", "8");
 	valuedump("int16", "16");

src/lib/reflect/tostring.go

--- a/src/lib/reflect/tostring.go
+++ b/src/lib/reflect/tostring.go
@@ -25,7 +25,7 @@ func DoubleQuote(s string) string {
 		case '\t':
 			out += `\t`;
 		case '\x00':
-			out += `\0`;
+			out += `\x00`;
 		case '"':
 			out += `\"`;
 		case '\\':

src/lib/reflect/type.go

--- a/src/lib/reflect/type.go
+++ b/src/lib/reflect/type.go
@@ -453,7 +453,7 @@ func init() {
 	typename =
 		name '.' name
 	doublequotedstring =
-		string in " ";  escapes are \0 (NUL) \n \t \" \\
+		string in " ";  escapes are \x00 (NUL) \n \t \" \\
 	fieldlist =
 		[ field { [ ',' | ';' ] field } ]
 	field =
@@ -492,6 +492,10 @@ func special(c uint8) bool {
 	return false;
 }

+func hex00(s string, i int) bool {
+	return i + 2 < len(s) && s[i] == '0' && s[i+1] == '0'
+}
+
 // Process backslashes.  String known to be well-formed.
 // Initial double-quote is left in, as an indication this token is a string.
 func unescape(s string, backslash bool) string {
@@ -509,8 +513,13 @@ func unescape(s string, backslash bool) string {
 			c = '\n';
 		case 't':
 			c = '\t';
-		case '0':	// it's not a legal go string but \0 means NUL
-			c = '\x00';
+		case 'x':
+			if hex00(s, i+1) {
+				i += 2;
+				c = 0;
+				break;
+			}
+			// otherwise just put an 'x'; erroneous but safe.
 			// default is correct already; \\ is \; \" is "
 			}
 		}

コアとなるコードの解説

src/lib/reflect/test.goの変更

  • typedump関数の呼び出しにおいて、テスト用の文字列リテラル内のヌル文字のエスケープシーケンスが\0から\x00に変更されました。
  • これは、reflectパッケージが新しい(より標準的な)ヌル文字のエスケープ形式を正しく解釈できることを検証するためのテストケースの更新です。

src/lib/reflect/tostring.goの変更

  • DoubleQuote関数は、文字列を二重引用符で囲んで表現する際に、ヌル文字(\x00)を\0ではなく\x00として出力するように変更されました。
  • この変更により、reflectパッケージが文字列を表現する際の一貫性が向上し、Go言語の文字列リテラルのエスケープルールに準拠した出力が生成されるようになります。

src/lib/reflect/type.goの変更

  1. doublequotedstringのコメント更新:

    • doublequotedstringの定義に関するコメントが、ヌル文字のエスケープシーケンスを\0 (NUL)から\x00 (NUL)に更新されました。これは、コードの変更とドキュメントの一貫性を保つためのものです。
  2. hex00関数の追加:

    • func hex00(s string, i int) boolという新しいヘルパー関数が追加されました。
    • この関数は、文字列sのインデックスiから始まる部分が00であるかどうかをチェックします。これは、\x00エスケープシーケンスを検出するために使用されます。
  3. unescape関数の変更:

    • unescape関数は、文字列リテラル内のバックスラッシュエスケープシーケンスを処理する役割を担っています。
    • 以前はcase '0':\0をヌル文字として特別に扱っていましたが、このロジックが削除されました。
    • 代わりに、case 'x':の処理が強化されました。\xの後にhex00関数で00が続く場合(つまり\x00の場合)、ヌル文字(c = 0)として解釈され、インデックスiが2つ進められます(x00の分)。
    • // otherwise just put an 'x'; erroneous but safe.というコメントが追加されており、\xの後に有効な16進数が続かない場合は、xがそのまま残るという挙動が示されています。これは、Goの文字列リテラルが厳密な形式を要求する一方で、reflectパッケージが多少の「誤り」に対しても安全に処理しようとする設計思想を反映しています。

これらの変更により、reflectパッケージはGo言語の文字列リテラルにおけるヌル文字の表現を\x00に統一し、より正確で堅牢なリフレクション機能を提供できるようになりました。

関連リンク

参考にした情報源リンク