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

[インデックス 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の特定のバグに対処するためのものです。

  1. test/blank.go の変更:

    • func (TI) M(x int, y int) のメソッドレシーバが func (_ TI) M(x int, y int) に変更されました。これは、メソッドレシーバの変数名が使用されない場合に、ブランク識別子を使用するというGoの慣例に合わせた変更です。gccgoがこの構文を正しく扱えることを確認するための変更である可能性があります。
  2. test/fixedbugs/bug430.go の新規追加:

    • このテストケースは、[2][]int 型のフィールドを持つ構造体Sと、同じ型の戻り値を持つ関数Fを定義しています。
    • main関数内で、[]S型のスライスaの要素a[0].fF()の戻り値を代入しようとしています。
    • コメントに「gccgo crashed compiling this.」とあるように、gccgoはこのコードのコンパイル時にクラッシュしていました。これは、多次元スライスを含む構造体の初期化や代入に関するgccgoのバグを示唆しています。
  3. 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がこの変換を誤って解釈していた可能性があります。
  4. test/fixedbugs/bug432.go の新規追加:

    • このテストケースは、空のインターフェースIを定義し、そのインターフェース型をフィールドに持つ匿名構造体struct{ I }を変数vとして宣言しています。
    • コメントに「gccgo crashed compiling this.」とあるように、gccgoはこのコードのコンパイル時にクラッシュしていました。これは、インターフェース型を匿名フィールドとして持つ構造体の宣言に関するgccgoのバグを示唆しています。
  5. 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})というアサーションで、フィールドが正しく初期化されたかを確認しています。

関連リンク

参考にした情報源リンク

  • コミット情報: /home/orange/Project/comemo/commit_data/12802.txt
  • Go言語の公式ドキュメント (ブランク識別子、定数、構造体リテラルなどに関する一般的な情報)
  • GCCGoに関する一般的な情報 (必要に応じてWeb検索)