[インデックス 10935] ファイルの概要
このコミットは、Go言語のコマンドラインツールであるgo
コマンドの一部、具体的にはテスト関連のフラグ処理を司るsrc/cmd/go/testflag.go
ファイルに対する修正です。このファイルは、go test
コマンドが受け取る引数を解析し、テストバイナリに渡すべき引数とそうでないものを区別する役割を担っています。
コミット
commit 16a2d2617fa96dc85359d9919e6dceff1413feab
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Dec 21 21:20:17 2011 +0900
cmd/go: avoid infinite loop with package specific flags
R=rsc
CC=golang-dev
https://golang.org/cl/5505053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/16a2d2617fa96dc85359d9919e6dceff1413feab
元コミット内容
cmd/go: avoid infinite loop with package specific flags
R=rsc
CC=golang-dev
https://golang.org/cl/5505053
変更の背景
このコミットは、go test
コマンドがパッケージ固有のフラグ(テストバイナリ自体に渡されるべきフラグ)を処理する際に発生していた無限ループのバグを修正するために行われました。
go test
コマンドは、Goのテストフレームワークを実行するための主要なツールです。このコマンドは、自身のフラグ(例: -v
、-run
)と、テストバイナリに直接渡されるべきフラグ(例: -test.v
、-test.run
、またはユーザー定義のフラグ)の両方を処理する必要があります。testflag.go
内のtestFlags
関数は、この引数の振り分けを担当していました。
元の実装では、go test
コマンドが認識しない、しかしテストバイナリに渡されるべき引数(パッケージ固有のフラグ)が与えられた場合、その引数をtestFlags
関数が処理中のargs
スライスに再度追加していました。これは、for
ループがargs
スライスの長さに依存しているため、ループ内でスライスが無限に拡張され、結果として無限ループに陥る原因となっていました。
前提知識の解説
go test
コマンドとフラグ処理
go test
コマンドは、Goプロジェクトのテストを実行するためのコマンドです。このコマンドは、大きく分けて2種類のフラグを扱います。
go test
コマンド自身のフラグ: 例えば、-v
(詳細出力)、-run <regexp>
(特定のテストの実行)、-count <n>
(テストの実行回数) などがあります。これらはgo test
コマンド自体が解釈し、テストの実行方法を制御します。- テストバイナリに渡されるフラグ:
go test
コマンドは、テストコードをコンパイルしてテストバイナリを生成し、それを実行します。このテストバイナリも独自のフラグを持つことができます。Goの標準テストパッケージtesting
が提供するフラグ(例:-test.v
、-test.run
)や、ユーザーがテストコード内でflag
パッケージなどを使って定義したカスタムフラグがこれに該当します。
go test
コマンドは、引数を解析する際に、どちらの種類のフラグであるかを判断し、適切なものだけをテストバイナリに渡す必要があります。この振り分け処理がsrc/cmd/go/testflag.go
のtestFlags
関数で行われています。
Go言語のスライスとappend
関数
Go言語のスライスは、可変長シーケンスを表現するためのデータ構造です。スライスは内部的に配列を参照しており、その長さと容量を持っています。
append
関数は、スライスに要素を追加するために使用されます。append
は新しいスライスを返すことがあり、これは元のスライスの容量が不足した場合に、より大きな新しい基底配列が割り当てられ、そこに要素がコピーされるためです。
このコミットの文脈では、args = append(args, arg)
という操作が重要です。これは、args
スライスにarg
を追加し、その結果を再びargs
に代入しています。もしargs
がループのイテレーション対象である場合、この操作はループの終了条件に影響を与え、無限ループを引き起こす可能性があります。
技術的詳細
src/cmd/go/testflag.go
内のtestFlags
関数は、go test
コマンドに与えられた引数args
をループで処理します。この関数は、各引数がgo test
コマンド自身のフラグであるか、それともテストバイナリに渡すべきフラグであるかをtestFlag
関数を使って判定します。
元のコードの関連部分:
func testFlags(args []string) (passToTest []string) {
for i := 0; i < len(args); i++ {
arg := args[i] // 現在の引数を取得
f, value, extraWord := testFlag(args, i)
if f == nil { // go testコマンドのフラグではない場合
args = append(args, arg) // !!! 問題の箇所 !!!
continue
}
// ... フラグの処理 ...
}
return
}
問題はif f == nil
のブロック内にありました。testFlag
関数がnil
を返した場合、それは現在の引数arg
がgo test
コマンド自身のフラグではないことを意味します。この場合、その引数はテストバイナリに渡されるべきものと判断されます。しかし、元のコードではargs = append(args, arg)
として、この引数を現在処理中のargs
スライスの末尾に再追加していました。
for
ループはlen(args)
を終了条件としています。ループ内でargs
に要素が追加されると、len(args)
が増加し、ループの終了条件が後退します。もしtestFlag
が常にnil
を返すような引数(例えば、go test
が認識しないがテストバイナリが認識するカスタムフラグ)が与えられた場合、その引数は無限にargs
スライスに追加され続け、結果としてループが終了しなくなる、つまり無限ループが発生していました。
このコミットは、この問題を解決するために、テストバイナリに渡すべき引数をargs
スライスではなく、関数の戻り値として定義されている別のスライスpassToTest
に追加するように修正しました。
コアとなるコードの変更箇所
--- a/src/cmd/go/testflag.go
+++ b/src/cmd/go/testflag.go
@@ -80,10 +80,9 @@ var testFlagDefn = []*testFlagSpec{
// test.out's arguments.
func testFlags(args []string) (passToTest []string) {
for i := 0; i < len(args); i++ {
-\t\targ := args[i]
\t\tf, value, extraWord := testFlag(args, i)
\t\tif f == nil {
-\t\t\targs = append(args, arg)
+\t\t\tpassToTest = append(passToTest, args[i])
\t\t\tcontinue
\t\t}
\t\tswitch f.name {
コアとなるコードの解説
変更はtestFlags
関数内の以下の1行です。
- 変更前:
args = append(args, arg)
- 変更後:
passToTest = append(passToTest, args[i])
この修正により、testFlag
関数がnil
を返した場合(つまり、引数がgo test
コマンド自身のフラグではない場合)、その引数args[i]
は、testFlags
関数の戻り値として定義されているpassToTest
スライスに追加されるようになりました。
passToTest
スライスは、testFlags
関数のローカル変数であり、for
ループのイテレーション対象であるargs
スライスとは独立しています。したがって、passToTest
に要素を追加してもargs
スライスの長さは変化せず、for
ループの終了条件に影響を与えることはありません。これにより、無限ループが回避され、go test
コマンドがパッケージ固有のフラグを正しく処理できるようになりました。
また、arg := args[i]
という行が削除されていますが、これはargs[i]
を直接passToTest
に渡すことで、一時変数arg
が不要になったためです。これはコードの簡潔化にも貢献しています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/16a2d2617fa96dc85359d9919e6dceff1413feab
- Go CL (Change List): https://golang.org/cl/5505053
参考にした情報源リンク
- Go言語の公式ドキュメント(
go test
コマンド、スライス、append
関数に関する一般的な情報) - Go言語のソースコード(
src/cmd/go/testflag.go
のコンテキスト理解のため) - Web検索("golang cmd/go testflag.go infinite loop")