[インデックス 15855] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるバグ修正に関するものです。具体的には、Go言語の仕様に反して、非ローカル型(現在のパッケージで定義されていない型)に対してメソッドを定義できてしまうという問題を修正しています。この問題は、既存のシンボル名が存在する場合にのみ発生していました。
コミット
commit 7c3694c4de2504eb39196d3c4b0da0f2f4e45ffc
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Wed Mar 20 22:18:20 2013 +0100
cmd/gc: reject methods on non-locals, even if symbol exists
Fixes #5089.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7767044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7c3694c4de2504eb39196d3c4b0da0f2f4e45ffc
元コミット内容
cmd/gc: reject methods on non-locals, even if symbol exists
Fixes #5089.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7767044
変更の背景
Go言語には、メソッドを定義できる型に関する厳格なルールがあります。具体的には、「メソッドは、そのメソッドが定義されているパッケージと同じパッケージで宣言された型に対してのみ定義できる」という制約があります。これは、他の言語に見られる「モンキーパッチ」(実行時に既存のクラスやオブジェクトに新しいメソッドを追加する手法)を防ぎ、コードの予測可能性とモジュール性を保つための重要な設計原則です。
しかし、Goコンパイラ(gc
)には、このルールが一部正しく適用されていないバグが存在しました。具体的には、非ローカル型(例えば、標準ライブラリや他のパッケージからインポートされた型)に対してメソッドを定義しようとした際に、もしそのメソッド名と同じ名前のシンボルが既に存在する場合、コンパイラが誤ってそのメソッド定義を許可してしまうという問題(Issue 5089)がありました。
このバグは、開発者が意図せずGo言語の設計原則に反するコードを記述してしまう可能性があり、予期せぬ動作や混乱を招く恐れがありました。このコミットは、このコンパイラの誤った挙動を修正し、Go言語の仕様に厳密に従うようにするためのものです。
前提知識の解説
Go言語におけるメソッドの定義
Go言語では、関数とは別に「メソッド」を定義できます。メソッドは特定の型に関連付けられた関数であり、その型の値(またはポインタ)に対して呼び出されます。メソッドの定義は以下の構文で行われます。
func (receiver Type) MethodName(parameters) (results) {
// メソッドの実装
}
ここで receiver
は、メソッドが関連付けられる型のインスタンス(レシーバ)です。
ローカル型と非ローカル型
Go言語の文脈において、「ローカル型」とは、現在コンパイルしているパッケージ内で定義された型を指します。例えば、package main
内で type MyStruct struct {}
と定義した場合、MyStruct
はローカル型です。
一方、「非ローカル型」とは、現在コンパイルしているパッケージとは異なるパッケージで定義され、インポートによって利用可能になっている型を指します。例えば、import "bufio"
して bufio.Reader
を使用する場合、bufio.Reader
は現在のパッケージにとっては非ローカル型です。
Go言語の仕様では、非ローカル型に対して新しいメソッドを定義することはできません。これは、他のパッケージの型の振る舞いを勝手に変更することを防ぐためです。
Goコンパイラ gc
gc
は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイル時には、Go言語の文法チェック、型チェック、セマンティックチェックなど、様々な検証が行われます。このコミットで修正されているのは、このgc
コンパイラの内部ロジックの一部です。
yyerror
関数
yyerror
は、コンパイラ内部で使用されるエラー報告関数です。コンパイル中に文法エラーやセマンティックエラーが検出された際に、ユーザーに対してエラーメッセージを出力するために呼び出されます。
技術的詳細
このコミットの技術的な核心は、Goコンパイラのsrc/cmd/gc/dcl.c
ファイル内のaddmethod
関数のロジック変更にあります。
addmethod
関数は、Goのソースコードでメソッド定義が検出された際に、そのメソッドを型に関連付ける処理を行うコンパイラ内部の関数です。この関数は、メソッドが定義される型がローカル型であるかどうかをチェックするロジックを含んでいます。
変更前のコードでは、非ローカル型に対するメソッド定義のチェックが、特定の条件(シンボルが既に存在するかどうかなど)の後に行われていました。このため、もし同じ名前のシンボルが既に存在する場合、コンパイラは誤ってそのメソッド定義を許可してしまうというバグがありました。
コミットによって、以下のコードブロックがaddmethod
関数のより早い段階に移動されました。
if(local && !pa->local) {
// defining method on non-local type.
yyerror("cannot define new methods on non-local type %T", pa);
return;
}
このコードは、以下の条件をチェックしています。
local
: 現在処理中のメソッド定義が、ローカルなコンテキストで定義されているかを示すフラグ。!pa->local
: メソッドが関連付けられる型(pa
)が非ローカル型であるかを示すフラグ。
つまり、このif
文は「もしローカルなコンテキストで、非ローカル型に対してメソッドを定義しようとしているならば」という条件をチェックしています。この条件が真の場合、yyerror
を呼び出して「非ローカル型に新しいメソッドを定義できません」というエラーメッセージを出力し、関数の処理を中断(return
)します。
このチェックを関数のより早い段階に移動することで、シンボルの存在に関わらず、非ローカル型に対するメソッド定義が常に拒否されるようになりました。これにより、Go言語のメソッド定義ルールが厳密に適用されるようになり、Issue 5089で報告されたバグが修正されました。
コアとなるコードの変更箇所
src/cmd/gc/dcl.c
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1380,6 +1380,12 @@ addmethod(Sym *sf, Type *t, int local, int nointerface)\n \t\t}\n \t}\n \n+\tif(local && !pa->local) {\n+\t\t// defining method on non-local type.\n+\t\tyyerror("cannot define new methods on non-local type %T", pa);\n+\t\treturn;\n+\t}\n+\n \tn = nod(ODCLFIELD, newname(sf), N);\n \tn->type = t;\n \n@@ -1395,12 +1401,6 @@ addmethod(Sym *sf, Type *t, int local, int nointerface)\n \t\treturn;\n \t}\n \n-\tif(local && !pa->local) {\n-\t\t// defining method on non-local type.\n-\t\tyyerror("cannot define new methods on non-local type %T", pa);\n-\t\treturn;\n-\t}\n-\n \tf = structfield(n);\n \tf->nointerface = nointerface;\n```
上記の差分が示すように、`addmethod`関数内で、非ローカル型に対するメソッド定義をチェックする`if`ブロックが、元の位置(約1395行目)から新しい位置(約1380行目)に移動されています。これにより、このチェックが他の処理よりも優先的に行われるようになりました。
### `test/fixedbugs/issue5089.go`
```diff
--- /dev/null
+++ b/test/fixedbugs/issue5089.go
@@ -0,0 +1,15 @@
+// errorcheck
+
+// 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.
+
+// issue 5089: gc allows methods on non-locals if symbol already exists
+
+package p
+
+import "bufio"
+
+func (b *bufio.Reader) Buffered() int { // ERROR "non-local"
+ return -1
+}
このファイルは、Issue 5089で報告されたバグを再現し、修正が正しく適用されたことを検証するための新しいテストケースです。bufio.Reader
はGo標準ライブラリの型であり、現在のパッケージp
にとっては非ローカル型です。このテストは、*bufio.Reader
に対してBuffered
というメソッドを定義しようとしています。
// ERROR "non-local"
というコメントは、この行がコンパイル時に「non-local」というエラーメッセージを生成することを期待していることを示しています。修正が適用されたコンパイラでは、このテストは期待通りエラーを発生させ、バグが修正されたことを確認できます。
コアとなるコードの解説
src/cmd/gc/dcl.c
のaddmethod
関数における変更は、コンパイラのセマンティック分析フェーズにおける重要な修正です。
移動されたif
文は、メソッドのレシーバ型が、そのメソッドが定義されているパッケージと同じパッケージに属しているかどうかを検証します。
local
: これは、現在コンパイル中のメソッド定義が、現在のコンパイル単位(通常は現在のパッケージ)内で発生していることを示すブール値です。pa->local
: これは、メソッドのレシーバ型(pa
は型を表す構造体へのポインタ)が、現在のパッケージ内で定義されている型であるかどうかを示すブール値です。
したがって、local && !pa->local
という条件は、「現在のパッケージ内でメソッドを定義しようとしているが、そのメソッドのレシーバ型は現在のパッケージで定義されたものではない(つまり非ローカル型である)」という状況を正確に捉えています。
この条件が真である場合、Go言語の仕様に違反しているため、yyerror
関数が呼び出され、コンパイルエラーが報告されます。そして、return
によってaddmethod
関数の残りの処理がスキップされ、不正なメソッド定義がコンパイルプロセスに組み込まれるのを防ぎます。
このチェックを関数のより早い段階に配置することで、コンパイラは他の複雑なシンボル解決や型チェックのロジックに入る前に、基本的な言語ルール違反を捕捉できるようになります。これにより、コンパイラの堅牢性が向上し、Go言語の設計原則がより厳密に強制されることになります。
関連リンク
- Go Issue 5089: https://github.com/golang/go/issues/5089
- このコミットが修正したバグの報告ページです。
参考にした情報源リンク
- Go言語の仕様 - メソッド宣言: https://go.dev/ref/spec#Method_declarations
- Go言語におけるメソッド定義の公式なルールが記載されています。特に「A method declared on a type must have a receiver of that type. A method declaration associates the method with the receiver base type. The method is in the same package as the receiver base type.」という部分が関連します。
- Goコンパイラのソースコード(
src/cmd/gc
ディレクトリ)- Goコンパイラの内部実装に関する理解の基礎となります。