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

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

このコミットは、Go言語の実験的な型システムパッケージ exp/types における、ドット付き識別子(dotted identifiers)の読み込みに関する問題を修正するものです。具体的には、gcimporter(Goコンパイラが生成するバイナリ形式の型情報をインポートするコンポーネント)が、特定の形式の識別子を正しく解析できないバグに対応しています。

コミット

commit bd7c626348f3013ef307f9e3ae7c51708e2579eb
Author: Robert Griesemer <gri@golang.org>
Date:   Tue May 29 13:15:13 2012 -0700

    exp/types: properly read dotted identifiers
    
    Fixes #3682.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6256067
---
 src/pkg/exp/types/gcimporter.go       | 2 +-\
 src/pkg/exp/types/gcimporter_test.go  | 7 -------
 src/pkg/exp/types/testdata/exports.go | 5 +++++
 3 files changed, 6 insertions(+), 8 deletions(-)

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

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

元コミット内容

exp/types: properly read dotted identifiers

このコミットは、Go言語の実験的な型システムパッケージ exp/types において、ドット付き識別子(例: pkg.Name)を適切に読み込むように修正します。

Fixes #3682.

この修正は、GoのIssueトラッカーにおけるIssue 3682を解決します。

R=rsc CC=golang-dev https://golang.org/cl/6256067

この変更は、rsc (Russ Cox) によってレビューされ、golang-dev メーリングリストにCCされています。関連するGoの変更リスト(Change List)は 6256067 です。

変更の背景

このコミットの背景には、Go言語の型システムが、コンパイル済みのパッケージから型情報を正確にインポートする際の課題がありました。特に、Goの内部的な表現において、エクスポートされた識別子(例えば、パッケージの外部から参照可能な変数、関数、型など)が、ドット(.)を含む形式で表現されることがあります。これは、パッケージ名と識別子名を結合した「ドット付き識別子」として知られています。

元の実装では、gcimporter がこれらのドット付き識別子を解析する際に、特定の文字(具体的には · (U+00B7))を識別子の一部として認識できない問題がありました。この問題は、crypto/md5 のような標準ライブラリパッケージの型情報をインポートしようとした際に顕在化しました。crypto/md5 には const init1 = 0x... のような定数宣言が含まれており、gcimporter がこれを関数として誤認識してしまうというバグ(Issue 3682)が発生していました。

この誤認識は、gcimporter がGoコンパイラによって生成されたバイナリ形式の型情報(GC export data)を読み込む際に、識別子を構成する文字セットが不完全であったために起こりました。結果として、型情報のインポートが失敗したり、誤った型情報が構築されたりする可能性がありました。

前提知識の解説

Go言語の型システム (exp/types)

exp/types は、Go言語のコンパイラやツールがGoのソースコードの型チェックを行うために使用する、実験的な型システムパッケージです。これは、Goのプログラムの抽象構文木(AST)を解析し、各識別子の型を決定し、型エラーを検出する役割を担います。このパッケージは、Goのコンパイラが内部的に使用するだけでなく、Goのコード分析ツールやIDEなどでも利用されます。

gcimporter

gcimporter は、exp/types パッケージの一部であり、Goコンパイラが生成するバイナリ形式の型情報(GC export data)を読み込むためのコンポーネントです。Goのコンパイラは、パッケージをコンパイルする際に、そのパッケージがエクスポートする型、関数、変数などの情報をバイナリ形式で出力します。他のパッケージがそのパッケージをインポートする際には、gcimporter がこのバイナリ情報を読み込み、メモリ上にGoの型システムが理解できる形式で再構築します。これにより、インポートされたパッケージの型情報を参照して、型チェックやコード補完などを行うことができます。

ドット付き識別子 (Dotted Identifiers)

