[インデックス 12463] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における、同じ名前の非エクスポートメソッドが混同されるバグを修正するものです。具体的には、異なるパッケージに属する型が、同じ名前の非エクスポートメソッドを持つ場合に、コンパイラがそれらを正しく区別できない問題に対処しています。
コミット
commit 987a580b9f91dfe0709d6927525952acf9101fc9
Author: Russ Cox <rsc@golang.org>
Date: Wed Mar 7 01:55:17 2012 -0500
cmd/gc: do not confuse unexported methods of same name
Fixes #3146.
R=ken2
CC=golang-dev
https://golang.org/cl/5756074
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/987a580b9f91dfe0709d6927525952acf9101fc9
元コミット内容
このコミットは、Goコンパイラが、異なるパッケージに存在する同じ名前の非エクスポートメソッドを誤って同一視してしまう問題を解決します。この問題は、特にインターフェースの埋め込みや型のアサーションにおいて、予期せぬメソッド呼び出しやコンパイルエラーを引き起こす可能性がありました。
変更の背景
このコミットは、Go issue #3146「gc: unexported methods of same name confuse interface embedding
」を修正するために作成されました。このバグは、異なるパッケージで定義された型が、たまたま同じ名前の非エクスポートメソッドを持っている場合に発生しました。Goのコンパイラは、メソッドのシンボル名を生成する際に、非エクスポートメソッドの場合にパッケージ情報を十分に考慮していなかったため、これらのメソッドを区別できず、結果として誤ったメソッドが解決されたり、コンパイルエラーが発生したりしていました。
具体的には、test/bugs/424.go
(後にtest/fixedbugs/bug424.go
に移動)で示されたテストケースがこの問題を浮き彫りにしました。このテストケースでは、lib
パッケージとmain
パッケージの両方にm()
という非エクスポートメソッドを持つ型が存在し、インターフェースの埋め込みを通じてこれらのメソッドがどのように解決されるかを検証していました。バグが存在する状態では、main
パッケージのmyT3
型がlib
パッケージのm()
メソッドを誤って解決してしまうという問題が発生していました。
前提知識の解説
Go言語におけるエクスポートと非エクスポート
Go言語では、識別子(変数、関数、型、メソッドなど)の可視性は、その名前の最初の文字が大文字か小文字かによって決まります。
- エクスポートされた識別子 (Exported Identifiers): 最初の文字が大文字の場合、その識別子はパッケージの外部からアクセス可能です。これは他の言語の
public
に相当します。 - 非エクスポートされた識別子 (Unexported Identifiers): 最初の文字が小文字の場合、その識別子は定義されたパッケージ内からのみアクセス可能です。これは他の言語の
private
やinternal
に相当します。
メソッドの場合も同様で、メソッド名が小文字で始まる場合、そのメソッドは定義されたパッケージ内でのみ呼び出すことができます。
Goコンパイラのシンボル生成
Goコンパイラは、プログラム内の様々な要素(変数、関数、型、メソッドなど)を一意に識別するために「シンボル」を生成します。特にメソッドの場合、シンボルはレシーバの型とメソッド名を組み合わせて生成されることが一般的です。しかし、非エクスポートメソッドの場合、同じ名前のメソッドが異なるパッケージに存在すると、コンパイラがそれらを区別するための追加情報(パッケージ名など)が必要になります。
src/cmd/gc/dcl.c
とmethodsym
関数
src/cmd/gc/dcl.c
は、Goコンパイラのフロントエンドの一部であり、宣言(declaration)の処理を担当しています。このファイルには、型、変数、関数、メソッドなどの宣言を解析し、それらのシンボルを生成するロジックが含まれています。
methodsym
関数は、特定の型に属するメソッドのシンボル名を生成する役割を担っています。この関数は、メソッドのレシーバ型(t0
)とメソッド名(nsym
)を受け取り、それらを組み合わせて一意のシンボル名を構築します。このシンボル名は、コンパイラの内部でメソッドを一意に識別するために使用されます。
バグの発生前は、methodsym
関数が非エクスポートメソッドのシンボル名を生成する際に、メソッドが属するパッケージの情報を十分に含めていませんでした。そのため、異なるパッケージに同じ名前の非エクスポートメソッドが存在すると、生成されるシンボル名が衝突し、コンパイラがそれらを区別できなくなっていました。
技術的詳細
このコミットの核心は、src/cmd/gc/dcl.c
内のmethodsym
関数の変更にあります。この関数は、Goコンパイラがメソッドの内部シンボル名を生成する際に使用されます。
変更前は、methodsym
関数はメソッドのレシーバ型とメソッド名に基づいてシンボル名を生成していました。非エクスポートメソッドの場合、この生成ロジックは、メソッドが属するパッケージを考慮していませんでした。
// 変更前の関連コードスニペット
if(t0->sym == S && isptr[t0->etype])
p = smprint("(%-hT).%s%s", t0, nsym->name, suffix);
else
p = smprint("%-hT.%s%s", t0, nsym->name, suffix);
このコードでは、nsym->name
(メソッド名)とt0
(レシーバ型)のみを使用してシンボル名を構築しています。suffix
は、インターフェースメソッドの場合に追加される接尾辞です。非エクスポートメソッドの場合、nsym->name
は小文字で始まりますが、これだけでは異なるパッケージの同じ名前のメソッドを区別できません。
このコミットでは、以下の条件が追加されました。
// 変更後の関連コードスニペット
if(nsym->pkg != s->pkg && !exportname(nsym->name)) {
if(t0->sym == S && isptr[t0->etype])
p = smprint("(%-hT).%s.%s%s", t0, nsym->pkg->prefix, nsym->name, suffix);
else
p = smprint("%-hT.%s.%s%s", t0, nsym->pkg->prefix, nsym->name, suffix);
} else {
// 既存のロジック
if(t0->sym == S && isptr[t0->etype])
p = smprint("(%-hT).%s%s", t0, nsym->name, suffix);
else
p = smprint("%-hT.%s%s", t0, nsym->name, suffix);
}
この変更のポイントは、if(nsym->pkg != s->pkg && !exportname(nsym->name))
という条件です。
nsym->pkg != s->pkg
: これは、メソッドが定義されているパッケージ(nsym->pkg
)が、現在のシンボルが属するパッケージ(s->pkg
)と異なるかどうかをチェックします。!exportname(nsym->name)
: これは、メソッド名(nsym->name
)が非エクスポート(つまり、小文字で始まる)であるかどうかをチェックします。exportname
関数は、Goの識別子がエクスポートされているかどうかを判断するユーティリティ関数です。
この2つの条件が真である場合、つまり、異なるパッケージに属する非エクスポートメソッドである場合に、生成されるシンボル名にnsym->pkg->prefix
(メソッドが定義されているパッケージのプレフィックス)が追加されるようになりました。
例:
- 変更前:
main.localT.m
(mainパッケージのlocalT型のmメソッド) - 変更後:
main.localT.main.m
(mainパッケージのlocalT型のmメソッド) またはlib.localT.lib.m
(libパッケージのlocalT型のmメソッド)
このようにパッケージプレフィックスをシンボル名に含めることで、コンパイラは異なるパッケージに存在する同じ名前の非エクスポートメソッドを明確に区別できるようになり、シンボル名の衝突が解消されます。
テストファイルも、test/bugs/424.go
からtest/fixedbugs/bug424.go
にリネームされ、test/bugs/424.dir
ディレクトリがtest/fixedbugs/bug424.dir
に移動されました。これは、バグが修正されたことを示す標準的な慣習です。また、テストコード自体も、reflect
パッケージとfmt
パッケージを使用して、メソッドの解決が正しく行われているかをより詳細に検証するように更新されています。特に、reflect.TypeOf(i).Method(j)
を使ってメソッドのパッケージパスと名前を動的に取得し、デバッグ情報を出力する部分が追加されています。
コアとなるコードの変更箇所
変更は主にsrc/cmd/gc/dcl.c
ファイル内のmethodsym
関数に集中しています。
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1195,10 +1195,17 @@ methodsym(Sym *nsym, Type *t0, int iface)
if(t0->width < types[tptr]->width)
suffix = "·i";
}\n- if(t0->sym == S && isptr[t0->etype])
-\t\tp = smprint("(%-hT).%s%s", t0, nsym->name, suffix);
-\telse
-\t\tp = smprint("%-hT.%s%s", t0, nsym->name, suffix);
+\tif(nsym->pkg != s->pkg && !exportname(nsym->name)) {
+\t\tif(t0->sym == S && isptr[t0->etype])
+\t\t\tp = smprint("(%-hT).%s.%s%s", t0, nsym->pkg->prefix, nsym->name, suffix);
+\t\telse
+\t\t\tp = smprint("%-hT.%s.%s%s", t0, nsym->pkg->prefix, nsym->name, suffix);
+\t} else {
+\t\tif(t0->sym == S && isptr[t0->etype])
+\t\t\tp = smprint("(%-hT).%s%s", t0, nsym->name, suffix);
+\t\telse
+\t\t\tp = smprint("%-hT.%s%s", t0, nsym->name, suffix);
+\t}\n s = pkglookup(p, s->pkg);
free(p);
return s;
また、テストファイルも以下の変更が行われています。
test/bugs/424.go
が削除され、test/fixedbugs/bug424.go
にリネームされました。test/bugs/424.dir/lib.go
がtest/fixedbugs/bug424.dir/lib.go
にリネームされました。test/bugs/424.dir/main.go
がtest/fixedbugs/bug424.go
にリネームされ、内容が更新されました。
コアとなるコードの解説
methodsym
関数は、Goコンパイラがメソッドの内部表現(シンボル)を生成する際に呼び出されます。この関数は、メソッドのレシーバ型(t0
)とメソッド名(nsym
)を受け取り、それらを基に一意の文字列を生成します。この文字列が、コンパイラ内部でメソッドを一意に識別するためのシンボル名となります。
変更の核心は、if(nsym->pkg != s->pkg && !exportname(nsym->name))
という条件分岐の追加です。
nsym->pkg != s->pkg
: これは、現在処理しているメソッドが、そのメソッドが属するパッケージとは異なるパッケージから参照されている場合に真となります。!exportname(nsym->name)
: これは、メソッド名が小文字で始まり、非エクスポートされている場合に真となります。
この両方の条件が真である場合、つまり「異なるパッケージから参照されている非エクスポートメソッド」である場合に、生成されるシンボル名にnsym->pkg->prefix
(メソッドが定義されているパッケージのプレフィックス)が追加されます。
例えば、package main
とpackage lib
があり、両方にfunc (t *T) m() string
という非エクスポートメソッドが存在する場合、変更前は両方のm
メソッドがT.m
のようなシンボル名になる可能性がありました。しかし、変更後はmain.T.main.m
とlib.T.lib.m
のように、パッケージ名がシンボル名に組み込まれるため、コンパイラはこれらを明確に区別できるようになります。
これにより、Goコンパイラは、異なるパッケージに存在する同じ名前の非エクスポートメソッドを誤って混同することなく、正しく解決できるようになりました。
関連リンク
- Go issue #3146: https://go.dev/issue/3146
- Go CL 5756074: https://go.dev/cl/5756074
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goコンパイラのソースコード (
src/cmd/gc/dcl.c
) - Go issue tracker (
go.dev/issue
) - Go code review system (
go.dev/cl
)