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

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

このコミットは、Go言語の型チェッカーである go/types パッケージにおける潜在的なクラッシュバグを修正するものです。具体的には、型変換処理 convertUntyped 関数において、target Typenil である場合にパニック(クラッシュ)するのを防ぐための防御的なコードが追加されました。

コミット

commit 25c99300b9316e5a983af421d4d1a180b7aabf3a
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Feb 27 15:22:14 2013 -0800

    go/types: don't crash if there are no hints
    
    R=r
    CC=golang-dev
    https://golang.org/cl/7375060
---
 src/pkg/go/types/expr.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/pkg/go/types/expr.go b/src/pkg/go/types/expr.go
index 5aacb02f86..8b645e4e20 100644
--- a/src/pkg/go/types/expr.go
+++ b/src/pkg/go/types/expr.go
@@ -293,6 +293,11 @@ func (check *checker) convertUntyped(x *operand, target Type) {
 
 	// typed target
 	switch t := underlying(target).(type) {
+	case nil:
+		// We may reach here due to previous type errors.
+		// Be conservative and don't crash.
+		x.mode = invalid
+		return
 	case *Basic:
 		check.isRepresentable(x, t)
 	case *Interface:
@@ -304,6 +309,7 @@ func (check *checker) convertUntyped(x *operand, target Type) {
 		\tgoto Error
 		}
 	default:
+		check.dump("x = %v, target = %v", x, target) // leave for debugging
 		\tunreachable()
 	}

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

https://github.com/golang/go/commit/25c99300b9316e5a983af421d4d1a180b7aabf3a

元コミット内容

go/types: don't crash if there are no hints

R=r
CC=golang-dev
https://golang.org/cl/7375060

変更の背景

Go言語のコンパイラには、コードの型チェックを行うための go/types パッケージが存在します。このパッケージは、Goプログラムの構文木を解析し、各式の型がGo言語の仕様に準拠しているかを確認する重要な役割を担っています。

convertUntyped 関数は、型が明示的に指定されていない(untpyed)リテラル(例: 10"hello")を、特定のターゲット型に変換する処理を担当しています。例えば、var i int = 10 のようなコードでは、10 は untyped integer literal ですが、int 型に変換される必要があります。

このコミットが作成された時点では、convertUntyped 関数内で target Typenil になるという予期せぬ状況が発生した場合に、プログラムがパニック(クラッシュ)する可能性がありました。これは通常、型チェックの前の段階で既に何らかの型エラーが発生しており、その結果として target Type が不正な nil 値として伝播してしまった場合に起こりえます。

コンパイラは、ユーザーのコードにエラーがあっても、可能な限りクラッシュせずにエラーメッセージを報告し続けるべきです。クラッシュは、コンパイラの安定性を損ない、ユーザー体験を著しく低下させます。このコミットは、このような不安定性を解消し、より堅牢な型チェッカーを実現することを目的としています。コミットメッセージの「no hints」という表現は、おそらく型推論のヒントとなるべきターゲット型が欠落している、あるいは無効な状態であることを指していると考えられます。

前提知識の解説

  • Go言語の型システム: Go言語は静的型付け言語であり、すべての変数と式にはコンパイル時に型が決定されます。型システムは、プログラムの安全性と信頼性を保証する上で非常に重要です。
  • go/types パッケージ: Go言語の標準ライブラリの一部であり、Goプログラムの型チェックを行うためのAPIを提供します。これはGoコンパイラの重要なコンポーネントであり、IDEやリンターなどのツールでも利用されます。
  • Untyped Constants/Literals: Go言語には、型が明示的に指定されていない定数やリテラルが存在します。これらは、使用される文脈によって適切な型に変換されます。例えば、10int にも float64 にもなり得ます。
  • operand 構造体: go/types パッケージ内で、式やリテラルの評価結果を表現するために使用される内部的な構造体です。mode フィールドは、そのオペランドが有効か、無効か、あるいは特定の型を持つかなどの状態を示します。
  • Type インターフェース: go/types パッケージにおけるすべての型(int, string, struct など)を表すインターフェースです。
  • underlying(Type) 関数: Go言語の型システムにおいて、型エイリアスや定義済み型(例: type MyInt int)の基底となる型(この例では int)を取得するためのヘルパー関数です。
  • unreachable() 関数: プログラムの実行フローが到達してはならないコードパスに遭遇した場合に呼び出される関数です。通常、これは論理的なエラーや予期せぬ状態を示し、パニックを引き起こします。開発時にはデバッグに役立ちますが、リリース版ではこのような状況は避けるべきです。
  • 防御的プログラミング: 予期せぬ入力や状態に対して、プログラムがクラッシュしたり不正な動作をしたりするのを防ぐためのプログラミング手法です。このコミットは、nil チェックを追加することで防御的プログラミングを実践しています。

技術的詳細

src/pkg/go/types/expr.go ファイルは、Go言語の式(expression)の型チェックに関連するロジックを実装しています。checker 構造体は型チェックのコンテキストを保持し、convertUntyped メソッドはその一部として機能します。

修正前のコードでは、convertUntyped 関数が target Type を受け取り、その基底型(underlying(target))に基づいて switch 文で処理を分岐していました。しかし、何らかの理由(例えば、以前の型エラーが原因で targetnil になってしまった場合)で underlying(target) の結果が nil になった場合、switch 文のどの case にもマッチせず、最終的に default ブロックに到達していました。

default ブロックには unreachable() 関数が呼び出されており、これはプログラムがこのコードパスに到達することは「ありえない」という開発者の想定を示していました。しかし、target Typenil になるケースは、開発者の想定外の状況であり、この unreachable() の呼び出しがパニックを引き起こし、コンパイラをクラッシュさせていました。

このコミットは、switch 文の最初に case nil: を追加することで、この問題に対処しています。

  • target Type の基底型が nil である場合、明示的にこのケースを捕捉します。
  • コメント // We may reach here due to previous type errors. が示すように、これは以前の型エラーの結果として発生する可能性があることを認識しています。
  • x.mode = invalid を設定することで、現在のオペランド x を無効な状態にし、これ以上処理を進めないようにします。
  • return することで、関数を安全に終了させ、パニックを回避します。

また、default ブロックにはデバッグ用の check.dump("x = %v, target = %v", x, target) が追加されています。これは、将来的に同様の予期せぬ default ケースに遭遇した場合に、デバッグ情報を出力するためのものです。ただし、この行はコメントアウトされており、本番環境では有効にならないようになっています。

この修正により、go/types パッケージは、不正な nil 型が伝播してきた場合でも、クラッシュすることなく、エラーを適切に処理し、型チェックを続行できるようになりました。これはコンパイラの堅牢性を高める上で重要な改善です。

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

変更は src/pkg/go/types/expr.go ファイルの convertUntyped 関数内で行われました。

--- a/src/pkg/go/types/expr.go
+++ b/src/pkg/go/types/expr.go
@@ -293,6 +293,11 @@ func (check *checker) convertUntyped(x *operand, target Type) {
 
 	// typed target
 	switch t := underlying(target).(type) {
+	case nil:
+		// We may reach here due to previous type errors.
+		// Be conservative and don't crash.
+		x.mode = invalid
+		return
 	case *Basic:
 		check.isRepresentable(x, t)
 	case *Interface:
@@ -304,6 +309,7 @@ func (check *checker) convertUntyped(x *operand, target Type) {
 		\tgoto Error
 		}
 	default:
+		check.dump("x = %v, target = %v", x, target) // leave for debugging
 		\tunreachable()
 	}

コアとなるコードの解説

追加されたコードは switch t := underlying(target).(type) 文の最初の case です。

	case nil:
		// We may reach here due to previous type errors.
		// Be conservative and don't crash.
		x.mode = invalid
		return
  • case nil:: underlying(target) の結果が nil である場合にこのブロックが実行されます。これは、targetnil であるか、あるいは target が何らかの理由で有効な型オブジェクトを指していない場合に発生します。
  • // We may reach here due to previous type errors.: このコメントは、この nil の状態が、型チェックのより早い段階で発生したエラーの結果として生じる可能性があることを明確に示しています。これは、コンパイラがエラーを検出した後も、可能な限り処理を続行しようとする「エラー回復」のメカニズムの一部です。
  • // Be conservative and don't crash.: 防御的プログラミングの原則に従い、予期せぬ nil 値によってプログラムがクラッシュするのを防ぐための意図が示されています。
  • x.mode = invalid: 現在処理中のオペランド x のモードを invalid に設定します。これにより、このオペランドがこれ以上有効な型として扱われないようになり、後続の処理でさらなるエラーを引き起こすことを防ぎます。
  • return: convertUntyped 関数を直ちに終了させます。これにより、niltarget Type で後続の処理が実行され、パニックが発生するのを防ぎます。

また、default ケースに以下の行が追加されました。

		check.dump("x = %v, target = %v", x, target) // leave for debugging

これは、unreachable() が呼び出される直前に、現在のオペランド x とターゲット型 target の値をダンプ(出力)するためのデバッグコードです。これにより、将来的に default ケースに到達するような新たな予期せぬ状況が発生した場合に、問題の原因を特定するための貴重な情報が得られます。コメント // leave for debugging が示すように、これは開発・デバッグ目的で残されたものです。

関連リンク

  • Go言語の go/types パッケージのドキュメント: https://pkg.go.dev/go/types
  • Go言語の型システムに関する公式ドキュメントやブログ記事(当時の情報源を探すのは困難ですが、現在のGo言語の型システムに関する一般的な情報が参考になります)

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特に go/types パッケージ)
  • Go言語のコミット履歴とコードレビューシステム (Gerrit)
  • 一般的なコンパイラ設計とエラー回復に関する知識