[インデックス 1336] ファイルの概要
このコミットは、Goコンパイラの型変換ロジックにおけるバグ修正を目的としています。具体的には、src/cmd/gc/const.c
内の convlit
関数が convlit1
にリファクタリングされ、型変換の挙動を制御するための新しい引数 conv
が導入されました。これにより、特定のコンテキストでの不適切な文字列リテラル変換が防止されます。
コミット
commit e683fb7a54ab60530e8c1e06c416cc646dbf519b
Author: Ken Thompson <ken@golang.org>
Date: Fri Dec 12 13:10:36 2008 -0800
bug104
R=r
OCL=21082
CL=21082
---
src/cmd/gc/const.c | 42 ++++++++++++++++++++++++++----------------
src/cmd/gc/go.h | 1 +
src/cmd/gc/walk.c | 2 +-\
3 files changed, 28 insertions(+), 17 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e683fb7a54ab60530e8c1e06c416cc646dbf519b
元コミット内容
bug104
変更の背景
このコミットは、Goコンパイラにおける「bug104」として知られる問題を修正するために行われました。Goコンパイラは、ソースコードを機械語に変換する過程で、様々な型変換(型キャストやリテラルの型推論など)を行います。特に、数値リテラルから文字列への変換は、Go言語の仕様上、特定の文脈でのみ許容されるべき挙動です。
元の実装では、convlit
関数がリテラルの型変換を処理していましたが、この関数が呼び出される全てのコンテキストにおいて、数値リテラルから文字列への変換が常に試みられてしまう可能性がありました。これにより、コンパイラが予期しない型変換を行い、結果として不正なコード生成やコンパイルエラーを引き起こす「bug104」が発生していたと考えられます。
このバグは、Go言語の初期開発段階で発見されたものであり、コンパイラの堅牢性と正確性を確保するために修正が必要でした。特に、Go言語が静的型付け言語である以上、型変換のルールは厳密に適用されるべきであり、コンパイラはそのルールを正確に反映する必要があります。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造とGo言語の基本的な型システムに関する知識が必要です。
- Goコンパイラ (
gc
): Go言語の公式コンパイラは、歴史的にgc
(Go Compiler) と呼ばれるツールチェーンの一部です。これは、ソースコードの解析、抽象構文木 (AST) の構築、型チェック、中間コード生成、最適化、最終的な機械語コード生成といった一連のコンパイルプロセスを担当します。 - 抽象構文木 (AST): ソースコードは、コンパイラによって解析され、プログラムの構造を木構造で表現したASTに変換されます。コンパイラの各フェーズ(型チェック、最適化など)はこのASTを走査(walk)しながら処理を進めます。
- リテラル: プログラム中に直接記述される値のことです。例えば、
123
(整数リテラル)、3.14
(浮動小数点リテラル)、"hello"
(文字列リテラル) などがあります。 - 型変換 (Type Conversion): ある型の値を別の型の値に変換することです。Go言語では、明示的な型変換(例:
int(f)
)と、コンパイラが自動的に行う暗黙的な型変換(例: 定数リテラルの型推論)があります。 Node
構造体: Goコンパイラの内部でASTの各ノードを表すデータ構造です。Node
には、そのノードが表す式や文の種類、型情報、値などが含まれます。Type
構造体: Goコンパイラの内部で型情報を表すデータ構造です。Mpflt
,Mpint
: Goコンパイラが内部で多倍長浮動小数点数や多倍長整数を扱うための構造体です。リテラル値はこれらの形式で表現されることがあります。Rune
: Go言語におけるUnicodeコードポイントを表す型(int32
のエイリアス)。文字列はルーンのシーケンスとして扱われます。String
構造体: Goコンパイラが内部で文字列を表現するための構造体。
このコミットは、特に convlit
関数がリテラル(特に数値リテラル)を他の型に変換する際の挙動に焦点を当てています。Go言語では、整数リテラルはデフォルトで型を持たず、使用される文脈によって適切な型に推論されます。しかし、string(10)
のように整数を明示的に文字列に変換する構文も存在します。このコミットは、このような明示的な変換と、それ以外の文脈での変換を区別するためのものです。
技術的詳細
このコミットの主要な変更点は、src/cmd/gc/const.c
にある convlit
関数のリファクタリングです。
-
convlit
からconvlit1
へのリネームと引数追加: 元のvoid convlit(Node *n, Type *t)
関数がvoid convlit1(Node *n, Type *t, int conv)
に名前変更され、新たにint conv
という引数が追加されました。このconv
引数は、型変換が「明示的な変換」であるかどうかを示すフラグとして機能します。conv
が1
の場合は明示的な変換、0
の場合はそれ以外の文脈での変換(例えば、リテラルの型推論など)を意味します。 -
文字列変換ロジックの条件付き実行:
convlit1
関数内で、数値リテラルから文字列への変換を行うコードブロック(isptrto(t, TSTRING)
で判定される部分)が、if(!conv) goto bad1;
という条件文の後に移動されました。- 変更前は、この文字列変換ロジックは常に実行される可能性がありました。
- 変更後は、
conv
が0
(つまり明示的な変換ではない)の場合、この文字列変換ロジックはスキップされ、bad1
ラベルにジャンプします。これは、明示的なstring(int)
のような変換でない限り、数値リテラルが自動的に文字列に変換されるべきではないという意図を反映しています。
-
新しい
convlit
関数の導入:void convlit(Node *n, Type *t)
という新しい関数が導入されました。この関数は単にconvlit1(n, t, 0);
を呼び出します。これにより、既存のコードベースでconvlit
を呼び出している箇所は、デフォルトでconv=0
の挙動(つまり、数値から文字列への変換を抑制する挙動)を維持できます。 -
src/cmd/gc/walk.c
の変更:src/cmd/gc/walk.c
内のwalk
関数(ASTを走査する主要な関数の一つ)において、convlit(l, t);
の呼び出しがconvlit1(l, t, 1);
に変更されました。この変更は非常に重要です。walk.c
のこの箇所は、おそらく明示的な型変換(例:string(expr)
)を処理するコンテキストであるため、conv
フラグを1
に設定することで、数値から文字列への変換が正しく許可されるようにしています。
これらの変更により、Goコンパイラは、数値リテラルから文字列への変換を、明示的に要求された場合にのみ実行し、それ以外の暗黙的な文脈では行わないように制御できるようになりました。これにより、「bug104」が修正され、コンパイラの型変換の挙動がより正確で予測可能になります。
コアとなるコードの変更箇所
src/cmd/gc/const.c
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -34,7 +34,7 @@ truncfltlit(Mpflt *fv, Type *t)
}
void
-convlit(Node *n, Type *t)
+convlit1(Node *n, Type *t, int conv)
{
int et, wt;
@@ -92,21 +92,6 @@ convlit(Node *n, Type *t)
defaultlit(n);
return;
}
- if(isptrto(t, TSTRING)) {
- Rune rune;
- int l;
- String *s;
-
- rune = mpgetfix(n->val.u.xval);
- l = runelen(rune);
- s = mal(sizeof(*s)+l);
- s->len = l;
- runetochar((char*)(s->s), &rune);
-
- n->val.u.sval = s;
- n->val.ctype = CTSTR;
- break;
- }
if(isint[et]) {
// int to int
if(mpcmpfixfix(n->val.u.xval, minintval[et]) < 0)
@@ -132,6 +117,25 @@ convlit(Node *n, Type *t)
truncfltlit(fv, t);
break;
}
+ if(!conv)
+ goto bad1;
+
+ // only done as string(CONST)
+ if(isptrto(t, TSTRING)) {
+ Rune rune;
+ int l;
+ String *s;
+
+ rune = mpgetfix(n->val.u.xval);
+ l = runelen(rune);
+ s = mal(sizeof(*s)+l);
+ s->len = l;
+ runetochar((char*)(s->s), &rune);
+
+ n->val.u.sval = s;
+ n->val.ctype = CTSTR;
+ break;
+ }
goto bad1;
case Wlitfloat:
@@ -186,6 +190,12 @@ bad3:
return;
}
+void
+convlit(Node *n, Type *t)
+{
+ convlit1(n, t, 0);
+}
+
void
evconst(Node *n)
{
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -822,6 +822,7 @@ Node* old2new(Node*, Type*);
/*
* const.c
*/
+void convlit1(Node*, Type*, int);
void convlit(Node*, Type*);
void evconst(Node*);
int cmpslit(Node *l, Node *r);
src/cmd/gc/walk.c
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -560,7 +560,7 @@ loop:
goto ret;
if(!iscomposite(t))
- convlit(l, t);
+ convlit1(l, t, 1);
// nil conversion
if(eqtype(t, l->type, 0)) {
コアとなるコードの解説
src/cmd/gc/const.c
の変更
-
convlit
からconvlit1
への変更:convlit
関数は、リテラルノードn
をターゲット型t
に変換する役割を担っていました。この関数がconvlit1
にリネームされ、int conv
という新しい引数が追加されました。このconv
引数は、変換が明示的な型変換(例:string(10)
)であるか、それとも暗黙的な型推論や他のコンテキストでの変換であるかを区別するために使用されます。 -
文字列変換ロジックの移動と条件付け: 元の
convlit
関数内にあった、整数リテラルを文字列に変換するロジック(isptrto(t, TSTRING)
で判定されるブロック)が、convlit1
の中でif(!conv) goto bad1;
の後に移動されました。isptrto(t, TSTRING)
は、ターゲット型t
が文字列型へのポインタ(Go言語では文字列は値型ですが、内部的にはポインタのように扱われることがあります)であるかをチェックします。rune = mpgetfix(n->val.u.xval);
は、リテラルノードn
の整数値をRune
型(Unicodeコードポイント)として取得します。l = runelen(rune);
は、そのルーンをUTF-8エンコードした場合のバイト長を計算します。s = mal(sizeof(*s)+l); s->len = l; runetochar((char*)(s->s), &rune);
は、新しいString
構造体を割り当て、ルーンをUTF-8エンコードして文字列データとして格納します。n->val.u.sval = s; n->val.ctype = CTSTR;
は、ノードn
の値を新しく作成した文字列に設定し、その型を文字列リテラル (CTSTR
) に変更します。- このブロックが
if(!conv) goto bad1;
の後に配置されたことで、conv
が0
の場合(つまり、明示的な変換ではない場合)は、この文字列変換ロジックは実行されずにbad1
にジャンプし、エラーとして扱われるか、他の変換パスが試されます。これにより、不適切な文字列変換が防止されます。
-
新しい
convlit
のラッパー関数:void convlit(Node *n, Type *t)
という新しい関数が定義され、内部でconvlit1(n, t, 0);
を呼び出しています。これは、既存のコードベースでconvlit
を呼び出している箇所が、この変更によって意図せず数値から文字列への変換を行うことを防ぐための互換性レイヤーです。デフォルトではconv=0
となり、文字列変換は抑制されます。
src/cmd/gc/go.h
の変更
convlit1
関数のプロトタイプ宣言が追加されました。これにより、他のファイルからconvlit1
を呼び出すことができるようになります。
src/cmd/gc/walk.c
の変更
walk
関数内で、convlit(l, t);
の呼び出しがconvlit1(l, t, 1);
に変更されました。walk
関数はASTを走査し、型チェックやコード生成の準備を行います。この特定の呼び出し箇所は、おそらく明示的な型変換(例:var s string = string(10)
のようなコード)を処理するコンテキストに該当します。- ここで
conv
を1
に設定することで、convlit1
内の文字列変換ロジックが実行され、数値リテラルl
がターゲット型t
(この場合は文字列型)に正しく変換されるようになります。
これらの変更は、Goコンパイラの型変換ロジックをより細かく制御し、特定のバグ(bug104)を修正するとともに、将来的な型変換の挙動の予測可能性と堅牢性を向上させるものです。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のコンパイラに関するドキュメント(Goのソースコード内や関連する設計ドキュメントを参照)
参考にした情報源リンク
- Go言語のソースコード(特に
src/cmd/gc
ディレクトリ) - Go言語の型システムに関する公式ドキュメントや仕様
- Goコンパイラの内部構造に関する技術記事や解説(例: "Go Compiler Internals" など)
- Go言語のバグトラッカーやメーリングリストアーカイブ("bug104" の詳細な議論がある場合)
- Web検索結果: "Go compiler bug104" (ただし、具体的なバグの詳細は公開されていないことが多い)