[インデックス 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言語では、変数は使用する前に必ず宣言する必要があります。宣言されていない変数を使用しようとすると、コンパイルエラーとなります。このコミットの文脈では、型チェッカーが未定義の変数に遭遇した際に、その変数を表す operand
が invalid
モードになることが想定されます。
技術的詳細
このコミットの核心は、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
が未定義であるため、その評価結果であるx
がinvalid
になる可能性があります。z.mode == invalid
: これは代入の左辺(LHS)のオペランドz
が無効な状態であるかをチェックします。undeclared = 991
の例では、undeclared
が未定義であるため、左辺のz
がinvalid
と評価される可能性が高いです。
どちらかのオペランドが 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 CL 7369059: https://golang.org/cl/7369059
参考にした情報源リンク
- Go言語の公式ドキュメント
go/types
パッケージのソースコード- Go言語の型システムに関する一般的な知識