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

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

このコミットは、Go言語のツールチェインの一部であるcmd/link(リンカ)におけるバグ修正に関するものです。具体的には、テストの再現性を損なっていたcloneProg関数の誤った戻り値を修正しています。

コミット

  • コミットハッシュ: 86ac6181459047f8e014e2a5cf0be8a7d9a8f63d
  • 作者: Rick Arnold rickarnoldjr@gmail.com
  • コミット日時: 2014年2月18日(火)17:59:44 -0800

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

https://github.com/golang/go/commit/86ac6181459047f8e014e2a5cf0be8a7d9a8f63d

元コミット内容

cmd/link: change cloneProg to return the cloned value

The code was returning the original value rather than the cloned value
resulting in the tests not being repeatable.

Fixes #7111.

LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/65720045

変更の背景

このコミットの背景には、Go言語のリンカ(cmd/link)のテストにおける再現性の問題がありました。prog_test.goファイル内のcloneProg関数は、Prog構造体のディープコピーを作成することを意図していました。しかし、実装上の誤りにより、この関数はコピーされた新しいProgインスタンスではなく、引数として渡された元のProgインスタンスを返していました。

この問題は、テストがcloneProgによって返された「クローン」された値に対して操作を行うことを期待している場合に、実際には元の値が変更されてしまい、テストの実行結果が不安定になったり、期待通りにならなかったりする原因となっていました。特に、テストが複数回実行される環境や、並行して実行される場合に、この非再現性は深刻な問題を引き起こす可能性があります。

コミットメッセージにあるFixes #7111は、この変更がGoのIssueトラッカーで報告されていたバグ(Issue 7111)を修正するものであることを示しています。このIssueは、cmd/linkのテストが非決定的な振る舞いを示すことに関連していたと考えられます。

前提知識の解説

Go言語のコンパイルプロセスは、ソースコードから実行可能ファイルを生成するまでに複数の段階を経ます。cmd/linkは、このプロセスの最終段階を担うリンカです。コンパイラ(cmd/compile)によって生成されたオブジェクトファイル(.oファイル)やアーカイブファイル(.aファイル)を結合し、必要なライブラリをリンクして、最終的な実行可能バイナリを生成します。

リンカは、プログラム内のシンボル(関数、変数など)を解決し、それらがメモリ上のどこに配置されるかを決定します。また、Goのランタイムに必要な情報(ガベージコレクションのメタデータ、スタック情報など)もバイナリに埋め込みます。

Prog構造体

Progは、Goリンカの内部でプログラムの命令やデータを表現するために使用されるデータ構造です。リンカは、オブジェクトファイルから読み込んだ命令やデータをProg構造体のインスタンスとして表現し、これらを操作して最終的なバイナリを構築します。Prog構造体は、命令のバイトコード、関連するメタデータ、そしてプログラムのセグメント(コードセグメント、データセグメントなど)への参照などを含むことができます。

Segment構造体

Segmentは、実行可能ファイル内の連続したメモリ領域を表すデータ構造です。典型的な実行可能ファイルは、コード(.text)、初期化済みデータ(.data)、初期化されていないデータ(.bss)などのセグメントに分割されます。Prog構造体は、これらのセグメントへの参照を持つことができ、プログラム全体のメモリレイアウトを表現します。

ディープコピーとシャローコピー

  • シャローコピー (Shallow Copy): オブジェクトをコピーする際に、そのオブジェクトが参照している他のオブジェクトはコピーせず、参照だけをコピーする方式です。結果として、元のオブジェクトとコピーされたオブジェクトは、同じ下位のオブジェクトを参照することになります。一方のオブジェクトを変更すると、もう一方のオブジェクトにも影響が及びます。
  • ディープコピー (Deep Copy): オブジェクトをコピーする際に、そのオブジェクトが参照しているすべての下位のオブジェクトも再帰的にコピーする方式です。結果として、元のオブジェクトとコピーされたオブジェクトは完全に独立しており、一方の変更がもう一方に影響を与えることはありません。

