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

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

このコミットは、Go言語のcgoツールにおいて、致命的なエラー発生時に適切な行番号を出力するように改善するものです。これにより、cgoのユーザーはエラーの発生源をより正確に特定できるようになります。具体的には、fatalf関数および関連するヘルパー関数のシグネチャが統一され、エラーメッセージに行番号情報が付加されるようになりました。

コミット

commit 0d07600de3ec06204fb71906c136421dce69e206
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Mon Feb 6 20:38:54 2012 +0100

    cgo: print line numbers in fatal errors when relevant.
    
    Signatures of fatalf and error_ helpers have been matched for
    consistency.
    Fixes #1800.
    
    R=rsc
    CC=golang-dev, remy
    https://golang.org/cl/5593049

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

https://github.com/golang/go/commit/0d07600de3ec06204fb71906c136421dce69e206

元コミット内容

cgo: print line numbers in fatal errors when relevant.

Signatures of fatalf and error_ helpers have been matched for consistency. Fixes #1800.

R=rsc CC=golang-dev, remy https://golang.org/cl/5593049

変更の背景

この変更の背景には、cgoツールがC言語のコードをGo言語のコードに変換する際に発生するエラーメッセージの改善要求がありました。以前のバージョンでは、cgoが致命的なエラーに遭遇した場合、エラーメッセージには問題の種類は示されるものの、そのエラーがソースコードのどの位置(ファイル名と行番号)で発生したのかという情報が欠落していました。

特に、Goの型とCの型を変換する過程で発生する「型変換ループ」や「予期しない型」といったエラーは、デバッグを困難にしていました。開発者はエラーメッセージだけでは問題の根本原因を特定しにくく、手動で関連するコード箇所を探す必要がありました。

この問題はGoのIssue #1800として報告されており、エラーメッセージに行番号情報を含めることで、cgoのデバッグ体験を大幅に向上させることが目的とされました。これにより、開発者はエラーメッセージから直接問題の箇所にジャンプできるようになり、開発効率が向上します。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  • cgo: Go言語のプログラムからC言語のコードを呼び出すためのGoのツールです。import "C"という特殊なインポート宣言を使用することで、Cの関数やデータ構造をGoのコードから利用できるようになります。cgoは、Cのヘッダーファイルを解析し、GoとCの間でデータをやり取りするための接着コード(binding code)を生成します。
  • DWARF (Debugging With Attributed Record Formats): コンパイルされたプログラムのデバッグ情報を格納するための標準的なフォーマットです。cgoは、Cコンパイラが生成したDWARF情報からCの型定義を読み取り、それをGoの対応する型に変換します。このDWARF情報には、ソースコードのファイル名や行番号といった位置情報も含まれています。
  • token.Pos: Go言語の標準ライブラリgo/tokenパッケージで定義されている型です。これは、Goのソースコード内の特定の位置(ファイル名、行番号、列番号)を表すために使用されます。コンパイラやツールがソースコードを解析する際に、エラーや警告の発生箇所を正確に報告するために不可欠な情報です。
  • 型変換 (Type Conversion): cgoの主要な機能の一つで、C言語の型(例: int, struct MyStruct, char*)をGo言語の対応する型(例: int32, struct { ... }, *C.char)に変換するプロセスです。この変換は複雑であり、特にポインタ、配列、構造体、共用体などの複雑な型の場合に、予期せぬ問題が発生することがあります。
  • fatalf関数: Goのプログラム、特にツールやコンパイラにおいて、回復不能なエラーが発生した場合に、エラーメッセージを出力してプログラムを終了させるために使用される関数です。通常、fmt.Errorflog.Fatalfのような標準的なエラー報告メカニズムと同様の役割を果たしますが、cgoの文脈では内部的なエラー報告に使用されます。

これらの概念を理解することで、cgoがどのようにCの型情報を処理し、エラーが発生した際にどのようにその位置を特定して報告するようになったのかを把握できます。

技術的詳細

