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

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

このコミットは、Go言語のツールチェーンの一部である cmd/cc における変更です。具体的には、src/cmd/cc/dcl.c ファイルが修正されており、C言語の union 型がポインタを含む場合に、Goのガベージコレクタが正しく動作しない問題に対処しています。

コミット

commit dbee8ad0f9eff310e1d3c696b334e9a596cab42b
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Wed May 22 21:13:30 2013 +0200

    cmd/cc: reject unions containing pointers
    
    If a union contains a pointer, it will mess up the garbage collector, causing memory corruption.
    
    R=golang-dev, dave, nightlyone, adg, dvyukov, bradfitz, minux.ma, r, iant
    CC=golang-dev
    https://golang.org/cl/8469043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/dbee8ad0f9eff310e1d3c696b334e9a596cab42b

元コミット内容

cmd/cc: reject unions containing pointers

If a union contains a pointer, it will mess up the garbage collector, causing memory corruption.

変更の背景

この変更の背景には、Go言語のランタイムが採用している「正確なガベージコレクタ(Precise Garbage Collector)」の特性と、C言語の union 型のメモリレイアウトの不整合があります。

Go言語のガベージコレクタは、ヒープ上のオブジェクトがまだ参照されているかどうかを正確に判断するために、メモリ内のポインタを識別する必要があります。これにより、到達可能なオブジェクトのみが保持され、到達不能なオブジェクトは解放されます。

一方、C言語の union 型は、異なる型のメンバが同じメモリ領域を共有するデータ構造です。union のサイズは、そのメンバの中で最も大きい型のサイズによって決まります。問題は、union のどのメンバが現在アクティブであるかをコンパイラやランタイムが常に正確に把握できるわけではない点にあります。

もし union がポインタ型と非ポインタ型(例えば整数型)の両方を含む場合、ガベージコレクタがその union のメモリ領域をスキャンする際に、それがポインタとして解釈されるべきか、それとも単なるデータとして解釈されるべきかを判断できません。もし非ポインタ型のデータが格納されているにもかかわらず、ガベージコレクタがそれをポインタとして誤って解釈し、その「ポインタ」が不正なメモリアドレスを指していた場合、ガベージコレクタは不正なメモリ領域にアクセスしようとし、結果としてメモリ破壊(memory corruption)やクラッシュを引き起こす可能性があります。

このコミットは、cmd/cc(Goのツールチェーンで使用されるCコンパイラ)が、ポインタを含む union 型を検出した場合に、コンパイル時に警告またはエラーを出すことで、このような潜在的なメモリ破壊の問題を未然に防ぐことを目的としています。

前提知識の解説

Go言語のガベージコレクタ (GC)

Go言語は自動メモリ管理を採用しており、ガベージコレクタが不要になったメモリを自動的に解放します。GoのGCは「並行(concurrent)」かつ「正確(precise)」なGCです。

  • 並行GC: アプリケーションの実行と並行してGCが動作するため、アプリケーションの一時停止(Stop-the-World)時間を最小限に抑えます。
  • 正確GC: メモリ内のどの値がポインタであり、どの値がポインタでないかを正確に識別できます。これにより、誤ってデータがポインタとして扱われたり、その逆の事態が起きたりするのを防ぎ、メモリリークやメモリ破壊のリスクを低減します。正確なGCは、ポインタの追跡によって到達可能なオブジェクトを特定し、それ以外のオブジェクトを解放します。

C言語の union

union はC言語の複合データ型の一つで、複数のメンバが同じメモリ位置を共有します。一度にアクティブになるメンバは一つだけであり、union のサイズは最も大きいメンバのサイズに等しくなります。

union Data {
    int i;
    float f;
    char str[20];
    void *ptr; // ポインタを含む場合
};

上記の例では、i, f, str, ptr のいずれか一つだけが同時に有効です。メモリは str のサイズ(20バイト)に合わせて確保されます。

ポインタ

ポインタは、メモリ上の特定のアドレスを指し示す変数です。プログラミングにおいて、データ構造の連結、動的メモリ割り当て、関数への参照渡しなどに広く利用されます。ガベージコレクタは、これらのポインタを追跡して、どのメモリ領域がまだ使用中であるかを判断します。

メモリ破壊 (Memory Corruption)

メモリ破壊とは、プログラムが意図しないメモリ領域に書き込みを行ったり、解放済みのメモリにアクセスしたりすることで、メモリの内容が不正な状態になる現象です。これは、プログラムのクラッシュ、予期せぬ動作、セキュリティ脆弱性など、深刻な問題を引き起こす可能性があります。ガベージコレクタがポインタを誤認識すると、メモリ破壊につながる可能性があります。

cmd/cc

cmd/cc は、Go言語のツールチェーンの一部として使用されるCコンパイラです。Goのランタイムや標準ライブラリの一部はC言語で書かれており、これらをコンパイルするために使用されます。Goのビルドプロセスにおいて、Cgo(GoとCの相互運用機能)を使用する際にも関連します。

技術的詳細

このコミットが対処している技術的な問題は、Goの正確なガベージコレクタが、C言語の union 型の内部にポインタが含まれている場合に、そのポインタを正確に識別できないという点にあります。

GoのGCは、ヒープ上のオブジェクトをスキャンする際に、そのオブジェクトの型情報に基づいて、どのオフセットにポインタが存在するかを把握しています。しかし、Cの union は、その性質上、同じメモリ領域が異なる型として解釈される可能性があるため、GoのGCが union のメモリ領域をスキャンする際に、現在アクティブなメンバがポインタであるか、それとも単なるデータであるかを判断するための信頼できる型情報がありません。

例えば、unionint*void を含む場合を考えます。

