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

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

このコミットは、Go言語のコンパイラの一つであるgccgoが特定のGoコードを誤ってコンパイルする問題を修正するために、複数の新しいテストケースを追加するものです。これらのテストケースは、gccgoがGo言語のセマンティクスを正しく解釈できていなかった、いくつかの具体的なシナリオを浮き彫りにしています。

コミット

commit 9bea6f3b2cf5efbf4df65293498dd7104c3f3f0f
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Dec 10 10:47:30 2013 -0800

    test: add some test cases that were miscompiled by gccgo
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/40310043

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

https://github.com/golang/go/commit/9bea6f3b2cf5efbf4df65293498dd7104c3f3f0f

元コミット内容

test: add some test cases that were miscompiled by gccgo

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/40310043

変更の背景

このコミットの主な背景は、Go言語の公式コンパイラであるgc(Goコンパイラ)とは異なる実装であるgccgoが、特定のGoコードパターンを誤ってコンパイルするという問題が存在したことです。gccgoはGCC(GNU Compiler Collection)のフロントエンドとしてGo言語をサポートしており、Go言語の仕様に厳密に従う必要がありますが、実装の複雑さから、時に仕様の解釈や最適化においてバグが発生することがあります。

このコミットで追加されたテストケースは、具体的に以下の3つの異なる誤コンパイルのシナリオを対象としています。

  1. 前方宣言された型のインポートの誤処理 (bug480): 異なるパッケージ間で型が前方宣言され、それがインポートされる際にgccgoが正しく処理できない問題。
  2. 文字列からスライスへの変換とインデックスアクセス (bug481): 文字列をバイトスライスやruneスライスに変換し、その結果に対して直接インデックスアクセスを行う際に、gccgoがコンパイルエラーを発生させる問題。
  3. 複合リテラル内のフィールド名とグローバル変数の名前衝突 (bug482): 構造体リテラル内でフィールド名として使用されている識別子が、同時に初期化中の構造体に依存するグローバル変数名としても存在する場合に、gccgoが「変数の初期化子が自身を参照している」という誤ったエラーを報告する問題。
  4. エクスポートされていない埋め込み構造体のハッシュ関数 (issue6789): エクスポートされた構造体にエクスポートされていない構造体が埋め込まれている場合、gccgoがその埋め込み構造体のハッシュ関数を見つけられない問題。これは特にマップのキーとして使用される場合などに問題となります。

これらの問題は、Go言語のコードがgccgoでコンパイルされた際に予期せぬ動作やコンパイルエラーを引き起こす可能性があり、Goプログラムの移植性や信頼性を損なうものでした。そのため、これらのバグを特定し、修正を促すために、具体的な再現テストケースがGoのテストスイートに追加されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラに関する前提知識が必要です。

  • Go言語のパッケージとインポート: Go言語では、コードはパッケージに分割され、他のパッケージの型や関数を使用するにはimport文を使用します。パッケージ間の依存関係や、循環参照の回避はGoの設計において重要です。
  • 前方宣言 (Forward Declaration): Go言語にはC/C++のような明示的な前方宣言の構文はありませんが、型や関数の定義順序がコンパイラによって解決される過程で、概念的に前方参照されるケースが存在します。特に異なるパッケージ間で型が相互に参照し合うような複雑なケースでは、コンパイラが正しく依存関係を解決する必要があります。
  • インターフェース (Interface): Goのインターフェースは、メソッドのシグネチャの集合を定義します。型がそのインターフェースのすべてのメソッドを実装していれば、そのインターフェースを満たします。インターフェース型は、異なる具象型を抽象的に扱うために使用されます。
  • 構造体 (Struct): 構造体は、異なる型のフィールドをまとめた複合データ型です。Goでは、構造体にメソッドを関連付けることで、オブジェクト指向的な振る舞いを実現します。
  • 埋め込み (Embedding): Goの構造体は、他の構造体やインターフェースを匿名フィールドとして埋め込むことができます。これにより、埋め込まれた型のフィールドやメソッドが、埋め込み元の構造体のフィールドやメソッドであるかのように直接アクセスできるようになります。これは、継承ではなく「コンポジション(合成)」によるコードの再利用を促進するGoの重要な機能です。
  • 文字列とスライス (String and Slice): Goの文字列は不変のバイトシーケンスです。文字列を[]byte[]runeに変換することで、その内容を可変のスライスとして操作できます。[]byte(s)[0]のような操作は、文字列の最初のバイト(またはrune)にアクセスする一般的な方法です。
  • 複合リテラル (Composite Literal): 構造体、配列、スライス、マップなどの複合型を初期化するための構文です。S{F: 1}のように、フィールド名と値を指定して構造体を初期化できます。
  • コンパイラ (Compiler): ソースコードを機械語や中間コードに変換するプログラムです。Go言語には主に以下の2つのコンパイラ実装があります。
    • gc (Go Compiler): Goチームが開発している公式のコンパイラ。高速なコンパイルと最適化が特徴です。
    • gccgo: GCCのフロントエンドとして実装されたGoコンパイラ。GCCの最適化パスを利用できる利点がありますが、Go言語のセマンティクスをGCCの内部表現にマッピングする際に複雑さが生じることがあります。
  • 誤コンパイル (Miscompilation): コンパイラがソースコードを誤って機械語に変換し、その結果、プログラムが意図しない動作をしたり、実行時にクラッシュしたりするバグのことです。
  • テスト駆動開発 (Test-Driven Development): バグを修正したり新機能を追加したりする際に、まずその問題や機能の振る舞いを再現するテストケースを記述し、そのテストが失敗することを確認してから、コードの修正や実装を行う開発手法。このコミットは、まさにこのアプローチでバグを修正するためのテストケースを追加しています。

