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

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

このコミットは、Goコンパイラの一つであるgccgoにおいて、構造体(struct)の型等価性判定に関するバグを露呈させるためのテストケースを追加するものです。具体的には、リンク時に未定義シンボルエラーを引き起こすような状況を再現し、gccgoの型システムにおける一貫性の問題を浮き彫りにすることを目的としています。

コミット

commit 1325732ab955f84a8cc8a6dbb8c39c8e0c3d7c30
Author: Ian Lance Taylor <iant@golang.org>
Date:   Thu Sep 19 15:20:39 2013 -0700

    test: add a test that causes gccgo to get a failure at link time
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/13788043

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

https://github.com/golang/go/commit/1325732ab955f84a8cc8a6dbb8c39c8e0c3d7c30

元コミット内容

test: add a test that causes gccgo to get a failure at link time

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

変更の背景

このコミットが追加された背景には、gccgoコンパイラがGoの構造体型を比較する際の一貫性の欠如がありました。Go言語では、型は非常に重要であり、特に異なるパッケージ間で型がどのように扱われるかは、プログラムの正しさとリンクの成功に直結します。

問題は、gccgoが特定の状況下で構造体の型等価性を正しく判断できず、その結果、リンク時に「未定義シンボル」エラーが発生するというものでした。これは、コンパイラがコンパイル時にはある型を認識しているにもかかわらず、リンカがその型に関連するシンボルを見つけられない、あるいは異なる型として扱ってしまうために起こります。このようなバグは、Goプログラムのビルドプロセスにおいて予期せぬ失敗を引き起こし、開発者を混乱させる可能性があります。

このコミットは、この特定の問題を再現し、将来的に回帰しないことを保証するためのテストケースを導入することで、gccgoの堅牢性を向上させることを目的としています。

前提知識の解説

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

  1. Goの型システムと構造体(Structs):

    • Goは静的型付け言語であり、すべての変数には型があります。
    • 構造体は、異なる型のフィールドをまとめることができる複合型です。
    • Goにおける型の等価性(Type Identity)は厳密に定義されています。2つの型が等しいとされるのは、それらが同じ型リテラルから構築された場合、または型定義によって同じ基底型を持つ場合など、特定のルールに基づきます。構造体の場合、フィールドの順序、名前、型がすべて一致する必要があります。
    • 埋め込み(Embedding): Goの構造体は、他の構造体を匿名フィールドとして埋め込むことができます。これにより、埋め込まれた構造体のフィールドやメソッドが、外側の構造体のものとして「昇格」されます。これは継承とは異なり、コンポジションの一種です。
  2. unsafe.Sizeof:

    • unsafeパッケージは、Goの型安全性とメモリ安全性の保証を一部バイパスする機能を提供します。
    • unsafe.Sizeofは、Goの組み込み関数で、任意の式のメモリ上のサイズ(バイト単位)を返します。この関数はコンパイル時に評価される定数式でなければなりません。
    • unsafe.Sizeofが構造体に対して使用される場合、その構造体のレイアウトとパディングを考慮した正確なサイズが計算されます。この計算には、構造体のフィールドの型情報が不可欠です。
  3. コンパイルとリンクのプロセス:

    • Goプログラムは通常、複数のソースファイルから構成され、これらはまず個別にコンパイルされてオブジェクトファイル(.oファイルなど)になります。
    • コンパイル段階では、各ソースファイル内の型情報やシンボルが生成されます。
    • 次に、リンカがこれらのオブジェクトファイルを結合し、実行可能ファイルを生成します。リンカは、あるオブジェクトファイルで参照されているシンボル(関数、変数、型など)を、別のオブジェクトファイルで定義されている対応するシンボルと解決します。
    • 未定義シンボルエラー(Undefined Symbol Error): リンカが参照されているシンボルに対応する定義を見つけられない場合に発生します。これは、シンボルがどこにも定義されていないか、またはリンカがそのシンボルを異なるものとして認識している場合に起こります。
  4. gcgccgo:

    • Go言語には主に2つの公式コンパイラ実装があります。
      • gc: Goチームが開発している標準のコンパイラ(go buildコマンドで通常使用される)。
      • gccgo: GCC(GNU Compiler Collection)のフロントエンドとして実装されたGoコンパイラ。gcとは異なるコード生成戦略や最適化を持つため、挙動が異なる場合があります。特に、型システムの実装やリンカとの連携において、gcとは異なる課題を抱えることがあります。

このコミットのバグは、gccgoが構造体の型等価性を判断する際に、unsafe.Sizeofのようなコンパイル時評価を伴う文脈と、リンク時のシンボル解決の文脈で一貫性を欠いていたことに起因します。

技術的詳細

このバグの核心は、gccgoが構造体の型等価性を判断するロジックにありました。Go言語では、構造体の型等価性は、そのフィールドの型、名前、順序によって決定されます。しかし、gccgoは、特に匿名フィールド(埋め込み)や異なるパッケージ間で型が参照される場合に、この等価性チェックで不整合を起こすことがありました。

