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

[インデックス 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++を連携させる際にも利用されます。

gcgccgoの間で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のメカニズムが必要だからです。

gcgccgo

  • 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_paniccrosscall2を介して呼び出し、その後意図的に不正なメモリアクセス(*(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実装ではそのテストが不要であるためと考えられます。

この変更により、gcgccgoの両方のコンパイラで、C言語からGoのパニックを発生させるテストが、それぞれのコンパイラのCgo実装の特性に合わせて正しく実行されるようになりました。

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

このコミットでは、以下の3つのファイルが変更されています。

  1. misc/cgo/test/callback_c.c:

    • callPanic関数がこのファイルから削除されました。
  2. misc/cgo/test/callback_c_gc.c: (新規ファイル)

    • +build gcビルドタグが追加されました。
    • callback_c.cから削除されたcallPanic関数が、このファイルに移動されました。この関数はcrosscall2を介して_cgo_panicを呼び出し、その後意図的に不正なメモリアクセスを行います。
  3. 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言語の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に関する解説記事(一般的な理解のため)