[インデックス 15551] ファイルの概要
このコミットは、Go言語の型チェッカーである go/types
パッケージにおける堅牢性の向上を目的としています。特に、不正なプログラムが入力された際の挙動を改善し、パニックや誤った型推論を防ぐためのチェックが追加されています。
コミット
commit 83c8cc54361b075e6ec9d0d62d3c29803fbbf594
Author: Robert Griesemer <gri@golang.org>
Date: Fri Mar 1 17:39:22 2013 -0800
go/types: fixed a few failure checks
More robustness in case of incorrect programs.
Fixes #4962.
R=dsymonds
CC=golang-dev
https://golang.org/cl/7429047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/83c8cc54361b075e6ec9d0d62d3c29803fbbf594
元コミット内容
このコミットは、go/types
パッケージにおいて、いくつかの「失敗チェック (failure checks)」を修正し、不正なプログラムに対する堅牢性を高めることを目的としています。具体的には、型チェックの過程で無効な状態(invalid
モード)のオペランドが検出された場合に、早期に処理を終了したり、無効な状態を適切に伝播させたりするロジックが追加されています。これにより、不正な入力によって型チェッカーがパニックを起こしたり、意味のない計算を続けたりすることを防ぎます。
この変更は、Go issue #4962 を修正するものです。
変更の背景
Go言語のコンパイラやツールチェーンにおいて、型チェッカーはプログラムの正当性を検証する非常に重要なコンポーネントです。しかし、ユーザーが記述するプログラムは常に文法的に正しく、型的に健全であるとは限りません。特に開発中のコードや、意図的に不正なコードを記述した場合など、型チェッカーは不正な入力に対しても安定して動作し、適切なエラーを報告する必要があります。
このコミットが行われた2013年当時、go/types
パッケージはまだ比較的新しい段階にあり、様々なエッジケースや不正な入力に対する堅牢性が継続的に改善されていました。Issue #4962 は、特定の不正なプログラムが go/types
パッケージ内でパニックを引き起こす問題として報告されており、このコミットはその問題を解決し、より安定した型チェックプロセスを提供するために導入されました。
具体的には、型チェックの途中で invalid
とマークされたオペランド(式の結果など)が後続の処理に渡された際に、その invalid
状態が適切にチェックされず、予期せぬエラーやパニックが発生する可能性がありました。このコミットは、このような状況を特定し、invalid
状態のオペランドを早期に検出し、それ以上の無意味な処理を避けることで、型チェッカー全体の安定性を向上させています。
前提知識の解説
go/types
パッケージ
go/types
はGo言語の標準ライブラリの一部であり、Goプログラムの型チェックを行うためのパッケージです。Goコンパイラや各種Goツール(go vet
、gopls
など)の基盤として利用されています。このパッケージは、Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として解析した後、各ノードの型情報を解決し、Go言語の型システム規則に違反していないかを検証します。
go/types
の主要な役割は以下の通りです。
- 型推論: 変数や式の型を決定します。
- 型チェック: 型の互換性、代入可能性、演算の正当性などを検証します。
- スコープ解決: 変数や関数の定義と使用を紐付けます。
- エラー報告: 型システム違反を検出した場合に、詳細なエラーメッセージを生成します。
operand
構造体と mode
フィールド
go/types
パッケージでは、式の結果や値の情報を operand
構造体で表現します。この operand
構造体には、その値がどのような状態にあるかを示す mode
フィールドが含まれています。mode
は operandMode
型の列挙型で、以下のような値を取ります(一部抜粋):
invalid
: オペランドが無効な状態であることを示します。これは、構文エラー、型エラー、未定義の識別子など、何らかの問題によって式の評価が失敗したことを意味します。value
: オペランドが有効な値であることを示します。type_
: オペランドが型であることを示します(例:int
,string
)。var_
: オペランドが変数であることを示します。
型チェックの過程で、不正な式や未解決の参照に遭遇した場合、その結果の operand
は invalid
モードに設定されます。この invalid
モードは、後続の型チェック処理において、そのオペランドが信頼できない、あるいは処理を継続すべきではないことを示すシグナルとして機能します。
goto Error
Go言語では goto
ステートメントの使用は一般的ではありませんが、コンパイラや低レベルのコードでは、エラーハンドリングや特定の状態遷移を効率的に行うために使用されることがあります。このコミットのコードでは、goto Error
が使用されており、これは現在の関数内で定義された Error
ラベルにジャンプし、エラー処理ロジックを実行することを意味します。これにより、複数のエラー発生箇所から共通のエラー処理パスに集約することができます。
技術的詳細
このコミットの技術的詳細は、go/types
パッケージがどのように不正なプログラムを扱うかという点に集約されます。型チェックは複雑なプロセスであり、ある部分でエラーが検出された場合、そのエラーが後続の処理にどのように影響するかを適切に管理する必要があります。
従来のコードでは、ある式が invalid
と評価された後も、その invalid
な結果を使ってさらに型チェックを進めようとすることがありました。これは、以下のような問題を引き起こす可能性があります。
- パニックの発生:
invalid
なオペランドが予期しない型や値を持っていると仮定して処理を進めた結果、nilポインタ参照や配列の範囲外アクセスなど、ランタイムパニックが発生する。 - 誤ったエラー報告:
invalid
なオペランドに基づいてさらにエラーが検出された場合、根本原因とは異なる、誤解を招くようなエラーメッセージがユーザーに表示される。 - 無駄な計算: 既に無効であることが確定している部分に対して、不必要な計算リソースを消費する。
このコミットでは、これらの問題を解決するために、型チェックの主要な関数(binary
, rawExpr
, assignment
など)の冒頭や、重要な処理の直後に operand.mode == invalid
のチェックを追加しています。
- 早期リターン/伝播:
binary
関数では、二項演算のオペランドのいずれかがinvalid
であれば、すぐに処理を終了するか、結果のオペランドもinvalid
に設定して返します。これにより、無効な入力に対する無駄な計算を防ぎます。 goto Error
によるエラーパスへの分岐:rawExpr
関数内の*ast.IndexExpr
,*ast.SliceExpr
,*ast.UnaryExpr
,*ast.BinaryExpr
の各ケースでは、処理を開始する前にオペランドがinvalid
であればgoto Error
を使ってエラー処理パスに分岐します。これにより、各式の型チェックロジックがinvalid
な入力で複雑になるのを防ぎ、エラー処理を一元化します。assignment
関数の堅牢化:assignment
関数は、ある値が特定の型に代入可能かをチェックする重要な関数です。この関数がinvalid
なオペランドを受け取った場合、すぐにfalse
を返すように変更されました。これにより、代入チェックのロジックがinvalid
な入力によって誤動作するのを防ぎます。- デバッグ出力の条件化:
rawExpr
のdefault
ケースにあったデバッグ出力がdebug
フラグによって条件化されました。これは直接的な堅牢性向上とは異なりますが、デバッグビルド以外での不要な出力を抑制し、パフォーマンスやクリーンさを向上させます。
これらの変更により、go/types
パッケージは、不正なプログラムに対してもより予測可能で安定した挙動を示すようになり、開発者にとってより信頼性の高いツールチェーンを提供できるようになりました。
コアとなるコードの変更箇所
src/pkg/go/types/expr.go
func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token, iota int)
@@ -550,6 +550,15 @@ func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token, iota
check.expr(x, lhs, nil, iota)
check.expr(&y, rhs, nil, iota)
+ if x.mode == invalid {
+ return
+ }
+ if y.mode == invalid {
+ x.mode = invalid
+ x.expr = y.expr
+ return
+ }
+
if isShift(op) {
check.shift(x, &y, op)
return
二項演算の型チェックを行う binary
関数において、左右のオペランド x
および y
の mode
が invalid
であるかをチェックするロジックが追加されました。もし x
が invalid
なら即座にリターンし、y
が invalid
なら x
も invalid
に設定してリターンします。これにより、無効なオペランドに対する後続の処理をスキップします。
func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle map[Type]bool)
この関数内の複数の case
で x.mode == invalid
のチェックと goto Error
が追加されました。
-
case *ast.IndexExpr:
@@ -1089,6 +1098,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *ast.IndexExpr: check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } valid := false length := int64(-1) // valid if >= 0
インデックス式(例:
a[i]
) の型チェックにおいて、基となる式e.X
の評価結果x
がinvalid
であれば、エラー処理にジャンプします。 -
case *Map:
(within*ast.IndexExpr
の内部)@@ -1130,9 +1142,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *Map: var key operand check.expr(&key, e.Index, nil, iota) - if key.mode == invalid || !check.assignment(&key, typ.Key) { - if x.mode != invalid { - check.invalidOp(x.pos(), "cannot use %s as map index of type %s", &key, typ.Key) + if !check.assignment(&key, typ.Key) { + if key.mode != invalid { + check.invalidOp(key.pos(), "cannot use %s as map index of type %s", &key, typ.Key) } goto Error }
マップのインデックスアクセスにおいて、キーのオペランド
key
がinvalid
であるかのチェックがcheck.assignment
関数に委譲されました。また、エラーメッセージの表示位置がx.pos()
からkey.pos()
に変更され、より正確なエラー報告が可能になりました。 -
case *ast.SliceExpr:
@@ -1157,6 +1169,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *ast.SliceExpr: check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } valid := false length := int64(-1) // valid if >= 0
スライス式(例:
a[low:high]
) の型チェックにおいて、基となる式e.X
の評価結果x
がinvalid
であれば、エラー処理にジャンプします。 -
case *ast.UnaryExpr:
@@ -1367,10 +1382,19 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *ast.UnaryExpr: check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } check.unary(x, e.Op) + if x.mode == invalid { + goto Error + }
単項演算(例:
-x
,!b
)の型チェックにおいて、オペランドe.X
の評価結果x
がinvalid
であれば、エラー処理にジャンプします。また、check.unary
呼び出し後にもx
がinvalid
になった場合に再度エラー処理にジャンプするチェックが追加されました。 -
case *ast.BinaryExpr:
@@ -1378,6 +1392,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *ast.BinaryExpr: check.binary(x, e.X, e.Y, e.Op, iota) + if x.mode == invalid { + goto Error + } case *ast.KeyValueExpr: // key:value expressions are handled in composite literals
二項演算の型チェックにおいて、
check.binary
呼び出し後に結果x
がinvalid
になった場合にエラー処理にジャンプするチェックが追加されました。 -
default:
(withinrawExpr
)@@ -1423,7 +1447,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle x.typ = &Chan{Dir: e.Dir, Elt: check.typ(e.Value, true)} default: - check.dump("e = %s", e) + if debug { + check.dump("expr = %v (%T)", e, e) + } unreachable() }
rawExpr
のdefault
ケースにおけるデバッグ出力がdebug
フラグによって条件化されました。
src/pkg/go/types/stmt.go
func (check *checker) assignment(x *operand, to Type) bool
@@ -18,6 +18,10 @@ import (
// TODO(gri) This latter behavior is for historic reasons and complicates
// callers. Needs to be cleaned up.
func (check *checker) assignment(x *operand, to Type) bool {
+ if x.mode == invalid {
+ return false
+ }
+
if t, ok := x.typ.(*Result); ok {
// TODO(gri) elsewhere we use "assignment count mismatch" (consolidate)
check.errorf(x.pos(), "%d-valued expression %s used as single value", len(t.Values), x)
代入可能性をチェックする assignment
関数において、入力オペランド x
の mode
が invalid
であれば、即座に false
を返すロジックが追加されました。これにより、無効なオペランドに対する代入チェックの試みを防ぎます。
コアとなるコードの解説
このコミットの核心は、go/types
パッケージが型チェックを行う際に、**「無効な状態の伝播と早期終了」**という原則をより厳密に適用した点にあります。
-
operand.mode == invalid
の積極的な利用:operand
構造体のmode
フィールドは、式の評価結果が有効であるか、あるいは何らかの理由で無効であるかを示す重要なメタデータです。このコミット以前は、invalid
モードが設定されていても、その後の処理で適切にチェックされずに利用されてしまうケースがありました。今回の変更では、binary
、rawExpr
、assignment
といった型チェックの中核を担う関数において、入力オペランドがinvalid
であれば、それ以上の詳細な型チェックを行わずに、早期に処理を終了するか、エラーパスに分岐するようになりました。 -
堅牢性の向上: 例えば、
binary
関数では、二項演算の左右のオペランドのいずれかが既にinvalid
であれば、その演算の結果もinvalid
とし、それ以上複雑な型推論やエラーチェックを行いません。これは、既にエラーが確定している部分に対して、さらにエラーを重ねて報告したり、パニックを引き起こしたりするのを防ぐための防御的なプログラミングです。 -
エラー処理の一元化と効率化:
rawExpr
関数内の複数の箇所でif x.mode == invalid { goto Error }
が追加されたことは、エラー処理のパターンを統一し、コードの可読性と保守性を向上させます。各式の型チェックロジックは、有効なオペランドが与えられた場合の処理に集中でき、エラーケースはError
ラベルに集約された共通のパスで処理されます。これにより、コードの重複が減り、エラーハンドリングのロジックがより明確になります。 -
正確なエラー報告: マップのインデックスアクセスに関する変更では、エラーメッセージの表示位置が
x.pos()
からkey.pos()
に変更されました。これは、エラーが実際に発生した原因(この場合はマップのキー)に最も近い位置を指し示すことで、ユーザーが問題を特定しやすくなるように、エラー報告の精度を高めるための改善です。
これらの変更は、Go言語の型チェッカーが、より複雑で、時には不正なプログラムに対しても安定して動作し、開発者に対してより有用で正確なフィードバックを提供するための重要なステップでした。
関連リンク
- Go issue #4962: https://github.com/golang/go/issues/4962
- Go CL 7429047: https://golang.org/cl/7429047 (Gerrit Code Review)
参考にした情報源リンク
- Go言語の公式ドキュメント:
go/types
パッケージ - Go言語のソースコード(特に
src/go/types
ディレクトリ) - Go issue tracker (GitHub)
- Gerrit Code Review (Goプロジェクトのコードレビューシステム)
[インデックス 15551] ファイルの概要
このコミットは、Go言語の型チェッカーである go/types
パッケージにおける堅牢性の向上を目的としています。特に、不正なプログラムが入力された際の挙動を改善し、パニックや誤った型推論を防ぐためのチェックが追加されています。
コミット
commit 83c8cc54361b075e6ec9d0d62d3c29803fbbf594
Author: Robert Griesemer <gri@golang.org>
Date: Fri Mar 1 17:39:22 2013 -0800
go/types: fixed a few failure checks
More robustness in case of incorrect programs.
Fixes #4962.
R=dsymonds
CC=golang-dev
https://golang.org/cl/7429047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/83c8cc54361b075e6ec9d0d62d3c29803fbbf594
元コミット内容
このコミットは、go/types
パッケージにおいて、いくつかの「失敗チェック (failure checks)」を修正し、不正なプログラムに対する堅牢性を高めることを目的としています。具体的には、型チェックの過程で無効な状態(invalid
モード)のオペランドが検出された場合に、早期に処理を終了したり、無効な状態を適切に伝播させたりするロジックが追加されています。これにより、不正な入力によって型チェッカーがパニックを起こしたり、意味のない計算を続けたりすることを防ぎます。
この変更は、Go issue #4962 を修正するものです。このIssueは、Goの古いIssueトラッカーまたは内部のトラッカーで管理されていたもので、現在はGerrit Code ReviewのCL (Change-List) https://golang.org/cl/7429047
に関連付けられています。
変更の背景
Go言語のコンパイラやツールチェーンにおいて、型チェッカーはプログラムの正当性を検証する非常に重要なコンポーネントです。しかし、ユーザーが記述するプログラムは常に文法的に正しく、型的に健全であるとは限りません。特に開発中のコードや、意図的に不正なコードを記述した場合など、型チェッカーは不正な入力に対しても安定して動作し、適切なエラーを報告する必要があります。
このコミットが行われた2013年当時、go/types
パッケージはまだ比較的新しい段階にあり、様々なエッジケースや不正な入力に対する堅牢性が継続的に改善されていました。Issue #4962 は、特定の不正なプログラムが go/types
パッケージ内でパニックを引き起こす問題として報告されており、このコミットはその問題を解決し、より安定した型チェックプロセスを提供するために導入されました。
具体的には、型チェックの途中で invalid
とマークされたオペランド(式の結果など)が後続の処理に渡された際に、その invalid
状態が適切にチェックされず、予期せぬエラーやパニックが発生する可能性がありました。このコミットは、このような状況を特定し、invalid
状態のオペランドを早期に検出し、それ以上の無意味な処理を避けることで、型チェッカー全体の安定性を向上させています。
前提知識の解説
go/types
パッケージ
go/types
はGo言語の標準ライブラリの一部であり、Goプログラムの型チェックを行うためのパッケージです。Goコンパイラや各種Goツール(go vet
、gopls
など)の基盤として利用されています。このパッケージは、Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として解析した後、各ノードの型情報を解決し、Go言語の型システム規則に違反していないかを検証します。
go/types
の主要な役割は以下の通りです。
- 型推論: 変数や式の型を決定します。
- 型チェック: 型の互換性、代入可能性、演算の正当性などを検証します。
- スコープ解決: 変数や関数の定義と使用を紐付けます。
- エラー報告: 型システム違反を検出した場合に、詳細なエラーメッセージを生成します。
operand
構造体と mode
フィールド
go/types
パッケージでは、式の結果や値の情報を operand
構造体で表現します。この operand
構造体には、その値がどのような状態にあるかを示す mode
フィールドが含まれています。mode
は operandMode
型の列挙型で、以下のような値を取ります(一部抜粋):
invalid
: オペランドが無効な状態であることを示します。これは、構文エラー、型エラー、未定義の識別子など、何らかの問題によって式の評価が失敗したことを意味します。value
: オペランドが有効な値であることを示します。type_
: オペランドが型であることを示します(例:int
,string
)。var_
: オペランドが変数であることを示します。
型チェックの過程で、不正な式や未解決の参照に遭遇した場合、その結果の operand
は invalid
モードに設定されます。この invalid
モードは、後続の型チェック処理において、そのオペランドが信頼できない、あるいは処理を継続すべきではないことを示すシグナルとして機能します。
goto Error
Go言語では goto
ステートメントの使用は一般的ではありませんが、コンパイラや低レベルのコードでは、エラーハンドリングや特定の状態遷移を効率的に行うために使用されることがあります。このコミットのコードでは、goto Error
が使用されており、これは現在の関数内で定義された Error
ラベルにジャンプし、エラー処理ロジックを実行することを意味します。これにより、複数のエラー発生箇所から共通のエラー処理パスに集約することができます。
技術的詳細
このコミットの技術的詳細は、go/types
パッケージがどのように不正なプログラムを扱うかという点に集約されます。型チェックは複雑なプロセスであり、ある部分でエラーが検出された場合、そのエラーが後続の処理にどのように影響するかを適切に管理する必要があります。
従来のコードでは、ある式が invalid
と評価された後も、その invalid
な結果を使ってさらに型チェックを進めようとすることがありました。これは、以下のような問題を引き起こす可能性があります。
- パニックの発生:
invalid
なオペランドが予期しない型や値を持っていると仮定して処理を進めた結果、nilポインタ参照や配列の範囲外アクセスなど、ランタイムパニックが発生する。 - 誤ったエラー報告:
invalid
なオペランドに基づいてさらにエラーが検出された場合、根本原因とは異なる、誤解を招くようなエラーメッセージがユーザーに表示される。 - 無駄な計算: 既に無効であることが確定している部分に対して、不必要な計算リソースを消費する。
このコミットでは、これらの問題を解決するために、型チェックの主要な関数(binary
, rawExpr
, assignment
など)の冒頭や、重要な処理の直後に operand.mode == invalid
のチェックを追加しています。
- 早期リターン/伝播:
binary
関数では、二項演算のオペランドのいずれかがinvalid
であれば、すぐに処理を終了するか、結果のオペランドもinvalid
に設定して返します。これにより、無効な入力に対する無駄な計算を防ぎます。 goto Error
によるエラーパスへの分岐:rawExpr
関数内の*ast.IndexExpr
,*ast.SliceExpr
,*ast.UnaryExpr
,*ast.BinaryExpr
の各ケースでは、処理を開始する前にオペランドがinvalid
であればgoto Error
を使ってエラー処理パスに分岐します。これにより、各式の型チェックロジックがinvalid
な入力で複雑になるのを防ぎ、エラー処理を一元化します。assignment
関数の堅牢化:assignment
関数は、ある値が特定の型に代入可能かをチェックする重要な関数です。この関数がinvalid
なオペランドを受け取った場合、すぐにfalse
を返すように変更されました。これにより、代入チェックのロジックがinvalid
な入力によって誤動作するのを防ぎます。- デバッグ出力の条件化:
rawExpr
のdefault
ケースにあったデバッグ出力がdebug
フラグによって条件化されました。これは直接的な堅牢性向上とは異なりますが、デバッグビルド以外での不要な出力を抑制し、パフォーマンスやクリーンさを向上させます。
これらの変更により、go/types
パッケージは、不正なプログラムに対してもより予測可能で安定した挙動を示すようになり、開発者にとってより信頼性の高いツールチェーンを提供できるようになりました。
コアとなるコードの変更箇所
src/pkg/go/types/expr.go
func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token, iota int)
@@ -550,6 +550,15 @@ func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token, iota
check.expr(x, lhs, nil, iota)
check.expr(&y, rhs, nil, iota)
+ if x.mode == invalid {
+ return
+ }
+ if y.mode == invalid {
+ x.mode = invalid
+ x.expr = y.expr
+ return
+ }
+
if isShift(op) {
check.shift(x, &y, op)
return
二項演算の型チェックを行う binary
関数において、左右のオペランド x
および y
の mode
が invalid
であるかをチェックするロジックが追加されました。もし x
が invalid
なら即座にリターンし、y
が invalid
なら x
も invalid
に設定してリターンします。これにより、無効なオペランドに対する後続の処理をスキップします。
func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle map[Type]bool)
この関数内の複数の case
で x.mode == invalid
のチェックと goto Error
が追加されました。
-
case *ast.IndexExpr:
@@ -1089,6 +1098,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *ast.IndexExpr: check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } valid := false length := int64(-1) // valid if >= 0
インデックス式(例:
a[i]
) の型チェックにおいて、基となる式e.X
の評価結果x
がinvalid
であれば、エラー処理にジャンプします。 -
case *Map:
(within*ast.IndexExpr
の内部)@@ -1130,9 +1142,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *Map: var key operand check.expr(&key, e.Index, nil, iota) - if key.mode == invalid || !check.assignment(&key, typ.Key) { - if x.mode != invalid { - check.invalidOp(x.pos(), "cannot use %s as map index of type %s", &key, typ.Key) + if !check.assignment(&key, typ.Key) { + if key.mode != invalid { + check.invalidOp(key.pos(), "cannot use %s as map index of type %s", &key, typ.Key) } goto Error }
マップのインデックスアクセスにおいて、キーのオペランド
key
がinvalid
であるかのチェックがcheck.assignment
関数に委譲されました。また、エラーメッセージの表示位置がx.pos()
からkey.pos()
に変更され、より正確なエラー報告が可能になりました。 -
case *ast.SliceExpr:
@@ -1157,6 +1169,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *ast.SliceExpr: check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } valid := false length := int64(-1) // valid if >= 0
スライス式(例:
a[low:high]
) の型チェックにおいて、基となる式e.X
の評価結果x
がinvalid
であれば、エラー処理にジャンプします。 -
case *ast.UnaryExpr:
@@ -1367,10 +1382,19 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *ast.UnaryExpr: check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } check.unary(x, e.Op) + if x.mode == invalid { + goto Error + }
単項演算(例:
-x
,!b
)の型チェックにおいて、オペランドe.X
の評価結果x
がinvalid
であれば、エラー処理にジャンプします。また、check.unary
呼び出し後にもx
がinvalid
になった場合に再度エラー処理にジャンプするチェックが追加されました。 -
case *ast.BinaryExpr:
@@ -1378,6 +1392,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *ast.BinaryExpr: check.binary(x, e.X, e.Y, e.Op, iota) + if x.mode == invalid { + goto Error + } case *ast.KeyValueExpr: // key:value expressions are handled in composite literals
二項演算の型チェックにおいて、
check.binary
呼び出し後に結果x
がinvalid
になった場合にエラー処理にジャンプするチェックが追加されました。 -
default:
(withinrawExpr
)@@ -1423,7 +1447,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle x.typ = &Chan{Dir: e.Dir, Elt: check.typ(e.Value, true)} default: - check.dump("e = %s", e) + if debug { + check.dump("expr = %v (%T)", e, e) + } unreachable() }
rawExpr
のdefault
ケースにおけるデバッグ出力がdebug
フラグによって条件化されました。
src/pkg/go/types/stmt.go
func (check *checker) assignment(x *operand, to Type) bool
@@ -18,6 +18,10 @@ import (
// TODO(gri) This latter behavior is for historic reasons and complicates
// callers. Needs to be cleaned up.
func (check *checker) assignment(x *operand, to Type) bool {
+ if x.mode == invalid {
+ return false
+ }
+
if t, ok := x.typ.(*Result); ok {
// TODO(gri) elsewhere we use "assignment count mismatch" (consolidate)
check.errorf(x.pos(), "%d-valued expression %s used as single value", len(t.Values), x)
代入可能性をチェックする assignment
関数において、入力オペランド x
の mode
が invalid
であれば、即座に false
を返すロジックが追加されました。これにより、無効なオペランドに対する代入チェックの試みを防ぎます。
コアとなるコードの解説
このコミットの核心は、go/types
パッケージが型チェックを行う際に、**「無効な状態の伝播と早期終了」**という原則をより厳密に適用した点にあります。
-
operand.mode == invalid
の積極的な利用:operand
構造体のmode
フィールドは、式の評価結果が有効であるか、あるいは何らかの理由で無効であるかを示す重要なメタデータです。このコミット以前は、invalid
モードが設定されていても、その後の処理で適切にチェックされずに利用されてしまうケースがありました。今回の変更では、binary
、rawExpr
、assignment
といった型チェックの中核を担う関数において、入力オペランドがinvalid
であれば、それ以上の詳細な型チェックを行わずに、早期に処理を終了するか、エラーパスに分岐するようになりました。 -
堅牢性の向上: 例えば、
binary
関数では、二項演算の左右のオペランドのいずれかが既にinvalid
であれば、その演算の結果もinvalid
とし、それ以上複雑な型推論やエラーチェックを行いません。これは、既にエラーが確定している部分に対して、さらにエラーを重ねて報告したり、パニックを引き起こしたりするのを防ぐための防御的なプログラミングです。 -
エラー処理の一元化と効率化:
rawExpr
関数内の複数の箇所でif x.mode == invalid { goto Error }
が追加されたことは、エラー処理のパターンを統一し、コードの可読性と保守性を向上させます。各式の型チェックロジックは、有効なオペランドが与えられた場合の処理に集中でき、エラーケースはError
ラベルに集約された共通のパスで処理されます。これにより、コードの重複が減り、エラーハンドリングのロジックがより明確になります。 -
正確なエラー報告: マップのインデックスアクセスに関する変更では、エラーメッセージの表示位置が
x.pos()
からkey.pos()
に変更されました。これは、エラーが実際に発生した原因(この場合はマップのキー)に最も近い位置を指し示すことで、ユーザーが問題を特定しやすくなるように、エラー報告の精度を高めるための改善です。
これらの変更は、Go言語の型チェッカーが、より複雑で、時には不正なプログラムに対しても安定して動作し、開発者に対してより有用で正確なフィードバックを提供するための重要なステップでした。
関連リンク
- Go issue #4962: このIssueはGoの古いIssueトラッカーで管理されていたもので、GitHub上では直接参照できませんが、このコミットによって修正されました。
- Go CL 7429047: https://golang.org/cl/7429047 (Gerrit Code Review)
参考にした情報源リンク
- Go言語の公式ドキュメント:
go/types
パッケージ - Go言語のソースコード(特に
src/go/types
ディレクトリ) - Gerrit Code Review (Goプロジェクトのコードレビューシステム)