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

[インデックス 17622] ファイルの概要

このコミットは、Goコンパイラ(cmd/gc)におけるメソッド探索時の挙動に関するバグ修正です。具体的には、構造体のフィールドをメソッドとして誤って報告しないようにする変更が含まれています。

コミット

commit e9453e05454acd5039ac0d499990fd93b11ff43b
Author: Russ Cox <rsc@golang.org>
Date:   Mon Sep 16 15:55:16 2013 -0400

    cmd/gc: do not report fields when looking for methods
    
    Fixes #6395.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/13470046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/e9453e05454acd5039ac0d499990fd93b11ff43b

元コミット内容

cmd/gc: do not report fields when looking for methods

Fixes #6395.

R=ken2
CC=golang-dev
https://golang.org/cl/13470046

変更の背景

このコミットは、Goコンパイラ(cmd/gc)がメソッドを探索する際に発生していたバグを修正するために行われました。Go言語では、構造体(struct)はフィールド(データ)とメソッド(関数)を持つことができます。コンパイラが特定の型に対してメソッドを解決しようとする際、誤ってその型のフィールドをメソッドとして認識してしまう可能性がありました。

特に、この問題はlookdot0関数内で発生していました。この関数は、ドットセレクタ(.)を使用してシンボル(フィールドやメソッド)を解決するGoコンパイラの内部関数です。元の実装では、メソッドを探す際に、フィールドも候補として含んでしまう条件がありました。これにより、コンパイラが不正なコードを許可したり、予期せぬエラーを引き起こしたりする可能性がありました。

コミットメッセージにある Fixes #6395 は、GoのIssueトラッカーで報告されたバグ #6395 を修正するものであることを示しています。このバグは、コンパイラがメソッド解決のロジックにおいて、フィールドとメソッドの区別を適切に行えていなかったことに起因すると考えられます。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラであり、Goのソースコードを機械語に変換する役割を担っています。cmd/gcは、Goのツールチェインの一部として提供されます。
  • 構造体 (struct): Go言語における複合データ型の一つで、異なる型のフィールドをまとめることができます。
  • メソッド: 特定の型に関連付けられた関数です。Goでは、レシーバ引数を持つ関数として定義されます。例えば、func (t MyType) MyMethod() {} のように定義されます。
  • フィールド: 構造体内で定義される変数です。構造体のインスタンスが持つデータを表します。
  • シンボル解決: コンパイラがソースコード中の識別子(変数名、関数名、型名など)が何を指しているのかを特定するプロセスです。
  • ドットセレクタ (.): Go言語で構造体のフィールドやメソッドにアクセスするために使用される演算子です。例: myStruct.myFieldmyStruct.myMethod().
  • Type 構造体: Goコンパイラの内部で型情報を表現するために使用されるデータ構造です。etype フィールドは型の種類(例: TSTRUCT は構造体、TFUNC は関数)を示します。
  • Sym 構造体: Goコンパイラの内部でシンボル(識別子)を表現するために使用されるデータ構造です。name フィールドはシンボルの名前を保持します。
  • f->type->thistuple > 0: Goコンパイラの内部表現において、thistuple は関数のレシーバ引数の数を表します。メソッドの場合、レシーバが存在するため thistuple は1以上になります。通常の関数やフィールドでは0です。

技術的詳細

このコミットの核心は、src/cmd/gc/subr.c ファイル内の lookdot0 関数の変更にあります。lookdot0 関数は、Goコンパイラがドットセレクタ(例: obj.fieldOrMethod)を処理する際に呼び出され、指定されたシンボルが型 t のフィールドまたはメソッドとして存在するかどうかを探索します。

