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

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

このコミットは、Goコンパイラ(gc)における構文エラー報告の精度を向上させるための修正です。具体的には、予期せぬEOF(End Of File)によって引き起こされる構文エラーが発生した場合に、誤った行番号が報告される問題を解決します。また、デバッグコードの追加と、この問題を再現するためのテストケースも含まれています。

コミット

  • コミットハッシュ: 42aa9abae967b27582e1275793a841bd312d18f4
  • Author: Anthony Martin ality@pbrane.org
  • Date: Thu Apr 26 02:57:23 2012 -0700
  • 変更ファイル:
    • src/cmd/gc/lex.c: 5行変更 (2追加, 3削除)
    • src/cmd/gc/subr.c: 5行変更 (5追加, 0削除)
    • test/fixedbugs/bug435.go: 15行変更 (15追加, 0削除)
  • 合計: 3ファイル変更、22行追加、3行削除

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

https://github.com/golang/go/commit/42aa9abae967b27582e1275793a841bd312d18f4

元コミット内容

gc: use correct line number for EOF syntax error

I also added some debugging code that's turned
on with -xx.

Fixes #3392.

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

変更の背景

このコミットは、Goコンパイラがソースコードの末尾で予期せぬEOFに遭遇し、それが構文エラーを引き起こした場合に、エラーメッセージに表示される行番号が正しくないというバグ(Issue 3392)を修正するために行われました。

コンパイラがソースコードを解析する際、通常は各行の終わりに改行文字を検出して行番号をインクリメントします。しかし、ファイルが予期せぬ形で終了した場合(例えば、不完全なコードブロックの途中でEOFに達した場合)、コンパイラは内部的に「偽の改行文字」を挿入して処理を続行することがあります。この偽の改行文字によって行番号が誤ってインクリメントされ、結果として報告されるエラーの行番号が実際の構文エラーの発生箇所よりも1行ずれてしまう問題がありました。

このずれは、開発者がエラーメッセージに基づいて問題を特定し、デバッグする際に混乱を招くため、正確な行番号を報告することが重要でした。

前提知識の解説

  • Goコンパイラ (gc): Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担います。gcは、字句解析(lexing)、構文解析(parsing)、型チェック、最適化、コード生成といった複数のフェーズを経てコンパイルを行います。
  • 字句解析 (Lexical Analysis / Lexing): ソースコードをトークン(意味を持つ最小単位、例: キーワード、識別子、演算子、リテラル)のストリームに変換するプロセスです。このフェーズで、コンパイラはソースコードの文字を読み込み、トークンにグループ化します。行番号の管理もこのフェーズで行われることが多いです。
  • EOF (End Of File): ファイルの終端を示す特殊なマーカーです。コンパイラがソースコードの読み込み中にEOFに達すると、それ以上入力がないことを認識します。
  • 構文エラー (Syntax Error): プログラミング言語の文法規則に違反している場合に発生するエラーです。例えば、括弧の閉じ忘れ、セミコロンの欠落、キーワードの誤用などが挙げられます。コンパイラは構文解析フェーズでこれらのエラーを検出します。
  • 行番号の管理: コンパイラは、エラーメッセージやデバッグ情報のために、現在処理しているソースコードの行番号を追跡します。これは通常、改行文字の検出によって行われます。

この問題は、字句解析器がEOFを処理する際に、行番号のインクリメントロジックが特定のシナリオで誤動作することに起因していました。

技術的詳細

