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

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

このコミットは、Goコンパイラ(cmd/gc)のplive.cファイルにおけるクラッシュバグの修正に関するものです。具体的には、コンパイラのライブネス解析(liveness analysis)のデバッグモード(-liveモード)において発生していたクラッシュを修正しています。

コミット

commit ab844022eeedc6c16d0cdb7fb6fee01dd9e6307a
Author: Russ Cox <rsc@golang.org>
Date:   Tue Mar 11 23:58:11 2014 -0400

    cmd/gc: fix crash in -live mode

    debuglive >= 1 is not the condition under which we
    start recording messages (we avoid printing for
    init functions even if debuglive is set).

    LGTM=bradfitz, iant
    R=golang-codereviews, bradfitz
    CC=golang-codereviews, iant, khr
    https://golang.org/cl/74390043

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

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

元コミット内容

cmd/gc: fix crash in -live mode

このコミットは、Goコンパイラの-liveモードにおけるクラッシュを修正します。 debuglive >= 1という条件は、メッセージの記録を開始する適切な条件ではありませんでした(debugliveが設定されていても、init関数では出力が抑制されます)。

変更の背景

Goコンパイラには、プログラムの最適化やガベージコレクションのために、変数の「ライブネス」(ある時点で変数の値が将来使用される可能性があるかどうか)を解析する機能があります。この解析にはデバッグモードがあり、debugliveというフラグによって制御されていました。

元のコードでは、デバッグメッセージの記録や出力を行う際に、debuglive >= 1という条件を使用していました。しかし、この条件だけでは不十分であり、特にinit関数(Goプログラムの初期化時に実行される特殊な関数)の場合、debugliveが設定されていてもメッセージの記録に必要なmsg変数が適切に初期化されていない可能性がありました。

msg変数がnil(ヌルポインタ)であるにもかかわらず、コードがその内容にアクセスしようとすると、ヌルポインタ参照解除によるクラッシュが発生します。このコミットは、この特定のシナリオでのクラッシュを防ぐことを目的としています。

前提知識の解説

  • Goコンパイラ (cmd/gc): Go言語のソースコードを機械語に変換する公式コンパイラです。Goのツールチェインの一部として提供されます。
  • ライブネス解析 (Liveness Analysis): コンパイラのデータフロー解析の一種で、プログラムの特定のポイントでどの変数が「ライブ」(その値が将来の計算で使用される可能性がある)であるかを決定します。これは、ガベージコレクションが不要なメモリを解放したり、レジスタ割り当てを最適化したりするために不可欠です。
  • init関数: Go言語の特殊な関数で、パッケージが初期化される際に自動的に実行されます。各パッケージは複数のinit関数を持つことができ、これらは宣言された順序で実行されます。
  • debuglive: Goコンパイラの内部的なデバッグフラグの一つで、ライブネス解析に関する詳細なデバッグ情報を出力するために使用されます。このフラグが設定されると、コンパイラはライブネス解析の過程で中間状態や結果に関するメッセージを生成する可能性があります。
  • ヌルポインタ参照解除 (Null Pointer Dereference): プログラミングにおいて、ヌル(無効な)アドレスを指すポインタを逆参照しようとすると発生するエラーです。これは通常、プログラムのクラッシュや未定義の動作を引き起こします。C言語で書かれたGoコンパイラのコードでは、これはセグメンテーション違反などの形で現れることがあります。
  • plive.c: Goコンパイラのソースコードの一部で、ライブネス解析のロジックが実装されているファイルです。

技術的詳細

このコミットの核心は、デバッグメッセージの出力条件の変更にあります。元のコードでは、debuglive >= 1という条件が、デバッグメッセージを記録するための主要なゲートとして機能していました。しかし、この条件は、メッセージを実際に格納するためのバッファや構造体(ここではmsg変数によって表される)が有効であるかどうかを保証していませんでした。

特にinit関数のような特定のコンテキストでは、debugliveフラグが設定されていても、メッセージ記録のメカニズムが完全に初期化されていない、または意図的に抑制されている場合があります。このような状況でmsgnilであるにもかかわらず、その内容にアクセスしようとすると、クラッシュが発生します。

