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

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

このコミットは、Go言語のレース検出器(Race Detector)の機能強化に関するものです。具体的には、コンパイラ(gc)のレース検出関連コード(racewalk.c)において、抽象構文木(AST)ノードの処理を改善し、インターフェースの比較やメソッド値に関する新たなテストケースを追加することで、レース検出の精度と網羅性を向上させています。

コミット

commit eb7c51c1483b2d51e5fc0b22ef692bdc96212477
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Tue Mar 26 08:27:18 2013 +0100

    cmd/gc: more race instrumentation.
    
    Handle interface comparison correctly,
    add a few more tests, mark more nodes as impossible.
    
    R=dvyukov, golang-dev
    CC=golang-dev
    https://golang.org/cl/7942045

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

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

元コミット内容

cmd/gc: more race instrumentation.

インターフェースの比較を正しく処理し、いくつかのテストを追加し、より多くのノードを(レース検出器にとって)「不可能」(処理対象外または既に低レベル化されている)としてマークする。

変更の背景

Go言語のレース検出器は、並行処理におけるデータ競合(data race)を検出するための強力なツールです。データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。このような競合は、予測不能なプログラムの動作やバグの原因となります。

このコミットが行われた2013年当時、Goのレース検出器はまだ比較的新しい機能であり、その精度と網羅性を継続的に向上させる必要がありました。特に、Goの言語機能であるインターフェースやメソッド値といった複雑な要素が絡むメモリ操作において、レース検出器が正しく動作しない、あるいは見落としが発生する可能性がありました。

このコミットの背景には、以下の課題があったと考えられます。

  1. インターフェース比較におけるデータ競合の検出漏れ: インターフェースの比較(==!=)は、内部的にインターフェースが保持する型情報や値へのポインタを読み取る操作を含みます。これらの内部的な読み取りが、他のゴルーチンによるインターフェース値への書き込みと競合する場合、データ競合が発生する可能性があります。しかし、当時のレース検出器は、この種の操作を適切にインストゥルメント(計測コードを挿入)できていなかった可能性があります。
  2. ASTノードの不適切な処理: Goコンパイラは、ソースコードをASTに変換し、様々な最適化やコード生成を行います。レース検出器は、このASTを走査(racewalk)して、メモリアクセスが発生する可能性のあるノードに計測コードを挿入します。しかし、一部のASTノード(例えば、コンパイラの「低レベル化(lowering)」フェーズで変換されるべきノードや、そもそもメモリアクセスを伴わないノード)が不適切に処理されたり、あるいは未処理のまま残されていたりする問題がありました。これにより、不要な計測が行われたり、逆に必要な計測が漏れたりする可能性がありました。
  3. テストカバレッジの不足: 新しい言語機能や複雑なケースが追加されるにつれて、レース検出器のテストカバレッジも更新・拡充される必要があります。インターフェースやメソッド値に関する特定の競合シナリオをカバーするテストが不足していたため、それらのケースでの検出器の正確性を保証できませんでした。

これらの課題に対処するため、本コミットでは、インターフェース比較の正しい処理、ASTノードの分類の見直し、そして関連するテストケースの追加が行われました。

前提知識の解説

Go Race Detector (レース検出器)

Go Race Detectorは、Goプログラムの実行中にデータ競合を動的に検出するツールです。データ競合は、並行処理における最も一般的なバグの一つであり、デバッグが非常に困難です。Go Race Detectorは、プログラムの実行時にメモリアクセスを監視し、以下の条件がすべて満たされた場合にデータ競合を報告します。

  1. 少なくとも2つのゴルーチンが同じメモリ位置にアクセスする。
  2. 少なくとも1つのアクセスが書き込みである。
  3. それらのアクセスが同期メカニズム(ミューテックス、チャネルなど)によって保護されていない。

レース検出器は、コンパイル時に特別なインストゥルメンテーション(計測コードの挿入)をGoバイナリに追加することで機能します。これにより、実行時のメモリアクセスパターンを追跡し、競合を特定します。

