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

[インデックス 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 vetgoplsなど)の基盤として利用されています。このパッケージは、Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として解析した後、各ノードの型情報を解決し、Go言語の型システム規則に違反していないかを検証します。

go/types の主要な役割は以下の通りです。

  • 型推論: 変数や式の型を決定します。
  • 型チェック: 型の互換性、代入可能性、演算の正当性などを検証します。
  • スコープ解決: 変数や関数の定義と使用を紐付けます。
  • エラー報告: 型システム違反を検出した場合に、詳細なエラーメッセージを生成します。

operand 構造体と mode フィールド

go/types パッケージでは、式の結果や値の情報を operand 構造体で表現します。この operand 構造体には、その値がどのような状態にあるかを示す mode フィールドが含まれています。modeoperandMode 型の列挙型で、以下のような値を取ります(一部抜粋):

  • invalid: オペランドが無効な状態であることを示します。これは、構文エラー、型エラー、未定義の識別子など、何らかの問題によって式の評価が失敗したことを意味します。
  • value: オペランドが有効な値であることを示します。
  • type_: オペランドが型であることを示します(例: int, string)。
  • var_: オペランドが変数であることを示します。

型チェックの過程で、不正な式や未解決の参照に遭遇した場合、その結果の operandinvalid モードに設定されます。この invalid モードは、後続の型チェック処理において、そのオペランドが信頼できない、あるいは処理を継続すべきではないことを示すシグナルとして機能します。

goto Error

Go言語では goto ステートメントの使用は一般的ではありませんが、コンパイラや低レベルのコードでは、エラーハンドリングや特定の状態遷移を効率的に行うために使用されることがあります。このコミットのコードでは、goto Error が使用されており、これは現在の関数内で定義された Error ラベルにジャンプし、エラー処理ロジックを実行することを意味します。これにより、複数のエラー発生箇所から共通のエラー処理パスに集約することができます。

技術的詳細

このコミットの技術的詳細は、go/types パッケージがどのように不正なプログラムを扱うかという点に集約されます。型チェックは複雑なプロセスであり、ある部分でエラーが検出された場合、そのエラーが後続の処理にどのように影響するかを適切に管理する必要があります。

従来のコードでは、ある式が invalid と評価された後も、その invalid な結果を使ってさらに型チェックを進めようとすることがありました。これは、以下のような問題を引き起こす可能性があります。

  1. パニックの発生: invalid なオペランドが予期しない型や値を持っていると仮定して処理を進めた結果、nilポインタ参照や配列の範囲外アクセスなど、ランタイムパニックが発生する。
  2. 誤ったエラー報告: invalid なオペランドに基づいてさらにエラーが検出された場合、根本原因とは異なる、誤解を招くようなエラーメッセージがユーザーに表示される。
  3. 無駄な計算: 既に無効であることが確定している部分に対して、不必要な計算リソースを消費する。

このコミットでは、これらの問題を解決するために、型チェックの主要な関数(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 な入力によって誤動作するのを防ぎます。
  • デバッグ出力の条件化: rawExprdefault ケースにあったデバッグ出力が 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 および ymodeinvalid であるかをチェックするロジックが追加されました。もし xinvalid なら即座にリターンし、yinvalid なら xinvalid に設定してリターンします。これにより、無効なオペランドに対する後続の処理をスキップします。

func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle map[Type]bool)

この関数内の複数の casex.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 の評価結果 xinvalid であれば、エラー処理にジャンプします。

  • 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
     			}
    

    マップのインデックスアクセスにおいて、キーのオペランド keyinvalid であるかのチェックが 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 の評価結果 xinvalid であれば、エラー処理にジャンプします。

  • 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 の評価結果 xinvalid であれば、エラー処理にジャンプします。また、check.unary 呼び出し後にも xinvalid になった場合に再度エラー処理にジャンプするチェックが追加されました。

  • 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 呼び出し後に結果 xinvalid になった場合にエラー処理にジャンプするチェックが追加されました。

  • default: (within rawExpr)

    @@ -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()
     	}
    

    rawExprdefault ケースにおけるデバッグ出力が 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 関数において、入力オペランド xmodeinvalid であれば、即座に false を返すロジックが追加されました。これにより、無効なオペランドに対する代入チェックの試みを防ぎます。

