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

[インデックス 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言語およびコンパイラの概念に関する知識が必要です。

  1. Go言語のcopy組み込み関数: copyはGo言語の組み込み関数の一つで、ソーススライスからデスティネーションスライスへ要素をコピーするために使用されます。そのシグネチャはcopy(dst, src []Type) intの形式で、コピーされた要素の数を返します。dstsrcは同じ要素型を持つスライスである必要があります。

  2. Goコンパイラの型チェック: Goコンパイラ(cmd/gc)は、ソースコードがGo言語の型システム規則に準拠しているかを検証する「型チェック」フェーズを持ちます。このフェーズでは、変数、関数呼び出し、演算子などの型が互換性があるか、正しい文脈で使用されているかなどが確認されます。型チェックは、プログラムが実行される前に多くのエラーを捕捉し、プログラムの信頼性を高める上で非常に重要です。

  3. 型付けされていないnil: Go言語において、nilは特定の型を持たない特殊なリテラルです。これは、ポインタ、チャネル、関数、インターフェース、マップ、スライスのゼロ値として使用されます。例えば、var s []int = nilのようにスライスに代入されると、nil[]int型に型付けされます。しかし、copy(nil, []int{})のように、型推論が困難な文脈でnilが直接使用される場合、コンパイラはnilの型を決定する必要があります。この「型付けされていないnil」の扱いは、コンパイラにとって複雑な課題となることがあります。

  4. defaultlit関数: Goコンパイラの内部では、defaultlitのような関数が、型付けされていないリテラル(数値リテラルやnilなど)に対して、その文脈に応じたデフォルトの型を割り当てる役割を担います。例えば、1という数値リテラルは、intint32float64など、様々な型に型付けされ得ますが、defaultlitは周囲のコードに基づいて最も適切な型を決定しようとします。nilに対しても同様に、スライスやインターフェースなどの型を推論しようとします。

  5. コンパイラの内部クラッシュ(パニック): コンパイラは、Goプログラムを機械語に変換するソフトウェアです。通常、不正なGoコードが与えられた場合、コンパイラはエラーメッセージを出力して処理を停止します。しかし、コンパイラ自身の内部ロジックにバグがある場合、予期せぬ状態に陥り、Goランタイムのパニックを引き起こして異常終了することがあります。これはコンパイラのバグであり、修正されるべき挙動です。

技術的詳細

このコミットは、Goコンパイラのsrc/cmd/gc/typecheck.cファイル内のtypecheck関数(またはその関連ロジック)におけるOCALLノード(関数呼び出しを表す抽象構文木ノード)の処理、特にcopy組み込み関数の型チェックロジックに焦点を当てています。

既存のコードでは、copy関数の引数(n->leftn->right)に対してdefaultlit関数が呼び出され、型付けされていないリテラル(特にnil)に型を割り当てようとします。しかし、defaultlitが型を決定できなかった場合、そのノードの型は特殊な内部表現であるT(またはtypes.T)のまま残ることがあります。これは、型チェックがまだ完了していない、または型が不明であることを示すコンパイラ内部の状態です。

問題は、defaultlitが型を決定できなかったにもかかわらず、その後のcopy関数の引数に対する型チェックロジックが、引数が有効な型を持っていることを前提として処理を進めてしまう点にありました。具体的には、isslice(n->left->type)のような型チェック関数が、n->left->typeTである場合に予期せぬ挙動を示し、コンパイラの内部クラッシュを引き起こしていました。

このコミットによる修正は、defaultlitの呼び出し後、copy関数の引数(n->leftn->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->leftn->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言語の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コミュニティ内で頻繁に行われています。