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

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

このコミットは、Goコンパイラ(cmd/gc)におけるバグ修正に関するものです。具体的には、インポートされたパッケージ内のエクスポートされていないメソッドの修飾名が、コンパイル時に破損する問題(Issue 6295)を解決します。これにより、インターフェースの埋め込みや型アサーションにおいて、エクスポートされていないメソッドが正しく扱われるようになります。

コミット

commit 20137eb4b9cdb2d71264e502333b1353f29a7e35
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Tue Jan 21 22:55:50 2014 -0500

    cmd/gc: preserve qualified names of unexported methods in imports.
    
    Fixes #6295.
    
    LGTM=rsc
    R=golang-codereviews, rsc
    CC=golang-codereviews
    https://golang.org/cl/20850043

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

https://github.com/golang/go/commit/20137eb4b9cdb2d71264e502333b1353f29a7e35

元コミット内容

diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index 6a4516668f..7df1d97a8c 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -941,8 +941,6 @@ interfacefield(Node *n)
 			f->nname = n->left;
 			f->embedded = n->embedded;
 			f->sym = f->nname->sym;
-			if(importpkg && !exportname(f->sym->name))
-				f->sym = pkglookup(f->sym->name, structpkg);
 		}
 
 		} else {
diff --git a/test/fixedbugs/issue6295.dir/p0.go b/test/fixedbugs/issue6295.dir/p0.go
new file mode 100644
index 0000000000..cf86fbcb56
--- /dev/null
+++ b/test/fixedbugs/issue6295.dir/p0.go
@@ -0,0 +1,13 @@
+// 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.
+
+package p0
+
+type T0 interface {
+	m0()
+}
+
+type S0 struct{}
+
+func (S0) m0() {}
diff --git a/test/fixedbugs/issue6295.dir/p1.go b/test/fixedbugs/issue6295.dir/p1.go
new file mode 100644
index 0000000000..974d02fb03
--- /dev/null
+++ b/test/fixedbugs/issue6295.dir/p1.go
@@ -0,0 +1,26 @@
+// 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.\n
+package p1
+
+import "./p0"
+
+type T1 interface {
+	p0.T0
+	m1()
+}
+
+type S1 struct {
+	p0.S0
+}
+
+func (S1) m1() {}
+
+func NewT0() p0.T0 {
+	return S1{}
+}
+
+func NewT1() T1 {
+	return S1{}
+}
diff --git a/test/fixedbugs/issue6295.dir/p2.go b/test/fixedbugs/issue6295.dir/p2.go
new file mode 100644
index 0000000000..4703ec0356
--- /dev/null
+++ b/test/fixedbugs/issue6295.dir/p2.go
@@ -0,0 +1,19 @@
+// 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.
+
+package main
+
+import (
+	"./p0"
+	"./p1"
+)
+
+var (
+	_ p0.T0 = p0.S0{}
+	_ p0.T0 = p1.S1{}
+	_ p0.T0 = p1.NewT0()
+	_ p0.T0 = p1.NewT1() // same as p1.S1{}
+)
+
+func main() {}
diff --git a/test/fixedbugs/issue6295.go b/test/fixedbugs/issue6295.go
new file mode 100644
index 0000000000..b8da21272e
--- /dev/null
+++ b/test/fixedbugs/issue6295.go
@@ -0,0 +1,10 @@
+// compiledir
+
+// 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 6295: qualified name of unexported methods
+// is corrupted during import.
+
+package ignored

変更の背景

このコミットは、Goコンパイラ(cmd/gc)における既知のバグ、Issue 6295「qualified name of unexported methods is corrupted during import.」(インポート時にエクスポートされていないメソッドの修飾名が破損する)を修正するために行われました。

Go言語では、メソッド名が小文字で始まる場合、そのメソッドはエクスポートされず、定義されたパッケージ内でのみアクセス可能です。しかし、このバグにより、あるパッケージが別のパッケージをインポートする際、インポートされたパッケージ内のエクスポートされていないメソッドの内部表現や参照方法が正しく処理されず、修飾名が破損していました。

この破損は、コンパイルやリンクの際に問題を引き起こす可能性がありました。たとえそれらのメソッドが直接アクセスされることを意図していなくても、ツールチェーンがそれらを誤って識別したり解決したりする可能性があったためです。特に、インターフェースの埋め込みや、異なるパッケージ間で型アサーションを行う際に、この問題が顕在化していました。この修正は、Goプログラムのコンパイル時の堅牢性と正確性を向上させることを目的としています。

前提知識の解説

Go言語におけるエクスポートとアンエクスポート

Go言語では、識別子(変数、関数、型、メソッドなど)の可視性は、その名前の最初の文字が大文字か小文字かによって決まります。

  • エクスポートされた識別子: 最初の文字が大文字の場合、その識別子はパッケージ外からアクセス可能です。
  • エクスポートされていない識別子: 最初の文字が小文字の場合、その識別子は定義されたパッケージ内でのみアクセス可能です。

メソッドの場合も同様で、小文字で始まるメソッドは「アンエクスポートされたメソッド」と呼ばれ、そのメソッドが定義されているパッケージ内からのみ呼び出すことができます。

修飾名 (Qualified Name)

Go言語において、別のパッケージから識別子を参照する際には、パッケージ名.識別子名という形式で「修飾名」を使用します。例えば、fmt.PrintlnfmtパッケージのPrintln関数を指します。アンエクスポートされたメソッドは通常、パッケージ外から直接修飾名で参照されることはありませんが、コンパイラの内部処理においては、そのメソッドがどのパッケージに属しているかを識別するために、修飾名のような概念が用いられます。

Goコンパイラ (cmd/gc) とインポート処理

cmd/gcはGo言語の公式コンパイラであり、Goソースコードをコンパイルして実行可能なバイナリを生成する役割を担っています。コンパイルプロセスの一部として、gcは他のパッケージの定義を読み込み、それらを現在のコンパイルユニットに統合する「インポート」処理を行います。この際、インポートされたパッケージの型情報、関数、メソッドなどのシンボル情報が、現在のコンパイル環境に適切にマッピングされる必要があります。

src/cmd/gc/dcl.cは、Goコンパイラの宣言(declaration)処理に関連する部分のC言語ソースファイルです。このファイルは、型、変数、関数、メソッドなどの宣言を解析し、それらのシンボル情報をコンパイラの内部データ構造に登録する役割を担っています。特に、インターフェースのフィールドやメソッドの処理、およびパッケージ間のシンボル解決において重要な役割を果たします。

インターフェースの埋め込み (Interface Embedding)

Go言語のインターフェースは、他のインターフェースを埋め込むことができます。これにより、埋め込まれたインターフェースのメソッドセットを、埋め込む側のインターフェースが自動的に継承します。例えば、interface Am0()を持ち、interface BAを埋め込む場合、Bm0()も持つことになります。この際、埋め込まれたインターフェースのメソッドがアンエクスポートされている場合でも、そのメソッドの型情報が正しく伝播される必要があります。

技術的詳細

このバグは、src/cmd/gc/dcl.c内のinterfacefield関数に存在していました。この関数は、インターフェースのフィールド(メソッドも含む)を処理する際に呼び出されます。

問題のコードは以下の部分でした。

			if(importpkg && !exportname(f->sym->name))
				f->sym = pkglookup(f->sym->name, structpkg);

このコードは、importpkg(現在インポート処理中であるかを示すフラグ)が真であり、かつメソッド名がエクスポートされていない(!exportname(f->sym->name))場合に、そのシンボル(f->sym)をpkglookup関数を使ってstructpkgという特別なパッケージに再マッピングしていました。

structpkgは、Goコンパイラの内部で構造体のフィールドやメソッドを扱うための「匿名」または「内部」パッケージのような役割を果たすことがあります。しかし、アンエクスポートされたメソッドのシンボルをstructpkgに再マッピングしてしまうと、そのメソッドが本来属するパッケージの修飾名情報が失われ、コンパイラがそのメソッドを正しく識別できなくなるという問題が発生していました。

具体的には、以下のようなシナリオで問題が発生しました。

  1. パッケージp0でアンエクスポートされたメソッドm0()を持つ型S0とインターフェースT0が定義される。
  2. パッケージp1p0をインポートし、p0.T0を埋め込んだインターフェースT1や、p0.S0を埋め込んだ構造体S1を定義する。
  3. p1内でp0.T0型の値を生成し、それをp0.T0型として扱う。
  4. mainパッケージがp0p1をインポートし、p1から返されるp0.T0型の値に対して型アサーションなどを行う。

この際、p1p0をインポートする段階で、p0.S0のアンエクスポートされたメソッドm0()の修飾名がstructpkgに誤ってマッピングされ、本来のp0パッケージとの関連が失われていました。結果として、mainパッケージでp1.NewT1()が返す値がp0.T0インターフェースを満たしているかどうかのチェックが正しく行えず、コンパイルエラーや予期せぬ動作を引き起こす可能性がありました。

この修正は、この誤った再マッピングのロジックを削除することで、アンエクスポートされたメソッドの修飾名がインポート時にも正しく保持されるようにしました。

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

変更はsrc/cmd/gc/dcl.cファイルにあります。

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -941,8 +941,6 @@ interfacefield(Node *n)
 			f->nname = n->left;
 			f->embedded = n->embedded;
 			f->sym = f->nname->sym;
-			if(importpkg && !exportname(f->sym->name))
-				f->sym = pkglookup(f->sym->name, structpkg);
 		}
 
 		} else {

具体的には、以下の2行が削除されました。

			if(importpkg && !exportname(f->sym->name))
				f->sym = pkglookup(f->sym->name, structpkg);

コアとなるコードの解説

削除されたコードブロックは、インターフェースのフィールド(メソッドを含む)を処理するinterfacefield関数内にありました。

  • importpkg: このグローバル変数は、現在コンパイラがパッケージのインポート処理を行っている最中である場合にtrueになります。
  • exportname(f->sym->name): この関数は、与えられたシンボル名(f->sym->name)がエクスポートされているかどうかをチェックします。Go言語の命名規則に従い、名前の最初の文字が大文字であればtrue、小文字であればfalseを返します。
  • pkglookup(f->sym->name, structpkg): この関数は、指定された名前(f->sym->name)とパッケージ(structpkg)に対応するシンボルを検索または作成します。structpkgは、Goコンパイラの内部で構造体のフィールドや匿名フィールドに関連するシンボルを管理するために使用される特別な「パッケージ」です。

削除されたif文の条件は、「現在インポート処理中であり、かつ処理中のシンボルがエクスポートされていないメソッドである場合」でした。この条件が真の場合、コンパイラはエクスポートされていないメソッドのシンボルを、本来属するパッケージからstructpkgという内部的なパッケージに再マッピングしていました。

この再マッピングが問題の原因でした。アンエクスポートされたメソッドは、その定義パッケージ内でのみ意味を持つべきであり、インポート時にそのパッケージのコンテキストを失うべきではありませんでした。structpkgへのマッピングは、そのメソッドがどのパッケージに属しているかという重要な修飾名情報を事実上「破損」させていました。これにより、インターフェースの埋め込みや型アサーションなど、パッケージ境界を越えて型が参照されるシナリオで、コンパイラがメソッドの解決に失敗する原因となっていました。

この2行を削除することで、コンパイラはインポート時であっても、エクスポートされていないメソッドのシンボルをstructpkgに強制的に再マッピングしなくなりました。その結果、アンエクスポートされたメソッドは、その本来のパッケージのコンテキストを保持したまま処理されるようになり、Issue 6295で報告された修飾名の破損が解消されました。

この修正は、GoコンパイラがGo言語の可視性ルール(エクスポート/アンエクスポート)をより正確に、かつ堅牢に処理できるようにするための重要な改善です。

関連リンク

参考にした情報源リンク