Go Compiler (gc) と AST (抽象構文木)

gcはGo言語の公式コンパイラです。Goのソースコードは、gcによって以下の主要なフェーズを経て実行可能なバイナリに変換されます。

  1. パース (Parsing): ソースコードを解析し、抽象構文木(AST)を構築します。ASTは、プログラムの構造を木構造で表現したものです。
  2. 型チェック (Type Checking): AST上の各ノードの型が正しいか検証します。
  3. 低レベル化 (Lowering): 高レベルな言語構造(例: for-rangeループ、複合リテラル、クロージャ)を、より低レベルで単純なASTノードの組み合わせに変換します。これにより、バックエンドでのコード生成が容易になります。
  4. 最適化 (Optimization): コードのパフォーマンスを向上させるための様々な最適化を適用します。
  5. コード生成 (Code Generation): ASTをターゲットアーキテクチャの機械語に変換します。

racewalk.cは、gcのコンパイルパイプラインの一部として、ASTを走査し、レース検出に必要な計測コードを挿入する役割を担っています。

racewalk.cの役割

src/cmd/gc/racewalk.cは、Goコンパイラ(gc)の一部であり、レース検出器のインストゥルメンテーションを担当するファイルです。このファイル内のracewalknode関数は、GoプログラムのASTを再帰的に走査し、メモリへの読み書き操作が行われる可能性のあるノードを特定します。そして、これらのノードに対して、レース検出器が実行時にメモリアクセスを監視するための特別なコード(インストゥルメンテーション)を挿入します。

racewalknode関数は、ASTノードのop(操作の種類)に基づいて異なる処理を行います。例えば、変数への代入(OAS)、ポインタのデリファレンス(OIND)、配列のインデックスアクセス(OINDEX)などは、メモリアクセスを伴うため、インストゥルメンテーションの対象となります。一方で、定数や型定義など、メモリアクセスを伴わないノードはスキップされます。

Goのインターフェースと内部表現

Goのインターフェースは、メソッドのセットを定義する型です。Goのインターフェース値は、内部的に2つのポインタから構成されます。

  1. 型ポインタ (type pointer): インターフェース値が保持する具体的な型(concrete type)の情報を指します。
  2. データポインタ (data pointer): インターフェース値が保持する具体的な値(concrete value)を指します。

インターフェースの比較(a == ba == nil)は、これらの内部ポインタを比較する操作を含みます。特に、データポインタが指すメモリ領域は、他のゴルーチンによって変更される可能性があるため、インターフェース比較がデータ競合の発生源となることがあります。

Goのメソッド値 (Method Values)

Goのメソッド値は、レシーバがバインドされた関数値です。例えば、type T struct {}; func (t T) M() {}という定義がある場合、var t T; f := t.Mのようにすると、ftにバインドされたMメソッドの関数値となります。このfを呼び出すと、t.M()が呼び出されます。

メソッド値の生成や呼び出しも、レシーバのメモリにアクセスする可能性があります。特に、レシーバがポインタ型であったり、メソッドがレシーバの値を変更したりする場合、並行アクセスによるデータ競合のリスクが生じます。

ASTノードの種類(例)

GoコンパイラのASTには、様々な種類のノードが存在し、それぞれがプログラムの異なる要素を表します。

  • OITAB: インターフェースの型情報テーブルに関連するノード。
  • OCMPIFACE: インターフェースの比較操作(==!=)を表すノード。
  • OCONVIFACE: 具体的な型からインターフェース型への変換を表すノード。
  • OMAKECHAN, OMAKEMAP, OMAKESLICE: make関数によるチャネル、マップ、スライスの作成を表すノード。
  • OCALLPART: メソッド値の生成など、部分的な関数呼び出しを表すノード。
  • OCLOSURE: クロージャの定義を表すノード。
  • ORANGE: for-rangeループを表すノード。
  • OARRAYLIT, OMAPLIT, OSTRUCTLIT: 配列、マップ、構造体のリテラル(複合リテラル)を表すノード。
  • OCHECKNOTNIL: nilチェックを表すノード。
  • OPARAM: 関数のパラメータを表すノード。
  • OCLOSUREVAR: クロージャによってキャプチャされた変数を表すノード。
  • ODOTMETH: メソッドの選択(例: obj.Method)を表すノード。

