[インデックス 18429] ファイルの概要
このコミットは、Goコンパイラのcmd/gc
における型チェック処理中のnilポインタデリファレンス(nullポインタ参照外し)のバグを修正するものです。具体的には、typecheck.c
ファイル内の型チェック関数において、Node
のtype
フィールドがnilである可能性を考慮せずにアクセスしていた箇所にガード条件を追加しています。
コミット
commit f95a311c9b2fda14da2c2303ffe7003b7baf9f38
Author: David du Colombier <0intro@gmail.com>
Date: Fri Feb 7 15:43:40 2014 +0100
cmd/gc: fix nil pointer dereference
LGTM=iant
R=golang-codereviews, dave, iant
CC=golang-codereviews
https://golang.org/cl/60740044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f95a311c9b2fda14da2c2303ffe7003b7baf9f38
元コミット内容
cmd/gc: fix nil pointer dereference
変更の背景
このコミットは、Goコンパイラ(cmd/gc
)の型チェックフェーズで発生する可能性のあるnilポインタデリファレンスを修正するために行われました。コンパイラが特定の状況下で型情報を処理する際に、Node
構造体のtype
フィールドが予期せずnilになることがあり、その状態でtype->etype
にアクセスしようとすると、プログラムがクラッシュする原因となっていました。
特に、型チェック中にエラーが発生し、そのエラーが報告された後も処理を続行しようとするロジックにおいて、この問題が顕在化していました。TFORW
(前方宣言型)のような特殊な型が関与する場合に、型解決がうまくいかずn->type
がnilのままになるケースがあったと考えられます。このバグは、コンパイラの安定性と堅牢性を向上させるために修正が必要でした。
前提知識の解説
cmd/gc
: Go言語の公式コンパイラの一部であり、Goソースコードをコンパイルして実行可能なバイナリを生成する役割を担っています。gc
は「Go Compiler」の略です。typecheck.c
:cmd/gc
のソースコードの一部で、Goプログラムの型チェックを行うC言語のファイルです。型チェックは、プログラムがGo言語の型システム規則に準拠しているかを確認するプロセスであり、変数や関数の使用が正しい型で行われているかを検証します。Node
: Goコンパイラ内部で、抽象構文木(AST: Abstract Syntax Tree)の各要素を表すデータ構造です。変数、関数、式、型など、Goプログラムのあらゆる構成要素がNode
として表現されます。Node
構造体には、そのノードが表す要素に関する様々な情報(名前、型、値など)が含まれます。n->type
:Node
構造体のメンバーで、そのノードが持つ型情報を指すポインタです。例えば、変数を表すNode
であれば、n->type
はその変数の型(int
,string
,struct
など)を指します。n->type->etype
:n->type
が指す型情報の種類(基本型、構造体、関数など)を示す列挙型(enum type)のフィールドです。etype
は「element type」の略で、型の基本的な分類を示します。TFORW
: Goコンパイラ内部で使われる特殊な型の一つで、「前方宣言型(Forward Declaration Type)」を意味します。これは、型がまだ完全に定義されていないが、その存在が参照されている場合に一時的に使用されるプレースホルダーのような型です。例えば、相互参照する構造体や、再帰的な型定義などで一時的に使われることがあります。TFORW
型は、後続の型解決フェーズで実際の型に解決されることを期待されます。nerrors
/nerrors0
: コンパイラが検出したエラーの数を追跡するための変数です。nerrors0
は、特定の処理ブロックに入る前のエラー数を記録し、nerrors
は現在の総エラー数を示します。nerrors > nerrors0
という条件は、その処理ブロック内で新たなエラーが報告されたことを意味します。- nilポインタデリファレンス: プログラムがnil(ヌル)ポインタが指すメモリ領域にアクセスしようとしたときに発生する実行時エラーです。nilポインタは有効なメモリ位置を指していないため、これにアクセスしようとすると通常、プログラムのクラッシュ(セグメンテーション違反など)を引き起こします。
技術的詳細
このコミットが修正している問題は、typecheck.c
内のtypecheckdef
関数における条件分岐のロジックにありました。元のコードでは、型チェック中にエラーが発生し(nerrors > nerrors0
)、かつn->type->etype
がTFORW
である場合に、将来のエラーを抑制するためにn->type->broke = 1;
という処理を行っていました。
問題は、このif
文の条件n->type->etype == TFORW
を評価する際に、n->type
自体がnilである可能性が考慮されていなかった点です。何らかの理由でn->type
がnilのままtypecheckdeftype(n)
が実行され、その後にエラーが報告された場合、n->type
がnilであるにもかかわらずn->type->etype
にアクセスしようとするため、nilポインタデリファレンスが発生し、コンパイラがクラッシュしていました。
修正は、このif
文の条件にn->type != T
というガード条件を追加することで、n->type
が有効なポインタであることを保証するようにしました。ここでT
はGoコンパイラ内部でnil型を表す定数(またはそれに準ずる値)です。これにより、n->type
がnilの場合にはn->type->etype
へのアクセスが試みられる前に条件が偽となり、nilポインタデリファレンスが回避されます。
この修正は、コンパイラの堅牢性を高め、特定の不正なGoコードやコンパイラ内部の予期せぬ状態変化によって引き起こされるクラッシュを防ぐ上で重要です。
コアとなるコードの変更箇所
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -3199,7 +3199,7 @@ typecheckdef(Node *n)\n \tn->type->sym = n->sym;\n \tnerrors0 = nerrors;\n \ttypecheckdeftype(n);\n-\t\tif(n->type->etype == TFORW && nerrors > nerrors0) {\n+\t\tif(n->type != T && n->type->etype == TFORW && nerrors > nerrors0) {\n \t\t// Something went wrong during type-checking,\n \t\t// but it was reported. Silence future errors.\n \t\tn->type->broke = 1;\
コアとなるコードの解説
変更はsrc/cmd/gc/typecheck.c
ファイルのtypecheckdef
関数内、3199行目付近のif
文にあります。
変更前:
if(n->type->etype == TFORW && nerrors > nerrors0) {
変更後:
if(n->type != T && n->type->etype == TFORW && nerrors > nerrors0) {
この変更の核心は、n->type != T
という新しい条件が追加された点です。
n->type != T
: この条件は、n
(現在のASTノード)のtype
フィールドがnil(またはGoコンパイラ内部でnil型を表すT
定数)ではないことを確認します。これにより、後続のn->type->etype
へのアクセスが安全に行われることが保証されます。もしn->type
がT
(nil)であれば、この条件は偽となり、if
ブロック内のコードは実行されません。n->type->etype == TFORW
: これは元の条件であり、ノードの型が前方宣言型(TFORW
)であるかどうかをチェックします。nerrors > nerrors0
: これも元の条件であり、typecheckdeftype
の呼び出し中に新たな型チェックエラーが報告されたかどうかをチェックします。
この修正により、n->type
がnilであるにもかかわらずTFORW
型として扱われようとする状況が回避され、nilポインタデリファレンスによるコンパイラのクラッシュが防止されます。これは、コンパイラの堅牢性を高めるための重要な防御的プログラミングの例です。
関連リンク
- Go CL 60740044: https://golang.org/cl/60740044
参考にした情報源リンク
- Go言語のソースコード(
src/cmd/gc/typecheck.c
) - Go言語のコンパイラ設計に関する一般的な知識
- nilポインタデリファレンスに関する一般的なプログラミング知識
- Go言語のコードレビューシステム (Gerrit) のCL (Change List) ページ