[インデックス 18723] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における内部クラッシュを修正するものです。具体的には、copy
組み込み関数の型チェック処理において、特定の不正な引数の組み合わせが与えられた際に発生していたコンパイラのパニックを解消します。この修正により、コンパイラはクラッシュする代わりに、適切な型エラーを報告するようになります。
コミット
commit 56b983c112ddca28cf29e4d1b0ab9f590ea69976
Author: Russ Cox <rsc@golang.org>
Date: Mon Mar 3 19:55:40 2014 -0500
cmd/gc: fix internal crash
TBR=ken2
CC=golang-codereviews
https://golang.org/cl/70200053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/56b983c112ddca28cf29e4d1b0ab9f590ea69976
元コミット内容
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 05efab4040..21021def95 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -1358,6 +1358,8 @@ reswitch:
gogo error;
defaultlit(&n->left, T);
defaultlit(&n->right, T);
+ if(n->left->type == T || n->right->type == T)
+ goto error;
// copy([]byte, string)
if(isslice(n->left->type) && n->right->type->etype == TSTRING) {
diff --git a/test/fixedbugs/issue7310.go b/test/fixedbugs/issue7310.go
new file mode 100644
index 0000000000..4a535a1fcc
--- /dev/null
+++ b/test/fixedbugs/issue7310.go
@@ -0,0 +1,15 @@
+// errorcheck
+
+// Copyright 2014 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.
+
+// Internal compiler crash used to stop errors during second copy.
+
+package main
+
+func main() {
+ _ = copy(nil, []int{}) // ERROR "use of untyped nil"
+ _ = copy([]int{}, nil) // ERROR "use of untyped nil"
+ _ = 1+true // ERROR "cannot convert true" "mismatched types int and bool"
+}
変更の背景
このコミットの背景には、Goコンパイラ(cmd/gc
)がcopy
組み込み関数の引数に対して不適切な型が与えられた際に、内部的なクラッシュ(パニック)を引き起こすバグが存在していました。特に、copy
関数の引数に型付けされていないnil
が渡された場合に問題が発生していました。
Go言語では、nil
は特定の型を持たない「型付けされていない(untyped)」値として扱われることがあります。例えば、nil
はインターフェース、スライス、マップ、チャネル、関数、ポインタなどのゼロ値として使用されますが、それ自体は具体的な型を持ちません。コンパイラは、このような型付けされていないnil
が使用される文脈に基づいて、その型を推論または決定する必要があります。
問題となっていたのは、copy(nil, []int{})
やcopy([]int{}, nil)
のように、copy
関数の引数の一方または両方に型付けされていないnil
が渡された場合です。コンパイラの型チェックロジックは、これらのケースを適切に処理できず、内部的な不整合を引き起こし、最終的にコンパイラが異常終了していました。
このクラッシュは、開発者がGoプログラムをコンパイルする際に予期せぬコンパイラエラーとして現れ、開発体験を損なうものでした。コンパイラは、不正なコードに対しては明確なエラーメッセージを出すべきであり、内部クラッシュは避けるべきです。このコミットは、このようなコンパイラの堅牢性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラの概念に関する知識が必要です。
-
Go言語の
copy
組み込み関数:copy
はGo言語の組み込み関数の一つで、ソーススライスからデスティネーションスライスへ要素をコピーするために使用されます。そのシグネチャはcopy(dst, src []Type) int
の形式で、コピーされた要素の数を返します。dst
とsrc
は同じ要素型を持つスライスである必要があります。 -
Goコンパイラの型チェック: Goコンパイラ(
cmd/gc
)は、ソースコードがGo言語の型システム規則に準拠しているかを検証する「型チェック」フェーズを持ちます。このフェーズでは、変数、関数呼び出し、演算子などの型が互換性があるか、正しい文脈で使用されているかなどが確認されます。型チェックは、プログラムが実行される前に多くのエラーを捕捉し、プログラムの信頼性を高める上で非常に重要です。 -
型付けされていない
nil
: Go言語において、nil
は特定の型を持たない特殊なリテラルです。これは、ポインタ、チャネル、関数、インターフェース、マップ、スライスのゼロ値として使用されます。例えば、var s []int = nil
のようにスライスに代入されると、nil
は[]int
型に型付けされます。しかし、copy(nil, []int{})
のように、型推論が困難な文脈でnil
が直接使用される場合、コンパイラはnil
の型を決定する必要があります。この「型付けされていないnil
」の扱いは、コンパイラにとって複雑な課題となることがあります。 -
defaultlit
関数: Goコンパイラの内部では、defaultlit
のような関数が、型付けされていないリテラル(数値リテラルやnil
など)に対して、その文脈に応じたデフォルトの型を割り当てる役割を担います。例えば、1
という数値リテラルは、int
、int32
、float64
など、様々な型に型付けされ得ますが、defaultlit
は周囲のコードに基づいて最も適切な型を決定しようとします。nil
に対しても同様に、スライスやインターフェースなどの型を推論しようとします。 -
コンパイラの内部クラッシュ(パニック): コンパイラは、Goプログラムを機械語に変換するソフトウェアです。通常、不正なGoコードが与えられた場合、コンパイラはエラーメッセージを出力して処理を停止します。しかし、コンパイラ自身の内部ロジックにバグがある場合、予期せぬ状態に陥り、Goランタイムのパニックを引き起こして異常終了することがあります。これはコンパイラのバグであり、修正されるべき挙動です。
技術的詳細
このコミットは、Goコンパイラのsrc/cmd/gc/typecheck.c
ファイル内のtypecheck
関数(またはその関連ロジック)におけるOCALL
ノード(関数呼び出しを表す抽象構文木ノード)の処理、特にcopy
組み込み関数の型チェックロジックに焦点を当てています。
既存のコードでは、copy
関数の引数(n->left
とn->right
)に対してdefaultlit
関数が呼び出され、型付けされていないリテラル(特にnil
)に型を割り当てようとします。しかし、defaultlit
が型を決定できなかった場合、そのノードの型は特殊な内部表現であるT
(またはtypes.T
)のまま残ることがあります。これは、型チェックがまだ完了していない、または型が不明であることを示すコンパイラ内部の状態です。
問題は、defaultlit
が型を決定できなかったにもかかわらず、その後のcopy
関数の引数に対する型チェックロジックが、引数が有効な型を持っていることを前提として処理を進めてしまう点にありました。具体的には、isslice(n->left->type)
のような型チェック関数が、n->left->type
がT
である場合に予期せぬ挙動を示し、コンパイラの内部クラッシュを引き起こしていました。
このコミットによる修正は、defaultlit
の呼び出し後、copy
関数の引数(n->left
とn->right
)の型がまだT
であるかどうかを明示的にチェックするガード句を追加することで、この問題を解決します。
if(n->left->type == T || n->right->type == T)
goto error;
このコードは、n->left
またはn->right
のいずれかの型がT
(つまり、defaultlit
が型を決定できなかった)である場合、直ちにgoto error;
を実行し、コンパイラが適切な型エラーを報告するようにします。これにより、コンパイラは不正な型を持つ引数でcopy
関数の型チェックを続行しようとせず、内部クラッシュを回避できます。
追加されたテストケースtest/fixedbugs/issue7310.go
は、この修正が意図通りに機能することを確認します。
_ = copy(nil, []int{})
_ = copy([]int{}, nil)
これらの行は、copy
関数の引数に型付けされていないnil
を渡し、コンパイラが「use of untyped nil
」というエラーを正しく報告することを確認します。修正前は、これらのケースでコンパイラがクラッシュしていました。
また、_ = 1+true
という行もテストケースに含まれていますが、これは別の型エラー(mismatched types int and bool
)を意図的に発生させるものであり、このコミットの主要な修正対象であるcopy
関数のクラッシュとは直接関係ありませんが、コンパイラの一般的なエラー報告能力をテストする目的で含まれていると考えられます。
コアとなるコードの変更箇所
変更はsrc/cmd/gc/typecheck.c
ファイル内のtypecheck
関数(または関連する型チェックロジック)のOCALL
ノード処理部分にあります。
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -1358,6 +1358,8 @@ reswitch:
gogo error;
defaultlit(&n->left, T);
defaultlit(&n->right, T);
+ if(n->left->type == T || n->right->type == T)
+ goto error;
// copy([]byte, string)
if(isslice(n->left->type) && n->right->type->etype == TSTRING) {
具体的には、defaultlit
関数がn->left
とn->right
に対して呼び出された直後に、以下の2行が追加されています。
if(n->left->type == T || n->right->type == T)
goto error;
コアとなるコードの解説
追加されたコードは、copy
組み込み関数の型チェックロジックの一部として機能します。
defaultlit(&n->left, T);
defaultlit(&n->right, T);
これらの行は、copy
関数の第一引数(n->left
)と第二引数(n->right
)が型付けされていないリテラル(例えばnil
)である場合に、コンパイラがそのリテラルに適切な型を割り当てようと試みる処理です。T
は、型がまだ決定されていないことを示す内部的なプレースホルダー型です。
その直後に追加された以下のif
文が、このコミットの核心的な修正です。
if(n->left->type == T || n->right->type == T)
goto error;
このif
文は、defaultlit
が呼び出された後でも、copy
関数の引数であるn->left
またはn->right
のいずれかの型が依然としてT
であるかどうかをチェックします。
n->left->type == T
:copy
関数の第一引数の型がまだ決定されていない場合。n->right->type == T
:copy
関数の第二引数の型がまだ決定されていない場合。
これらの条件のいずれかが真である場合、それはdefaultlit
が引数に有効な型を割り当てることができなかったことを意味します。このような状況で型チェックを続行すると、コンパイラの内部ロジックが不正な状態に陥り、クラッシュする可能性がありました。
goto error;
は、コンパイラが現在の型チェック処理を中断し、エラー処理ルーチンにジャンプすることを示します。これにより、コンパイラは内部クラッシュを回避し、代わりにユーザーに対して「use of untyped nil
」のような適切な型エラーメッセージを出力するようになります。
この修正は、コンパイラの堅牢性を高め、不正な入力に対する回復力を向上させる上で非常に重要です。
関連リンク
- Go CL 70200053: https://golang.org/cl/70200053
参考にした情報源リンク
- Go言語の
copy
組み込み関数に関する公式ドキュメント: https://pkg.go.dev/builtin#copy - Go言語の
nil
に関する解説(例: Go by Example - Nil): https://gobyexample.com/nil - Goコンパイラの内部構造に関する一般的な情報(例: Go Compiler Internals): https://go.dev/doc/articles/go-compiler-internals (これは一般的な情報源であり、特定のコミットに直接関連するものではありませんが、背景知識として有用です。)
- Go言語の型システムに関する情報: https://go.dev/ref/spec#Types
- Go言語の
errorcheck
テストディレクティブに関する情報(Goのテストフレームワークの一部): https://go.dev/cmd/go/#hdr-Test_packages (特に// errorcheck
コメントの挙動について) - Go言語の
defaultlit
の概念に関する議論(GoのIssueやメーリングリストなど): 関連する具体的なリンクは特定できませんでしたが、Goコンパイラの型推論とリテラル処理に関する議論は、Goコミュニティ内で頻繁に行われています。