[インデックス 16534] ファイルの概要
このコミットは、Goコンパイラのcmd/cc
ツールにおける字句解析(lexing)のバグ修正に関するものです。具体的には、lexbody
関数が負の文字(バイト)を誤って解釈する問題に対処しています。この問題は、特にruntime·foo
のようなシンボル名に含まれる·
(中点)のような非ASCII文字がバッファの先頭に来た場合に、字句解析が正しく行われない原因となっていました。
コミット
commit 5b15b4443491d2405c494857819341186bb760ba
Author: Russ Cox <rsc@golang.org>
Date: Mon Jun 10 16:13:25 2013 -0400
cmd/cc: fix lexbody for negative chars
The new code matches the code in cc/lex.c and the #define GETC.
This was causing problems scanning runtime·foo if the leading
· byte was returned by the buffer fill.
R=ken2
CC=golang-dev
https://golang.org/cl/10167043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5b15b4443491d2405c494857819341186bb760ba
元コミット内容
--- a/src/cmd/cc/lexbody
+++ b/src/cmd/cc/lexbody
@@ -665,7 +665,7 @@ loop:
goto pop;
}\n fi.p = i->b + 1;
-\treturn i->b[0];
+\treturn i->b[0] & 0xff;
pop:
iostack = i->link;
@@ -678,7 +678,7 @@ pop:
fi.c = i->c;
if(--fi.c < 0)
goto loop;
-\treturn *fi.p++;
+\treturn *fi.p++ & 0xff;
}
void
変更の背景
この変更は、Goコンパイラのcmd/cc
ツールが、特定の文字(特に非ASCII文字)を字句解析する際に発生するバグを修正するために行われました。問題は、lexbody
関数がバイト値を符号付き整数として解釈していたことに起因します。これにより、·
(中点、Unicode U+00B7)のような、UTF-8エンコーディングで負の値として解釈されうるバイト(例: 0xC2 0xB7
の0xB7
)が入力ストリームの先頭に来た場合に、字句解析器が予期しない動作をする可能性がありました。
特に、Goの内部では、リンケージ名やシンボル名にruntime·foo
のように中点(·
)が使用されることがあります。このようなシンボルを処理する際に、字句解析器が中点のバイトを正しく読み取れないと、コンパイルエラーや不正なコード生成につながる恐れがありました。
コミットメッセージにある「The new code matches the code in cc/lex.c and the #define GETC.」という記述は、この修正が既存のcc/lex.c
ファイル内の関連コードやGETC
マクロの動作と整合性を持たせるためのものであることを示唆しています。これは、コードベース全体での一貫性と正確性を確保するための重要なステップです。
前提知識の解説
1. Goコンパイラ (cmd/cc
)
Go言語のコンパイラは、Goのソースコードを機械語に変換するツールチェーンの一部です。cmd/cc
は、GoのツールチェーンにおけるC言語のコンパイラ(またはそれに類する機能)を指す可能性があります。Goの初期のコンパイラはC言語で書かれており、その名残や、C言語との相互運用性に関連する部分でcc
という名称が使われることがあります。この文脈では、Goのランタイムや標準ライブラリの一部がC言語で書かれているか、あるいはC言語のコードを生成・リンクする過程で使われるコンポーネントであると考えられます。
2. 字句解析 (Lexical Analysis / Lexing)
字句解析は、コンパイラの最初のフェーズです。ソースコードを読み込み、意味のある最小単位である「トークン」(例: キーワード、識別子、演算子、リテラルなど)のストリームに分割します。字句解析器(lexerまたはscanner)は、この処理を行います。
3. lexbody
関数
lexbody
は、字句解析器の内部で文字(バイト)を読み取るための関数であると推測されます。ソースコードから次の文字を効率的に取得し、字句解析器に渡す役割を担っています。
4. 符号付き整数と符号なし整数 (Signed vs. Unsigned Integers)
コンピュータのメモリでは、数値はビットの並びとして表現されます。1バイト(8ビット)の場合、00000000
から11111111
までの256通りの値を取ります。
- 符号なし整数 (Unsigned Integer): 全てのビットを数値の大きさに使用します。1バイトの場合、0から255までの値を表現できます。
- 符号付き整数 (Signed Integer): 最上位ビット(MSB)を符号(0が正、1が負)に、残りのビットを数値の大きさに使用します。一般的には2の補数表現が用いられます。1バイトの場合、-128から127までの値を表現できます。
C言語のような言語では、char
型がデフォルトで符号付きか符号なしかは処理系依存です。もしchar
が符号付きとして扱われる環境で、0x80
(10進数で128)以上のバイト値(例: 0xB7
)を読み取ると、それは負の値(例: -73)として解釈されてしまいます。
5. & 0xff
(ビットマスク)
&
はビットAND演算子です。0xff
は16進数で、2進数では11111111
です。
任意のバイト値B
に対してB & 0xff
という演算を行うと、B
が符号付き整数として解釈された場合でも、その下位8ビットのみが保持され、結果は常に0から255の範囲の符号なし整数になります。これにより、負の値として誤って解釈されることを防ぎ、バイト値を純粋な8ビットの符号なし値として扱えるようになります。
6. runtime·foo
と·
(中点)
Go言語の内部では、リンケージ名やシンボル名に特殊な文字が使われることがあります。runtime·foo
は、runtime
パッケージ内のfoo
というシンボル(関数や変数など)を指す内部的な命名規則です。ここで使われている·
(中点、U+00B7)は、Goのソースコードでは直接記述されませんが、コンパイラやリンカが内部的にシンボル名を区別するために使用します。UTF-8エンコーディングでは、中点·
は2バイトシーケンス0xC2 0xB7
として表現されます。この0xB7
というバイト値は、符号付き8ビット整数として解釈されると負の値になります。
技術的詳細
このコミットの技術的な核心は、字句解析器が入力ストリームからバイトを読み取る際の、符号付き/符号なしの解釈の不一致を解消することにあります。
Goコンパイラのcmd/cc
は、C言語のコードベースから派生したか、C言語の慣習に従っている部分があると考えられます。C言語では、char
型が符号付きか符号なしかは実装定義(compiler-dependent)です。多くのシステムではchar
は符号付きとして扱われます。
元のコードでは、i->b[0]
や*fi.p++
といった形で直接バイトを読み取り、それをreturn
していました。もしこれらのバイトが0x80
(128)以上の値を持つ場合、そしてコンパイラがchar
を符号付きとして扱っていた場合、これらの値は負の整数として解釈されてしまいます。
例えば、中点·
のUTF-8エンコーディングの2バイト目である0xB7
は、符号付き8ビット整数として解釈されると-73
(10進数)になります。字句解析器が文字コードやバイト値を期待する場所で負の値を受け取ると、それは予期しない分岐やエラーを引き起こす可能性があります。特に、文字の範囲チェックや、文字の種類に応じた処理を行う際に、負の値は問題となります。
修正後のコードでは、& 0xff
というビットマスクが追加されています。
return i->b[0] & 0xff;
return *fi.p++ & 0xff;
この& 0xff
演算は、読み取ったバイト値がどのような符号付き/符号なしの型で表現されていても、その下位8ビットのみを抽出し、結果を常に0から255の範囲の符号なし整数として扱われるようにします。これにより、0xB7
のようなバイト値が負の数として誤って解釈されることがなくなり、常に183
(10進数)として扱われるようになります。
コミットメッセージにある「The new code matches the code in cc/lex.c and the #define GETC.」という記述は、この修正が、既にcc/lex.c
ファイル内で定義されているGETC
マクロ(おそらく文字取得のためのマクロ)や関連する文字処理ロジックと整合性を持たせるためのものであることを示唆しています。これは、コードベース全体でバイトの解釈方法を一貫させることで、将来的なバグの発生を防ぎ、コードの堅牢性を高める狙いがあります。
この修正により、runtime·foo
のような内部シンボル名に含まれる非ASCII文字が正しく字句解析されるようになり、Goコンパイラの安定性と正確性が向上しました。
コアとなるコードの変更箇所
変更はsrc/cmd/cc/lexbody
ファイル内の2箇所です。
-
loop
ラベル内のreturn
文:- return i->b[0]; + return i->b[0] & 0xff;
この行は、バッファの先頭から最初のバイトを読み取る際に使用されます。
-
pop
ラベル内のreturn
文:- return *fi.p++; + return *fi.p++ & 0xff;
この行は、バッファ内の現在のポインタから次のバイトを読み取り、ポインタを進める際に使用されます。
どちらの変更も、読み取ったバイト値に対して& 0xff
というビットマスクを適用することで、その値を符号なしの8ビット整数として強制的に扱うようにしています。
コアとなるコードの解説
src/cmd/cc/lexbody
ファイルは、Goコンパイラのcmd/cc
ツールにおける字句解析器のコアロジックの一部を定義していると考えられます。このファイルは、おそらくC言語のプリプロセッサによって処理されるテンプレートのようなもので、実際のCソースコードに展開される可能性があります。
変更された2つの行は、字句解析器が入力ストリームから次の文字(バイト)を取得する際の基本的な操作です。
i->b[0]
は、おそらく入力バッファb
の先頭にあるバイトを指します。これは、新しいバッファがロードされた直後や、バッファの先頭から読み取りを開始する際に使われる可能性があります。*fi.p++
は、現在の入力ポインタfi.p
が指すバイトを読み取り、その後ポインタを1バイト進める操作です。これは、連続して文字を読み進める際に使われる一般的なパターンです。
これらの行に& 0xff
が追加されたことで、以下の効果が得られます。
- 符号拡張の防止: C言語では、
char
型の値をint
型などのより広い型に代入する際に、符号拡張(sign extension)が発生することがあります。もしchar
が符号付きで、その値が負(例:0xB7
が-73
)の場合、int
に変換されると、上位ビットが全て1で埋められてしまいます(例:0xFFFFFFB7
)。& 0xff
を適用することで、この符号拡張を防ぎ、常に0x000000B7
のような正の値として扱われるようになります。 - 一貫したバイト解釈: 字句解析器の他の部分や、
cc/lex.c
およびGETC
マクロがバイトを符号なしとして扱っている場合、この修正によってlexbody
関数も同様にバイトを解釈するようになり、システム全体での一貫性が保たれます。 - 非ASCII文字の正確な処理:
·
(中点)のような非ASCII文字のUTF-8エンコーディングに含まれるバイト(例:0xB7
)が、負の値として誤って解釈されることなく、正しいバイト値として処理されるようになります。これにより、runtime·foo
のような内部シンボル名が正しく認識され、コンパイルプロセスが安定します。
この修正は、一見すると小さな変更ですが、コンパイラの字句解析という基盤部分におけるバイト解釈の正確性を保証する上で非常に重要です。特に、多言語対応や、内部的に特殊な文字を使用するシステムにおいては、このようなバイトレベルの正確な処理が不可欠となります。
関連リンク
- Go言語のコンパイラツールチェーンに関する公式ドキュメント(当時のものを見つけるのは難しいかもしれませんが、現在のGoのコンパイラ設計に関する情報は参考になります)
- UTF-8エンコーディングに関する情報
- C言語における符号付き/符号なし整数、およびビット演算に関する情報
参考にした情報源リンク
- Go言語の公式ドキュメント(現在のコンパイラに関する情報)
- C言語の仕様書やチュートリアル(
char
型、符号付き/符号なし整数、ビット演算について) - UTF-8の仕様
- 一般的なコンパイラ設計の原則に関する資料(字句解析について)
- GitHubのgolang/goリポジトリのコミット履歴と関連するIssue/CL(Change List)
- https://golang.org/cl/10167043 (このコミットのChange List)
- GoのChange Listは、コミットの背景や議論が詳細に記述されていることが多いため、非常に有用な情報源となります。