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

[インデックス 18319] ファイルの概要

このコミットは、Goコンパイラの型チェックロジックにおけるバグ修正に関するものです。具体的には、多重代入におけるnil型の型チェックの挙動を修正し、issue #6572で報告された問題を解決します。

コミット

commit 062ae4571168608ca2bcd6fa1a9d122a20a5d677
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date:   Tue Jan 21 22:44:54 2014 -0500

    cmd/gc: do not typecheck nil types in multiple assignment
    
    Fixes #6572.
    
    LGTM=rsc, daniel.morsing, rsc
    R=golang-codereviews, bradfitz, minux.ma, iant, rsc, gobot, daniel.morsing
    CC=golang-codereviews
    https://golang.org/cl/14516055

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/062ae4571168608ca2bcd6fa1a9d122a20a5d677

元コミット内容

commit 062ae4571168608ca2bcd6fa1a9d122a20a5d677
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date:   Tue Jan 21 22:44:54 2014 -0500

    cmd/gc: do not typecheck nil types in multiple assignment
    
    Fixes #6572.
    
    LGTM=rsc, daniel.morsing, rsc
    R=golang-codereviews, bradfitz, minux.ma, iant, rsc, gobot, daniel.morsing
    CC=golang-codereviews
    https://golang.org/cl/14516055