Go言語では、パッケージの外部からエクスポートされた識別子を参照する際に、パッケージ名.識別子名 の形式を使用します。例えば、fmt.PrintlnPrintlnfmt パッケージの関数です。Goのコンパイラが生成する内部的な型情報では、これらのエクスポートされた識別子を表現するために、特殊な文字(例えば · (U+00B7))を使用してパッケージ名と識別子名を結合することがあります。これは、Goのソースコード上では見えない内部的な表現であり、gcimporter がこれを正しく解析する必要があります。

scanner.Scan()scanner.TokenText()

Goの text/scanner パッケージは、Goのソースコードやその他のテキストをトークンに分割するためのスキャナーを提供します。

  • scanner.Scan(): 入力ストリームから次のトークンを読み込み、そのトークンの種類(scanner.Identscanner.Intscanner.String など)を返します。
  • scanner.TokenText(): scanner.Scan() によって読み込まれた最新のトークンのテキスト表現を返します。

このコミットでは、gcimporter が内部的に使用するスキャナーが、ドット付き識別子を構成する特殊な文字を正しくトークンとして認識できるように、scanner.Scan() の結果を処理するロジックが変更されています。

技術的詳細

このコミットの技術的な核心は、gcimporter がGoコンパイラによって生成されたバイナリ形式の型情報(GC export data)を解析する際に、ドット付き識別子を正しく処理できるようにすることです。

Goの内部では、エクスポートされた識別子、特にパッケージの外部から参照される識別子(例: fmt.PrintlnPrintln)は、バイナリ形式の型情報において、パッケージパスと識別子名を結合した特殊な形式で表現されることがあります。この結合には、Goの内部で「ドット」として機能する特殊な文字 · (U+00B7, Middle Dot) が使用されることがあります。

元の gcimporter.gonext() メソッドでは、スキャナーが読み込んだトークンの種類を switch 文で判定し、scanner.Ident (識別子)、scanner.Int (整数リテラル)、scanner.String (文字列リテラル) の場合に p.lit にトークンのテキストを格納していました。しかし、この switch 文には、ドット付き識別子を構成する · 文字がトークンとして現れた場合の処理が欠けていました。

その結果、gcimporter· を含む識別子を正しく認識できず、例えば crypto/md5 パッケージの init1 定数(内部的には crypto/md5·init1 のように表現される可能性があった)を、誤って関数として解釈してしまうというバグが発生していました。これは、gcimporter が期待するトークンのシーケンスと、実際に読み込んだトークンのシーケンスが一致しないために、解析ロジックが誤ったパスに進んでしまったためと考えられます。

このコミットでは、gcimporter.gonext() メソッドの switch 文に case '·': を追加することで、スキャナーが · 文字を読み込んだ場合にも、そのトークンのテキストを p.lit に正しく格納するように修正しています。これにより、gcimporter はドット付き識別子を構成するすべての要素を正しく認識し、完全な識別子として処理できるようになります。

また、gcimporter_test.go から、このバグを一時的にスキップしていたテストコードが削除されています。これは、修正によってバグが解消されたため、もはやスキップする必要がなくなったことを示しています。

さらに、testdata/exports.go には、Issue 3682を再現し、修正を検証するための新しいテストケースが追加されています。具体的には、const init1 = 0func init() {} という宣言が追加されており、gcimporter がこれらの宣言を正しく解析できることを確認します。特に init1 のような名前は、Goの内部で特殊な扱いを受けることがあるため、このテストケースは重要です。

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

src/pkg/exp/types/gcimporter.go

