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

[インデックス 14739] ファイルの概要

このコミットは、Go言語のランタイムにおけるCgo呼び出しに関連する_defer構造体のfreeフィールドを明示的にゼロ値(false)に初期化する変更です。これは、GoのセマンティクスとC言語のローカル変数の初期化の違いに起因する潜在的な問題を解決するためのものです。

コミット

runtime: zero d.free field

Not programming in Go anymore:
have to clear fields in local variables.

R=ken2
CC=golang-dev
https://golang.org/cl/7002053

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/403f012534b7042160d26377a70b6624ccfd976c

元コミット内容

commit 403f012534b7042160d26377a70b6624ccfd976c
Author: Russ Cox <rsc@golang.org>
Date:   Sat Dec 22 18:23:26 2012 -0500

    runtime: zero d.free field
    
    Not programming in Go anymore:
    have to clear fields in local variables.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7002053
---
 src/pkg/runtime/cgocall.c | 2 ++\
 1 file changed, 2 insertions(+)

diff --git a/src/pkg/runtime/cgocall.c b/src/pkg/runtime/cgocall.c
index 7b540951b3..ed859c07b9 100644
--- a/src/pkg/runtime/cgocall.c
+++ b/src/pkg/runtime/cgocall.c
@@ -132,6 +132,7 @@ runtime·cgocall(void (*fn)(void*), void *arg)
 	\td.link = g->defer;
 	\td.argp = (void*)-1;  // unused because unlockm never recovers
 	\td.special = true;
+\t\td.free = false;
 	\tg->defer = &d;
 	}
 
@@ -237,6 +238,7 @@ runtime·cgocallbackg(void (*fn)(void), void *arg, uintptr argsize)
 	\td.link = g->defer;
 	\td.argp = (void*)-1;  // unused because unwindm never recovers
 	\td.special = true;
+\t\td.free = false;
 	\tg->defer = &d;
 
 	\tif(raceenabled)

変更の背景

このコミットの背景には、Go言語のランタイムがC言語で書かれた部分(特にsrc/pkg/runtime/cgocall.c)とGo言語のセマンティクスとの間の微妙な相互作用があります。コミットメッセージの「Not programming in Go anymore: have to clear fields in local variables.」という一文がその核心を突いています。

Go言語では、変数を宣言すると自動的にその型のゼロ値で初期化されます。例えば、ブーリアン型はfalse、整数型は0、ポインタ型はnilになります。これはGoの設計思想の一部であり、未初期化変数によるバグを防ぐのに役立ちます。

しかし、Goランタイムの一部はC言語で書かれており、C言語ではローカル変数が自動的にゼロ初期化されるとは限りません。特にスタック上に確保される構造体のフィールドは、明示的に初期化しない限り、不定な(ガベージ)値を持つ可能性があります。

このコミットは、Cgo呼び出しの際にGoのdeferメカニズムを管理するために使用される_defer構造体が、C言語の関数内でローカル変数として宣言される場合に、そのfreeフィールドが不定な値を持つ可能性があるという問題に対処しています。freeフィールドが意図せずtrueになってしまうと、defer構造体が不適切に解放されようとするなど、ランタイムの不安定性やクラッシュを引き起こす可能性があります。

したがって、この変更は、C言語のコードベースにおいてGoのセマンティクス(ゼロ値初期化)を強制し、ランタイムの堅牢性と予測可能性を向上させることを目的としています。

前提知識の解説

Go言語のdeferステートメント

deferステートメントは、Go言語のユニークな機能の一つで、関数がリターンする直前に実行される関数呼び出しをスケジュールするために使用されます。これは、リソースの解放(ファイルのクローズ、ロックの解除など)やエラーハンドリングにおいて非常に便利です。deferされた関数は、その関数が正常に終了するか、パニックによって終了するかにかかわらず、必ず実行されます。

Goランタイム (runtime)

Goランタイムは、Goプログラムの実行を管理する低レベルなシステムです。これには、ガベージコレクション、ゴルーチンのスケジューリング、チャネル通信、メモリ管理、そしてCgo呼び出しの処理などが含まれます。ランタイムの大部分はGoで書かれていますが、OSとのインターフェースやパフォーマンスが重要な一部のコンポーネントはC言語(またはアセンブリ言語)で書かれています。

Cgo

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。これにより、既存のCライブラリをGoプロジェクトで利用したり、Goの特定の機能をCから利用したりすることが可能になります。Cgoは、GoとCの間のデータ変換やスタック管理など、複雑な相互運用メカニズムを扱います。

src/pkg/runtime/cgocall.c

このファイルは、Goランタイムの一部であり、Cgo呼び出しのメカニズムを実装しています。GoからCへの呼び出し(runtime·cgocall)や、CからGoへのコールバック(runtime·cgocallbackg)など、GoとCの間の遷移を管理する低レベルなCコードが含まれています。

_defer構造体

Goランタイム内部では、deferステートメントは_deferというC言語の構造体(またはそれに相当する内部表現)によって管理されます。この構造体は、deferされた関数へのポインタ、その引数、そして他の_defer構造体へのリンク(deferリストを形成するため)などの情報を含んでいます。

重要なフィールドの一つがfreeです。このフィールドは、_defer構造体がヒープから割り当てられたものであり、使用後に解放する必要があるかどうかを示すフラグとして機能します。通常、defer構造体はスタック上に割り当てられますが、deferの数が非常に多い場合や、特定の最適化パスではヒープに割り当てられることもあります。