修正は、条件をdebuglive >= 1からmsg != nilに変更することで、この問題を解決します。これにより、メッセージの記録や出力に関連するコードブロックは、msg変数が有効なポインタ(つまり、メッセージを格納するための領域が確保されている)である場合にのみ実行されるようになります。これは、ヌルポインタ参照解除を防ぐための典型的な防御的プログラミングの手法です。

livenessepilogue関数は、ライブネス解析の最終段階で呼び出される関数であり、解析結果に基づいてデバッグメッセージを生成・出力する役割を担っていると考えられます。この関数内でmsgnilチェックされることで、安全性が向上します。

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

変更はsrc/cmd/gc/plive.cファイルにあります。

--- a/src/cmd/gc/plive.c
+++ b/src/cmd/gc/plive.c
@@ -1607,7 +1607,7 @@ livenessepilogue(Liveness *lv)\n 				// We're interpreting the args and locals bitmap instead of liveout so that we\n 				// include the bits added by the avarinit logic in the\n 				// previous loop.\n-				if(debuglive >= 1) {\n+				if(msg != nil) {\n 					fmtstrinit(&fmt);\n 					fmtprint(&fmt, "%L: live at ", p->lineno);\n 					if(p->as == ACALL && p->to.node)\n@@ -1657,7 +1657,7 @@ livenessepilogue(Liveness *lv)\n 				pos--;\n 			}\n 		}\n-		if(debuglive >= 1) {\n+		if(msg != nil) {\n 			for(j=startmsg; j<nmsg; j++) \n 				if(msg[j] != nil)\n 					print("%s", msg[j]);\n```

## コアとなるコードの解説

変更は`livenessepilogue`関数内の2箇所で行われています。

1.  **1607行目付近の変更**:
    ```c
    -				if(debuglive >= 1) {
    +				if(msg != nil) {
    ```
    このブロックは、ライブネス解析のデバッグメッセージをフォーマットし、記録する部分です。元のコードでは`debuglive >= 1`という条件でこの処理を実行していましたが、`msg`が`nil`である場合に`fmtstrinit`や`fmtprint`といった関数がヌルポインタを扱おうとしてクラッシュする可能性がありました。修正後は、`msg`が有効な場合にのみこのブロックが実行されるため、安全性が確保されます。

2.  **1657行目付近の変更**:
    ```c
    -		if(debuglive >= 1) {
    +		if(msg != nil) {
    ```
    このブロックは、記録されたデバッグメッセージを実際に標準出力に`print`する部分です。ここでも同様に、`msg`が`nil`であるにもかかわらずループ内で`msg[j]`にアクセスしようとするとクラッシュが発生する可能性がありました。`msg != nil`のチェックを追加することで、メッセージ配列が有効な場合にのみ出力処理が行われるようになります。

これらの変更により、`debuglive`フラグが設定されていても、メッセージ記録のための内部構造体`msg`が初期化されていない(`nil`である)場合に、その構造体へのアクセスを試みなくなり、結果としてコンパイラのクラッシュが回避されます。

## 関連リンク

*   Go issue tracker: このコミットに関連するGoのIssueや変更リスト(CL)は、コミットメッセージに記載されている`https://golang.org/cl/74390043`で確認できます。このリンクは、GoのコードレビューシステムGerritへのリンクであり、より詳細な議論や変更の経緯が記録されています。

## 参考にした情報源リンク

*   Go言語の公式ドキュメント
*   Goコンパイラのソースコード(`src/cmd/gc/plive.c`)
*   ライブネス解析に関する一般的なコンパイラ理論の知識
*   コミットメッセージに記載された情報
*   GitHubのコミットページ: [https://github.com/golang/go/commit/ab844022eeedc6c16d0cdb7fb6fee01dd9e6307a](https://github.com/golang/go/commit/ab844022eeedc6c16d0cdb7fb6fee01dd9e6307a)
*   Go Code Review (Gerrit) CL: [https://golang.org/cl/74390043](https://golang.org/cl/74390043)