[インデックス 17871] ファイルの概要
このコミットは、Go言語のcmd/cgo
ツールにおけるバグ修正に関するものです。具体的には、Clangコンパイラを使用する際に、ポインタの配列のサイズが誤って計算される問題を修正しています。この問題は、Clangがポインタ型の「サイズ」フィールドをDWARF情報に記録しないことに起因し、cgo
が配列のサイズを計算する際に、ネストされたポインタ型のサイズが正しく反映されないために発生していました。
コミット
commit 6be1cb8c7a8771310e0cc36c3d8fa783d48d0cf9
Author: Russ Cox <rsc@golang.org>
Date: Thu Nov 7 15:24:51 2013 -0500
cmd/cgo: fix handling of array of pointers when using clang
Clang does not record the "size" field for pointer types,
so we must insert the size ourselves. We were already
doing this, but only for the case of pointer types.
For an array of pointer types, the setting of the size for
the nested pointer type was happening after the computation
of the size of the array type, meaning that the array type
was always computed as 0 bytes. Delay the size computation.
This bug happens on all Clang systems, not just FreeBSD.
Our test checked that cgo wrote something, not that it was correct.
FreeBSD's default clang rejects array[0] as a C struct field,
so it noticed the incorrect sizes. But the sizes were incorrect
everywhere.
Update testcdefs to check the output has the right semantics.
Fixes #6292.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/22840043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6be1cb8c7a8771310e0cc36c3d8fa783d48d0cf9
元コミット内容
cmd/cgo
: Clang使用時のポインタの配列のハンドリングを修正
Clangはポインタ型の「サイズ」フィールドを記録しないため、我々自身でサイズを挿入する必要がある。これは既にポインタ型の場合には行っていたが、ポインタの配列の場合、ネストされたポインタ型のサイズ設定が配列型のサイズ計算後に行われていたため、配列型が常に0バイトとして計算されていた。サイズ計算を遅延させる。
このバグはFreeBSDだけでなく、全てのClangシステムで発生する。我々のテストはcgoが何かを書き出すことをチェックしていただけで、それが正しいことをチェックしていなかった。FreeBSDのデフォルトのclangはC構造体フィールドとしてarray[0]
を拒否するため、不正なサイズに気づいた。しかし、サイズはどこでも不正だった。
testcdefs
を更新し、出力が正しいセマンティクスを持つことをチェックするようにした。
Fixes #6292.
変更の背景
Go言語のcgo
ツールは、GoプログラムからC言語のコードを呼び出す(またはその逆)ためのメカニズムを提供します。このツールは、Cの型定義をGoの型に変換する際に、コンパイラが生成するデバッグ情報(DWARF)を利用します。
問題は、Clangコンパイラがポインタ型のサイズ情報をDWARFエントリに記録しないという特定の挙動にありました。cgo
は、Cの構造体や配列のサイズを正確に計算するために、これらの型情報に依存しています。
既存のcgo
の実装では、ポインタ型自体のサイズ(例えば、64ビットシステムでは8バイト)は正しく挿入されていました。しかし、int8 *array3[20];
のような「ポインタの配列」の場合、cgo
はまず配列全体のサイズを計算しようとします。この時、配列の要素であるポインタのサイズがまだ不明(ClangのDWARF情報にはないため)だと、ポインタのサイズが0として扱われ、結果として配列全体のサイズも0バイトと誤って計算されてしまっていました。
このバグは、FreeBSDのClang環境で特に顕在化しました。FreeBSDのClangは、C構造体フィールドとしてarray[0]
のような不正なサイズを持つ配列を拒否する厳格なチェックを行うため、この誤ったサイズ計算がビルドエラーとして表面化したのです。しかし、コミットメッセージが示すように、この問題はFreeBSDに限定されず、Clangを使用する全てのシステムで潜在的に存在していました。既存のテストは、cgo
が何らかの出力を生成することを確認するだけで、その出力のセマンティクス(特にサイズ情報)が正しいことを検証していなかったため、これまで見過ごされていました。
この修正の目的は、cgo
がClang環境下でもポインタの配列のサイズを正確に計算できるようにし、クロスプラットフォームでのcgo
の信頼性を向上させることです。
前提知識の解説
cgo
cgo
はGo言語のツールチェーンの一部で、GoプログラムからC言語の関数を呼び出したり、C言語の型をGoの型として利用したりするためのものです。GoとCの間の相互運用性を提供し、既存のCライブラリをGoから利用する際に不可欠です。cgo
は、Goのソースコード内の特別なコメント(import "C"
ブロック)を解析し、Cのヘッダーファイルを読み込んで、GoとCの間でデータを変換するための接着コードを生成します。
DWARF (Debugging With Arbitrary Record Formats)
DWARFは、プログラムのソースコードとコンパイルされたバイナリコードの間のマッピングを記述するための標準的なデバッグ情報フォーマットです。コンパイラは、ソースコードをコンパイルする際に、変数名、型情報、関数名、行番号などのデバッグ情報をDWARF形式で生成し、実行可能ファイルに埋め込みます。デバッガや、今回のcgo
のように型情報を必要とするツールは、このDWARF情報を解析して、プログラムの構造を理解します。
sizeof
と offsetof
sizeof
: C言語の演算子で、指定された型または変数のメモリ上のサイズ(バイト単位)を返します。例えば、sizeof(int)
はint
型のサイズを返します。offsetof
: C言語のマクロで、構造体(struct
)の先頭から、指定されたメンバーまでのオフセット(バイト単位)を返します。例えば、offsetof(MyStruct, myMember)
はMyStruct
構造体内のmyMember
フィールドのオフセットを返します。 これらの演算子は、メモリレイアウトを理解し、ポインタ演算を行う上で非常に重要です。
ポインタ型と配列型
- ポインタ型: メモリ上の特定のアドレスを指す変数の型です。例えば、
int *p;
はint
型へのポインタp
を宣言します。ポインタ自体のサイズは、システムのアドレス空間のサイズ(32ビットシステムでは4バイト、64ビットシステムでは8バイト)に依存します。 - 配列型: 同じ型の要素が連続してメモリに配置されたデータ構造です。例えば、
int arr[10];
は10個のint
型要素を持つ配列を宣言します。配列のサイズは、要素の型サイズと要素数の積で決まります。 - ポインタの配列: ポインタを要素とする配列です。例えば、
int *arr_ptr[10];
は10個のint
型へのポインタを要素とする配列を宣言します。この場合、配列全体のサイズは、ポインタのサイズと要素数の積になります。
Clangの挙動
ClangはLLVMプロジェクトの一部であるC/C++/Objective-Cコンパイラです。コミットメッセージが指摘するように、Clangは特定の状況下でポインタ型のサイズ情報をDWARFエントリに明示的に記録しないことがあります。これは、ポインタのサイズがプラットフォームに依存し、コンパイル時に決定されるため、DWARFに固定値を記録するよりも、デバッガが実行時にポインタのサイズを決定する方が柔軟であるという設計思想によるものかもしれません。しかし、cgo
のようなツールにとっては、この情報が欠落していると型変換の際に問題を引き起こす可能性があります。
技術的詳細
このバグは、src/cmd/cgo/gcc.go
内のType
メソッドのロジックに起因していました。このメソッドは、CのDWARF型情報をGoの型に変換する役割を担っています。
元のコードでは、Type
メソッドの冒頭でt.Size = dtype.Size()
という行があり、ここでDWARF型から直接サイズを取得しようとしていました。しかし、Clangがポインタ型のサイズをDWARFに記録しないため、ポインタ型の場合、dtype.Size()
は0を返していました。
単一のポインタ型(例: int8 *
)の場合、cgo
は後続のロジックでこの0バイトのサイズを検出し、正しいポインタサイズ(例: 8バイト)に修正していました。
問題は、int8 *array3[20];
のような「ポインタの配列」の場合に発生しました。
cgo
はまず、配列型array3
のDWARF情報を処理します。- 配列のサイズを計算するために、
cgo
は要素の型(int8 *
)のサイズを必要とします。 - この時点で、ネストされたポインタ型
int8 *
のType
メソッドが再帰的に呼び出されます。 - 再帰呼び出しされた
Type
メソッドの冒頭で、t.Size = dtype.Size()
が実行されますが、Clangの挙動によりdtype.Size()
はポインタ型に対して0を返します。 - しかし、この0バイトのサイズが正しいポインタサイズに修正されるロジックは、
Type
メソッドの後半に位置していました。 - そのため、配列型
array3
のサイズ計算が実行される時点では、要素であるポインタのサイズがまだ0のままであり、結果として配列全体のサイズも20 * 0 = 0
と誤って計算されてしまっていたのです。
この修正は、このサイズ修正ロジックの実行タイミングを調整することで問題を解決しています。具体的には、t.Size <= 0
の場合に再度dtype.Size()
を呼び出すロジックを、Type
メソッドのより後方、つまりネストされたポインタ型のサイズが既に正しく設定された後に実行されるように移動しました。これにより、配列のサイズを計算する際には、要素であるポインタの正しいサイズが利用可能になり、配列全体のサイズも正確に計算されるようになります。
また、この修正には、misc/cgo/testcdefs
ディレクトリ内のテストケースの更新も含まれています。以前のテストは、cgo
が何らかの出力を生成することしか確認していませんでしたが、新しいテストは、生成されたCの構造体定義のsizeof
とoffsetof
が、元のCの定義と一致するかどうかを明示的にチェックすることで、セマンティクスが正しいことを検証しています。これにより、将来同様のサイズ計算のバグが導入されるのを防ぐための、より堅牢なテストカバレッジが提供されます。
コアとなるコードの変更箇所
変更は主に以下のファイルで行われています。
src/cmd/cgo/gcc.go
:Type
メソッドのロジック修正misc/cgo/testcdefs/main.c
: 新しいテストケースの追加misc/cgo/testcdefs/main.go
: テスト実行のためのmain
関数追加misc/cgo/testcdefs/test.bash
: テストスクリプトの更新
src/cmd/cgo/gcc.go
の変更
--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -1046,21 +1046,11 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
}
t := new(Type)
- t.Size = dtype.Size()
+ t.Size = dtype.Size() // note: wrong for array of pointers, corrected below
t.Align = -1
t.C = &TypeRepr{Repr: dtype.Common().Name}
c.m[dtype] = t
- if t.Size < 0 {
- // Unsized types are [0]byte
- t.Size = 0
- t.Go = c.Opaque(0)
- if t.C.Empty() {
- t.C.Set("void")
- }
- return t
- }
-
switch dt := dtype.(type) {
default:
fatalf("%s: unexpected type: %s", lineno(pos), dtype)
@@ -1207,6 +1197,9 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
return t
case *dwarf.StructType:
+ if dt.ByteSize < 0 { // opaque struct
+ break
+ }
// Convert to Go struct, being careful about alignment.
// Have to give it a name to simulate C "struct foo" references.
tag := dt.StructName
@@ -1325,6 +1318,25 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
}
}
+ if t.Size <= 0 {
+ // Clang does not record the size of a pointer in its DWARF entry,
+ // so if dtype is an array, the call to dtype.Size at the top of the function
+ // computed the size as the array length * 0 = 0.
+ // The type switch called Type (this function) recursively on the pointer
+ // entry, and the code near the top of the function updated the size to
+ // be correct, so calling dtype.Size again will produce the correct value.
+ t.Size = dtype.Size()
+ if t.Size < 0 {
+ // Unsized types are [0]byte
+ t.Size = 0
+ t.Go = c.Opaque(0)
+ if t.C.Empty() {
+ t.C.Set("void")
+ }
+ return t
+ }
+ }
+
if t.C.Empty() {
fatalf("%s: internal error: did not create C name for %s", lineno(pos), dtype)
}
misc/cgo/testcdefs/main.c
の追加
このファイルは、cgo
によって生成されたCの定義(CdefsTest
構造体)と、元のCの定義(CdefsOrig
構造体)のsizeof
とoffsetof
を比較するテストコードを含んでいます。これにより、cgo
が正しく型情報を変換しているかを確認します。
misc/cgo/testcdefs/main.go
の変更
テストを実行するためのGoのエントリポイントが追加されました。
misc/cgo/testcdefs/test.bash
の変更
テストスクリプトが更新され、go build .
の後に ./testcdefs
を実行して、main.c
で定義されたテストが実行されるようになりました。
コアとなるコードの解説
src/cmd/cgo/gcc.go
の Type
メソッド
このメソッドは、CのDWARF型をGoの型に変換する中心的なロジックを含んでいます。
-
初期のサイズ計算の変更:
- t.Size = dtype.Size() + t.Size = dtype.Size() // note: wrong for array of pointers, corrected below
t.Size = dtype.Size()
という行はそのまま残されていますが、コメントが追加され、ポインタの配列の場合にはこの初期計算が不正確である可能性が示唆されています。これは、ClangがポインタのサイズをDWARFに記録しないため、dtype.Size()
が0を返すことがあるためです。 -
初期のサイズチェックと
Opaque
型への変換ロジックの削除: 元のコードでは、t.Size < 0
(未定義サイズ)の場合にt.Size
を0に設定し、Opaque
型(Go側で不透明な型として扱う)に変換するロジックが、メソッドの早い段階にありました。このブロックが削除されました。これは、ポインタの配列のサイズ計算が遅延されるようになったため、この早期のチェックが不要になったか、あるいは誤ったタイミングで実行される可能性があったためと考えられます。 -
dwarf.StructType
の処理における変更:case *dwarf.StructType: if dt.ByteSize < 0 { // opaque struct break }
dwarf.StructType
を処理する際に、dt.ByteSize < 0
(不透明な構造体)の場合にbreak
する条件が追加されました。これは、不透明な構造体(定義が完全でない構造体)のサイズ計算をスキップするためのものです。 -
遅延されたサイズ計算と
Opaque
型への変換ロジックの追加:if t.Size <= 0 { // Clang does not record the size of a pointer in its DWARF entry, // so if dtype is an array, the call to dtype.Size at the top of the function // computed the size as the array length * 0 = 0. // The type switch called Type (this function) recursively on the pointer // entry, and the code near the top of the function updated the size to // be correct, so calling dtype.Size again will produce the correct value. t.Size = dtype.Size() if t.Size < 0 { // Unsized types are [0]byte t.Size = 0 t.Go = c.Opaque(0) if t.C.Empty() { t.C.Set("void") } return t } }
これがこのコミットの最も重要な変更点です。
Type
メソッドの最後に近い位置に、t.Size <= 0
の場合に再度dtype.Size()
を呼び出すロジックが追加されました。t.Size <= 0
の条件: これは、初期のdtype.Size()
の呼び出しでサイズが0(Clangのポインタ型の場合)または負の値(未定義サイズ)であった場合にトリガーされます。- 再度の
dtype.Size()
呼び出し: この時点で、もしdtype
がポインタの配列であり、その要素であるポインタ型が既に再帰的なType
呼び出しによって正しくサイズが設定されている場合、dtype.Size()
は配列の正しいサイズを返すようになります。これは、dtype.Size()
が内部的に要素のサイズに依存しているためです。 Opaque
型への変換ロジックの再配置:t.Size < 0
の場合にt.Size
を0に設定し、Opaque
型に変換するロジックが、この新しいブロック内に移動されました。これにより、未定義サイズの型が適切に処理されることが保証されます。
この変更により、ポインタの配列のサイズ計算が、ネストされたポインタのサイズが確定した後に実行されるようになり、Clang環境下でのcgo
の型変換の正確性が向上しました。
テストケースの変更 (misc/cgo/testcdefs/
)
main.c
では、CdefsOrig
という元のC構造体と、cgo
が生成するCdefsTest
構造体を定義し、それぞれのメンバーのsizeof
とoffsetof
を比較しています。特に、int8 *array3[20];
やint8 **array5[20][20];
のようなポインタの配列に対して、サイズとオフセットが一致するかを厳密にチェックしています。これにより、cgo
が生成する型定義のセマンティクスが正しいことを検証しています。main.go
は、main.c
で定義されたtest()
関数を呼び出し、その戻り値(テスト結果)をGoのos.Exit
に渡すシンプルなラッパーです。test.bash
は、go tool cgo -cdefs
コマンドでCの定義を生成し、その後go build . && ./testcdefs
でコンパイルとテストの実行を行うように変更されました。
これらのテストの追加により、cgo
が生成するCの型定義が、元のCの型定義とメモリレイアウトに関して互換性があることが保証されるようになりました。
関連リンク
- GitHubコミット: https://github.com/golang/go/commit/6be1cb8c7a8771310e0cc36c3d8fa783d48d0cf9
- Go Issue #6292: https://code.google.com/p/go/issues/detail?id=6292 (現在はGitHub Issuesに移行している可能性がありますが、当時のリンクです)
- Go CL 22840043: https://golang.org/cl/22840043 (当時のGerritコードレビューシステムへのリンク)
参考にした情報源リンク
- Go言語の公式ドキュメント (cgoに関する情報)
- DWARF Debugging Information Format (DWARFの仕様に関する情報)
- Clang Compiler User's Manual (Clangの挙動に関する情報)
- C言語の
sizeof
とoffsetof
演算子に関する一般的な情報 - ポインタと配列に関するC言語の基本的な概念