[インデックス 16230] ファイルの概要
このコミットは、Goコンパイラのガベージコレクション(GC)に関連するリフレクション処理におけるクラッシュバグを修正するものです。具体的には、型情報の幅(t->width)が適切に初期化されていない場合に発生する問題に対処しています。
コミット
commit 13cbf41a7f22f16354b06b159f087d8f64abfb37
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Thu Apr 25 18:47:12 2013 +0200
cmd/gc: initialize t->width in dgcsym() if required
Update #5291.
R=golang-dev, daniel.morsing, iant, r
CC=golang-dev
https://golang.org/cl/8663052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/13cbf41a7f22f16354b06b159f087d8f64abfb37
元コミット内容
cmd/gc: initialize t->width in dgcsym() if required
Update #5291.
変更の背景
このコミットは、Goコンパイラ(cmd/gc)におけるガベージコレクション(GC)関連のクラッシュバグ、具体的にはGo Issue 5291を修正するために導入されました。
Goのコンパイラは、プログラムの型情報を処理し、それに基づいてメモリレイアウトやGCの動作を決定します。このプロセスにおいて、各型のメモリ上のサイズ(幅、width)は非常に重要な情報です。reflect.cファイルは、GoのランタイムリフレクションとGCが型情報をどのように扱うかを定義する部分です。
Issue 5291は、特定の複雑な型(ポインタのポインタ、特にスライスへのポインタなど)がGCによって処理される際に、その型のwidthフィールドがBADWIDTH(未初期化または無効な状態を示す値)のままであるために発生するクラッシュでした。dgcsym関数やdgcsym1関数は、GCがシンボル(型)を走査し、そのポインタ情報を収集する際に使用されます。これらの関数がwidthが未初期化の型に遭遇すると、不正なメモリアクセスや計算が行われ、結果としてコンパイラやランタイムがクラッシュするという問題がありました。
このバグは、特にGCが実行されるタイミングで、特定の型の構造がリフレクションによって検査される際に顕在化しました。修正の目的は、GCが型情報を利用する前に、必要な型のwidthが確実に計算され、初期化されていることを保証することです。
前提知識の解説
Goコンパイラ (cmd/gc)
Go言語の公式コンパイラは、cmd/gcというディレクトリにその主要なコードベースがあります。これは、Goソースコードを機械語に変換する役割を担っています。コンパイルプロセスには、字句解析、構文解析、型チェック、中間コード生成、最適化、コード生成などが含まれます。
ガベージコレクション (GC)
Goは自動メモリ管理(ガベージコレクション)を採用しています。GCは、プログラムが動的に確保したメモリのうち、もはや到達不可能(参照されていない)になったものを自動的に解放する仕組みです。GoのGCは並行・低遅延が特徴で、プログラムの実行と並行して動作します。GCは、メモリ上のオブジェクトの型情報(特にポインタが含まれているか、そのサイズはどれくらいかなど)を利用して、どのメモリ領域をスキャンすべきかを判断します。
リフレクション (Reflection)
Goのリフレクションは、プログラムの実行中に型情報(reflect.Type)や値情報(reflect.Value)を検査・操作する機能です。これにより、コンパイル時には未知の型や構造を持つデータを動的に扱ったり、汎用的なデータ処理ロジックを記述したりすることが可能になります。コンパイラの内部では、リフレクションが利用する型情報を生成し、ランタイムがそれを参照できるようにしています。
Type構造体とwidth、align
Goコンパイラの内部では、Type構造体がGo言語の各型の情報を表現します。この構造体には、型の種類(etype)、要素の型(type、スライスや配列の場合)、配列の要素数(bound)、そしてメモリ上のサイズ(width)やアライメント(align)などの情報が含まれます。
width: その型がメモリ上で占めるバイト数を示します。例えば、int型は通常8バイト、bool型は1バイトなどです。構造体や配列の場合、そのメンバーのwidthとアライメントを考慮して計算されます。align: その型がメモリ上で配置される際のアライメント要件を示します。例えば、8バイトアライメントの型は、アドレスが8の倍数であるメモリ位置に配置される必要があります。BADWIDTH:widthフィールドがまだ計算されていないか、無効な状態であることを示す特殊な値です。型情報の処理中に、この値が残っていると問題を引き起こす可能性があります。
dowidth関数
dowidth関数は、Goコンパイラの内部関数で、与えられたType構造体のwidthとalignフィールドを計算し、設定する役割を担います。この関数は、型の構造(例えば、構造体のフィールドや配列の要素)を再帰的にたどり、それぞれのサイズとアライメントを考慮して全体のサイズを決定します。
dgcsymとdgcsym1関数
dgcsymとdgcsym1は、src/cmd/gc/reflect.cに存在する関数で、GCがポインタをスキャンするために必要な型情報を生成するプロセスの一部です。これらの関数は、特定の型がGCによってどのように扱われるべきか(例えば、その型の中にポインタが含まれているか、含まれている場合、どこにポインタがあるかなど)を決定するために、型の構造を走査します。この走査の過程で、型のwidth情報が利用されます。
技術的詳細
この修正は、src/cmd/gc/reflect.cファイル内のdgcsym1関数とdgcsym関数に、型のwidthがBADWIDTHである場合にdowidth関数を呼び出してwidthを初期化するロジックを追加することで行われました。
dgcsym1関数における変更
dgcsym1関数は、特定のシンボル(型)のGC情報を生成する際に呼び出されます。この関数内で、型tのwidthがBADWIDTHである場合、dowidth(t)が呼び出され、tのwidthが計算されます。
さらに、TARRAY(配列型)のケースでは、配列の要素型(t->type)のwidthがBADWIDTHである場合にもdowidth(t->type)が呼び出されます。これは、配列全体のwidthを正確に計算するためには、その要素のwidthが事前に確定している必要があるためです。特に、ポインタのスライス(*[]T2のような型)がネストしている場合に、この要素型のwidthが未初期化のままGCに渡されると問題が発生していました。
dgcsym関数における変更
dgcsym関数は、GCシンボルテーブルに型を追加する際に呼び出される主要な関数です。ここでも同様に、型tのwidthがBADWIDTHである場合、dowidth(t)が呼び出され、tのwidthが確実に初期化されるようにします。
これらの変更により、GCが型情報を利用する前に、必要なwidth情報が常に利用可能であることが保証され、未初期化のwidthに起因するクラッシュが防止されます。これは、Goの型システムとGCの連携における堅牢性を高める重要な修正です。
コアとなるコードの変更箇所
src/cmd/gc/reflect.c
--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -1046,6 +1046,9 @@ dgcsym1(Sym *s, int ot, Type *t, vlong *off, int stack_size)\n \n \tif(t->align > 0 && (*off % t->align) != 0)\n \t\tfatal(\"dgcsym1: invalid initial alignment, %T\", t);\n+\n+\tif(t->width == BADWIDTH)\n+\t\tdowidth(t);\n \t\n \tswitch(t->etype) {\n \tcase TINT8:\
@@ -1141,6 +1144,8 @@ dgcsym1(Sym *s, int ot, Type *t, vlong *off, int stack_size)\n \tcase TARRAY:\n \t\tif(t->bound < -1)\n \t\t\tfatal(\"dgcsym1: invalid bound, %T\", t);\n+\t\tif(t->type->width == BADWIDTH)\n+\t\t\tdowidth(t->type);\n \t\tif(isslice(t)) {\n \t\t\t// NOTE: Any changes here need to be made to reflect.SliceOf as well.\n \t\t\t// struct { byte* array; uint32 len; uint32 cap; }\
@@ -1214,6 +1219,9 @@ dgcsym(Type *t)\n \t\treturn s;\n \ts->flags |= SymGcgen;\n \n+\tif(t->width == BADWIDTH)\n+\t\tdowidth(t);\n+\n \tot = 0;\n \toff = 0;\n \tot = duintptr(s, ot, t->width);\
新規追加されたテストファイル
test/fixedbugs/issue5291.dir/pkg1.gotest/fixedbugs/issue5291.dir/prog.gotest/fixedbugs/issue5291.go
コアとなるコードの解説
src/cmd/gc/reflect.c の変更点
-
dgcsym1関数内でのt->widthの初期化:if(t->width == BADWIDTH) dowidth(t);dgcsym1関数が型tを処理する際に、もしそのwidthがまだ計算されていない(BADWIDTHである)場合、dowidth(t)を呼び出してwidthを計算させます。これにより、GCが型tの情報を利用する前に、そのサイズが確定していることが保証されます。 -
dgcsym1関数内のTARRAY(配列型) 処理における要素型の初期化:case TARRAY: // ... if(t->type->width == BADWIDTH) dowidth(t->type);配列型(
TARRAY)を処理する際、その要素の型(t->type)のwidthがBADWIDTHである場合、dowidth(t->type)を呼び出して要素型のwidthを計算させます。配列全体のメモリレイアウトを正確に決定するためには、個々の要素のサイズが既知である必要があります。この修正は、特にポインタのスライス(例:*[]T2)のように、ネストされたポインタ型が絡む場合に重要です。 -
dgcsym関数内でのt->widthの初期化:if(t->width == BADWIDTH) dowidth(t);dgcsym関数は、GCシンボルを生成する主要なエントリポイントの一つです。ここでも同様に、処理対象の型tのwidthがBADWIDTHであれば、dowidth(t)を呼び出してwidthを初期化します。これにより、GCシンボルが登録される時点で、関連する型情報が完全であることが保証されます。
これらの変更は、Goコンパイラの型システムとGCの連携における潜在的な競合状態や未初期化アクセスを防ぐための防御的なプログラミングパターンを導入しています。GCがメモリをスキャンする際に、型の正確なサイズ情報が不可欠であるため、これらのチェックと初期化はクラッシュを防ぐ上で極めて重要です。
新規追加されたテストファイル
-
test/fixedbugs/issue5291.dir/pkg1.go: このファイルは、バグを再現するための特定の型定義と関数CrashCallを含んでいます。type T2 *[]stringとtype Data struct { T1 *[]T2 }というネストされたポインタとスライスの型を定義しています。CrashCall関数内では、Data型の変数を作成し、runtime.GC()を繰り返し呼び出し、make([]T2, len)でスライスを作成し、その要素に*[]string型の値を代入しています。この操作が、GCがData構造体のT1フィールド(*[]T2型)を処理する際に、T2のwidthが未初期化であるためにクラッシュを引き起こしていました。 -
test/fixedbugs/issue5291.dir/prog.go:pkg1パッケージをインポートし、pkg1.CrashCall()を呼び出すmain関数を持つシンプルなプログラムです。type message struct { data pkg1.Data }という構造体の存在がクラッシュを引き起こす、というコメントがあります。これは、pkg1.Data型がGCによって処理されるパスをトリガーし、バグを顕在化させるためのものです。 -
test/fixedbugs/issue5291.go: これはGoのテストフレームワークが使用するテストスクリプトで、// rundirディレクティブを含んでいます。これは、このテストが特定のディレクトリ(issue5291.dir)内で実行されるべきであることを示します。このファイル自体はGoのコードを含まず、テストランナーに対する指示を提供します。
これらのテストファイルは、Goコンパイラのバグ報告と修正プロセスにおいて非常に重要です。これらは、特定のコードパターンがどのようにバグをトリガーするかを正確に示し、修正が正しく機能することを確認するための回帰テストとして機能します。
関連リンク
- Go Issue 5291: https://github.com/golang/go/issues/5291 (このコミットが修正したバグの元のIssue)
- Go CL 8663052: https://golang.org/cl/8663052 (このコミットに対応するGoのコードレビューシステム上の変更リスト)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goコンパイラのソースコード (
src/cmd/gc/reflect.c) - Go Issue Tracker (Issue 5291の議論)
- Go Code Review (CL 8663052の議論)
- Go言語のガベージコレクションに関する一般的な情報源
- Go言語のリフレクションに関する一般的な情報源
- Go言語の型システムに関する一般的な情報源
- Go言語のコンパイラ設計に関する一般的な情報源
[インデックス 16230] ファイルの概要
このコミットは、Goコンパイラのガベージコレクション(GC)に関連するリフレクション処理におけるクラッシュバグを修正するものです。具体的には、型情報の幅(t->width)が適切に初期化されていない場合に発生する問題に対処しています。
コミット
commit 13cbf41a7f22f16354b06b159f087d8f64abfb37
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Thu Apr 25 18:47:12 2013 +0200
cmd/gc: initialize t->width in dgcsym() if required
Update #5291.
R=golang-dev, daniel.morsing, iant, r
CC=golang-dev
https://golang.org/cl/8663052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/13cbf41a7f22f16354b06b159f087d8f64abfb37
元コミット内容
cmd/gc: initialize t->width in dgcsym() if required
Update #5291.
変更の背景
このコミットは、Goコンパイラ(cmd/gc)におけるガベージコレクション(GC)関連のクラッシュバグ、具体的にはGo Issue 5291を修正するために導入されました。
Goのコンパイラは、プログラムの型情報を処理し、それに基づいてメモリレイアウトやGCの動作を決定します。このプロセスにおいて、各型のメモリ上のサイズ(幅、width)は非常に重要な情報です。reflect.cファイルは、GoのランタイムリフレクションとGCが型情報をどのように扱うかを定義する部分です。
Issue 5291は、特定の複雑な型(ポインタのポインタ、特にスライスへのポインタなど)がGCによって処理される際に、その型のwidthフィールドがBADWIDTH(未初期化または無効な状態を示す値)のままであるために発生するクラッシュでした。dgcsym関数やdgcsym1関数は、GCがシンボル(型)を走査し、そのポインタ情報を収集する際に使用されます。これらの関数がwidthが未初期化の型に遭遇すると、不正なメモリアクセスや計算が行われ、結果としてコンパイラやランタイムがクラッシュするという問題がありました。
このバグは、特にGCが実行されるタイミングで、特定の型の構造がリフレクションによって検査される際に顕在化しました。修正の目的は、GCが型情報を利用する前に、必要な型のwidthが確実に計算され、初期化されていることを保証することです。
前提知識の解説
Goコンパイラ (cmd/gc)
Go言語の公式コンパイラは、cmd/gcというディレクトリにその主要なコードベースがあります。これは、Goソースコードを機械語に変換する役割を担っています。コンパイルプロセスには、字句解析、構文解析、型チェック、中間コード生成、最適化、コード生成などが含まれます。
ガベージコレクション (GC)
Goは自動メモリ管理(ガベージコレクション)を採用しています。GCは、プログラムが動的に確保したメモリのうち、もはや到達不可能(参照されていない)になったものを自動的に解放する仕組みです。GoのGCは並行・低遅延が特徴で、プログラムの実行と並行して動作します。GCは、メモリ上のオブジェクトの型情報(特にポインタが含まれているか、そのサイズはどれくらいかなど)を利用して、どのメモリ領域をスキャンすべきかを判断します。
リフレクション (Reflection)
Goのリフレクションは、プログラムの実行中に型情報(reflect.Type)や値情報(reflect.Value)を検査・操作する機能です。これにより、コンパイル時には未知の型や構造を持つデータを動的に扱ったり、汎用的なデータ処理ロジックを記述したりすることが可能になります。コンパイラの内部では、リフレクションが利用する型情報を生成し、ランタイムがそれを参照できるようにしています。
Type構造体とwidth、align
Goコンパイラの内部では、Type構造体がGo言語の各型の情報を表現します。この構造体には、型の種類(etype)、要素の型(type、スライスや配列の場合)、配列の要素数(bound)、そしてメモリ上のサイズ(width)やアライメント(align)などの情報が含まれます。
width: その型がメモリ上で占めるバイト数を示します。例えば、int型は通常8バイト、bool型は1バイトなどです。構造体や配列の場合、そのメンバーのwidthとアライメントを考慮して計算されます。align: その型がメモリ上で配置される際のアライメント要件を示します。例えば、8バイトアライメントの型は、アドレスが8の倍数であるメモリ位置に配置される必要があります。BADWIDTH:widthフィールドがまだ計算されていないか、無効な状態であることを示す特殊な値です。型情報の処理中に、この値が残っていると問題を引き起こす可能性があります。
dowidth関数
dowidth関数は、Goコンパイラの内部関数で、与えられたType構造体のwidthとalignフィールドを計算し、設定する役割を担います。この関数は、型の構造(例えば、構造体のフィールドや配列の要素)を再帰的にたどり、それぞれのサイズとアライメントを考慮して全体のサイズを決定します。
dgcsymとdgcsym1関数
dgcsymとdgcsym1は、src/cmd/gc/reflect.cに存在する関数で、GCがポインタをスキャンするために必要な型情報を生成するプロセスの一部です。これらの関数は、特定の型がGCによってどのように扱われるべきか(例えば、その型の中にポインタが含まれているか、含まれている場合、どこにポインタがあるかなど)を決定するために、型の構造を走査します。この走査の過程で、型のwidth情報が利用されます。
技術的詳細
この修正は、src/cmd/gc/reflect.cファイル内のdgcsym1関数とdgcsym関数に、型のwidthがBADWIDTHである場合にdowidth関数を呼び出してwidthを初期化するロジックを追加することで行われました。
dgcsym1関数における変更
dgcsym1関数は、特定のシンボル(型)のGC情報を生成する際に呼び出されます。この関数内で、型tのwidthがBADWIDTHである場合、dowidth(t)が呼び出され、tのwidthが計算されます。
さらに、TARRAY(配列型)のケースでは、配列の要素型(t->type)のwidthがBADWIDTHである場合にもdowidth(t->type)が呼び出されます。これは、配列全体のwidthを正確に計算するためには、その要素のwidthが事前に確定している必要があるためです。特に、ポインタのスライス(*[]T2のような型)がネストしている場合に、この要素型のwidthが未初期化のままGCに渡されると問題が発生していました。
dgcsym関数における変更
dgcsym関数は、GCシンボルテーブルに型を追加する際に呼び出される主要な関数です。ここでも同様に、型tのwidthがBADWIDTHである場合、dowidth(t)が呼び出され、tのwidthが確実に初期化されるようにします。
これらの変更により、GCが型情報を利用する前に、必要なwidth情報が常に利用可能であることが保証され、未初期化のwidthに起因するクラッシュが防止されます。これは、Goの型システムとGCの連携における堅牢性を高める重要な修正です。
コアとなるコードの変更箇所
src/cmd/gc/reflect.c
--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -1046,6 +1046,9 @@ dgcsym1(Sym *s, int ot, Type *t, vlong *off, int stack_size)\n \n \tif(t->align > 0 && (*off % t->align) != 0)\n \t\tfatal(\"dgcsym1: invalid initial alignment, %T\", t);\n+\n+\tif(t->width == BADWIDTH)\n+\t\tdowidth(t);\n \t\n \tswitch(t->etype) {\n \tcase TINT8:\
@@ -1141,6 +1144,8 @@ dgcsym1(Sym *s, int ot, Type *t, vlong *off, int stack_size)\n \tcase TARRAY:\n \t\tif(t->bound < -1)\n \t\t\tfatal(\"dgcsym1: invalid bound, %T\", t);\n+\t\tif(t->type->width == BADWIDTH)\n+\t\t\tdowidth(t->type);\n \t\tif(isslice(t)) {\n \t\t\t// NOTE: Any changes here need to be made to reflect.SliceOf as well.\n \t\t\t// struct { byte* array; uint32 len; uint32 cap; }\
@@ -1214,6 +1219,9 @@ dgcsym(Type *t)\n \t\treturn s;\n \ts->flags |= SymGcgen;\n \n+\tif(t->width == BADWIDTH)\n+\t\tdowidth(t);\n+\n \tot = 0;\n \toff = 0;\n \tot = duintptr(s, ot, t->width);\
新規追加されたテストファイル
test/fixedbugs/issue5291.dir/pkg1.gotest/fixedbugs/issue5291.dir/prog.gotest/fixedbugs/issue5291.go
コアとなるコードの解説
src/cmd/gc/reflect.c の変更点
-
dgcsym1関数内でのt->widthの初期化:if(t->width == BADWIDTH) dowidth(t);dgcsym1関数が型tを処理する際に、もしそのwidthがまだ計算されていない(BADWIDTHである)場合、dowidth(t)を呼び出してwidthを計算させます。これにより、GCが型tの情報を利用する前に、そのサイズが確定していることが保証されます。 -
dgcsym1関数内のTARRAY(配列型) 処理における要素型の初期化:case TARRAY: // ... if(t->type->width == BADWIDTH) dowidth(t->type);配列型(
TARRAY)を処理する際、その要素の型(t->type)のwidthがBADWIDTHである場合、dowidth(t->type)を呼び出して要素型のwidthを計算させます。配列全体のメモリレイアウトを正確に決定するためには、個々の要素のサイズが既知である必要があります。この修正は、特にポインタのスライス(例:*[]T2)のように、ネストされたポインタ型が絡む場合に重要です。 -
dgcsym関数内でのt->widthの初期化:if(t->width == BADWIDTH) dowidth(t);dgcsym関数は、GCシンボルを生成する主要なエントリポイントの一つです。ここでも同様に、処理対象の型tのwidthがBADWIDTHであれば、dowidth(t)を呼び出してwidthを初期化します。これにより、GCシンボルが登録される時点で、関連する型情報が完全であることが保証されます。
これらの変更は、Goコンパイラの型システムとGCの連携における潜在的な競合状態や未初期化アクセスを防ぐための防御的なプログラミングパターンを導入しています。GCがメモリをスキャンする際に、型の正確なサイズ情報が不可欠であるため、これらのチェックと初期化はクラッシュを防ぐ上で極めて重要です。
新規追加されたテストファイル
-
test/fixedbugs/issue5291.dir/pkg1.go: このファイルは、バグを再現するための特定の型定義と関数CrashCallを含んでいます。type T2 *[]stringとtype Data struct { T1 *[]T2 }というネストされたポインタとスライスの型を定義しています。CrashCall関数内では、Data型の変数を作成し、runtime.GC()を繰り返し呼び出し、make([]T2, len)でスライスを作成し、その要素に*[]string型の値を代入しています。この操作が、GCがData構造体のT1フィールド(*[]T2型)を処理する際に、T2のwidthが未初期化であるためにクラッシュを引き起こしていました。 -
test/fixedbugs/issue5291.dir/prog.go:pkg1パッケージをインポートし、pkg1.CrashCall()を呼び出すmain関数を持つシンプルなプログラムです。type message struct { data pkg1.Data }という構造体の存在がクラッシュを引き起こす、というコメントがあります。これは、pkg1.Data型がGCによって処理されるパスをトリガーし、バグを顕在化させるためのものです。 -
test/fixedbugs/issue5291.go: これはGoのテストフレームワークが使用するテストスクリプトで、// rundirディレクティブを含んでいます。これは、このテストが特定のディレクトリ(issue5291.dir)内で実行されるべきであることを示します。このファイル自体はGoのコードを含まず、テストランナーに対する指示を提供します。
これらのテストファイルは、Goコンパイラのバグ報告と修正プロセスにおいて非常に重要です。これらは、特定のコードパターンがどのようにバグをトリガーするかを正確に示し、修正が正しく機能することを確認するための回帰テストとして機能します。
関連リンク
- Go Issue 5291: https://github.com/golang/go/issues/5291 (このコミットが修正したバグの元のIssue)
- Go CL 8663052: https://golang.org/cl/8663052 (このコミットに対応するGoのコードレビューシステム上の変更リスト)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goコンパイラのソースコード (
src/cmd/gc/reflect.c) - Go Issue Tracker (Issue 5291の議論)
- Go Code Review (CL 8663052の議論)
- Go言語のガベージコレクションに関する一般的な情報源
- Go言語のリフレクションに関する一般的な情報源
- Go言語の型システムに関する一般的な情報源
- Go言語のコンパイラ設計に関する一般的な情報源