[インデックス 17464] ファイルの概要
このコミットは、Go言語のcgo
ツールにおけるバグ修正に関するものです。具体的には、#cgo
ディレクティブがCコードの行番号に影響を与え、エラーメッセージの行番号がずれてしまう問題を解決します。これにより、cgo
を介してGoとCを連携させる際に、Cコード内で発生したエラーのデバッグがより正確に行えるようになります。
コミット
commit f68c23e2bba617d8f9bbe1cb53a920aeaf8901ad
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Sep 3 21:15:15 2013 -0700
cmd/cgo: don't let #cgo directives mess up line numbering
Fixes #5272.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/13498046
---
misc/cgo/errors/err1.go | 14 ++++++++++++++
misc/cgo/errors/test.bash | 19 +++++++++++++++++++
src/cmd/cgo/gcc.go | 2 ++\
src/run.bash | 6 ++++++\
4 files changed, 41 insertions(+)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f68c23e2bba617d8f9bbe1cb53a920aeaf8901ad
元コミット内容
cmd/cgo: don't let #cgo directives mess up line numbering
このコミットは、cmd/cgo
ツールが#cgo
ディレクティブによって行番号を狂わせないようにするものです。Issue 5272を修正します。
変更の背景
このコミットは、GoのIssue 5272「cmd/cgo
: #cgo
directives mess up line numbering」を修正するために作成されました。
Go言語のcgo
ツールは、GoプログラムからCコードを呼び出すためのメカニズムを提供します。cgo
を使用する際、Goのソースファイル内にCコードを埋め込んだり、Cのライブラリにリンクするためのディレクティブ(#cgo
)を記述したりします。
この問題の背景には、cgo
がGoのソースファイルを処理し、Cコードを抽出してコンパイル可能な形式に変換する過程があります。以前の実装では、#cgo
ディレクティブを含む行がCコードのプリプロセッサに渡される際に、これらのディレクティブが削除されることで、元のGoファイルと生成されたCファイルの間で行番号のずれが生じていました。
具体的には、Goファイル内でCコードブロックの前に#cgo
ディレクティブが存在すると、cgo
ツールがそのディレクティブを処理して削除した後、Cコンパイラに渡されるCコードの行番号が、元のGoファイルでのCコードの開始行番号と一致しなくなっていました。これにより、Cコード内でコンパイルエラーが発生した場合、Cコンパイラが出力するエラーメッセージの行番号が、開発者が参照しているGoソースファイル内の実際の行番号と異なるため、デバッグが非常に困難になるという問題がありました。
このコミットは、この行番号のずれを解消し、CコンパイラからのエラーメッセージがGoソースファイル内の正しい行番号を指すようにすることで、cgo
を利用した開発のデバッグ体験を向上させることを目的としています。
前提知識の解説
Go言語のcgo
cgo
は、GoプログラムからC言語のコードを呼び出すためのGoツールチェーンの一部です。Goは通常、純粋なGoコードで記述されますが、既存のCライブラリを利用したい場合や、特定のパフォーマンス要件のためにCコードを使用したい場合にcgo
が役立ちます。
cgo
の基本的な仕組みは以下の通りです。
-
Goファイル内のCコード: Goのソースファイル(例:
main.go
)内に、特別なコメントブロックを使ってCコードを記述できます。このブロックはimport "C"
の直前に配置されます。package main /* #include <stdio.h> void my_c_function() { printf("Hello from C!\n"); } */ import "C" // この行の直前にCコードブロックを置く func main() { C.my_c_function() // C関数を呼び出す }
-
#cgo
ディレクティブ: Cコードブロック内、またはGoファイルのどこかに#cgo
ディレクティブを記述することで、Cコンパイラやリンカに渡すフラグを指定できます。これにより、外部のCライブラリへのリンクや、コンパイル時のオプションを設定できます。/* #cgo LDFLAGS: -L/usr/local/lib -lmyc #cgo CFLAGS: -I/usr/local/include void my_c_function(); */ import "C"
LDFLAGS
: リンカに渡すフラグ(例: ライブラリのパスやライブラリ名)CFLAGS
: Cコンパイラに渡すフラグ(例: インクルードパスやコンパイルオプション)CPPFLAGS
: Cプリプロセッサに渡すフラグCXXFLAGS
: C++コンパイラに渡すフラグFFLAGS
: Fortranコンパイラに渡すフラグPKG_CONFIG
:pkg-config
ツールを使用してライブラリのフラグを取得
-
cgo
ツールの処理: GoコンパイラがGoファイルを処理する前に、cgo
ツールが介入します。cgo
は以下の処理を行います。- GoファイルからCコードブロックを抽出し、Cコンパイラが処理できる形式の
.c
ファイルや.h
ファイルを生成します。 #cgo
ディレクティブを解析し、それらの情報を基にCコンパイラとリンカを呼び出します。- GoとCの間でデータをやり取りするためのスタブコードを生成します。
- 最終的に、Goコンパイラは生成されたGoコードとCコンパイラによってコンパイルされたオブジェクトファイルをリンクして実行可能ファイルを生成します。
- GoファイルからCコードブロックを抽出し、Cコンパイラが処理できる形式の
行番号の重要性
プログラミングにおいて、行番号はデバッグやエラー報告において非常に重要です。コンパイラやインタプリタがエラーを報告する際、通常はエラーが発生したファイル名と行番号を提示します。これにより、開発者は問題の箇所を迅速に特定し、修正することができます。
cgo
のような異言語間連携ツールでは、元のソースファイル(Goファイル)と、ツールによって生成されコンパイラに渡される中間ファイル(Cファイル)の間で行番号の対応関係が正確であることが不可欠です。もしこの対応関係がずれてしまうと、Cコンパイラが「X行目でエラー」と報告しても、そのX行目が元のGoファイルでは全く別のコードを指している、という混乱が生じます。
技術的詳細
このコミットの技術的な核心は、cgo
ツールがGoソースファイル内の#cgo
ディレクティブを処理する際に、Cコンパイラに渡すコードの行番号の整合性を保つ方法にあります。
以前のcgo
の実装では、Goファイル内のCコードブロックを抽出する際、#cgo
ディレクティブを含む行は単に削除されていました。例えば、以下のようなGoファイルがあったとします。
// main.go
package main
/*
#cgo LDFLAGS: -c // Line 4
void test() { // Line 5
xxx; // Line 6 (original)
}
*/
import "C"
func main() {
C.test()
}
この場合、cgo
がCコードを抽出してCコンパイラに渡す際、#cgo LDFLAGS: -c
の行が削除されると、Cコンパイラに渡されるコードは以下のようになります。
void test() { // Line 1 (generated)
xxx; // Line 2 (generated)
}
もしxxx;
の行でコンパイルエラーが発生した場合、Cコンパイラは「Line 2でエラー」と報告します。しかし、元のmain.go
ファイルではxxx;
はLine 6にあります。このように、行番号が4行ずれてしまうため、開発者はエラーの場所を特定するのに苦労していました。
このコミットの修正は、src/cmd/cgo/gcc.go
のDiscardCgoDirectives
関数にあります。この関数は、Goファイル内のCコードブロックから#cgo
ディレクティブを「破棄」する役割を担っています。
修正前は、#cgo
ディレクティブの行はlinesOut
(出力される行のリスト)に追加されませんでした。つまり、完全に削除されていました。
修正後は、#cgo
ディレクティブの行が見つかった場合でも、その行を完全に削除するのではなく、空文字列 (""
) をlinesOut
に追加するように変更されました。これにより、#cgo
ディレクティブがあった場所には空行が挿入されることになります。
上記の例で考えると、修正後のcgo
がCコードを抽出してCコンパイラに渡すコードは以下のようになります。
// Line 1 (empty line for #cgo LDFLAGS: -c)
void test() { // Line 2 (original Line 5)
xxx; // Line 3 (original Line 6)
}
この結果、xxx;
の行でエラーが発生した場合、Cコンパイラは「Line 3でエラー」と報告します。元のmain.go
ファイルではxxx;
はLine 6にあります。まだずれはありますが、これはCプリプロセッサが#line
ディレクティブを処理する際に、元のファイル名と行番号を正確に伝えるための標準的な方法です。
このコミットの目的は、#cgo
ディレクティブがCコードの行番号に影響を与えないようにすることです。空行を挿入することで、Cコンパイラに渡されるコードの行数が元のGoファイル内のCコードブロックの行数と一致するようになり、Cコンパイラが生成するエラーメッセージの行番号が、Goソースファイル内の対応する行番号とより正確に一致するようになります。
これは、Cプリプロセッサが#line
ディレクティブを挿入する際に、正確な行番号情報を提供するための準備とも言えます。#line
ディレクティブは、コンパイラに対して、現在のソースコードの行番号とファイル名を指定されたものに変更するよう指示するもので、プリプロセッサによって生成されたコードのデバッグを容易にするために広く使用されます。cgo
は内部的にこのようなメカニズムを利用して、Goファイル内のCコードと生成されたCコードの間の行番号の対応を維持しています。このコミットは、その対応をより正確にするための基盤となる変更です。
コアとなるコードの変更箇所
src/cmd/cgo/gcc.go
--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -76,6 +76,8 @@ func (f *File) DiscardCgoDirectives() {
\tl := strings.TrimSpace(line)
\tif len(l) < 5 || l[:4] != "#cgo" || !unicode.IsSpace(rune(l[4])) {
\t\tlinesOut = append(linesOut, line)
+\t\t} else {
+\t\t\tlinesOut = append(linesOut, "")
\t}\
}\
f.Preamble = strings.Join(linesOut, "\\n")
この変更は、DiscardCgoDirectives
関数内で、#cgo
ディレクティブの行を処理する方法を変更しています。以前は#cgo
ディレクティブの行はlinesOut
に追加されませんでしたが、変更後は空文字列""
が追加されるようになりました。
misc/cgo/errors/err1.go
(新規ファイル)
package main
/*
#cgo LDFLAGS: -c
void test() {
xxx; // This is line 7.
}
*/
import "C"
func main() {
C.test()
}
このファイルは、#cgo
ディレクティブとCコードを含むGoプログラムのテストケースです。意図的にxxx;
という未定義のシンボルを含めることで、Cコンパイラがエラーを発生させるようにしています。コメントで// This is line 7.
と明記されており、エラーがこの行で報告されることを期待しています。
misc/cgo/errors/test.bash
(新規ファイル)
# Copyright 2013 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.
if go tool cgo err1.go >errs 2>&1; then
echo 1>&2 misc/cgo/errors/test.bash: BUG: expected cgo to fail but it succeeded
exit 1
fi
if ! test -s errs; then
echo 1>&2 misc/cgo/errors/test.bash: BUG: expected error output but saw none
exit 1
fi
if ! fgrep err1.go:7 errs >/dev/null 2>&1; then
echo 1>&2 misc/cgo/errors/test.bash: BUG: expected error on line 7 but saw:
cat 1>&2 errs
exit 1
fi
rm -rf errs _obj
exit 0
このシェルスクリプトは、err1.go
をcgo
ツールでコンパイルし、エラーが発生すること、そしてそのエラーメッセージがerr1.go:7
(7行目)を指していることを検証するテストです。
src/run.bash
--- a/src/run.bash
+++ b/src/run.bash
@@ -145,6 +145,12 @@ esac
go run main.go || exit 1
) || exit $?\
+[ "$CGO_ENABLED" != 1 ] ||
+[ "$GOHOSTOS" == windows ] ||
+(xcd ../misc/cgo/errors
+./test.bash || exit 1
+) || exit $?\
+\
(xcd ../doc/progs
time ./run || exit 1
) || exit $?\
この変更は、Goのテストスイートを実行するrun.bash
スクリプトに、新しく追加されたmisc/cgo/errors/test.bash
テストケースの実行を追加しています。CGO_ENABLED
が1でない場合(cgoが無効な場合)や、Windows環境の場合はテストをスキップします。
コアとなるコードの解説
このコミットの核心は、src/cmd/cgo/gcc.go
内のDiscardCgoDirectives
関数における変更です。
func (f *File) DiscardCgoDirectives() {
linesOut := make([]string, 0, len(f.Preamble))
for _, line := range strings.Split(f.Preamble, "\n") {
l := strings.TrimSpace(line)
// Check if the line starts with "#cgo" followed by a space.
// This is a simple check to identify cgo directives.
if len(l) < 5 || l[:4] != "#cgo" || !unicode.IsSpace(rune(l[4])) {
// If it's not a #cgo directive, keep the original line.
linesOut = append(linesOut, line)
} else {
// If it is a #cgo directive, replace it with an empty string.
// This preserves the line numbering for the C compiler.
linesOut = append(linesOut, "")
}
}
f.Preamble = strings.Join(linesOut, "\n")
}
この関数は、Goソースファイル内のCコードブロック(f.Preamble
)を処理し、#cgo
ディレクティブを「破棄」します。
f.Preamble
は、Goファイル内のimport "C"
の直前にあるCコードの文字列です。- この関数は、
f.Preamble
を改行で分割し、各行をループ処理します。 - 各行について、
strings.TrimSpace(line)
で前後の空白を削除した文字列l
を取得します。 if len(l) < 5 || l[:4] != "#cgo" || !unicode.IsSpace(rune(l[4]))
の条件で、その行が#cgo
ディレクティブであるかどうかを判定します。len(l) < 5
:#cgo
は4文字なので、最低でも5文字(#cgo
+ スペース)必要です。l[:4] != "#cgo"
: 行の先頭が#cgo
であるか。!unicode.IsSpace(rune(l[4]))
:#cgo
の直後に空白文字が続くか。
- 変更点:
- もしその行が
#cgo
ディレクティブではない場合 (if
ブロック)、元の行line
をlinesOut
スライスに追加します。 - もしその行が
#cgo
ディレクティブである場合 (else
ブロック)、以前は何も追加されませんでしたが、このコミットにより**空文字列""
**がlinesOut
スライスに追加されるようになりました。
- もしその行が
この変更の意図は、#cgo
ディレクティブの行を完全に削除するのではなく、その場所に空行を挿入することで、Cコンパイラに渡されるコードの行数を元のGoファイル内のCコードブロックの行数と一致させることです。これにより、Cコンパイラがエラーを報告する際の行番号が、Goソースファイル内の実際の行番号とより正確に同期されるようになります。
新しく追加されたテストケースmisc/cgo/errors/err1.go
とmisc/cgo/errors/test.bash
は、この修正が正しく機能することを検証します。err1.go
は意図的にコンパイルエラーを発生させるCコードを含み、test.bash
はcgo
を実行し、エラーメッセージがerr1.go:7
を指していることをfgrep
コマンドで確認します。このテストの成功は、#cgo
ディレクティブによる行番号のずれが解消されたことを意味します。
関連リンク
- Go Issue 5272: cmd/cgo: #cgo directives mess up line numbering
- Go Code Review: https://golang.org/cl/13498046
参考にした情報源リンク
- Go Issue 5272の議論内容
- Go言語の
cgo
に関する公式ドキュメントやチュートリアル - C言語のプリプロセッサと
#line
ディレクティブに関する一般的な情報