[インデックス 12802] ファイルの概要
このコミットは、Go言語のコンパイラの一つであるgccgo
で発生していたバグを特定し、それらを再現するためのテストケースを追加することを目的としています。具体的には、gccgo
が正しくコンパイルできなかったり、誤ったエラーを出力したり、クラッシュしたりするような、Go言語の仕様上は有効なコードに対するテストが追加されています。これにより、gccgo
の堅牢性とGo言語仕様への準拠を向上させることが狙いです。
コミット
commit 373f1a95b0261673e5b2c7aea20d1a479af24713
Author: Ian Lance Taylor <iant@golang.org>
Date: Fri Mar 30 08:42:21 2012 -0700
test: add some tests of valid code that failed with gccgo
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5971044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/373f1a95b0261673e5b2c7aea20d1a479af24713
元コミット内容
test: add some tests of valid code that failed with gccgo
このコミットは、gccgo
で失敗した有効なコードのテストを追加します。
変更の背景
Go言語には、公式のコンパイラであるgc
(Go Compiler)の他に、GCC(GNU Compiler Collection)をベースにしたgccgo
というコンパイラが存在します。gccgo
は、GCCの最適化機能や既存のツールチェインとの統合といった利点を提供しますが、Go言語の仕様の進化やgc
との実装の違いにより、特定の有効なGoコードのコンパイルに失敗したり、予期せぬ動作を引き起こしたりするバグが発生することがあります。
このコミットの背景には、まさにそのようなgccgo
特有のバグが存在していました。開発者は、Go言語の仕様に準拠しているにもかかわらず、gccgo
がクラッシュしたり、誤ったエラーを出したり、正しくコンパイルできなかったりするケースを発見しました。これらの問題を修正するためには、まず問題を再現できるテストケースが必要となります。このコミットは、これらの既知のgccgo
のバグを捕捉し、将来的な回帰を防ぐためのテストスイートを強化することを目的としています。
前提知識の解説
gccgo
gccgo
は、Go言語のプログラムをコンパイルするための代替コンパイラです。Go言語の公式コンパイラであるgc
とは異なり、gccgo
はGCCのフロントエンドとして実装されており、GCCのバックエンドを利用してコードを生成します。これにより、GCCがサポートする様々なアーキテクチャへの対応や、既存のC/C++ライブラリとの連携が容易になるという利点があります。しかし、Go言語の進化に追従し、gc
と同等の機能と安定性を提供するためには、継続的な開発とバグ修正が必要です。
Go言語のテストディレクトリ構造とテストの種類
Go言語の標準ライブラリやツールチェインのテストは、通常test/
ディレクトリ以下に配置されます。このディレクトリには、言語仕様の様々な側面やコンパイラの挙動を検証するための多種多様なテストが含まれています。
test/blank.go
: このファイルは、Go言語のブランク識別子(_
)の挙動に関するテストを含む、一般的なテストファイルの一つです。ブランク識別子は、変数や関数の戻り値などを意図的に破棄する場合に使用されます。test/fixedbugs/
: このディレクトリは、過去に発見され修正された特定のバグを再現するためのテストケースを格納するために使用されます。これにより、修正されたバグが将来のバージョンで再発しないことを保証します(回帰テスト)。
テストファイルのディレクティブ
Go言語のテストファイルには、特別なコメント行で始まるディレクティブが記述されることがあります。これらは、テストの実行方法や期待される結果をコンパイラやテストランナーに指示します。
// compile
: このディレクティブは、ファイルがコンパイル可能であることをテストします。コンパイルエラーが発生した場合、テストは失敗とみなされます。// run
: このディレクティブは、ファイルがコンパイルされ、実行可能であり、かつ実行時にパニックを起こさずに正常終了することをテストします。通常、main
関数が含まれ、特定の条件が満たされない場合にpanic
を発生させるロジックが含まれます。
ブランク識別子(_
)
Go言語におけるブランク識別子(_
)は、値を使用しないことを明示的に示すために使用されます。例えば、関数の戻り値の一部を無視する場合や、メソッドのレシーバ変数を参照しない場合などに利用されます。このコミットでは、メソッドのレシーバにブランク識別子を使用するケースがtest/blank.go
で修正されています。
技術的詳細
このコミットでは、主に以下の5つのファイルが変更または新規追加されています。それぞれの変更は、gccgo
の特定のバグに対処するためのものです。
-
test/blank.go
の変更:func (TI) M(x int, y int)
のメソッドレシーバがfunc (_ TI) M(x int, y int)
に変更されました。これは、メソッドレシーバの変数名が使用されない場合に、ブランク識別子を使用するというGoの慣例に合わせた変更です。gccgo
がこの構文を正しく扱えることを確認するための変更である可能性があります。
-
test/fixedbugs/bug430.go
の新規追加:- このテストケースは、
[2][]int
型のフィールドを持つ構造体S
と、同じ型の戻り値を持つ関数F
を定義しています。 main
関数内で、[]S
型のスライスa
の要素a[0].f
にF()
の戻り値を代入しようとしています。- コメントに「
gccgo crashed compiling this.
」とあるように、gccgo
はこのコードのコンパイル時にクラッシュしていました。これは、多次元スライスを含む構造体の初期化や代入に関するgccgo
のバグを示唆しています。
- このテストケースは、
-
test/fixedbugs/bug431.go
の新規追加:- このテストケースは、
1<<63 - 1
という大きな定数C
を定義しています。これはint64
の最大値に相当します。 var V = F(int64(C) / 1e6)
という行で、この大きな定数をint64
にキャストし、1e6
(100万)で割った結果を関数F
に渡し、その結果を変数V
に代入しています。- コメントに「
gccgo gave an invalid error ("floating point constant truncated to integer") compiling this.
」とあるように、gccgo
はこのコードのコンパイル時に「浮動小数点定数が整数に切り捨てられました」という誤ったエラーを出力していました。これは、大きな整数定数の扱い、特に浮動小数点数との演算におけるgccgo
のバグを示しています。Go言語では、型なし定数は必要に応じて適切な型に変換されますが、gccgo
がこの変換を誤って解釈していた可能性があります。
- このテストケースは、
-
test/fixedbugs/bug432.go
の新規追加:- このテストケースは、空のインターフェース
I
を定義し、そのインターフェース型をフィールドに持つ匿名構造体struct{ I }
を変数v
として宣言しています。 - コメントに「
gccgo crashed compiling this.
」とあるように、gccgo
はこのコードのコンパイル時にクラッシュしていました。これは、インターフェース型を匿名フィールドとして持つ構造体の宣言に関するgccgo
のバグを示唆しています。
- このテストケースは、空のインターフェース
-
test/fixedbugs/bug433.go
の新規追加:- このテストケースは、構造体
S
のフィールドi1
,i2
,i3
を、定義順とは異なる順序で初期化するケースをテストしています(i1: v(0), i3: v(1), i2: v(2)
)。 v
関数は、グローバル変数G
の値をチェックし、G
をインクリメントしてその新しい値を返します。これにより、v
関数が呼び出される順序を追跡できます。- コメントに「
Test that initializing struct fields out of order still runs functions in the right order. This failed with gccgo.
」とあるように、gccgo
は構造体フィールドが定義順と異なる順序で初期化された場合に、初期化式内の関数呼び出しの順序を誤って処理していました。Go言語の仕様では、構造体リテラルの要素は記述された順序で評価されるため、このテストはgccgo
がこの仕様に準拠していないことを示していました。
- このテストケースは、構造体
コアとなるコードの変更箇所
test/blank.go
: メソッドレシーバの変数名をブランク識別子に変更。test/fixedbugs/bug430.go
: 新規追加。多次元スライスを含む構造体の代入でgccgo
がクラッシュするバグのテスト。test/fixedbugs/bug431.go
: 新規追加。大きな整数定数と浮動小数点数の演算でgccgo
が誤ったエラーを出すバグのテスト。test/fixedbugs/bug432.go
: 新規追加。インターフェース型を匿名フィールドに持つ構造体の宣言でgccgo
がクラッシュするバグのテスト。test/fixedbugs/bug433.go
: 新規追加。構造体フィールドの順不同初期化における関数呼び出し順序のバグのテスト。
コアとなるコードの解説
test/blank.go
の変更
--- a/test/blank.go
+++ b/test/blank.go
@@ -113,7 +113,7 @@ type I interface {
type TI struct{}
-func (TI) M(x int, y int) {
+func (_ TI) M(x int, y int) {
if x != y {
println("invalid M call:", x, y)
panic("bad M")
この変更は、TI
型のメソッドM
のレシーバを匿名化しています。元のコードではレシーバの変数名が省略されていましたが、Goの慣例として、レシーバ変数がメソッド内で使用されない場合は明示的にブランク識別子_
を使用します。この変更は、gccgo
がこのような構文を正しく処理できることを確認するためのものです。
test/fixedbugs/bug430.go
// compile
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// gccgo crashed compiling this.
package main
type S struct {
f [2][]int
}
func F() (r [2][]int) {
return
}
func main() {
var a []S
a[0].f = F()
}
このテストは、[2][]int
という配列とスライスの組み合わせを含む型S
のフィールドf
への代入がgccgo
でクラッシュする問題を示しています。a[0].f = F()
という行で、F()
が返す[2][]int
型の値をa[0].f
に代入しようとしています。gccgo
がこの複雑な型の代入を正しく処理できなかったことが示唆されます。
test/fixedbugs/bug431.go
// compile
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// gccgo gave an invalid error ("floating point constant truncated to
// integer") compiling this.
package p
const C = 1<<63 - 1
func F(i int64) int64 {
return i
}
var V = F(int64(C) / 1e6)
このテストは、int64
の最大値に近い大きな定数C
を定義し、それを1e6
(浮動小数点数)で割る演算を行っています。Go言語では、型なし定数は必要に応じて型推論されますが、gccgo
がこの演算の過程で「浮動小数点定数が整数に切り捨てられました」という誤ったエラーを出力していました。これは、gccgo
が大きな整数定数と浮動小数点数の混合演算を正しく扱えていなかったことを示しています。
test/fixedbugs/bug432.go
// compile
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// gccgo crashed compiling this.
package p
var v struct{ I }
type I interface{}
このテストは、空のインターフェースI
を定義し、そのインターフェース型を匿名フィールドとして持つ構造体struct{ I }
を変数v
として宣言しています。gccgo
がこの構文のコンパイル時にクラッシュしていたことから、インターフェース型を匿名フィールドとして含む構造体の処理に問題があったことがわかります。
test/fixedbugs/bug433.go
// run
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test that initializing struct fields out of order still runs
// functions in the right order. This failed with gccgo.
package main
type S struct {
i1, i2, i3 int
}
var G int
func v(i int) int {
if i != G {
panic(i)
}
G = i + 1
return G
}
func F() S {
return S{
i1: v(0),
i3: v(1),
i2: v(2),
}
}
func main() {
s := F()
if s != (S{1, 3, 2}) {
panic(s)
}
}
このテストは、構造体S
のフィールドi1
, i2
, i3
を、定義順とは異なる順序(i1
, i3
, i2
)で初期化する際に、初期化式内の関数呼び出しv(0)
, v(1)
, v(2)
がGo言語の仕様通りに記述順で評価されることを検証しています。v
関数はグローバル変数G
を使って呼び出し順序を追跡し、期待される順序でなければパニックを起こします。gccgo
はこの順序を誤って処理していたため、このテストはgccgo
のバグを露呈させました。最終的にs != (S{1, 3, 2})
というアサーションで、フィールドが正しく初期化されたかを確認しています。
関連リンク
- Go CL 5971044: https://golang.org/cl/5971044
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/12802.txt
- Go言語の公式ドキュメント (ブランク識別子、定数、構造体リテラルなどに関する一般的な情報)
- GCCGoに関する一般的な情報 (必要に応じてWeb検索)