C言語のローカル変数の初期化

C言語では、静的ストレージ期間を持つ変数(グローバル変数や静的ローカル変数)は自動的にゼロ初期化されますが、自動ストレージ期間を持つローカル変数(スタック上に確保される変数)は、明示的に初期化しない限り不定な値(以前そのメモリ位置にあったデータ)を持ちます。これは、Goの「ゼロ値」の概念とは対照的であり、GoランタイムのCコードでGoのセマンティクスを維持する際に注意が必要な点です。

技術的詳細

このコミットは、Goランタイムのsrc/pkg/runtime/cgocall.cファイル内の二つの関数、runtime·cgocallruntime·cgocallbackgにおける_defer構造体のインスタンスdfreeフィールドを明示的にfalseに設定しています。

runtime·cgocallはGoからCへの呼び出しを処理し、runtime·cgocallbackgはCからGoへのコールバックを処理します。これらの関数は、Cgo呼び出しのコンテキストでGoのdeferメカニズムが正しく機能するように、一時的な_defer構造体(d)をスタック上に作成します。

問題は、このdがC言語のローカル変数として宣言されているため、そのフィールドがGoのように自動的にゼロ初期化されない可能性があることです。特にd.freeフィールドは、ブーリアン型(またはそれに相当する整数型)であり、初期化されない場合、trueと解釈されうる非ゼロの値を持つ可能性があります。

もしd.freeが意図せずtrueになってしまうと、Goランタイムは、この_defer構造体がヒープから割り当てられたものであり、使用後にm_freeなどの関数で解放する必要があると誤解する可能性があります。しかし、実際にはdはスタック上に割り当てられた一時的な変数であるため、これを解放しようとすると、不正なメモリアクセスや二重解放などの深刻なランタイムエラー(クラッシュ)を引き起こす可能性があります。

このコミットは、d.free = false;という行を追加することで、この潜在的な問題を解決しています。これにより、d.freeは常にfalseであることが保証され、ランタイムがスタック上の_defer構造体を誤って解放しようとすることを防ぎます。これは、C言語のコードベースにおいてGoの「ゼロ値」の保証を明示的に適用する典型的な例であり、ランタイムの安定性と信頼性を向上させるための重要な修正です。

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

--- a/src/pkg/runtime/cgocall.c
+++ b/src/pkg/runtime/cgocall.c
@@ -132,6 +132,7 @@ runtime·cgocall(void (*fn)(void*), void *arg)
 	\td.link = g->defer;
 	\td.argp = (void*)-1;  // unused because unlockm never recovers
 	\td.special = true;
+\t\td.free = false;
 	\tg->defer = &d;
 	}
 
@@ -237,6 +238,7 @@ runtime·cgocallbackg(void (*fn)(void), void *arg, uintptr argsize)
 	\td.link = g->defer;
 	\td.argp = (void*)-1;  // unused because unwindm never recovers
 	\td.special = true;
+\t\td.free = false;
 	\tg->defer = &d;
 
 	\tif(raceenabled)

コアとなるコードの解説

変更はsrc/pkg/runtime/cgocall.cファイル内の2箇所にあります。

  1. runtime·cgocall関数内: この関数は、GoコードからCコードを呼び出す際に使用されます。GoのゴルーチンがC関数を呼び出す前に、現在のゴルーチンのdeferリストに一時的な_defer構造体dを追加しています。これは、Cgo呼び出し中にGoのランタイムが特定のクリーンアップ処理(例えば、unlockm)を実行できるようにするためです。 追加された行 d.free = false; は、このスタック上に確保された_defer構造体dfreeフィールドを明示的にfalseに設定しています。これにより、この_defer構造体がヒープから割り当てられたものではないことをランタイムに保証し、Cgo呼び出しの完了後に誤って解放されるのを防ぎます。

  2. runtime·cgocallbackg関数内: この関数は、CコードからGoコードへのコールバックを処理する際に使用されます。runtime·cgocallと同様に、Cgoコールバック中にGoのランタイムが特定のクリーンアップ処理(例えば、unwindm)を実行できるように、一時的な_defer構造体dを現在のゴルーチンのdeferリストに追加しています。 ここでも、追加された行 d.free = false; は、スタック上に確保された_defer構造体dfreeフィールドを明示的にfalseに設定し、誤ったメモリ解放を防ぐ役割を果たします。

どちらの変更も、C言語のローカル変数が自動的にゼロ初期化されないという特性を考慮し、GoランタイムのdeferメカニズムがCgoのコンテキストで堅牢かつ安全に動作することを保証するためのものです。これにより、ランタイムのクラッシュや予期せぬ動作を防ぎ、GoとCの相互運用性を安定させます。

関連リンク

参考にした情報源リンク

  • Go言語のゼロ値に関する解説: https://go.dev/doc/effective_go#zero_value
  • C言語のローカル変数の初期化に関する一般的な情報 (例: C Standard Library documentation, C programming tutorials)
  • Goの_defer構造体に関する議論やソースコードの分析 (例: GoのIssueトラッカー、Goのメーリングリストのアーカイブ、Goランタイムのソースコード自体)
  • Go CL 7002053: https://golang.org/cl/7002053 (このコミットの元のコードレビューページ)
  • Goのdeferメカニズムの内部動作に関するブログ記事や解説 (例: "Go's defer in depth" などで検索)
  • Goのガベージコレクションとメモリ管理に関する情報 (例: "Go's Memory Allocator" などで検索)