[インデックス 1961] ファイルの概要
このコミットは、Goコンパイラにおける型チェックとmake
組み込み関数の挙動に関する2つの重要なバグ修正、およびテストファイル内の軽微なタイプミス修正を含んでいます。具体的には、固定長配列とスライスの間の不正な型変換、およびmake
関数が固定長配列に対して誤って適用される問題に対処しています。
コミット
commit 907cb4f1e604d5c21df32583470c33bec14fec5a
Author: Russ Cox <rsc@golang.org>
Date: Fri Apr 3 23:20:51 2009 -0700
fix both of anton's bugs:
* make([100]int) was being compiled to
make([]int), kind of.
* []this = [100]that was working for any this, that.
turned up a typo in pipe_test.go
R=ken
OCL=27081
CL=27081
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/907cb4f1e604d5c21df32583470c33bec14fec5a
元コミット内容
このコミットは、"anton"によって報告された2つのバグを修正することを目的としています。
make([100]int)
という形式の呼び出しが、あたかもmake([]int)
(スライスを作成する意図)であるかのようにコンパイルされてしまう問題。これは、make
関数が固定長配列の型引数を受け取った際に、コンパイラが誤った解釈をしていたことを示唆しています。Go言語において、make
はスライス、マップ、チャネルの初期化にのみ使用され、固定長配列の作成には使用されません。[]this = [100]that
のような代入(スライス型変数に固定長配列を代入する操作)が、this
とthat
の型に関わらず動作してしまう問題。これは、スライスと配列間の代入における型互換性チェックが不十分であったことを示しています。Goでは、スライスと配列は異なる型であり、直接的な代入には厳密な型互換性が必要です。
さらに、この修正作業中にpipe_test.go
ファイル内でタイプミスが発見され、それも同時に修正されています。
変更の背景
Go言語の初期段階において、コンパイラはまだ成熟しておらず、型システムや組み込み関数のセマンティクスに関するエッジケースや誤解釈が存在していました。このコミットは、Goの型安全性と予測可能な挙動を保証するために、特にスライスと配列の扱いにおけるコンパイラの厳密性を向上させる必要があったことを示しています。
具体的には、以下の問題が背景にありました。
make
関数の誤用:make
はスライス、マップ、チャネルといった参照型のデータ構造を初期化するために設計されています。固定長配列は値型であり、make
で初期化する概念がありません。しかし、コンパイラがmake([100]int)
のような不正なコードをエラーとして検出せず、誤ってスライスとして扱ってしまうバグが存在しました。これにより、開発者は意図しない挙動に遭遇したり、実行時エラーを引き起こしたりする可能性がありました。- スライスと配列の代入における型不整合: Goでは、スライスは動的なビューであり、配列は固定長のデータ構造です。これらは異なる型であり、代入には厳密な型互換性が求められます。例えば、
[]int
型のスライス変数に[100]int
型の配列を代入する場合、要素の型が一致している必要があります。このバグは、要素の型が一致しない場合でも代入が許されてしまうという、型安全性を損なう問題でした。
これらのバグは、Goプログラムの信頼性と堅牢性を低下させる可能性があったため、早期に修正する必要がありました。
前提知識の解説
Go言語における配列とスライス
-
配列 (Array):
- Goの配列は、同じ型の要素を固定数だけ格納する値型です。
- 宣言時にサイズが指定され、そのサイズは型の不可欠な一部となります。例:
[5]int
と[10]int
は異なる型です。 - 配列は値渡しされます。つまり、関数に配列を渡すと、そのコピーが作成されます。
var a [5]int
のように宣言します。
-
スライス (Slice):
- Goのスライスは、配列のセグメント(部分)への動的なビューです。
- スライスは参照型であり、基になる配列を参照します。
- サイズは宣言時には指定されず、実行時に動的に変更できます(ただし、基になる配列の容量の範囲内)。
[]int
のように宣言します。make
関数を使って作成・初期化されます。例:s := make([]int, 5, 10)
(長さ5、容量10のスライスを作成)。- スライスは、配列とは異なり、
append
関数などで要素を追加できます。
make
組み込み関数
make
はGoの組み込み関数で、スライス、マップ、チャネルといった参照型のデータ構造を初期化するために使用されます。これらの型は、使用する前にメモリを割り当てる必要があるため、make
がその役割を担います。
- スライス:
make([]Type, length, capacity)
- マップ:
make(map[KeyType]ValueType)
- チャネル:
make(chan Type, bufferCapacity)
make
は、固定長配列の作成には使用されません。固定長配列は、宣言時にサイズが決定され、メモリが割り当てられます。
型互換性 (Type Compatibility)
Go言語は静的型付け言語であり、厳密な型チェックを行います。代入や関数呼び出しの際に、値の型が期待される型と互換性があるかどうかが検証されます。
- 配列とスライスの代入: 配列をスライスに直接代入することはできません。ただし、配列のポインタからスライスを作成したり、配列全体をスライスとして扱うことは可能です。このコミットのバグは、この型互換性チェックが不十分であったことを示しています。
技術的詳細
このコミットは、主にGoコンパイラのsrc/cmd/gc/walk.c
ファイルと、テストファイルsrc/lib/io/pipe_test.go
に変更を加えています。
src/cmd/gc/walk.c
の変更
walk.c
はGoコンパイラのバックエンドの一部であり、抽象構文木 (AST) を走査し、型チェック、最適化、コード生成の準備を行う役割を担っています。
-
ascompat
関数の修正:ascompat
関数は、型変換や代入における型の互換性をチェックするために使用されます。変更前:
if(isslice(dst) && isfixedarray(src)) return 1;
変更後:
if(isslice(dst) && isfixedarray(src) && eqtype(dst->type, src->type, 0)) return 1;
この変更は、スライス型
dst
に固定長配列型src
を代入する際の互換性チェックを強化しています。以前は、dst
がスライスでsrc
が固定長配列であれば、要素の型が異なっていても互換性があると見なされていました。修正後は、eqtype(dst->type, src->type, 0)
が追加され、スライスの要素型と配列の要素型が等しい場合にのみ互換性があると判断されるようになりました。これにより、[]int = [100]float64
のような不正な代入がコンパイル時にエラーとして検出されるようになります。 -
makecompat
関数の修正:makecompat
関数は、make
組み込み関数の引数の型をチェックし、適切な処理を行う役割を担っています。変更前:
case TARRAY: return arrayop(n, Erv); // ... yyerror("cannot make(%T)", t);
変更後:
case TARRAY: if(!isslice(t)) goto bad; return arrayop(n, Erv); // ... bad: if(!n->diag) { n->diag = 1; yyerror("cannot make(%T)", t); } return n;
この修正は、
make
関数の引数としてTARRAY
(配列型)が渡された場合の挙動を変更しています。 以前は、TARRAY
の場合に無条件でarrayop
を呼び出していました。これは、make([100]int)
のような固定長配列の型が渡された場合でも、コンパイラがそれをスライスのように扱おうとしていたことを示唆しています。 修正後は、if(!isslice(t)) goto bad;
というチェックが追加されました。これは、make
の引数として渡された配列型t
がスライスではない場合(つまり、固定長配列の場合)には、bad
ラベルにジャンプし、yyerror("cannot make(%T)", t)
というエラーメッセージを出力するようにしています。これにより、make([100]int)
のような不正なmake
の呼び出しがコンパイル時に明確なエラーとして報告されるようになり、最初のバグが修正されました。
src/lib/io/pipe_test.go
の変更
このファイルは、Goのio
パッケージにおけるパイプのテストコードです。
変更前:
var buf [64]int;
変更後:
var buf [64]byte;
この変更は、testPipeReadClose
関数内のbuf
変数の型を[64]int
から[64]byte
に修正しています。io.Reader
インターフェースのRead
メソッドは、バイトスライス([]byte
)を引数として受け取ります。したがって、[64]int
型のバッファを使用することは論理的に誤りであり、[64]byte
が正しい型です。これは、コミットメッセージで言及されている「pipe_test.goのタイプミス」に該当します。
コアとなるコードの変更箇所
src/cmd/gc/walk.c
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1955,7 +1955,7 @@ ascompat(Type *dst, Type *src)
if(eqtype(dst, src, 0))
return 1;
- if(isslice(dst) && isfixedarray(src))
+ if(isslice(dst) && isfixedarray(src) && eqtype(dst->type, src->type, 0))
return 1;
if(isnilinter(dst) || isnilinter(src))
@@ -2080,6 +2080,8 @@ makecompat(Node *n)
if(t != T)
switch(t->etype) {
case TARRAY:
+ if(!isslice(t))
+ goto bad;
return arrayop(n, Erv);
case TMAP:
return mapop(n, Erv);
@@ -2087,15 +2089,11 @@ makecompat(Node *n)
return chanop(n, Erv);
}
- /*
- * ken had code to malloc here,
- * but rsc cut it out so that make(int)
- * is diagnosed as an error (probably meant new).
- * might come back once we know the
- * language semantics for make(int).
- */
-
- yyerror("cannot make(%T)", t);
+bad:
+ if(!n->diag) {
+ n->diag = 1;
+ yyerror("cannot make(%T)", t);
+ }
return n;
}
src/lib/io/pipe_test.go
--- a/src/lib/io/pipe_test.go
+++ b/src/lib/io/pipe_test.go
@@ -148,7 +148,7 @@ func testPipeReadClose(t *testing.T, async bool) {
} else {
delayClose(t, w, c);
}
- var buf [64]int;
+ var buf [64]byte;
n, err := r.Read(buf);
<-c;
if err != nil {
コアとなるコードの解説
src/cmd/gc/walk.c
における修正
-
ascompat
関数の変更:if(isslice(dst) && isfixedarray(src) && eqtype(dst->type, src->type, 0))
この変更は、スライスと固定長配列間の代入互換性チェックに、要素の型が一致するという条件を追加しました。これにより、[]int
型のスライス変数に[100]float64
のような異なる要素型を持つ固定長配列を代入しようとした場合に、コンパイルエラーが発生するようになり、Goの型安全性が向上しました。 -
makecompat
関数の変更:if(!isslice(t)) goto bad;
make
関数に配列型が渡された際に、それがスライス型でない(つまり固定長配列である)場合に、明示的にbad
ラベルにジャンプしてエラーを報告するようにしました。これにより、make([100]int)
のような不正なmake
の呼び出しがコンパイル時に「cannot make([100]int)」といったエラーメッセージとして表示されるようになり、開発者が意図しないmake
の使用を防ぐことができます。これは、make
がスライス、マップ、チャネル専用の関数であるというGo言語のセマンティクスを厳密に適用するための重要な修正です。
src/lib/io/pipe_test.go
における修正
var buf [64]byte;
この修正は、io.Reader
のRead
メソッドがバイトスライスを期待するというGoの標準的なI/O操作の慣習に合わせるためのものです。[64]int
から[64]byte
への変更は、テストコードの正確性を保証し、潜在的な実行時エラーや誤ったテスト結果を防ぎます。これは、GoのI/O操作の基本的な理解と、それに伴う型選択の重要性を示しています。
関連リンク
特になし。
参考にした情報源リンク
- Go言語の公式ドキュメント (Go言語の配列、スライス、
make
関数に関する一般的な知識) - このコミットのGitHubページ: https://github.com/golang/go/commit/907cb4f1e604d5c21df32583470c33bec14fec5a
- Go言語のソースコード (
src/cmd/gc/walk.c
およびsrc/lib/io/pipe_test.go
の変更点) - Go言語仕様 (型互換性に関する一般的な規則) I have generated the detailed explanation in Markdown format, following all the specified sections and requirements. The output is in Japanese and covers the background, prerequisite knowledge, technical details, and core code changes. I have ensured that the output is only to standard output.