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

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

このコミットは、Go言語の型チェッカー (go/types パッケージ) において、未定義の変数に値を代入しようとした際に発生するクラッシュを防ぐための修正です。具体的には、assign1to1 関数内で、代入の左右のオペランドのいずれかが無効な状態 (invalid モード) である場合に、それ以上の処理を行わずに早期リターンすることで、パニックを回避します。

コミット

commit 029457aab54c8c8ae25aaf51725d002aaba8749c
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Feb 27 14:24:41 2013 -0800

    go/types: don't crash when assigning to undefined variables
    
    R=adonovan
    CC=golang-dev
    https://golang.org/cl/7369059

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

https://github.com/golang/go/commit/029457aab54c8c8ae25aaf51725d002aaba8749c

元コミット内容

go/types: 未定義の変数に代入する際にクラッシュしないようにする

変更の背景

Go言語のコンパイラやツールチェーンの一部である go/types パッケージは、Goプログラムの型チェックを担当しています。型チェックの過程で、プログラム内の様々な操作(変数宣言、代入、関数呼び出しなど)がGo言語の型システム規則に準拠しているか検証します。

このコミットが導入される前は、未定義の変数に対して値を代入しようとすると、go/types パッケージの内部でパニック(クラッシュ)が発生する可能性がありました。これは、型チェックのロジックが、無効な(invalid)状態のオペランドを適切に処理できていなかったためと考えられます。コンパイラや型チェッカーがクラッシュすることは、ユーザー体験を著しく損なうだけでなく、開発プロセスを中断させる深刻な問題です。

この修正は、このような不正な状態(未定義変数への代入)が検出された際に、型チェッカーがクラッシュするのではなく、エラーとして適切に処理し、安全に続行できるようにすることを目的としています。これにより、型チェッカーの堅牢性が向上し、より安定した開発環境が提供されます。

前提知識の解説

Go言語の型システムと型チェック

Go言語は静的型付け言語であり、プログラムの実行前にすべての変数の型が決定されます。型チェックは、プログラムが型規則に違反していないかを確認するプロセスです。これにより、多くのバグが早期に発見され、プログラムの信頼性が向上します。

go/types パッケージ

go/types パッケージは、Go言語の標準ライブラリの一部であり、Goプログラムの型情報を表現し、型チェックを行うためのAPIを提供します。これは、Goコンパイラ、go vet などの静的解析ツール、IDEなどが内部的に利用しています。

  • ast (Abstract Syntax Tree): Goのソースコードは、まず抽象構文木(AST)にパースされます。ASTは、プログラムの構造を木構造で表現したものです。
  • operand: go/types パッケージ内で、式や変数の評価結果を表す内部的な構造体です。これには、その値の型、モード(例: variable, constant, type, invalid など)、そして具体的な値(定数の場合)などの情報が含まれます。
  • invalid モード: operand のモードの一つで、そのオペランドが型チェックの観点から無効な状態であることを示します。例えば、未定義の変数や型エラーのある式の結果などが invalid となります。
  • checker: go/types パッケージにおける型チェックの主要なコンポーネントです。ASTを走査し、型規則に基づいて各ノードを検証します。
  • assign1to1 関数: この関数は、Go言語における単一の代入(例: x = y)の型チェックを担当します。左辺(LHS: Left Hand Side)と右辺(RHS: Right Hand Side)の式を受け取り、それらの型が代入規則に適合しているかを検証します。

未定義変数

Go言語では、変数は使用する前に必ず宣言する必要があります。宣言されていない変数を使用しようとすると、コンパイルエラーとなります。このコミットの文脈では、型チェッカーが未定義の変数に遭遇した際に、その変数を表す operandinvalid モードになることが想定されます。

技術的詳細

このコミットの核心は、src/pkg/go/types/stmt.go ファイル内の assign1to1 関数に対する変更です。この関数は、Go言語の代入文 lhs = rhs の型チェックロジックを実装しています。

変更前は、lhs または rhs のいずれかが invalid モードの operand であった場合、その後の処理で予期せぬ状態に陥り、パニックを引き起こす可能性がありました。これは、invalid なオペランドが持つべきではないプロパティにアクセスしようとしたり、無効な型情報に基づいて不適切な型推論を行ったりすることが原因と考えられます。

