[インデックス 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