[インデックス 1402] ファイルの概要
このコミットは、Go言語の初期コンパイラの一部である src/cmd/cc/lexbody
ファイルに対する修正です。lexbody
は、Goコンパイラの字句解析器(lexer/scanner)の本体を構成するファイルであり、ソースコードを読み込み、トークンに分割する役割を担っています。この特定の修正は、コメント行の処理における行番号のカウントに関するバグを解決しています。
コミット
fix 6a line number bug -
was incrementing lineno twice for
the \n after a // comment.
R=r
DELTA=3 (0 added, 2 deleted, 1 changed)
OCL=21984
CL=22021
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c14c2b231ff05ff944a42978ea6032688a643d10
元コミット内容
fix 6a line number bug -
was incrementing lineno twice for
the \n after a // comment.
R=r
DELTA=3 (0 added, 2 deleted, 1 changed)
OCL=21984
CL=22021
変更の背景
このコミットは、Go言語のコンパイラにおける行番号のカウントに関するバグを修正するために行われました。具体的には、//
スタイルの単一行コメントの末尾にある改行文字 (\n
) を処理する際に、字句解析器が行番号 (lineno
) を二重にインクリメントしてしまう問題がありました。
コンパイラやインタプリタにおいて、正確な行番号の追跡は非常に重要です。エラーメッセージやデバッグ情報が行番号に基づいて報告されるため、行番号がずれると、開発者は問題の特定に困難をきたします。例えば、コンパイルエラーが発生した際に、誤った行番号が報告されると、実際のバグの場所を見つけるのが難しくなります。
このバグは、特に単一行コメントの直後にコードが続く場合に顕著な影響を与えたと考えられます。コメントの後の改行で余分なインクリメントが発生し、その後のコードの行番号がすべて1つずれてしまうため、コンパイルエラーや実行時エラーの報告が混乱を招く可能性がありました。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
- 字句解析器(Lexer/Scanner): コンパイラの最初のフェーズであり、ソースコードを読み込み、意味のある最小単位である「トークン」に分割する役割を担います。例えば、
int x = 10;
というコードは、int
(キーワード),x
(識別子),=
(演算子),10
(リテラル),;
(区切り文字) といったトークンに分割されます。字句解析器は、コメントや空白文字をスキップする機能も持ちます。 - 行番号(Line Number): ソースコードの各行に割り当てられる番号で、エラー報告やデバッグの際にコードの位置を特定するために使用されます。字句解析器は、改行文字 (
\n
) を検出するたびに行番号をインクリメントするのが一般的です。 src/cmd/cc/lexbody
: Go言語の初期コンパイラ(cmd/cc
はC言語で書かれたGoコンパイラの一部でした)における字句解析器の本体を定義するファイルです。Go言語のコンパイラは、初期にはC言語で書かれており、その後のバージョンでGo言語自身で書き直されました(セルフホスト)。このファイルは、そのC言語時代の名残です。GETC()
: 字句解析器が入力ストリームから次の文字を読み込むための関数(またはマクロ)です。通常、この関数が呼び出されるたびに、入力ポインタが1文字進みます。lineno
: 現在処理中の行番号を保持する変数です。yyerror()
: 字句解析中にエラーが発生した場合に呼び出されるエラー報告関数です。通常、エラーメッセージを標準エラー出力に出力します。errorexit()
: エラー発生時にプログラムを終了させる関数です。goto l1;
: C言語におけるgoto
ステートメントで、ラベルl1
に処理をジャンプさせます。字句解析器では、特定の状態遷移や、コメントや空白をスキップした後に次のトークンの解析を再開する際などに使用されることがあります。この場合、コメントの終わりで次のトークン解析の開始点に戻るために使われています。
技術的詳細
このバグは、src/cmd/cc/lexbody
内のコメント処理ロジックに起因していました。Go言語の単一行コメントは //
で始まり、行末まで続きます。字句解析器は //
を検出すると、その行の残りの部分をスキップし、次の改行文字 (\n
) を見つけるまで文字を読み飛ばします。
問題のコードは以下のようになっていました(修正前):
if(c1 == '/') { // '/' が検出された場合、コメントの可能性
for(;;) { // 無限ループでコメントの終わりを探す
c = GETC(); // 次の文字を読み込む
if(c == '\n') { // 改行文字を検出した場合
lineno++; // 行番号をインクリメント
goto l1; // コメント処理を終了し、次のトークン解析へ
}
// ... その他のコメント処理(EOFチェックなど)
}
}
このコードの問題点は、if(c == '\n')
のブロック内で lineno++
が明示的に行われている点です。字句解析器の一般的な実装では、GETC()
のような文字読み込み関数が内部的に改行文字を検出した際に行番号をインクリメントするロジックを持っていることがあります。
つまり、以下の2つの場所で lineno
がインクリメントされる可能性がありました。
GETC()
が改行文字を読み込んだ際に、その内部ロジックでlineno
がインクリメントされる。if(c == '\n')
の条件が真になった際に、明示的にlineno++
が実行される。
これにより、単一行コメントの末尾の改行文字に対して lineno
が二重にインクリメントされ、結果として行番号が1つ余分に進んでしまうというバグが発生していました。
この修正は、if(c == '\n')
ブロック内の lineno++
を削除することで、この二重インクリメントを防ぎ、行番号のカウントを正確にするものです。
コアとなるコードの変更箇所
--- a/src/cmd/cc/lexbody
+++ b/src/cmd/cc/lexbody
@@ -462,10 +462,8 @@ l1:
if(c1 == '/') {
for(;;) {
c = GETC();
- if(c == '\n') {
- lineno++;
+ if(c == '\n')
goto l1;
- }
if(c == EOF) {
yyerror("eof in comment");
errorexit();
コアとなるコードの解説
変更は src/cmd/cc/lexbody
ファイルの462行目付近にあります。
-
修正前:
if(c == '\n') { lineno++; goto l1; }
このコードブロックは、字句解析器が
//
コメントの処理中に改行文字 (\n
) を検出した際に実行されます。ここでlineno++
が明示的に行番号をインクリメントしています。 -
修正後:
if(c == '\n') goto l1;
修正では、
if(c == '\n')
のブロックからlineno++;
の行が削除されました。これにより、改行文字を検出した際のlineno
の明示的なインクリメントがなくなります。
この変更の意図は、GETC()
関数(またはその背後にあるメカニズム)が既に改行文字を読み込んだ際に lineno
を適切にインクリメントしているという前提に基づいています。したがって、コメント処理ロジック内で再度 lineno++
を行う必要はなく、むしろそれが二重インクリメントの原因となっていたため、削除することで正しい行番号のカウントが保証されます。
結果として、単一行コメントの末尾の改行文字が処理される際に、行番号が一度だけ正確にインクリメントされるようになり、コンパイラが報告するエラーメッセージやデバッグ情報の行番号の正確性が向上しました。
関連リンク
- Go言語の初期のコンパイラに関する情報:
- The Go Programming Language: https://go.dev/
- Goの歴史に関するドキュメントやブログ記事(例: "Go at Google: Language Design in the Service of Software Engineering" by Rob Pike, Russ Cox, Ken Thompson)
参考にした情報源リンク
- Go言語の公式ドキュメント
- コンパイラの設計に関する一般的な知識(字句解析、構文解析など)
- C言語の
goto
ステートメントに関する知識 - Gitのコミットログと差分表示の解釈