これらのノードは、コンパイルの進行に伴って「低レベル化」され、より基本的な操作に分解されていきます。レース検出器は、低レベル化される前の高レベルなノード、あるいは低レベル化された後のノードのいずれかで、適切なタイミングでインストゥルメンテーションを行う必要があります。

技術的詳細

このコミットの技術的詳細は、主にsrc/cmd/gc/racewalk.cにおけるASTノードの処理ロジックの変更と、src/pkg/runtime/race/testdata/mop_test.goにおけるテストケースの追加に集約されます。

racewalk.cにおける変更

racewalk.cracewalknode関数は、ASTノードを走査し、レース検出のためのインストゥルメンテーションが必要かどうかを判断します。このコミットでは、以下の重要な変更が行われました。

  1. OITABノードの処理追加: 以前はOITABノードはracewalknode関数内で明示的に処理されていませんでした。このコミットにより、case OITAB:が追加され、racewalknode(&n->left, init, 0, 0);が呼び出されるようになりました。これは、インターフェースの型情報テーブルへのアクセスもレース検出の対象とすることで、インターフェース関連の競合検出を強化する意図があります。OITABはインターフェースの内部表現の一部であり、そのアクセスが競合を引き起こす可能性があるため、この変更は重要です。

  2. OCMPIFACEノードの分類変更: OCMPIFACE(インターフェース比較)ノードは、以前は// unimplemented(未実装)セクションに分類されていました。これは、レース検出器がインターフェース比較を適切に処理できていなかったことを示唆します。このコミットでは、OCMPIFACE// should not appear in AST by now(この時点ではASTに存在すべきではない)セクションに移動されました。これは、インターフェース比較がコンパイルのより早い段階で低レベル化され、より基本的なメモリ操作に分解されるようになったことを意味します。レース検出器は、この低レベル化された操作に対してインストゥルメンテーションを行うことで、インターフェース比較における競合を検出できるようになります。

  3. 「低レベル化されるべきノード」の明確化: OCALLPART, OCLOSURE, ORANGE, OARRAYLIT, OMAPLIT, OSTRUCTLITといったノードが、新たに// should not appear in AST by nowセクションに追加されました。これらのノードは、Goの複合的な言語機能(メソッド値、クロージャ、for-range、複合リテラル)を表します。コンパイラの低レベル化フェーズで、これらはより単純なASTノード(例えば、ORANGEは通常のforループと配列アクセスに、複合リテラルは一連の代入に)に変換されます。レース検出器は、これらの高レベルなノードではなく、低レベル化された後の基本的なメモリ操作に対してインストゥルメンテーションを行うべきです。この変更は、コンパイラのパイプラインにおけるレース検出器の処理タイミングと対象をより正確に定義し、不要な処理を避け、必要な処理を確実に行うためのものです。

  4. 「インストゥルメンテーション不要なノード」の拡充: OCHECKNOTNIL, OPARAM, OCLOSUREVAR, ODOTMETHといったノードが、// does not require instrumentation(インストゥルメンテーション不要)セクションに移動または追加されました。

    • OCHECKNOTNIL: nilチェックは通常、その後に続くメモリ読み取り操作の一部として扱われるため、別途インストゥルメンテーションは不要と判断されたと考えられます。
    • OPARAM: 関数のパラメータは、通常、関数のエントリポイントでスタックまたはレジスタにコピーされるため、特別なインストゥルメンテーションは不要と判断された可能性があります。
    • OCLOSUREVAR: クロージャによってキャプチャされた変数へのポインタは不変であるため、そのポインタ自体へのアクセスは競合を引き起こさないと判断された可能性があります。ただし、そのポインタが指す先の値へのアクセスはインストゥルメンテーションの対象となります。
    • ODOTMETH: メソッドの選択自体はメモリアクセスを伴わず、その後のメソッド呼び出しやメソッド値の生成時に処理されるため、インストゥルメンテーションは不要と判断されたと考えられます。