この修正は、主にGoコンパイラの字句解析部分 (src/cmd/gc/lex.c) とエラー報告部分 (src/cmd/gc/subr.c) に変更を加えています。

  1. src/cmd/gc/lex.c の変更:

    • getc 関数は、コンパイラがソースコードから次の文字を読み込むための内部関数です。
    • 変更前は、getc 関数内で改行文字 (\n) を検出した場合に lexlineno(現在の字句解析の行番号)をインクリメントしていました。
    • 修正では、peekcpeekc1 を使用した文字の先読みロジックから、改行文字による lexlineno の直接的なインクリメントを削除し、goto check; を導入しています。これにより、文字の取得と行番号のチェックロジックが分離され、check: ラベル以降の共通の処理で改行文字の処理が行われるようになります。
    • この変更の意図は、EOF時に内部的に挿入される「偽の改行」が、本来のソースコードの行番号に影響を与えないようにすることです。
  2. src/cmd/gc/subr.c の変更:

    • yyerror 関数は、コンパイラが構文エラーを報告する際に使用される関数です。
    • この関数に、curio.eofnl という新しいフラグをチェックするロジックが追加されました。curio.eofnl は、予期せぬEOFによって偽の改行文字が挿入されたことを示すフラグであると推測されます。
    • もし curio.eofnl が真であれば、lexlineno(現在の行番号)を prevlineno(直前の行番号)に戻すことで、行番号のずれを修正します。これは、EOFによる構文エラーの場合に、偽の改行によって誤ってインクリメントされた行番号を元に戻し、正確なエラー位置を指すようにするためです。
    • また、デバッグフラグ -xx が有効な場合に yyerror のデバッグ情報を出力するコードも追加されています。
  3. test/fixedbugs/bug435.go の追加:

    • この新しいテストファイルは、Issue 3392で報告されたバグを再現し、修正が正しく機能することを確認するためのものです。
    • // errorcheck ディレクティブは、このファイルがコンパイルエラーを意図しており、特定のパターン(ERROR "unexpected")のエラーメッセージが出力されることを期待していることを示します。
    • func foo() { bar(1, // ERROR "unexpected" } というコードは、bar(1, の後に引数が不足している状態でEOFに達するため、構文エラーが発生します。このテストは、このエラーが正しい行番号で報告されることを検証します。

これらの変更により、GoコンパイラはEOF関連の構文エラーに対して、より正確な行番号を報告できるようになりました。

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

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1531,9 +1531,7 @@ getc(void)
 	if(c != 0) {
 		curio.peekc = curio.peekc1;
 		curio.peekc1 = 0;
-		if(c == '\n' && pushedio.bin == nil)
-			lexlineno++;
-		return c;
+		goto check;
 	}
 	
 	if(curio.bin == nil) {
@@ -1543,6 +1541,7 @@ getc(void)
 		tc = Bgetc(curio.bin);
 
+check:
 	switch(c) {
 	case 0:
 		if(curio.bin != nil) {

src/cmd/gc/subr.c

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -142,6 +142,11 @@ yyerror(char *fmt, ...)
 		if(debug['x'])	
 			print("yyerror: yystate=%d yychar=%d\n", yystate, yychar);
 
+		// An unexpected EOF caused a syntax error. Use the previous
+		// line number since getc generated a fake newline character.
+		if(curio.eofnl)
+			lexlineno = prevlineno;
+
 		// only one syntax error per line
 		if(lastsyntax == lexlineno)
 			return;

test/fixedbugs/bug435.go

--- /dev/null
+++ b/test/fixedbugs/bug435.go
@@ -0,0 +1,15 @@
+// errorcheck
+
+// Copyright 2012 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Test that a syntax error caused by an unexpected EOF
+// gives an error message with the correct line number.
+//
+// https://code.google.com/p/go/issues/detail?id=3392
+
+package main
+
+func foo() {
+	bar(1, // ERROR "unexpected"

コアとなるコードの解説

src/cmd/gc/lex.c の変更点

  • getc 関数の変更:
    • 変更前は、peekcpeekc1 を使った先読みバッファから文字を取得した後、その文字が改行文字 (\n) であり、かつ pushedio.binnil でない場合に lexlineno をインクリメントしていました。これは、通常の文字読み込みにおける行番号の更新ロジックです。
    • 変更後は、この特定の改行文字による lexlineno のインクリメントロジックが削除され、代わりに goto check; が追加されました。これにより、文字の取得と、その文字が改行であるかどうかのチェック、そしてそれに基づく行番号の更新が、check: ラベル以降の共通の処理ブロックで行われるようになりました。
    • この変更の主な目的は、EOF時にコンパイラが内部的に生成する「偽の改行文字」が、実際のソースコードの行番号に影響を与えないようにすることです。これにより、yyerror 関数で正確な行番号を計算するための前提が整います。

src/cmd/gc/subr.c の変更点

  • yyerror 関数の変更:
    • yyerror は構文エラーが発生した際に呼び出される関数です。
    • 追加されたコードブロックは、curio.eofnl というフラグをチェックします。このフラグは、字句解析器が予期せぬEOFに遭遇し、それによって内部的に偽の改行文字が挿入された場合に真となるものと推測されます。
    • もし curio.eofnl が真であれば、lexlineno = prevlineno; が実行されます。これは、現在の行番号 (lexlineno) を直前の行番号 (prevlineno) に戻すことを意味します。
    • この修正により、EOFによる構文エラーの場合に、偽の改行文字によって誤って1行進んでしまった行番号を元に戻し、エラーが実際に発生したソースコードの行を正確に指すようにします。これにより、ユーザーに報告されるエラーメッセージの行番号が正確になります。
    • debug['x'] が有効な場合のデバッグ出力も追加されており、yyerror が呼び出された際の yystateyychar の値を確認できるようになっています。

test/fixedbugs/bug435.go の追加

  • このファイルは、Goコンパイラのテストスイートに追加された新しいテストケースです。
  • // errorcheck コメントは、このテストがコンパイルエラーを期待していることをコンパイラテストツールに伝えます。
  • func foo() { bar(1, // ERROR "unexpected" } というコードは、bar(1, の後に引数が不足している状態でファイルの終端に達するため、構文エラーが発生します。
  • // ERROR "unexpected" は、この行で「unexpected」という文字列を含むエラーメッセージが出力されることを期待していることを示します。このテストは、このエラーがこの行で正確に報告されることを検証し、修正が正しく機能していることを確認します。

これらの変更は、Goコンパイラの堅牢性とユーザーエクスペリエンスを向上させる上で重要な役割を果たしています。

関連リンク

参考にした情報源リンク