---
 src/cmd/gc/typecheck.c      |  2 +-\n test/fixedbugs/issue6572.go | 21 +++++++++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 68d2c3404d..3c27d99154 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2814,7 +2814,7 @@ typecheckas2(Node *n)\n 		tn->op = OAS2FUNC;\n 		tt = structfirst(&s, &r->type);\n 		for(ll=n->list; ll; ll=ll->next) {\n-			if(ll->n->type != T)\n+			if(t->type != T && ll->n->type != T)\n 				checkassignto(t->type, ll->n);\n 			if(ll->n->defn == n && ll->n->ntype == N)\n 				ll->n->type = t->type;\ndiff --git a/test/fixedbugs/issue6572.go b/test/fixedbugs/issue6572.go
new file mode 100644
index 0000000000..e75da54c99 100644
--- /dev/null
+++ b/test/fixedbugs/issue6572.go
@@ -0,0 +1,21 @@
+// 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.
+
+package main
+
+func foo() (T, T) { // ERROR "undefined"
+	return 0, 0
+}
+
+func bar() (T, string, T) { // ERROR "undefined"
+	return 0, "", 0
+}
+
+func main() {
+	var x, y, z int
+	x, y = foo()
+	x, y, z = bar() // ERROR "cannot (use type|assign) string"
+}

変更の背景

このコミットは、Goコンパイラ(cmd/gc)における多重代入の型チェックに関するバグを修正します。具体的には、issue #6572で報告された問題に対応しています。

issue #6572は、関数が複数の値を返す際に、その戻り値の中に未定義の型(T)が含まれている場合、コンパイラが誤った型チェックを行うというものでした。特に、nil型(Goコンパイラの内部表現でTとして扱われることがあります)が絡む多重代入において、不適切なエラーが発生する可能性がありました。

このバグにより、本来はコンパイルが成功すべきコードがエラーになったり、あるいは逆に、型エラーがあるべきコードが誤ってコンパイルされてしまうといった問題が引き起こされる可能性がありました。このコミットは、このような誤った型チェックの挙動を修正し、コンパイラの堅牢性と正確性を向上させることを目的としています。

前提知識の解説

このコミットの理解には、以下のGo言語およびコンパイラの概念に関する知識が役立ちます。

  • 多重代入 (Multiple Assignment): Go言語の大きな特徴の一つで、複数の変数に複数の値を一度に代入できる機能です。例えば、x, y = foo() のように、関数が複数の戻り値を返す場合や、複数の変数を同時に初期化する場合に利用されます。
  • 型チェック (Type Checking): コンパイラの重要なフェーズの一つで、プログラム中の各式や変数の型がGo言語の型システム規則に適合しているかを確認するプロセスです。型チェックにより、型に関する誤り(例: 整数型変数に文字列を代入しようとする)をコンパイル時に検出できます。
  • nil型とGoコンパイラの内部表現: Go言語においてnilは、ポインタ、スライス、マップ、チャネル、インターフェース、関数などのゼロ値を表すために使用されます。コンパイラの内部では、型がまだ決定されていない、あるいは特定の型に依存しない汎用的な「型なし」の状態を示すために、特別な内部型が使用されることがあります。このコミットの文脈では、Tという内部表現がnil型や未定義の型を示すために使われている可能性があります。
  • src/cmd/gc/typecheck.c: Goコンパイラのソースコードの一部で、C言語で書かれた型チェックのロジックを実装しているファイルです。Goコンパイラは、初期の段階ではC言語で書かれており、その後Go言語自身で書かれたコンパイラ(gc)に移行しました。このファイルは、その過渡期または初期のコンパイラの一部を示しています。
  • checkassignto関数: typecheck.c内で定義されている関数で、ある型から別の型への代入が有効であるかをチェックする役割を担っています。

技術的詳細

このバグは、src/cmd/gc/typecheck.c内のtypecheckas2関数、特に多重代入を処理する部分で発生していました。typecheckas2関数は、x, y = foo()のような多重代入文を型チェックする際に呼び出されます。

元のコードでは、多重代入の右辺(foo()の戻り値など)の型と、左辺の各変数の型を比較し、代入の妥当性をcheckassignto関数で検証していました。しかし、このチェックがll->n->type != Tという条件で行われていました。ここでTは、Goコンパイラの内部で「型なし」または「nil型」を示す特別な型として扱われることがあります。

問題は、右辺の戻り値の型(t->type)がTである場合(つまり、nil型や未定義の型である場合)に、左辺の変数(ll->n->type)がTでないにもかかわらず、checkassigntoが呼び出されてしまうことでした。これは、nil型が特定の型に代入される際に、その型がまだ確定していないにもかかわらず、不必要な型チェックが実行され、結果として誤ったエラー(例: undefinedエラーやcannot assign stringエラー)が発生する原因となっていました。

修正は、checkassigntoを呼び出す条件をif(t->type != T && ll->n->type != T)に変更することです。この変更により、右辺の戻り値の型(t->type)がTである場合、または左辺の変数の型(ll->n->type)がTである場合には、checkassignto関数による厳密な型チェックをスキップするようになります。これにより、nil型が絡む多重代入において、型がまだ確定していない状態での不適切な型チェックが回避され、コンパイラが正しい挙動を示すようになります。

test/fixedbugs/issue6572.goという新しいテストファイルが追加されており、このテストは修正前のコンパイラではエラーとなるが、修正後のコンパイラでは正しく動作することを確認します。このテストケースは、foo()bar()のような関数が未定義の型Tを返す場合に、多重代入がどのように振る舞うかを検証しています。

コアとなるコードの変更箇所

src/cmd/gc/typecheck.cファイルのtypecheckas2関数内の以下の行が変更されました。

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2814,7 +2814,7 @@ typecheckas2(Node *n)\
 		tn->op = OAS2FUNC;\
 		tt = structfirst(&s, &r->type);\
 		for(ll=n->list; ll; ll=ll->next) {\
-			if(ll->n->type != T)\
+			if(t->type != T && ll->n->type != T)\
 				checkassignto(t->type, ll->n);\
 			if(ll->n->defn == n && ll->n->ntype == N)\
 				ll->n->type = t->type;

コアとなるコードの解説

変更された行は、多重代入の各要素に対する型チェックの条件を修正しています。

  • 変更前: if(ll->n->type != T)

    • この条件は、「左辺の変数の型がT(型なし/nil型)でない場合」にcheckassignto関数を呼び出して型チェックを行う、という意味でした。
    • 問題は、右辺の戻り値の型(t->type)がTであるにもかかわらず、左辺の変数の型がTでない場合に、不適切な型チェックが行われてしまうことでした。例えば、func foo() (T, T)のような関数から返されるTnil型として扱われる場合、そのnilint型の変数に代入しようとすると、checkassigntoが呼び出され、誤ったエラーが発生する可能性がありました。
  • 変更後: if(t->type != T && ll->n->type != T)

    • この新しい条件は、「右辺の戻り値の型がTなくかつ左辺の変数の型もTない場合」にのみcheckassignto関数を呼び出す、という意味になります。
    • これにより、右辺の戻り値の型がTである場合(つまり、nil型や未定義の型である場合)には、左辺の変数の型が何であってもcheckassigntoは呼び出されなくなります。これは、nil型が特定の型に代入される際に、その型がまだ確定していない状態での不必要な型チェックを回避するために重要です。
    • この修正により、コンパイラはnil型が絡む多重代入において、より正確な型推論と型チェックを行うことができるようになり、issue #6572で報告されたような誤ったエラーの発生を防ぎます。

関連リンク

参考にした情報源リンク