[インデックス 16397] ファイルの概要
このコミットは、Go言語のcgo
ツールにおける、Goの文字列(string)とスライス(slice)の構造体定義において、C言語側で使用されるサイズおよび長さの型をint
からintgo
に変更するものです。これにより、異なるアーキテクチャ間でのGoとCの相互運用性における潜在的なバグ(特にポインタサイズと整数サイズの不一致に起因する問題)を修正します。具体的には、Goのstring
やslice
がC言語にエクスポートされる際に、その長さや容量を表すフィールドの型が、C側で正しく解釈されるように調整されます。
コミット
commit 8f7863d6382d92267bff313564d3e077d5e68ac3
Author: Ian Lance Taylor <iant@golang.org>
Date: Thu May 23 22:51:07 2013 -0700
cmd/cgo: use intgo, not int, for string and slice structures
Fixes #5548.
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/9643044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8f7863d6382d92267bff313564d3e077d5e68ac3
元コミット内容
cmd/cgo: use intgo, not int, for string and slice structures
Fixes #5548.
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/9643044
変更の背景
この変更は、Go言語のIssue #5548を修正するために行われました。Issue #5548は、cgo
を使用してGoの関数をCから呼び出す際に、Goのstring
型やslice
型が引数として渡されると、C側でそれらの長さや容量が正しく解釈されない可能性があるという問題でした。
具体的には、Goのstring
型は内部的にポインタと長さ(len
)のペアで、slice
型はポインタ、長さ(len
)、容量(cap
)のトリプルで表現されます。これらの長さや容量のフィールドはGoのint
型ですが、C言語のint
型はシステムによってサイズが異なる場合があります(例: 32ビットシステムでは4バイト、64ビットシステムでは4バイトまたは8バイト)。
問題は、C側でGoのstring
やslice
構造体を受け取る際に、Cのint
がGoのint
と同じサイズであると仮定してしまうことにありました。特に、64ビットシステムでCのint
が32ビットである場合、Goのint
(通常64ビット)で表現された長さや容量がC側で切り詰められたり、誤ったメモリレイアウトでアクセスされたりする可能性がありました。これにより、GoからCへのコールバックで文字列やスライスの情報が破損し、クラッシュや不正な動作を引き起こす可能性がありました。
このコミットは、cgo
が生成するCのコードにおいて、Goのint
に対応する型として、ポインタサイズと同じサイズの整数型であるintgo
を使用することで、この問題を解決します。
前提知識の解説
cgo
cgo
は、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoのツールです。GoとCの間のデータ型の変換や関数呼び出しのメカニズムを提供し、異なる言語間の相互運用性を可能にします。cgo
は、Goのソースコード内にCのコードを直接記述したり、既存のCライブラリをリンクしたりするために使用されます。
Goの文字列(string)とスライス(slice)の内部表現
Goのstring
型は、読み取り専用のバイト列へのポインタと、そのバイト列の長さ(バイト数)のペアとして内部的に表現されます。
例: type stringStruct struct { ptr *byte; len int }
Goのslice
型は、基底配列へのポインタ、スライスの長さ(len
)、そしてスライスの容量(cap
)のトリプルとして内部的に表現されます。
例: type sliceStruct struct { array unsafe.Pointer; len int; cap int }
ここで、len
とcap
はGoの組み込み型int
で表現されます。Goのint
型は、実行環境のCPUアーキテクチャに依存し、32ビットシステムでは32ビット、64ビットシステムでは64ビットの整数型となります。
C言語の整数型と__PTRDIFF_TYPE__
C言語のint
型は、そのサイズが標準で保証されていません。通常、16ビット、32ビット、または64ビットのいずれかですが、具体的なサイズはコンパイラやターゲットアーキテクチャに依存します。
一方、__PTRDIFF_TYPE__
はGCCなどのコンパイラで定義されるマクロで、2つのポインタの差を保持できる符号付き整数型を表します。これは通常、ポインタと同じサイズ(32ビットシステムでは32ビット、64ビットシステムでは64ビット)になります。この特性から、ポインタサイズに依存するGoのint
型とC言語側で安全にやり取りするために適しています。
intgo
の導入
このコミットで導入されるintgo
は、cgo
がGoとCの間でデータをやり取りする際に、Goのint
型に対応するCの型として定義されます。intgo
は、__PTRDIFF_TYPE__
が存在すればそれを使用し、そうでなければlong long
(64ビットシステムの場合)またはint
(32ビットシステムの場合)を使用するように定義されます。これにより、Goのint
が32ビットでも64ビットでも、C側で常に適切なサイズの整数型として扱われるようになります。
技術的詳細
このコミットの核心は、cgo
がGoのstring
やslice
をC言語の構造体として表現する際に、その長さや容量のフィールドの型をint
からintgo
に変更することです。
変更前は、cgo
はGoのstring
をCの_GoString_
構造体(char *p; int n;
)として、Goのslice
をCの_GoBytes_
構造体(char *p; int n; int c;
)として定義していました。ここで、n
(長さ)やc
(容量)のフィールドはCのint
型でした。
問題は、64ビットシステムにおいてGoのint
が64ビットであるのに対し、Cのint
が32ビットである場合に発生しました。この場合、GoからCへstring
やslice
が渡されると、Goの64ビットの長さ/容量がCの32ビットのint
にコピーされる際に上位ビットが切り捨てられ、C側で不正な長さや容量として解釈される可能性がありました。
このコミットでは、src/cmd/cgo/out.go
内のbuiltinProlog
定数に、intgo
の定義が追加されました。
/* Define intgo when compiling with GCC. */
#ifdef __PTRDIFF_TYPE__
typedef __PTRDIFF_TYPE__ intgo;
#elif defined(_LP64)
typedef long long intgo;
#else
typedef int intgo;
#endif
この定義により、intgo
は以下のように決定されます。
__PTRDIFF_TYPE__
が定義されていれば、それを使用します。これは通常、ポインタと同じサイズ(64ビットシステムでは64ビット)になります。_LP64
(64ビットシステムを示すマクロ)が定義されていれば、long long
(通常64ビット)を使用します。- それ以外の場合(主に32ビットシステム)、
int
を使用します。
このintgo
型が、_GoString_
と_GoBytes_
構造体のn
およびc
フィールドの型として使用されるようになりました。
typedef struct { char *p; intgo n; } _GoString_;
typedef struct { char *p; intgo n; intgo c; } _GoBytes_;
さらに、GoStringN
やGoBytes
といったGoとCの間で文字列やバイト列を変換するヘルパー関数の引数型もint32
からintgo
に変更されました。
これにより、Goのint
型が32ビットであろうと64ビットであろうと、C側でそのサイズに合わせたintgo
型で長さや容量が表現されるため、データの切り捨てや誤った解釈を防ぐことができます。
また、src/cmd/cgo/out.go
のcgoType
関数内のGoのstring
とslice
のサイズ計算も調整されました。
string
のサイズは、以前は「1ポインタ + 1 int」で「アライメントにより常に2ポインタに丸められる」とコメントされていましたが、新しいコメントでは「1ポインタ + 1 (ポインタサイズの) int」と明確化され、2 * p.PtrSize
という計算は維持されています。これは、intgo
がポインタサイズと同じになるため、Goのstring
のメモリレイアウトがC側で正しく解釈されることを保証します。slice
のサイズは、以前はp.PtrSize + 8
(ポインタ + 8バイト)とされていましたが、p.PtrSize * 3
(ポインタ + 2つのポインタサイズの整数)に変更されました。これは、スライスがポインタ、長さ、容量の3つのポインタサイズの要素で構成されることを正確に反映しています。
これらの変更により、GoとCの間で文字列やスライスが受け渡される際のメモリレイアウトと型の整合性が保証され、Issue #5548で報告されたような問題が解決されます。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下のファイルに集中しています。
-
src/cmd/cgo/out.go
:cgo
ツールがCのヘッダーやスタブコードを生成する際のロジックが含まれています。builtinProlog
定数にintgo
型の定義が追加されました。_GoString_
および_GoBytes_
構造体のn
(長さ)およびc
(容量)フィールドの型がint
からintgo
に変更されました。_Cfunc_GoStringN
および_Cfunc_GoBytes
関数の引数型がint32
からintgo
に変更されました。- Goの
string
とslice
のcgoType
におけるサイズ計算のコメントと値が更新されました。 GoString
とGoSlice
のC側のtypedef定義で、n
とlen
/cap
の型がint
からGoInt
に変更されました。
-
misc/cgo/test/cgo_test.go
:cgo
のテストスイートのメインファイル。Test5548
という新しいテスト関数が追加されました。
-
misc/cgo/test/issue5548.go
: Issue #5548を再現し、修正を検証するためのGoのテストファイル。issue5548FromC
というGo関数が定義されており、Cから呼び出されることを想定しています。この関数はstring
とint
を引数にとり、それらの値が期待通りであるかを検証します。test5548
関数がCの関数issue5548_in_c
を呼び出し、結果を検証します。
-
misc/cgo/test/issue5548_c.c
: Issue #5548を再現し、修正を検証するためのCのテストファイル。issue5548_in_c
というC関数が定義されており、Goの関数issue5548FromC
を呼び出します。call_go
関数内で、GoのGoString
構造体をC側で手動で構築し、Go関数に渡しています。ここでGoString
のn
フィールドにGoのint
に対応する値が設定されます。clobber_stack
関数は、スタックを破壊することで、メモリレイアウトの不整合が顕在化しやすい状況を作り出しています。
コアとなるコードの解説
src/cmd/cgo/out.go
このファイルはcgo
のバックエンドの一部であり、Goの型とCの型の間で相互運用するためのCコードを生成する役割を担っています。
-
builtinProlog
のintgo
定義:const builtinProlog = ` /* Define intgo when compiling with GCC. */ #ifdef __PTRDIFF_TYPE__ typedef __PTRDIFF_TYPE__ intgo; #elif defined(_LP64) typedef long long intgo; #else typedef int intgo; #endif // ... typedef struct { char *p; intgo n; } _GoString_; typedef struct { char *p; intgo n; intgo c; } _GoBytes_; // ... _GoString_ GoStringN(char *p, intgo l); _GoBytes_ GoBytes(void *p, intgo n); `
この部分が、Goの
int
型に対応するC側の型intgo
を定義しています。__PTRDIFF_TYPE__
はポインタの差を表現できる型であり、通常ポインタと同じサイズです。これにより、Goのint
が64ビットの場合でも、C側で正しく64ビットの整数として扱われるようになります。_GoString_
と_GoBytes_
構造体のn
とc
フィールドがintgo
に変更されたことで、Goの文字列やスライスの長さ・容量がC側で正しく解釈されるようになります。また、関連するヘルパー関数GoStringN
とGoBytes
の引数型もintgo
に変更されています。 -
cgoType
関数内のサイズ計算の修正:case *ast.ArrayType: if t.Len == nil { // Slice: pointer, len, cap. return &Type{Size: p.PtrSize * 3, Align: p.PtrSize, C: c("GoSlice")} } // ... if t.Name == "string" { // The string data is 1 pointer + 1 (pointer-sized) int. return &Type{Size: 2 * p.PtrSize, Align: p.PtrSize, C: c("GoString")} }
Goの
slice
型はポインタ、長さ、容量の3つの要素で構成されるため、そのサイズはp.PtrSize * 3
と正確に計算されるようになりました。string
型についても、ポインタとポインタサイズの整数(intgo
)で構成されることがコメントで明確化され、サイズ計算2 * p.PtrSize
がその表現と一致することが示されています。これにより、cgo
がGoの型をCの型にマッピングする際のメモリレイアウトの整合性が向上します。 -
_Cfunc_GoStringN
と_Cfunc_GoBytes
の引数型変更:void ·_Cfunc_GoStringN(int8 *p, intgo l, String s) // ... void ·_Cfunc_GoBytes(int8 *p, intgo l, Slice s)
これらの関数は、CからGoの文字列やスライスを生成する際に使用される内部的なヘルパー関数です。引数
l
(長さ)の型がint32
からintgo
に変更されたことで、Goのint
の実際のサイズ(32ビットまたは64ビット)に関わらず、C側で正しく長さが渡されるようになります。
misc/cgo/test/issue5548.go
と misc/cgo/test/issue5548_c.c
これらのファイルは、Issue #5548で報告された問題を再現し、修正が正しく機能するかを検証するためのテストケースです。
-
issue5548.go
://export issue5548FromC func issue5548FromC(s string, i int) int { if len(s) == 4 && s == "test" && i == 42 { return 1 } return 0 }
このGo関数
issue5548FromC
は、Cから呼び出されることを想定しています。string
型の引数s
とint
型の引数i
を受け取り、それらの値が期待通りであるか(文字列が"test"で長さが4、整数が42)を検証します。もしcgo
が文字列の長さを正しく扱えない場合、len(s)
が期待値と異なる結果となり、テストが失敗します。 -
issue5548_c.c
:static int call_go() { GoString s; s.p = "test"; s.n = 4; // ここでGoStringの長さを設定 return issue5548FromC(s, 42); } int issue5548_in_c() { clobber_stack(); // スタックを破壊して問題が顕在化しやすい状況を作る return call_go(); }
このCコードでは、Goの
GoString
構造体をC側で手動で構築し、Go関数issue5548FromC
に渡しています。特にs.n = 4;
の部分で、C側でGoの文字列の長さを設定しています。変更前は、このn
フィールドがCのint
型として定義されていたため、64ビットシステムでGoのint
が64ビットである場合に、Cのint
が32ビットだと、Go関数が受け取る長さが不正になる可能性がありました。clobber_stack()
関数は、スタックを意図的に破壊することで、メモリレイアウトの不整合がより顕著に現れるようにしています。このテストケースは、intgo
の導入によって、CからGoへ文字列が渡される際の長さ情報が正しく伝達されることを確認します。
これらの変更とテストケースにより、cgo
がGoの文字列やスライスをCと相互運用する際の型の整合性が大幅に向上し、異なるアーキテクチャ間での潜在的なバグが修正されました。
関連リンク
- GitHubコミット: https://github.com/golang/go/commit/8f7863d6382d92267bff313564d3e077d5e68ac3
- Go Issue #5548: https://go.dev/issue/5548 (Web検索結果から推測されるリンク)
- Gerrit Change-Id:
https://golang.org/cl/9643044
(コミットメッセージに記載)
参考にした情報源リンク
- Web search results for "Go issue 5548":
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE3s9jWS70G64chNmhxhEQNV7mT_EN8btoIvMGEb3rBHStN0Mguth0ad1hZfGP6o4XiW9j70ACAntWIXvGm5BQRUhDz-NmrOfvWA7wQD1VqvvG8g7vhpyoWYrLU2TCgBHBQc9aJEUUZu3SLaiHS808oHzhV9bnZdo77KoKHdKIv0fN7IWcjqPPtC9Uhe7dLGA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFLIPq8uiL5NtJwldZtUSkZHIUd0QSkj4FHpucmHeWjkeBKCAcSuYDrL9T70d_lsiz0biDzGaz8A6xSWcfLi-jQq4H60Dbwa7UMpgYjP37fevhcjrEoawsb-Q_K0NdRD4CpNHO2xliL4JaVY32LW5nmTy847WgSWmdjB0h30r4zRkc-yXA9Cl0sWABgqgE-rYPEMH9vQfmrjRcD
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH2zb6npBRSTO3VC-jjMAGXKlkqPySi4-jmzYm8HLsVyDYcG1aEiWp4dVp_vOitn-g5f3QRWVQyN0N0oBgX6JyKKHTVGQbsjopGd6dmVnK9q0xMoDekOy_kfEePTdrK9okjW0ZwwwnLkPrVHDkg42CElioy_nh1J18fHaxEQHm3V08lRvKzvxsab34v0uKqFwHU_88ud3DEnY9RC6k=