[インデックス 14473] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)のソースコードにおいて、メモリ割り当て関数malloc
の戻り値をチェックする処理を追加するものです。具体的には、malloc
がメモリの割り当てに失敗した場合(nil
を返す場合)に、適切なエラーハンドリングを行うことで、システムの安定性と堅牢性を向上させています。
コミット
cmd/gc: check malloc return value
Check the return value from malloc - do not assume that we were
allocated memory just because we asked for it.
Update #4415.
R=minux.ma, daniel.morsing, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/6782100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5da5e8e02f6ac18d36934a57ec02c069eda9f63f
元コミット内容
cmd/gc: check malloc return value
malloc
の戻り値をチェックする。メモリを要求したからといって、それが割り当てられたと仮定してはならない。
Issue #4415を更新。
変更の背景
プログラムが動的にメモリを要求する際、malloc
のような関数を使用します。しかし、システムに十分なメモリがない場合や、他の要因によってmalloc
がメモリを割り当てられないことがあります。このような場合、malloc
は通常、NULLポインタ(C言語ではnil
)を返します。
このコミット以前のGoコンパイラのコードでは、malloc
の戻り値が適切にチェックされていませんでした。これは、malloc
が常に成功すると仮定していることを意味します。もしmalloc
が失敗し、その戻り値(nil
)がそのまま使用されると、ヌルポインタ参照(dereferencing a null pointer)が発生し、プログラムがクラッシュしたり、未定義の動作を引き起こしたりする可能性があります。
この変更は、このような潜在的なクラッシュや不安定性を防ぎ、Goコンパイラの堅牢性を高めるために行われました。Issue #4415は、このメモリ割り当て失敗時の挙動に関する問題点を追跡していたものと考えられます。
前提知識の解説
malloc
関数
malloc
はC標準ライブラリ(stdlib.h
)に含まれる関数で、指定されたサイズのメモリブロックをヒープから動的に割り当てます。成功すると、割り当てられたメモリブロックの先頭へのポインタ(void*
型)を返します。失敗すると、NULLポインタを返します。
nil
(C言語におけるNULL)
Go言語ではnil
が多くの型におけるゼロ値や「何もない」状態を表しますが、このコミットが対象としているsrc/cmd/gc
はC言語で書かれたGoコンパイラの初期部分です。C言語では、ポインタが何も指していない状態を示すためにNULL
マクロが使用されます。nil
という表記は、Go言語の文脈でC言語のNULL
を指しているか、あるいはGo言語のコードベース全体での慣習としてNULL
をnil
と表現している可能性があります。本質的には、メモリ割り当てが失敗したことを示す「無効なポインタ」を意味します。
メモリ割り当ての失敗とエラーハンドリング
オペレーティングシステムは、プロセスが利用できるメモリ量に制限を設けています。プログラムがこの制限を超えてメモリを要求したり、システム全体のメモリが枯渇したりすると、malloc
のようなメモリ割り当て関数は失敗します。
堅牢なプログラムでは、このようなメモリ割り当ての失敗を適切に処理する必要があります。一般的なC言語の慣習では、malloc
の戻り値がNULL
であるかどうかをチェックし、NULL
であればエラーメッセージを表示してプログラムを終了するか、代替の処理を行うなどのエラーハンドリングロジックを実装します。
Goコンパイラ (cmd/gc
)
Go言語のコンパイラは、Goのソースコードを機械語に変換するプログラムです。cmd/gc
は、Go言語の公式コンパイラの主要部分を指します。コンパイラは、ソースコードの解析、抽象構文木の構築、型チェック、最適化、コード生成など、多くの複雑な処理を行います。これらの処理の過程で、シンボルテーブル、構文木ノード、中間表現など、様々なデータ構造を動的に生成・管理するため、大量のメモリを必要とします。したがって、コンパイラ内部でのメモリ割り当ての失敗は、コンパイルプロセスの途中で致命的なエラーにつながる可能性があります。
技術的詳細
このコミットの技術的詳細は、GoコンパイラのC言語で書かれた部分におけるメモリ割り当ての安全性向上にあります。
Goコンパイラは、その初期段階でC言語で実装されており、malloc
関数を直接使用して動的にメモリを確保しています。このコミットは、src/cmd/gc/lex.c
(字句解析器の一部)とsrc/cmd/gc/sinit.c
(初期化処理の一部)の2つのファイルに修正を加えています。
修正前は、これらのファイル内のmalloc
呼び出しの後、戻り値のチェックが行われていませんでした。これは、malloc
が常に成功し、有効なメモリポインタを返すと暗黙的に仮定していたことを意味します。しかし、これは危険な仮定です。システムメモリが不足している場合、malloc
はnil
(C言語のNULL
)を返します。このnil
ポインタを後続のコードで参照しようとすると、セグメンテーション違反(segmentation fault)などの実行時エラーが発生し、コンパイラがクラッシュします。
このコミットでは、malloc
呼び出しの直後に以下のエラーハンドリングロジックを追加しています。
if(h == nil)
またはif(l == nil)
:malloc
が返したポインタがnil
であるかどうかをチェックします。h
やl
は、malloc
によって割り当てられたメモリへのポインタを格納する変数です。flusherrors()
: これは、Goコンパイラ内部のエラーバッファをフラッシュする関数であると推測されます。これにより、これまでに発生したエラーメッセージが確実に表示されるようになります。yyerror("out of memory")
: これは、Goコンパイラがユーザーに対して「メモリ不足」のエラーメッセージを出力するための関数であると推測されます。yyerror
は、yacc
やbison
などのパーサジェネレータによって生成されるパーサでよく見られるエラー報告関数です。errorexit()
: これは、Goコンパイラがエラーを検出した際に、適切に終了するための関数であると推測されます。通常、エラーコードを返してプログラムを終了します。
この変更により、Goコンパイラはメモリ不足の状況に遭遇した場合でも、クラッシュする代わりに、ユーザーに対して明確なエラーメッセージを報告し、制御された形で終了できるようになります。これは、コンパイラの信頼性とユーザーエクスペリエンスを大幅に向上させます。
コアとなるコードの変更箇所
src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1165,6 +1165,11 @@ l0:
case '[':
if(loophack || lstk != nil) {
h = malloc(sizeof *h);
+ if(h == nil) {
+ flusherrors();
+ yyerror("out of memory");
+ errorexit();
+ }
h->v = loophack;
h->next = lstk;
lstk = h;
src/cmd/gc/sinit.c
--- a/src/cmd/gc/sinit.c
+++ b/src/cmd/gc/sinit.c
@@ -84,6 +84,11 @@ init1(Node *n, NodeList **out)
}
n->initorder = InitPending;
l = malloc(sizeof *l);
+ if(l == nil) {
+ flusherrors();
+ yyerror("out of memory");
+ errorexit();
+ }
l->next = initlist;
l->n = n;
l->end = nil;
コアとなるコードの解説
両方のファイルで、malloc
関数を呼び出した直後に、割り当てられたポインタがnil
(C言語のNULL
に相当)であるかどうかを確認するif
文が追加されています。
src/cmd/gc/lex.c
では、h = malloc(sizeof *h);
の後に、if(h == nil)
のチェックが追加されています。src/cmd/gc/sinit.c
では、l = malloc(sizeof *l);
の後に、if(l == nil)
のチェックが追加されています。
このif
ブロックの中では、以下の3つの関数が呼び出されています。
flusherrors()
: これまでに蓄積されたエラーメッセージをフラッシュ(出力)します。これにより、メモリ不足エラーが発生する前に発生した可能性のある他のエラーもユーザーに通知されます。yyerror("out of memory")
: コンパイラがユーザーに対して「out of memory」(メモリ不足)というエラーメッセージを表示します。これは、問題の原因を明確に伝えるための重要なステップです。errorexit()
: プログラムを終了します。これにより、メモリ不足という回復不能な状態での未定義の動作を防ぎ、コンパイラが安全にシャットダウンされます。
これらの変更は、Goコンパイラがメモリ割り当ての失敗という例外的な状況に遭遇した場合でも、予測可能で安全な方法で応答できるようにするための、基本的ながらも非常に重要な堅牢性向上策です。
関連リンク
- Go CL 6782100: https://golang.org/cl/6782100
- Go Issue 4415: このコミットが参照しているIssueの詳細は、GoのIssueトラッカーで検索することで見つかる可能性があります。
参考にした情報源リンク
- C言語
malloc
関数: https://man7.org/linux/man-pages/man3/malloc.3.html - C言語
NULL
ポインタ: 一般的なC言語の教科書やオンラインリソース - Goコンパイラの構造に関する情報: Goの公式ドキュメントやGoコンパイラのソースコード自体。