[インデックス 16127] ファイルの概要
このコミットは、Go言語の reflect
パッケージにおける StringHeader
および SliceHeader
構造体の Data
フィールドの型を unsafe.Pointer
から uintptr
に戻す変更を元に戻すものです。これは、以前のコミット (CL 8363045 / a3ce42f9748b) がエクスポートされたAPIを変更し、ビルドを壊したために行われました。
コミット
commit 2be84a8c2bbe7efbc621f1ab84213c5b6ddf4d2f
Author: David Symonds <dsymonds@golang.org>
Date: Mon Apr 8 07:59:47 2013 +1000
undo CL 8363045 / a3ce42f9748b
It changes an exported API, and breaks the build.
««« original CL description
reflect: use unsafe.Pointer in StringHeader and SliceHeader
Relates to issue 5193.
R=r
CC=golang-dev
https://golang.org/cl/8363045
»»»
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/8357051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2be84a8c2bbe7efbc621f1ab84213c5b6ddf4d2f
元コミット内容
このコミットは、以下の元のコミット (CL 8363045 / a3ce42f9748b) を元に戻すものです。
reflect: use unsafe.Pointer in StringHeader and SliceHeader
Relates to issue 5193.
R=r
CC=golang-dev
https://golang.org/cl/8363045
元のコミットは、reflect
パッケージの StringHeader
および SliceHeader
構造体内の Data
フィールドの型を uintptr
から unsafe.Pointer
に変更しようとしました。
変更の背景
このコミットの主な背景は、以前のコミット (CL 8363045 / a3ce42f9748b) がGo言語のビルドを壊し、かつエクスポートされたAPIを変更してしまったため、その変更を元に戻す必要があったことです。
Go言語では、後方互換性が非常に重視されます。特に、エクスポートされたAPI(外部から利用可能な関数、型、変数など)の変更は、既存のコードベースに大きな影響を与えるため、非常に慎重に行われます。元のコミットが StringHeader
と SliceHeader
の Data
フィールドの型を変更したことは、これらの構造体を直接利用している可能性のあるコードに対して破壊的な変更となりました。
また、「breaks the build」という記述から、この変更がGoの標準ライブラリやツールチェーンのビルドプロセスにおいて何らかのコンパイルエラーやリンケージエラーを引き起こしたことが示唆されます。これは、Goの安定性と信頼性を維持する上で許容できない問題であり、迅速なロールバックが必要とされました。
元のコミットが「Relates to issue 5193」と記載していますが、Goの公式イシュートラッカーにはこの番号のイシューは存在しないようです。これは、内部的な参照番号であるか、あるいは別のシステムでのイシューを指している可能性があります。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の概念について理解しておく必要があります。
1. unsafe.Pointer
unsafe.Pointer
は、Go言語における特殊なポインタ型です。その名の通り「unsafe(安全でない)」であり、Goの型安全性の保証をバイパスするために使用されます。通常、Goでは異なる型のポインタ間で直接変換することはできませんが、unsafe.Pointer
を介することで、任意の型のポインタを別の型のポインタに変換したり、ポインタと uintptr
の間で変換を行ったりすることが可能になります。
- 用途: 主に、C言語との相互運用、低レベルのメモリ操作、またはGoの型システムでは表現できない特殊なデータ構造の操作など、非常に限定的な状況で使用されます。
- 危険性:
unsafe.Pointer
を使用すると、Goのガベージコレクタがオブジェクトの参照を正しく追跡できなくなる可能性があり、メモリリークやクラッシュなどの問題を引き起こす可能性があります。そのため、使用は極力避けるべきであり、使用する際にはその影響を完全に理解している必要があります。
2. uintptr
uintptr
は、ポインタを保持するのに十分な大きさの符号なし整数型です。これは、ポインタの値を整数として扱うことを可能にします。
unsafe.Pointer
との関係:uintptr
はunsafe.Pointer
との間で相互に変換可能です。unsafe.Pointer
からuintptr
への変換は、ポインタのメモリアドレスを整数値として取得します。uintptr
からunsafe.Pointer
への変換は、整数値をメモリアドレスとして解釈し、ポインタに変換します。
- ガベージコレクション:
uintptr
は単なる整数であり、ガベージコレクタはuintptr
が指すメモリを追跡しません。したがって、uintptr
に変換されたポインタが指すメモリが、他の有効なポインタによって参照されていない場合、ガベージコレクタによって解放されてしまう可能性があります。これがunsafe.Pointer
を介してuintptr
を使用する際の主要な危険性の一つです。
3. reflect.StringHeader
と reflect.SliceHeader
Go言語の reflect
パッケージは、実行時に型情報を検査し、操作するための機能を提供します。StringHeader
と SliceHeader
は、Goの文字列 (string
) とスライス ([]T
) の内部表現を公開する構造体です。これらは unsafe
パッケージの一部ではありませんが、その性質上、unsafe
パッケージと組み合わせて使用されることがよくあります。
-
StringHeader
:type StringHeader struct { Data uintptr // または unsafe.Pointer Len int }
Data
フィールドは文字列のバイトデータが格納されているメモリの先頭アドレスを指し、Len
フィールドは文字列の長さをバイト単位で表します。 -
SliceHeader
:type SliceHeader struct { Data uintptr // または unsafe.Pointer Len int Cap int }
Data
フィールドはスライスの要素が格納されているメモリの先頭アドレスを指し、Len
フィールドはスライスの現在の長さを要素数で表し、Cap
フィールドはスライスの容量(基底配列のサイズ)を要素数で表します。
これらの構造体は、Goの文字列やスライスを直接操作する際に使用されますが、その使用は「安全でない」とされており、Goのドキュメントでも「安全に、または移植性をもって使用することはできない」と明記されています。これは、これらの構造体の内部表現がGoのバージョンやアーキテクチャによって変更される可能性があり、またガベージコレクタとの相互作用に注意が必要なためです。
技術的詳細
このコミットは、src/pkg/reflect/value.go
ファイルに対して行われた変更を元に戻すものです。具体的には、reflect.StringHeader
と reflect.SliceHeader
構造体の Data
フィールドの型を unsafe.Pointer
から uintptr
に戻し、それに伴う関連コードの修正も元に戻しています。
元のコミットでは、Data
フィールドを unsafe.Pointer
に変更することで、ポインタ演算を行う際に明示的な uintptr
への変換を減らし、コードを簡潔にしようとした可能性があります。しかし、この変更は以下の問題を引き起こしました。
- エクスポートされたAPIの変更:
StringHeader
とSliceHeader
はreflect
パッケージの一部としてエクスポートされており、これらの構造体のフィールドの型を変更することは、それらを使用している外部のコードに対して破壊的な変更となります。Goの互換性ポリシーに反するため、これは許容されません。 - ビルドの破損: 型の変更が、Goのビルドシステムや他の標準ライブラリのコンパイルに影響を与え、ビルドが失敗する原因となりました。これは、
unsafe.Pointer
とuintptr
の間の変換ルールや、ポインタ演算のセマンティクスが、Goのコンパイラやランタイムの特定の期待と合致しなかったためと考えられます。
このコミットでは、これらの問題を解決するために、元の変更を完全にロールバックしています。これにより、reflect
パッケージのAPIの安定性が維持され、ビルドが再び正常に機能するようになります。
具体的には、Data
フィールドが uintptr
であることを前提としたポインタ演算(例: uintptr(s.Data) + uintptr(i)*typ.size
)が復活し、unsafe.Pointer
と uintptr
の間の明示的な変換が再度必要となります。これは、Goの型システムとガベージコレクタの挙動を考慮した、より安全で安定したアプローチと見なされます。
コアとなるコードの変更箇所
src/pkg/reflect/value.go
ファイルにおいて、以下の変更が元に戻されました。
--- a/src/pkg/reflect/value.go
+++ b/src/pkg/reflect/value.go
@@ -910,7 +910,7 @@ func (v Value) Index(i int) Value {
\ttt := (*sliceType)(unsafe.Pointer(v.typ))\n \t\ttyp := tt.elem\n \t\tfl |= flag(typ.Kind()) << flagKindShift\n-\t\tval := unsafe.Pointer(uintptr(s.Data) + uintptr(i)*typ.size)\n+\t\tval := unsafe.Pointer(s.Data + uintptr(i)*typ.size)\n \t\treturn Value{typ, val, fl}\n \n \tcase String:\n@@ -919,7 +919,7 @@ func (v Value) Index(i int) Value {\n \t\tif i < 0 || i >= s.Len {\n \t\t\tpanic(\"reflect: string index out of range\")\n \t\t}\n-\t\tval := *(*byte)(unsafe.Pointer(uintptr(s.Data) + uintptr(i)))\n+\t\tval := *(*byte)(unsafe.Pointer(s.Data + uintptr(i)))\n \t\treturn Value{uint8Type, unsafe.Pointer(uintptr(val)), fl}\n \t}\n \tpanic(&ValueError{\"reflect.Value.Index\", k})\
@@ -1310,7 +1310,7 @@ func (v Value) Pointer() uintptr {\
\t\treturn uintptr(p)\n \n \tcase Slice:\n-\t\treturn uintptr((*SliceHeader)(v.val).Data)\n+\t\treturn (*SliceHeader)(v.val).Data\n \t}\n \tpanic(&ValueError{\"reflect.Value.Pointer\", k})\
}\n@@ -1565,7 +1565,7 @@ func (v Value) Slice(beg, end int) Value {\
\t\t}\n \t\tvar x string\n \t\tval := (*StringHeader)(unsafe.Pointer(&x))\n-\t\tval.Data = unsafe.Pointer(uintptr(s.Data) + uintptr(beg))\n+\t\tval.Data = s.Data + uintptr(beg)\n \t\tval.Len = end - beg\n \t\treturn Value{v.typ, unsafe.Pointer(&x), v.flag}\n \t}\n@@ -1579,7 +1579,7 @@ func (v Value) Slice(beg, end int) Value {\
\n \t// Reinterpret as *SliceHeader to edit.\n \ts := (*SliceHeader)(unsafe.Pointer(&x))\n-\ts.Data = unsafe.Pointer(uintptr(base) + uintptr(beg)*typ.elem.Size())\n+\ts.Data = uintptr(base) + uintptr(beg)*typ.elem.Size()\n \ts.Len = end - beg\n \ts.Cap = cap - beg\n \n@@ -1701,14 +1701,14 @@ func (v Value) UnsafeAddr() uintptr {\
// StringHeader is the runtime representation of a string.\n // It cannot be used safely or portably.\n type StringHeader struct {\n-\tData unsafe.Pointer\n+\tData uintptr\n \tLen int\n }\n \n // SliceHeader is the runtime representation of a slice.\n // It cannot be used safely or portably.\n type SliceHeader struct {\n-\tData unsafe.Pointer\n+\tData uintptr\n \tLen int\n \tCap int\n }\n@@ -1988,7 +1988,7 @@ func MakeSlice(typ Type, len, cap int) Value {\
\n \t// Reinterpret as *SliceHeader to edit.\n \ts := (*SliceHeader)(unsafe.Pointer(&x))\n-\ts.Data = unsafe_NewArray(typ.Elem().(*rtype), cap)\n+\ts.Data = uintptr(unsafe_NewArray(typ.Elem().(*rtype), cap))\
\ts.Len = len\n \ts.Cap = cap\n \n```
## コアとなるコードの解説
このコミットの主要な変更は、`StringHeader` と `SliceHeader` 構造体の `Data` フィールドの型を `unsafe.Pointer` から `uintptr` に戻すことです。これに伴い、これらの構造体を利用する関数内のポインタ演算も元の形式に戻されています。
1. **`StringHeader` と `SliceHeader` の `Data` フィールドの型変更**:
```go
type StringHeader struct {
- Data unsafe.Pointer
+ Data uintptr
Len int
}
type SliceHeader struct {
- Data unsafe.Pointer
+ Data uintptr
Len int
Cap int
}
```
これは、元のコミットが `Data` フィールドを `unsafe.Pointer` に変更したものを元に戻し、Go 1.0のリリース以来の安定したAPIである `uintptr` に戻しています。これにより、これらの構造体を使用する既存のコードとの互換性が回復します。
2. **`Value.Index` メソッドの変更**:
`Value.Index` メソッドは、スライスや文字列の要素にアクセスするために使用されます。
```go
// スライスの場合
- val := unsafe.Pointer(uintptr(s.Data) + uintptr(i)*typ.size)
+ val := unsafe.Pointer(s.Data + uintptr(i)*typ.size)
// 文字列の場合
- val := *(*byte)(unsafe.Pointer(uintptr(s.Data) + uintptr(i)))
+ val := *(*byte)(unsafe.Pointer(s.Data + uintptr(i)))
```
`s.Data` が `uintptr` に戻ったため、ポインタ演算を行う際に `uintptr(s.Data)` のような冗長な変換が不要になりました。`s.Data` はすでに `uintptr` なので、直接 `+ uintptr(i)*typ.size` や `+ uintptr(i)` といった演算が可能です。結果を `unsafe.Pointer` にキャストして使用します。
3. **`Value.Pointer` メソッドの変更**:
`Value.Pointer` メソッドは、スライスの基底アドレスを `uintptr` として返します。
```go
case Slice:
- return uintptr((*SliceHeader)(v.val).Data)
+ return (*SliceHeader)(v.val).Data
```
`(*SliceHeader)(v.val).Data` はすでに `uintptr` 型であるため、`uintptr()` への明示的なキャストが不要になりました。
4. **`Value.Slice` メソッドの変更**:
`Value.Slice` メソッドは、文字列やスライスから新しいスライスを作成するために使用されます。
```go
// 文字列の場合
- val.Data = unsafe.Pointer(uintptr(s.Data) + uintptr(beg))
+ val.Data = s.Data + uintptr(beg)
// スライスの場合
- s.Data = unsafe.Pointer(uintptr(base) + uintptr(beg)*typ.elem.Size())
+ s.Data = uintptr(base) + uintptr(beg)*typ.elem.Size()
```
ここでも、`s.Data` や `base` が `uintptr` に戻ったため、冗長な `uintptr()` キャストが削除されました。`StringHeader.Data` は `uintptr` なので、`s.Data + uintptr(beg)` の結果を直接代入できます。`SliceHeader.Data` も `uintptr` なので、`uintptr(base) + uintptr(beg)*typ.elem.Size()` の結果を直接代入できます。
5. **`MakeSlice` 関数の変更**:
`MakeSlice` 関数は、新しいスライスを作成するために使用されます。
```go
- s.Data = unsafe_NewArray(typ.Elem().(*rtype), cap)
+ s.Data = uintptr(unsafe_NewArray(typ.Elem().(*rtype), cap))
```
`unsafe_NewArray` は `unsafe.Pointer` を返すため、`s.Data` が `uintptr` に戻ったことで、その結果を `uintptr()` に明示的にキャストする必要が再び生じました。
これらの変更は、Goの `reflect` パッケージが低レベルのメモリ操作を行う際に、`unsafe.Pointer` と `uintptr` の間の変換をどのように管理しているかを示しています。`uintptr` はメモリアドレスを整数として扱うため、ポインタ演算(アドレスの加算など)に適していますが、ガベージコレクタの追跡対象ではないため、`unsafe.Pointer` を介してGoの型システムに再導入する際には細心の注意が必要です。このコミットは、APIの安定性とビルドの健全性を優先し、より保守的なアプローチに戻したものです。
## 関連リンク
* Go言語の `unsafe` パッケージ: [https://pkg.go.dev/unsafe](https://pkg.go.dev/unsafe)
* Go言語の `reflect` パッケージ: [https://pkg.go.dev/reflect](https://pkg.go.dev/reflect)
* Go言語の互換性に関するドキュメント (Go 1 Compatibility Guarantee): [https://go.dev/doc/go1compat](https://go.dev/doc/go1compat)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (pkg.go.dev)
* Go言語のGitHubリポジトリ (github.com/golang/go)
* Go言語のコードレビューシステム (golang.org/cl)
* `unsafe.Pointer` と `uintptr` に関するGoコミュニティの議論やブログ記事 (一般的な知識として)
* Goのイシュー追跡システム (issue 5193に関する情報が公式イシュートラッカーには見当たらなかったため、一般的なGoのイシュー管理に関する知識)
* Goのビルドプロセスに関する一般的な知識