cloneProg関数は、Prog構造体のディープコピーを作成することを意図していました。これは、テストにおいて元のProgインスタンスを破壊せずに、そのコピーに対して操作を行う必要があるためです。

技術的詳細

src/cmd/link/prog_test.goファイル内のcloneProg関数は、*Prog型の引数pを受け取り、そのディープコピーを返すことを目的としていました。この関数は、新しいProgインスタンスqを作成し、pのフィールドをqにコピーします。特に、p.SegmentsProgが持つセグメントの配列)についても、cloneSegment関数を使って各セグメントのディープコピーを作成し、それをq.Segmentsに割り当てています。

問題は、この関数が最後にreturn pとしていた点にありました。これは、新しく作成し、ディープコピーされたqを返すのではなく、引数として渡された元のpをそのまま返してしまうことを意味します。

func cloneProg(p *Prog) *Prog {
	q := new(Prog)
	*q = *p // シャローコピーでProgの基本フィールドをコピー
	// ... その他のフィールドのコピー ...
	for i, seg := range p.Segments {
		q.Segments[i] = cloneSegment(seg) // Segmentsのディープコピー
	}
	return p // ここが問題だった
}

この誤った戻り値のため、cloneProgを呼び出したテストコードは、ディープコピーされた独立したProgインスタンスを受け取ったと誤解していました。しかし実際には、元のProgインスタンスが返されていたため、テストがその「クローン」に対して行った変更は、元のProgインスタンスに直接影響を与えていました。これにより、テストの実行順序や並行性によって結果が変わり、テストが非再現性(flaky tests)を示す原因となっていました。

修正は非常にシンプルで、return preturn qに変更することで、正しくディープコピーされたProgインスタンスが返されるようにしました。これにより、テストは独立したデータに対して操作を行うことができ、再現性が確保されるようになりました。

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

--- a/src/cmd/link/prog_test.go
+++ b/src/cmd/link/prog_test.go
@@ -112,7 +112,7 @@ func cloneProg(p *Prog) *Prog {
 	for i, seg := range p.Segments {
 		q.Segments[i] = cloneSegment(seg)
 	}
-	return p
+	return q
 }
 
 // cloneSegment returns a deep copy of seg.

コアとなるコードの解説

変更はsrc/cmd/link/prog_test.goファイル内のcloneProg関数にあります。

元のコードでは、cloneProg関数はProg構造体のディープコピーを作成しようとしていましたが、最後にreturn pと記述されていました。ここでpは関数に渡された元のProgインスタンスへのポインタです。

修正後のコードでは、この行がreturn qに変更されています。ここでqcloneProg関数内で新しく作成され、元のpのディープコピーとして構築されたProgインスタンスへのポインタです。

この一文字の変更により、cloneProg関数は意図通りに、元のProgインスタンスとは独立した新しいProgインスタンス(そのセグメントもディープコピーされている)を返すようになりました。これにより、この関数を使用するテストは、元のデータに影響を与えることなく、コピーされたデータに対して安全に操作を実行できるようになり、テストの再現性が保証されます。

関連リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/86ac6181459047f8e014e2a5cf0be8a7d9a8f63d
  • Go Issue 7111: このコミットが修正したIssueの詳細は、GoのIssueトラッカーで確認できますが、直接的なリンクはコミットメッセージには含まれていません。通常、Fixes #XXXXはGoのIssueトラッカーへの参照です。
  • Gerrit Code Review (CL 65720045): https://golang.org/cl/65720045 (GoプロジェクトのGerritコードレビューシステムにおけるこの変更のリンク)

参考にした情報源リンク

  • Go言語の公式ドキュメント (Goリンカ、コンパイルプロセスに関する一般的な情報)
  • Go言語のソースコード (src/cmd/linkディレクトリ内の関連ファイル)
  • ディープコピーとシャローコピーに関する一般的なプログラミング概念
  • Go Issue Tracker (Issue 7111に関する情報)
  • Gerrit Code Review (Goプロジェクトのコードレビュープロセスに関する情報)I have generated the explanation as requested.