これらの変更は、racewalk.cがASTをより正確に理解し、どのノードがメモリアクセスを伴い、どのノードが低レベル化されるべきか、あるいはどのノードがインストゥルメンテーションを必要としないかを適切に判断できるようにすることで、レース検出器の効率と正確性を向上させます。

mop_test.goにおけるテストの追加

src/pkg/runtime/race/testdata/mop_test.goは、Goのレース検出器のテストスイートの一部です。このコミットでは、特にインターフェースの比較とメソッド値に関する複数の新しいテストケースが追加されました。これらのテストは、racewalk.cの変更が意図した通りに機能し、以前は検出できなかったデータ競合を正しく検出できるようになったことを検証するために不可欠です。

  1. TestRaceIfaceCmp: このテストは、2つのインターフェース値の比較(a == b)が並行して行われる場合にデータ競合が発生するかどうかを検証します。aがゴルーチン内で書き換えられている間に、メインゴルーチンでa == bが評価されるシナリオをシミュレートしています。

  2. TestRaceIfaceCmpNil: このテストは、インターフェース値とnilの比較(a == nil)が並行して行われる場合にデータ競合が発生するかどうかを検証します。aがゴルーチン内で書き換えられている間に、メインゴルーチンでa == nilが評価されるシナリオをシミュレートしています。

  3. TestRaceMethodValue, TestRaceMethodValue2, TestRaceMethodValue3: これらのテストは、メソッド値の生成と、そのレシーバへの並行アクセスがデータ競合を引き起こすかどうかを検証します。

    • TestRaceMethodValue: 具体的な値レシーバを持つメソッド値のケース。
    • TestRaceMethodValue2: インターフェースレシーバを持つメソッド値のケース。
    • TestRaceMethodValue3: 暗黙的なデリファレンスを伴うメソッド値のケース(ポインタレシーバのデリファレンス)。 これらのテストは、メソッド値の生成がレシーバのメモリを読み取る操作であり、その操作が並行して行われる書き込みと競合する可能性があることを示しています。
  4. TestNoRaceMethodValue: このテストは、メソッド値の生成がレシーバのアドレスを暗黙的に取得するだけで、値自体をデリファレンスしない場合に、データ競合が発生しないことを検証します。これは、レース検出器が不必要な競合を報告しないことを確認するための重要なテストです。

  5. TestRaceSliceString: このテストは、文字列のスライス操作(x[2:3])が並行して行われる書き込みと競合するかどうかを検証します。文字列はGoでは不変ですが、その基盤となるバイト配列へのアクセスは競合を引き起こす可能性があります。

これらのテストの追加は、レース検出器がGoのより複雑な言語機能(インターフェース、メソッド値、文字列操作)におけるデータ競合を正確に検出できるようになったことを保証するために不可欠です。

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

src/cmd/gc/racewalk.c

--- a/src/cmd/gc/racewalk.c
+++ b/src/cmd/gc/racewalk.c
@@ -312,6 +312,10 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n \t\tracewalknode(&n->right, init, 0, 0);\n \t\tgoto ret;\n \n+\tcase OITAB:\n+\t\tracewalknode(&n->left, init, 0, 0);\n+\t\tgoto ret;\n+\n \t// should not appear in AST by now\n \tcase OSEND:\n \tcase ORECV:\
@@ -323,6 +327,7 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n \tcase OPANIC:\n \tcase ORECOVER:\n \tcase OCONVIFACE:\
+\tcase OCMPIFACE:\
 \tcase OMAKECHAN:\
 \tcase OMAKEMAP:\
 \tcase OMAKESLICE:\
