[インデックス 11737] ファイルの概要
このコミットは、Goコンパイラ(gc
)におけるエクスポートデータ内の暗黙的な型に関するバグ修正を目的としています。具体的には、複合リテラル(&T{...}
やT{...}
)の処理において、コンパイラが内部的に使用する「暗黙的」フラグの扱いを修正し、その情報がエクスポートデータに誤って含まれることで発生していた問題を解決します。
コミット
commit 5c52404aca4f4c2800031ce60624c76b7058b245
Author: Russ Cox <rsc@golang.org>
Date: Thu Feb 9 00:26:08 2012 -0500
gc: implicit type bug fix in export data
TBR=lvd
CC=golang-dev
https://golang.org/cl/5644064
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5c52404aca4f4c2800031ce60624c76b7058b245
元コミット内容
gc: implicit type bug fix in export data
TBR=lvd
CC=golang-dev
https://golang.org/cl/5644064
変更の背景
Go言語のコンパイラ(gc
)は、ソースコードを解析し、抽象構文木(AST)を構築し、型チェックを行い、最終的に実行可能なバイナリを生成します。このプロセスの中で、コンパイラはパッケージ間で型情報を共有するために「エクスポートデータ」を生成します。
このコミットが行われた当時、Go言語には複合リテラル(Composite Literal)という構文がありました。これは、構造体、配列、スライス、マップなどの複合型を簡潔に初期化するためのものです。例えば、&T{...}
という構文は、型T
の構造体のインスタンスを生成し、そのポインタを返すものです。
問題は、コンパイラが内部的にASTノードに付与するimplicit
というフラグの扱いにありました。このフラグは、ソースコードには明示的に書かれていないが、コンパイラが内部的に生成したノード(例えば、&T{...}
が内部的に(*T){...}
と解釈される際のポインタ逆参照ノード)を示すために使われていました。
バグの背景には、このimplicit
フラグがエクスポートデータに誤って含まれてしまうことがありました。エクスポートデータは、他のパッケージが現在のパッケージの公開された要素(型、関数など)を利用する際に参照する情報です。もし、内部的な実装詳細であるimplicit
フラグがエクスポートデータに含まれてしまうと、他のパッケージがそのエクスポートデータを読み込んだ際に、予期せぬ型情報として解釈され、コンパイルエラーや不正な動作を引き起こす可能性がありました。
特に、&T{...}
のような複合リテラルが関連していました。この構文は、内部的にはポインタの逆参照(OIND
)と複合リテラル(OCOMPLIT
)の組み合わせとして表現されます。この際に生成されるOIND
ノードにimplicit
フラグが設定されていましたが、このフラグがエクスポートデータに漏れ出すことで、他のパッケージがこの型をインポートした際に問題が生じていたと考えられます。
このコミットは、このimplicit
フラグの定義を簡素化し、その伝播をより厳密に制御することで、エクスポートデータに不要な内部情報が漏洩するのを防ぎ、コンパイラの堅牢性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラ(gc
)の内部構造と概念に関する知識が必要です。
-
Goコンパイラ (
gc
) の構造:gc
はGo言語の公式コンパイラであり、C言語で書かれています(当時は)。- コンパイラは、字句解析、構文解析、型チェック、最適化、コード生成といった複数のフェーズで構成されます。
src/cmd/gc/
ディレクトリには、コンパイラの主要なソースコードが含まれています。
-
抽象構文木 (AST):
- コンパイラはソースコードを解析して、プログラムの構造を木構造で表現したASTを構築します。
- ASTの各ノードは、変数、式、文、型などを表します。
Node
構造体(src/cmd/gc/go.h
で定義)は、ASTのノードを表す基本的なデータ構造です。
-
implicit
フラグ:Node
構造体にはimplicit
というフィールドがあります。これは、コンパイラがソースコードに明示的に書かれていないが、内部的な変換や最適化の過程で生成したASTノードをマークするために使用されます。- 例えば、
&T{...}
という構文は、ユーザーが明示的にポインタ逆参照を記述していませんが、コンパイラは内部的にポインタを生成し、そのポインタを逆参照して複合リテラルにアクセスするようなASTを構築します。この際に生成されるポインタ逆参照ノードにimplicit
フラグが設定されることがあります。 - このコミット以前は、
implicit
はExplicit
,Implicit
,ImplPtr
というenum
で表現されていました。Explicit
: 明示的に書かれたノード。Implicit
: コンパイラが生成したノードで、出力時には表示しない。ImplPtr
:&T{...}
のような複合リテラルで、ポインタ逆参照が暗黙的に追加されたノード。
- このコミットでは、この
enum
が削除され、implicit
が単なるuchar
(0または1)として扱われるようになります。これは、implicit
のセマンティクスを「出力時に表示しない」という単純なブール値に統一する変更です。
-
複合リテラル (Composite Literal):
- Go言語の構文の一つで、構造体、配列、スライス、マップなどの複合型を初期化するために使われます。
- 例:
MyStruct{Field: value}
,[]int{1, 2, 3}
,map[string]int{"a": 1}
&T{...}
: 構造体T
のインスタンスを生成し、そのポインタを返す。
-
エクスポートデータ (Export Data):
- Goコンパイラは、コンパイルされたパッケージの公開された(エクスポートされた)型、関数、変数などの情報を、他のパッケージがインポートして利用できるように、特別な形式で出力します。これがエクスポートデータです。
- エクスポートデータは、コンパイル済みパッケージの
.a
ファイル(アーカイブファイル)や、Goモジュールキャッシュ内のファイルに格納されます。 - 他のパッケージが
import "mypackage"
とすると、コンパイラはこのエクスポートデータを読み込み、mypackage
の公開された要素の型情報を取得します。
-
Yacc/Bison:
src/cmd/gc/go.y
は、Go言語の構文を定義するYacc(またはBison)の文法ファイルです。- Yacc/Bisonは、文法定義ファイル(
.y
)からC言語のパーサーコード(y.tab.c
とy.tab.h
)を生成するツールです。 y.tab.c
は生成されたパーサーの実装、y.tab.h
はトークン定義やパーサーのインターフェースを含むヘッダーファイルです。
-
fmt.c
:- コンパイラのデバッグ出力や内部表現のフォーマットを担当するファイル。ASTノードを文字列として表現するロジックが含まれます。
-
typecheck.c
:- コンパイラの型チェックフェーズを担当するファイル。ASTノードの型を推論し、型の一貫性を検証します。
技術的詳細
このコミットの技術的詳細は、主にimplicit
フラグのセマンティクスの変更と、それに伴うコンパイラ内部の処理の調整にあります。
-
implicit
フラグの簡素化 (go.h
の変更):- 以前は
enum
でExplicit
,Implicit
,ImplPtr
の3つの状態を持っていたimplicit
フラグが、単なるuchar
(符号なし文字型、実質的にブール値)に変更されました。 - これにより、
implicit
は「このノードはコンパイラによって暗黙的に生成されたものであり、通常は出力に表示すべきではない」という単純な意味を持つようになりました。ImplPtr
のような特定の暗黙的生成のケースを区別する必要がなくなったことを示唆しています。
- 以前は
-
fmt.c
におけるexprfmt
の変更:exprfmt
関数は、ASTノードを文字列としてフォーマットする役割を担っています。- 変更前:
while(n && n->implicit)
- 変更後:
while(n && n->implicit && (n->op == OIND || n->op == OADDR))
- これは、暗黙的なノードをスキップする条件をより厳密にしたものです。以前は
implicit
フラグが立っていれば無条件にスキップしていましたが、変更後はOIND
(ポインタ逆参照)またはOADDR
(アドレス取得)の操作である場合にのみスキップするようになりました。これにより、他の種類の暗黙的なノードが誤ってスキップされることを防ぎます。
- これは、暗黙的なノードをスキップする条件をより厳密にしたものです。以前は
OPTRLIT
(ポインタリテラル)、OSTRUCTLIT
(構造体リテラル)、OMAPLIT
(マップリテラル)のケースでも、n->left->right->implicit == Implicit
やn->right->implicit == Implicit
といったenum
値の比較が、単にn->left->implicit
やn->implicit
といったブール値のチェックに変わっています。これはgo.h
の変更と整合しています。
-
go.y
におけるパーサーの変更:&T{...}
構文の処理において、以前は$$->right->implicit = ImplPtr;
と設定されていた箇所が、$$->right->implicit = 1;
に変更されました。- これは、
ImplPtr
という特定のenum
値が廃止されたことに伴う変更であり、implicit
が単なるブール値として扱われるようになったことを反映しています。
-
typecheck.c
における型チェックの変更:pushtype
関数では、n->right->implicit = 1;
に加えてn->implicit = 1;
が設定されるようになりました。これは、複合リテラルの型が暗黙的に生成される場合に、そのノード自体も暗黙的であるとマークすることで、エクスポートデータに不要な情報が漏洩するのを防ぐ意図があると考えられます。typecheckcomplit
関数では、ポインタ型が複合リテラル型として許可されないというチェックにおいて、n->right->implicit == Explicit
という条件が!n->right->implicit
に変更されました。これは、「明示的でない(つまり暗黙的である)場合にのみ許可する」というロジックを、implicit
がブール値になった新しいセマンティクスに合わせて調整したものです。
-
y.tab.c
とy.tab.h
の更新:- これらのファイルはYacc/Bisonによって自動生成されるため、
go.y
やgo.h
の変更に伴い、再生成されました。 - 特に
y.tab.c
の差分が大きいのは、Bisonのバージョンが2.3から2.4.1に更新されたことによるボイラープレートコードの変更と、go.y
の文法定義の変更が反映されたためです。
- これらのファイルはYacc/Bisonによって自動生成されるため、
-
テストケースの追加 (
test/fixedbugs/bug392.dir
とbug392.go
):- このコミットには、
bug392.go
という新しいテストケースが追加されています。このテストは、修正されたバグを再現し、修正が正しく機能することを確認するために書かれました。 bug392.go
の内容は、おそらく複合リテラルと型のエクスポートに関連する特定のコードパターンを含んでおり、以前のコンパイラでは問題を引き起こしていたものです。
- このコミットには、
全体として、このコミットはGoコンパイラの内部的な型表現のクリーンアップと、エクスポートデータ生成の堅牢性向上に貢献しています。implicit
フラグのセマンティクスをよりシンプルにすることで、コンパイラのコードベースの理解と保守が容易になり、将来的なバグの発生を防ぐ効果も期待できます。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、src/cmd/gc/go.h
におけるimplicit
フラグの定義の変更と、それに伴うsrc/cmd/gc/fmt.c
、src/cmd/gc/go.y
、src/cmd/gc/typecheck.c
でのimplicit
フラグの利用箇所の修正です。
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -215,13 +215,6 @@ enum
EscNever,
};
-enum
-{
- Explicit,
- Implicit, // don't print in output
- ImplPtr, // OIND added by &T{ ... } literal
-};
-
struct Node
{
// Tree structure.
@@ -257,7 +250,7 @@ struct Node
uchar used;
uchar isddd;
uchar readonly;
- uchar implicit; // Explicit, Implicit, ImplPtr.
+ uchar implicit;
uchar addrtaken; // address taken, even if not moved to heap
uchar dupok; // duplicate definitions ok (for func)
src/cmd/gc/fmt.c
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1058,7 +1058,7 @@ exprfmt(Fmt *f, Node *n, int prec)
NodeList *l;
Type *t;
- while(n && n->implicit)
+ while(n && n->implicit && (n->op == OIND || n->op == OADDR))
n = n->left;
if(n == N)
@@ -1160,13 +1160,13 @@ exprfmt(Fmt *f, Node *n, int prec)
return fmtprint(f, "%N{ %,H }", n->right, n->list);
case OPTRLIT:
- if (fmtmode == FExp && n->left->right->implicit == Implicit)
+ if(fmtmode == FExp && n->left->implicit)
return fmtprint(f, "%N", n->left);
return fmtprint(f, "&%N", n->left);
case OSTRUCTLIT:
- if (fmtmode == FExp) { // requires special handling of field names
- if(n->right->implicit == Implicit)
+ if(fmtmode == FExp) { // requires special handling of field names
+ if(n->implicit)
fmtstrcpy(f, "{");
else
fmtprint(f, "%T{", n->type);
@@ -1194,6 +1194,8 @@ exprfmt(Fmt *f, Node *n, int prec)
case OMAPLIT:
if(fmtmode == FErr)
return fmtprint(f, "%T literal", n->type);
+ if(fmtmode == FExp && n->implicit)
+ return fmtprint(f, "{ %,H }", n->list);
return fmtprint(f, "%T{ %,H }", n->type, n->list);
case OKEY:
src/cmd/gc/go.y
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -808,7 +808,7 @@ uexpr:
// Special case for &T{...}: turn into (*T){...}.
$$ = $2;
$$->right = nod(OIND, $$->right, N);
- $$->right->implicit = ImplPtr;
+ $$->right->implicit = 1;
} else {
$$ = nod(OADDR, $2, N);
}
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2008,7 +2008,8 @@ pushtype(Node *n, Type *t)
if(n->right == N) {
n->right = typenod(t);
- n->right->implicit = 1;
+ n->implicit = 1; // don't print
+ n->right->implicit = 1; // * is okay
}
else if(debug['s']) {
typecheck(&n->right, Etype);
@@ -2048,8 +2049,8 @@ typecheckcomplit(Node **np)
if(isptr[t->etype]) {
// For better or worse, we don't allow pointers as the composite literal type,
- // except when using the &T syntax, which sets implicit to ImplPtr.
- if(n->right->implicit == Explicit) {
+ // except when using the &T syntax, which sets implicit on the OIND.
+ if(!n->right->implicit) {
yyerror("invalid pointer type %T for composite literal (use &%T instead)", t, t->type);
goto error;
}
コアとなるコードの解説
上記の変更は、GoコンパイラのASTノードにおけるimplicit
フラグの扱いを根本的に変更し、それに伴うパーサー、型チェッカー、およびフォーマッターの動作を調整しています。
-
go.h
の変更:enum { Explicit, Implicit, ImplPtr }
が削除され、Node
構造体のimplicit
フィールドが単なるuchar
型になりました。- これは、
implicit
フラグがもはや複数の状態を持つ必要がなく、「暗黙的に生成されたノードであるか否か」という単純なブール値として機能することを意味します。これにより、コンパイラの内部ロジックが簡素化され、エクスポートデータに不要な詳細が漏洩するリスクが低減されます。
-
fmt.c
の変更:exprfmt
関数は、ASTノードを人間が読める形式に変換する際に、暗黙的なノードをスキップするロジックを含んでいます。while(n && n->implicit)
からwhile(n && n->implicit && (n->op == OIND || n->op == OADDR))
への変更は、暗黙的なノードのスキップ条件をより厳密にしました。これにより、OIND
(ポインタ逆参照)やOADDR
(アドレス取得)といった特定の操作に関連する暗黙的なノードのみがスキップされるようになり、他の種類の暗黙的なノードが誤って出力されることを防ぎます。OPTRLIT
,OSTRUCTLIT
,OMAPLIT
のケースでのimplicit
チェックも、enum
値との比較から単純なブール値チェックに変わっています。これはgo.h
の変更に合わせたものです。特にOMAPLIT
では、FExp
モード(式をフォーマットするモード)でn->implicit
が真の場合に{ %,H }
という形式で出力する新しいロジックが追加されました。これは、マップリテラルが暗黙的に生成された場合に、その型情報を省略してより簡潔に表示するためのものです。
-
go.y
の変更:&T{...}
という複合リテラル構文は、Goコンパイラ内部では(*T){...}
のようにポインタ逆参照ノード(OIND
)が暗黙的に追加される形で表現されます。- 以前は、この暗黙的に追加された
OIND
ノードのimplicit
フラグにImplPtr
という特定のenum
値が設定されていました。この変更により、ImplPtr
が廃止されたため、単に1
(真)が設定されるようになりました。これは、implicit
がブール値として機能するという新しいセマンティクスに準拠しています。
-
typecheck.c
の変更:pushtype
関数は、型推論や型変換の際にASTノードに型情報を付与します。この関数内で、n->right->implicit = 1;
に加えてn->implicit = 1;
が設定されるようになりました。これは、複合リテラルの型が暗黙的に生成される場合、その型ノード自体も暗黙的であるとマークすることで、エクスポートデータに不要な内部表現が漏洩するのを防ぐための重要な変更です。typecheckcomplit
関数は、複合リテラルの型チェックを行います。ポインタ型が複合リテラル型として直接使用できないという制約(&T{...}
形式でのみ許可)をチェックするロジックが変更されました。以前はn->right->implicit == Explicit
(明示的なポインタ型の場合にエラー)でしたが、!n->right->implicit
(暗黙的でないポインタ型の場合にエラー)に変更されました。これは、implicit
がブール値になった新しいセマンティクスに合わせて、&T{...}
のように暗黙的にポインタ逆参照が追加されるケースを正しく区別するための調整です。
これらの変更は、Goコンパイラの内部的な型表現の整合性を高め、特に複合リテラルとエクスポートデータ生成におけるバグを修正することを目的としています。implicit
フラグのセマンティクスを簡素化し、その使用箇所を厳密にすることで、コンパイラの堅牢性と保守性が向上しました。
関連リンク
- Go言語の公式ウェブサイト: https://golang.org/
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語のIssueトラッカー: https://github.com/golang/go/issues
参考にした情報源リンク
- Go CL 5644064: gc: implicit type bug fix in export data: https://golang.org/cl/5644064
- Go Issue 392: gc: implicit type bug in export data: https://github.com/golang/go/issues/392
- Bison (parser generator): https://www.gnu.org/software/bison/
- Go Composite Literals: https://go.dev/ref/spec#Composite_literals
- Go Language Specification (for general Go language concepts): https://go.dev/ref/spec
- Go Compiler Internals (general understanding of
gc
): (Specific links are hard to find for 2012, but general knowledge of compiler phases and ASTs is relevant)- A good starting point for modern Go compiler internals: https://go.dev/blog/go1.5-compiler (though this commit predates Go 1.5, the general concepts apply)
- "Go compiler internals" search on Google for more resources.
- Yacc/Bison documentation (for understanding
.y
files): https://www.gnu.org/software/bison/manual/