Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.caddmethod関数における変更は、コンパイラのセマンティック分析フェーズにおける重要な修正です。

移動されたif文は、メソッドのレシーバ型が、そのメソッドが定義されているパッケージと同じパッケージに属しているかどうかを検証します。

  • local: これは、現在コンパイル中のメソッド定義が、現在のコンパイル単位(通常は現在のパッケージ)内で発生していることを示すブール値です。
  • pa->local: これは、メソッドのレシーバ型(paは型を表す構造体へのポインタ)が、現在のパッケージ内で定義されている型であるかどうかを示すブール値です。

したがって、local && !pa->localという条件は、「現在のパッケージ内でメソッドを定義しようとしているが、そのメソッドのレシーバ型は現在のパッケージで定義されたものではない(つまり非ローカル型である)」という状況を正確に捉えています。

この条件が真である場合、Go言語の仕様に違反しているため、yyerror関数が呼び出され、コンパイルエラーが報告されます。そして、returnによってaddmethod関数の残りの処理がスキップされ、不正なメソッド定義がコンパイルプロセスに組み込まれるのを防ぎます。

このチェックを関数のより早い段階に配置することで、コンパイラは他の複雑なシンボル解決や型チェックのロジックに入る前に、基本的な言語ルール違反を捕捉できるようになります。これにより、コンパイラの堅牢性が向上し、Go言語の設計原則がより厳密に強制されることになります。

関連リンク

参考にした情報源リンク

  • 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コンパイラの内部実装に関する理解の基礎となります。