技術的詳細

このコミットで追加された各テストケースは、gccgoの特定の誤コンパイルのシナリオを詳細に示しています。

test/fixedbugs/bug480.go (および a.go, b.go)

このテストケースは、異なるパッケージ間で型が前方宣言され、それがインポートされる際のgccgoの誤処理を対象としています。

  • a.goでは、インターフェースSと構造体T、そしてerror型を埋め込んだ構造体Uが定義されています。TSインターフェースを埋め込んでおり、SインターフェースはT型を返すメソッドF()を持っています。これは、型が相互に参照し合うような複雑な前方参照のシナリオを作り出しています。
  • b.goでは、aパッケージをインポートし、a.T型のグローバル変数tを宣言しています。また、a.U{}を返す関数F()も定義しています。
  • bug480.goのコメントには「Gccgo mishandled an import of a forward declared type.」と明記されており、gccgoがこのような複雑な型定義とインポートの組み合わせを正しく解決できなかったことを示唆しています。具体的には、a.Ta.Sインターフェースのメソッドシグネチャ内で参照されているため、gccgoa.Tの完全な定義を解決する前にa.Sを処理しようとした際に問題が発生した可能性があります。

test/fixedbugs/bug481.go

このテストケースは、文字列からスライスへの変換と、その結果に対するインデックスアクセスに関するgccgoのコンパイルエラーを修正します。

  • F1(s string) byte関数は[]byte(s)[0]を返し、F2(s string) rune関数は[]rune(s)[0]を返します。
  • コメントには「Returning an index into a conversion from string to slice caused a compilation error when using gccgo.」とあります。これは、gccgoが文字列からスライスへの一時的な変換結果に対して直接インデックスアクセスを行う最適化や中間表現の生成において、何らかのバグを抱えていたことを示しています。Go言語の仕様では、このような操作は合法であり、期待通りに動作するべきです。

test/fixedbugs/bug482.go

このテストケースは、複合リテラル内のフィールド名とグローバル変数の名前衝突に関するgccgoの誤ったエラー報告を修正します。

  • 構造体SFというint型のフィールドを持ちます。
  • グローバル変数VS{F: 1}という複合リテラルで初期化されます。
  • 別のグローバル変数FV.Fで初期化されます。
  • コメントには「Using the same name for a field in a composite literal and for a global variable that depends on the variable being initialized caused gccgo to erroneously report "variable initializer refers to itself".」とあります。
  • この問題は、gccgoが変数の初期化順序とスコープ解決のルールを誤って適用したために発生したと考えられます。Go言語では、パッケージレベルの変数は宣言順に初期化され、初期化式が他のパッケージレベルの変数に依存する場合、その依存関係が解決されてから初期化が行われます。Vの初期化式S{F: 1}の中のFは構造体SのフィールドFを指しており、グローバル変数Fとは異なるスコープにあります。しかし、gccgoはこれをグローバル変数Fへの自己参照と誤解釈し、循環依存のエラーを報告してしまったようです。

test/fixedbugs/issue6789.go (および a.go, b.go)

このテストケースは、エクスポートされていない埋め込み構造体のハッシュ関数に関するgccgoの問題を修正します。

  • a.goでは、エクスポートされていない構造体unexportedと、それを埋め込んだエクスポートされた構造体Structが定義されています。
  • b.goでは、aパッケージをインポートし、a.Structを基にした型sを定義しています。
  • issue6789.goのコメントには「Issue 6789: gccgo failed to find the hash function for an unexported struct embedded in an exported struct.」とあります。
  • Go言語では、マップのキーとして使用される型はハッシュ可能である必要があります。構造体がハッシュ可能であるためには、そのすべてのフィールドがハッシュ可能である必要があります。このケースでは、Structunexportedを埋め込んでいるため、Structのハッシュ関数を生成する際に、gccgounexported構造体のハッシュ関数を正しく見つけられなかったか、生成できなかったと考えられます。これは、エクスポートされていない型に対する内部的な処理が不完全であったことを示唆しています。

