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

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

このコミットは、Goコンパイラ(gc)における//lineディレクティブのファイル名解析に関するバグ修正です。具体的には、ファイル名にコロン(:)が含まれる場合に、正しく行番号を認識できない問題を解決します。

コミット

commit ecda0fa5d4a448806bdea15e506b11bb71798d8a
Author: Russ Cox <rsc@golang.org>
Date:   Mon Dec 12 15:41:54 2011 -0500

    gc: allow colon in //line file name
    
    Assume last colon introduces line number.
    
    Fixes #2543.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5485047

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

https://github.com/golang/go/commit/ecda0fa5d4a448806bdea15e506b11bb71798d8a

元コミット内容

gc: allow colon in //line file name Assume last colon introduces line number. Fixes #2543.

変更の背景

この変更は、Go言語のIssue 2543「gc: //line directive doesn't handle colons in filenames」を修正するために行われました。

Go言語のコンパイラには、//lineディレクティブという特殊なコメント行があります。これは、コンパイラに対して、その後のコードが指定されたファイルと行番号から来ているかのように扱うよう指示するものです。主に、コード生成ツール(例: yacclexのようなツール、あるいはGoのgo generateで利用されるツール)が生成したコードのデバッグ情報を元のソースコードにマッピングするために使用されます。これにより、生成されたコードでエラーが発生した場合でも、ユーザーは元のソースコードのどの部分が原因であるかを特定できます。

しかし、従来のgcコンパイラの実装では、//lineディレクティブのファイル名部分にコロン(:)が含まれていると、そのコロンをファイル名と行番号の区切りと誤認識してしまう問題がありました。特にWindows環境では、パスにドライブレター(例: C:\path\to\file.go)が含まれるため、この問題が顕著でした。C:のコロンが行番号の区切りと解釈され、ファイル名が途中で切れてしまい、正しいデバッグ情報が生成されない、あるいはコンパイルエラーになる可能性がありました。

このコミットは、この問題を解決し、ファイル名にコロンが含まれていても//lineディレクティブが正しく機能するようにすることを目的としています。

前提知識の解説

//line ディレクティブ

//lineディレクティブは、Go言語のソースコード内で使用される特殊なコメント形式です。その構文は通常、//line filename:line_numberのようになります。 例: //line generated.go:100

このディレクティブは、コンパイラに対して、そのディレクティブ以降のコードが、指定されたfilenameline_numberから来ているものとして処理するよう指示します。これにより、以下のような利点があります。

  • デバッグ情報の改善: コード生成ツールが元のソースコードからGoコードを生成する際、生成されたコードのエラーメッセージやスタックトレースを、元のソースコードのファイル名と行番号にマッピングできます。
  • エラー報告の正確性: コンパイルエラーが発生した場合、ユーザーは生成されたコードではなく、元の意図したソースコードの場所を指摘されるため、問題の特定が容易になります。

Goコンパイラ (gc) の構造と lex.c

