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

[インデックス 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言語のコードベース全体での慣習としてNULLnilと表現している可能性があります。本質的には、メモリ割り当てが失敗したことを示す「無効なポインタ」を意味します。

メモリ割り当ての失敗とエラーハンドリング

オペレーティングシステムは、プロセスが利用できるメモリ量に制限を設けています。プログラムがこの制限を超えてメモリを要求したり、システム全体のメモリが枯渇したりすると、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が常に成功し、有効なメモリポインタを返すと暗黙的に仮定していたことを意味します。しかし、これは危険な仮定です。システムメモリが不足している場合、mallocnil(C言語のNULL)を返します。このnilポインタを後続のコードで参照しようとすると、セグメンテーション違反(segmentation fault)などの実行時エラーが発生し、コンパイラがクラッシュします。

このコミットでは、malloc呼び出しの直後に以下のエラーハンドリングロジックを追加しています。

  1. if(h == nil) または if(l == nil): mallocが返したポインタがnilであるかどうかをチェックします。hlは、mallocによって割り当てられたメモリへのポインタを格納する変数です。
  2. flusherrors(): これは、Goコンパイラ内部のエラーバッファをフラッシュする関数であると推測されます。これにより、これまでに発生したエラーメッセージが確実に表示されるようになります。
  3. yyerror("out of memory"): これは、Goコンパイラがユーザーに対して「メモリ不足」のエラーメッセージを出力するための関数であると推測されます。yyerrorは、yaccbisonなどのパーサジェネレータによって生成されるパーサでよく見られるエラー報告関数です。
  4. 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つの関数が呼び出されています。

  1. flusherrors(): これまでに蓄積されたエラーメッセージをフラッシュ(出力)します。これにより、メモリ不足エラーが発生する前に発生した可能性のある他のエラーもユーザーに通知されます。
  2. yyerror("out of memory"): コンパイラがユーザーに対して「out of memory」(メモリ不足)というエラーメッセージを表示します。これは、問題の原因を明確に伝えるための重要なステップです。
  3. 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コンパイラのソースコード自体。