[インデックス 18379] ファイルの概要
このコミットは、Goコンパイラの一部である cmd/8g
(x86-64アーキテクチャ向けのアセンブラ) において、Prog
構造体の u.branch
フィールドが nil
である場合に発生するクラッシュを防ぐための修正です。特に、アセンブリコードをダンプする -S
オプションを使用した際にクラッシュが発生していました。
コミット
commit 289d46399b794a15be12f57a8162fa514bd8f306
Author: Rob Pike <r@golang.org>
Date: Wed Jan 29 16:14:45 2014 -0800
cmd/8g: don't crash if Prog->u.branch is nil
The code is copied from cmd/6g.
Empirically, all branch targets are nil in this code so
something is still wrong, but at least this stops 8g -S
from crashing.
Update #7178
LGTM=dave, iant
R=iant, dave
CC=golang-codereviews
https://golang.org/cl/58400043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/289d46399b794a15be12f57a8162fa514bd8f306
元コミット内容
cmd/8g
: Prog->u.branch
が nil
の場合にクラッシュしないようにする。
このコードは cmd/6g
からコピーされたものです。
経験的に、このコードではすべての分岐ターゲットが nil
であるため、何らかの問題がまだ残っていますが、少なくともこれにより 8g -S
がクラッシュするのを防ぎます。
Issue #7178 を更新。
変更の背景
このコミットの背景には、Goコンパイラの cmd/8g
ツールが特定の条件下でクラッシュするというバグが存在していました。具体的には、8g
コマンドに -S
フラグ(生成されたアセンブリコードを表示するためのフラグ)を付けて実行すると、プログラムが異常終了していました。
クラッシュの原因は、Prog
構造体(Goコンパイラが中間表現として使用する命令を表す構造体)の u.branch
フィールドが nil
であるにもかかわらず、そのフィールドが参照される可能性があったためです。u.branch
は通常、分岐命令のターゲットとなる Prog
へのポインタを保持しますが、何らかの理由で nil
になっている場合に、nil
ポインタデリファレンスが発生し、プログラムがクラッシュしていました。
コミットメッセージには「経験的に、このコードではすべての分岐ターゲットが nil
であるため、何らかの問題がまだ残っています」とあり、これは u.branch
が nil
になる根本原因は解決されていないものの、少なくともクラッシュを防ぐための緊急的な修正であることを示唆しています。この修正は、同様の問題を抱えていた cmd/6g
(x86-32アーキテクチャ向けのアセンブラ) のコードから移植されたものです。
前提知識の解説
- Goコンパイラ (
cmd/
): Go言語のソースコードを機械語に変換するツール群です。cmd/
ディレクトリ以下に、各アーキテクチャ向けのアセンブラやリンカなどが含まれています。cmd/8g
: x86-64 (64ビットIntel/AMD) アーキテクチャ向けのGoアセンブラです。Goのソースコードをコンパイルする際に、最終的な機械語を生成する過程で利用されます。cmd/6g
: x86-32 (32ビットIntel/AMD) アーキテクチャ向けのGoアセンブラです。
Prog
構造体: Goコンパイラの内部で、アセンブリ命令や中間表現の命令を表すために使用されるデータ構造です。各Prog
オブジェクトは、オペコード、オペランド、そして次の命令へのポインタなど、命令に関する情報を保持します。u.branch
フィールド:Prog
構造体内のフィールドの一つで、主に分岐命令(JMP
,CALL
など)のターゲットとなる別のProg
オブジェクトへのポインタを保持します。u
はユニオン(共用体)のようなもので、命令の種類によって異なるデータが格納されることを示唆しています。nil
: Go言語におけるゼロ値の一つで、ポインタ、スライス、マップ、チャネル、関数、インターフェースなどの参照型が何も指していない状態を表します。C言語のNULL
に相当します。nil
ポインタをデリファレンス(nil
が指す先のメモリにアクセスしようとすること)すると、プログラムはランタイムエラー(パニック)を起こし、クラッシュします。8g -S
:8g
コマンドに-S
オプションを付けて実行すると、コンパイルされたGoプログラムの生成されたアセンブリコードが標準出力にダンプされます。これはデバッグやパフォーマンス分析に役立ちます。Dconv
関数:src/cmd/8g/list.c
に存在する関数で、Prog
構造体の内容を文字列に変換(フォーマット)して表示するために使用されます。特に、アセンブリコードのダンプ (-S
オプション) の際に、Prog
オブジェクトの各フィールドを人間が読める形式で出力する役割を担っています。
技術的詳細
このコミットは、src/cmd/8g/list.c
ファイル内の Dconv
関数における D_BRANCH
ケースの処理を修正しています。Dconv
関数は、Prog
構造体の内容を整形して出力する際に呼び出されます。D_BRANCH
は、現在の Prog
オブジェクトが分岐命令を表している場合に処理されるケースです。
修正前のコードでは、D_BRANCH
の場合、a->u.branch->loc
という形で直接 u.branch
フィールドが指す Prog
オブジェクトの loc
フィールドにアクセスしていました。ここで a
は現在の Prog
オブジェクトを指します。
case D_BRANCH:
snprint(str, sizeof(str), "%d", a->u.branch->loc);
break;
問題は、何らかの理由で a->u.branch
が nil
になっている場合、a->u.branch->loc
にアクセスしようとすると nil
ポインタデリファレンスが発生し、プログラムがクラッシュすることでした。これは特に 8g -S
のようなアセンブリダンプのシナリオで顕在化しました。
修正は非常にシンプルで、a->u.branch
が nil
であるかどうかをチェックする条件分岐を追加しました。
case D_BRANCH:
if(a->u.branch == nil)
snprint(str, sizeof(str), "<nil>");
else
snprint(str, sizeof(str), "%d", a->u.branch->loc);
break;
この変更により、u.branch
が nil
の場合は、クラッシュする代わりに <nil>
という文字列が出力されるようになります。これにより、nil
ポインタデリファレンスによるクラッシュは回避されます。
コミットメッセージにある「経験的に、このコードではすべての分岐ターゲットが nil
であるため、何らかの問題がまだ残っています」という記述は重要です。これは、u.branch
が nil
になること自体が予期せぬ状態であり、その根本原因(コンパイラの他の部分でのバグや、特定のコードパターンに対する不適切な Prog
生成)はまだ解決されていないことを示しています。このコミットは、根本原因を解決するのではなく、その結果として発生するクラッシュを回避するための防御的なプログラミング(nilチェック)を導入したものです。
コアとなるコードの変更箇所
--- a/src/cmd/8g/list.c
+++ b/src/cmd/8g/list.c
@@ -107,7 +107,10 @@ Dconv(Fmt *fp)
break;
case D_BRANCH:
- snprint(str, sizeof(str), "%d", a->u.branch->loc);
+ if(a->u.branch == nil)
+ snprint(str, sizeof(str), "<nil>");
+ else
+ snprint(str, sizeof(str), "%d", a->u.branch->loc);
break;
case D_EXTERN:
コアとなるコードの解説
変更は src/cmd/8g/list.c
ファイル内の Dconv
関数にあります。この関数は、Goコンパイラの内部表現である Prog
構造体を人間が読める形式の文字列に変換する役割を担っています。
特に、case D_BRANCH:
の部分が修正されました。これは、処理中の Prog
オブジェクトが分岐命令(例: JMP
, CALL
)を表している場合に実行されるコードブロックです。
修正前は以下のようになっていました。
snprint(str, sizeof(str), "%d", a->u.branch->loc);
ここで a
は現在の Prog
オブジェクトへのポインタです。a->u.branch
は分岐ターゲットの Prog
オブジェクトへのポインタを保持しています。この行は、分岐ターゲットの Prog
オブジェクトの loc
フィールド(おそらく命令のアドレスやオフセットを示す)を整数として文字列 str
にフォーマットしようとしていました。
修正後は以下のようになりました。
if(a->u.branch == nil)
snprint(str, sizeof(str), "<nil>");
else
snprint(str, sizeof(str), "%d", a->u.branch->loc);
この変更により、a->u.branch
が nil
であるかどうかのチェックが追加されました。
- もし
a->u.branch
がnil
であれば、つまり分岐ターゲットが設定されていないか、無効な状態である場合、snprint
関数はstr
に<nil>
という文字列を書き込みます。これにより、nil
ポインタデリファレンスが回避され、クラッシュが防がれます。 - もし
a->u.branch
がnil
でなければ、以前と同様にa->u.branch->loc
の値を文字列にフォーマットします。
この修正は、nil
ポインタデリファレンスという特定のランタイムエラーを直接的に解決するものであり、プログラムの堅牢性を向上させます。ただし、u.branch
が nil
になる根本的な原因については、このコミットでは触れられていません。
関連リンク
- Go CL 58400043: https://golang.org/cl/58400043
- Go Issue 7178: https://golang.org/issue/7178
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/8g/list.c
および関連するコンパイラコード) - Go言語のIssueトラッカー (Issue 7178)
- Go言語のコードレビューシステム (CL 58400043)
- Goコンパイラの内部構造に関する一般的な知識 (Prog構造体、アセンブラの動作など)
- C言語における
nil
ポインタ(NULL
ポインタ)の概念とデリファレンスによるクラッシュ