コアとなるコードの解説

このコミットの核心は、go/types パッケージが型チェックを行う際に、**「無効な状態の伝播と早期終了」**という原則をより厳密に適用した点にあります。

  1. operand.mode == invalid の積極的な利用: operand 構造体の mode フィールドは、式の評価結果が有効であるか、あるいは何らかの理由で無効であるかを示す重要なメタデータです。このコミット以前は、invalid モードが設定されていても、その後の処理で適切にチェックされずに利用されてしまうケースがありました。今回の変更では、binaryrawExprassignment といった型チェックの中核を担う関数において、入力オペランドが invalid であれば、それ以上の詳細な型チェックを行わずに、早期に処理を終了するか、エラーパスに分岐するようになりました。

  2. 堅牢性の向上: 例えば、binary 関数では、二項演算の左右のオペランドのいずれかが既に invalid であれば、その演算の結果も invalid とし、それ以上複雑な型推論やエラーチェックを行いません。これは、既にエラーが確定している部分に対して、さらにエラーを重ねて報告したり、パニックを引き起こしたりするのを防ぐための防御的なプログラミングです。

  3. エラー処理の一元化と効率化: rawExpr 関数内の複数の箇所で if x.mode == invalid { goto Error } が追加されたことは、エラー処理のパターンを統一し、コードの可読性と保守性を向上させます。各式の型チェックロジックは、有効なオペランドが与えられた場合の処理に集中でき、エラーケースは Error ラベルに集約された共通のパスで処理されます。これにより、コードの重複が減り、エラーハンドリングのロジックがより明確になります。

  4. 正確なエラー報告: マップのインデックスアクセスに関する変更では、エラーメッセージの表示位置が x.pos() から key.pos() に変更されました。これは、エラーが実際に発生した原因(この場合はマップのキー)に最も近い位置を指し示すことで、ユーザーが問題を特定しやすくなるように、エラー報告の精度を高めるための改善です。

これらの変更は、Go言語の型チェッカーが、より複雑で、時には不正なプログラムに対しても安定して動作し、開発者に対してより有用で正確なフィードバックを提供するための重要なステップでした。

関連リンク

参考にした情報源リンク

  • 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 vetgoplsなど)の基盤として利用されています。このパッケージは、Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として解析した後、各ノードの型情報を解決し、Go言語の型システム規則に違反していないかを検証します。

go/types の主要な役割は以下の通りです。

  • 型推論: 変数や式の型を決定します。
  • 型チェック: 型の互換性、代入可能性、演算の正当性などを検証します。
  • スコープ解決: 変数や関数の定義と使用を紐付けます。
  • エラー報告: 型システム違反を検出した場合に、詳細なエラーメッセージを生成します。

operand 構造体と mode フィールド

go/types パッケージでは、式の結果や値の情報を operand 構造体で表現します。この operand 構造体には、その値がどのような状態にあるかを示す mode フィールドが含まれています。modeoperandMode 型の列挙型で、以下のような値を取ります(一部抜粋):

  • invalid: オペランドが無効な状態であることを示します。これは、構文エラー、型エラー、未定義の識別子など、何らかの問題によって式の評価が失敗したことを意味します。
  • value: オペランドが有効な値であることを示します。
  • type_: オペランドが型であることを示します(例: int, string)。
  • var_: オペランドが変数であることを示します。

型チェックの過程で、不正な式や未解決の参照に遭遇した場合、その結果の operandinvalid モードに設定されます。この invalid モードは、後続の型チェック処理において、そのオペランドが信頼できない、あるいは処理を継続すべきではないことを示すシグナルとして機能します。

goto Error

Go言語では goto ステートメントの使用は一般的ではありませんが、コンパイラや低レベルのコードでは、エラーハンドリングや特定の状態遷移を効率的に行うために使用されることがあります。このコミットのコードでは、goto Error が使用されており、これは現在の関数内で定義された Error ラベルにジャンプし、エラー処理ロジックを実行することを意味します。これにより、複数のエラー発生箇所から共通のエラー処理パスに集約することができます。

