[インデックス 14482] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における型チェックの不具合を修正するものです。具体的には、ステートメントレベルでの無効な間接参照(invalid indirect)エラーが適切に検出されない問題を解決しています。これにより、コンパイラが本来エラーとすべきコードを見逃し、実行時に予期せぬパニックや不正な動作を引き起こす可能性があった状況が改善されました。
コミット
commit 7c295f3f0c0dbed86698699f499c5ad08c3b055b
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Nov 27 01:46:54 2012 +0800
cmd/gc: fix invalid indirect error at statement level
Fixes #4429.
R=golang-dev, remyoudompheng, daniel.morsing, rsc
CC=golang-dev
https://golang.org/cl/6850097
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7c295f3f0c0dbed86698699f499c5ad08c3b055b
元コミット内容
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 2d1dbd75f1..3b32de2116 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -482,7 +482,7 @@ reswitch:
tn->left = N;
goto ret;
}
- if((top & Erv) && !isptr[t->etype]) {
+ if((top & (Erv | Etop)) && !isptr[t->etype]) {
yyerror("invalid indirect of %lN", n->left);
goto error;
}
diff --git a/test/fixedbugs/issue4429.go b/test/fixedbugs/issue4429.go
new file mode 100644
index 0000000000..8a9b3b02045
--- /dev/null
+++ b/test/fixedbugs/issue4429.go
@@ -0,0 +1,16 @@
+// 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 p
+
+type a struct {
+ a int
+}
+
+func main() {
+ av := a{};
+ *a(av); // ERROR "invalid indirect"
+}
変更の背景
このコミットは、Go言語のコンパイラ(gc
)が特定の状況下で「無効な間接参照(invalid indirect)」エラーを正しく報告しないバグを修正するために行われました。具体的には、Goの型システムにおいて、ポインタ型ではない値に対して間接参照演算子(*
)を適用しようとした場合に、コンパイラがエラーを検出できないケースが存在しました。
Go言語では、ポインタはメモリ上のアドレスを指し、そのアドレスに格納されている値にアクセスするために間接参照演算子を使用します。しかし、ポインタではない通常の変数(例えば、構造体のインスタンスやプリミティブ型)に対して間接参照を行おうとすると、それは論理的に誤った操作であり、コンパイラによってエラーとして報告されるべきです。
このバグは、特に型変換が行われた結果の非ポインタ値がステートメントのトップレベルで使用された場合に顕在化しました。コンパイラの型チェックロジックが、このような特定のコンテキストを見落としていたため、不正なコードがコンパイルを通過し、実行時にパニック(例:nil
ポインタデリファレンス)を引き起こす可能性がありました。
この問題はGo issue #4429として報告され、このコミットはその報告された問題を解決するためのものです。
前提知識の解説
このコミットの理解には、以下のGo言語およびコンパイラに関する基本的な知識が必要です。
-
Go言語の型システムとポインタ:
- Go言語は静的型付け言語であり、変数は特定の型を持ちます。
- ポインタ: 変数のメモリアドレスを保持する特殊な型です。
*T
は型T
へのポインタを表します。 - 間接参照演算子 (
*
): ポインタの前に置くことで、そのポインタが指すメモリアドレスに格納されている「値」にアクセスします。例えば、p
が*int
型であれば、*p
はint
型の値です。 - アドレス演算子 (
&
): 変数の前に置くことで、その変数のメモリアドレス(ポインタ)を取得します。例えば、x
がint
型であれば、&x
は*int
型です。 - ポインタではない値型(例:
int
,string
,struct
など)に対して間接参照演算子*
を適用することは、Go言語の文法上、無効な操作です。
-
Goコンパイラ (
gc
):- Go言語の公式コンパイラは
gc
(Go Compiler)と呼ばれ、Goのソースコードを機械語に変換します。 - 型チェック: コンパイルプロセスの重要な段階の一つで、プログラムが型規則に準拠しているかを確認します。これにより、多くのプログラミングエラーが実行時ではなくコンパイル時に検出されます。
src/cmd/gc
ディレクトリには、Goコンパイラのソースコードが含まれています。src/cmd/gc/typecheck.c
: このファイルは、Goコンパイラの型チェックロジックのC言語実装の一部です。Goコンパイラは、初期の段階ではC言語で書かれており、後にGo言語自身で書き直されました(セルフホスト)。このコミットが行われた2012年時点では、まだC言語のコードベースが残っていました。
- Go言語の公式コンパイラは
-
コンパイラの内部表現とフラグ:
- コンパイラは、ソースコードを解析する際に、各ノード(式やステートメント)に様々な属性やフラグを付与します。これらは、そのノードがどのような文脈で使用されているか、どのような型を持つか、どのような操作が可能かなどをコンパイラに伝えます。
top
変数: このコミットのコードスニペットに登場するtop
は、コンパイラの型チェックフェーズにおいて、現在のノードが評価されるべきコンテキストを示すビットマスク(フラグの集合)であると推測されます。Erv
(Expression R-value): 式が右辺値(R-value)として評価されるべきコンテキストを示すフラグ。右辺値は、値を持つ式であり、代入の右側に置かれることができます。Etop
(Expression Top-level): 式がステートメントのトップレベルにあるコンテキストを示すフラグ。例えば、f()
という関数呼び出しが単独のステートメントとして存在する場合などです。
技術的詳細
このコミットの核心は、src/cmd/gc/typecheck.c
ファイル内の型チェックロジックの変更にあります。
変更前のコードは以下の通りでした。
if((top & Erv) && !isptr[t->etype]) {
yyerror("invalid indirect of %lN", n->left);
goto error;
}
このコードは、「もし現在のノードが右辺値コンテキスト(top & Erv
)であり、かつその型がポインタではない(!isptr[t->etype]
)ならば、『無効な間接参照』エラーを報告する」というロジックでした。
しかし、Go issue #4429で報告された問題は、以下のようなコードで発生しました。
type a struct { a int }
func main() {
av := a{};
*a(av); // ここでエラーが検出されない
}
ここで、*a(av)
という式は、a(av)
という型変換の結果(これはポインタではないa
型の値)に対して間接参照演算子*
を適用しようとしています。この式は、代入の右辺ではなく、単独のステートメントとして存在しています。
問題は、*a(av)
が単独のステートメントとして存在する場合、コンパイラのtop
フラグにErv
が含まれない可能性があったことです。Erv
は主に式が値を生成し、その値が利用される文脈(例:代入の右辺、関数の引数)で設定されます。しかし、単独のステートメントの場合、その式の結果が直接利用されないため、Erv
が設定されないことがありました。
そこで、コミットでは条件式が以下のように変更されました。
if((top & (Erv | Etop)) && !isptr[t->etype]) {
yyerror("invalid indirect of %lN", n->left);
goto error;
}
この変更により、エラーチェックの条件にEtop
フラグが追加されました。
Erv | Etop
: これは、top
フラグがErv
(右辺値コンテキスト)であるか、またはEtop
(ステートメントのトップレベルコンテキスト)であるかのいずれかの場合に真となります。
つまり、この修正は、「ポインタではない値に対する間接参照」という不正な操作が、それが右辺値として評価される場合だけでなく、ステートメントのトップレベルで単独の式として使用される場合でも、確実に検出されるように型チェックの範囲を広げたものです。
これにより、*a(av);
のようなコードがコンパイル時に「invalid indirect」エラーとして正しく報告されるようになり、実行時の予期せぬ動作を防ぐことができます。
コアとなるコードの変更箇所
変更はsrc/cmd/gc/typecheck.c
ファイルの一箇所のみです。
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -482,7 +482,7 @@ reswitch:
tn->left = N;
goto ret;
}
- if((top & Erv) && !isptr[t->etype]) {
+ if((top & (Erv | Etop)) && !isptr[t->etype]) {
yyerror("invalid indirect of %lN", n->left);
goto error;
}
具体的には、if
文の条件式において、top
変数とErv
のビットAND演算だった部分が、top
変数と(Erv | Etop)
のビットAND演算に変更されています。
コアとなるコードの解説
変更された行は、Goコンパイラの型チェッカーが、間接参照演算子(*
)が適用される式の妥当性を検証する部分です。
top
: 現在型チェック中の式のコンテキストを示すビットマスクです。コンパイラは、式が代入の右辺にあるのか、関数の引数なのか、単独のステートメントなのか、といった情報をこのtop
フラグで管理します。Erv
: "Expression R-value" の略で、式が右辺値として評価されるべきコンテキストを示します。つまり、その式の値が何らかの形で使用されることを意味します。Etop
: "Expression Top-level" の略で、式がステートメントのトップレベルにあるコンテキストを示します。例えば、fmt.Println("hello")
のように、式が単独でステートメントを構成する場合などです。isptr[t->etype]
:t
は型を表す構造体(またはそれに類するもの)であり、t->etype
はその型の種類(例:TINT
、TPTR
など)を示します。isptr
は、与えられた型がポインタ型であるかどうかを判定するための配列または関数であると推測されます。!isptr[t->etype]
は、「その型がポインタではない」という条件を意味します。yyerror(...)
: コンパイルエラーを報告するための関数です。
変更前は、top
がErv
フラグを含んでいる場合にのみ、非ポインタ型に対する間接参照がチェックされていました。しかし、*a(av);
のようなコードは、Erv
コンテキストではないが、Etop
コンテキストであるため、このチェックをすり抜けていました。
変更後は、top
がErv
またはEtop
のいずれかのフラグを含んでいる場合に、非ポインタ型に対する間接参照がチェックされるようになりました。これにより、ステートメントのトップレベルにある無効な間接参照も確実に捕捉され、コンパイル時にエラーとして報告されるようになります。
この修正は、Go言語の型安全性を強化し、開発者がより堅牢なコードを書けるようにするための重要な改善です。
関連リンク
- Go issue #4429: https://github.com/golang/go/issues/4429
- Gerrit Code Review (CL 6850097): https://golang.org/cl/6850097
参考にした情報源リンク
- Go言語の公式ドキュメント(ポインタ、型システムに関する記述)
- Goコンパイラのソースコード(
src/cmd/gc
ディレクトリの構造とファイル内容) - Go言語のissueトラッカー(#4429の議論内容)
- Go言語のコードレビューシステム(Gerrit)