[インデックス 10653] ファイルの概要
このコミットは、Goコンパイラ(gc
)における型チェックのバグ修正と、そのバグを再現するためのテストケースの追加を含んでいます。具体的には、src/cmd/gc/typecheck.c
ファイルが修正され、test/fixedbugs/bug380.go
という新しいテストファイルが追加されています。
コミット
- コミットハッシュ:
5e98505ba7eda4f5ad6525444e19b4ada04677ab
- Author: Russ Cox rsc@golang.org
- Date: Wed Dec 7 15:48:55 2011 -0500
- コミットメッセージ:
gc: fix spurious typecheck loop in &composite literal Fixes #2538. R=ken2 CC=golang-dev https://golang.org/cl/5449114
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5e98505ba7eda4f5ad6525444e19b4ada04677ab
元コミット内容
gc: fix spurious typecheck loop in &composite literal
Fixes #2538.
R=ken2
CC=golang-dev
https://golang.org/cl/5449114
変更の背景
このコミットは、Goコンパイラ(gc
)において、複合リテラル(composite literal)にアドレス演算子(&
)を適用した際に発生する、誤った型チェックの無限ループ(spurious typecheck loop)を修正するために行われました。この問題は、Go言語のIssue #2538として報告されており、特定のコードパターンがコンパイラを無限ループに陥らせ、コンパイルが完了しないという深刻なバグでした。
具体的には、&T{}
のような形式で複合リテラルのアドレスを取る際に、コンパイラの型チェックロジックが誤った状態に陥り、同じノードに対して繰り返し型チェックを試みることで無限ループが発生していました。このようなバグは、コンパイラの安定性と信頼性を著しく損なうため、早期の修正が求められました。
前提知識の解説
Go言語の型システムと型チェック
Go言語は静的型付け言語であり、コンパイル時に厳密な型チェックが行われます。型チェックは、プログラムが型の規則に違反していないかを確認するプロセスであり、これにより多くの実行時エラーを未然に防ぐことができます。Goコンパイラ(gc
)は、ソースコードを抽象構文木(AST)に変換した後、このASTを走査しながら各ノードの型を決定し、型の整合性を検証します。
複合リテラル (Composite Literals)
複合リテラルは、Go言語で構造体、配列、スライス、マップなどの複合型を初期化するための構文です。例えば、struct { X int }{X: 10}
や []int{1, 2, 3}
、map[string]int{"a": 1}
などがあります。これらは、新しい複合型の値を直接生成するために使用されます。
アドレス演算子 (&
)
Go言語におけるアドレス演算子 &
は、変数のメモリアドレスを取得するために使用されます。例えば、p := &v
と書くと、変数 v
のアドレスがポインタ変数 p
に代入されます。複合リテラルに対して &
を適用すると、その複合リテラルによって生成された値のポインタが得られます。例えば、&T{}
は、型 T
のゼロ値のインスタンスを生成し、そのインスタンスへのポインタを返します。
コンパイラの型チェックプロセスと無限ループ
コンパイラの型チェックは通常、ASTのノードを一度だけ処理するように設計されています。しかし、複雑な型推論や相互依存関係がある場合、特定のノードが複数回処理される可能性があります。この際、コンパイラが「このノードは既に型チェック済みである」という状態を正しく管理できないと、同じノードに対して無限に型チェックを試み続ける「無限ループ」が発生することがあります。これは、コンパイラのバグであり、コンパイルプロセスのハングアップやリソースの枯渇を引き起こします。
技術的詳細
このバグは、&composite literal
の形式、特に &T{}
のような場合に発生していました。Goコンパイラの gc
における型チェック関数 typecheckcomplit
は、複合リテラルの型チェックを担当しています。
問題の根源は、複合リテラルのアドレスを取る際に、そのリテラル自体の型チェックと、そのリテラルが指す基底型の型チェックの間の相互作用にありました。コンパイラは、&T{}
のような式を処理する際に、まず T{}
の部分を型チェックし、次にその結果に対して &
演算子を適用します。このプロセスにおいて、T{}
の型チェックが完了したことを示すフラグ(n->typecheck = 1
)が設定されるものの、その内部で参照される型(n->left->type
)の型チェック状態が適切に更新されない場合がありました。
具体的には、&T{}
のような式が処理される際、n
は &
演算子を表すノード、n->left
は T{}
の複合リテラルを表すノードとなります。typecheckcomplit
関数内で、n->type = n->left->type;
の行で &
演算子の結果の型が設定されますが、この時点では n->left
(複合リテラル) の型チェックが完了したことを示す n->left->typecheck
フラグが設定されていませんでした。
これにより、コンパイラが再度 n->left
の型チェックを試みた際に、既に型が決定されているにもかかわらず、その状態が認識されずに無限に型チェックのロジックが繰り返されてしまうという状況が発生していました。これは、コンパイラがノードの型チェック状態を適切に伝播・管理できていなかったことに起因します。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 072b577a56..90db76960d 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2198,6 +2198,7 @@ typecheckcomplit(Node **np)
t->typecheck = 1;
t->type = n->left->type;
t->left->type = t;
+ t->left->typecheck = 1;
}
*np = n;
コアとなるコードの解説
変更は src/cmd/gc/typecheck.c
ファイルの typecheckcomplit
関数内の一箇所です。
追加された行は以下の通りです。
t->left->typecheck = 1;
この行は、複合リテラル(n->left
)の型チェックが完了したことを明示的に示すフラグ typecheck
を 1
に設定しています。
修正前のコードでは、&
演算子の結果の型 (n->type
) は n->left->type
から設定されていましたが、n->left
自体の typecheck
フラグは設定されていませんでした。これにより、コンパイラが n->left
を再度型チェックしようとした際に、既に型が決定されているにもかかわらず、その状態が認識されずに無限ループに陥っていました。
この修正により、&
演算子の型チェックが完了した時点で、そのオペランドである複合リテラル (n->left
) の型チェックも完了したとマークされるようになります。これにより、コンパイラが同じノードに対して不必要に型チェックを繰り返すことがなくなり、無限ループが解消されます。
関連リンク
- Go Issue #2538: このコミットが修正したバグの報告。Goの公式Issueトラッカーで詳細を確認できます。
- Go CL 5449114: このコミットに対応するGerrit Code Reviewのチェンジリスト。より詳細な議論やレビューコメントが含まれている可能性があります。