Goコンパイラ(gc)は、Go言語のソースコードを機械語に変換する主要なツールです。その処理は複数のフェーズに分かれています。

  1. 字句解析 (Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。
  2. 構文解析 (Syntactic Analysis): トークンのストリームを解析し、抽象構文木(AST)を構築します。
  3. 型チェック (Type Checking): ASTに対して型の一貫性を検証します。
  4. 中間コード生成 (Intermediate Code Generation): ASTを中間表現に変換します。
  5. 最適化 (Optimization): 中間コードを最適化します。
  6. コード生成 (Code Generation): 最適化された中間コードをターゲットアーキテクチャの機械語に変換します。

このコミットで変更されたsrc/cmd/gc/lex.cファイルは、Goコンパイラの字句解析フェーズの一部を担っています。特に、//lineディレクティブのような特殊なコメントを処理し、その中のファイル名や行番号を抽出するロジックが含まれています。getlinepragma関数は、この//lineディレクティブの解析を担当する関数です。

技術的詳細

変更の核心は、getlinepragma関数が//lineディレクティブのファイル名と行番号をどのように区別するか、という点にあります。

変更前: getlinepragma関数は、//lineディレクティブのファイル名部分を読み込む際に、最初に現れるコロン(:)を行番号の区切りと見なしていました。 例えば、//line C:\path\to\file.go:100というディレクティブがあった場合、コンパイラはCの後のコロンを区切りと判断し、ファイル名をC、行番号を\path\to\file.go:100として解釈しようとしていました。これは明らかに誤りです。

変更後: このコミットでは、この解析ロジックを修正し、最後のコロンが行番号の区切りであると仮定するように変更しました。

  1. linepという新しいポインタ変数を導入しました。これは、ファイル名部分を読み進める中でコロンが見つかるたびに、そのコロンの位置を記録します。
  2. ファイル名部分の読み込みループは、コロンが見つかっても中断せず、改行文字(\n)またはファイルの終端(EOF)に達するまで続行します。
  3. ループが終了した後、linepが指す位置(つまり、最後に発見されたコロンの位置)にヌル終端文字(\0)を挿入します。これにより、ファイル名が正しく区切られます。
  4. 行番号の解析は、linepの次の文字から開始されます。行番号は数字のみで構成されるため、その部分を数値としてパースします。

この変更により、C:\path\to\file.go:100のようなパスでも、最後のコロン(:100の前のコロン)が正しく行番号の区切りとして認識され、ファイル名がC:\path\to\file.go、行番号が100と正確に解析されるようになります。

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

src/cmd/gc/lex.cファイルのgetlinepragma関数における変更点です。

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1336,7 +1336,7 @@ static int
 getlinepragma(void)
 {
  	int i, c, n;
- 	char *cp, *ep;
+ 	char *cp, *ep, *linep;
  	Hist *h;
  
  	for(i=0; i<5; i++) {
@@ -1347,32 +1347,36 @@ getlinepragma(void)
  
  	cp = lexbuf;
  	ep = lexbuf+sizeof(lexbuf)-5;
+ 	linep = nil;
  	for(;;) {
  	 	c = getr();
- 	 	if(c == '\n' || c == EOF)
+ 	 	if(c == EOF)
  	 	 	goto out;
+ 	 	if(c == '\n')
+ 	 	 	break;
  	 	if(c == ' ')
  	 	 	continue;
  	 	if(c == ':')
- 	 	 	break;
+ 	 	 	linep = cp;
  	 	if(cp < ep)
  	 	 	*cp++ = c;
  	}
  	*cp = 0;
  
+ 	if(linep == nil || linep >= ep)
+ 	 	goto out;
+ 	*linep++ = '\0';
  	n = 0;
- 	for(;;) {
- 	 	c = getr();
- 	 	if(!yy_isdigit(c))
- 	 	 	break;
- 	 	n = n*10 + (c-'0');
+ 	for(cp=linep; *cp; cp++) {
+ 	 	if(*cp < '0' || *cp > '9')
+ 	 	 	goto out;
+ 	 	n = n*10 + *cp - '0';
  	 	if(n > 1e8) {
  	 	 	yyerror("line number out of range");
  	 	 	errorexit();
  	 	}
  	}
-
- 	if(c != '\n' || n <= 0)
+ 	if(n <= 0)
  	 	goto out;
  
  	// try to avoid allocating file name over and over

コアとなるコードの解説

  1. char *cp, *ep, *linep;:

    • linepという新しいポインタが追加されました。これは、ファイル名部分を読み込む際に最後に検出されたコロンの位置を記憶するために使用されます。
  2. linep = nil;:

    • linepが初期化され、コロンがまだ見つかっていない状態を示します。
  3. ファイル名解析ループの変更:

    • if(c == '\n' || c == EOF)if(c == EOF)if(c == '\n') break; に分割されました。これにより、改行文字が見つかった場合でも、すぐにループを抜けるのではなく、ファイル名部分の読み込みを完了させ、その後に改行文字を処理するようになりました。
    • if(c == ':') break;if(c == ':') linep = cp; に変更されました。これは最も重要な変更点です。以前はコロンが見つかるとすぐにファイル名の読み込みを中断していましたが、変更後はコロンが見つかっても読み込みを続行し、そのコロンの位置をlinepに記録するだけになりました。これにより、ファイル名に複数のコロンが含まれていても、最後のコロンが区切りとして扱われるようになります。
  4. ファイル名と行番号の区切り処理:

    • if(linep == nil || linep >= ep) goto out; が追加されました。これは、コロンが全く見つからなかった場合、またはコロンがバッファの終端近くで見つかり、行番号をパースするスペースがない場合にエラーとして処理するためのガードです。
    • *linep++ = '\0'; が追加されました。これは、linepが指す最後のコロンの位置にヌル終端文字を挿入することで、ファイル名文字列をそこで区切ります。linepはその後、行番号の開始位置を指すようにインクリメントされます。
  5. 行番号解析ループの変更:

    • 行番号の解析ロジックが for(cp=linep; *cp; cp++) に変更されました。以前はgetr()で一文字ずつ読み込んでいましたが、変更後はlinepが指す位置からヌル終端までを文字列として扱い、その中の文字が数字であるかをチェックしながら数値に変換します。これにより、ファイル名と行番号の区切りが明確になったため、行番号部分の解析がより堅牢になりました。
  6. 終了条件の変更:

    • if(c != '\n' || n <= 0)if(n <= 0) に変更されました。ファイル名解析ループで改行文字の処理が改善されたため、行番号が0以下であるかどうかのチェックのみで十分になりました。

これらの変更により、//lineディレクティブのファイル名にコロンが含まれていても、最後のコロンが行番号の区切りとして正しく認識され、コンパイラが正確なデバッグ情報を生成できるようになりました。

関連リンク

参考にした情報源リンク

  • Go Issue 2543の議論内容
  • Go言語の//lineディレクティブに関する一般的なドキュメント(Go言語の公式ドキュメントや関連ブログ記事)
  • Goコンパイラのソースコード(src/cmd/gc/lex.c
  • 字句解析、構文解析に関する一般的なコンパイラ理論の知識

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

このコミットは、Goコンパイラ(gc)における//lineディレクティブのファイル名解析に関するバグ修正です。具体的には、ファイル名にコロン(:)が含まれる場合に、正しく行番号を認識できない問題を解決します。

コミット

commit ecda0fa5d4a448806bdea15e506b11bb71798d8a
Author: Russ Cox <rsc@golang.org>
Date:   Mon Dec 12 15:41:54 2011 -0500

    gc: allow colon in //line file name
    
    Assume last colon introduces line number.
    
    Fixes #2543.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5485047

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

https://github.com/golang/go/commit/ecda0fa5d4a448806bdea15e506b11bb71798d8a

元コミット内容

gc: allow colon in //line file name Assume last colon introduces line number. Fixes #2543.

変更の背景

この変更は、Go言語のIssue 2543「gc: //line directive doesn't handle colons in filenames」を修正するために行われました。

Go言語のコンパイラには、//lineディレクティブという特殊なコメント行があります。これは、コンパイラに対して、その後のコードが指定されたファイルと行番号から来ているかのように扱うよう指示するものです。主に、コード生成ツール(例: yacclexのようなツール、あるいはGoのgo generateで利用されるツール)が生成したコードのデバッグ情報を元のソースコードにマッピングするために使用されます。これにより、生成されたコードでエラーが発生した場合でも、ユーザーは元のソースコードのどの部分が原因であるかを特定できます。

しかし、従来のgcコンパイラの実装では、//lineディレクティブのファイル名部分にコロン(:)が含まれていると、そのコロンをファイル名と行番号の区切りと誤認識してしまう問題がありました。特にWindows環境では、パスにドライブレター(例: C:\path\to\file.go)が含まれるため、この問題が顕著でした。C:のコロンが行番号の区切りと解釈され、ファイル名が途中で切れてしまい、正しいデバッグ情報が生成されない、あるいはコンパイルエラーになる可能性がありました。

このコミットは、この問題を解決し、ファイル名にコロンが含まれていても//lineディレクティブが正しく機能するようにすることを目的としています。

前提知識の解説

//line ディレクティブ

//lineディレクティブは、Go言語のソースコード内で使用される特殊なコメント形式です。その構文は通常、//line filename:line_numberのようになります。 例: //line generated.go:100

このディレクティブは、コンパイラに対して、そのディレクティブ以降のコードが、指定されたfilenameline_numberから来ているものとして処理するよう指示します。これにより、以下のような利点があります。

  • デバッグ情報の改善: コード生成ツールが元のソースコードからGoコードを生成する際、生成されたコードのエラーメッセージやスタックトレースを、元のソースコードのファイル名と行番号にマッピングできます。
  • エラー報告の正確性: コンパイルエラーが発生した場合、ユーザーは生成されたコードではなく、元の意図したソースコードの場所を指摘されるため、問題の特定が容易になります。

Goコンパイラ (gc) の構造と lex.c

Goコンパイラ(gc)は、Go言語のソースコードを機械語に変換する主要なツールです。その処理は複数のフェーズに分かれています。

  1. 字句解析 (Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。
  2. 構文解析 (Syntactic Analysis): トークンのストリームを解析し、抽象構文木(AST)を構築します。
  3. 型チェック (Type Checking): ASTに対して型の一貫性を検証します。
  4. 中間コード生成 (Intermediate Code Generation): ASTを中間表現に変換します。
  5. 最適化 (Optimization): 中間コードを最適化します。
  6. コード生成 (Code Generation): 最適化された中間コードをターゲットアーキテクチャの機械語に変換します。

このコミットで変更されたsrc/cmd/gc/lex.cファイルは、Goコンパイラの字句解析フェーズの一部を担っています。特に、//lineディレクティブのような特殊なコメントを処理し、その中のファイル名や行番号を抽出するロジックが含まれています。getlinepragma関数は、この//lineディレクティブの解析を担当する関数です。

技術的詳細

変更の核心は、getlinepragma関数が//lineディレクティブのファイル名と行番号をどのように区別するか、という点にあります。

変更前: getlinepragma関数は、//lineディレクティブのファイル名部分を読み込む際に、最初に現れるコロン(:)を行番号の区切りと見なしていました。 例えば、//line C:\path\to\file.go:100というディレクティブがあった場合、コンパイラはCの後のコロンを区切りと判断し、ファイル名をC、行番号を\path\to\file.go:100として解釈しようとしていました。これは明らかに誤りです。

変更後: このコミットでは、この解析ロジックを修正し、最後のコロンが行番号の区切りであると仮定するように変更しました。

  1. linepという新しいポインタ変数を導入しました。これは、ファイル名部分を読み進める中でコロンが見つかるたびに、そのコロンの位置を記録します。
  2. ファイル名部分の読み込みループは、コロンが見つかっても中断せず、改行文字(\n)またはファイルの終端(EOF)に達するまで続行します。
  3. ループが終了した後、linepが指す位置(つまり、最後に発見されたコロンの位置)にヌル終端文字(\0)を挿入します。これにより、ファイル名が正しく区切られます。
  4. 行番号の解析は、linepの次の文字から開始されます。行番号は数字のみで構成されるため、その部分を数値としてパースします。

この変更により、C:\path\to\file.go:100のようなパスでも、最後のコロン(:100の前のコロン)が正しく行番号の区切りとして認識され、ファイル名がC:\path\to\file.go、行番号が100と正確に解析されるようになります。

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

src/cmd/gc/lex.cファイルのgetlinepragma関数における変更点です。

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1336,7 +1336,7 @@ static int
 getlinepragma(void)
 {
  	int i, c, n;
- 	char *cp, *ep;
+ 	char *cp, *ep, *linep;
  	Hist *h;
  
  	for(i=0; i<5; i++) {
@@ -1347,32 +1347,36 @@ getlinepragma(void)
  
  	cp = lexbuf;
  	ep = lexbuf+sizeof(lexbuf)-5;
+ 	linep = nil;
  	for(;;) {
  	 	c = getr();
- 	 	if(c == '\n' || c == EOF)
+ 	 	if(c == EOF)
  	 	 	goto out;
+ 	 	if(c == '\n')
+ 	 	 	break;
  	 	if(c == ' ')
  	 	 	continue;
  	 	if(c == ':')
- 	 	 	break;
+ 	 	 	linep = cp;
  	 	if(cp < ep)
  	 	 	*cp++ = c;
  	}
  	*cp = 0;
  
+ 	if(linep == nil || linep >= ep)
+ 	 	goto out;
+ 	*linep++ = '\0';
  	n = 0;
- 	for(;;) {
- 	 	c = getr();
- 	 	if(!yy_isdigit(c))
- 	 	 	break;
- 	 	n = n*10 + (c-'0');
+ 	for(cp=linep; *cp; cp++) {
+ 	 	if(*cp < '0' || *cp > '9')
+ 	 	 	goto out;
+ 	 	n = n*10 + *cp - '0';
  	 	if(n > 1e8) {
  	 	 	yyerror("line number out of range");
  	 	 	errorexit();
  	 	}
  	}
-
- 	if(c != '\n' || n <= 0)
+ 	if(n <= 0)
  	 	goto out;
  
  	// try to avoid allocating file name over and over

コアとなるコードの解説

  1. char *cp, *ep, *linep;:

    • linepという新しいポインタが追加されました。これは、ファイル名部分を読み込む際に最後に検出されたコロンの位置を記憶するために使用されます。
  2. linep = nil;:

    • linepが初期化され、コロンがまだ見つかっていない状態を示します。
  3. ファイル名解析ループの変更:

    • if(c == '\n' || c == EOF)if(c == EOF)if(c == '\n') break; に分割されました。これにより、改行文字が見つかった場合でも、すぐにループを抜けるのではなく、ファイル名部分の読み込みを完了させ、その後に改行文字を処理するようになりました。
    • if(c == ':') break;if(c == ':') linep = cp; に変更されました。これは最も重要な変更点です。以前はコロンが見つかるとすぐにファイル名の読み込みを中断していましたが、変更後はコロンが見つかっても読み込みを続行し、そのコロンの位置をlinepに記録するだけになりました。これにより、ファイル名に複数のコロンが含まれていても、最後のコロンが区切りとして扱われるようになります。
  4. ファイル名と行番号の区切り処理:

    • if(linep == nil || linep >= ep) goto out; が追加されました。これは、コロンが全く見つからなかった場合、またはコロンがバッファの終端近くで見つかり、行番号をパースするスペースがない場合にエラーとして処理するためのガードです。
    • *linep++ = '\0'; が追加されました。これは、linepが指す最後のコロンの位置にヌル終端文字を挿入することで、ファイル名文字列をそこで区切ります。linepはその後、行番号の開始位置を指すようにインクリメントされます。
  5. 行番号解析ループの変更:

    • 行番号の解析ロジックが for(cp=linep; *cp; cp++) に変更されました。以前はgetr()で一文字ずつ読み込んでいましたが、変更後はlinepが指す位置からヌル終端までを文字列として扱い、その中の文字が数字であるかをチェックしながら数値に変換します。これにより、ファイル名と行番号の区切りが明確になったため、行番号部分の解析がより堅牢になりました。
  6. 終了条件の変更:

    • if(c != '\n' || n <= 0)if(n <= 0) に変更されました。ファイル名解析ループで改行文字の処理が改善されたため、行番号が0以下であるかどうかのチェックのみで十分になりました。

これらの変更により、//lineディレクティブのファイル名にコロンが含まれていても、最後のコロンが行番号の区切りとして正しく認識され、コンパイラが正確なデバッグ情報を生成できるようになりました。

関連リンク

参考にした情報源リンク

  • Go Issue 2543の議論内容
  • Go言語の//lineディレクティブに関する一般的なドキュメント(Go言語の公式ドキュメントや関連ブログ記事)
  • Goコンパイラのソースコード(src/cmd/gc/lex.c
  • 字句解析、構文解析に関する一般的なコンパイラ理論の知識