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

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

このコミットは、Goコンパイラ(cmd/gc)のデータ競合検出器(Race Detector)関連のコード(racewalk)におけるコンパイラクラッシュのバグを修正するものです。特定の配列インデックス計算を含むGoコードがコンパイル時にセグメンテーション違反を引き起こす問題が解決されました。

コミット

commit ca4b868e9ae9ef11515a3224a9e311c7437e2c9c
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Mon Dec 17 12:55:41 2012 +0400

    cmd/gc: racewalk: fix compiler crash
    The code:
    func main() {
            v := make([]int64, 10)
            i := 1
            _ = v[(i*4)/3]
    }
    crashes compiler with:
    
    Program received signal SIGSEGV, Segmentation fault.
    0x000000000043c274 in walkexpr (np=0x7fffffffc9b8, init=0x0) at src/cmd/gc/walk.c:587
    587                     *init = concat(*init, n->ninit);\n
    (gdb) bt
    #0  0x000000000043c274 in walkexpr (np=0x7fffffffc9b8, init=0x0) at src/cmd/gc/walk.c:587
    #1  0x0000000000432d15 in copyexpr (n=0x7ffff7f69a48, t=<optimized out>, init=0x0) at src/cmd/gc/subr.c:2020
    #2  0x000000000043f281 in walkdiv (init=0x0, np=0x7fffffffca70) at src/cmd/gc/walk.c:2901
    #3  walkexpr (np=0x7ffff7f69760, init=0x0) at src/cmd/gc/walk.c:956
    #4  0x000000000043d801 in walkexpr (np=0x7ffff7f69bc0, init=0x0) at src/cmd/gc/walk.c:988
    #5  0x000000000043cc9b in walkexpr (np=0x7ffff7f69d38, init=0x0) at src/cmd/gc/walk.c:1068
    #6  0x000000000043c50b in walkexpr (np=0x7ffff7f69f50, init=0x0) at src/cmd/gc/walk.c:879
    #7  000000000043c50b in walkexpr (np=0x7ffff7f6a0c8, init=0x0) at src/cmd/gc/walk.c:879
    #8  0x0000000000440a53 in walkexprlist (l=0x7ffff7f6a0c8, init=0x0) at src/cmd/gc/walk.c:357
    #9  0x000000000043d0bf in walkexpr (np=0x7fffffffd318, init=0x0) at src/cmd/gc/walk.c:566
    #10 0x00000000004402bf in vmkcall (fn=<optimized out>, t=0x0, init=0x0, va=0x7fffffffd368) at src/cmd/gc/walk.c:2275
    #11 0x000000000044059a in mkcall (name=<optimized out>, t=0x0, init=0x0) at src/cmd/gc/walk.c:2287
    #12 0x000000000042862b in callinstr (np=0x7fffffffd4c8, init=0x7fffffffd568, wr=0, skip=<optimized out>) at src/cmd/gc/racewalk.c:478
    #13 0x00000000004288b7 in racewalknode (np=0x7ffff7f68108, init=0x7fffffffd568, wr=0, skip=0) at src/cmd/gc/racewalk.c:287
    #14 0x0000000000428781 in racewalknode (np=0x7ffff7f65840, init=0x7fffffffd568, wr=0, skip=0) at src/cmd/gc/racewalk.c:302
    #15 0x0000000000428abd in racewalklist (l=0x7ffff7f65840, init=0x0) at src/cmd/gc/racewalk.c:97
    #16 0x0000000000428d0b in racewalk (fn=0x7ffff7f5f010) at src/cmd/gc/racewalk.c:63
    #17 0x0000000000402b9c in compile (fn=0x7ffff7f5f010) at src/cmd/6g/../gc/pgen.c:67
    #18 0x0000000000419f86 in funccompile (n=0x7ffff7f5f010, isclosure=0) at src/cmd/gc/dcl.c:1414
    #19 0x0000000000424161 in p9main (argc=<optimized out>, argv=<optimized out>) at src/cmd/gc/lex.c:431
    #20 0x0000000000401739 in main (argc=<optimized out>, argv=<optimized out>) at src/lib9/main.c:35
    
    The problem is nil init passed to mkcall().
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6940045

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

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

元コミット内容