具体的には、この問題は以下のようなシナリオで発生しました。

  1. 匿名フィールドと型情報: S1S2を匿名で埋め込んでいる場合、S1S2のフィールドとメソッドを「昇格」させます。このとき、S1の内部表現においてS2の型情報がどのように扱われるかが重要になります。
  2. unsafe.Sizeofのコンパイル時評価: const C = unsafe.Sizeof(S2{})のような式は、コンパイル時にS2のサイズを決定します。この際、gccgoS2の型情報を内部的に表現し、そのサイズを計算します。
  3. リンク時の不整合: プログラムが複数のパッケージに分割され、あるパッケージで定義された型が別のパッケージで参照される場合、リンカはこれらの型が同じものであることを確認する必要があります。gccgoがコンパイル時にS2の型をある方法で表現し、リンク時に別の方法で表現したり、あるいは型等価性チェックが不正確であったりすると、リンカは同じ型であるべきものを異なる型と見なし、結果として未定義シンボルエラーを引き起こします。

このバグは、gccgoが構造体の型を識別するための内部的なハッシュ計算や比較ロジックが、特定の複雑な構造体(特に匿名フィールドを持つもの)に対して不完全であったことを示唆しています。これにより、コンパイル時には問題なく見えるコードが、最終的なリンク段階で失敗するという、デバッグが困難な問題が発生していました。

このコミットで追加されたテストケースは、まさにこのシナリオを再現するように設計されており、gccgoがこの問題を修正したことを検証するための重要な手段となります。

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

このコミットでは、以下の3つの新しいファイルが追加されています。

  1. test/fixedbugs/bug479.dir/a.go
  2. test/fixedbugs/bug479.dir/b.go
  3. test/fixedbugs/bug479.go

これらのファイルは、gccgoのバグを再現するためのテストスイートの一部として機能します。

test/fixedbugs/bug479.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 p

import "unsafe"

type S2 struct {}

const C = unsafe.Sizeof(S2{})

type S1 struct {
	S2
}

test/fixedbugs/bug479.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 S3 struct {
	p.S1
}

func main() {
	var i interface{} = S3{}
	_ = i
}

test/fixedbugs/bug479.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.

// Gccgo was not consistent in deciding how to compare a struct type
// for equality, leading to an undefined symbol at link time.

package ignored

コアとなるコードの解説

このテストケースは、複数のパッケージにまたがる構造体の埋め込みと、unsafe.Sizeofの使用を組み合わせることで、gccgoの型等価性判定のバグを誘発するように巧妙に設計されています。

test/fixedbugs/bug479.dir/a.go

  • このファイルはpパッケージに属しています。
  • S2という空の構造体が定義されています。
  • const C = unsafe.Sizeof(S2{})という行が重要です。これはコンパイル時にS2のサイズを計算し、定数Cに割り当てます。unsafe.Sizeofは、コンパイラがS2の型情報を正確に理解していることを前提とします。
  • S1という構造体が定義されており、その中にS2が匿名フィールドとして埋め込まれています。これにより、S1S2のフィールド(この場合はありませんが)やメソッドを「昇格」させます。

test/fixedbugs/bug479.dir/b.go

  • このファイルはmainパッケージに属しており、./aパッケージをインポートしています。
  • S3という構造体が定義されており、その中にp.S1が匿名フィールドとして埋め込まれています。ここで、異なるパッケージ(p)で定義された型が使用されています。
  • main関数内で、S3{}のインスタンスが作成され、それが空のインターフェース変数iに代入されています。インターフェースへの型アサーションや代入は、Goのランタイムが型の動的な情報を必要とするため、コンパイラとリンカが型の完全な情報を保持していることを保証する必要があります。

test/fixedbugs/bug479.go

  • このファイルはテストランナーによって実行されるメインのテストスクリプトです。
  • // rundirというコメントは、このテストが特定のディレクトリ構造(bug479.dir)を持つことを示しています。
  • コメントで「Gccgo was not consistent in deciding how to compare a struct type for equality, leading to an undefined symbol at link time.」と明記されており、このテストの目的がgccgoの型等価性に関するリンク時エラーを捕捉することであることが示されています。

バグの誘発メカニズム

このテストケースは、以下のようにgccgoのバグを誘発します。

  1. a.goS2が定義され、そのサイズがunsafe.Sizeofによってコンパイル時に評価されます。このとき、gccgoS2の内部的な型表現を生成します。
  2. a.goS1S2を埋め込みます。
  3. b.goS3p.S1を埋め込みます。ここで、p.S1p.S2を埋め込んでいるため、S3は間接的にp.S2の型情報を含んでいます。
  4. main関数でS3{}がインターフェースに代入される際、GoのランタイムはS3の完全な型情報(その中に埋め込まれたp.S1、さらにその中に埋め込まれたp.S2の型情報を含む)を必要とします。
  5. gccgop.S2の型等価性を判断する際に、unsafe.Sizeofが評価されたコンパイル時と、S3がインターフェースに代入されるリンク時(またはその準備段階)で、p.S2の型表現に関する一貫性を欠いていた可能性があります。
  6. この不一致により、リンカはp.S2に関連するシンボル(例えば、その型記述子やメソッドテーブルなど)を正しく解決できず、「未定義シンボル」エラーを発生させます。

このテストは、gccgoが異なるコンパイルフェーズや異なるパッケージ間で、複雑な構造体(特に匿名フィールドを持つもの)の型情報を一貫して扱い、正しく型等価性を判断できるようになったことを検証するために不可欠です。

関連リンク

参考にした情報源リンク