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

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

このコミットは、Goコンパイラのcmd/gcにおける型チェック処理中のnilポインタデリファレンス(nullポインタ参照外し)のバグを修正するものです。具体的には、typecheck.cファイル内の型チェック関数において、Nodetypeフィールドが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->etypeTFORWである場合に、将来のエラーを抑制するために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->typeT(nil)であれば、この条件は偽となり、ifブロック内のコードは実行されません。
  • n->type->etype == TFORW: これは元の条件であり、ノードの型が前方宣言型(TFORW)であるかどうかをチェックします。
  • nerrors > nerrors0: これも元の条件であり、typecheckdeftypeの呼び出し中に新たな型チェックエラーが報告されたかどうかをチェックします。

この修正により、n->typeがnilであるにもかかわらずTFORW型として扱われようとする状況が回避され、nilポインタデリファレンスによるコンパイラのクラッシュが防止されます。これは、コンパイラの堅牢性を高めるための重要な防御的プログラミングの例です。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(src/cmd/gc/typecheck.c
  • Go言語のコンパイラ設計に関する一般的な知識
  • nilポインタデリファレンスに関する一般的なプログラミング知識
  • Go言語のコードレビューシステム (Gerrit) のCL (Change List) ページ