[インデックス 11588] ファイルの概要
このコミットは、test/ken/array.go
ファイルに新しいテストケースを追加するものです。このファイルはGo言語のテストスイートの一部であり、配列やスライスに関する様々な挙動を検証するために使用されます。
コミット
commit 450c955bd973f153ba99c340022a424be5c75f73
Author: Ian Lance Taylor <iant@golang.org>
Date: Fri Feb 3 06:29:30 2012 -0800
test: test slice beyond len
When slicing a slice, the bounds may be > len as long as they
are <= cap. Interestingly, gccgo got that wrong and still
passed the testsuite and all the library tests.
R=golang-dev, rsc, iant
CC=golang-dev
https://golang.org/cl/5622053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/450c955bd973f153ba99c340022a424be5c75f73
元コミット内容
commit 450c955bd973f153ba99c340022a424be5c75f73
Author: Ian Lance Taylor <iant@golang.org>
Date: Fri Feb 3 06:29:30 2012 -0800
test: test slice beyond len
When slicing a slice, the bounds may be > len as long as they
are <= cap. Interestingly, gccgo got that wrong and still
passed the testsuite and all the library tests.
R=golang-dev, rsc, iant
CC=golang-dev
https://golang.org/cl/5622053
変更の背景
このコミットの主な背景は、Go言語のコンパイラの一つであるgccgo
が、スライスのスライス操作において、len
(現在の長さ)を超えてcap
(容量)までの範囲でスライスを作成する際の挙動を誤って解釈していたという問題にあります。
Goのスライスは、基盤となる配列への参照であり、len
とcap
という2つの重要なプロパティを持ちます。スライス操作(s[low:high]
)では、high
インデックスが現在のスライスのlen
を超えていても、基盤となる配列のcap
以内であれば有効です。これにより、既存の基盤配列のメモリを再利用しつつ、スライスの「ビュー」を拡張することができます。
しかし、gccgo
はこの仕様を正しく実装しておらず、len
を超えるスライス操作を誤って処理していました。驚くべきことに、このバグが存在するにもかかわらず、既存のテストスイートや標準ライブラリのテストはすべてパスしていました。これは、既存のテストがこの特定のエッジケースを十分にカバーしていなかったことを示唆しています。
このコミットは、gccgo
のこの誤った挙動を露呈させ、修正を促すための新しいテストケースを追加することで、Go言語の仕様に対するコンパイラの準拠を強化することを目的としています。
前提知識の解説
Go言語のスライス
Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、基盤となる配列、そのセグメントの長さ(len
)、およびそのセグメントが基盤となる配列から拡張できる最大容量(cap
)の3つの要素で構成されます。
len
(長さ): スライスに含まれる要素の数です。len
は0
からcap
までの範囲で変動します。cap
(容量): スライスの基盤となる配列の先頭から、スライスが参照している部分の末尾までの要素の数です。これは、スライスが再割り当てなしで保持できる要素の最大数を示します。
スライスのスライス操作 (s[low:high]
)
Goでは、既存のスライスから新しいスライスを作成する「スライスのスライス」操作が可能です。この操作はs[low:high]
という形式で行われます。
low
: 新しいスライスの開始インデックス(基盤となる配列に対する相対位置)。high
: 新しいスライスの終了インデックス(基盤となる配列に対する相対位置)。high
は含まれません。
この操作において重要なのは、high
インデックスが元のスライスのlen
を超えていても、元のスライスのcap
以下であれば有効であるという点です。
例:
s := make([]int, 3, 5) // len=3, cap=5 のスライスを作成
// s は現在 [0 0 0] を参照している。基盤配列は [0 0 0 X X] のようなイメージ。
// s[0:5] は有効なスライス操作
// 新しいスライスは len=5, cap=5 となり、基盤配列全体を参照する。
// これは、元のスライスの len (3) を超えているが、cap (5) 以内であるため許容される。
new_s := s[0:5]
もしhigh
がcap
を超えた場合、Goランタイムはパニック(slice bounds out of range
)を引き起こします。
gccgo
gccgo
は、GCC(GNU Compiler Collection)のフロントエンドとして実装されたGo言語のコンパイラです。Go言語の公式コンパイラであるgc
とは異なる実装であり、GoプログラムをGCCのバックエンドを通じてコンパイルします。異なる実装であるため、Go言語の仕様に対する解釈や最適化の挙動がgc
と異なる場合があります。
技術的詳細
このコミットが対処している技術的な問題は、Go言語のスライス操作におけるhigh
インデックスの許容範囲に関するgccgo
の誤った実装です。
Go言語の仕様では、スライスのスライス操作s[low:high]
において、high
インデックスは0 <= low <= high <= cap(s)
という条件を満たす必要があります。ここで重要なのは、high
が元のスライスのlen(s)
を超えていても、cap(s)
以内であれば有効であるという点です。この挙動は、既存の基盤配列のメモリを効率的に再利用し、スライスの「ビュー」を拡張するために不可欠です。
しかし、gccgo
は過去にこの仕様を正しく扱えていませんでした。具体的には、high
がlen
を超えているがcap
以内であるようなスライス操作に対して、gccgo
が誤ったコードを生成したり、期待される結果を返さなかったりするバグが存在したと考えられます。
コミットメッセージにある「Interestingly, gccgo got that wrong and still passed the testsuite and all the library tests.」という記述は、このバグが既存のテストでは検出されなかったことを示しています。これは、Goのテストスイートが、スライスのlen
とcap
の境界条件を厳密にテストするような特定のシナリオを欠いていたためです。
このコミットで追加されたテストケースは、まさにこのエッジケース、つまりlen
を超えてcap
までの範囲でスライスを作成するシナリオを明示的に検証することで、gccgo
のようなコンパイラがGo言語の仕様に厳密に準拠していることを保証しようとしています。このようなテストは、異なるコンパイラ実装間での互換性を高め、Goプログラムの移植性と信頼性を向上させる上で非常に重要です。
コアとなるコードの変更箇所
変更はtest/ken/array.go
ファイルに対して行われました。
--- a/test/ken/array.go
+++ b/test/ken/array.go
@@ -68,6 +68,9 @@ func testpdpd() {
a = a[5:25]
res(sumpd(a), 5, 25)
+
+ a = a[30:95]
+ res(sumpd(a), 35, 100)
}
// call ptr fixed with ptr fixed
具体的には、testpdpd
関数内に以下の2行が追加されています。
a = a[30:95]
res(sumpd(a), 35, 100)
コアとなるコードの解説
追加されたコードは、testpdpd
関数内の既存のスライス操作の後に続きます。
-
a = a[30:95]
- この行が、
len
を超えてcap
までの範囲でスライスを作成するテストの核心です。 - この時点でのスライス
a
の具体的なlen
とcap
は、testpdpd
関数のこれまでの処理に依存しますが、このテストが意図しているのは、a
の現在のlen
が30未満であり、かつcap
が95以上であるような状況です。 - もし
a
のlen
が30未満で、cap
が95以上であれば、このスライス操作はGoの仕様上有効です。新しいスライスa
は、元の基盤配列のインデックス30から94までの要素を参照するようになります。 gccgo
がこの操作を誤って処理した場合、後続のsumpd(a)
の結果が期待通りにならないか、あるいはパニックを引き起こす可能性があります。
- この行が、
-
res(sumpd(a), 35, 100)
res
関数は、テスト結果を検証するためのユーティリティ関数です。sumpd(a)
は、新しくスライスされたa
の要素の合計を計算します。35
と100
は、それぞれ期待される合計値と、おそらくスライスの長さや要素の範囲に関連する期待値です。- この行は、
a = a[30:95]
というスライス操作がGoの仕様通りに正しく行われた場合に、sumpd(a)
が特定の期待値を返すことを検証します。もしgccgo
がスライス操作を誤っていた場合、このres
関数が失敗し、バグが検出されることになります。
このテストケースは、Goのスライスが持つ柔軟なスライス機能、特にlen
とcap
の関係性を正しく理解し、実装しているかを検証するための重要なエッジケースをカバーしています。
関連リンク
- Go CL 5622053: https://golang.org/cl/5622053
参考にした情報源リンク
- Go Slices: usage and internals: https://go.dev/blog/slices-intro
- Go Slices:
len
vscap
: https://stackoverflow.com/questions/27517900/go-slices-len-vs-cap - Go Slices:
append
function: https://go.dev/blog/go-slices-usage-and-internals gccgo
and Go compatibility: https://go.dev/doc/install/gccgo- Go slice beyond len cap: https://stackoverflow.com/questions/27517900/go-slices-len-vs-cap
- Go slice beyond len cap: https://www.ardanlabs.com/blog/2013/09/slice-internals-in-go.html
- Go slice beyond len cap: https://www.w3schools.com/go/go_slices.php
gccgo
slice beyond len cap bug: https://go.dev/issue/2935 (関連する可能性のあるGo issue)gccgo
slice beyond len cap bug: https://stackoverflow.com/questions/6799799/go-slice-bounds-out-of-range (一般的なスライス範囲外エラーに関する議論)