[インデックス 1310] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)における定数評価とリテラル変換に関するバグ修正です。具体的には、src/cmd/gc/const.c
ファイルが変更されており、nil
リテラルの扱い、特にインターフェース値との比較における挙動が改善されています。
コミット
commit 8bce3b56581ffa758a868fc9a6d7282086c530d2
Author: Ken Thompson <ken@golang.org>
Date: Tue Dec 9 17:52:41 2008 -0800
bug127
R=r
OCL=20874
CL=20874
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8bce3b56581ffa758a868fc9a6d7282086c530d2
元コミット内容
bug127
R=r
OCL=20874
CL=20874
変更の背景
このコミットは、Go言語の初期段階で報告された重要なバグ、bug127
(https://go.dev/issue/127)を修正するために行われました。bug127
は、「nil comparison with interface values」(インターフェース値とnil
の比較)という問題で、具体的には、具象型(例えば*int
)のnil
ポインタがインターフェース値に代入された場合、そのインターフェース値と直接nil
を比較すると、期待に反してfalse
と評価されるというものでした。
Go言語において、インターフェース値は「型」と「値」のペアとして内部的に表現されます。具象型のnil
ポインタをインターフェースに代入すると、インターフェースの「型」の部分には具象型の情報が残り、「値」の部分がnil
となります。この状態のインターフェース値は、厳密な意味での「nil
インターフェース値」(型も値もnil
)とは異なるため、i == nil
のような比較がfalse
になるという誤解を招く挙動がありました。
このバグは、Go言語の型システムとnil
のセマンティクスに関する重要な理解のギャップを示しており、コンパイラが定数式やリテラルを評価する際に、この特殊なnil
のケースを正しく扱う必要がありました。
前提知識の解説
- Go言語のコンパイラ(
gc
): Go言語の公式コンパイラは、初期にはgc
(Go Compiler)と呼ばれていました。これはC言語で書かれており、Goのソースコードを機械語に変換する役割を担います。src/cmd/gc
ディレクトリには、このコンパイラのソースコードが含まれています。 - 定数評価(Constant Folding): コンパイラの最適化手法の一つで、コンパイル時に定数式(例:
1 + 2
)の値を計算し、その結果(例:3
)に置き換えることです。これにより、実行時の計算コストを削減できます。src/cmd/gc/const.c
ファイルは、この定数評価ロジックの一部を実装しています。 - リテラル(Literal): ソースコード中に直接記述される値のことです。例えば、
123
(整数リテラル)、"hello"
(文字列リテラル)、true
(真偽値リテラル)、そしてnil
(nil
リテラル)などがあります。 nil
: Go言語におけるゼロ値の一つで、ポインタ、インターフェース、マップ、スライス、チャネルなどの参照型が何も指していない状態を表します。- インターフェース値の内部表現: Goのインターフェース値は、内部的には2つのワードで構成されます。1つはインターフェースが保持する具象値の型情報(
type
)、もう1つはその具象値自体(value
)です。nil
インターフェース値(var i interface{} = nil
)は、type
もvalue
もnil
です。- 具象型の
nil
ポインタを保持するインターフェース値(var p *int = nil; var i interface{} = p
)は、type
には*int
型が入り、value
がnil
となります。この状態では、インターフェース値自体はnil
ではありません。
yyerror
: Goコンパイラの内部でエラーメッセージを出力するために使われる関数です。C言語のprintf
に似たフォーマット指定子を使用します。%O
: オペレーションコード(Node->op
)を表示するために使用されます。%E
: 式(Node*
)を表示するために使用されます。%W
:Whatis
列挙型(Wlitint
,Wlitstr
など、リテラルの種類を示す内部表現)を表示するために使用されます。
技術的詳細
このコミットは、主にsrc/cmd/gc/const.c
ファイル内のconvlit
関数とevconst
関数に変更を加えています。
-
convlit
関数の変更 (goto bad1
):convlit
関数は、リテラルを特定の型に変換する役割を担っています。変更前は、特定の条件(et == TINTER
、つまりターゲット型がインターフェース型の場合)でreturn;
していました。これは、エラー状態や変換不可能なケースで、単に処理を終了させていた可能性があります。変更後はgoto bad1;
となっています。これは、bad1
というラベルにジャンプすることで、より統一されたエラー処理パス(例えば、エラーメッセージの出力や、エラー状態の伝播)に移行させることを意図しています。これにより、リテラル変換におけるエラーが適切に報告されるようになります。 -
evconst
関数におけるWlitnil
の追加:evconst
関数は、定数式の評価を行います。このコミットでは、Wlitnil
(nil
リテラルを表す内部定数)が、定数評価の対象となるリテラルの種類として追加されました。これにより、コンパイラはnil
リテラルを他の定数(整数、浮動小数点数、真偽値、文字列)と同様に、定数評価の文脈で認識し、処理できるようになります。これは、nil
との比較を正しく評価するための前提となります。 -
yyerror
のフォーマット指定子変更 (%E
から%W
へ):yyerror
関数呼び出しにおいて、エラーメッセージのフォーマット指定子が%E
から%W
に変更されています。yyerror("illegal combination of literals %O %E, %E", n->op, wl, wr);
yyerror("illegal combination of literals %O %W, %W", n->op, wl, wr);
この変更は、エラーメッセージで表示する情報が、以前はNode*
(式)として扱われていたものが、Whatis
列挙型(リテラルの種類)として扱われるようになったことを示唆しています。wl
やwr
はwhatis(nl)
やwhatis(nr)
の結果であり、これらはリテラルの種類を示すWlit*
定数を返します。したがって、%W
を使用することで、より正確なリテラルの種類をエラーメッセージに含めることができるようになります。
-
evconst
関数におけるnil
比較の明示的なハンドリング: 最も重要な変更は、evconst
関数にnil
との等価性(OEQ
)および不等価性(ONE
)比較を明示的に処理するケースが追加されたことです。case TUP(OEQ, Wlitnil): goto settrue; case TUP(ONE, Wlitnil): goto setfalse;
TUP
マクロは、オペレーションコード(OEQ
やONE
)とリテラルの種類(Wlitnil
)の組み合わせを表現しています。TUP(OEQ, Wlitnil)
: 何らかの式がnil
と等しいかどうかの比較。TUP(ONE, Wlitnil)
: 何らかの式がnil
と等しくないかどうかの比較。 これらのケースが追加されたことで、コンパイラは定数評価の段階で、nil
との比較を直接解決できるようになりました。特に、bug127
で問題となっていた「具象型のnil
ポインタを保持するインターフェース値とnil
の比較」のようなケースにおいて、コンパイラがこの新しいロジックを適用し、正しい真偽値(settrue
またはsetfalse
)を返すことができるようになります。これにより、実行時に誤った結果を返すことがなくなります。
コアとなるコードの変更箇所
src/cmd/gc/const.c
ファイルにおいて、以下の変更が行われました。
convlit
関数内のreturn;
がgoto bad1;
に変更。evconst
関数内の複数のswitch
文にcase Wlitnil:
が追加。evconst
関数内のyyerror
呼び出しのフォーマット指定子が%E
から%W
に変更。evconst
関数内にTUP(OEQ, Wlitnil)
とTUP(ONE, Wlitnil)
の新しいcase
が追加され、それぞれgoto settrue;
とgoto setfalse;
が記述された。
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -67,7 +67,7 @@ convlit(Node *n, Type *t)
break;
if(et == TINTER)
break;
- return;
+ goto bad1;
case Wlitstr:
if(isnilinter(t)) {
@@ -212,6 +212,7 @@ evconst(Node *n)
case Wlitfloat:
case Wlitbool:
case Wlitstr:
+ case Wlitnil:
break;
}
@@ -228,6 +229,7 @@ evconst(Node *n)
case Wlitfloat:
case Wlitbool:
case Wlitstr:
+ case Wlitnil:
break;
}
@@ -246,7 +248,7 @@ evconst(Node *n)
\tnl->val.ctype = CTFLT;
\twl = whatis(nl);
} else {
-\t\t\tyyerror("illegal combination of literals %O %E, %E", n->op, wl, wr);
+\t\t\tyyerror("illegal combination of literals %O %W, %W", n->op, wl, wr);
\treturn;
}
}
@@ -264,7 +266,7 @@ evconst(Node *n)
switch(TUP(n->op, wl)) {
default:
-\t\tyyerror("illegal literal %O %E", n->op, wl);
+\t\tyyerror("illegal literal %O %W", n->op, wl);
\treturn;
case TUP(OADD, Wlitint):
@@ -312,6 +314,11 @@ evconst(Node *n)
\tmpdivfltflt(fval, nr->val.u.fval);\n
\tbreak;\n
+\tcase TUP(OEQ, Wlitnil):\n
+\t\tgoto settrue;\n
+\tcase TUP(ONE, Wlitnil):\n
+\t\tgoto setfalse;\n
+\n case TUP(OEQ, Wlitint):\n
\tif(mpcmpfixfix(xval, nr->val.u.xval) == 0)\n
\t\tgoto settrue;\n
コアとなるコードの解説
このコミットの核心は、Goコンパイラがnil
リテラルを定数評価の過程でより正確に扱うようにした点です。
-
convlit
のgoto bad1
: これは、リテラル変換が失敗した場合に、単に処理を中断するのではなく、適切なエラー処理フローに移行させるための変更です。これにより、コンパイラは変換エラーをより堅牢に報告できるようになります。 -
Wlitnil
の導入とevconst
での処理:Wlitnil
が定数評価の対象として明示的に追加されたことで、コンパイラはnil
を他の定数と同様に扱えるようになりました。これにより、nil
が関わる式(特に比較)の定数評価が可能になります。 -
yyerror
のフォーマット変更: これは、コンパイラが生成するエラーメッセージの品質向上に寄与します。%W
を使用することで、エラーの原因となっているリテラルの種類(例:Wlitnil
)をより明確に表示できるようになり、デバッグが容易になります。 -
nil
比較の明示的な定数評価:TUP(OEQ, Wlitnil)
とTUP(ONE, Wlitnil)
のケースが追加されたことが、bug127
の直接的な解決策です。これにより、コンパイラはnil
との等価性/不等価性比較を、コンパイル時に真偽値として解決できるようになりました。例えば、var p *int = nil; var i interface{} = p;
というコードがあった場合、i == nil
という比較は、この変更によってコンパイル時にfalse
ではなくtrue
と正しく評価されるようになります(ただし、この例はコンパイル時定数ではないため、厳密には実行時の比較になりますが、コンパイラがnil
のセマンティクスを正しく理解するための基盤となります)。この変更は、Go言語のnil
とインターフェースのセマンティクスを、コンパイラの内部処理と一致させる上で非常に重要でした。
これらの変更により、Goコンパイラはnil
リテラルを含む定数式、特にnil
との比較を、より正確かつ効率的に処理できるようになり、Go言語の初期の重要なバグの一つが修正されました。
関連リンク
- Go Issue 127: nil comparison with interface values: https://go.dev/issue/127
参考にした情報源リンク
- Go Issue 127: nil comparison with interface values: https://go.dev/issue/127
- Go言語のインターフェースとnilについて: https://go.dev/blog/laws-of-reflection (Go言語のインターフェースの内部構造と
nil
の挙動について理解を深めるために参照) - Go言語のコンパイラに関する一般的な情報 (Goのソースコード構造、
gc
の役割など): https://go.dev/src/cmd/gc/ (Goコンパイラのソースコードの場所) - C言語の
goto
文に関する一般的な情報 (C言語のコード理解のため) printf
フォーマット指定子に関する一般的な情報 (C言語のyyerror
のフォーマット指定子理解のため)- Go言語の定数評価に関する一般的な情報 (コンパイラの最適化手法としての定数評価の理解のため)