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

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

このコミットは、Goコンパイラの一つであるgccgoが特定のGoコードパターンに対して誤って「同じシンボルの多重定義」エラーを発生させるバグを再現するためのテストケースを追加するものです。このテストケースは、gccgoのコンパイルエラー(Issue #4734)を修正するために導入されました。

コミット

commit 193ff39ac99695ea73da75d0c004e4c6d960e11e
Author: Ian Lance Taylor <iant@golang.org>
Date:   Thu Jan 31 15:59:30 2013 -0800

    test: add test that caused a gccgo compilation failure
    
    Updates #4734.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/7228079

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

https://github.com/golang/go/commit/193ff39ac99695ea73da75d0c004e4c6d960e11e

元コミット内容

test: add test that caused a gccgo compilation failure

このコミットは、gccgoコンパイラでコンパイルエラーを引き起こしたテストを追加するものです。これはIssue #4734に関連する更新です。

変更の背景

Go言語のコンパイラには、公式のgcコンパイラ(Goツールチェインに含まれる標準コンパイラ)と、GCC(GNU Compiler Collection)をバックエンドとして利用するgccgoの二種類が主要なものとして存在します。これらは異なる実装を持つため、Go言語の仕様に準拠しつつも、内部的なシンボル管理やコード生成のロジックが異なる場合があります。

このコミットが追加された背景には、gccgoが特定のGoコードパターン、特に**匿名構造体(anonymous struct)**の定義において、内部的なシンボル名を適切に生成できていなかったというバグが存在しました。具体的には、異なるコンテキストで定義されたにもかかわらず、gccgoが同じ内部シンボル名を割り当ててしまい、結果としてリンカが「同じシンボルの多重定義(multiple definitions of the same symbol)」エラーを報告するという問題が発生していました。

このコミットは、そのgccgoのバグを再現し、将来的な回帰を防ぐためのテストケースとして導入されました。テストが失敗すればバグが再発したことを示し、成功すればバグが修正されたことを確認できます。

前提知識の解説

1. Go言語の匿名構造体(Anonymous Struct)

Go言語では、名前を持たない構造体型をその場で定義して使用することができます。これを匿名構造体と呼びます。

// 例: 匿名構造体を変数として定義
p := struct {
    X int
    Y string
}{X: 10, Y: "hello"}

// 例: 匿名構造体を関数の引数や戻り値の型として使用
func process(data struct{ ID int; Name string }) { /* ... */ }

匿名構造体は、一時的なデータ構造や、特定の関数内でのみ使用されるような場合に便利です。

2. 埋め込みフィールド(Embedded Fields)

Go言語の構造体では、フィールド名なしで型を記述することで、その型のフィールドを構造体に「埋め込む」ことができます。これにより、埋め込まれた型のメソッドやフィールドを、外側の構造体のインスタンスから直接アクセスできるようになります。

type Base struct {
    Value int
}

func (b *Base) GetValue() int {
    return b.Value
}

type Container struct {
    Base // Base型を埋め込み
    Name string
}

func main() {
    c := Container{Base: Base{Value: 100}, Name: "MyContainer"}
    fmt.Println(c.GetValue()) // Baseのメソッドに直接アクセス
}

3. gccgoとシンボル定義

コンパイラは、Goのソースコードを機械語に変換する過程で、関数、変数、型などのプログラム要素に対して内部的な「シンボル」を生成します。これらのシンボルは、リンカが異なるコンパイル単位(オブジェクトファイル)を結合する際に、それぞれの要素を識別するために使用されます。

gccgoはGCCのバックエンドを利用するため、C/C++のコンパイルプロセスと同様に、Goの型や関数をGCCが理解できる中間表現に変換し、最終的にアセンブリコードを生成します。この際、特に匿名型のような名前を持たない要素に対しては、コンパイラが内部的にユニークな名前(マングルされた名前)を生成する必要があります。

「同じシンボルの多重定義」エラーは、リンカが複数のオブジェクトファイル内で同じ名前のシンボル定義を見つけた場合に発生します。これは通常、グローバル変数や関数の名前が重複している場合に起こりますが、コンパイラのバグによって、本来異なるべき匿名型が同じ内部シンボル名を持ってしまうことでも発生し得ます。

技術的詳細

このコミットが追加したテストケースは、gccgoが匿名構造体の内部シンボル名を生成する際のバグを狙ったものです。問題の核心は、異なるコンテキストで定義された匿名構造体が、gccgoによって同じ内部シンボル名を与えられてしまうという点にありました。

具体的には、テストコードには以下の2つの匿名構造体が登場します。

  1. type S2 struct { F struct{ *S1 } } の中の struct{ *S1 }
    • これはS2構造体のフィールドFの型として定義された匿名構造体です。
  2. _ = struct{ *S1 }{} の中の struct{ *S1 }
    • これは関数Fの内部で、一時的な変数として定義された匿名構造体です。

Go言語の仕様上、これら二つの匿名構造体は、たとえ内部のフィールド構成が同じであっても、異なるコンテキストで定義されているため、それぞれが独立した型として扱われます。したがって、コンパイラはこれらに対して異なる内部シンボル名を生成し、リンカが衝突しないようにする必要があります。

しかし、当時のgccgoの実装では、これらの匿名構造体が同じstruct { *S1 }という構造を持っていることから、内部的なシンボル生成ロジックがこれらを区別できず、結果として同じシンボル名を生成してしまっていたと考えられます。これにより、コンパイルされたオブジェクトファイルには同じ名前の型定義シンボルが複数存在することになり、リンカが最終的な実行ファイルを生成する際に多重定義エラーを報告していました。

このバグは、コンパイラが匿名型に対してユニークな識別子を生成するアルゴリズムに不備があったことを示唆しています。例えば、型の構造だけでなく、それが定義されたスコープやパス情報なども含めてシンボル名を生成する必要があったと考えられます。

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

diff --git a/test/fixedbugs/issue4734.go b/test/fixedbugs/issue4734.go
new file mode 100644
index 0000000000..69f66f2129
--- /dev/null
+++ b/test/fixedbugs/issue4734.go
@@ -0,0 +1,21 @@
+// 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.
+
+// Caused gccgo to emit multiple definitions of the same symbol.
+
+package p
+
+type S1 struct{}
+
+func (s *S1) M() {}
+
+type S2 struct {
+	F struct{ *S1 }
+}
+
+func F() {
+	_ = struct{ *S1 }{}\n}

コアとなるコードの解説

このコミットによって追加されたtest/fixedbugs/issue4734.goファイルの内容を以下に解説します。

// compile

このコメントは、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.

標準的なGoのソースコードヘッダで、著作権情報とライセンスに関する記述です。

// Caused gccgo to emit multiple definitions of the same symbol.

このコメントは、このテストケースがgccgoコンパイラで「同じシンボルの多重定義」エラーを引き起こした原因であることを明示しています。

package p

パッケージ宣言です。このコードはpというパッケージに属します。

type S1 struct{}

空の構造体S1を定義しています。これは後続の匿名構造体でポインタ型として埋め込まれる基底型となります。

func (s *S1) M() {}

S1のポインタレシーバを持つメソッドMを定義しています。このメソッド自体は空ですが、S1がメソッドを持つ型であることを示しています。

type S2 struct {
	F struct{ *S1 }
}

ここで最初の重要な匿名構造体が登場します。 S2という構造体を定義しており、その中にFというフィールドがあります。このFの型がstruct{ *S1 }という匿名構造体です。この匿名構造体は、S1へのポインタを埋め込みフィールドとして持っています。

func F() {
	_ = struct{ *S1 }{}\n}

ここで二番目の重要な匿名構造体が登場します。 Fという名前の関数を定義しています。この関数内で、_ = struct{ *S1 }{}という行があります。これは、struct{ *S1 }という別の匿名構造体をその場で定義し、そのゼロ値を生成して、結果を破棄(_に代入)しています。この匿名構造体も、S1へのポインタを埋め込みフィールドとして持っています。

問題点: type S2 struct { F struct{ *S1 } } の中の struct{ *S1 } と、 _ = struct{ *S1 }{} の中の struct{ *S1 } は、 Go言語の型システム上は異なるコンテキストで定義された独立した型です。しかし、gccgoはこれら二つの匿名構造体に対して、内部的に同じシンボル名を生成してしまい、リンカが多重定義エラーを報告していました。このテストケースは、この特定のコードパターンがgccgoのバグをトリガーすることを確認するために設計されています。

関連リンク

  • Go Code Review: https://golang.org/cl/7228079

参考にした情報源リンク

  • Go Code Review: https://golang.org/cl/7228079 (このコミットのレビューページ)
  • Go言語の仕様(匿名構造体、埋め込みフィールドに関するセクション)
    • Go言語の公式ドキュメントや仕様書は、匿名構造体や埋め込みフィールドの挙動を理解する上で参照しました。
  • GCCのコンパイルおよびリンキングに関する一般的な知識
    • コンパイラがシンボルを生成し、リンカがそれらを解決する一般的なプロセスについて参照しました。