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

[インデックス 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つのバグを修正することを目的としています。

  1. make([100]int)という形式の呼び出しが、あたかもmake([]int)(スライスを作成する意図)であるかのようにコンパイルされてしまう問題。これは、make関数が固定長配列の型引数を受け取った際に、コンパイラが誤った解釈をしていたことを示唆しています。Go言語において、makeはスライス、マップ、チャネルの初期化にのみ使用され、固定長配列の作成には使用されません。
  2. []this = [100]thatのような代入(スライス型変数に固定長配列を代入する操作)が、thisthatの型に関わらず動作してしまう問題。これは、スライスと配列間の代入における型互換性チェックが不十分であったことを示しています。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) を走査し、型チェック、最適化、コード生成の準備を行う役割を担っています。

  1. 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のような不正な代入がコンパイル時にエラーとして検出されるようになります。

  2. 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における修正

  1. ascompat関数の変更: if(isslice(dst) && isfixedarray(src) && eqtype(dst->type, src->type, 0)) この変更は、スライスと固定長配列間の代入互換性チェックに、要素の型が一致するという条件を追加しました。これにより、[]int型のスライス変数に[100]float64のような異なる要素型を持つ固定長配列を代入しようとした場合に、コンパイルエラーが発生するようになり、Goの型安全性が向上しました。

  2. 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.ReaderReadメソッドがバイトスライスを期待するという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.