このコミットの技術的詳細は、主にcgoの型変換ロジックとエラー報告メカニズムの変更に集約されます。

  1. token.Posの伝播:

    • src/cmd/cgo/gcc.go内のPackage.loadDWARF関数が、DWARF情報からCの型を読み込む際に、その型が定義されているソースコード上の位置情報(token.Pos)を取得するようになりました。
    • この位置情報は、conv.FuncTypeconv.Typeといった型変換を行う関数に新たな引数pos token.Posとして渡されるようになりました。これにより、型変換の過程でエラーが発生した場合でも、そのエラーがどのソースコードの行に関連しているかを特定できるようになります。
    • nameToRefという新しいマップが導入され、*Name(DWARFのシンボル名)から*Ref(DWARFの参照情報、位置情報を含む)へのマッピングを保持することで、位置情報の取得を効率化しています。
  2. エラーメッセージへの行番号の追加:

    • src/cmd/cgo/util.golineno(pos token.Pos) stringという新しいヘルパー関数が追加されました。この関数は、token.Posオブジェクトを受け取り、fset.Position(pos).String()を呼び出すことで、「ファイル名:行番号」形式の文字列(例: main.go:15)を生成します。
    • src/cmd/cgo/gcc.go内のtypeConv構造体のメソッド(Type, FuncArg, FuncType, Structなど)内で呼び出されるfatalf関数が、このlineno(pos)の戻り値を最初の引数として受け取るように変更されました。
    • 例えば、fatalf("type conversion loop at %s", dtype)fatalf("%s: type conversion loop at %s", lineno(pos), dtype)のように変更され、エラーメッセージの先頭に位置情報が付加されるようになりました。
  3. fatalfおよび関連ヘルパー関数のシグネチャの一貫性:

    • コミットメッセージにあるように、fatalfおよびerror_ヘルパー関数のシグネチャが、行番号情報を渡すための一貫した形式に調整されました。これにより、コードベース全体でエラー報告のパターンが統一され、保守性が向上します。
  4. テストケースの追加:

    • test/fixedbugs/bug408.goという新しいテストファイルが追加されました。このテストは、cgoが意図的にエラーを発生させるようなコード(例: C.printf(nil))を含み、そのエラーメッセージに行番号が含まれていることをerrchkディレクティブで検証します。これにより、変更が正しく機能していることが保証されます。

これらの変更により、cgoはより詳細でデバッグしやすいエラーメッセージを提供するようになり、開発者はcgo関連の問題を迅速に特定し、解決できるようになりました。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/cmd/cgo/gcc.go:

    • Package.loadDWARF関数内で、nameToRefマップの導入と、token.Posの取得ロジックが追加されました。
    • conv.FuncTypeconv.Typeの呼び出しにpos引数が追加されました。
    • typeConv構造体のメソッド(Type, FuncArg, FuncType, Struct)のシグネチャにpos token.Pos引数が追加され、内部のfatalf呼び出しに行番号情報が渡されるようになりました。
  2. src/cmd/cgo/util.go:

    • lineno(pos token.Pos) stringヘルパー関数が新しく追加されました。
  3. test/fixedbugs/bug408.go:

    • 行番号付きエラーメッセージの出力を検証するための新しいテストケースが追加されました。

src/cmd/cgo/gcc.go の変更例:

--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -597,12 +601,16 @@ func (p *Package) loadDWARF(f *File, names []*Name) {
 		if types[i] == nil {
 			continue
 		}
+		pos := token.NoPos
+		if ref, ok := nameToRef[n]; ok {
+			pos = ref.Pos()
+		}
 		f, fok := types[i].(*dwarf.FuncType)
 		if n.Kind != "type" && fok {
 			n.Kind = "func"
-			n.FuncType = conv.FuncType(f)
+			n.FuncType = conv.FuncType(f, pos)
 		} else {
-			n.Type = conv.Type(types[i])
+			n.Type = conv.Type(types[i], pos)
 			if enums[i] != 0 && n.Type.EnumValues != nil {
 				k := fmt.Sprintf("__cgo_enum__%d", i)
 				n.Kind = "const"
@@ -972,10 +980,10 @@ func (tr *TypeRepr) Set(repr string, fargs ...interface{}) {
 
 // Type returns a *Type with the same memory layout as
 // dtype when used as the type of a variable or a struct field.
-func (c *typeConv) Type(dtype dwarf.Type) *Type {
+func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
 	if t, ok := c.m[dtype]; ok {
 		if t.Go == nil {
-			fatalf("type conversion loop at %s", dtype)
+			fatalf("%s: type conversion loop at %s", lineno(pos), dtype)
 		}
 		return t
 	}

src/cmd/cgo/util.go の変更例:

--- a/src/cmd/cgo/util.go
+++ b/src/cmd/cgo/util.go
@@ -64,6 +64,10 @@ func run(stdin []byte, argv []string) (stdout, stderr []byte, ok bool) {
 	return
 }
 
+func lineno(pos token.Pos) string {
+	return fset.Position(pos).String()
+}
+
 // Die with an error message.
 func fatalf(msg string, args ...interface{}) {
 	fmt.Fprintf(os.Stderr, msg+"\\n", args...)

test/fixedbugs/bug408.go の追加:

--- /dev/null
+++ b/test/fixedbugs/bug408.go
@@ -0,0 +1,16 @@
+// errchk cgo $D/$F.go
+
+// Copyright 2012 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.
+
+// Issue 1800: cgo not reporting line numbers.
+
+package main
+
+// #include <stdio.h>
+import "C"
+
+func f() {
+	C.printf(nil) // ERROR "go:15.*unexpected type"
+}

コアとなるコードの解説

このコミットの核心は、cgoがCの型情報をGoの型に変換する際に、その変換元となるCのコードの位置情報(ファイル名と行番号)を保持し、エラー発生時にその情報をエラーメッセージに含めるようにした点です。

  1. 位置情報の取得と伝播 (src/cmd/cgo/gcc.go):

    • Package.loadDWARF関数は、Cコンパイラが生成したDWARFデバッグ情報から型定義を読み込みます。この過程で、nameToRefマップが導入され、DWARFのシンボル名(*Name)と、そのシンボルが定義されているソースコード上の位置情報を含む*Refオブジェクトを関連付けます。
    • pos := token.NoPosで初期化されたpos変数に、ref.Pos()を介して実際の位置情報が格納されます。
    • このpos変数は、conv.FuncTypeconv.Typeといった型変換を行う関数に新たな引数として渡されます。これにより、型変換ロジックの深い階層まで位置情報が伝播されるようになります。
  2. エラーメッセージの整形 (src/cmd/cgo/util.go):

    • 新しく追加されたlineno(pos token.Pos) string関数は、token.Posオブジェクトを人間が読める形式の文字列(例: filename.go:line_number)に変換する役割を担います。fset.Position(pos).String()がこの変換を実行します。
    • fatalf関数は、cgoが致命的なエラーを報告する際に使用されます。変更後、fatalfの呼び出し元は、エラーメッセージのフォーマット文字列の最初のプレースホルダーにlineno(pos)の戻り値を渡すようになりました。これにより、エラーメッセージの冒頭にエラー発生箇所のファイル名と行番号が自動的に付加されます。
  3. 型変換ロジックの変更 (src/cmd/cgo/gcc.go内のtypeConvメソッド群):

    • typeConv構造体のType, FuncArg, FuncType, Structといった主要な型変換メソッドのシグネチャにpos token.Posが追加されました。
    • これらのメソッド内で発生するfatalf呼び出しはすべて、lineno(pos)を引数として受け取るように変更されました。例えば、型変換ループを検出した際のエラーメッセージは、単に「type conversion loop at ...」ではなく、「filename.go:line_number: type conversion loop at ...」のように出力されるようになります。
    • 再帰的に型変換を行う箇所(例: 配列の要素型、ポインタの指す型、構造体のフィールド型を変換する際)では、親の型から受け取ったposを子要素の型変換関数に引き渡すことで、エラー発生時の正確な位置情報を保証しています。
  4. テストによる検証 (test/fixedbugs/bug408.go):

    • この新しいテストファイルは、cgoがエラーを発生させる特定のシナリオ(この場合はC.printf(nil)という不正なC関数呼び出し)を記述しています。
    • // ERROR "go:15.*unexpected type"というコメントは、errchkツールに対する指示であり、この行でcgoが「go:15」という行番号と「unexpected type」という文字列を含むエラーメッセージを出力することを期待しています。これにより、行番号の出力機能が正しく実装されていることが自動的に検証されます。

これらの変更により、cgoのエラーメッセージは格段に分かりやすくなり、cgoを使用する開発者のデバッグ作業が大幅に効率化されました。

関連リンク

  • Go Issue #1800: https://code.google.com/p/go/issues/detail?id=1800 (元のGoプロジェクトのIssueトラッカーはGoogle CodeからGitHubへ移行しているため、直接のリンクは機能しない可能性がありますが、このIssue番号が変更のトリガーとなりました。)
  • Go CL 5593049: https://golang.org/cl/5593049 (GoのコードレビューシステムGerritの変更リストへのリンク)

参考にした情報源リンク