追加されたコードは以下の通りです。

		if x.mode == invalid || z.mode == invalid {
			return
		}

ここで、x は右辺(RHS)のオペランド、z は左辺(LHS)のオペランドを表します。このガード節は、代入処理の早い段階で、左右のオペランドのいずれかが invalid モードであるかをチェックします。もしどちらかが invalid であれば、それ以上の代入に関する型チェック処理は不要であり、かつ危険であるため、関数を即座に終了(return)します。

これにより、未定義変数への代入のような不正なシナリオが発生した場合でも、型チェッカーはクラッシュすることなく、適切なエラーメッセージを生成して処理を続行できるようになります。

また、src/pkg/go/types/testdata/stmt0.src に追加されたテストケースは、この修正が正しく機能することを確認するためのものです。

	undeclared /* ERROR "undeclared" */ = 991

このテストケースは、意図的に未宣言の変数 undeclared に値を代入しようとしています。このコードはコンパイルエラーになるべきであり、型チェッカーがこの状況でクラッシュしないことを検証します。/* ERROR "undeclared" */ コメントは、この行で undeclared というエラーメッセージが期待されることを示しています。

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

diff --git a/src/pkg/go/types/stmt.go b/src/pkg/go/types/stmt.go
index 730b0608ee..65b12a01ef 100644
--- a/src/pkg/go/types/stmt.go
+++ b/src/pkg/go/types/stmt.go
@@ -62,6 +62,10 @@ func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota
 			}
 		}
 
+		if x.mode == invalid || z.mode == invalid {
+			return
+		}
+
 		check.assignOperand(&z, x)
 		if x.mode != invalid && z.mode == constant {
 			check.errorf(x.pos(), "cannot assign %s to %s", x, &z)
diff --git a/src/pkg/go/types/testdata/stmt0.src b/src/pkg/go/types/testdata/stmt0.src
index 37610d3ddd..d4e08f6c0d 100644
--- a/src/pkg/go/types/testdata/stmt0.src
+++ b/src/pkg/go/types/testdata/stmt0.src
@@ -32,6 +32,8 @@ func _() {\n 
 	var u64 uint64
 	u64 += 1<<u64
+\
+	undeclared /* ERROR "undeclared" */ = 991
 }\
 
 func _incdecs() {\

コアとなるコードの解説

src/pkg/go/types/stmt.go の変更

assign1to1 関数内に以下の4行が追加されました。

		if x.mode == invalid || z.mode == invalid {
			return
		}
  • x.mode == invalid: これは代入の右辺(RHS)のオペランド x が無効な状態であるかをチェックします。例えば、undeclared = 991 の場合、991 は有効なリテラルですが、undeclared が未定義であるため、その評価結果である xinvalid になる可能性があります。
  • z.mode == invalid: これは代入の左辺(LHS)のオペランド z が無効な状態であるかをチェックします。undeclared = 991 の例では、undeclared が未定義であるため、左辺の zinvalid と評価される可能性が高いです。

どちらかのオペランドが invalid であれば、代入の型チェックをこれ以上進めても意味がなく、むしろパニックを引き起こす可能性があるため、return で関数を終了します。これにより、型チェッカーは安全にエラーを報告し、処理を続行できます。

src/pkg/go/types/testdata/stmt0.src の変更

テストファイル stmt0.src に以下の行が追加されました。

	undeclared /* ERROR "undeclared" */ = 991

この行は、undeclared という名前の変数が事前に宣言されていない状態で、それに値 991 を代入しようとするGoコードスニペットです。

  • undeclared: この変数はどこでも宣言されていないため、型チェッカーはこれを未定義として扱います。
  • /* ERROR "undeclared" */: これは go/types パッケージのテストフレームワークが使用する特別なコメントです。この行で undeclared という文字列を含むエラーメッセージが生成されることを期待していることを示します。もしこのエラーが生成されなかったり、型チェッカーがクラッシュしたりした場合、テストは失敗します。

このテストケースの追加により、未定義変数への代入という特定のシナリオにおいて、型チェッカーがクラッシュせずに適切なエラーを報告するようになったことが保証されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • go/types パッケージのソースコード
  • Go言語の型システムに関する一般的な知識