このコミットは、Goコンパイラが特定のGoコードをコンパイルする際にクラッシュするバグを修正します。問題のコードは以下の通りです。

func main() {
        v := make([]int64, 10)
        i := 1
        _ = v[(i*4)/3] // ここでクラッシュが発生
}

このコードをコンパイルすると、コンパイラはセグメンテーション違反(Segmentation fault)で異常終了します。スタックトレースから、src/cmd/gc/walk.cwalkexpr関数内の*init = concat(*init, n->ninit);という行でクラッシュしていることが示されています。根本原因は、mkcall()関数にnilinit引数が渡されたことにあると特定されています。

変更の背景

Go言語には、並行処理におけるデータ競合(data race)を検出するための「Race Detector」というツールが組み込まれています。このツールは、コンパイル時に特定のインストゥルメンテーションコードを挿入することで機能します。

このバグは、Race Detectorが有効な状態で、配列のインデックス計算に複雑な算術式(例: (i*4)/3)が含まれる場合に発生しました。コンパイラがこのような式を処理し、同時にRace Detectorのためのraceread(読み込み競合検出)またはracewrite(書き込み競合検出)の呼び出しを挿入しようとした際に、内部的な初期化リストの処理が正しく行われず、nilポインタの逆参照が発生し、コンパイラがクラッシュしていました。

コンパイラのクラッシュは、開発プロセスを中断させ、Goプログラムのビルドを妨げる重大な問題です。そのため、このバグの修正はGoコンパイラの安定性と信頼性を確保するために不可欠でした。

前提知識の解説

このコミットの理解には、以下の概念が役立ちます。

  1. Goコンパイラ (cmd/gc): Go言語の公式コンパイラです。ソースコードを機械語に変換する役割を担います。コンパイルプロセスは複数のフェーズに分かれており、構文解析、型チェック、中間表現(AST)の生成、最適化、コード生成などがあります。
  2. 抽象構文木 (AST: Abstract Syntax Tree): ソースコードの構造を木構造で表現したものです。コンパイラはASTを操作しながら、コードの意味解析や変換を行います。Goコンパイラでは、Node構造体がASTの各ノードを表します。
  3. walkフェーズ: Goコンパイラの重要なフェーズの一つで、ASTを走査(walk)しながら、高レベルなASTノードをより低レベルなノードに変換したり、特定の最適化やコード変換を行ったりします。このフェーズで、関数呼び出しの準備や、変数の初期化などが処理されます。
  4. Go Race Detector: Goランタイムに組み込まれたデータ競合検出ツールです。並行実行されるゴルーチン間で共有データへのアクセスが適切に同期されていない場合に発生するデータ競合を特定します。コンパイル時にracereadracewriteといった特殊な関数呼び出しを挿入することで、実行時にメモリアクセスを監視します。
  5. mkcall関数: Goコンパイラの内部関数で、AST内に新しい関数呼び出しノードを生成するために使用されます。この関数は、呼び出す関数、戻り値の型、引数、そして初期化文のリストinitリスト)を受け取ります。initリストは、関数呼び出しの引数を評価する際などに必要となる一時的な初期化文を格納するために使われます。
  6. NodeListinitリスト: NodeListはASTノードのリンクリストです。コンパイラは、文(statement)や式(expression)の評価に伴って生成される初期化処理を、このNodeListとしてinitリストに蓄積します。これらの初期化文は、後続のコード生成フェーズで適切な位置に挿入されます。
  7. SIGSEGV (Segmentation Fault): プログラムが不正なメモリアドレスにアクセスしようとしたときに発生するエラーです。通常、nilポインタの逆参照(nilであるポインタが指すメモリ領域にアクセスしようとすること)が原因で発生します。

技術的詳細

このバグは、Goコンパイラのracewalkフェーズで発生していました。racewalkは、Race Detectorのためにメモリアクセス(読み込み/書き込み)を監視するracereadracewriteといった関数呼び出しをASTに挿入する役割を担っています。

問題のGoコード_ = v[(i*4)/3]では、配列vへのアクセスが行われています。このアクセスは、Race Detectorによってraceread呼び出しに変換される必要があります。racewalk.c内のcallinstr関数は、このracereadまたはracewrite呼び出しをASTに挿入する処理を担当しています。

