Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15113] ファイルの概要

このコミットは、Go言語のコンパイラ (cmd/gc) における make 関数の引数チェックロジックを更新し、スライス、マップ、チャネル作成時の len および cap 引数に関する最新のルールを実装するものです。特に、負の値、非整数値、および lencap を超えるといった不正な引数に対するエラーハンドリングが強化されています。

コミット

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) のようなスライス作成において、lencap の値が負でないこと、整数であること、そして lencap を超えないことなどの基本的な制約に加え、システムが確保できるメモリサイズを超えないかどうかのチェックがより厳密に行われるようになりました。これにより、不正な引数に対するエラーが早期に、かつ適切なメッセージで報告されるようになります。

前提知識の解説

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関数が実際にメモリを割り当て、スライスを構築する際には、このランタイムのコードが呼び出されます。

mpintvlong

  • Mpint: Goコンパイラ内部で使用される多倍長整数型(Multiple Precision Integer)。コンパイル時に大きな定数値を扱うために使用されます。
  • vlong: long long 型のエイリアスで、64ビット整数を表します。

これらの型は、make関数の引数として与えられる lencap の値が非常に大きい場合や、定数式として評価される場合に、その値を正確に表現し、比較するために使用されます。

技術的詳細

このコミットの技術的な変更点は、主に src/cmd/gc/typecheck.csrc/pkg/runtime/slice.c の2つのファイルに集中しています。

src/cmd/gc/typecheck.c の変更

このファイルでは、make関数の引数チェックロジックが大幅に修正されています。

  1. 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 を返します。
  2. OMAKE ノードの処理の変更:

    • typecheck 関数内の case OMAKE: ブロックで、make関数の引数チェックが checkmake 関数を呼び出すように変更されました。
    • 以前は、isint を直接チェックしたり、defaultlit を早期に呼び出したりしていましたが、これらが checkmake に集約されました。
    • スライスの場合 (TARRAY または TSLICE)、lencap の両方がチェックされます。
    • 特に、lencap を超える場合 (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) の場合も、それぞれの引数(sizebuffer)に対して checkmake が呼び出されます。

src/pkg/runtime/slice.c の変更

このファイルでは、ランタイムにおけるスライス作成時の lencap の範囲チェックが修正されています。

  • runtime·makeslice 関数において、lencap の範囲チェックに 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) 関数は、多倍長整数 bvlong 型の整数 c を比較する関数です。
  • 以前は tmpmovecfix(&a, c); return mpcmpfixfix(&a, b); となっていましたが、tmpmovecfix(&c1, c); return mpcmpfixfix(b, &c1); に変更されました。
  • これは、比較の順序を bc1 に変更し、mpcmpfixfix の引数の順序と整合性を保つための修正です。機能的な変更というよりは、コードの整合性と正確性を高めるためのものです。

テストファイルの追加

  • test/fixedbugs/issue4085a.go: コンパイル時にエラーが検出されるべきケース(負の len/cap、非整数値、lencap を超える、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.ccheckmake 関数

この checkmake 関数は、make 関数の引数(len, cap, size, buffer)の妥当性を検証する中心的なロジックをカプセル化しています。

  1. 定数引数の処理:

    • 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 の引数としての妥当性チェックが優先され、より適切なエラーメッセージがユーザーに提示されます。
  2. 非定数引数の処理:

    • 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.cruntime·makeslice 関数

この関数は、実際にスライスが作成されるランタイムレベルの関数です。

  • if(len < 0 || (intgo)len != len || t->elem->size > 0 && len > MaxMem / t->elem->size):
    • len < 0: 長さが負でないことを確認します。
    • (intgo)len != len: lenintgo 型(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」というパニックを発生させます。

この変更により、lencap の両方でメモリ範囲チェックが行われるようになり、特に len が非常に大きい場合に len out of range が優先的に報告されるようになりました。これは、ユーザーが 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のテストフレームワークの慣習