[インデックス 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のテストフレームワークの慣習