[インデックス 19491] ファイルの概要
このコミットは、Go言語のcmd/cgo
ツールにおけるバグ修正です。具体的には、C言語の匿名構造体へのtypedef
が複数のGoファイルで参照された際に、cgo
がそれぞれ異なるGoの型を生成してしまう問題を解決し、一貫したGoの型が使用されるように改善しています。
コミット
commit 4e65f18cae1b4ee6074e3e544c322af030d04288
Author: Ian Lance Taylor <iant@golang.org>
Date: Mon Jun 2 12:55:43 2014 -0700
cmd/cgo: use same Go type for typedef to anonymous struct
If we see a typedef to an anonymous struct more than once,
presumably in two different Go files that import "C", use the
same Go type name.
Fixes #8133.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/102080043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e65f18cae1b4ee6074e3e544c322af030d04288
元コミット内容
cmd/cgo: use same Go type for typedef to anonymous struct
If we see a typedef to an anonymous struct more than once,
presumably in two different Go files that import "C", use the
same Go type name.
Fixes #8133.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/102080043
変更の背景
この変更は、Go言語のcgo
ツールがC言語の型定義をGoの型に変換する際の問題を修正するために行われました。具体的には、C言語で匿名構造体(名前を持たない構造体)がtypedef
によって新しい型名を与えられた場合、そのtypedef
が複数のGoファイル(それぞれがimport "C"
を通じてCのヘッダーファイルをインポートしている)で参照されると、cgo
がそれぞれ異なるGoの型を生成してしまうという問題がありました。
この問題が発生すると、論理的には同じCの型であるにもかかわらず、Goのコンパイラはそれらを異なる型として認識するため、型不一致のエラーが発生したり、予期せぬ実行時動作を引き起こしたりする可能性がありました。例えば、C.MyStruct
という型がfileA.go
とfileB.go
で異なる内部表現を持つGoの型に変換されてしまうと、両ファイル間でその型を共有する際に問題が生じます。
このコミットは、Fixes #8133
と関連付けられています。issue 8133
は、cmd/cgo
がCの型定義に対して一貫性のないGoの型を生成することに関する広範な問題の一部として報告されており、このコミットはその中でも特に匿名構造体のtypedef
に起因する具体的なケースを解決することを目的としています。
前提知識の解説
このコミットの理解には、以下の概念が役立ちます。
-
cgo: GoプログラムからC言語のコードを呼び出すためのGoのツールです。
import "C"
という特殊なインポート宣言を使用することで、Goコード内でCの関数を呼び出したり、Cの型を使用したりできるようになります。cgo
は、GoとCの間の型変換、メモリ管理、関数呼び出しの橋渡しを行います。ビルド時にCのコンパイラ(通常はGCC)と連携して、GoとCの間のバインディングコードを生成します。 -
typedef (C言語): C言語のキーワードで、既存のデータ型に新しい名前を付けるために使用されます。これにより、コードの可読性が向上し、複雑な型宣言を簡略化できます。 例:
typedef struct { int x; int y; } Point;
この例では、struct { int x; int y; }
という匿名構造体にPoint
という新しい型名を与えています。 -
匿名構造体 (C言語): 名前を持たない構造体です。通常、
typedef
と組み合わせて使用されるか、あるいは一度だけ使用される場合にインラインで定義されます。 例:struct { int a; char b; } my_var;
この構造体自体には名前がありませんが、my_var
という変数の型として定義されています。typedef
と組み合わせることで、この匿名構造体を再利用可能な型として扱うことができます。 -
GoにおけるC型:
cgo
は、Cの型をGoの型にマッピングします。例えば、Cのint
はGoではC.int
として、Cのstruct MyStruct
はGoではC.struct_MyStruct
として扱われます。このマッピングは、cgo
がCのヘッダーファイルを解析し、GoのコードからCの型を参照できるようにGoの型定義を生成する過程で行われます。 -
DWARF (Debugging With Attributed Record Formats): プログラムのデバッグ情報のための標準的なフォーマットです。コンパイラはソースコードから実行可能ファイルを生成する際に、変数名、型情報、関数名、ソースコードの行番号などのデバッグ情報をDWARF形式で出力できます。
cmd/cgo
は、Cのコンパイラが生成するDWARF情報からCの型情報を読み取り、それをGoの型に変換するために利用します。 -
dwarf.Type
: Goの標準ライブラリdebug/dwarf
パッケージに含まれる型で、DWARF形式で表現された型情報をGoのプログラム内で扱うための構造体です。cmd/cgo
はこの型を使用してCの型定義を解析します。 -
token.Pos
: Goの標準ライブラリgo/token
パッケージに含まれる型で、Goのソースコード内の位置(ファイル名、行番号、列番号など)を表します。コンパイラやツールがエラーメッセージやデバッグ情報で正確な位置を示すために使用されます。
技術的詳細
cmd/cgo
は、Goのソースファイルに埋め込まれたCコード(import "C"
ブロック内)と、Goコードが参照するCのヘッダーファイルを解析し、GoとCの間の相互運用に必要なコードを生成します。このプロセスには、Cの型を対応するGoの型に変換する作業が含まれます。
このコミット以前のcmd/cgo
の動作では、Cのtypedef
が匿名構造体を参照している場合、cgo
はtypedef
の名前自体は認識していましたが、その背後にある匿名構造体の「同一性」を適切に追跡できていませんでした。その結果、同じtypedef
名を持つ匿名構造体が、異なるGoのパッケージやファイルでimport "C"
されるたびに、cgo
はそれぞれを新しい、独立したGoの型として扱ってしまっていました。
例えば、issue8331.h
というヘッダーファイルに以下のような定義があったとします。
// issue8331.h
typedef struct {
int i;
} issue8331;
そして、issue8331a.go
とissue8331b.go
という2つのGoファイルがそれぞれこのヘッダーファイルをimport "C"
で取り込んだ場合、cgo
はC.issue8331
というGoの型を生成します。しかし、このコミット以前は、issue8331a.go
で生成されたC.issue8331
とissue8331b.go
で生成されたC.issue8331
が、Goの型システム上では異なる型として扱われてしまう可能性がありました。これは、匿名構造体自体には名前がないため、cgo
がその構造体の「実体」を識別する際に、typedef
名だけでは不十分だったためと考えられます。
このコミットの目的は、この問題を解決し、同じtypedef
名が匿名構造体を参照している場合には、cgo
が常に同じGoの型を生成するようにすることです。これにより、Goの型システムにおけるCの型の整合性が保たれ、異なるGoファイル間でCの型を安全に共有できるようになります。
コアとなるコードの変更箇所
変更は主に src/cmd/cgo/gcc.go
ファイルの func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type
メソッド内で行われています。
--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -1269,7 +1269,8 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
sub := c.Type(dt.Type, pos)
t.Size = sub.Size
t.Align = sub.Align
- if _, ok := typedef[name.Name]; !ok {
+ oldType := typedef[name.Name]
+ if oldType == nil {
ttt := *t
ttt.Go = sub.Go
typedef[name.Name] = &ttt
@@ -1281,6 +1282,15 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
// In -godefs and -cdefs mode, do this for all typedefs.
if isStructUnionClass(sub.Go) || *godefs || *cdefs {
t.Go = sub.Go
+
+ // If we've seen this typedef before, and it
+ // was an anonymous struct/union/class before
+ // too, use the old definition.
+ // TODO: it would be safer to only do this if
+ // we verify that the types are the same.
+ if oldType != nil && isStructUnionClass(oldType.Go) {
+ t.Go = oldType.Go
+ }
}
case *dwarf.UcharType:
コアとなるコードの解説
src/cmd/cgo/gcc.go
内の typeConv.Type
メソッドは、Cの型情報を表すdwarf.Type
オブジェクトを受け取り、それをGoの型システムで表現可能な*Type
オブジェクトに変換する役割を担っています。このメソッドは、cgo
がCのヘッダーファイルを解析し、Goの型定義を生成する際の中心的なロジックを含んでいます。
変更点の詳細:
-
既存の
typedef
の取得:oldType := typedef[name.Name]
この行が追加されました。
typedef
はcmd/cgo
が内部的に管理しているマップで、Cのtypedef
名とそれに対応するGoの型定義を関連付けています。ここで、現在処理しているtypedef
名 (name.Name
) が既にマップに存在するかどうかを確認し、存在すればその既存のGoの型定義をoldType
変数に格納します。 -
新しい
typedef
の登録ロジックの維持:if oldType == nil { ttt := *t ttt.Go = sub.Go typedef[name.Name] = &ttt }
このブロックは、
typedef
がまだtypedef
マップに登録されていない場合に、新しいGoの型を生成してマップに保存するという従来のロジックを維持しています。sub.Go
は、typedef
が参照している基底の型(この場合は匿名構造体)に対応するGoの型です。 -
匿名構造体の
typedef
に対するGoの型再利用ロジックの追加:if isStructUnionClass(sub.Go) || *godefs || *cdefs { t.Go = sub.Go // If we've seen this typedef before, and it // was an anonymous struct/union/class before // too, use the old definition. // TODO: it would be safer to only do this if // we verify that the types are the same. if oldType != nil && isStructUnionClass(oldType.Go) { t.Go = oldType.Go } }
この部分がこのコミットの核心です。
- 外側の
if
文if isStructUnionClass(sub.Go) || *godefs || *cdefs
は、変換対象の型が構造体、共用体、またはクラスである場合(あるいは-godefs
や-cdefs
モードの場合)に、Goの型をsub.Go
に設定する一般的な処理です。 - その内側に新しく追加された
if
文if oldType != nil && isStructUnionClass(oldType.Go)
が、匿名構造体のtypedef
に対する修正ロジックです。oldType != nil
: 同じtypedef
名が以前にも処理され、typedef
マップに登録されていることを意味します。isStructUnionClass(oldType.Go)
: 以前に登録されたtypedef
のGoの型も、構造体、共用体、またはクラスであったことを確認します。これは、匿名構造体へのtypedef
である可能性が高いことを示唆します。- この両方の条件が真である場合、
t.Go = oldType.Go
が実行されます。これは、現在処理しているtypedef
に対応するGoの型として、以前に生成されたGoの型 (oldType.Go
) を再利用することを意味します。
- 外側の
この変更により、cgo
は同じtypedef
名が匿名構造体を参照している場合、異なるGoファイルから参照されても、常に同じGoの型を生成するようになります。これにより、Goの型システムにおけるCの型の整合性が保たれ、型不一致の問題が解消されます。
コメント // TODO: it would be safer to only do this if // we verify that the types are the same.
は、この修正が完全な型検証を行っているわけではないことを示唆していますが、この特定のケース(匿名構造体へのtypedef
)においては、同じtypedef
名であれば同じ型であると仮定して再利用することが、実用上は正しい解決策であると判断されたことを示しています。
関連リンク
- Go Issue 8133: cmd/cgo: inconsistent definitions for C.sfColor https://github.com/golang/go/issues/8133
- Go Code Review (CL 102080043): cmd/cgo: use same Go type for typedef to anonymous struct https://go-review.googlesource.com/c/go/+/102080043
参考にした情報源リンク
- Go issue 8133に関するWeb検索結果
- Go言語の
cgo
に関する一般的なドキュメントと知識 - C言語の
typedef
と匿名構造体に関する一般的な知識 - DWARFデバッグ情報フォーマットに関する一般的な知識