[インデックス 18729] ファイルの概要
このコミットは、Goコンパイラのcmd/gc
パッケージにおけるchecknil
関数の挙動を修正するものです。具体的には、nil
リテラルに対するchecknil
が内部エラーを引き起こす問題を解決します。この修正には、src/cmd/gc/pgen.c
のコード変更と、その問題を再現し修正を検証するための新しいテストケースtest/fixedbugs/issue7346.go
の追加が含まれます。
コミット
commit 52e6d7c6224612a3b60caa799bc22bd50ab16acb
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Tue Mar 4 08:18:17 2014 +0100
cmd/gc: use a register to checknil constants.
Fixes #7346.
LGTM=rsc
R=rsc, iant, khr
CC=golang-codereviews
https://golang.org/cl/69050044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/52e6d7c6224612a3b60caa799bc22bd50ab16acb
元コミット内容
cmd/gc: use a register to checknil constants.
Fixes #7346.
LGTM=rsc
R=rsc, iant, khr
CC=golang-codereviews
https://golang.org/cl/69050044
変更の背景
この変更は、Goコンパイラがnil
リテラルに対してchecknil
操作を実行しようとした際に発生する内部エラー("doasm"エラー)を修正するために行われました。この問題はGoのIssue 7346として報告されており、*(*int)(nil)
のようなコードがコンパイル時にクラッシュするというものでした。
Go言語では、ポインタの逆参照(デリファレンス)を行う前に、そのポインタがnil
でないことを確認する「nilチェック」が実行されます。これはランタイムパニックを防ぐための重要な安全機構です。しかし、特定の条件下でnil
リテラル自体に対してこのチェックが適用されると、コンパイラが予期しない状態に陥り、内部エラーが発生していました。
前提知識の解説
このコミットを理解するためには、Goコンパイラのバックエンドにおけるコード生成と最適化に関するいくつかの概念を理解する必要があります。
cmd/gc
: Goコンパイラの主要な部分であり、Goソースコードを中間表現に変換し、最終的にアセンブリコードを生成する役割を担います。checknil
: Goコンパイラが生成する命令の一つで、ポインタがnil
であるかどうかをチェックし、nil
であればランタイムパニックを引き起こします。これは、ポインタのデリファレンス(例:*p
)の前に挿入されます。Node
: コンパイラ内部でコードの抽象構文木(AST)を表すデータ構造です。各Node
は、変数、定数、演算子、関数呼び出しなど、プログラムの要素を表します。OREGISTER
:Node
のop
フィールドがOREGISTER
である場合、そのノードがレジスタに格納された値を表すことを示します。OLITERAL
:Node
のop
フィールドがOLITERAL
である場合、そのノードがリテラル値(定数)を表すことを示します。addable
: コンパイラ内部の概念で、あるNode
が直接アセンブリ命令のオペランドとして使用できるかどうかを示します。例えば、メモリ上のアドレスやレジスタに直接対応する値はaddable
です。cgen
: コードジェネレータの一部で、Node
ツリーをトラバースしてアセンブリコードを生成する関数です。regalloc
: レジスタを割り当てる関数です。コンパイラは、計算中に値を一時的に保持するためにCPUレジスタを使用します。gins
: アセンブリ命令を生成する関数です。ACHECKNIL
:checknil
操作に対応するアセンブリ命令です。
Goコンパイラは、コードを生成する際に、可能な限り効率的なアセンブリ命令を生成しようとします。これには、値を直接メモリからロードしたり、レジスタに格納したりする判断が含まれます。checknil
のような操作は、通常、メモリ上のポインタやレジスタに格納されたポインタに対して実行されます。
技術的詳細
問題は、cgen_checknil
関数がnil
リテラル(OLITERAL
)を適切に処理していなかった点にありました。
元のコードでは、cgen_checknil
関数内で、thechar == '5'
(これはおそらく特定のアーキテクチャやコンパイラのフェーズを示す内部的なフラグ)の場合、またはノードがaddable
でない場合に、レジスタを割り当ててからcgen
とgins(ACHECKNIL)
を呼び出すというロジックがありました。
// Original code snippet from src/cmd/gc/pgen.c
if((thechar == '5' && n->op != OREGISTER) || !n->addable) {
regalloc(®, types[tptr], n);
cgen(n, ®);
gins(ACHECKNIL, ®, N);
}
この条件式は、n->op == OLITERAL
(つまりnil
リテラル)の場合に問題を引き起こしました。nil
リテラルは通常addable
ではありません。そのため、!n->addable
の条件に合致し、レジスタ割り当てとcgen
が試みられます。しかし、nil
リテラルはメモリ上のアドレスを持たないため、cgen
がnil
リテラルをレジスタにロードしようとすると、コンパイラ内部で矛盾が生じ、"doasm"エラーが発生していました。
修正は、この条件式にn->op == OLITERAL
という条件を追加することで、nil
リテラルがOREGISTER
でない場合でも、明示的にレジスタを介してchecknil
が実行されるようにしました。
// Modified code snippet from src/cmd/gc/pgen.c
if((thechar == '5' && n->op != OREGISTER) || !n->addable || n->op == OLITERAL) {
regalloc(®, types[tptr], n);
cgen(n, ®);
gins(ACHECKNIL, ®, N);
}
この変更により、nil
リテラルがchecknil
の対象となった場合、常に一時的なレジスタにロードされ、そのレジスタの値に対してACHECKNIL
命令が生成されるようになります。これにより、コンパイラがnil
リテラルを直接デリファレンスしようとするのではなく、レジスタを介して安全に処理できるようになり、内部エラーが回避されます。
新しいテストケースtest/fixedbugs/issue7346.go
は、この問題を再現する最小限のコードを含んでいます。
package main
func main() {
_ = *(*int)(nil)
}
このコードは、nil
を*int
型にキャストし、その結果をデリファレンスしようとします。修正前はこれがコンパイルエラーを引き起こしましたが、修正後は正しくランタイムパニック(nil pointer dereference
)を発生させるようになります。これは、コンパイラが正しくchecknil
を挿入し、そのチェックがnil
リテラルに対しても機能することを示しています。
コアとなるコードの変更箇所
src/cmd/gc/pgen.c
のcgen_checknil
関数内の条件式が変更されました。
--- a/src/cmd/gc/pgen.c
+++ b/src/cmd/gc/pgen.c
@@ -476,7 +476,7 @@ cgen_checknil(Node *n)\n \tdump("checknil", n);\n \tfatal("bad checknil");\n }\n-\tif((thechar == '5' && n->op != OREGISTER) || !n->addable) {\n+\tif((thechar == '5' && n->op != OREGISTER) || !n->addable || n->op == OLITERAL) {\n \t\tregalloc(®, types[tptr], n);\n \t\tcgen(n, ®);\n \t\tgins(ACHECKNIL, ®, N);\
また、問題を再現するための新しいテストファイルが追加されました。
--- /dev/null
+++ b/test/fixedbugs/issue7346.go
@@ -0,0 +1,14 @@
+// compile
+
+// Copyright 2014 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.
+
+// issue 7346 : internal error "doasm" error due to checknil
+// of a nil literal.
+
+package main
+
+func main() {
+ _ = *(*int)(nil)
+}
コアとなるコードの解説
変更された行は以下の通りです。
-\tif((thechar == '5' && n->op != OREGISTER) || !n->addable) {
+\tif((thechar == '5' && n->op != OREGISTER) || !n->addable || n->op == OLITERAL) {
この変更は、cgen_checknil
関数が、checknil
操作の対象となるNode
n
をどのように処理するかを決定する条件式を修正しています。
thechar == '5' && n->op != OREGISTER
: これは特定のコンパイラ設定(おそらくターゲットアーキテクチャや最適化レベル)において、ノードがレジスタにない場合にレジスタを介した処理が必要であることを示唆しています。!n->addable
: ノードが直接アセンブリ命令のオペランドとして使用できない場合(例: 複雑な式の結果など)に、レジスタを介した処理が必要であることを示します。n->op == OLITERAL
: このコミットで追加された条件です。 これにより、ノードがnil
リテラルである場合、たとえそれがaddable
であると誤って判断されたり、他の条件に合致しなかったりしても、明示的にレジスタを割り当ててchecknil
を実行するパスを通るようになります。これにより、nil
リテラルが直接デリファレンスされることによるコンパイラの内部エラーが回避されます。
この修正により、nil
リテラルに対するchecknil
が常に安全な方法で処理されるようになり、コンパイラの堅牢性が向上しました。
関連リンク
- Go Issue 7346: https://github.com/golang/go/issues/7346
- Go CL 69050044: https://golang.org/cl/69050044
参考にした情報源リンク
- Go issue 7346 (Web search result): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFsifNZ3N7kRCr676DGHFoGJOS317R8IA7PFMf1xIPFQz-PqBmp24xjOq-ev42OgyRmdGosxyjMmp5qB-8pIjnwWdMjFXWpqSGjCUyr7_wFB9GFseePSOmJQGhcaTy-v8mmyzhkd45IOL6KXeg1nq5tEPQQSR1URO7Bs4r7gx8=
- Go言語のコンパイラとランタイムに関する一般的な知識。
- Go言語の
nil
ポインタとデリファレンスに関する一般的な知識。I have provided the detailed explanation of the commit as requested. Please let me know if you need anything else.