元のコードでは、構造体(u->etype == TSTRUCT)またはインターフェース(u->etype == TINTER)のメンバーを探索するループ内で、シンボル s が見つかった場合に、それがフィールドであるかメソッドであるかを厳密に区別していませんでした。特に、ignorecasetrue の場合(大文字小文字を区別しない検索の場合)、f->sym == s || (ignorecase && ucistrcmp(f->sym->name, s->name) == 0) という条件でシンボル名が一致するかどうかだけを見ていました。

この条件では、名前が一致するフィールドとメソッドの両方が見つかる可能性があり、コンパイラがメソッドを探しているにもかかわらず、誤ってフィールドを候補として報告してしまう問題がありました。

このコミットでは、この条件に f->type->etype == TFUNC && f->type->thistuple > 0 という追加の条件が加えられました。

  • f->type->etype == TFUNC: これは、現在探索しているメンバー f の型が関数型(TFUNC)であることを確認します。これにより、フィールド(データ型)がメソッドとして誤って認識されるのを防ぎます。
  • f->type->thistuple > 0: これは、関数型がレシーバを持つ(つまりメソッドである)ことを確認します。Go言語では、メソッドは必ずレシーバを持ち、そのレシーバはコンパイラの内部表現で thistuple が1以上として表現されます。通常の関数はレシーバを持たないため、thistuple は0です。

この変更により、lookdot0 関数は、メソッドを探索する際に、名前が一致するだけでなく、そのシンボルが実際にレシーバを持つ関数(メソッド)である場合にのみ、それを有効な候補として報告するようになりました。これにより、コンパイラがフィールドをメソッドとして誤認識するバグが修正され、より正確なシンボル解決が可能になりました。

コアとなるコードの変更箇所

変更は src/cmd/gc/subr.c ファイルの lookdot0 関数内の一行です。

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2183,7 +2183,7 @@ lookdot0(Sym *s, Type *t, Type **save, int ignorecase)
  	c = 0;
  	if(u->etype == TSTRUCT || u->etype == TINTER) {
  		for(f=u->type; f!=T; f=f->down)
- 			if(f->sym == s || (ignorecase && ucistrcmp(f->sym->name, s->name) == 0)) {
+ 			if(f->sym == s || (ignorecase && f->type->etype == TFUNC && f->type->thistuple > 0 && ucistrcmp(f->sym->name, s->name) == 0)) {
  				if(save)
  					*save = f;
  				c++;

コアとなるコードの解説

変更された行は、lookdot0 関数内の if 文の条件式です。

元のコード: if(f->sym == s || (ignorecase && ucistrcmp(f->sym->name, s->name) == 0))

変更後のコード: if(f->sym == s || (ignorecase && f->type->etype == TFUNC && f->type->thistuple > 0 && ucistrcmp(f->sym->name, s->name) == 0))

この変更は、ignorecasetrue の場合の条件に f->type->etype == TFUNC && f->type->thistuple > 0 を追加しています。

  • f->sym == s: これは、シンボルが完全に一致する場合の条件です。
  • ignorecase: 大文字小文字を区別しない検索が有効な場合。
  • f->type->etype == TFUNC: 探索中のシンボル f の型が関数型であることを確認します。これにより、フィールドが除外されます。
  • f->type->thistuple > 0: 探索中のシンボル f がレシーバを持つ関数(つまりメソッド)であることを確認します。これにより、通常の関数が除外されます。
  • ucistrcmp(f->sym->name, s->name) == 0: 大文字小文字を区別せずにシンボル名が一致するかどうかを比較します。

この修正により、lookdot0 関数は、大文字小文字を区別しない検索を行う際に、名前が一致するだけでなく、それが実際にレシーバを持つメソッドである場合にのみ、そのシンボルを有効な候補として扱うようになりました。これにより、コンパイラがフィールドをメソッドとして誤って解決する問題が解消されました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (src/cmd/gc/subr.c)
  • Go言語のIssueトラッカー (Issue #6395に関する情報)
  • Go言語のGerritチェンジリスト (CL 13470046)
  • Go言語のコンパイラ設計に関する一般的な知識