--- a/src/pkg/exp/types/gcimporter.go
+++ b/src/pkg/exp/types/gcimporter.go
@@ -182,7 +182,7 @@ func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*
 func (p *gcParser) next() {
 	p.tok = p.scanner.Scan()
 	switch p.tok {
-	case scanner.Ident, scanner.Int, scanner.String:
+	case scanner.Ident, scanner.Int, scanner.String, '·':
 		p.lit = p.scanner.TokenText()
 	default:
 		p.lit = ""

src/pkg/exp/types/gcimporter_test.go

--- a/src/pkg/exp/types/gcimporter_test.go
+++ b/src/pkg/exp/types/gcimporter_test.go
@@ -92,13 +92,6 @@ func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
 }
 
 func TestGcImport(t *testing.T) {
-	// Dies trying to read crypto/md5, which contains
-	//	const init1 = 0x...\
-	// The importer believes init1 should be a function for some reason.
-	// golang.org/issue/3682.
-	t.Logf("broken; skipping")
-	return
-
 	// On cross-compile builds, the path will not exist.
 	// Need to use GOHOSTOS, which is not available.
 	if _, err := os.Stat(gcPath); err != nil {

src/pkg/exp/types/testdata/exports.go

--- a/src/pkg/exp/types/testdata/exports.go
+++ b/src/pkg/exp/types/testdata/exports.go
@@ -11,6 +11,11 @@ import (
 	"go/ast"
 )
 
+// Issue 3682: Correctly read dotted identifiers from export data.
+const init1 = 0
+
+func init() {}
+
 const (
 	C0 int = 0
 	C1     = 3.14159265

コアとなるコードの解説

src/pkg/exp/types/gcimporter.go の変更

この変更は、gcParser 構造体の next() メソッド内で行われています。next() メソッドは、gcimporter がバイナリ形式の型情報を解析する際に、次のトークンを読み込む役割を担っています。

元のコードでは、p.scanner.Scan() で読み込んだトークン p.tokscanner.Ident (識別子)、scanner.Int (整数リテラル)、scanner.String (文字列リテラル) のいずれかである場合に、そのトークンのテキストを p.lit に格納していました。

修正後のコードでは、この switch 文に '·' (U+00B7, Middle Dot) が追加されています。これは、Goの内部的な表現において、パッケージ名と識別子名を結合するために使用される特殊な文字です。この変更により、gcimporter· 文字を識別子の一部として正しく認識し、そのテキストを p.lit に格納できるようになります。これにより、crypto/md5·init1 のようなドット付き識別子全体を正しく解析し、init1 が定数であることを適切に判断できるようになります。

src/pkg/exp/types/gcimporter_test.go の変更

このファイルでは、TestGcImport 関数から、Issue 3682に関連する一時的なスキップロジックが削除されています。

元のコードでは、crypto/md5 のインポートが失敗する問題(init1 が関数として誤認識される)があったため、t.Logf("broken; skipping")return を使用して、このテストケースを一時的にスキップしていました。

今回の修正によって、gcimporter がドット付き識別子を正しく処理できるようになり、このバグが解消されたため、スキップロジックは不要になりました。これにより、TestGcImport は完全に実行され、gcimporter の機能が正しく動作することを検証できるようになります。

src/pkg/exp/types/testdata/exports.go の変更

このファイルは、gcimporter のテストデータとして使用されるGoのソースコードです。

追加されたコードは以下の通りです。

// Issue 3682: Correctly read dotted identifiers from export data.
const init1 = 0

func init() {}
  • // Issue 3682: Correctly read dotted identifiers from export data.: このコメントは、このコードがIssue 3682の修正を検証するためのものであることを明確に示しています。
  • const init1 = 0: init1 という名前の定数を宣言しています。この名前は、Goの内部で特殊な意味を持つ init 関数と関連付けられることがあり、gcimporter がこれを正しく定数として認識できるかどうかのテストケースとして重要です。
  • func init() {}: Goの init 関数を宣言しています。これは、パッケージの初期化時に自動的に実行される特殊な関数です。gcimporterinit 関数を正しく識別できるかどうかも、型情報の正確なインポートにとって重要です。

これらの追加により、gcimporterinit1 のような名前を持つ定数や、init 関数のような特殊な関数を、バイナリ形式の型情報から正しく読み取れることを検証できるようになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Go Issue Tracker
  • Go Code Review (Gerrit)
  • text/scanner パッケージのドキュメント
  • Go言語の内部的な型表現に関する情報 (Goコンパイラの設計ドキュメントなど)
  • Go言語の init 関数に関する情報