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

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

コミット

commit 767845b6fae6eb4cb6253f1ea87fbb62bc2134a4
Author: Ken Thompson <ken@golang.org>
Date:   Wed Mar 11 17:37:04 2009 -0700

    bug 125
    
    R=r
    OCL=26146
    CL=26146

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/767845b6fae6eb4cb6253f1ea87fbb62bc2134a4

元コミット内容

このコミットは、Go言語の組み込み関数 make の引数に関するバグ(バグ125)を修正するものです。具体的には、make を用いて mapchanarray(スライス)を作成する際に、引数の数が多すぎる場合にコンパイルエラーを発生させるように変更が加えられています。

変更の概要は以下の通りです。

  • src/cmd/gc/walk.c ファイルが修正され、make 関数の処理ロジックが更新されました。
  • mapmake において、引数が1つより多い場合にエラー (yyerror("too many arguments to make map")) を出すようになりました。
  • chanmake において、引数が1つより多い場合にエラー (yyerror("too many arguments to make chan")) を出すようになりました。
  • array (スライス) の make において、引数が2つより多い場合にエラー (yyerror("too many arguments to make array")) を出すようになりました。
  • テストファイル test/bugs/bug122.gotest/fixedbugs/bug122.go にリネームされ、test/golden.out も更新されています。これは、修正されたバグに関連するテストが fixedbugs ディレクトリに移動されたことを示しています。

変更の背景

このコミットの背景には、Go言語の make 関数が、mapchannelslice の初期化に使用される際の引数の取り扱いに関するバグが存在していました。Go言語の make 関数は、これらの組み込み型を初期化するために設計されており、それぞれ特定の数の引数を期待します。

  • make(map[K]V, initialCapacity): マップの場合、型とオプションの初期容量の最大2つの引数を取ります。
  • make(chan T, capacity): チャネルの場合、型とオプションのバッファ容量の最大2つの引数を取ります。
  • make([]T, length, capacity): スライスの場合、型、長さ、オプションの容量の最大3つの引数を取ります。

しかし、このコミット以前のコンパイラでは、make 関数に期待される数よりも多くの引数が渡された場合に、適切なコンパイルエラーを発生させず、未定義の動作や予期せぬ結果を引き起こす可能性がありました。

bug 125 は、まさにこの問題、つまり make 関数への不正な引数渡しがコンパイル時に適切に検出されないというバグを指しています。この修正は、コンパイラが make 関数の引数チェックを厳格化し、開発者が誤った使い方をした場合に早期にエラーを通知することで、コードの堅牢性と開発体験を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念とコンパイラの構造に関する知識が必要です。

1. Go言語の make 関数

make はGo言語の組み込み関数で、スライス、マップ、チャネルといった参照型を初期化するために使用されます。これらの型は、使用する前にメモリを割り当てて初期化する必要があります。new 関数がゼロ値のメモリを割り当てるのに対し、make は型に応じた初期化を行い、すぐに使える状態にします。

  • スライス ([]T): make([]T, length, capacity)
    • length: スライスの初期の長さ。
    • capacity: スライスの基盤となる配列の容量。lengthcapacity 以下である必要があります。capacity は省略可能です。
  • マップ (map[K]V): make(map[K]V, initialCapacity)
    • initialCapacity: マップの初期容量。マップがこの容量に達すると、自動的にサイズが変更されます。省略可能です。
  • チャネル (chan T): make(chan T, capacity)
    • capacity: チャネルのバッファ容量。0の場合はバッファなし(非同期チャネル)、正の数の場合はバッファあり(同期チャネル)です。省略可能です。

make 関数は、これらの型に対してメモリを割り当て、適切な内部構造を初期化する役割を担っています。

2. Goコンパイラの構造と src/cmd/gc/walk.c

