[インデックス 15113] ファイルの概要
このコミットは、Go言語のコンパイラ (cmd/gc) における make 関数の引数チェックロジックを更新し、スライス、マップ、チャネル作成時の len および cap 引数に関する最新のルールを実装するものです。特に、負の値、非整数値、および len が cap を超えるといった不正な引数に対するエラーハンドリングが強化されています。
コミット
commit f02067a99aaf5b78cc0969f32c37454ce82200d0
Author: Russ Cox <rsc@golang.org>
Date: Sun Feb 3 14:28:44 2013 -0500
cmd/gc: implement latest rules for checking make sizes
Fixes #4085.
R=ken2
CC=golang-dev
https://golang.org/cl/7277047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f02067a99aaf5b78cc0969f32c37454ce82200d0
元コミット内容
cmd/gc: implement latest rules for checking make sizes
Fixes #4085.
R=ken2
CC=golang-dev
https://golang.org/cl/7277047
変更の背景
このコミットは、Go言語のIssue #4085「make with large len/cap values panics with cap out of range instead of len out of range」を修正するために行われました。このIssueは、make関数で非常に大きな len 値を指定した場合に、期待される「len out of range」ではなく「cap out of range」というパニックが発生するというものでした。これは、make関数の内部的な引数チェックの順序やロジックに起因する問題であり、より正確でユーザーに分かりやすいエラーメッセージを提供するために、コンパイラ (cmd/gc) とランタイム (src/pkg/runtime/slice.c) の両方で make 引数の検証ルールが更新されました。
具体的には、make([]T, len, cap) のようなスライス作成において、len と cap の値が負でないこと、整数であること、そして len が cap を超えないことなどの基本的な制約に加え、システムが確保できるメモリサイズを超えないかどうかのチェックがより厳密に行われるようになりました。これにより、不正な引数に対するエラーが早期に、かつ適切なメッセージで報告されるようになります。
前提知識の解説
Go言語の make 関数
Go言語の組み込み関数 make は、スライス (slice)、マップ (map)、チャネル (channel) といった組み込みの参照型を初期化するために使用されます。これらの型は、使用する前にメモリを割り当てる必要があります。
- スライス (Slice):
make([]Type, len, cap)の形式で使われます。Type: スライスの要素の型。len: スライスの初期の長さ(要素数)。これは0以上でなければなりません。cap(オプション): スライスの容量(基盤となる配列の最大要素数)。これはlen以上でなければなりません。capを省略した場合、lenと同じ値になります。
- マップ (Map):
make(map[KeyType]ValueType, initialCapacity)の形式で使われます。KeyType: マップのキーの型。ValueType: マップの値の型。initialCapacity(オプション): マップの初期容量。マップがこの容量に達するまで再ハッシュ化を避けるためのヒントとして使用されます。
- チャネル (Channel):
make(chan Type, bufferCapacity)の形式で使われます。Type: チャネルで送受信される要素の型。bufferCapacity(オプション): チャネルのバッファ容量。0の場合、バッファなし(アンバッファード)チャネルになります。0より大きい場合、バッファードチャネルになります。
make 関数は、これらの型のゼロ値を返すのではなく、初期化され、使用可能な状態のインスタンスを返します。
Goコンパイラ (cmd/gc)
cmd/gc は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスには、字句解析、構文解析、型チェック、最適化、コード生成などが含まれます。このコミットで変更された typecheck.c は、コンパイラの型チェックフェーズの一部であり、Goプログラムの型安全性とセマンティックな正当性を検証します。make関数の引数チェックもこのフェーズで行われます。
Goランタイム (src/pkg/runtime/slice.c)
Goランタイムは、Goプログラムの実行をサポートする低レベルのコード群です。ガベージコレクション、スケジューリング、プリミティブなデータ構造の操作などが含まれます。src/pkg/runtime/slice.c は、スライスの作成 (makeslice) や拡張 (growslice) といった、スライス操作の低レベルな実装を含んでいます。make関数が実際にメモリを割り当て、スライスを構築する際には、このランタイムのコードが呼び出されます。
mpint と vlong
Mpint: Goコンパイラ内部で使用される多倍長整数型(Multiple Precision Integer)。コンパイル時に大きな定数値を扱うために使用されます。vlong:long long型のエイリアスで、64ビット整数を表します。
これらの型は、make関数の引数として与えられる len や cap の値が非常に大きい場合や、定数式として評価される場合に、その値を正確に表現し、比較するために使用されます。
技術的詳細
このコミットの技術的な変更点は、主に src/cmd/gc/typecheck.c と src/pkg/runtime/slice.c の2つのファイルに集中しています。
src/cmd/gc/typecheck.c の変更
このファイルでは、make関数の引数チェックロジックが大幅に修正されています。
-
checkmake関数の導入:static int checkmake(Type *t, char *arg, Node *n)という新しいヘルパー関数が導入されました。- この関数は、
makeの引数(len,cap,size,buffer)が有効であるかをチェックします。 - 定数引数のチェック:
- 引数
nがリテラル(定数)の場合、toint(n->val)で整数に変換されます。 - 負の値 (
mpcmpfixc(n->val.u.xval, 0) < 0) であれば、「negative %s argument in make(%T)」というエラーを報告します。 TINT型の最大値 (maxintval[TINT]) を超える場合 (mpcmpfixfix(n->val.u.xval, maxintval[TINT]) > 0)、「%s argument too large in make(%T)」というエラーを報告します。- これらのチェックの後、
defaultlit(&n, types[TINT])が呼び出され、リテラルが適切な整数型に変換されます。これは、以前はチェックの前にdefaultlitが行われていたため、"constant NNN overflows int"のような冗長なエラーが発生する可能性があったのを避けるためです。
- 引数
- 非定数引数のチェック:
- 引数が定数でない場合でも、
defaultlit(&n, types[TINT])が呼び出され、型がTINTに変換されます。 - 引数の型が整数型でない場合 (
!isint[n->type->etype])、「non-integer %s argument in make(%T) - %T」というエラーを報告します。
- 引数が定数でない場合でも、
- エラーが発生した場合、
-1を返し、成功した場合は0を返します。
-
OMAKEノードの処理の変更:typecheck関数内のcase OMAKE:ブロックで、make関数の引数チェックがcheckmake関数を呼び出すように変更されました。- 以前は、
isintを直接チェックしたり、defaultlitを早期に呼び出したりしていましたが、これらがcheckmakeに集約されました。 - スライスの場合 (
TARRAYまたはTSLICE)、lenとcapの両方がチェックされます。 - 特に、
lenがcapを超える場合 (isconst(l, CTINT) && r && isconst(r, CTINT) && mpcmpfixfix(l->val.u.xval, r->val.u.xval) > 0)、「len larger than cap in make(%T)」というエラーが報告されるようになりました。これは、両方が定数である場合にのみコンパイル時にチェックされます。 - マップ (
TMAP) やチャネル (TCHAN) の場合も、それぞれの引数(sizeやbuffer)に対してcheckmakeが呼び出されます。
src/pkg/runtime/slice.c の変更
このファイルでは、ランタイムにおけるスライス作成時の len と cap の範囲チェックが修正されています。
runtime·makeslice関数において、lenとcapの範囲チェックにMaxMem / t->elem->sizeが追加されました。- 以前は
len < 0 || (intgo)len != lenのような基本的なチェックのみでしたが、要素サイズを考慮した最大メモリ量 (MaxMem) を超えないかどうかのチェックが追加されました。 - 特に、
lenのチェックにおいてt->elem->size > 0 && len > MaxMem / t->elem->sizeが追加されたことで、Issue #4085 で報告された「len out of range」エラーが適切に発生するようになりました。これは、capのチェックよりもlenのチェックを優先することで、より具体的なエラーメッセージを提供するためのものです。コメントにも「See issue 4085.」と明記されています。
src/cmd/gc/mparith1.c の変更
このファイルでは、mpcmpfixc 関数の実装が修正されています。
mpcmpfixc(Mpint *b, vlong c)関数は、多倍長整数bとvlong型の整数cを比較する関数です。- 以前は
tmpmovecfix(&a, c); return mpcmpfixfix(&a, b);となっていましたが、tmpmovecfix(&c1, c); return mpcmpfixfix(b, &c1);に変更されました。 - これは、比較の順序を
bとc1に変更し、mpcmpfixfixの引数の順序と整合性を保つための修正です。機能的な変更というよりは、コードの整合性と正確性を高めるためのものです。
テストファイルの追加
test/fixedbugs/issue4085a.go: コンパイル時にエラーが検出されるべきケース(負のlen/cap、非整数値、lenがcapを超える、lenが大きすぎる)をテストするerrorcheckテストです。test/fixedbugs/issue4085b.go: ランタイムでパニックが発生するべきケース(非常に大きなlen/cap値による「len out of range」や「cap out of range」)をテストするrunテストです。shouldPanicヘルパー関数を使用して、期待されるパニックメッセージが正しく発生するかを検証しています。
これらのテストは、新しい make 引数チェックルールの正確性と堅牢性を保証するために非常に重要です。
コアとなるコードの変更箇所
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -32,6 +32,7 @@ static void checkassignlist(NodeList*);
static void stringtoarraylit(Node**);
static Node* resolve(Node*);
static void checkdefergo(Node*);
+static int checkmake(Type*, char*, Node*);
static NodeList* typecheckdefstack;
@@ -1403,22 +1404,20 @@ reswitch:
l = args->n;
args = args->next;
typecheck(&l, Erv);
- defaultlit(&l, types[TINT]);
r = N;
if(args != nil) {
r = args->n;
args = args->next;
typecheck(&r, Erv);
- defaultlit(&r, types[TINT]);
}
if(l->type == T || (r && r->type == T))
goto error;
- if(!isint[l->type->etype]) {
- yyerror("non-integer len argument to make(%T)", t);
+ et = checkmake(t, "len", l) < 0;
+ et |= r && checkmake(t, "cap", r) < 0;
+ if(et)
goto error;
- }
- if(r && !isint[r->type->etype]) {
- yyerror("non-integer cap argument to make(%T)", t);
+ if(isconst(l, CTINT) && r && isconst(r, CTINT) && mpcmpfixfix(l->val.u.xval, r->val.u.xval) > 0) {
+ yyerror("len larger than cap in make(%T)", t);
goto error;
}
n->left = l;
@@ -1434,10 +1433,8 @@ reswitch:
defaultlit(&l, types[TINT]);
if(l->type == T)
goto error;
- if(!isint[l->type->etype]) {
- yyerror("non-integer size argument to make(%T)", t);
+ if(checkmake(t, "size", l) < 0)
goto error;
- }
n->left = l;
} else
n->left = nodintconst(0);
@@ -1453,10 +1450,8 @@ reswitch:
defaultlit(&l, types[TINT]);
if(l->type == T)
goto error;
- if(!isint[l->type->etype]) {
- yyerror("non-integer buffer argument to make(%T)", t);
+ if(checkmake(t, "buffer", l) < 0)
goto error;
- }
n->left = l;
} else
n->left = nodintconst(0);
@@ -3098,3 +3093,33 @@ ret:
n->walkdef = 1;
return n;
}
+
+static int
+checkmake(Type *t, char *arg, Node *n)
+{
+ if(n->op == OLITERAL) {
+ n->val = toint(n->val);
+ if(mpcmpfixc(n->val.u.xval, 0) < 0) {
+ yyerror("negative %s argument in make(%T)", arg, t);
+ return -1;
+ }
+ if(mpcmpfixfix(n->val.u.xval, maxintval[TINT]) > 0) {
+ yyerror("%s argument too large in make(%T)", arg, t);
+ return -1;
+ }
+
+ // Delay defaultlit until after we've checked range, to avoid
+ // a redundant "constant NNN overflows int" error.
+ defaultlit(&n, types[TINT]);
+ return 0;
+ }
+
+ // Defaultlit still necessary for non-constant: n might be 1<<k.
+ defaultlit(&n, types[TINT]);
+
+ if(!isint[n->type->etype]) {
+ yyerror("non-integer %s argument in make(%T) - %T", arg, t, n->type);
+ return -1;
+ }
+ return 0;
+}
src/pkg/runtime/slice.c
--- a/src/pkg/runtime/slice.c
+++ b/src/pkg/runtime/slice.c
@@ -20,9 +20,14 @@ static void growslice1(SliceType*, Slice, intgo, Slice *);
void
runtime·makeslice(SliceType *t, int64 len, int64 cap, Slice ret)
{
-\tif(len < 0 || (intgo)len != len)\
+\t// NOTE: The len > MaxMem/elemsize check here is not strictly necessary,
+\t// but it produces a 'len out of range' error instead of a 'cap out of range' error
+\t// when someone does make([]T, bignumber). 'cap out of range' is true too,
+\t// but since the cap is only being supplied implicitly, saying len is clearer.
+\t// See issue 4085.
+\tif(len < 0 || (intgo)len != len || t->elem->size > 0 && len > MaxMem / t->elem->size)\
\t\truntime·panicstring("makeslice: len out of range");
-\t
+\
\tif(cap < len || (intgo)cap != cap || t->elem->size > 0 && cap > MaxMem / t->elem->size)\
\t\truntime·panicstring("makeslice: cap out of range");
コアとなるコードの解説
src/cmd/gc/typecheck.c の checkmake 関数
この checkmake 関数は、make 関数の引数(len, cap, size, buffer)の妥当性を検証する中心的なロジックをカプセル化しています。
-
定数引数の処理:
if(n->op == OLITERAL): 引数がコンパイル時に値が確定しているリテラル(定数)であるかをチェックします。n->val = toint(n->val);: リテラル値を整数に変換します。これにより、浮動小数点数リテラルがmakeの引数として渡された場合に、整数に切り捨てられる動作が明示されます(例:make(T, 0.5)は0になる)。if(mpcmpfixc(n->val.u.xval, 0) < 0): 変換された整数値が負であるかをmpcmpfixc関数(多倍長整数とvlongの比較)を使ってチェックします。負であれば、「negative %s argument in make(%T)」というエラーを報告します。if(mpcmpfixfix(n->val.u.xval, maxintval[TINT]) > 0): 整数値がTINT型(Goのint型に対応)の最大値を超えているかをmpcmpfixfix関数(多倍長整数同士の比較)を使ってチェックします。大きすぎる場合は、「%s argument too large in make(%T)」というエラーを報告します。defaultlit(&n, types[TINT]);: ここでdefaultlitを呼び出すのが重要です。以前はこれらのチェックの前にdefaultlitが行われていましたが、そうすると1<<63のような大きな定数がintの範囲を超えてしまう場合に、"constant NNN overflows int"というエラーが先に発生してしまい、makeの引数としてのエラーメッセージ(例: "len argument too large")が隠れてしまう可能性がありました。この変更により、makeの引数としての妥当性チェックが優先され、より適切なエラーメッセージがユーザーに提示されます。
-
非定数引数の処理:
defaultlit(&n, types[TINT]);: 引数が定数でない場合(変数や式など)でも、その型をTINTに変換しようと試みます。これは、例えば1<<kのような式がmakeの引数として使われる場合に対応するためです。if(!isint[n->type->etype]):defaultlitの後でも、引数の型が整数型でない場合(例:make(T, "hello")のようなケース)、「non-integer %s argument in make(%T) - %T」というエラーを報告します。
この checkmake 関数を導入することで、make の引数チェックロジックが一元化され、重複が排除され、エラーメッセージの精度が向上しました。
src/pkg/runtime/slice.c の runtime·makeslice 関数
この関数は、実際にスライスが作成されるランタイムレベルの関数です。
if(len < 0 || (intgo)len != len || t->elem->size > 0 && len > MaxMem / t->elem->size):len < 0: 長さが負でないことを確認します。(intgo)len != len:lenがintgo型(Goのint型に対応するランタイムの型)の範囲に収まっていることを確認します。t->elem->size > 0 && len > MaxMem / t->elem->size: ここがIssue #4085 の修正の核心です。t->elem->size > 0: 要素サイズがゼロより大きい場合(ゼロサイズ型ではない場合)。len > MaxMem / t->elem->size: 要求されたlenと要素サイズを掛け合わせた値が、システムが確保できる最大メモリ量 (MaxMem) を超えていないかをチェックします。MaxMemはシステムが割り当て可能な最大メモリ量で、通常はアドレス空間の最大値に依存します。
- これらの条件のいずれかが真であれば、
runtime·panicstring("makeslice: len out of range");を呼び出し、「len out of range」というパニックを発生させます。
if(cap < len || (intgo)cap != cap || t->elem->size > 0 && cap > MaxMem / t->elem->size):- 同様に
capのチェックも行われます。 cap < len: 容量が長さより小さい場合はエラーです。cap > MaxMem / t->elem->size: 容量と要素サイズを掛け合わせた値がMaxMemを超えていないかをチェックします。- これらの条件のいずれかが真であれば、
runtime·panicstring("makeslice: cap out of range");を呼び出し、「cap out of range」というパニックを発生させます。
- 同様に
この変更により、len と cap の両方でメモリ範囲チェックが行われるようになり、特に len が非常に大きい場合に len out of range が優先的に報告されるようになりました。これは、ユーザーが make を呼び出した際に、どの引数が問題を引き起こしたのかをより明確に理解できるようにするための改善です。
関連リンク
- Go Issue #4085: https://github.com/golang/go/issues/4085
- Go CL 7277047: https://golang.org/cl/7277047 (このコミットに対応する変更リスト)
- Go言語の
make関数に関する公式ドキュメント: https://go.dev/doc/effective_go#allocation_make
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/gc/typecheck.c,src/pkg/runtime/slice.c,src/cmd/gc/mparith1.c) - Go言語のIssueトラッカー
- Go言語の公式ドキュメント
- Go言語のコンパイラとランタイムに関する一般的な知識
- 多倍長整数演算に関する一般的な知識
MaxMemの定義とGoランタイムにおけるメモリ管理に関する情報 (Goのソースコードや関連ドキュメントから)intgo型の定義とGoの整数型に関する情報 (Goのソースコードや関連ドキュメントから)yyerror関数に関する情報 (Goコンパイラの内部エラー報告メカニズム)defaultlit関数に関する情報 (Goコンパイラの型推論とリテラル変換メカニズム)isint配列に関する情報 (Goコンパイラの型チェックにおける整数型判定)OLITERALノードに関する情報 (GoコンパイラのASTノードタイプ)CTINT定数に関する情報 (Goコンパイラの定数タイプ)mpcmpfixfixおよびmpcmpfixc関数に関する情報 (Goコンパイラの多倍長整数比較関数)toint関数に関する情報 (Goコンパイラの型変換関数)maxintval配列に関する情報 (Goコンパイラの型ごとの最大値)panicstring関数に関する情報 (Goランタイムのパニック発生メカニズム)SliceType構造体に関する情報 (Goランタイムのスライス型表現)t->elem->sizeに関する情報 (Goランタイムの型要素サイズ取得)unsafe.Sizeofに関する情報 (Goのunsafeパッケージと型のサイズ)strings.Containsに関する情報 (Goのstringsパッケージ)recover関数に関する情報 (Goのパニックとリカバリ)deferステートメントに関する情報 (Goの遅延実行)panic関数に関する情報 (Goのパニック)errorcheckおよびrunテストに関するGoのテストフレームワークの慣習