技術的詳細

このコミットの技術的詳細は、go/types パッケージがどのように不正なプログラムを扱うかという点に集約されます。型チェックは複雑なプロセスであり、ある部分でエラーが検出された場合、そのエラーが後続の処理にどのように影響するかを適切に管理する必要があります。

従来のコードでは、ある式が invalid と評価された後も、その invalid な結果を使ってさらに型チェックを進めようとすることがありました。これは、以下のような問題を引き起こす可能性があります。

  1. パニックの発生: invalid なオペランドが予期しない型や値を持っていると仮定して処理を進めた結果、nilポインタ参照や配列の範囲外アクセスなど、ランタイムパニックが発生する。
  2. 誤ったエラー報告: invalid なオペランドに基づいてさらにエラーが検出された場合、根本原因とは異なる、誤解を招くようなエラーメッセージがユーザーに表示される。
  3. 無駄な計算: 既に無効であることが確定している部分に対して、不必要な計算リソースを消費する。

このコミットでは、これらの問題を解決するために、型チェックの主要な関数(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 な入力によって誤動作するのを防ぎます。
  • デバッグ出力の条件化: rawExprdefault ケースにあったデバッグ出力が 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 および ymodeinvalid であるかをチェックするロジックが追加されました。もし xinvalid なら即座にリターンし、yinvalid なら xinvalid に設定してリターンします。これにより、無効なオペランドに対する後続の処理をスキップします。

func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle map[Type]bool)

この関数内の複数の casex.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 の評価結果 xinvalid であれば、エラー処理にジャンプします。

  • 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
     			}
    

    マップのインデックスアクセスにおいて、キーのオペランド keyinvalid であるかのチェックが 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 の評価結果 xinvalid であれば、エラー処理にジャンプします。

  • 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 の評価結果 xinvalid であれば、エラー処理にジャンプします。また、check.unary 呼び出し後にも xinvalid になった場合に再度エラー処理にジャンプするチェックが追加されました。

  • 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 呼び出し後に結果 xinvalid になった場合にエラー処理にジャンプするチェックが追加されました。

  • default: (within rawExpr)

    @@ -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()
     	}
    

    rawExprdefault ケースにおけるデバッグ出力が 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 関数において、入力オペランド xmodeinvalid であれば、即座に false を返すロジックが追加されました。これにより、無効なオペランドに対する代入チェックの試みを防ぎます。

コアとなるコードの解説

このコミットの核心は、go/types パッケージが型チェックを行う際に、**「無効な状態の伝播と早期終了」**という原則をより厳密に適用した点にあります。

  1. operand.mode == invalid の積極的な利用: operand 構造体の mode フィールドは、式の評価結果が有効であるか、あるいは何らかの理由で無効であるかを示す重要なメタデータです。このコミット以前は、invalid モードが設定されていても、その後の処理で適切にチェックされずに利用されてしまうケースがありました。今回の変更では、binaryrawExprassignment といった型チェックの中核を担う関数において、入力オペランドが invalid であれば、それ以上の詳細な型チェックを行わずに、早期に処理を終了するか、エラーパスに分岐するようになりました。

  2. 堅牢性の向上: 例えば、binary 関数では、二項演算の左右のオペランドのいずれかが既に invalid であれば、その演算の結果も invalid とし、それ以上複雑な型推論やエラーチェックを行いません。これは、既にエラーが確定している部分に対して、さらにエラーを重ねて報告したり、パニックを引き起こしたりするのを防ぐための防御的なプログラミングです。

  3. エラー処理の一元化と効率化: rawExpr 関数内の複数の箇所で if x.mode == invalid { goto Error } が追加されたことは、エラー処理のパターンを統一し、コードの可読性と保守性を向上させます。各式の型チェックロジックは、有効なオペランドが与えられた場合の処理に集中でき、エラーケースは Error ラベルに集約された共通のパスで処理されます。これにより、コードの重複が減り、エラーハンドリングのロジックがより明確になります。

  4. 正確なエラー報告: マップのインデックスアクセスに関する変更では、エラーメッセージの表示位置が 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プロジェクトのコードレビューシステム)