[インデックス 17785] ファイルの概要
このコミットは、Go言語のmisc/cgo/test
ディレクトリにあるC言語からのパニックテストが、gccgo
コンパイラで正しく動作するように修正するものです。具体的には、Goの標準コンパイラ(gc
)とgccgo
コンパイラで、C言語からGoのパニック機構を呼び出す際の挙動の違いに対応するため、ビルドタグ(+build gc
と+build gccgo
)を用いて異なる実装を提供しています。
コミット
commit cd61565ffc003506c9544eb670eed195825bf4da
Author: Ian Lance Taylor <iant@golang.org>
Date: Fri Oct 11 11:24:54 2013 -0700
misc/cgo/test: fix C panic test to work with gccgo
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/14611043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cd61565ffc003506c9544eb670eed195825bf4da
元コミット内容
misc/cgo/test: fix C panic test to work with gccgo
このコミットは、C言語からGoのパニックを発生させるテストが、gccgo
コンパイラ環境で正しく機能するように修正することを目的としています。
変更の背景
Go言語は、公式のGoコンパイラ(gc
)と、GCCをバックエンドとするgccgo
という2つの主要なコンパイラ実装を持っています。これらはGoのコードをコンパイルしますが、特にCgo(GoとC言語の相互運用機能)のような低レベルな部分では、内部的な実装や挙動に違いが生じることがあります。
このコミットの背景には、C言語からGoのパニック機構を呼び出すテストケースが、gccgo
環境で期待通りに動作しないという問題がありました。コミットメッセージには「This is what SWIG does.」とあり、これはSWIG(Simplified Wrapper and Interface Generator)のようなツールがCgoを介してGoのパニックを呼び出す際に、同様の問題に直面する可能性があることを示唆しています。SWIGは、異なるプログラミング言語間でインターフェースを生成するためのツールであり、GoとC/C++を連携させる際にも利用されます。
gc
とgccgo
の間でCgoの内部実装、特にGoランタイムとの連携方法に差異があったため、C言語から_cgo_panic
を呼び出すメカニズムがgccgo
では異なる方法で処理される必要がありました。このコミットは、その差異を吸収し、両方のコンパイラでテストが成功するようにするためのものです。
前提知識の解説
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Goのソースファイル内にimport "C"
という行を記述することで有効になり、C言語の関数や型をGoのコードから直接利用できるようになります。Cgoは、GoとCの間のデータ変換やスタック管理など、複雑な相互運用を抽象化して提供します。
Goのパニック (Panic)
Goにおけるパニックは、プログラムの回復不可能なエラー状態を示すメカニズムです。通常、Goプログラムはエラーをerror
型で明示的に返しますが、予期せぬ致命的な状況(例:nilポインタのデリファレンス、配列の範囲外アクセス)ではパニックが発生します。パニックが発生すると、通常の実行フローは中断され、遅延関数(defer
)が実行された後、スタックが巻き戻されます。recover
関数を使うことで、パニックを捕捉し、プログラムのクラッシュを防ぐことも可能です。
crosscall2
と_cgo_panic
_cgo_panic
: Cgoを介してC言語からGoのパニック機構を呼び出すためのGoランタイム内部の関数です。Cgoによって生成されたコードや、SWIGのようなツールがGoのパニックをトリガーするために使用します。crosscall2
: Goランタイム内部の関数で、Cgoを介してGoの関数をC言語から呼び出す際に使用される低レベルなメカニズムです。GoとCのスタックフレームの切り替えや引数の受け渡しなどを担当します。_cgo_panic
を呼び出すためにcrosscall2
が使われるのは、_cgo_panic
がGoの関数であり、C言語からGoの関数を呼び出すための一般的なCgoのメカニズムが必要だからです。
gc
とgccgo
gc
(Go Compiler): Goプロジェクトによって開発されている公式のGoコンパイラです。Go言語の進化に合わせて常に最新の機能と最適化が提供されます。gccgo
: GCC(GNU Compiler Collection)のフロントエンドとして実装されたGoコンパイラです。GCCの最適化バックエンドを利用できるため、特定の環境や用途で利点がある場合があります。しかし、gc
とは異なるコード生成戦略やランタイムとの連携方法を持つため、Cgoのような低レベルな相互運用においては挙動の差異が生じることがあります。
ビルドタグ (+build
)
Goのビルドタグは、特定のファイルが特定のビルド条件(例:OS、アーキテクチャ、コンパイラ、カスタムタグ)が満たされた場合にのみコンパイルされるように指定するためのディレクティブです。ソースファイルの先頭に// +build tagname
のように記述します。このコミットでは、+build gc
と+build gccgo
が使用されており、それぞれgc
コンパイラとgccgo
コンパイラでビルドされる場合にのみ、そのファイルがコンパイル対象となることを意味します。これにより、コンパイラごとの異なる実装を同じプロジェクト内で共存させることができます。
技術的詳細
このコミットの核心は、C言語からGoのパニックをトリガーするメカニズムが、gc
コンパイラとgccgo
コンパイラで異なる実装を必要とすることにあります。
元のmisc/cgo/test/callback_c.c
には、callPanic
という関数がありました。この関数は、Goの内部関数である_cgo_panic
をcrosscall2
を介して呼び出し、その後意図的に不正なメモリアクセス(*(int*)1 = 1;
)を行ってCレベルでのクラッシュを引き起こしていました。これは、Cgoを介してGoのパニックが適切に処理されるか、あるいはCレベルのクラッシュがGoのパニックとして捕捉されるか、といった挙動をテストするためのものでした。
しかし、gccgo
コンパイラは、gc
コンパイラとは異なるCgoのランタイム統合を持っています。特に、_cgo_panic
のシグネチャ(引数の型)や、C言語からGoの関数を呼び出すための内部メカニズムがgc
とは異なっていたと考えられます。
-
gc
コンパイラの場合:_cgo_panic
は、パニックメッセージを含む構造体へのポインタと、そのサイズを引数として受け取ります。crosscall2
は、Goの関数を呼び出すための汎用的なメカニズムとして機能し、この引数渡しを処理します。callback_c_gc.c
に移動されたコードは、このgc
の挙動に合致しています。*(int*)1 = 1;
という行は、_cgo_panic
が呼び出された後にCレベルで発生するクラッシュをシミュレートし、GoのパニックハンドリングがCのクラッシュを適切に処理できるかをテストする意図があったと推測されます。 -
gccgo
コンパイラの場合:gccgo
における_cgo_panic
は、より単純にパニックメッセージの文字列(const char *
)を直接引数として受け取るように設計されています。gccgo
のCgoランタイムは、crosscall2
のような汎用的なGo関数呼び出しメカニズムを介さずに、C言語から直接_cgo_panic
を呼び出すための最適化されたパスを持っている可能性があります。そのため、callback_c_gccgo.c
では、crosscall2
を使わずに直接_cgo_panic("panic from C");
と呼び出しています。また、*(int*)1 = 1;
のようなCレベルのクラッシュを誘発するコードは含まれていません。これは、gccgo
のテストではGoのパニックがCから正しくトリガーされることのみを検証し、Cレベルのクラッシュのハンドリングは別の方法でテストされるか、あるいはgccgo
のCgo実装ではそのテストが不要であるためと考えられます。
この変更により、gc
とgccgo
の両方のコンパイラで、C言語からGoのパニックを発生させるテストが、それぞれのコンパイラのCgo実装の特性に合わせて正しく実行されるようになりました。
コアとなるコードの変更箇所
このコミットでは、以下の3つのファイルが変更されています。
-
misc/cgo/test/callback_c.c
:callPanic
関数がこのファイルから削除されました。
-
misc/cgo/test/callback_c_gc.c
: (新規ファイル)+build gc
ビルドタグが追加されました。callback_c.c
から削除されたcallPanic
関数が、このファイルに移動されました。この関数はcrosscall2
を介して_cgo_panic
を呼び出し、その後意図的に不正なメモリアクセスを行います。
-
misc/cgo/test/callback_c_gccgo.c
: (新規ファイル)+build gccgo
ビルドタグが追加されました。callPanic
関数が新しく定義されました。この関数は、_cgo_panic
を直接呼び出し、パニックメッセージの文字列を引数として渡します。crosscall2
や不正なメモリアクセスは含まれません。
コアとなるコードの解説
misc/cgo/test/callback_c.c
からの削除
-/* Test calling panic from C. This is what SWIG does. */
-
-extern void crosscall2(void (*fn)(void *, int), void *, int);
-extern void _cgo_panic(void *, int);
-
-void
-callPanic(void)
-{
- struct { const char *p; } a;
- a.p = "panic from C";
- crosscall2(_cgo_panic, &a, sizeof a);
- *(int*)1 = 1;
-}
このコードブロックは、C言語からGoのパニックを呼び出すためのテスト関数callPanic
を定義していました。crosscall2
を使って_cgo_panic
を呼び出し、その後*(int*)1 = 1;
で意図的にセグメンテーション違反を起こしていました。この実装は、gc
コンパイラに特化したものであったため、汎用的なcallback_c.c
から削除されました。
misc/cgo/test/callback_c_gc.c
(新規ファイル)
// 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.
// +build gc
#include "_cgo_export.h"
/* Test calling panic from C. This is what SWIG does. */
extern void crosscall2(void (*fn)(void *, int), void *, int);
extern void _cgo_panic(void *, int);
void
callPanic(void)
{
struct { const char *p; } a;
a.p = "panic from C";
crosscall2(_cgo_panic, &a, sizeof a);
*(int*)1 = 1;
}
このファイルは、+build gc
というビルドタグを持つため、Goの標準コンパイラ(gc
)でビルドされる場合にのみコンパイルされます。内容は、callback_c.c
から移動された元のcallPanic
関数と全く同じです。これは、gc
コンパイラがcrosscall2
と_cgo_panic(void *, int)
のシグネチャを期待しているためです。*(int*)1 = 1;
は、_cgo_panic
が呼び出された後にCレベルで発生するクラッシュをシミュレートし、GoのパニックハンドリングがCのクラッシュを適切に処理できるかをテストする意図があります。
misc/cgo/test/callback_c_gccgo.c
(新規ファイル)
// 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.
// +build gccgo
#include "_cgo_export.h"
/* Test calling panic from C. This is what SWIG does. */
extern void _cgo_panic(const char *);
void
callPanic(void)
{
_cgo_panic("panic from C");
}
このファイルは、+build gccgo
というビルドタグを持つため、gccgo
コンパイラでビルドされる場合にのみコンパイルされます。gccgo
の_cgo_panic
は、const char *
型の引数を直接受け取るように定義されているため、crosscall2
を介さずに直接_cgo_panic("panic from C");
と呼び出しています。また、gc
版にあった*(int*)1 = 1;
のようなCレベルのクラッシュを誘発するコードは含まれていません。これは、gccgo
のCgo実装におけるパニックテストの要件がgc
とは異なることを示しています。
関連リンク
- Go CL 14611043: https://golang.org/cl/14611043
参考にした情報源リンク
- Go言語のCgoに関する公式ドキュメント: https://go.dev/blog/cgo
- Go言語のパニックとリカバリーに関する公式ドキュメント: https://go.dev/blog/defer-panic-and-recover
- Goのビルドタグに関する公式ドキュメント: https://go.dev/cmd/go/#hdr-Build_constraints
- GCCGoに関する情報 (GCCの公式ドキュメントなど): https://gcc.gnu.org/onlinedocs/gccgo/
- SWIGの公式ウェブサイト: http://www.swig.org/
- Goのソースコード(特に
src/runtime/cgo/
ディレクトリやsrc/cmd/cgo/
ディレクトリ) - Goのテストコード(特に
misc/cgo/test/
ディレクトリ) - GoのIssueトラッカーやメーリングリストでの関連議論(もしあれば)
- Stack Overflowや技術ブログでのCgo、panic、gccgoに関する解説記事(一般的な理解のため)