[インデックス 10225] ファイルの概要
コミット
このコミットは、Goコンパイラ(gc
)におけるバグ修正を目的としています。具体的には、メソッドではない要素が誤ってxmethod
リストに混入するのを防ぐための変更です。これにより、コンパイラの内部処理の堅牢性が向上し、予期せぬ動作やクラッシュを防ぎます。
- コミットハッシュ:
11075ed893193a415d6b16cd28f06ad4bcc49092
- 作者: Luuk van Dijk lvd@golang.org
- 日付: 2011年11月3日 木曜日 17:51:15 +0100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/11075ed893193a415d6b16cd28f06ad4bcc49092
元コミット内容
gc: Don't pollute the xmethod list with non-methods.
Fixes #2355.
I have a test, but not sure if it's worth adding. Instead i've made
the patching-over in reflect.c methods more fatal and more descriptive.
R=rsc
CC=golang-dev
https://golang.org/cl/5302082
変更の背景
このコミットは、Goコンパイラが型に紐付けられたメソッドを処理する際に発生していた問題に対処しています。Go言語では、構造体(struct
)にメソッドを定義できますが、フィールドとして関数型の変数を定義することも可能です。このコミット以前のコンパイラでは、構造体のフィールドとして定義された関数が、誤ってその構造体の「メソッド」として内部リスト(xmethod
リスト)に登録されてしまう可能性がありました。
このような誤った登録は、コンパイラが型のメソッド情報を取得する際に、本来メソッドではない要素をメソッドとして扱おうとすることで、予期せぬエラーやコンパイラのクラッシュを引き起こす原因となっていました。特に、リフレクション(reflect
パッケージ)のような、実行時に型情報を動的に扱う機能において、この問題が顕在化する可能性がありました。
コミットメッセージにある「Fixes #2355」は、Goの内部バグトラッカーにおける特定の課題番号を指しています。この課題は、まさにこの「非メソッドがメソッドリストに混入する」問題とその影響を報告していたものと考えられます。
前提知識の解説
このコミットの理解には、以下のGo言語およびコンパイラの基本的な概念が役立ちます。
- Go言語のメソッド: Go言語において、メソッドは特定の型に関連付けられた関数です。レシーバ引数(
func (r ReceiverType) MethodName(...)
のr ReceiverType
の部分)を持つことで、その型の値やポインタに対して呼び出すことができます。 - 構造体(
struct
): 複数のフィールドをまとめた複合型です。構造体はフィールドだけでなく、メソッドを持つこともできます。 - 埋め込み(Embedding): Go言語の構造体は、他の構造体やインターフェースを匿名フィールドとして埋め込むことができます。これにより、埋め込まれた型のメソッドやフィールドを、埋め込んだ構造体が直接持っているかのようにアクセスできます。この機能は、コードの再利用や「is-a」関係の表現に利用されます。
- Goコンパイラ(
gc
): Go言語の公式コンパイラです。ソースコードを機械語に変換する過程で、型の情報、メソッド情報、シンボルテーブルなどを内部的に管理します。 xmethod
リスト: Goコンパイラの内部で、特定の型が持つメソッドの情報を管理するためのリストです。コンパイラは、このリストを参照して、メソッド呼び出しの解決やリフレクション情報の生成を行います。TFUNC
: Goコンパイラの内部で、関数型を表すための型定数です。TFIELD
: Goコンパイラの内部で、構造体のフィールドを表すための型定数です。thistuple
: Goコンパイラの内部で、関数のレシーバ引数の数を表すためのフィールドです。メソッドの場合、レシーバが存在するため、この値は0より大きくなります。通常の関数では0です。fatal
関数: Goコンパイラの内部で、回復不可能なエラーが発生した場合にプログラムを終了させるための関数です。デバッグやコンパイラの堅牢性確保のために使用されます。
技術的詳細
このコミットの主要な変更点は、Goコンパイラのsrc/cmd/gc/reflect.c
とsrc/cmd/gc/subr.c
ファイルにあります。これらのファイルは、それぞれリフレクション情報の生成と、シンボルおよび型の処理を担当しています。
src/cmd/gc/reflect.c
の変更
methods
関数は、特定の型(Type *t
)が持つメソッドのリストを生成する役割を担っています。変更前は、xmethod
リストを走査する際に、f->type->etype != TFUNC
という条件で関数型でないものをスキップしていました。しかし、これだけでは不十分でした。なぜなら、構造体のフィールドとして定義された関数型の変数もTFUNC
であるため、誤ってメソッドとして扱われる可能性があったからです。
このコミットでは、以下のチェックが追加されました。
if(f->etype != TFIELD)
:f
がフィールドでない場合にfatal
エラーを発生させます。これは、xmethod
リストに登録されるべき要素はフィールド(つまり、型に紐付けられたシンボル)であるべきだという前提を強化しています。if (f->type->etype != TFUNC || f->type->thistuple == 0)
:f->type->etype != TFUNC
:f
の型が関数型でない場合にfatal
エラーを発生させます。これは既存のチェックをより厳密にしたものです。f->type->thistuple == 0
:f
の型が関数型でありながら、レシーバ引数(thistuple
が0)を持たない場合にfatal
エラーを発生させます。これが最も重要な変更点であり、通常の関数とメソッドを区別するための決定的な条件です。メソッドは必ずレシーバを持つため、thistuple
は0より大きくなります。
if (!getthisx(f->type)->type)
: レシーバの型が存在しない場合にfatal
エラーを発生させます。これは、メソッドとして扱われるべき要素が正しくレシーバ情報を持っていることを保証するための追加チェックです。
これらの変更により、methods
関数はxmethod
リストを処理する際に、より厳密なチェックを行い、非メソッドが誤ってリストに混入した場合に即座にエラーを検出してコンパイラを停止させるようになりました。これにより、コンパイラの内部状態の整合性が保たれ、後続の処理での問題発生を防ぎます。
src/cmd/gc/subr.c
の変更
expandmeth
関数は、型のメソッドを「展開」する、つまり、埋め込み型などによって継承されるメソッドを処理する役割を担っています。この関数内で、addot1
という内部関数が任意のフィールドを掘り出す可能性があり、その結果、メソッドではないフィールドが誤ってメソッドとして扱われる可能性がありました。
変更前は、if(c == 1)
(c
は一致するフィールドの数)の場合にsl->good = 1; sl->field = f;
として、見つかったフィールドを無条件にメソッドとして登録していました。
このコミットでは、以下の条件が追加されました。
if(f->type->etype == TFUNC && f->type->thistuple > 0)
:f->type->etype == TFUNC
: 見つかったフィールドが関数型であること。f->type->thistuple > 0
: その関数型がレシーバ引数を持つこと。
この条件を満たす場合にのみ、sl->good = 1; sl->field = f;
としてフィールドをメソッドとして登録するように変更されました。これにより、expandmeth
関数がメソッドを処理する際に、非メソッドのフィールドが誤ってメソッドとして登録されることを防ぎます。
test/fixedbugs/bug372.go
の追加
このコミットでは、test/fixedbugs/bug372.go
という新しいテストファイルが追加されました。このテストは、まさにこのバグを再現し、修正が正しく適用されたことを検証するためのものです。
package main
type T struct {}
func (T) m() string { return "T" } // T型のメソッドm
type TT struct {
T // Tを埋め込み
m func() string // mという名前の関数型フィールド
}
func ff() string { return "ff" }
func main() {
var tt TT
tt.m = ff // TTのフィールドmにff関数を代入
if tt.m() != "ff" {
println(tt.m(), "!= \"ff\"")
}
}
このテストケースのポイントは、TT
構造体がT
型を埋め込んでいるため、T
のメソッドm()
を継承しているように見える点と、同時にm
という名前の関数型フィールドを持っている点です。バグが存在するバージョンでは、TT
のフィールドm
が、T
のメソッドm
と混同され、xmethod
リストに誤って登録される可能性がありました。
このテストでは、tt.m = ff
としてTT
のフィールドm
に別の関数ff
を代入し、その後にtt.m()
を呼び出しています。もしコンパイラがTT
のフィールドm
を正しく認識せず、T
のメソッドm
と混同していた場合、tt.m()
の呼び出し結果が期待通りにならないか、コンパイルエラーが発生する可能性がありました。修正後は、tt.m()
はフィールドm
に代入されたff
関数を正しく呼び出し、"ff"を返すことが期待されます。
コアとなるコードの変更箇所
src/cmd/gc/reflect.c
--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -158,10 +158,13 @@ methods(Type *t)
// generating code if necessary.
a = nil;
for(f=mt->xmethod; f; f=f->down) {
- if(f->type->etype != TFUNC)
- continue;
if(f->etype != TFIELD)
- fatal("methods: not field");
+ fatal("methods: not field %T", f);
+ if (f->type->etype != TFUNC || f->type->thistuple == 0)
+ fatal("non-method on %T method %S %T\n", mt, f->sym, f);
+ if (!getthisx(f->type)->type)
+ fatal("receiver with no type on %T method %S %T\n", mt, f->sym, f);
+
method = f->sym;
if(method == nil)
continue;
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2178,8 +2178,11 @@ expandmeth(Sym *s, Type *t)
if(c == 0)
continue;
if(c == 1) {
- sl->good = 1;
- sl->field = f;
+ // addot1 may have dug out arbitrary fields, we only want methods.
+ if(f->type->etype == TFUNC && f->type->thistuple > 0) {
+ sl->good = 1;
+ sl->field = f;
+ }
}
break;
}
test/fixedbugs/bug372.go
--- /dev/null
+++ b/test/fixedbugs/bug372.go
@@ -0,0 +1,28 @@
+// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: bug372
+
+// Copyright 2011 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 2355
+package main
+
+type T struct {}
+func (T) m() string { return "T" }
+
+type TT struct {
+ T
+ m func() string
+}
+
+
+func ff() string { return "ff" }
+
+func main() {
+ var tt TT
+ tt.m = ff
+
+ if tt.m() != "ff" {
+ println(tt.m(), "!= \"ff\"")
+ }
+}
コアとなるコードの解説
src/cmd/gc/reflect.c
の変更点
methods
関数内の変更は、xmethod
リストを走査する際のバリデーションを強化しています。
if(f->etype != TFIELD)
:f
がTFIELD
(構造体のフィールド)でない場合、これは予期せぬ状態であるため、fatal
エラーでコンパイラを停止させます。xmethod
リストにはフィールドとして登録されたメソッドのみが含まれるべきという前提を強制します。if (f->type->etype != TFUNC || f->type->thistuple == 0)
:f->type->etype != TFUNC
:f
の型が関数型でない場合、それはメソッドではないためエラーとします。f->type->thistuple == 0
:f
の型が関数型であっても、レシーバ引数(thistuple
が0)を持たない場合、それは通常の関数でありメソッドではないためエラーとします。この条件が、構造体の関数型フィールドと実際のメソッドを区別する鍵となります。
if (!getthisx(f->type)->type)
: メソッドのレシーバ型が取得できない場合もエラーとします。これは、メソッドの定義が不完全であるか、内部的な不整合があることを示唆します。
これらのfatal
コールは、コンパイラの早期段階で問題を検出し、より詳細なエラーメッセージを提供することで、デバッグを容易にし、コンパイラの安定性を向上させます。
src/cmd/gc/subr.c
の変更点
expandmeth
関数内の変更は、addot1
が発見したフィールドが実際にメソッドであるかどうかを厳密にチェックします。
if(f->type->etype == TFUNC && f->type->thistuple > 0)
:f->type->etype == TFUNC
: 見つかったフィールドf
の型が関数型であることを確認します。f->type->thistuple > 0
: その関数型がレシーバ引数を持つことを確認します。
この二つの条件が同時に満たされた場合にのみ、sl->good = 1; sl->field = f;
として、そのフィールドを有効なメソッドとして登録します。これにより、addot1
が誤って掘り出した非メソッドの関数型フィールドが、メソッドとして扱われることを防ぎます。
test/fixedbugs/bug372.go
の追加
このテストは、Go言語の埋め込みとフィールドの命名が重なるエッジケースを狙っています。TT
構造体はT
を埋め込んでいるため、T.m()
メソッドを「継承」します。しかし、同時にTT
自身もm func() string
というフィールドを持っています。
このテストの目的は、コンパイラがtt.m
がTT
のフィールドm
を指していることを正しく認識し、T
のメソッドm
と混同しないことを確認することです。修正前は、この混同が発生し、tt.m = ff
のような代入が正しく機能しないか、コンパイルエラーを引き起こす可能性がありました。修正後は、tt.m()
はff
関数を呼び出し、期待通り"ff"を返すことで、コンパイラが正しく動作していることを示します。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のIssueトラッカー(このコミットが修正したIssue #2355の直接のリンクは、公開されているGitHubリポジトリのIssueリストからは見つけられませんでしたが、内部的なトラッカーの可能性があります。)
- Go言語のコンパイラに関するドキュメント(Goのソースコード内や公式ドキュメントを参照)
参考にした情報源リンク
- Go言語のソースコード(特に
src/cmd/gc/
ディレクトリ) - Go言語の仕様書
- Go言語のコンパイラに関する技術記事や解説(一般的なGoコンパイラの動作原理に関する情報)
- GitHubのコミット履歴と関連する議論
- Go言語の
reflect
パッケージに関するドキュメント(リフレクションの動作を理解するため) - Go言語の埋め込みに関するドキュメント