[インデックス 14926] ファイルの概要
このコミットは、Goコンパイラの型チェックフェーズにおけるOIND
ノード(間接参照操作)の処理をより堅牢にするための変更です。具体的には、無効な間接参照に関するエラーチェックのロジックを改善し、特定の不正なコードパターンに対してより正確なエラーメッセージが出力されるように修正しています。
コミット
commit c0d9bf5650361191f5e86c4a31176a01a9c2867b
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Fri Jan 18 22:46:10 2013 +0100
cmd/gc: more robust checking of OIND nodes.
Fixes #4610.
R=golang-dev, remyoudompheng, rsc
CC=golang-dev, nigeltao
https://golang.org/cl/7058057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c0d9bf5650361191f5e86c4a31176a01a9c2867b
元コミット内容
このコミットは、Goコンパイラのcmd/gc
部分におけるOIND
ノードのチェックをより堅牢にするものです。これにより、Go言語のIssue #4610で報告された問題が修正されます。
変更の背景
Goコンパイラは、ソースコードを抽象構文木(AST)に変換し、そのASTに対して型チェックなどの様々な処理を行います。このコミットが行われる前は、特定の不正なコードパターン、例えば&foo{}
のような形式で、foo
が型名ではなく変数名である場合に、コンパイラが不正確なエラーメッセージを出力したり、内部で予期せぬ動作をしたりする可能性がありました。
具体的には、&foo{}
という表現は、foo
が型名であるかのように複合リテラル({}
)と組み合わせて使用しようとしていますが、foo
は既に宣言された変数であるため、これはGoの文法として誤りです。このような場合、コンパイラは「foo
は型ではありません」というような、より適切なエラーを出すべきです。しかし、以前の実装では、OIND
ノード(間接参照)のチェックロジックが広範すぎたため、この種の誤用が「無効な間接参照」という、文脈にそぐわないエラーとして報告されるか、あるいは型チェックの途中で不適切なパスに進んでしまう可能性がありました。
この問題はGoのIssue #4610として報告されており、このコミットはその修正を目的としています。修正の目的は、コンパイラが不正なコードに対してより正確で分かりやすいエラーメッセージを提供し、開発者のデバッグ体験を向上させることにあります。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造と概念に関する知識が必要です。
- Goコンパイラ (
cmd/gc
): Go言語の公式コンパイラのフロントエンド部分です。ソースコードの字句解析、構文解析、型チェック、中間コード生成などを行います。 - 抽象構文木 (AST): ソースコードの構造を木構造で表現したものです。コンパイラはASTを操作して様々な解析や変換を行います。
- ASTノードの種類: ASTの各要素は特定のノードタイプで表現されます。
OIND
(Opcode for INDirection): ポインタの逆参照(間接参照)操作を表すノードです。例えば、*p
のような式に対応します。OADDR
(Opcode for ADDRess): 変数のアドレスを取得する操作を表すノードです。例えば、&x
のような式に対応します。OLIT
(Opcode for LITeral): リテラル値(数値、文字列など)を表すノードです。OCALL
(Opcode for CALL): 関数呼び出しを表すノードです。
- 型チェック (
typecheck.c
):cmd/gc
内のtypecheck.c
ファイルは、ASTノードの型がGo言語の型システム規則に適合しているかを検証する役割を担っています。このフェーズで、型の一貫性、正しい演算子の使用、関数呼び出しの引数と戻り値の型の一致などがチェックされます。 Node
構造体: Goコンパイラ内部でASTの各ノードを表すC言語の構造体です。Node
は、そのノードの操作タイプ(OIND
,OADDR
など)、関連する型情報(type
フィールド)、子ノード(left
,right
など)などの情報を含みます。top
フラグ:typecheck
関数に渡される引数で、現在のノードが評価されるべきコンテキストを示します。Erv
(Expression is Rvalue): 式が右辺値(値として評価されるべき)であることを示します。Etop
(Expression is TOP-level): 式がトップレベルのステートメントとして評価されるべきであることを示します。
isptr
配列: 型のカテゴリを示すブール値の配列で、特定の型がポインタ型であるかどうかを効率的にチェックするために使用されます。t->etype
はノードt
の基本型(element type)を示します。yyerror
: コンパイラがエラーメッセージを出力するために使用する関数です。
技術的詳細
このコミットの技術的詳細な変更は、src/cmd/gc/typecheck.c
ファイル内のOIND
ノードを処理する部分にあります。
元のコードでは、OIND
ノードの型チェックにおいて、以下の条件で「無効な間接参照」エラーを発生させていました。
if((top & (Erv | Etop)) && !isptr[t->etype]) {
yyerror("invalid indirect of %lN", n->left);
goto error;
}
このロジックは、「もし現在の式が右辺値またはトップレベルのコンテキストにあり、かつその型がポインタ型でない場合」にエラーを出す、というものでした。
しかし、この条件は一部のケースで過剰に厳しく、例えば&foo{}
のような、OADDR
(アドレス取得)操作と複合リテラルが絡む不正な構文に対して、より適切なエラー(例:「foo
は型ではありません」)ではなく、「無効な間接参照」というエラーを出してしまう可能性がありました。これは、コンパイラの内部処理でOADDR
がOIND
のチェックロジックと相互作用したり、あるいはOIND
のチェックが早すぎる段階で実行され、より詳細な型チェックが妨げられていたためと考えられます。
新しいコードでは、この条件を以下のように変更しました。
if(!isptr[t->etype]) {
if(top & (Erv | Etop)) {
yyerror("invalid indirect of %lN", n->left);
goto error;
}
goto ret;
}
この変更のポイントは、if(!isptr[t->etype])
(型がポインタでない場合)という条件を外側のif
文に移動し、その内部でif(top & (Erv | Etop))
(右辺値またはトップレベルのコンテキストにある場合)という条件をネストさせたことです。
これにより、以下の挙動が実現されます。
- 型がポインタでない場合 (
!isptr[t->etype]
):- かつ、右辺値またはトップレベルのコンテキストにある場合 (
top & (Erv | Etop)
): 従来通り「無効な間接参照」エラーを発生させます。これは、本当にポインタでない型に対して間接参照を行おうとしている、かつその結果が値として使われるべき、という明確なエラーケースに対応します。 - しかし、右辺値またはトップレベルのコンテキストにない場合 (
!(top & (Erv | Etop))
): エラーを発生させずにgoto ret;
(関数から戻る)します。これは、OIND
ノードが非ポインタ型に対して適用されているものの、それがまだ中間的な状態であるか、あるいは別のより適切なエラーが後続の型チェックフェーズで検出されるべきケースに対応します。例えば、&foo{}
のようなケースでは、foo
が型ではないというエラーが優先されるべきであり、この変更によってそのエラーが適切に報告されるようになります。
- かつ、右辺値またはトップレベルのコンテキストにある場合 (
この修正により、コンパイラはOIND
ノードのチェックをより文脈に即して行い、不正なコードに対してより正確で具体的なエラーメッセージを出力できるようになりました。
コアとなるコードの変更箇所
変更は主に以下の2つのファイルで行われています。
-
src/cmd/gc/typecheck.c
:reswitch
ラベルの直下、OIND
ノードの型チェックロジックが変更されました。- 元の単一の
if
文が、ネストされたif
文に置き換えられ、エラー発生の条件がより厳密になりました。
--- a/src/cmd/gc/typecheck.c +++ b/src/cmd/gc/typecheck.c @@ -482,9 +482,12 @@ reswitch: t->type = n->left->type; if(t->type == T) { n->left = N; goto ret; } - if((top & (Erv | Etop)) && !isptr[t->etype]) { - yyerror("invalid indirect of %lN", n->left); - goto error; + if(!isptr[t->etype]) { + if(top & (Erv | Etop)) { + yyerror("invalid indirect of %lN", n->left); + goto error; + } + goto ret; } ok |= Erv; n->type = t->type;
-
test/fixedbugs/issue4610.go
:- このコミットによって新しく追加されたテストファイルです。
errorcheck
ディレクティブが含まれており、コンパイル時に特定のエラーメッセージが出力されることを期待しています。- テストコードは
_ = &foo{} // ERROR "is not a type"
という行を含み、foo
が型ではない場合に「is not a type」というエラーが正しく検出されることを検証します。
// errorcheck // Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main type bar struct { x int } func main() { var foo bar _ = &foo{} // ERROR "is not a type" }
コアとなるコードの解説
src/cmd/gc/typecheck.c
の変更は、OIND
ノードの型チェックロジックをより洗練させるものです。
変更前のコードでは、OIND
ノードが処理される際に、その左の子ノード(n->left
)の型t
がポインタ型でない場合、かつそのOIND
ノードが右辺値またはトップレベルのコンテキストで評価されるべき場合に、即座に「invalid indirect」エラーを発生させていました。
しかし、&foo{}
のようなケースでは、foo
は変数であり型ではないため、foo{}
という複合リテラル自体が不正です。この不正な複合リテラルのアドレスを取ろうとする&
演算子(OADDR
)が、内部的にOIND
のチェックロジックと関連付けられていたか、あるいはOIND
のチェックが早すぎる段階で実行され、より根本的な「foo
は型ではない」というエラーがマスクされてしまっていたと考えられます。
新しいコードでは、まず!isptr[t->etype]
(型がポインタでない)という条件で分岐します。
- もし型がポインタでない場合、さらに
top & (Erv | Etop)
(右辺値またはトップレベルのコンテキストにある)という条件をチェックします。- この内側の条件が真であれば、それは本当にポインタでないものに対して間接参照を行おうとしている明確なエラーケースなので、「invalid indirect」エラーを出力します。
- しかし、この内側の条件が偽であれば(つまり、ポインタでないものに対して間接参照を行おうとしているが、それが右辺値やトップレベルのコンテキストではない場合)、エラーを出力せずに
goto ret;
で処理を続行させます。
この「エラーを出力せずに処理を続行させる」という変更が重要です。これにより、&foo{}
のようなケースで、OIND
のチェックが早期にエラーを発生させることなく、コンパイラの他の部分がfoo
が型ではないことを正しく認識し、「is not a type」というより適切なエラーメッセージを出力できるようになります。
test/fixedbugs/issue4610.go
は、この修正が意図通りに機能することを確認するための回帰テストです。このテストは、_ = &foo{}
という不正なコードがコンパイルされた際に、コンパイラが「is not a type」というエラーを正確に報告することを保証します。
関連リンク
- Go言語のIssue #4610: このコミットが修正した問題のトラッキング。
- https://golang.org/issue/4610 (Web検索で直接見つからなかったため、コミットメッセージの
Fixes #4610
から推測されるリンク)
- https://golang.org/issue/4610 (Web検索で直接見つからなかったため、コミットメッセージの
- Gerrit Change-Id:
7058057
- https://golang.org/cl/7058057 (GoプロジェクトのコードレビューシステムGerritのリンク)
参考にした情報源リンク
- Go言語のソースコード (
src/cmd/gc/typecheck.c
,test/fixedbugs/issue4610.go
) - Go言語のコンパイラに関する一般的な知識(AST、型チェックなど)
- Go言語のIssueトラッカーの慣習(
Fixes #XXXX
) - Go言語のGerritコードレビューシステム
- Go言語の複合リテラルとアドレス演算子に関する言語仕様
- Go言語の
errorcheck
テストディレクティブに関する情報 - Go言語のコンパイラ開発に関するブログ記事やドキュメント(一般的な理解のため)
- C言語のビット演算子(
&
,|
)とgoto
文の一般的な知識 - Go言語の
cmd/gc
におけるNode
構造体やtop
フラグ、isptr
配列などの内部表現に関する情報(Goのソースコードから直接読み解いたもの)