[インデックス 10722] ファイルの概要
このコミットは、Go言語の reflect
パッケージにおけるスライスの cap
(キャパシティ) の計算に関するバグを修正するものです。具体的には、reflect.Value.Slice
メソッドが新しいスライスのキャパシティを誤って設定していた問題を解決し、テストケースを追加してこの修正を検証しています。
コミット
commit 3dc278d3e2678ad7c7953fac5ae8ad641fe4bd6e
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Mon Dec 12 19:45:40 2011 -0200
reflect: fix Slice cap
R=golang-dev, dsymonds, r, rsc
CC=golang-dev
https://golang.org/cl/5483044
---
src/pkg/reflect/all_test.go | 22 +++++++++++++++++-----\
src/pkg/reflect/value.go | 2 +-\
2 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/src/pkg/reflect/all_test.go b/src/pkg/reflect/all_test.go
index e43260196f..2f9f83fbc6 100644
--- a/src/pkg/reflect/all_test.go
+++ b/src/pkg/reflect/all_test.go
@@ -1557,14 +1557,26 @@ func TestSmallNegativeInt(t *testing.T) {
func TestSlice(t *testing.T) {
xs := []int{1, 2, 3, 4, 5, 6, 7, 8}
v := ValueOf(xs).Slice(3, 5).Interface().([]int)
- if len(v) != 2 || v[0] != 4 || v[1] != 5 {
- t.Errorf("xs.Slice(3, 5) = %v", v)
+ if len(v) != 2 {
+ t.Errorf("len(xs.Slice(3, 5)) = %d", len(v))
+ }
+ if cap(v) != 5 {
+ t.Errorf("cap(xs.Slice(3, 5)) = %d", cap(v))
+ }
+ if !DeepEqual(v[0:5], xs[3:]) {
+ t.Errorf("xs.Slice(3, 5)[0:5] = %v", v[0:5])
}
- xa := [7]int{10, 20, 30, 40, 50, 60, 70}
+ xa := [8]int{10, 20, 30, 40, 50, 60, 70, 80}
v = ValueOf(&xa).Elem().Slice(2, 5).Interface().([]int)
- if len(v) != 3 || v[0] != 30 || v[1] != 40 || v[2] != 50 {
- t.Errorf("xa.Slice(2, 5) = %v", v)
+ if len(v) != 3 {
+ t.Errorf("len(xa.Slice(2, 5)) = %d", len(v))
+ }
+ if cap(v) != 6 {
+ t.Errorf("cap(xa.Slice(2, 5)) = %d", cap(v))
+ }
+ if !DeepEqual(v[0:6], xa[2:]) {
+ t.Errorf("xs.Slice(2, 5)[0:6] = %v", v[0:6])
}
}
diff --git a/src/pkg/reflect/value.go b/src/pkg/reflect/value.go
index 1c4f67de3a..92c10ba2b9 100644
--- a/src/pkg/reflect/value.go
+++ b/src/pkg/reflect/value.go
@@ -1356,7 +1356,7 @@ func (v Value) Slice(beg, end int) Value {
s := (*SliceHeader)(unsafe.Pointer(&x))\n \ts.Data = uintptr(base) + uintptr(beg)*toCommonType(typ.elem).Size()\n \ts.Len = end - beg\n-\ts.Cap = end - beg\n+\ts.Cap = cap - beg\n \n \tfl := v.flag&flagRO | flagIndir | flag(Slice)<<flagKindShift\n \treturn Value{typ.common(), unsafe.Pointer(&x), fl}\n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/3dc278d3e2678ad7c7953fac5ae8ad641fe4bd6e](https://github.com/golang/go/commit/3dc278d3e2678ad7c7953fac5ae8ad641fe4bd6e)
## 元コミット内容
reflect: fix Slice cap
R=golang-dev, dsymonds, r, rsc CC=golang-dev https://golang.org/cl/5483044
## 変更の背景
Go言語の `reflect` パッケージは、実行時に型情報を検査したり、値の操作を行ったりするための機能を提供します。スライスはGo言語の基本的なデータ構造の一つであり、その操作は頻繁に行われます。`reflect.Value.Slice` メソッドは、既存のスライスや配列から新しいスライスを作成するために使用されます。
このコミット以前は、`reflect.Value.Slice` メソッドが生成する新しいスライスの `cap` (キャパシティ) が誤って計算されていました。具体的には、新しいスライスの `cap` が `end - beg` (新しいスライスの長さ) と同じ値に設定されていました。しかし、Goのスライスのセマンティクスでは、元のスライスまたは配列からスライスを作成した場合、新しいスライスのキャパシティは元のスライスのキャパシティから開始インデックスを引いた値になります。この誤った計算は、`reflect` パッケージを使用してスライスを操作する際に、予期せぬ動作やバグを引き起こす可能性がありました。例えば、新しいスライスに要素を追加しようとした際に、本来利用可能なはずのメモリ領域が利用できないために再アロケーションが発生したり、パニックを引き起こしたりする可能性がありました。
このバグは、`reflect` パッケージの正確性と信頼性を確保するために修正される必要がありました。
## 前提知識の解説
### Go言語のスライス (Slice)
Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは以下の3つの要素で構成されます。
1. **ポインタ (Pointer)**: スライスが参照する基底配列の先頭要素へのポインタ。
2. **長さ (Length)**: スライスに含まれる要素の数。`len()` 関数で取得できます。
3. **キャパシティ (Capacity)**: スライスが基底配列のどこまで拡張できるかを示す最大長。`cap()` 関数で取得できます。
スライスは、`make([]T, length, capacity)` で作成したり、既存の配列やスライスから `array[low:high]` や `slice[low:high]` のようにスライス操作で作成したりできます。
スライス操作 `s[low:high]` で新しいスライスを作成する場合、新しいスライスの長さは `high - low` となり、キャパシティは元のスライスのキャパシティから `low` を引いた値になります。
例:
```go
arr := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s := arr[2:5] // len(s) = 3 (5-2), cap(s) = 8 (10-2)
reflect
パッケージ
reflect
パッケージは、Goプログラムが実行時に自身の構造を検査し、変更することを可能にする機能(リフレクション)を提供します。これにより、型がコンパイル時に不明な場合でも、変数の型や値を操作できます。
reflect.ValueOf(i interface{}) Value
: 任意のGoの値をreflect.Value
型に変換します。reflect.Value.Interface() interface{}
:reflect.Value
を元のGoのインターフェース値に戻します。reflect.Value.Slice(i, j int) Value
:reflect.Value
がスライスまたは配列を表す場合、i
からj
までの範囲で新しいスライスを作成し、そのreflect.Value
を返します。
unsafe.Pointer
と SliceHeader
unsafe.Pointer
: 任意の型のポインタを保持できる特殊なポインタ型です。Goの型システムをバイパスしてメモリを直接操作するために使用されます。非常に強力ですが、誤用するとメモリ安全性の問題を引き起こす可能性があります。reflect.SliceHeader
:reflect
パッケージで定義されている構造体で、Goのスライスの内部表現を模倣しています。type SliceHeader struct { Data uintptr // ポインタ Len int // 長さ Cap int // キャパシティ }
unsafe.Pointer
を使用して、GoのスライスをSliceHeader
にキャストすることで、スライスの内部構造(ポインタ、長さ、キャパシティ)に直接アクセスし、変更することができます。
技術的詳細
このバグは、reflect.Value.Slice
メソッドの実装において、新しいスライスの Cap
フィールドが誤って end - beg
(新しいスライスの長さ) に設定されていたことに起因します。
元のコード:
func (v Value) Slice(beg, end int) Value {
// ...
s := (*SliceHeader)(unsafe.Pointer(&x))
s.Data = uintptr(base) + uintptr(beg)*toCommonType(typ.elem).Size()
s.Len = end - beg
s.Cap = end - beg // ここが問題
// ...
}
Go言語のスライスのセマンティクスでは、s[low:high]
の形式でスライスを作成した場合、新しいスライスのキャパシティは元のスライスのキャパシティから low
を引いた値になります。reflect.Value.Slice
メソッドもこのセマンティクスに従うべきです。
このコミットでは、reflect.Value.Slice
メソッドが呼び出される時点での元のスライスのキャパシティ (cap
) を取得し、そこから開始インデックス (beg
) を引いた値を新しいスライスのキャパシティとして設定するように修正されました。
修正後のコード:
func (v Value) Slice(beg, end int) Value {
// ...
s := (*SliceHeader)(unsafe.Pointer(&x))
s.Data = uintptr(base) + uintptr(beg)*toCommonType(typ.elem).Size()
s.Len = end - beg
s.Cap = cap - beg // 修正後
// ...
}
ここで cap
は、v.Cap()
で取得される元のスライスのキャパシティを指します。この修正により、reflect.Value.Slice
がGo言語のスライス操作の正しいセマンティクスに従うようになり、reflect
パッケージを介して作成されたスライスが期待通りのキャパシティを持つようになりました。
また、この修正を検証するために src/pkg/reflect/all_test.go
にテストケースが追加されました。これらのテストケースは、reflect.Value.Slice
を使用して作成されたスライスの len
と cap
が期待通りの値であることを確認します。特に cap
の検証が追加されたことで、将来的に同様の回帰バグが発生するのを防ぐことができます。
コアとなるコードの変更箇所
src/pkg/reflect/all_test.go
テストケース TestSlice
が修正され、reflect.Value.Slice
で作成されたスライスの cap
を検証するアサーションが追加されました。
xs := []int{1, 2, 3, 4, 5, 6, 7, 8}
からv := ValueOf(xs).Slice(3, 5)
で作成されたスライスv
のcap
が5
であることを確認する行が追加されました。- 元の
xs
のcap
は8
。Slice(3, 5)
はインデックス3
から始まるため、新しいスライスのcap
は8 - 3 = 5
となるべきです。
- 元の
xa := [8]int{10, ..., 80}
(配列のサイズが7
から8
に変更) からv = ValueOf(&xa).Elem().Slice(2, 5)
で作成されたスライスv
のcap
が6
であることを確認する行が追加されました。- 元の
xa
のcap
は8
。Slice(2, 5)
はインデックス2
から始まるため、新しいスライスのcap
は8 - 2 = 6
となるべきです。
- 元の
DeepEqual
を用いて、スライスされた範囲の要素が元のスライスの対応する要素と一致することも確認しています。
src/pkg/reflect/value.go
Value.Slice
メソッドの s.Cap
の設定箇所が修正されました。
- 変更前:
s.Cap = end - beg
- 変更後:
s.Cap = cap - beg
ここで cap
は、Value.Slice
メソッドが呼び出される時点での元の Value
のキャパシティ (v.Cap()
) を指します。
コアとなるコードの解説
src/pkg/reflect/value.go
の変更
// 変更前
s.Cap = end - beg
// 変更後
s.Cap = cap - beg
この変更は、reflect.Value.Slice
メソッドが新しいスライスのキャパシティを計算する方法を修正しています。
- 変更前 (
s.Cap = end - beg
): これは、新しいスライスのキャパシティをその長さ (end - beg
) と同じ値に設定していました。これはGoのスライスセマンティクスに反しており、元の基底配列の残りの容量を考慮していませんでした。例えば、元のスライスが[1,2,3,4,5]
でcap
が5
の場合、s[1:3]
は[2,3]
となり、len
は2
です。しかし、cap
は5-1=4
となるべきです。end - beg
を使用するとcap
が2
となり、誤った値になります。 - 変更後 (
s.Cap = cap - beg
): ここでのcap
は、Value.Slice
メソッドが呼び出された元のreflect.Value
(つまり、スライス操作の対象となる元のスライスまたは配列) のキャパシティを指します。beg
は新しいスライスの開始インデックスです。この計算cap - beg
は、Go言語のスライス操作s[low:high]
における新しいスライスのキャパシティの計算方法と完全に一致します。これにより、reflect
パッケージを介してスライスが作成された場合でも、そのキャパシティが正しく設定されるようになりました。
src/pkg/reflect/all_test.go
の変更
テストケース TestSlice
は、この cap
の修正が正しく機能していることを確認するために拡張されました。
func TestSlice(t *testing.T) {
xs := []int{1, 2, 3, 4, 5, 6, 7, 8}
v := ValueOf(xs).Slice(3, 5).Interface().([]int)
if len(v) != 2 {
t.Errorf("len(xs.Slice(3, 5)) = %d", len(v))
}
if cap(v) != 5 { // 追加された検証
t.Errorf("cap(xs.Slice(3, 5)) = %d", cap(v))
}
if !DeepEqual(v[0:5], xs[3:]) { // 追加された検証
t.Errorf("xs.Slice(3, 5)[0:5] = %v", v[0:5])
}
xa := [8]int{10, 20, 30, 40, 50, 60, 70, 80} // 配列サイズ変更
v = ValueOf(&xa).Elem().Slice(2, 5).Interface().([]int)
if len(v) != 3 {
t.Errorf("len(xa.Slice(2, 5)) = %d", len(v))
}
if cap(v) != 6 { // 追加された検証
t.Errorf("cap(xa.Slice(2, 5)) = %d", cap(v))
}
if !DeepEqual(v[0:6], xa[2:]) { // 追加された検証
t.Errorf("xs.Slice(2, 5)[0:6] = %v", v[0:6])
}
}
これらのテストは、reflect.Value.Slice
を使用して作成されたスライスの len
と cap
が期待される値と一致するかどうかを厳密にチェックします。特に cap(v) != X
のアサーションは、このコミットの主要な目的である cap
の修正が正しく適用されていることを保証します。また、DeepEqual
を使用して、スライスされた要素の内容も検証することで、スライス操作全体の正確性を確認しています。配列 xa
のサイズが 7
から 8
に変更されたのは、Slice(2, 5)
操作後の cap
が 6
となるように、元のキャパシティを 8
に調整するためです。
関連リンク
- Go Code Review: https://golang.org/cl/5483044
参考にした情報源リンク
- Go Slices: usage and internals: https://go.dev/blog/slices-intro
- The Go Programming Language Specification - Slice expressions: https://go.dev/ref/spec#Slice_expressions
- Package reflect: https://pkg.go.dev/reflect
- Package unsafe: https://pkg.go.dev/unsafe
- Go reflect.SliceHeader: https://pkg.go.dev/reflect#SliceHeader