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

[インデックス 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つの主要な変更が導入されています。

  1. ast.StarExprStarフィールドへの位置情報の追加: 以前のコードでは、変数の参照がポインタ参照に書き換えられる際(例: x*xに変換される場合)、ast.StarExprノードが作成されていましたが、そのStarフィールド(アスタリスクの位置を表す)には位置情報が設定されていませんでした。 変更前: expr = &ast.StarExpr{X: expr} 変更後: expr = &ast.StarExpr{Star: (*r.Expr).Pos(), X: expr} これにより、新しく生成されるast.StarExprノードが、元の式(*r.Expr)の開始位置をStarフィールドに持つようになり、より正確な位置情報が保持されるようになりました。

  2. 変換後のASTノードへの位置情報のコピー: rewriteRef関数は、様々な種類のast.Exprを変換します。変換後の新しいast.Exprが、元のast.Exprが持っていた正確な位置情報を持つように、明示的に位置情報をコピーするロジックが追加されました。 特に、ast.Ident(識別子)の場合に、元の式の位置情報((*r.Expr).Pos())を新しいast.IdentNamePosフィールドにコピーしています。

    // 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.gomisc/cgo/errors/err2.goが追加・修正されています。 err1.goはCコード内の構文エラー(xxx;)を、err2.goはGoコードからC関数を不正に呼び出すエラー(C.malloc(s))を意図的に発生させ、これらのエラーが正しい行番号で報告されることをtest.bashスクリプトで検証しています。test.bashスクリプトも、複数のエラーケースをチェックできるように汎用化されています。

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

src/cmd/cgo/gcc.gorewriteRef関数内。

--- 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ノードの変換と位置情報の伝播に関する修正を示しています。

  1. expr = &ast.StarExpr{Star: (*r.Expr).Pos(), X: expr}: この行は、Goの変数がCgoによってポインタ参照として扱われる必要がある場合に、ast.StarExpr(ポインタデリファレンスまたはポインタ型)を構築する部分です。 変更前はStarフィールドが設定されていませんでしたが、変更後は(*r.Expr).Pos()、つまり元の式r.Exprの開始位置をStarフィールドに明示的に設定しています。これにより、*記号がソースコードのどの位置に対応するかという情報が正確に保持されます。これは、Cgoが生成するエラーメッセージにおいて、ポインタ関連のエラーが正確な行番号で報告されるために重要です。

  2. 位置情報のコピーブロック:

    // 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の議論
  • Go CL 14870046のコードレビューとコメント
  • Go言語のgo/astおよびgo/tokenパッケージのドキュメント (Goソースコード内またはGo公式ドキュメント)
  • Cgoのドキュメント (Go公式ドキュメント)
  • AST (抽象構文木) に関する一般的な情報