[インデックス 17823] ファイルの概要
このコミットは、Go言語のcmd/cgo
ツールにおけるエラーメッセージの行番号の修正に関するものです。具体的には、Cgoが生成するエラーメッセージにおいて、正しいソースコードの行番号が報告されるように改善されています。
コミット
commit dbe2eacf04898a9c77684424ec7c62700d08fb0c
Author: Russ Cox <rsc@golang.org>
Date: Fri Oct 18 16:52:44 2013 -0400
cmd/cgo: fix line number in an error message
Fixes #6563.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/14870046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dbe2eacf04898a9c77684424ec7c62700d08fb0c
元コミット内容
cmd/cgo: fix line number in an error message
Fixes #6563.
このコミットは、cmd/cgo
ツールが生成するエラーメッセージにおいて、誤った行番号が報告される問題を修正することを目的としています。これはGoのIssue 6563に対応するものです。
変更の背景
Go言語のcgo
ツールは、GoプログラムからCコードを呼び出すためのメカニズムを提供します。Cgoは、GoとCの間のインターフェースコードを生成する際に、GoのAST(抽象構文木)を操作します。この操作の過程で、元のGoソースコードのASTノードが新しいASTノードに置き換えられることがあります。
問題は、ASTノードが置き換えられた際に、新しいASTノードが元のノードの正確な位置情報(ファイル名と行番号)を引き継がない場合があることでした。これにより、Cgoがエラーを報告する際に、実際のエラーが発生した場所とは異なる行番号が表示されてしまい、デバッグが困難になるというユーザーエクスペリエンス上の問題が発生していました。
特に、new(T)
のような式が&T{}
に書き換えられたり、変数の参照がポインタ参照に書き換えられたりする際に、この位置情報の欠落が顕著でした。Issue 6563では、C.malloc(s)
のようなCgo呼び出しでエラーが発生した際に、誤った行番号が報告される具体的なケースが挙げられています。
このコミットは、このようなAST変換後も正確な位置情報が保持されるようにすることで、Cgoのエラーメッセージの精度を向上させ、開発者が問題をより迅速に特定できるようにすることを目的としています。
前提知識の解説
- cgo: Go言語の標準ツールの一つで、GoプログラムからC言語の関数を呼び出したり、C言語のコードをGoプログラムに埋め込んだりするための機能を提供します。Cgoは、GoとCの間のバインディングコードを生成し、コンパイルプロセスを管理します。
- AST (Abstract Syntax Tree): 抽象構文木。ソースコードの構文構造を木構造で表現したものです。コンパイラやリンタなどのツールは、ソースコードをASTに変換し、その木構造を操作することで、コードの解析、変換、最適化などを行います。Goコンパイラも、GoのソースコードをASTにパースし、その後の処理に利用します。
token.Pos
: Go言語のgo/token
パッケージで定義されている型で、ソースコード内の位置(ファイル、行番号、列番号)を表します。ASTノードは通常、このtoken.Pos
情報を持っており、エラー報告やデバッグの際に利用されます。ast.Expr
: Go言語のgo/ast
パッケージで定義されているインターフェースで、Goの式(expression)を表すASTノードの基本型です。ast.StarExpr
: ポインタのデリファレンス(*x
)やポインタ型(*T
)を表すASTノードです。ast.Ident
: 識別子(変数名、関数名など)を表すASTノードです。rewriteRef
関数:src/cmd/cgo/gcc.go
内に存在する関数で、CgoがGoのASTを操作し、Cgo固有の変換を行う際に使用されます。この関数は、Goの式をCgoが処理しやすい形に書き換える役割を担っています。
技術的詳細
このコミットの主要な技術的変更は、src/cmd/cgo/gcc.go
内のrewriteRef
関数に集中しています。この関数は、GoのASTを走査し、CgoがC関数呼び出しやC型との相互作用を処理するために必要な変換を行います。
問題は、rewriteRef
関数がASTノードを置き換える際に、新しいノードが元のノードのtoken.Pos
情報(ソースコード上の位置)を適切に引き継いでいなかった点にありました。これにより、Cgoがエラーを報告する際に、エラーが発生した実際の行番号ではなく、変換後のASTノードが持つデフォルトの、あるいは不正確な位置情報が使われてしまっていました。
このコミットでは、以下の2つの主要な変更が導入されています。
-
ast.StarExpr
のStar
フィールドへの位置情報の追加: 以前のコードでは、変数の参照がポインタ参照に書き換えられる際(例:x
が*x
に変換される場合)、ast.StarExpr
ノードが作成されていましたが、そのStar
フィールド(アスタリスクの位置を表す)には位置情報が設定されていませんでした。 変更前:expr = &ast.StarExpr{X: expr}
変更後:expr = &ast.StarExpr{Star: (*r.Expr).Pos(), X: expr}
これにより、新しく生成されるast.StarExpr
ノードが、元の式(*r.Expr)
の開始位置をStar
フィールドに持つようになり、より正確な位置情報が保持されるようになりました。 -
変換後のASTノードへの位置情報のコピー:
rewriteRef
関数は、様々な種類のast.Expr
を変換します。変換後の新しいast.Expr
が、元のast.Expr
が持っていた正確な位置情報を持つように、明示的に位置情報をコピーするロジックが追加されました。 特に、ast.Ident
(識別子)の場合に、元の式の位置情報((*r.Expr).Pos()
)を新しいast.Ident
のNamePos
フィールドにコピーしています。// Copy position information from old expr into new expr, // in case expression being replaced is first on line. // See golang.org/issue/6563. pos := (*r.Expr).Pos() switch x := expr.(type) { case *ast.Ident: expr = &ast.Ident{NamePos: pos, Name: x.Name} }
この変更により、式が置き換えられた後も、その式がソースコードのどの位置に存在していたかという情報が失われずに済みます。これは、特にエラーメッセージの行番号の正確性を保証するために重要です。
これらの変更により、CgoがASTを変換する際に、元のソースコードの位置情報がより適切に保持されるようになり、結果としてエラーメッセージの行番号が正確に報告されるようになりました。
テストケースとして、misc/cgo/errors/err1.go
とmisc/cgo/errors/err2.go
が追加・修正されています。
err1.go
はCコード内の構文エラー(xxx;
)を、err2.go
はGoコードからC関数を不正に呼び出すエラー(C.malloc(s)
)を意図的に発生させ、これらのエラーが正しい行番号で報告されることをtest.bash
スクリプトで検証しています。test.bash
スクリプトも、複数のエラーケースをチェックできるように汎用化されています。
コアとなるコードの変更箇所
src/cmd/cgo/gcc.go
のrewriteRef
関数内。
--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -654,7 +654,7 @@ func (p *Package) rewriteRef(f *File) {
// Okay - might be new(T)
expr = r.Name.Type.Go
} else if r.Name.Kind == "var" {
- expr = &ast.StarExpr{X: expr}
+ expr = &ast.StarExpr{Star: (*r.Expr).Pos(), X: expr}
}
case "type":
@@ -683,6 +683,16 @@ func (p *Package) rewriteRef(f *File) {
}
}
}
+
+ // Copy position information from old expr into new expr,
+ // in case expression being replaced is first on line.
+ // See golang.org/issue/6563.
+ pos := (*r.Expr).Pos()
+ switch x := expr.(type) {
+ case *ast.Ident:
+ expr = &ast.Ident{NamePos: pos, Name: x.Name}
+ }
+
*r.Expr = expr
}
コアとなるコードの解説
上記の差分は、rewriteRef
関数におけるASTノードの変換と位置情報の伝播に関する修正を示しています。
-
expr = &ast.StarExpr{Star: (*r.Expr).Pos(), X: expr}
: この行は、Goの変数がCgoによってポインタ参照として扱われる必要がある場合に、ast.StarExpr
(ポインタデリファレンスまたはポインタ型)を構築する部分です。 変更前はStar
フィールドが設定されていませんでしたが、変更後は(*r.Expr).Pos()
、つまり元の式r.Expr
の開始位置をStar
フィールドに明示的に設定しています。これにより、*
記号がソースコードのどの位置に対応するかという情報が正確に保持されます。これは、Cgoが生成するエラーメッセージにおいて、ポインタ関連のエラーが正確な行番号で報告されるために重要です。 -
位置情報のコピーブロック:
// Copy position information from old expr into new expr, // in case expression being replaced is first on line. // See golang.org/issue/6563. pos := (*r.Expr).Pos() switch x := expr.(type) { case *ast.Ident: expr = &ast.Ident{NamePos: pos, Name: x.Name} }
このブロックは、
rewriteRef
関数が式expr
を変換した後に実行されます。pos := (*r.Expr).Pos()
で、変換前の元の式r.Expr
の開始位置を取得します。switch x := expr.(type)
で、変換後のexpr
の型をチェックします。case *ast.Ident:
は、変換後の式が識別子(変数名など)である場合に適用されます。expr = &ast.Ident{NamePos: pos, Name: x.Name}
では、新しいast.Ident
ノードを構築し、そのNamePos
フィールドに、元の式の位置情報pos
をコピーしています。これにより、識別子が置き換えられた場合でも、その識別子がソースコードのどの行に存在していたかという情報が失われず、Cgoのエラーメッセージが正確な行番号を指すようになります。
これらの変更は、CgoがGoのASTを操作する際の「位置情報の透過性」を向上させることを目的としています。つまり、ASTが変換されても、その変換が元のソースコードのどの部分に対応するかという情報が正確に維持されるようにすることで、コンパイラやツールがより正確なエラー報告やデバッグ情報を提供できるようになります。
関連リンク
- Go Issue 6563: https://github.com/golang/go/issues/6563
- Go CL 14870046: https://golang.org/cl/14870046 (このコミットに対応するGoのコードレビューリンク)
参考にした情報源リンク
- Go Issue 6563の議論
- Go CL 14870046のコードレビューとコメント
- Go言語の
go/ast
およびgo/token
パッケージのドキュメント (Goソースコード内またはGo公式ドキュメント) - Cgoのドキュメント (Go公式ドキュメント)
- AST (抽象構文木) に関する一般的な情報