[インデックス 15077] ファイルの概要
このコミットは、Go言語のcmd/cgo
ツールにおける、生成されるCコードの行番号アノテーションの不正確さを修正するものです。具体的には、go/ast
パッケージのCommentGroup.Text
メソッドがコメントの先頭の空白行を削除してしまうために発生していた、行番号のずれを解消します。
コミット
commit f20e3a10654e71823d58fb5d2deefce24d44a76f
Author: Russ Cox <rsc@golang.org>
Date: Fri Feb 1 08:34:08 2013 -0800
cmd/cgo: fix line number annotations in generated C code
The old version was using go/ast's CommentGroup.Text method,
but that method drops leading blank lines from the result, so that
if the comment looked like one of
//
// syntax error
import "C"
/*
syntax error
*/
import "C"
then the line numbers for the syntax error would be off by the
number of leading blank lines (1 in each of the above cases).
The new text extractor preserves blank lines.
Fixes #4019.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/7232071
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f20e3a10654e71823d58fb5d2deefce24d44a76f
元コミット内容
cmd/cgo: fix line number annotations in generated C code
このコミットは、cmd/cgo
が生成するCコードにおける行番号のアノテーションを修正します。以前のバージョンでは、go/ast
パッケージのCommentGroup.Text
メソッドを使用していましたが、このメソッドは結果から先頭の空白行を削除していました。そのため、コメントが以下のような形式の場合、
//
// syntax error
import "C"
または
/*
syntax error
*/
import "C"
構文エラーの行番号が、先頭の空白行の数(上記の各ケースでは1行)だけずれていました。新しいテキスト抽出器は空白行を保持します。
Issue #4019を修正します。
変更の背景
Go言語のcgo
ツールは、GoコードからC言語の関数を呼び出したり、C言語のコードをGoプログラムに組み込んだりするための重要なツールです。cgo
はGoのソースコードを解析し、C言語のコードを生成する際に、元のGoコードの行番号情報をCコードに埋め込みます。これは、Cコンパイラがエラーや警告を報告する際に、元のGoソースコードの正確な位置を示すために不可欠です。
しかし、以前のcgo
の実装では、Goの抽象構文木(AST)を扱うgo/ast
パッケージのCommentGroup.Text
メソッドを使用して、コメントブロックからテキストを抽出していました。このText
メソッドには、抽出されたテキストの先頭にある空白行を自動的に削除するという挙動がありました。
この挙動が問題を引き起こしたのは、cgo
がGoコード内のimport "C"
ディレクティブの前に記述されたコメントを処理する際でした。例えば、以下のようなコードがあったとします。
//
// This is a comment with a blank line above.
import "C"
CommentGroup.Text
メソッドがこのコメントを処理すると、先頭の空白行が削除され、コメントのテキストが実際よりも上の行にあるかのように扱われてしまいました。その結果、cgo
が生成するCコードに埋め込まれる#line
ディレクティブ(Cプリプロセッサに行番号情報を提供する)が不正確になり、Cコンパイラが報告するエラーメッセージの行番号が元のGoソースコードと一致しないという問題が発生していました。これは、特にデバッグ作業において開発者を混乱させる原因となっていました。
このコミットは、この行番号のずれを修正し、cgo
がより正確なデバッグ情報を提供できるようにすることを目的としています。
前提知識の解説
cgo
cgo
は、GoプログラムからC言語のコードを呼び出すためのGoツールです。Goのソースファイルにimport "C"
という特別なインポート文を記述することで、C言語の関数や変数、型をGoコードから利用できるようになります。cgo
は、GoとCの間のデータ変換や関数呼び出しの橋渡しをするためのコードを自動生成します。この生成されたCコードには、元のGoソースコードの行番号情報が#line
ディレクティブとして埋め込まれます。
Goの抽象構文木 (AST) と go/ast
パッケージ
Goコンパイラは、Goのソースコードを解析して抽象構文木(AST)を構築します。ASTは、プログラムの構造を木構造で表現したものです。go/ast
パッケージは、このASTをプログラム的に操作するための機能を提供します。
ast.CommentGroup
go/ast
パッケージにおいて、ast.CommentGroup
は、ソースコード内の連続するコメントのグループを表す構造体です。例えば、関数定義の前に複数行のコメントがある場合、それらは一つのCommentGroup
として扱われます。
ast.CommentGroup.Text()
メソッド
ast.CommentGroup
構造体には、そのコメントグループのテキスト内容を文字列として返すText()
メソッドがあります。このメソッドは、コメントマーカー(//
や/* ... */
)を取り除き、整形されたコメントテキストを返しますが、先頭の空白行を削除するという特定の挙動を持っていました。この挙動が、本コミットで修正される問題の根本原因でした。
#line
ディレクティブ
C言語のプリプロセッサディレクティブの一つである#line
は、コンパイラに対して、現在のソースファイルの行番号とファイル名を指定された値に変更するよう指示します。これは、特にコード生成ツール(cgo
のような)が、生成されたコードのエラーメッセージを元のソースコードの行番号にマッピングするために使用されます。例えば、#line 10 "original.go"
というディレクティブは、それ以降のコードがoriginal.go
ファイルの10行目から始まったものとして扱われることを意味します。
技術的詳細
このコミットの技術的な核心は、go/ast
パッケージのCommentGroup.Text()
メソッドの代わりに、空白行を保持する新しいコメントテキスト抽出関数commentText
を導入した点にあります。
以前のcmd/cgo/ast.go
では、f.Preamble += cg.Text() + "\\n"
という行で、コメントグループcg
のテキストをcg.Text()
メソッドで取得し、それをf.Preamble
(生成されるCコードのプレアンブル部分)に追加していました。このcg.Text()
が先頭の空白行を削除するため、#line
ディレクティブで指定された行番号と、実際にCコードに埋め込まれるコメントの行番号がずれていました。
この修正では、cg.Text()
の呼び出しをcommentText(cg)
に置き換えています。そして、commentText
という新しいヘルパー関数がsrc/cmd/cgo/ast.go
に追加されました。
commentText
関数の実装は以下の通りです。
g *ast.CommentGroup
を受け取る: コメントグループへのポインタを引数として受け取ります。- nilチェック:
g
がnil
の場合は空文字列を返します。 pieces
スライス: コメントの各行(またはブロック)のテキストを格納するための文字列スライスpieces
を初期化します。- コメントのイテレーション:
g.List
(*ast.Comment
のリスト)をループ処理します。*ast.Comment
は個々のコメント要素を表します。 - コメントマーカーの除去: 各コメント
com
のcom.Text
フィールド(コメントマーカーを含む生のテキスト)を取得し、switch
文を使ってコメントの種類(//
スタイルか/* ... */
スタイルか)を判別し、適切なスライス操作でマーカーを除去します。//
スタイルのコメントの場合 (c[1] == '/'
):c[2:] + "\\n"
とすることで、//
を除去し、末尾に改行を追加します。これにより、元のコメントの改行が保持されます。/* ... */
スタイルのコメントの場合 (c[1] == '*'
):c[2 : len(c)-2]
とすることで、/*
と*/
を除去します。
pieces
に追加: マーカーを除去したコメントテキストをpieces
スライスに追加します。- 結合: 最後に、
strings.Join(pieces, "")
を使用して、pieces
スライス内のすべての文字列を結合し、単一の文字列として返します。この結合時に区切り文字として空文字列""
を使用することで、元のコメントの改行がそのまま保持され、空白行も失われることなく結合されます。
このcommentText
関数は、CommentGroup.Text()
が持っていた「先頭の空白行を削除する」という挙動を回避し、コメントの元の構造(空白行を含む)を正確に保持することで、#line
ディレクティブの精度を向上させています。
コアとなるコードの変更箇所
--- a/src/cmd/cgo/ast.go
+++ b/src/cmd/cgo/ast.go
@@ -78,7 +78,7 @@ func (f *File) ReadGo(name string) {
}
if cg != nil {
f.Preamble += fmt.Sprintf("#line %d %q\\n", sourceLine(cg), name)
- f.Preamble += cg.Text() + "\\n"
+ f.Preamble += commentText(cg) + "\\n"
}
}
}
@@ -131,6 +131,30 @@ func (f *File) ReadGo(name string) {
f.AST = ast2
}
+// Like ast.CommentGroup's Text method but preserves
+// leading blank lines, so that line numbers line up.
+func commentText(g *ast.CommentGroup) string {
+ if g == nil {
+ return ""
+ }
+ var pieces []string
+ for _, com := range g.List {
+ c := string(com.Text)
+ // Remove comment markers.
+ // The parser has given us exactly the comment text.
+ switch c[1] {
+ case '/':
+ //-style comment (no newline at the end)
+ c = c[2:] + "\\n"
+ case '*':
+ /*-style comment */
+ c = c[2 : len(c)-2]
+ }
+ pieces = append(pieces, c)
+ }
+ return strings.Join(pieces, "")
+}
+
// Save references to C.xxx for later processing.
func (f *File) saveRef(x interface{}, context string) {
n, ok := x.(*ast.Expr)
コアとなるコードの解説
このコミットの主要な変更は、src/cmd/cgo/ast.go
ファイル内の2箇所です。
-
ReadGo
関数内の変更:ReadGo
関数は、Goソースファイルを読み込み、そのASTを処理する役割を担っています。この関数内で、コメントグループcg
のテキストをプレアンブルに追加する部分が変更されました。- f.Preamble += cg.Text() + "\\n"
+ f.Preamble += commentText(cg) + "\\n"
ここで、既存のcg.Text()
メソッドの呼び出しが、新しく定義されたcommentText(cg)
関数に置き換えられています。これにより、コメントテキストの抽出方法が変更され、先頭の空白行が保持されるようになります。 -
commentText
関数の新規追加: この関数は、ast.CommentGroup
のText
メソッドと同様の目的を持ちますが、先頭の空白行を保持するという重要な違いがあります。func commentText(g *ast.CommentGroup) string { if g == nil { return "" } var pieces []string for _, com := range g.List { c := string(com.Text) // Remove comment markers. // The parser has given us exactly the comment text. switch c[1] { case '/': //-style comment (no newline at the end) c = c[2:] + "\\n" case '*': /*-style comment */ c = c[2 : len(c)-2] } pieces = append(pieces, c) } return strings.Join(pieces, "") }
if g == nil
: コメントグループがnil
の場合(コメントが存在しない場合)は、空文字列を返します。var pieces []string
: 各コメント行から抽出されたテキストを一時的に保持するための文字列スライスを宣言します。for _, com := range g.List
:CommentGroup
は複数のComment
(個々のコメント要素)を含むことができるため、それらをループで処理します。c := string(com.Text)
: 各Comment
の生のテキスト(//
や/* */
などのマーカーを含む)を取得します。switch c[1]
: コメントの2文字目(//
の/
や/*
の*
)をチェックして、コメントのスタイルを判別します。case '/'
://
スタイルのコメントの場合、c[2:]
で//
を除去し、末尾に\n
を追加します。これにより、元のコメントの改行が保持されます。case '*'
:/* ... */
スタイルのコメントの場合、c[2 : len(c)-2]
で/*
と*/
を除去します。
pieces = append(pieces, c)
: 処理されたコメントテキストをpieces
スライスに追加します。return strings.Join(pieces, "")
: 最後に、pieces
スライス内のすべての文字列を空文字列を区切り文字として結合し、単一の文字列として返します。この結合方法により、元のコメントに含まれる空白行がそのまま保持され、行番号のずれが解消されます。
この変更により、cgo
が生成するCコードの#line
ディレクティブが、Goソースコードのコメントの正確な行番号を反映するようになり、デバッグ時の利便性が向上します。
関連リンク
参考にした情報源リンク
- コミットメッセージに記載されているIssue #4019 (ただし、Web検索ではGo言語の公式なIssueトラッカーやコミュニティで広く認識されている問題としての情報は見つかりませんでした。コミットメッセージに記載されているため、内部的なトラッキング番号である可能性があります。)