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

[インデックス 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.PointerSliceHeader

  • 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 を使用して作成されたスライスの lencap が期待通りの値であることを確認します。特に 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) で作成されたスライス vcap5 であることを確認する行が追加されました。
    • 元の xscap8Slice(3, 5) はインデックス 3 から始まるため、新しいスライスの cap8 - 3 = 5 となるべきです。
  • xa := [8]int{10, ..., 80} (配列のサイズが 7 から 8 に変更) から v = ValueOf(&xa).Elem().Slice(2, 5) で作成されたスライス vcap6 であることを確認する行が追加されました。
    • 元の xacap8Slice(2, 5) はインデックス 2 から始まるため、新しいスライスの cap8 - 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]cap5 の場合、s[1:3][2,3] となり、len2 です。しかし、cap5-1=4 となるべきです。end - beg を使用すると cap2 となり、誤った値になります。
  • 変更後 (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 を使用して作成されたスライスの lencap が期待される値と一致するかどうかを厳密にチェックします。特に cap(v) != X のアサーションは、このコミットの主要な目的である cap の修正が正しく適用されていることを保証します。また、DeepEqual を使用して、スライスされた要素の内容も検証することで、スライス操作全体の正確性を確認しています。配列 xa のサイズが 7 から 8 に変更されたのは、Slice(2, 5) 操作後の cap6 となるように、元のキャパシティを 8 に調整するためです。

関連リンク

参考にした情報源リンク