Goコンパイラは複数のステージで構成されており、src/cmd/gc はGo言語のフロントエンドとバックエンドの一部を担う主要なコンポーネントです。

  • gc (Go Compiler): Go言語のソースコードを機械語に変換する主要なコンパイラです。
  • walk.c: gc の内部にあるファイルの一つで、抽象構文木 (AST) の「ウォーク」(走査)と変換を行う部分です。このステージでは、ASTが最適化され、より低レベルの中間表現に変換されます。具体的には、組み込み関数の呼び出し(make など)が、ランタイムライブラリの対応する関数呼び出しに変換される処理などがここで行われます。
    • Node *n: ASTのノードを表す構造体。n->op はノードの操作(オペレーション)の種類を示します。
    • OMAKE: make 関数の呼び出しを表すオペレーションコード。
    • listcount(n->left): make 関数の引数の数を数えるための関数。n->leftmake 関数の引数リストを表します。
    • yyerror(...): コンパイルエラーを報告するための関数。

このファイルは、Goのソースコードがコンパイルされる過程で、型チェックやセマンティック分析の後、最終的なコード生成の前に、ASTをさらに処理する重要な役割を担っています。make 関数の引数チェックは、この walk ステージで行われるセマンティック分析の一部として実装されています。

3. test/golden.out とテストフレームワーク

Goプロジェクトでは、コンパイラの出力やツールの動作を検証するために、golden ファイルと呼ばれる参照出力ファイルを使用することがあります。

  • test/golden.out: これは、Goコンパイラやその他のツールの標準出力やエラー出力の期待される内容を記録したファイルです。テスト実行時に、実際の出力がこの golden ファイルの内容と一致するかどうかを比較することで、コンパイラの動作が期待通りであることを検証します。
  • test/bugstest/fixedbugs: Goのテストスイートでは、バグを再現するテストケースを test/bugs ディレクトリに配置し、そのバグが修正された後に test/fixedbugs ディレクトリに移動するという慣習があります。これにより、修正されたバグが将来的に再発しないことを保証します。

これらの知識は、コミットがGoコンパイラのどの部分に影響を与え、どのようにテストされているかを理解する上で不可欠です。

技術的詳細

このコミットの技術的詳細は、Goコンパイラの src/cmd/gc/walk.c ファイルにおける make 関数の処理ロジックの変更に集約されます。

walk.c 内には、mapopchanoparrayop といった関数が存在し、それぞれ mapchannelarray(スライス)の make 呼び出しを処理します。これらの関数内で、OMAKE オペレーション(make 関数呼び出し)が検出された際に、引数の数をチェックするロジックが追加されました。

