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

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

このコミットは、Goコンパイラ(cmd/gc)におけるインターフェースメソッド呼び出しの処理に関するバグ修正を含んでいます。具体的には、src/cmd/gc/reflect.cというコンパイラの内部処理を司るファイルと、その修正を検証するためのテストケースであるtest/method4.dir/prog.goが変更されています。

コミット

  • コミットハッシュ: f30392125192124450c2831a08416e875ab5baf0
  • Author: Rémy Oudompheng oudomphe@phare.normalesup.org
  • Date: Tue Jan 7 13:26:48 2014 +0100

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

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

元コミット内容

cmd/gc: do not omit wrapper for expression (interface{...}).F

Fixes #6723.

R=rsc
CC=golang-codereviews
https://golang.org/cl/41570047

変更の背景

このコミットは、Goコンパイラ(cmd/gc)が特定の状況下でインターフェースメソッドの「ラッパー」(wrapper)の生成を誤って省略してしまうバグ(Issue 6723)を修正するために行われました。

具体的には、匿名インターフェース型(interface{...})を介してメソッドを呼び出す際に、コンパイラがそのメソッド呼び出しに必要な内部的なラッパー関数を生成しないことが問題でした。このラッパーは、インターフェースの動的なディスパッチ(実行時にどの具体的なメソッドを呼び出すかを決定する仕組み)を正しく機能させるために不可欠です。ラッパーが省略されると、プログラムが期待通りに動作しない、あるいは実行時エラーが発生する可能性がありました。

コミットメッセージにあるFixes #6723は、このコミットがGoのIssueトラッカーに登録されていた6723番のバグを解決することを示しています。Goのコードレビューシステム(Gerrit)のリンクhttps://golang.org/cl/41570047も提供されており、この変更に関する議論や詳細な経緯を確認できます。

前提知識の解説

Goコンパイラ (cmd/gc)

cmd/gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスには、字句解析、構文解析、型チェック、中間コード生成、最適化、最終的な機械語コード生成など、多くの段階が含まれます。このコミットで変更されたsrc/cmd/gc/reflect.cは、コンパイラのバックエンドの一部であり、特に型情報やメソッドのディスパッチに関する処理を担当しています。

インターフェースとメソッド

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは、JavaやC++のような明示的なimplementsキーワードを必要とせず、型がインターフェースで定義されたすべてのメソッドを実装していれば、そのインターフェースを満たすと見なされます(構造的型付け)。

インターフェースを介してメソッドを呼び出す場合、コンパイラはコンパイル時にどの具体的な型がそのインターフェースを満たすかを知りません。そのため、実行時にインターフェース値が保持する具体的な型のメソッドを動的に呼び出す必要があります。この動的な呼び出しを可能にするために、Goコンパイラは「メソッドラッパー」と呼ばれる小さな関数を生成することがあります。

メソッドラッパー (Method Wrapper)

メソッドラッパーは、インターフェースの動的なディスパッチを処理するための補助的なコードです。インターフェース値は、内部的に2つのポインタで構成されます。1つは基となる具体的な型のデータへのポインタ、もう1つはその型が実装するメソッドテーブルへのポインタです。インターフェースメソッドが呼び出されると、このメソッドテーブルから対応するメソッドのアドレスが検索され、ラッパーを介して呼び出されます。

ラッパーは、例えばレシーバの型変換や、メソッド呼び出しの準備など、動的なディスパッチに必要な追加の処理を行うことがあります。このコミットの文脈では、コンパイラが特定のケースでこのラッパーの生成を誤って省略し、結果としてインターフェースメソッドの呼び出しが正しく行われない問題が発生していました。

匿名インターフェース型 (interface{...})

Goでは、interface{}のように、メソッドを一切持たないインターフェースを定義できます。これは「空のインターフェース」と呼ばれ、あらゆる型の値を保持できます。また、interface{ SomeMethod() }のように、その場で匿名でインターフェース型を定義することも可能です。このコミットのバグは、特にこのような匿名インターフェース型を介したメソッド呼び出しで顕在化していました。

技術的詳細

このバグは、Goコンパイラのcmd/gcが、インターフェースメソッドのラッパーを生成する際の条件判断に誤りがあったことに起因します。

src/cmd/gc/reflect.c内のimethods関数は、インターフェース型が持つメソッドを処理し、それらのメソッドに対応するラッパーを生成するかどうかを決定する役割を担っています。元のコードでは、ラッパーを省略する条件として以下の2つがありました。

  1. t->sym == S:インターフェース型が名前を持たない場合(匿名インターフェース型)。
  2. isblanksym(method):メソッド名がブランク識別子(_)である場合。