これらのテストケースは、gccgoがGo言語の型システム、スコープルール、およびランタイムのセマンティクスを完全に理解し、正しく実装することの複雑さを示しています。

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

このコミットは、既存のコードを変更するものではなく、gccgoの誤コンパイルを再現するための新しいテストファイルを追加しています。

追加されたファイルは以下の通りです。

  • test/fixedbugs/bug480.dir/a.go
  • test/fixedbugs/bug480.dir/b.go
  • test/fixedbugs/bug480.go
  • test/fixedbugs/bug481.go
  • test/fixedbugs/bug482.go
  • test/fixedbugs/issue6789.dir/a.go
  • test/fixedbugs/issue6789.dir/b.go
  • test/fixedbugs/issue6789.go

これらのファイルは、それぞれ特定のgccgoのバグを再現するように設計されたGoのソースコードを含んでいます。

コアとなるコードの解説

各テストファイルは、gccgoが誤ってコンパイルする特定のGo言語の構文やセマンティクスを意図的に含んでいます。

test/fixedbugs/bug480.dir/a.go

// Copyright 2013 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.

package a

type S interface{
	F() T
}

type T struct {
	S
}

type U struct {
	error
}

このファイルは、パッケージa内でインターフェースS、構造体T、構造体Uを定義しています。SインターフェースのメソッドF()T型を返すように定義されており、T構造体はSインターフェースを埋め込んでいます。これは、型が相互に参照し合う複雑な前方参照のシナリオを作り出しています。U構造体は組み込みのerrorインターフェースを埋め込んでいます。

test/fixedbugs/bug480.dir/b.go

// Copyright 2013 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.

package b

import "./a"

var t a.T

func F() error {
	return a.U{}
}

このファイルは、パッケージb内でパッケージaをインポートし、a.T型のグローバル変数tを宣言しています。また、a.U{}を返す関数F()も定義しています。gccgoは、aパッケージの複雑な型定義をインポートする際に問題を起こしていました。

test/fixedbugs/bug480.go

// compiledir

// Copyright 2013 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 mishandled an import of a forward declared type.

package ignored

このファイルは、compiledirディレクティブを持つテストスクリプトであり、bug480.dirディレクトリ内のa.gob.goをコンパイルして、gccgoが前方宣言された型のインポートを正しく処理できるようになったかを確認します。

test/fixedbugs/bug481.go

// compile

// Copyright 2013 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.

// Returning an index into a conversion from string to slice caused a
// compilation error when using gccgo.

package p

func F1(s string) byte {
	return []byte(s)[0]
}

func F2(s string) rune {
	return []rune(s)[0]
}

このファイルは、文字列をバイトスライスまたはruneスライスに変換し、その結果に対して直接インデックスアクセスを行う関数を定義しています。gccgoは以前、この操作でコンパイルエラーを発生させていました。compileディレクティブは、このファイルがコンパイル可能であることをテストします。

test/fixedbugs/bug482.go

// compile

// Copyright 2013 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.

// Using the same name for a field in a composite literal and for a
// global variable that depends on the variable being initialized
// caused gccgo to erroneously report "variable initializer refers to
// itself".

package p

type S struct {
	F int
}

var V = S{F: 1}

var F = V.F

このファイルは、構造体リテラル内のフィールド名と、初期化中の変数に依存するグローバル変数の名前が同じである場合に、gccgoが誤った自己参照エラーを報告する問題を再現します。Vの初期化におけるFは構造体のフィールドを指し、その後のグローバル変数Fとは異なるスコープにあります。

test/fixedbugs/issue6789.dir/a.go

// Copyright 2013 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.

package a

type unexported struct {
        a int
        b bool
}

type Struct struct {
        unexported
}

このファイルは、エクスポートされていない構造体unexportedと、それを埋め込んだエクスポートされた構造体Structを定義しています。

test/fixedbugs/issue6789.dir/b.go

// Copyright 2013 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.

package main

import "./a"

type s a.Struct

func main() {
}

このファイルは、aパッケージをインポートし、a.Structを基にした型sを定義しています。gccgoは、a.Structが埋め込んでいるエクスポートされていないunexported構造体のハッシュ関数を正しく処理できませんでした。

test/fixedbugs/issue6789.go

// rundir

// Copyright 2013 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.

// Issue 6789: gccgo failed to find the hash function for an
// unexported struct embedded in an exported struct.

package ignored

このファイルは、rundirディレクティブを持つテストスクリプトであり、issue6789.dirディレクトリ内のコードを実行して、gccgoがエクスポートされていない埋め込み構造体のハッシュ関数を正しく処理できるようになったかを確認します。

これらのテストケースは、Go言語のコンパイラが直面する複雑なエッジケースを浮き彫りにし、コンパイラの堅牢性を高める上で不可欠な役割を果たします。

関連リンク

参考にした情報源リンク