変更点の内訳

  1. mapop 関数 (OMAKE ケース):

    @@ -2220,6 +2220,10 @@ mapop(Node *n, int top)
     	fatal("mapop: unknown op %O", n->op);
    
     case OMAKE:
    +	cl = listcount(n->left);
    +	if(cl > 1)
    +		yyerror("too many arguments to make map");
    +
     	if(top != Erv)
     		goto nottop;
    
    @@ -2232,8 +2236,9 @@ mapop(Node *n, int top)
     		break;
    
     	a = n->left;				// hint
    -	if(n->left == N)
    +	if(cl != 1)
     		a = nodintconst(0);
    +
     	r = a;
     	a = nodintconst(algtype(t->type));	// val algorithm
     	r = list(a, r);
    
    • cl = listcount(n->left);make 関数の引数の数を取得します。
    • if(cl > 1): もし引数の数が1より多ければ(つまり、型引数と容量引数以外に余計な引数があれば)、yyerror("too many arguments to make map") を呼び出してコンパイルエラーを発生させます。
    • 以前は if(n->left == N) で引数がない場合をチェックしていましたが、if(cl != 1) に変更され、引数が1つでない場合に nodintconst(0) を設定するようになりました。これは、容量引数が省略された場合のデフォルト値(0)の扱いをより正確にするための変更です。
  2. chanop 関数 (OMAKE ケース):

    @@ -2427,6 +2432,10 @@ chanop(Node *n, int top)
     	fatal("chanop: unknown op %O", n->op);
    
     case OMAKE:
    +	cl = listcount(n->left);
    +	if(cl > 1)
    +		yyerror("too many arguments to make chan");
    +
     	// newchan(elemsize int, elemalg int,
     	//	hint int) (hmap *chan[any-1]);
    
    @@ -2434,12 +2443,12 @@ chanop(Node *n, int top)
     	if(t == T)
     		break;
    
    -	if(n->left != N) {
    +	a = nodintconst(0);
    +	if(cl == 1) {
     		// async buf size
     		a = nod(OCONV, n->left, N);
     		a->type = types[TINT];
    -	} else
    -		a = nodintconst(0);
    +	}
     	r = a;
     	a = nodintconst(algtype(t->type));	// elem algorithm
     	r = list(a, r);
    
    • cl = listcount(n->left); で引数の数を取得します。
    • if(cl > 1): もし引数の数が1より多ければ、yyerror("too many arguments to make chan") を呼び出してコンパイルエラーを発生させます。
    • チャネルの容量引数の処理も変更され、a = nodintconst(0); でデフォルト値を設定し、if(cl == 1) の場合にのみ引数から容量を抽出するように修正されました。これにより、引数が多すぎる場合でも、容量の解釈が正しく行われるようになります。
  3. arrayop 関数 (OMAKE ケース):

    @@ -2602,6 +2611,7 @@ arrayop(Node *n, int top)
     	Type *t, *tl;
     	Node *on;
     	Iter save;
    +	int cl;
    
     	r = n;
     	switch(n->op) {
    @@ -2658,6 +2668,10 @@ arrayop(Node *n, int top)
     	return n;
    
     case OMAKE:
    +	cl = listcount(n->left);
    +	if(cl > 2)
    +		yyerror("too many arguments to make array");
    +
     	// newarray(nel int, max int, width int) (ary []any)
     	t = fixarray(n->type);
     	if(t == T)
    
    • int cl; が追加され、引数の数を格納するためのローカル変数が宣言されました。
    • cl = listcount(n->left); で引数の数を取得します。
    • if(cl > 2): もし引数の数が2より多ければ(つまり、型引数、長さ引数、容量引数以外に余計な引数があれば)、yyerror("too many arguments to make array") を呼び出してコンパイルエラーを発生させます。

これらの変更により、Goコンパイラは make 関数の引数の数を厳密にチェックし、不正な引数渡しをコンパイル時に捕捉できるようになりました。これは、Go言語の型安全性と堅牢性を高める上で重要な改善です。

コアとなるコードの変更箇所

変更は主に src/cmd/gc/walk.c ファイルに集中しています。

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -2220,6 +2220,10 @@ mapop(Node *n, int top)
 	fatal("mapop: unknown op %O", n->op);

 case OMAKE:
+	cl = listcount(n->left);
+	if(cl > 1)
+		yyerror("too many arguments to make map");
+
 	if(top != Erv)
 		goto nottop;

@@ -2232,8 +2236,9 @@ mapop(Node *n, int top)
 		break;

 	a = n->left;				// hint
-	if(n->left == N)
+	if(cl != 1)
 		a = nodintconst(0);
+
 	r = a;
 	a = nodintconst(algtype(t->type));	// val algorithm
 	r = list(a, r);
@@ -2427,6 +2432,10 @@ chanop(Node *n, int top)
 	fatal("chanop: unknown op %O", n->op);

 case OMAKE:
+	cl = listcount(n->left);
+	if(cl > 1)
+		yyerror("too many arguments to make chan");
+
 	// newchan(elemsize int, elemalg int,
 	//	hint int) (hmap *chan[any-1]);

@@ -2434,12 +2443,12 @@ chanop(Node *n, int top)
 	if(t == T)
 		break;

-	if(n->left != N) {
    +	a = nodintconst(0);
    +	if(cl == 1) {
 		// async buf size
 		a = nod(OCONV, n->left, N);
 		a->type = types[TINT];
-	} else
-		a = nodintconst(0);
+	}
 	r = a;
 	a = nodintconst(algtype(t->type));	// elem algorithm
 	r = list(a, r);
@@ -2602,6 +2611,7 @@ arrayop(Node *n, int top)
 	Type *t, *tl;
 	Node *on;
 	Iter save;
+	int cl;

 	r = n;
 	switch(n->op) {
@@ -2658,6 +2668,10 @@ arrayop(Node *n, int top)
 	return n;

 case OMAKE:
+	cl = listcount(n->left);
+	if(cl > 2)
+		yyerror("too many arguments to make array");
+
 	// newarray(nel int, max int, width int) (ary []any)
 	t = fixarray(n->type);
 	if(t == T)

また、テストファイルのリネームと golden.out の変更も行われています。

--- a/test/bugs/bug122.go
+++ b/test/fixedbugs/bug122.go
similarity index 100%
rename from test/bugs/bug122.go
rename to test/fixedbugs/bug122.go
--- a/test/golden.out
+++ b/test/golden.out
@@ -125,9 +125,6 @@ bugs/bug117.go:9: illegal types for operand: RETURN
 	int
 BUG: should compile

-=========== bugs/bug122.go
-BUG: compilation succeeds incorrectly
-
 =========== bugs/bug125.go
 BUG: errchk: command succeeded unexpectedly:  6g bugs/bug125.go

@@ -268,6 +265,9 @@ fixedbugs/bug121.go:20: illegal types for operand: AS
 	I
 	*S

+=========== fixedbugs/bug122.go
+fixedbugs/bug122.go:6: too many arguments to make array
+
 =========== fixedbugs/bug133.go
 fixedbugs/bug133.dir/bug2.go:11: undefined DOT i on bug0.T
 fixedbugs/bug133.dir/bug2.go:11: illegal types for operand: RETURN

コアとなるコードの解説

src/cmd/gc/walk.c 内の mapopchanoparrayop 関数は、Goコンパイラのセマンティック分析および中間コード生成フェーズの一部として機能します。これらの関数は、抽象構文木 (AST) を走査し、make 関数の呼び出し (OMAKE オペレーション) を検出すると、その引数を検証し、必要に応じてランタイム関数への呼び出しに変換します。

このコミットのコアとなる変更は、各 make 呼び出しの処理において、listcount(n->left) を使用して make に渡された引数の数を取得し、その数が期待される引数の数を超えている場合に yyerror を呼び出してコンパイルエラーを発生させる点です。

  • mapop (マップの make):

    • make(map[K]V) は最大2つの引数(型とオプションの容量)を取ります。
    • if(cl > 1): 引数が1つ(型のみ)または2つ(型と容量)が許容されるため、引数が2つ以上ある場合はエラーとします。listcount(n->left) は型引数を除いた、ユーザーが指定した引数の数を返します。したがって、cl > 1 は、容量引数以外に余計な引数があることを意味します。
    • if(cl != 1): 容量引数が指定されていない場合(cl が0の場合)や、容量引数のみが指定されている場合(cl が1の場合)に、容量を0として扱うためのロジックです。
  • chanop (チャネルの make):

    • make(chan T) は最大2つの引数(型とオプションの容量)を取ります。
    • if(cl > 1): マップと同様に、引数が1つより多い場合はエラーとします。
    • a = nodintconst(0); if(cl == 1) { ... }: チャネルの容量引数の処理をより明確にしました。デフォルトで容量を0とし、引数が1つ(容量が指定されている)場合にのみ、その引数を容量として使用します。
  • arrayop (スライスの make):

    • make([]T) は最大3つの引数(型、長さ、オプションの容量)を取ります。
    • if(cl > 2): 引数が2つ(長さと容量)より多い場合はエラーとします。listcount(n->left) は型引数を除いた、ユーザーが指定した引数の数を返します。したがって、cl > 2 は、長さ引数と容量引数以外に余計な引数があることを意味します。

これらの変更により、Goコンパイラは make 関数の引数に関する厳密なチェックを行うようになり、開発者がGo言語の仕様に準拠しない make の使い方をした場合に、コンパイル時に明確なエラーメッセージを返すようになりました。これにより、Goプログラムの信頼性とデバッグの容易さが向上します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/cmd/gc/walk.c の変更履歴)
  • Go言語のバグトラッカー (Go Issues)
  • Go言語のコンパイラに関する技術記事や解説

(注:具体的な bug 125 のIssueページへの直接リンクは、古いバグであるため、現在のGo Issuesトラッカーでは見つけにくい場合があります。しかし、コミットメッセージに明記されているため、このバグが存在し、このコミットによって修正されたことは確かです。)