このコミット以前のコードでは、t->sym == Sという条件が含まれていました。これは、匿名インターフェース型の場合にラッパーの生成をスキップするという意味になります。しかし、匿名インターフェース型であっても、そのインターフェースを介してメソッドが呼び出される場合には、動的なディスパッチのためにラッパーが必要となることがあります。特に、interface{...}.Fのような式でメソッドを呼び出す場合、コンパイラはラッパーを必要としますが、この条件のために生成が省略されていました。

この誤った最適化(ラッパーの省略)が、Issue 6723で報告された問題を引き起こしていました。コンパイラがラッパーを生成しないことで、実行時にインターフェースメソッドの呼び出しが正しく解決されず、予期せぬ動作やクラッシュにつながる可能性がありました。

修正は、このt->sym == Sという条件を削除することによって行われました。これにより、匿名インターフェース型であっても、ブランク識別子でない限り、必要なラッパーが常に生成されるようになります。

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

src/cmd/gc/reflect.c

--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -476,9 +476,8 @@ imethods(Type *t)
 			last->link = a;
 		last = a;
 
-		// Compiler can only refer to wrappers for
-		// named interface types and non-blank methods.
-		if(t->sym == S || isblanksym(method))
+		// Compiler can only refer to wrappers for non-blank methods.
+		if(isblanksym(method))
 			continue;
 
 		// NOTE(rsc): Perhaps an oversight that

test/method4.dir/prog.go

--- a/test/method4.dir/prog.go
+++ b/test/method4.dir/prog.go
@@ -73,7 +73,14 @@ func main() {
 	f4 := I2.Sum
 	eq(f4(t1, a, 17), 27)
 	eq(f4(t2, a, 18), 28)
-	
+
+	// issue 6723
+	f5 := (interface {
+		I2
+	}).Sum
+	eq(f5(t1, a, 19), 29)
+	eq(f5(t2, a, 20), 30)
+
 	mt1 := method4a.T1(4)
 	mt2 := &method4a.T2{4}
 

コアとなるコードの解説

src/cmd/gc/reflect.cの変更

src/cmd/gc/reflect.cimethods関数内の条件文が変更されました。

元のコード:

		// Compiler can only refer to wrappers for
		// named interface types and non-blank methods.
		if(t->sym == S || isblanksym(method))
			continue;

変更後のコード:

		// Compiler can only refer to wrappers for non-blank methods.
		if(isblanksym(method))
			continue;

この変更の核心は、t->sym == Sという条件が削除されたことです。

  • t->sym == Sは、インターフェース型tがシンボル(名前)を持たない、つまり匿名インターフェース型であることを意味します。
  • isblanksym(method)は、メソッド名がブランク識別子(_)であるかどうかをチェックします。ブランク識別子のメソッドは通常、コンパイラによって無視されるか、特殊な扱いを受けます。

元のコードでは、「匿名インターフェース型である」または「メソッド名がブランク識別子である」場合にラッパーの生成をスキップしていました。この「匿名インターフェース型である」という条件が、issue 6723の原因でした。匿名インターフェース型であっても、そのメソッドが実際に呼び出される場合にはラッパーが必要となるため、この条件を削除することで、匿名インターフェース型の場合でもラッパーが適切に生成されるようになりました。

新しいコメント// Compiler can only refer to wrappers for non-blank methods.は、この変更の意図を明確にしています。つまり、コンパイラがラッパーを参照できるのは、ブランク識別子ではないメソッドに対してのみである、ということを示しています。

test/method4.dir/prog.goの変更

このファイルには、issue 6723というコメントとともに新しいテストケースが追加されました。

	// issue 6723
	f5 := (interface {
		I2
	}).Sum
	eq(f5(t1, a, 19), 29)
	eq(f5(t2, a, 20), 30)

このテストケースは、匿名インターフェース型を介したメソッド呼び出しを具体的に検証しています。

  • (interface { I2 }).Sumという式は、I2インターフェースを埋め込んだ匿名インターフェース型を定義し、そのSumメソッドを取得しようとしています。
  • I2は、おそらく既存のテストファイル内で定義されているインターフェースであり、Sumメソッドを持っていると推測されます。
  • f5(t1, a, 19)f5(t2, a, 20)のような呼び出しは、この匿名インターフェース型を介してSumメソッドが正しく呼び出され、期待される結果(29, 30)を返すことを確認しています。

このテストケースの追加により、src/cmd/gc/reflect.cの変更が、issue 6723で報告された特定のシナリオを正しく修正したことを保証しています。

関連リンク

参考にした情報源リンク

  • Go Code Review (Gerrit) 41570047: https://golang.org/cl/41570047
    • このコードレビューページは、コミットの背景、議論、および関連する問題(後のリグレッションとその修正)に関する重要な情報を提供しました。