@@ -338,6 +343,12 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n \tcase OADDSTR:\
 \tcase ODOTTYPE:\
 \tcase ODOTTYPE2:\
+\tcase OCALLPART: // lowered to PTRLIT\
+\tcase OCLOSURE:  // lowered to PTRLIT\
+\tcase ORANGE:    // lowered to ordinary for loop\
+\tcase OARRAYLIT: // lowered to assignments\
+\tcase OMAPLIT:\
+\tcase OSTRUCTLIT:\
 \t\tyyerror(\"racewalk: %O must be lowered by now\", n->op);\
 \t\tgoto ret;\
 \n@@ -364,30 +375,23 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n \t// does not require instrumentation\n \tcase OPRINT:     // don\'t bother instrumenting it\n \tcase OPRINTN:    // don\'t bother instrumenting it\
+\tcase OCHECKNOTNIL: // always followed by a read.\
 \tcase OPARAM:     // it appears only in fn->exit to copy heap params back\
+\tcase OCLOSUREVAR:// immutable pointer to captured variable\
+\tcase ODOTMETH:   // either part of CALLMETH or CALLPART (lowered to PTRLIT)\
 \t\tgoto ret;\
 \n \t// unimplemented\n \tcase OSLICESTR:\
 \tcase OAPPEND:\
-\tcase OCMPIFACE:\
-\tcase OARRAYLIT:\
-\tcase OMAPLIT:\
-\tcase OSTRUCTLIT:\
-\tcase OCLOSURE:\
 \tcase ODCL:\
 \tcase ODCLCONST:\
 \tcase ODCLTYPE:\
 \tcase OLITERAL:\
-\tcase ORANGE:\
 \tcase OTYPE:\
 \tcase ONONAME:\
 \tcase OINDREG:\
-\tcase ODOTMETH:\
-\tcase OITAB:\
 \tcase OHMUL:\
-\tcase OCHECKNOTNIL:\
-\tcase OCLOSUREVAR:\
 \t\tgoto ret;\
 \t}\

src/pkg/runtime/race/testdata/mop_test.go

--- a/src/pkg/runtime/race/testdata/mop_test.go
+++ b/src/pkg/runtime/race/testdata/mop_test.go
@@ -576,6 +576,30 @@ func TestRaceIfaceWW(t *testing.T) {\
 \ta = b\n }\n \n+func TestRaceIfaceCmp(t *testing.T) {\n+\tvar a, b Writer\n+\ta = DummyWriter{1}\n+\tch := make(chan bool, 1)\n+\tgo func() {\n+\t\ta = DummyWriter{1}\n+\t\tch <- true\n+\t}()\n+\t_ = a == b\n+\t<-ch\n+}\n+\n+func TestRaceIfaceCmpNil(t *testing.T) {\n+\tvar a Writer\n+\ta = DummyWriter{1}\n+\tch := make(chan bool, 1)\n+\tgo func() {\n+\t\ta = DummyWriter{1}\n+\t\tch <- true\n+\t}()\n+\t_ = a == nil\n+\t<-ch\n+}\n+\n func TestRaceEfaceConv(t *testing.T) {\
 \tc := make(chan bool)\n \tv := 0\n@@ -1151,6 +1175,15 @@ func (p InterImpl) Foo(x int) {\
 \t_, _, _ = x, y, z\n }\n \n+type InterImpl2 InterImpl\n+\n+func (p *InterImpl2) Foo(x int) {\n+\tif p == nil {\n+\t\tInterImpl{}.Foo(x)\n+\t}\n+\tInterImpl(*p).Foo(x)\n+}\n+\n func TestRaceInterCall(t *testing.T) {\
 \tc := make(chan bool, 1)\n \tp := InterImpl{}\n@@ -1212,6 +1245,54 @@ func TestRaceMethodCall2(t *testing.T) {\
 \t<-c\n }\n \n+// Method value with concrete value receiver.\n+func TestRaceMethodValue(t *testing.T) {\n+\tc := make(chan bool, 1)\n+\ti := InterImpl{}\n+\tgo func() {\n+\t\ti = InterImpl{}\n+\t\tc <- true\n+\t}()\n+\t_ = i.Foo\n+\t<-c\n+}\n+\n+// Method value with interface receiver.\n+func TestRaceMethodValue2(t *testing.T) {\n+\tc := make(chan bool, 1)\n+\tvar i Inter = InterImpl{}\n+\tgo func() {\n+\t\ti = InterImpl{}\n+\t\tc <- true\n+\t}()\n+\t_ = i.Foo\n+\t<-c\n+}\n+\n+// Method value with implicit dereference.\n+func TestRaceMethodValue3(t *testing.T) {\n+\tc := make(chan bool, 1)\n+\ti := &InterImpl{}\n+\tgo func() {\n+\t\t*i = InterImpl{}\n+\t\tc <- true\n+\t}()\n+\t_ = i.Foo // dereferences i.\n+\t<-c\n+}\n+\n+// Method value implicitly taking receiver address.\n+func TestNoRaceMethodValue(t *testing.T) {\n+\tc := make(chan bool, 1)\n+\ti := InterImpl2{}\n+\tgo func() {\n+\t\ti = InterImpl2{}\n+\t\tc <- true\n+\t}()\n+\t_ = i.Foo // takes the address of i only.\n+\t<-c\n+}\n+\n func TestRacePanicArg(t *testing.T) {\
 \tc := make(chan bool, 1)\n \terr := errors.New(\"err\")\n@@ -1338,6 +1419,17 @@ func TestRaceSliceSlice2(t *testing.T) {\
 \t<-c\n }\n \n+func TestRaceSliceString(t *testing.T) {\n+\tc := make(chan bool, 1)\n+\tx := \"hello\"\n+\tgo func() {\n+\t\tx = \"world\"\n+\t\tc <- true\n+\t}()\n+\t_ = x[2:3]\n+\t<-c\n+}\n+\n // http://golang.org/issue/4453\n func TestRaceFailingSliceStruct(t *testing.T) {\
 \ttype X struct {\

コアとなるコードの解説

src/cmd/gc/racewalk.cの変更点

このファイルは、Goコンパイラがレース検出器のインストゥルメンテーションを行う際に、ASTを走査するロジックを含んでいます。

  • OITABの追加: racewalknode関数にcase OITAB:が追加され、n->leftに対して再帰的にracewalknodeが呼び出されるようになりました。これは、インターフェースの型情報テーブル(ITAB)へのアクセスもレース検出の対象とすることで、インターフェース関連の競合検出をより網羅的に行うための変更です。ITABはインターフェースの動的な型解決に用いられる重要なデータ構造であり、そのアクセスが並行処理下で競合する可能性を考慮に入れています。

  • OCMPIFACEの移動: OCMPIFACEノードが、以前の// unimplementedセクションから// should not appear in AST by nowセクションに移動しました。これは、インターフェースの比較操作が、racewalk.cが処理するASTの段階よりも前のコンパイルフェーズで、より低レベルなメモリ操作(例えば、インターフェースの内部ポインタの比較)に変換されるようになったことを意味します。レース検出器は、この低レベル化された操作に対してインストゥルメンテーションを行うことで、インターフェース比較におけるデータ競合を正確に検出できるようになります。

  • 「低レベル化されるべきノード」の追加: OCALLPART, OCLOSURE, ORANGE, OARRAYLIT, OMAPLIT, OSTRUCTLITといったノードが、// should not appear in AST by nowセクションに追加されました。これらのノードは、Goの言語機能(メソッド値、クロージャ、for-rangeループ、複合リテラル)の高レベルな表現です。コンパイラはこれらのノードを、より基本的な操作(例: for-rangeは通常のループと配列アクセスに、複合リテラルは一連の代入に)に「低レベル化」します。racewalk.cは、これらの高レベルなノードではなく、低レベル化された後の基本的なメモリ操作に対してインストゥルメンテーションを行うべきです。この変更は、レース検出器がコンパイラのパイプラインの適切な段階で、適切な粒度でインストゥルメンテーションを行うためのものです。

  • 「インストゥルメンテーション不要なノード」の拡充: OCHECKNOTNIL, OPARAM, OCLOSUREVAR, ODOTMETHといったノードが、// does not require instrumentationセクションに移動または追加されました。これらのノードは、それ自体が直接的なメモリ読み書きを伴わないか、あるいはその後の操作で既にインストゥルメンテーションの対象となるため、重複や不要な処理を避けるために明示的に除外されました。例えば、OCHECKNOTNILは通常、その後のメモリ読み取り操作の一部として扱われます。OCLOSUREVARは、キャプチャされた変数への不変のポインタを表すため、ポインタ自体へのアクセスは競合を引き起こしません。

これらの変更は、racewalk.cがASTをより正確に解釈し、レース検出器のインストゥルメンテーションをより効率的かつ正確に行うためのものです。これにより、検出器のオーバーヘッドを最小限に抑えつつ、より多くの種類のデータ競合を検出できるようになります。

src/pkg/runtime/race/testdata/mop_test.goの変更点

このファイルは、Goのレース検出器のテストケースを含んでいます。追加されたテストは、racewalk.cの変更が意図した通りに機能し、特定のシナリオでデータ競合を正しく検出できることを検証します。

  • TestRaceIfaceCmpTestRaceIfaceCmpNil: これらのテストは、インターフェースの比較(a == bおよびa == nil)が並行して行われる場合にデータ競合が発生するかどうかを検証します。インターフェースの比較は、内部的にインターフェースが保持する型情報や値へのポインタを読み取る操作を含みます。これらのテストは、インターフェース値が別のゴルーチンによって書き換えられている最中に比較が行われるシナリオをシミュレートし、レース検出器がこの競合を正しく報告することを確認します。

  • TestRaceMethodValue, TestRaceMethodValue2, TestRaceMethodValue3: これらのテストは、メソッド値の生成と、そのレシーバへの並行アクセスがデータ競合を引き起こすかどうかを検証します。メソッド値の生成は、レシーバのメモリを読み取る操作を伴うため、レシーバが別のゴルーチンによって書き換えられている場合に競合が発生する可能性があります。これらのテストは、具体的な値レシーバ、インターフェースレシーバ、および暗黙的なデリファレンスを伴うケースをカバーし、レース検出器がこれらの複雑なシナリオを正しく処理できることを確認します。

  • TestNoRaceMethodValue: このテストは、メソッド値の生成がレシーバのアドレスを暗黙的に取得するだけで、値自体をデリファレンスしない場合に、データ競合が発生しないことを検証します。これは、レース検出器が不必要な競合を報告しないことを確認するための重要なテストであり、検出器の精度を保証します。

  • TestRaceSliceString: このテストは、文字列のスライス操作(x[2:3])が並行して行われる書き込みと競合するかどうかを検証します。文字列はGoでは不変ですが、その基盤となるバイト配列へのアクセスは競合を引き起こす可能性があります。このテストは、文字列操作における潜在的な競合を検出できることを確認します。

これらのテストの追加は、レース検出器がGoのより複雑な言語機能(インターフェース、メソッド値、文字列操作)におけるデータ競合を正確に検出できるようになったことを保証するために不可欠です。これにより、開発者はこれらの機能を使用する際に、より信頼性の高いデータ競合検出の恩恵を受けることができます。

関連リンク

  • Go Race Detector: https://go.dev/doc/articles/race_detector
  • Go Compiler Internals (AST, Loweringなど): Goのコンパイラ内部に関する公式ドキュメントは特定のURLがありませんが、Goのソースコード(特にsrc/cmd/compile以下)や、Goのコンポジタに関するブログ記事やプレゼンテーションが参考になります。
  • Goのインターフェースの内部表現: https://research.swtch.com/interfaces

参考にした情報源リンク