callinstr関数内で、mkcall関数が使用され、racereadまたはracewriteの関数呼び出しノードが生成されます。元のコードでは、このmkcall関数に渡されるinit引数がnilとなっていました。

// src/cmd/gc/racewalk.c (修正前)
f = mkcall(wr ? "racewrite" : "raceread", T, nil, uintptraddr(n));

mkcall関数は、生成される関数呼び出しに必要な初期化文を格納するためのinitリストを受け取ります。配列のインデックス計算 (i*4)/3のような複雑な式は、評価の過程で一時的な変数の割り当てや、中間結果の計算といった初期化文を生成する可能性があります。

mkcallnilが渡された場合、これらの初期化文は適切にinitリストに連結されません。その結果、コンパイラのwalkフェーズの後半で、src/cmd/gc/walk.c:587*init = concat(*init, n->ninit);という行が実行される際に問題が発生します。この行は、現在のノードnに関連する初期化文n->ninitを、親のinitリストに連結しようとします。しかし、racewalkフェーズでmkcallnilが渡されたために、initポインタがnilのままとなり、*initを逆参照しようとした際にセグメンテーション違反が発生し、コンパイラがクラッシュしていました。

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

このコミットによる主要なコード変更は、src/cmd/gc/racewalk.cファイルにあります。

diff --git a/src/cmd/gc/racewalk.c b/src/cmd/gc/racewalk.c
index 2d216ec67a..1840c6529e 100644
--- a/src/cmd/gc/racewalk.c
+++ b/src/cmd/gc/racewalk.c
@@ -475,8 +475,7 @@ callinstr(Node **np, NodeList **init, int wr, int skip)\n 	\t\t*np = n;\n 	\t}\n 	\tn = treecopy(n);\n-\t\tf = mkcall(wr ? "racewrite" : "raceread", T, nil, uintptraddr(n));\n-\t\t//typecheck(&f, Etop);\n+\t\tf = mkcall(wr ? "racewrite" : "raceread", T, init, uintptraddr(n));\n 	\t*init = list(*init, f);\n 	\treturn 1;\n 	}\

また、このバグを再現し、修正を検証するためのテストケースがsrc/pkg/runtime/race/testdata/regression_test.goに追加されました。

diff --git a/src/pkg/runtime/race/testdata/regression_test.go b/src/pkg/runtime/race/testdata/regression_test.go
index 442379d7ed..c48f7b8600 100644
--- a/src/pkg/runtime/race/testdata/regression_test.go
+++ b/src/pkg/runtime/race/testdata/regression_test.go
@@ -121,3 +121,9 @@ func TestNoRaceRpcChan(t *testing.T) {\n \t\tt.Fatalf(\"makeChanCalls %d, expected 1\\n\", makeChanCalls)\n \t}\n }\n+\n+func divInSlice() {\n+\tv := make([]int64, 10)\n+\ti := 1\n+\t_ = v[(i*4)/3]\n+}\

コアとなるコードの解説

修正の核心は、src/cmd/gc/racewalk.ccallinstr関数におけるmkcallの呼び出しです。

元のコード:

f = mkcall(wr ? "racewrite" : "raceread", T, nil, uintptraddr(n));

修正後のコード:

f = mkcall(wr ? "racewrite" : "raceread", T, init, uintptraddr(n));

変更点は、mkcall関数の第3引数(initリスト)にnilではなく、callinstr関数自身が受け取ったinitポインタ(NodeList **init)を渡すようにしたことです。

これにより、racewriteまたはraceread呼び出しを生成する際に、その呼び出しに必要なすべての初期化文(例えば、配列インデックスの計算結果など)が、正しく親のinitリストに連結されるようになります。initポインタがnilでなくなるため、後続のwalkexpr関数内で*init = concat(*init, n->ninit);のような操作が行われても、nilポインタの逆参照が発生せず、コンパイラのクラッシュが回避されます。

この修正は、Race Detectorが挿入するコードが、コンパイラの他の部分と正しく連携し、ASTの初期化リストの管理規則に従うことを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goコンパイラのソースコード(src/cmd/gcディレクトリ)
  • Go Race Detectorに関する一般的な情報