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

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

このコミットは、Go言語の初期段階におけるnew組み込み関数の引数処理に関するバグ修正を目的としています。具体的には、new関数がスライス型に対して不適切な数の引数(本来make関数が受け取るべき長さや容量の引数)を受け入れてしまう問題を修正するためのテストケースを追加しています。

コミット

  • コミットハッシュ: 7692a93173cb8555acf4d24433224d48f531bf5a
  • 作者: Robert Griesemer gri@golang.org
  • コミット日時: Mon Nov 17 16:46:56 2008 -0800
  • コミットメッセージ:
    - new() accepts too many arguments
    
    R=r
    OCL=19413
    CL=19413
    

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

https://github.com/golang/go/commit/7692a93173cb8555acf4d24433224d48f531bf5a

元コミット内容

commit 7692a93173cb8555acf4d24433224d48f531bf5a
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Nov 17 16:46:56 2008 -0800

    - new() accepts too many arguments

    R=r
    OCL=19413
    CL=19413
---
 test/bugs/bug122.go | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/test/bugs/bug122.go b/test/bugs/bug122.go
new file mode 100644
index 0000000000..da58944b77
--- /dev/null
+++ b/test/bugs/bug122.go
@@ -0,0 +1,11 @@
+// errchk $G $D/$F.go
+
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func main() {
+\ta := new([]int, 10, 20, 30, 40);  // should allow at most 2 sizes
+}\n

変更の背景

このコミットが行われた2008年当時、Go言語はまだ開発の初期段階にありました。new組み込み関数は、特定の型のためのメモリを割り当て、そのゼロ値へのポインタを返す役割を担っています。しかし、スライス型に対してnew関数が呼び出された際に、本来make関数が受け取るべき引数(スライスの長さや容量)を誤って受け入れてしまうというバグが存在していました。

このバグは、コンパイラがnew関数の引数検証を適切に行っていなかったことに起因します。new([]int, 10, 20, 30, 40)のような不正な呼び出しがコンパイル時にエラーとして検出されず、予期せぬ動作やクラッシュを引き起こす可能性がありました。このコミットは、このような不正なnew関数の使用をコンパイル時に確実に検出するための回帰テストを追加することで、この問題を修正しようとしています。

前提知識の解説

Go言語には、メモリを割り当てるための主要な組み込み関数としてnewmakeの2つがあります。これらは異なる目的で使用され、特にスライス、マップ、チャネルといった参照型においてはその違いが重要です。

  1. new(Type):

    • 任意の型Typeのゼロ値を格納するのに十分なメモリを割り当て、その型へのポインタ(*Type)を返します。
    • 割り当てられたメモリは、その型のゼロ値で初期化されます(例: 数値型は0、文字列は""、ブール値はfalse、ポインタはnil)。
    • スライス型に対してnew([]T)と呼び出すと、それはスライスのヘッダ(基底配列へのポインタ、長さ、容量)のためのメモリを割り当てるだけであり、基底配列自体は割り当てられません。結果として得られるスライスはnilスライスと同様に長さ0、容量0となります。
  2. make(Type, size, capacity):

    • スライス、マップ、チャネルといった組み込みの参照型のみに使用されます。
    • これらの型を初期化し、使用可能な状態にします。
    • スライスの場合: make([]T, length, capacity)のように使用します。lengthはスライスの初期要素数、capacityは基底配列の総容量を指定します。capacityは省略可能で、その場合はlengthと同じ値になります。makeは指定された長さと容量を持つ基底配列を割り当て、その配列を参照するスライスヘッダを返します。

このコミットの文脈では、new([]int, 10, 20, 30, 40)という呼び出しが問題となっています。new関数はスライスの長さや容量を指定する引数を受け取るべきではありません。これらの引数はmake関数に特有のものです。このバグは、Goコンパイラがnewmakeの引数規則を厳密に区別していなかったことを示しています。

技術的詳細

このコミットの技術的詳細は、Goコンパイラの型チェックと組み込み関数の引数検証の厳密性に関するものです。

Go言語の設計哲学の一つに「早期エラー検出」があります。これは、開発プロセスの早い段階(コンパイル時)で可能な限り多くのエラーを検出することで、ランタイムエラーや予期せぬバグの発生を防ぐというものです。

new関数とmake関数は、そのセマンティクスが大きく異なります。

  • new(T)は、T型の変数を宣言し、そのアドレスを取得するのと概念的に似ています。例えば、var x T; p := &xp := new(T)は、pT型のゼロ値へのポインタであるという点で似ています。
  • make(T, ...)は、スライス、マップ、チャネルといった特殊なデータ構造を「構築」するために使用されます。これらの型は、単にメモリを割り当てるだけでなく、内部的なデータ構造(例えば、スライスの基底配列、マップのハッシュテーブル、チャネルのバッファ)を適切に初期化する必要があります。

問題となっていたのは、コンパイラがnew関数に対して、make関数にのみ有効な引数(スライスの長さや容量)が渡された場合に、それを不正な呼び出しとして認識しなかった点です。new([]int, 10, 20, 30, 40)というコードは、newの定義から逸脱しており、コンパイル時にエラーとなるべきです。このコミットは、この特定のケースを捕捉するためのテストを追加することで、コンパイラの引数検証ロジックが強化されたことを示唆しています。

test/bugs/bug122.goファイルの先頭にある// errchk $G $D/$F.goというコメントは、Goのテストフレームワークにおける特別なディレクティブです。これは、そのファイルがコンパイルされる際に、指定されたコンパイラ($GはGoコンパイラを指す)がエラーを発生させることを期待していることを示します。もしエラーが発生しなければ、テストは失敗します。これにより、コンパイラが将来的にこの種の不正なnew呼び出しを誤って許可しないことが保証されます。

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

このコミットで追加された唯一のファイルは、test/bugs/bug122.goです。

--- /dev/null
+++ b/test/bugs/bug122.go
@@ -0,0 +1,11 @@
+// errchk $G $D/$F.go
+
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func main() {
+\ta := new([]int, 10, 20, 30, 40);  // should allow at most 2 sizes
+}\n

コアとなるコードの解説

追加されたtest/bugs/bug122.goは、Goコンパイラのバグをテストするための非常に簡潔なプログラムです。

  1. // errchk $G $D/$F.go:

    • これはGoのテストシステムに対する指示であり、このファイルがコンパイルされる際にコンパイラ($G)がエラーを出すことを期待していることを示します。もしエラーが出なければ、テストは失敗します。これは、このコミットが修正しようとしているバグが、コンパイル時に検出されるべきエラーであることを明確にしています。
  2. package mainfunc main() {}:

    • これはGoの実行可能なプログラムの標準的な構造です。
  3. a := new([]int, 10, 20, 30, 40); // should allow at most 2 sizes:

    • この行がテストの核心です。new関数は、スライス型[]intに対して呼び出されていますが、それに続いて10, 20, 30, 40という4つの引数が渡されています。
    • コメント// should allow at most 2 sizesが示すように、スライスを初期化する際に長さと容量を指定できるのはmake関数であり、その場合でも最大2つの引数(長さと容量)しか取りません。new関数は型引数のみを受け取るべきであり、このような追加の引数は不正です。
    • このテストの目的は、コンパイラがこの不正なnew呼び出しを検出し、コンパイルエラーを発生させることを確認することです。これにより、new関数の引数検証が正しく機能していることが保証されます。

このテストケースの追加により、Goコンパイラはnew関数の誤用を早期に検出し、開発者がより堅牢なコードを書くのを助けることができるようになりました。

関連リンク

参考にした情報源リンク