[インデックス 14348] ファイルの概要
本コミットは、Goコンパイラ(cmd/gc)における、不正な構造体定義が原因で発生する内部コンパイラエラー("internal compiler error: lookdot badwidth")を修正するものです。具体的には、構造体のフィールドが未定義の型を参照している場合に、コンパイラがクラッシュする問題を解決します。
コミット
commit c208a3a263648a5f2a363b3af801a9d1522be03b
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Nov 7 23:09:01 2012 +0100
cmd/gc: fix internal compiler error with broken structs.
Fixes #4359.
R=golang-dev, daniel.morsing, rsc
CC=golang-dev
https://golang.org/cl/6834043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c208a3a263648a5f2a363b3af801a9d1522be03b
元コミット内容
cmd/gc: fix internal compiler error with broken structs.
Fixes #4359.
R=golang-dev, daniel.morsing, rsc
CC=golang-dev
https://golang.org/cl/6834043
変更の背景
このコミットは、Goコンパイラが特定の不正な構造体定義を処理する際に発生する内部コンパイラエラー(ICE: Internal Compiler Error)を修正するために行われました。具体的には、構造体のフィールドが未定義の型を参照している場合、コンパイラがその不正な型を適切に処理できず、クラッシュするというバグ(Issue 4359)が存在していました。
コンパイラは、ソースコードを解析し、抽象構文木(AST)を構築し、型チェックを行い、最終的に実行可能なバイナリを生成します。このプロセスの中で、型チェックの段階で不正な型定義に遭遇した場合、コンパイラはエラーを報告して処理を停止するのが正しい挙動です。しかし、このバグでは、エラー報告の前にコンパイラ自身の内部状態が不正になり、予期せぬクラッシュを引き起こしていました。このようなICEは、コンパイラの安定性に関わる重大な問題であり、開発者体験を著しく損なうため、早急な修正が必要とされました。
前提知識の解説
Goコンパイラ (cmd/gc)
cmd/gcは、Go言語の公式コンパイラの実装の一つです。Go言語のソースコードを機械語に変換する役割を担っています。コンパイラの主要なフェーズには、字句解析、構文解析、意味解析(型チェックを含む)、中間コード生成、最適化、コード生成などがあります。本コミットが関連するのは、主に意味解析フェーズにおける型チェックの部分です。
型チェック (Type Checking)
型チェックは、プログラムが型の規則に従っているかを確認するプロセスです。Go言語は静的型付け言語であり、コンパイル時に厳密な型チェックが行われます。これにより、多くのプログラミングエラーが実行時ではなくコンパイル時に検出され、プログラムの信頼性が向上します。 型チェックの過程で、コンパイラは各変数の型、関数の引数と戻り値の型、演算子のオペランドの型などが、言語仕様に適合しているかを検証します。もし型が一致しない、あるいは未定義の型が使用されている場合、コンパイラは型エラーを報告します。
構造体 (Structs)
Go言語における構造体は、異なる型のフィールド(メンバー変数)をまとめた複合データ型です。構造体を使用することで、関連するデータを一つの論理的な単位として扱うことができます。
例:
type Person struct {
Name string
Age int
}
構造体のフィールドは、他の型を参照することができます。しかし、参照する型が未定義である場合、それは不正な構造体定義となります。
内部コンパイラエラー (Internal Compiler Error - ICE)
内部コンパイラエラーは、コンパイラ自身のコードにバグがある場合に発生するエラーです。これは、ユーザーが書いたコードの文法や型が間違っていることによるエラー(例: syntax error, undefined variable)とは異なり、コンパイラが予期せぬ状態に陥り、処理を続行できなくなったことを示します。ICEは通常、コンパイラの開発者が修正すべき問題であり、ユーザーがソースコードを修正して回避できるものではありません。
n->type->broke
Goコンパイラの内部では、型情報が構造体(C言語のType構造体など)として表現されます。brokeというフラグは、その型が何らかの理由で「壊れている」または「不正である」状態を示します。例えば、型定義が循環参照を含んでいたり、未定義の型を参照していたりする場合に、このフラグがセットされることがあります。コンパイラは、brokeフラグがセットされた型を検出した場合、それ以上その型に依存する処理を進めないようにすることで、さらなるエラーやクラッシュを防ぐ必要があります。
技術的詳細
このコミットは、src/cmd/gc/typecheck.cファイル内のOTYPE(型定義ノード)の処理ロジックを修正しています。OTYPEは、Goのソースコードでtype T struct { ... }のような型定義がコンパイラの内部表現として扱われる際のノードです。
修正前のコードでは、n->type == Tという条件で、型がT(おそらくnilまたは無効な型を示すグローバル変数)である場合にのみエラー処理にジャンプしていました。しかし、この条件だけでは不十分でした。
問題のシナリオは以下の通りです。
- ユーザーが以下のような不正な構造体を定義します。
package main type T struct { x T1 // T1 は未定義 } func f() { var t *T _ = t.x // ここでコンパイラがクラッシュ } - コンパイラが
type T struct { x T1 }を解析する際、フィールドxの型T1が未定義であることを検出します。このとき、T構造体の型情報(n->type)は、T1が不正であるという情報を内部的に保持します(例えば、n->type->brokeフラグがセットされる)。 - しかし、
n->type自体はnilではないため、修正前のif(n->type == T)の条件はfalseとなり、エラー処理にジャンプしませんでした。 - その後、
_ = t.xのようなコードで、不正な型を持つ構造体のフィールドにアクセスしようとすると、コンパイラは不正な型情報に基づいて処理を続行しようとし、結果として"internal compiler error: lookdot badwidth"のようなクラッシュを引き起こしていました。これは、コンパイラが不正な型のサイズやオフセットを計算しようとして失敗するためです。
修正は、このif文の条件にn->type->brokeを追加することで、不正な型定義が検出された場合にも早期にエラー処理に移行するように変更しました。
test/fixedbugs/issue4359.goは、このバグを再現し、修正が正しく適用されたことを検証するためのテストケースです。このテストケースは、前述の不正な構造体定義を含み、コンパイル時にundefinedエラーが期待されることを// ERROR "undefined"コメントで示しています。これにより、コンパイラがクラッシュせずに、適切なエラーメッセージを出力して終了することを確認します。
コアとなるコードの変更箇所
src/cmd/gc/typecheck.cファイルの以下の行が変更されました。
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -443,7 +443,7 @@ reswitch:
ok |= Etype;
n->op = OTYPE;
n->type = tostruct(n->list);
- if(n->type == T)
+ if(n->type == T || n->type->broke)
goto error;
n->list = nil;
break;
また、バグ修正を検証するための新しいテストファイルが追加されました。
test/fixedbugs/issue4359.go
// errorcheck
// Copyright 2012 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 4359: wrong handling of broken struct fields
// causes "internal compiler error: lookdot badwidth".
package main
type T struct {
x T1 // ERROR "undefined"
}
func f() {
var t *T
_ = t.x
}
コアとなるコードの解説
src/cmd/gc/typecheck.cの変更
変更の中心は、typecheck.c内のreswitchラベルの直下にあるOTYPEノードを処理する部分です。
n->op = OTYPE;は、現在のノードnが型定義を表すことを示しています。
n->type = tostruct(n->list);は、構造体のフィールドリスト(n->list)から実際の構造体型を構築しようとする処理です。このtostruct関数が、もしフィールドに未定義の型が含まれていれば、結果として返される型オブジェクトの内部でbrokeフラグをセットする可能性があります。
修正前のif(n->type == T)という条件は、n->typeがT(おそらくコンパイラ内部で定義された、無効な型を示す特別なポインタまたは値)と完全に一致する場合にのみエラー処理(goto error;)にジャンプしていました。しかし、tostructが不正な型を検出した場合でも、n->typeがTと等しくならないケースがありました。例えば、tostructは不正な型であっても、何らかの型オブジェクトを生成し、そのオブジェクトのbrokeフラグをセットするだけで、Tを返さないことがあります。
修正後のif(n->type == T || n->type->broke)という条件は、この問題を解決します。
n->type == T: 以前と同様に、型が完全に無効な場合を捕捉します。n->type->broke:tostruct関数が型を構築しようとしたが、その過程で何らかの不正(例: 未定義のフィールド型)を検出し、結果として生成された型オブジェクトのbrokeフラグをセットした場合を捕捉します。
この|| n->type->brokeの追加により、不正な構造体定義によって生成された「壊れた」型オブジェクトが検出された場合でも、コンパイラは早期にgoto error;にジャンプし、適切なエラーメッセージを出力して処理を停止するようになります。これにより、後続の処理で不正な型情報に基づいて計算が行われ、内部コンパイラエラーが発生するのを防ぎます。
test/fixedbugs/issue4359.goの追加
このテストファイルは、修正が正しく機能することを確認するためのものです。
// errorcheck: このコメントは、このファイルがコンパイルエラーを期待するテストであることをgo testツールに伝えます。type T struct { x T1 // ERROR "undefined" }: ここでT1はどこにも定義されていない型です。コンパイラはx T1の行で「undefined」エラーを報告することが期待されます。func f() { var t *T; _ = t.x }: この部分は、元のバグがトリガーされたコードパスを再現しています。不正な構造体Tのフィールドxにアクセスしようとすることで、修正前のコンパイラではICEが発生していました。修正後は、T1が未定義であることによる型エラーが適切に報告され、ICEは発生しなくなります。
このテストの追加により、将来的に同様の回帰バグが発生することを防ぐことができます。
関連リンク
- Go言語のIssueトラッカー: https://github.com/golang/go/issues/4359
- Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/6834043
参考にした情報源リンク
- Go言語のIssue 4359: https://github.com/golang/go/issues/4359
- Go言語のGerrit変更セット 6834043: https://golang.org/cl/6834043
- Go言語のコンパイラに関する一般的な情報 (例:
cmd/gcの構造):- "The Go Programming Language Specification": https://go.dev/ref/spec
- "Go compiler internals" (非公式な解説やブログ記事など): 検索エンジンで「Go compiler internals」や「Go cmd/gc source code」などで検索すると、より詳細な情報が見つかる場合があります。
- C言語のポインタと構造体に関する一般的な知識。
- コンパイラの型チェックに関する一般的な知識。