[インデックス 1554] ファイルの概要
このコミットは、Goコンパイラ(特に当時の6gコンパイラ)におけるバグ、すなわち構造体のフィールドの可視性が誤って判断されるケースを特定し、その再現テストを追加するものです。具体的には、パッケージのインポート順序によって、本来非公開であるべきフィールドが誤って公開されているとコンパイラが認識してしまう問題に対処しています。
コミット
- コミットハッシュ:
2a4f4dd84234fd12cf40641abc112df97e3c0bec - Author: Ian Lance Taylor iant@golang.org
- Date: Mon Jan 26 09:59:59 2009 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2a4f4dd84234fd12cf40641abc112df97e3c0bec
元コミット内容
Add a test for a case where 6g thinks that a field is visible
when it should not be. I couldn't get this any simpler; the
error seems to have to do with the order of the imports in
bug2.go.
R=rsc
DELTA=26 (26 added, 0 deleted, 0 changed)
OCL=23450
CL=23482
変更の背景
このコミットは、Go言語の初期のコンパイラである6gに存在した、構造体のフィールドの可視性に関するバグを修正するためのテストケースを追加するものです。Go言語では、識別子(変数名、関数名、型名、フィールド名など)がエクスポートされる(つまり、他のパッケージからアクセス可能になる)かどうかは、その識別子の最初の文字が大文字であるか小文字であるかによって決まります。小文字で始まる識別子は、その識別子が定義されているパッケージ内でのみ可視(非公開)であり、大文字で始まる識別子は、他のパッケージからも可視(公開)となります。
このバグは、6gコンパイラが、特定の状況下(特にパッケージのインポート順序が関係している)で、本来非公開であるべきフィールドを誤って公開されていると判断してしまうというものでした。これにより、コンパイラはエラーを報告すべき箇所でエラーを報告せず、不正なコードがコンパイルされてしまう可能性がありました。このコミットは、この特定のバグを再現させ、将来的な回帰を防ぐためのテストを追加することを目的としています。
前提知識の解説
Go言語のパッケージと可視性
Go言語は、コードを「パッケージ」という単位で整理します。各Goファイルは必ずpackage宣言を持ち、同じパッケージに属するファイルは同じ名前空間を共有します。異なるパッケージ間でコードを再利用するには、import文を使用してそのパッケージをインポートする必要があります。
Go言語における識別子の可視性(エクスポートルール)は非常にシンプルです。
- 公開 (Exported): 識別子の最初の文字が大文字の場合、その識別子はパッケージ外からアクセス可能です。
- 非公開 (Unexported): 識別子の最初の文字が小文字の場合、その識別子は定義されたパッケージ内でのみアクセス可能です。
例えば、type MyStruct struct { MyField int; myField int }という構造体があった場合、MyFieldは公開され、myFieldは非公開となります。他のパッケージからMyStructをインポートした場合、MyFieldにはアクセスできますが、myFieldには直接アクセスできません。
6gコンパイラ
6gは、Go言語の初期に存在したコンパイラの一つです。Go言語のツールチェインは、当初、ターゲットアーキテクチャごとに異なるコンパイラを持っていました。
6g: x86-64 (amd64) アーキテクチャ用8g: x86 (386) アーキテクチャ用5g: ARM アーキテクチャ用
これらのコンパイラは、Go言語の公式コンパイラであるgc(Go compiler)ツールチェインの一部でした。Go 1.5以降、これらのアーキテクチャ固有のコンパイラは単一のcompileコマンドに統合され、現代のGo開発では直接6gのようなコマンドを使用することはありません。しかし、このコミットが作成された2009年当時は、6gが主要なコンパイラの一つでした。
このコミットは、6gコンパイラがGo言語の可視性ルールを正しく適用できていなかったという、コンパイラのバグを指摘しています。
技術的詳細
このバグは、Goコンパイラが構造体のフィールドの可視性を判断する際に、特定のインポート順序が絡むと誤った判断を下すというものです。具体的には、bug0パッケージで定義された非公開フィールドiを持つ型bug0.Tが、bug1パッケージを介してbug2パッケージに間接的にインポートされる際に問題が発生します。
bug0.goで定義されるbug0.T型は、非公開フィールドiを持っています。
package bug0
type T struct { i int } // 'i' is unexported
bug1.goでは、bug0パッケージをインポートし、bug0.Tを埋め込んだbug1.T型を定義しています。
package bug1
import "bug0"
type T struct { t bug0.T }
問題のbug2.goでは、bug1パッケージとbug0パッケージの両方をインポートしています。
package bug1 // Note: This is also package bug1, not bug2
import "bug1" // This imports the bug1 package itself, which is unusual but valid
import "bug0"
type T2 struct { t bug0.T }
func fn(p *T2) int {
// This reference should be invalid, because bug0.T.i is local
// to package bug0 and should not be visible in package bug1.
return p.t.i
}
ここで重要なのは、bug2.goもpackage bug1として定義されている点です。そして、import "bug1"という行があります。これは、bug2.goが属するbug1パッケージが、自分自身(bug1パッケージ)をインポートしているように見えますが、実際にはGoのビルドシステムがこれをどのように解釈するか、あるいはコンパイラがどのようにシンボル解決を行うかによって、予期せぬ挙動を引き起こす可能性があります。
このコミットの作者は、「エラーはbug2.goのインポート順序に関係しているようだ」と述べており、これがコンパイラのシンボルテーブル構築や可視性チェックのロジックに影響を与えていたことを示唆しています。本来、bug0.T.iはbug0パッケージの非公開フィールドであるため、bug1パッケージ(bug2.goが属するパッケージ)からはアクセスできないはずです。しかし、6gコンパイラはこのアクセスを許可してしまい、コンパイルエラーを発生させなかったのです。
このテストは、この不正なアクセスがコンパイル時にエラーとなることを期待しています。errchk $G $D/$F.dir/bug2.goというコマンドは、bug2.goのコンパイルがエラーになることを検証するためのものです。
コアとなるコードの変更箇所
このコミットでは、以下のファイルが追加されています。
test/bugs/bug133.dir/bug0.go:bug0パッケージを定義し、非公開フィールドiを持つ構造体Tを宣言します。test/bugs/bug133.dir/bug1.go:bug1パッケージを定義し、bug0.Tを埋め込んだ構造体Tを宣言します。test/bugs/bug133.dir/bug2.go:bug1パッケージに属し、bug1とbug0をインポートし、bug0.Tの非公開フィールドiに不正にアクセスする関数fnを定義します。test/bugs/bug133.go: 上記のGoファイルをコンパイルし、bug2.goがエラーを発生させることを検証するテストスクリプトです。test/golden.out: テストの期待される出力(この場合はbug133.goが「BUG: succeeds incorrectly」と報告すること)を記録するファイルです。
コアとなるコードの解説
test/bugs/bug133.dir/bug0.go
// Copyright 2009 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 bug0
type T struct { i int }
このファイルはbug0パッケージを定義しています。Tという名前の構造体を宣言し、その中にiという名前のint型フィールドを持っています。Go言語の可視性ルールにより、iは小文字で始まるため、bug0パッケージ外からはアクセスできない非公開フィールドです。
test/bugs/bug133.dir/bug1.go
// Copyright 2009 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 bug1
import "bug0"
type T struct { t bug0.T }
このファイルはbug1パッケージを定義しています。bug0パッケージをインポートし、Tという名前の構造体を宣言しています。このT構造体は、bug0.T型のフィールドtを埋め込んでいます。
test/bugs/bug133.dir/bug2.go
// Copyright 2009 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 bug1
import "bug1"
import "bug0"
type T2 struct { t bug0.T }
func fn(p *T2) int {
// This reference should be invalid, because bug0.T.i is local
// to package bug0 and should not be visible in package bug1.
return p.t.i
}
このファイルもbug1パッケージに属しています。ここで注目すべきは、import "bug1"とimport "bug0"の両方が存在することです。T2という構造体が定義され、これもbug0.T型のフィールドtを持っています。
fn関数内で、p.t.iというアクセスが行われています。pは*T2型であり、T2はbug0.T型のフィールドtを持っています。したがって、p.tはbug0.T型です。bug0.Tのフィールドiはbug0パッケージ内で非公開として定義されています。bug2.goはbug1パッケージに属しているため、bug0パッケージの非公開フィールドiに直接アクセスすることはGoの可視性ルールに違反します。
このテストの目的は、このreturn p.t.iの行がコンパイルエラーを引き起こすことを確認することです。もし6gコンパイラがこのエラーを検出できない場合、それはバグであることを示します。
test/bugs/bug133.go
// $G $D/$F.dir/bug0.go && $G $D/$F.dir/bug1.go && errchk $G $D/$F.dir/bug2.go
// Copyright 2009 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.
ignored
これはテストスクリプトです。
$G: Goコンパイラ(この場合は6g)を指します。$D/$F.dir/bug0.go:bug0.goファイルをコンパイルします。$D/$F.dir/bug1.go:bug1.goファイルをコンパイルします。errchk $G $D/$F.dir/bug2.go:bug2.goファイルをコンパイルし、そのコンパイルがエラーになることを期待します。errchkは、指定されたコマンドが非ゼロの終了コード(エラーを示す)を返すことを検証するテストユーティリティです。
このスクリプトは、bug0.goとbug1.goが正常にコンパイルされた後、bug2.goがコンパイルエラーになることを確認することで、6gコンパイラの可視性バグが修正されたことを検証します。
関連リンク
N/A