union Mixed {
    int value;
    void *pointer;
};

もし Mixed 型の変数に int が格納されているときに、GoのGCがそのメモリ領域をスキャンし、たまたま int のビットパターンが有効なメモリアドレスのように見えた場合、GCはそれをポインタとして誤認識し、そのアドレスを追跡しようとします。これが不正なアドレスであれば、GCはクラッシュするか、無関係なメモリを解放してしまい、メモリ破壊を引き起こす可能性があります。

このコミットでは、cmd/cc がコンパイル時に union の定義を解析し、その union のメンバの中にポインタ型(またはポインタを含む構造体や配列)が含まれているかどうかをチェックする新しいロジックを導入しています。もしポインタを含む union が検出された場合、コンパイラは警告を発することで、開発者に潜在的な問題があることを知らせます。これにより、開発者は union の設計を見直すか、GoのGCが安全に扱えるような代替手段(例えば、ポインタと非ポインタを別々のフィールドに持つ構造体を使用するなど)を検討することができます。

このアプローチは、コンパイル時に問題を特定することで、実行時の未定義動作やデバッグが困難なメモリ破壊を防ぐための予防策となります。

コアとなるコードの変更箇所

変更は src/cmd/cc/dcl.c ファイルに集中しています。

  1. haspointers という新しい静的関数が追加されました。
  2. sualign 関数内で、union の要素がポインタを含む場合に警告を出すための呼び出しが追加されました。
--- a/src/cmd/cc/dcl.c
+++ b/src/cmd/cc/dcl.c
@@ -554,6 +554,28 @@ newlist(Node *l, Node *r)
 	return new(OLIST, l, r);
 }
 
+static int
+haspointers(Type *t)
+{
+	Type *fld;
+
+	switch(t->etype) {
+	case TSTRUCT:
+		for(fld = t->link; fld != T; fld = fld->down) {
+			if(haspointers(fld))
+				return 1;
+		}
+		return 0;
+	case TARRAY:
+		return haspointers(t->link);
+	case TFUNC:
+	case TIND:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
 void
 sualign(Type *t)
 {
@@ -608,6 +630,9 @@ sualign(Type *t)
 				diag(Z, "incomplete union element");
 			l->offset = 0;
 			l->shift = 0;
+			if((debug['q'] || debug['Q']) && haspointers(l))
+				diag(Z, "precise garbage collector cannot handle unions with pointers");
+
 			o = align(align(0, l, Ael1, &maxal), l, Ael2, &maxal);
 			if(o > w)
 				w = o;

コアとなるコードの解説

haspointers 関数

この関数は、与えられた型 t がポインタを含むかどうかを再帰的にチェックします。

  • switch(t->etype): 型の基本要素タイプ(etype)に基づいて処理を分岐します。
    • case TSTRUCT:: 型が構造体の場合、その構造体の各フィールド(fld = t->link から fld = fld->down で辿る)を再帰的に haspointers でチェックします。いずれかのフィールドがポインタを含んでいれば 1(true)を返します。
    • case TARRAY:: 型が配列の場合、その配列の要素型(t->link)を haspointers でチェックします。配列の要素がポインタを含んでいれば 1 を返します。
    • case TFUNC:: 型が関数型の場合、これは関数ポインタを意味するため、1 を返します。
    • case TIND:: 型が間接参照型(ポインタ型)の場合、1 を返します。
    • default:: 上記以外の型(整数、浮動小数点数など)の場合はポインタを含まないため、0(false)を返します。

この関数は、複合型(構造体や配列)の深部まで探索し、隠れたポインタの存在を検出する役割を担っています。

sualign 関数内の変更

sualign 関数は、構造体や共用体(union)のメンバのアライメントとオフセットを計算する役割を持つ関数です。この関数内で、union の要素を処理する部分に以下の行が追加されました。

if((debug['q'] || debug['Q']) && haspointers(l))
    diag(Z, "precise garbage collector cannot handle unions with pointers");
  • debug['q'] || debug['Q']: これはデバッグフラグのチェックです。q または Q フラグが有効な場合にのみ、このチェックが実行されます。これは、通常は警告を出さないが、デバッグ時や特定のビルド設定で詳細なチェックを行うためのものです。
  • haspointers(l): 現在処理している union の要素 l がポインタを含んでいるかどうかを haspointers 関数でチェックします。
  • diag(Z, "precise garbage collector cannot handle unions with pointers");: もし union の要素がポインタを含んでおり、デバッグフラグが有効な場合、この diag 関数がコンパイル時に警告メッセージを出力します。メッセージは「正確なガベージコレクタはポインタを含む共用体を扱えません」という内容です。

この変更により、cmd/cc はポインタを含む union を検出した際に、開発者にその事実を通知し、潜在的なメモリ安全性の問題に対する注意を促すことができるようになりました。

関連リンク

  • Go言語のガベージコレクタに関する公式ドキュメントやブログ記事 (当時の情報源を探すのは困難ですが、GoのGCの進化に関する一般的な記事が参考になります)
  • C言語の union 型に関するC標準のドキュメント
  • Go言語の cmd/cc のソースコード(Goリポジトリ内)

参考にした情報源リンク

  • Go言語のガベージコレクタの仕組みに関する一般的な情報 (例: "Go's Garbage Collector: A Comprehensive Guide")
  • C言語の union 型のメモリレイアウトに関する情報
  • Go言語の公式リポジトリのコミット履歴と関連するコードレビュー (https://golang.org/cl/8469043)I have provided the comprehensive technical explanation in Markdown format, following all the specified sections and instructions. The output is directed to standard output only, as requested.

If you have any further questions or need more assistance, feel free to ask!