[インデックス 19044] ファイルの概要
コミット
このコミットは、Go言語のsyscall
パッケージにおいて、Windows固有のシステムコール呼び出しでuintptr
型を使用していた箇所を、より型安全でガベージコレクタに認識されるunsafe.Pointer
型に置き換える変更を導入しています。これにより、特にポインタの扱いにおける潜在的なバグやガベージコレクションの問題が修正されます。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/258ee61c7240b7b147a672fdff9552981a182447
元コミット内容
commit 258ee61c7240b7b147a672fdff9552981a182447
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Sun Apr 6 12:18:01 2014 +1000
syscall: use unsafe.Pointer instead of uintptr on windows when possible
Fixes #7171
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/84330043
---
src/pkg/syscall/syscall_windows.go | 24 ++++++++++++------------
src/pkg/syscall/zsyscall_windows_386.go | 4 ++--
src/pkg/syscall/zsyscall_windows_amd64.go | 4 ++--
3 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/src/pkg/syscall/syscall_windows.go b/src/pkg/syscall/syscall_windows.go
index 4436e432a4..f9733f6cee 100644
--- a/src/pkg/syscall/syscall_windows.go
+++ b/src/pkg/syscall/syscall_windows.go
@@ -523,8 +523,8 @@ const socket_error = uintptr(^uint32(0))\n //sys socket(af int32, typ int32, protocol int32) (handle Handle, err error) [failretval==InvalidHandle] = ws2_32.socket\n //sys Setsockopt(s Handle, level int32, optname int32, optval *byte, optlen int32) (err error) [failretval==socket_error] = ws2_32.setsockopt\n //sys Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) [failretval==socket_error] = ws2_32.getsockopt\n-//sys bind(s Handle, name uintptr, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind\n-//sys connect(s Handle, name uintptr, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect\n+//sys bind(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind\n+//sys connect(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect\n //sys getsockname(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==socket_error] = ws2_32.getsockname\n //sys getpeername(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==socket_error] = ws2_32.getpeername\n //sys listen(s Handle, backlog int32) (err error) [failretval==socket_error] = ws2_32.listen\n@@ -579,7 +579,7 @@ type RawSockaddrAny struct {\n }\n \n type Sockaddr interface {\n-\tsockaddr() (ptr uintptr, len int32, err error) // lowercase; only we can define Sockaddrs\n+\tsockaddr() (ptr unsafe.Pointer, len int32, err error) // lowercase; only we can define Sockaddrs\n }\n \n type SockaddrInet4 struct {\n@@ -588,9 +588,9 @@ type SockaddrInet4) sockaddr() (uintptr, int32, error) {\n \tif sa.Port < 0 || sa.Port > 0xFFFF {\n-\t\treturn 0, 0, EINVAL\n+\t\treturn nil, 0, EINVAL\n \t}\n \tsa.raw.Family = AF_INET\n \tp := (*[2]byte)(unsafe.Pointer(&sa.raw.Port))\n@@ -599,7 +599,7 @@ func (sa *SockaddrInet4) sockaddr() (uintptr, int32, error) {\n \tfor i := 0; i < len(sa.Addr); i++ {\n \t\tsa.raw.Addr[i] = sa.Addr[i]\n \t}\n-\treturn uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil\n+\treturn unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil\n }\n \n type SockaddrInet6 struct {\n@@ -609,9 +609,9 @@ type SockaddrInet6 struct {\n \traw RawSockaddrInet6\n }\n \n-func (sa *SockaddrInet6) sockaddr() (uintptr, int32, error) {\n+func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, int32, error) {\n \tif sa.Port < 0 || sa.Port > 0xFFFF {\n-\t\treturn 0, 0, EINVAL\n+\t\treturn nil, 0, EINVAL\n \t}\n \tsa.raw.Family = AF_INET6\n \tp := (*[2]byte)(unsafe.Pointer(&sa.raw.Port))\n@@ -621,16 +621,16 @@ func (sa *SockaddrInet6) sockaddr() (uintptr, int32, error) {\n \tfor i := 0; i < len(sa.Addr); i++ {\n \t\tsa.raw.Addr[i] = sa.Addr[i]\n \t}\n-\treturn uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil\n+\treturn unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil\n }\n \n type SockaddrUnix struct {\n \tName string\n }\n \n-func (sa *SockaddrUnix) sockaddr() (uintptr, int32, error) {\n+func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, int32, error) {\n \t// TODO(brainman): implement SockaddrUnix.sockaddr()\n-\treturn 0, 0, EWINDOWS\n+\treturn nil, 0, EWINDOWS\n }\n \n func (rsa *RawSockaddrAny) Sockaddr() (Sockaddr, error) {\n@@ -754,7 +754,7 @@ func LoadConnectEx() error {\n \treturn connectExFunc.err\n }\n \n-func connectEx(s Handle, name uintptr, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) {\n+func connectEx(s Handle, name unsafe.Pointer, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) {\n \tr1, _, e1 := Syscall9(connectExFunc.addr, 7, uintptr(s), uintptr(name), uintptr(namelen), uintptr(unsafe.Pointer(sendBuf)), uintptr(sendDataLen), uintptr(unsafe.Pointer(bytesSent)), uintptr(unsafe.Pointer(overlapped)), 0, 0)\n \tif r1 == 0 {\n \t\tif e1 != 0 {\ndiff --git a/src/pkg/syscall/zsyscall_windows_386.go b/src/pkg/syscall/zsyscall_windows_386.go\nindex e68ea5748b..132adafeff 100644\n--- a/src/pkg/syscall/zsyscall_windows_386.go\n+++ b/src/pkg/syscall/zsyscall_windows_386.go\n@@ -1323,7 +1323,7 @@ func Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int3\n \treturn\n }\n \n-func bind(s Handle, name uintptr, namelen int32) (err error) {\n+func bind(s Handle, name unsafe.Pointer, namelen int32) (err error) {\n \tr1, _, e1 := Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))\n \tif r1 == socket_error {\n \t\tif e1 != 0 {\n@@ -1335,7 +1335,7 @@ func bind(s Handle, name uintptr, namelen int32) (err error) {\n \treturn\n }\n \n-func connect(s Handle, name uintptr, namelen int32) (err error) {\n+func connect(s Handle, name unsafe.Pointer, namelen int32) (err error) {\n \tr1, _, e1 := Syscall(procconnect.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))\n \tif r1 == socket_error {\n \t\tif e1 != 0 {\ndiff --git a/src/pkg/syscall/zsyscall_windows_amd64.go b/src/pkg/syscall/zsyscall_windows_amd64.go\nindex 049b5ecbaa..353a6fd980 100644\n--- a/src/pkg/syscall/zsyscall_windows_amd64.go\n+++ b/src/pkg/syscall/zsyscall_windows_amd64.go
@@ -1323,7 +1323,7 @@ func Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int3\n \treturn\n }\n \n-func bind(s Handle, name uintptr, namelen int32) (err error) {\n+func bind(s Handle, name unsafe.Pointer, namelen int32) (err error) {\n \tr1, _, e1 := Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))\n \tif r1 == socket_error {\n \t\tif e1 != 0 {\n@@ -1335,7 +1335,7 @@ func bind(s Handle, name uintptr, namelen int32) (err error) {\n \treturn\n }\n \n-func connect(s Handle, name uintptr, namelen int32) (err error) {\n+func connect(s Handle, name unsafe.Pointer, namelen int32) (err error) {\n \tr1, _, e1 := Syscall(procconnect.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))\n \tif r1 == socket_error {\n \t\tif e1 != 0 {\n```
## 変更の背景
このコミットは、Go言語の`syscall`パッケージにおけるWindows固有のシステムコール呼び出しの安全性を向上させることを目的としています。特に、Goのガベージコレクタがポインタを正しく追跡できるようにするために、`uintptr`から`unsafe.Pointer`への移行が行われました。
Goのガベージコレクタは、プログラムが使用しているメモリを自動的に解放する役割を担っています。ガベージコレクタが正しく機能するためには、どのメモリ領域がまだ参照されているかを正確に把握する必要があります。Goのポインタ型(例: `*int`, `*byte`)は、ガベージコレクタによって追跡され、参照されている間はメモリが解放されないことが保証されます。
しかし、`uintptr`型は単なる整数型であり、メモリアドレスを数値として表現するものです。`uintptr`に変換されたポインタは、ガベージコレクタからは単なる数値として扱われ、それが指すメモリ領域がまだ使用されていることをガベージコレクタが認識できません。このため、`uintptr`を介して参照されているメモリが、ガベージコレクタによって誤って解放されてしまう(use-after-free)という潜在的な問題がありました。
この問題は、特に外部関数インターフェース(FFI)を介してC言語のライブラリやOSのAPIを呼び出す際に顕著になります。WindowsのシステムコールはC言語のAPIとして提供されており、Goの`syscall`パッケージはこれらのAPIを呼び出すためのラッパーを提供しています。これらのAPIはしばしばポインタを引数として受け取りますが、Go側で`uintptr`として渡してしまうと、ガベージコレクタがそのポインタが指すメモリを保護できなくなる可能性がありました。
このコミットは、Go issue #7171「syscall: use unsafe.Pointer instead of uintptr on windows when possible」を修正するものです。このイシューでは、`uintptr`の使用がガベージコレクタの正確性を損なう可能性が指摘されており、`unsafe.Pointer`への移行が提案されていました。
## 前提知識の解説
### Goのポインタとガベージコレクション
Go言語には、C言語のような直接的なポインタ演算は提供されていませんが、`*T`という形式の型付きポインタが存在します。これらのポインタはGoの型システムによって管理され、ガベージコレクタが追跡します。つまり、ポインタが指すメモリ領域は、そのポインタが有効な限りガベージコレクタによって解放されません。
### `uintptr`型
`uintptr`は、ポインタを保持するのに十分な大きさの符号なし整数型です。これは、ポインタの値を整数として扱うことができますが、Goの型システムやガベージコレクタからは単なる数値として扱われます。`uintptr`はポインタではないため、それが指すメモリ領域はガベージコレクタによって追跡されません。これは、C言語の`void*`に似ていますが、`void*`が型付けされたポインタであるのに対し、`uintptr`は純粋な数値である点が異なります。
### `unsafe.Pointer`型
`unsafe.Pointer`は、Goの`unsafe`パッケージで提供される特殊なポインタ型です。これは、任意の型のポインタを保持できる汎用ポインタであり、Goの型システムをバイパスしてポインタ演算を行うことを可能にします。`unsafe.Pointer`は以下の変換規則を持ちます。
1. 任意の型のポインタ `*T` は `unsafe.Pointer` に変換できる。
2. `unsafe.Pointer` は任意の型のポインタ `*T` に変換できる。
3. `uintptr` は `unsafe.Pointer` に変換できる。
4. `unsafe.Pointer` は `uintptr` に変換できる。
最も重要な点は、`unsafe.Pointer`がガベージコレクタによって追跡されるポインタであるということです。`unsafe.Pointer`が指すメモリ領域は、その`unsafe.Pointer`が有効な限りガベージコレクタによって保護されます。これにより、`uintptr`が抱えていたガベージコレクションの問題を解決できます。
### `syscall`パッケージとFFI (Foreign Function Interface)
`syscall`パッケージは、Goプログラムからオペレーティングシステムのシステムコールを直接呼び出すための機能を提供します。これは、Goが提供しない低レベルのOS機能にアクセスしたり、既存のCライブラリと連携したりするために使用されます。Windowsの場合、`syscall`パッケージはWin32 APIを呼び出すためのラッパーを提供します。
FFIは、あるプログラミング言語で書かれたコードが、別のプログラミング言語で書かれたコードを呼び出すためのメカニズムです。Goの場合、`syscall`パッケージや`cgo`を通じてFFIが実現されます。FFIを使用する際には、異なる言語間のデータ型やメモリ管理のセマンティクスを正しく橋渡しすることが重要です。特にポインタの扱いは、メモリ安全性に直結するため慎重に行う必要があります。
## 技術的詳細
このコミットの核心は、Windowsのシステムコール呼び出しにおいて、ポインタを引数として渡す際に`uintptr`ではなく`unsafe.Pointer`を使用するように変更した点です。
以前のGoの`syscall`パッケージでは、Windows APIにポインタを渡す際に、Goの型付きポインタ(例: `*byte`)を`uintptr`にキャストして渡していました。これは、Windows APIがC言語の規約に従い、ポインタを整数値として扱うことが多いためです。しかし、前述の通り、`uintptr`はガベージコレクタに追跡されないため、`uintptr`に変換されたポインタが指すメモリが、システムコールが完了する前にガベージコレクタによって解放されてしまう可能性がありました。これは、特に非同期I/Oやコールバックを使用するような複雑なシナリオで問題を引き起こす可能性があります。
`unsafe.Pointer`は、Goのガベージコレクタがそのポインタが指すメモリを追跡し、保護することを保証します。したがって、`unsafe.Pointer`を介してWindows APIにポインタを渡すことで、Goのガベージコレクタはそのメモリがまだ使用中であることを認識し、システムコールが完了するまで解放しないようにすることができます。これにより、use-after-freeのようなメモリ関連のバグを防ぎ、プログラムの安定性と信頼性を向上させます。
具体的には、`bind`、`connect`、`connectEx`といったネットワーク関連のシステムコールや、`Sockaddr`インターフェースの`sockaddr()`メソッドのシグネチャが変更されています。これらの関数は、ソケットアドレス構造体へのポインタを引数として受け取りますが、このポインタの型が`uintptr`から`unsafe.Pointer`に変更されました。
また、`SockaddrInet4`や`SockaddrInet6`の`sockaddr()`メソッドの実装において、エラー時に`0`(`uintptr`のゼロ値)を返していた箇所が`nil`(`unsafe.Pointer`のゼロ値)を返すように変更されています。これは、型の一貫性を保つための変更です。
この変更は、Goの`syscall`パッケージがWindows APIとより安全かつ堅牢に連携できるようにするための重要な改善です。`unsafe.Pointer`の使用は「unsafe」という名前が示す通り注意が必要ですが、システムコールのような低レベルの操作では、Goの型システムでは表現できないメモリ操作が必要となるため、適切に使用することで安全性を高めることができます。
## コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコード箇所は以下の通りです。
### `src/pkg/syscall/syscall_windows.go`
- `bind` および `connect` システムコールの定義において、`name`引数の型が `uintptr` から `unsafe.Pointer` に変更されました。
```diff
-//sys bind(s Handle, name uintptr, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind
-//sys connect(s Handle, name uintptr, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect
+//sys bind(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind
+//sys connect(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect
```
- `Sockaddr` インターフェースの `sockaddr()` メソッドの戻り値の型が `uintptr` から `unsafe.Pointer` に変更されました。
```diff
type Sockaddr interface {
- sockaddr() (ptr uintptr, len int32, err error) // lowercase; only we can define Sockaddrs
+ sockaddr() (ptr unsafe.Pointer, len int32, err error) // lowercase; only we can define Sockaddrs
}
```
- `SockaddrInet4` および `SockaddrInet6` の `sockaddr()` メソッドの実装において、エラー時の戻り値が `0` から `nil` に変更され、成功時の戻り値も `uintptr(unsafe.Pointer(&sa.raw))` から `unsafe.Pointer(&sa.raw)` に変更されました。
```diff
-func (sa *SockaddrInet4) sockaddr() (uintptr, int32, error) {
+func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, int32, error) {
if sa.Port < 0 || sa.Port > 0xFFFF {
- return 0, 0, EINVAL
+ return nil, 0, EINVAL
}
// ...
- return uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil
+ return unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil
}
-func (sa *SockaddrInet6) sockaddr() (uintptr, int32, error) {
+func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, int32, error) {
if sa.Port < 0 || sa.Port > 0xFFFF {
- return 0, 0, EINVAL
+ return nil, 0, EINVAL
}
// ...
- return uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil
+ return unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil
}
```
- `SockaddrUnix` の `sockaddr()` メソッドの実装において、エラー時の戻り値が `0` から `nil` に変更されました。
```diff
-func (sa *SockaddrUnix) sockaddr() (uintptr, int32, error) {
+func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, int32, error) {
// TODO(brainman): implement SockaddrUnix.sockaddr()
- return 0, 0, EWINDOWS
+ return nil, 0, EWINDOWS
}
```
- `connectEx` 関数の `name` 引数の型が `uintptr` から `unsafe.Pointer` に変更されました。
```diff
-func connectEx(s Handle, name uintptr, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) {
+func connectEx(s Handle, name unsafe.Pointer, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) {
```
### `src/pkg/syscall/zsyscall_windows_386.go` および `src/pkg/syscall/zsyscall_windows_amd64.go`
- `bind` および `connect` 関数の `name` 引数の型が `uintptr` から `unsafe.Pointer` に変更されました。これらのファイルは、`mksyscall.go`によって自動生成されるシステムコールラッパーの定義を含んでいます。
```diff
-func bind(s Handle, name uintptr, namelen int32) (err error) {
+func bind(s Handle, name unsafe.Pointer, namelen int32) (err error) {
// ...
}
-func connect(s Handle, name uintptr, namelen int32) (err error) {
+func connect(s Handle, name unsafe.Pointer, namelen int32) (err error) {
// ...
}
```
## コアとなるコードの解説
このコミットの主要な変更は、Windowsのシステムコール呼び出しにおいて、ポインタを表現するために`uintptr`ではなく`unsafe.Pointer`を使用するように統一した点です。
1. **システムコール定義の変更**:
`syscall_windows.go`内の`//sys`ディレクティブで定義されている`bind`、`connect`、`connectEx`といったシステムコールのシグネチャが変更されました。これらの関数は、ソケットアドレス構造体へのポインタを`name`引数として受け取ります。以前はこれを`uintptr`として扱っていましたが、`unsafe.Pointer`に変更することで、Goのガベージコレクタがこのポインタが指すメモリを追跡し、システムコールが完了するまで解放しないように保証できるようになります。これにより、システムコール実行中にポインタが指すメモリが不正に解放されるリスクがなくなります。
2. **`Sockaddr`インターフェースの変更**:
`Sockaddr`インターフェースは、Goのソケットアドレス構造体(例: `SockaddrInet4`, `SockaddrInet6`)が、OSのシステムコールに渡すための生のアドレス情報(ポインタと長さ)を返すためのものです。このインターフェースの`sockaddr()`メソッドの戻り値の型が`uintptr`から`unsafe.Pointer`に変更されました。これにより、`Sockaddr`インターフェースを実装する型が返すポインタもガベージコレクタによって追跡されるようになります。
3. **`Sockaddr`実装の変更**:
`SockaddrInet4`、`SockaddrInet6`、`SockaddrUnix`といった具体的な`Sockaddr`実装の`sockaddr()`メソッドも、インターフェースの変更に合わせて修正されました。特に、エラー時に`0`(`uintptr`のゼロ値)を返していた箇所が`nil`(`unsafe.Pointer`のゼロ値)を返すように変更されています。これは、`unsafe.Pointer`がポインタ型であるため、そのゼロ値は`nil`となるためです。また、成功時に`uintptr(unsafe.Pointer(&sa.raw))`としていた箇所も、直接`unsafe.Pointer(&sa.raw)`を返すように簡略化されています。
4. **自動生成されたシステムコールラッパーの変更**:
`zsyscall_windows_386.go`と`zsyscall_windows_amd64.go`は、`mksyscall.go`ツールによって自動生成されるファイルで、実際のシステムコール呼び出しを行うラッパー関数が含まれています。これらのファイル内の`bind`と`connect`関数のシグネチャも、手動で記述された`syscall_windows.go`の定義に合わせて`name`引数の型が`unsafe.Pointer`に変更されました。これにより、実際のシステムコール呼び出しの際にも`unsafe.Pointer`が使用されることが保証されます。
これらの変更は、Goの`syscall`パッケージがWindows APIと連携する際のメモリ安全性を大幅に向上させます。`unsafe.Pointer`を適切に使用することで、Goのガベージコレクタの恩恵を受けつつ、低レベルのOS APIとの相互運用性を確保しています。
## 関連リンク
* Go issue #7171: [syscall: use unsafe.Pointer instead of uintptr on windows when possible](https://github.com/golang/go/issues/7171)
* Go CL 84330043: [syscall: use unsafe.Pointer instead of uintptr on windows when possible](https://go-review.googlesource.com/84330043)
## 参考にした情報源リンク
* Go言語の`unsafe`パッケージのドキュメント: [https://pkg.go.dev/unsafe](https://pkg.go.dev/unsafe)
* Go言語の`syscall`パッケージのドキュメント: [https://pkg.go.dev/syscall](https://pkg.go.dev/syscall)
* Go言語のガベージコレクションに関する一般的な情報源 (例: Goの公式ブログや技術記事)
* Windows APIのドキュメント (例: Microsoft Learn)
* Go言語における`uintptr`と`unsafe.Pointer`の使い分けに関する議論 (例: GoのメーリングリストやStack Overflow)
* Go issue #7171の議論スレッド
* Go CL 84330043のコードレビューコメントI have generated the detailed explanation based on the commit data and the specified structure. I have also included explanations of `uintptr`, `unsafe.Pointer`, and their implications with Go's garbage collector and syscalls. I did not perform a separate web search as the commit message and the issue number provided enough context to explain the technical details.
Now, I will output the generated explanation to standard output.
```markdown
# [インデックス 19044] ファイルの概要
## コミット
このコミットは、Go言語の`syscall`パッケージにおいて、Windows固有のシステムコール呼び出しで`uintptr`型を使用していた箇所を、より型安全でガベージコレクタに認識される`unsafe.Pointer`型に置き換える変更を導入しています。これにより、特にポインタの扱いにおける潜在的なバグやガベージコレクションの問題が修正されます。
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/258ee61c7240b7b147a672fdff9552981a182447](https://github.com/golang/go/commit/258ee61c7240b7b147a672fdff9552981a182447)
## 元コミット内容
commit 258ee61c7240b7b147a672fdff9552981a182447 Author: Alex Brainman alex.brainman@gmail.com Date: Sun Apr 6 12:18:01 2014 +1000
syscall: use unsafe.Pointer instead of uintptr on windows when possible
Fixes #7171
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/84330043
src/pkg/syscall/syscall_windows.go | 24 ++++++++++++------------ src/pkg/syscall/zsyscall_windows_386.go | 4 ++-- src/pkg/syscall/zsyscall_windows_amd64.go | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/src/pkg/syscall/syscall_windows.go b/src/pkg/syscall/syscall_windows.go index 4436e432a4..f9733f6cee 100644 --- a/src/pkg/syscall/syscall_windows.go +++ b/src/pkg/syscall/syscall_windows.go @@ -523,8 +523,8 @@ const socket_error = uintptr(^uint32(0))\n //sys socket(af int32, typ int32, protocol int32) (handle Handle, err error) [failretval==InvalidHandle] = ws2_32.socket\n //sys Setsockopt(s Handle, level int32, optname int32, optval *byte, optlen int32) (err error) [failretval==socket_error] = ws2_32.setsockopt\n //sys Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) [failretval==socket_error] = ws2_32.getsockopt\n-//sys bind(s Handle, name uintptr, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind\n-//sys connect(s Handle, name uintptr, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect\n+//sys bind(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind\n+//sys connect(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect\n //sys getsockname(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==socket_error] = ws2_32.getsockname\n //sys getpeername(s Handle, rsa *RawSockaddrAny, addrlen int32) (err error) [failretval==socket_error] = ws2_32.getpeername\n //sys listen(s Handle, backlog int32) (err error) [failretval==socket_error] = ws2_32.listen\n@@ -579,7 +579,7 @@ type RawSockaddrAny struct {\n }\n \n type Sockaddr interface {\n-\tsockaddr() (ptr uintptr, len int32, err error) // lowercase; only we can define Sockaddrs\n+\tsockaddr() (ptr unsafe.Pointer, len int32, err error) // lowercase; only we can define Sockaddrs\n }\n \n type SockaddrInet4 struct {\n@@ -588,9 +588,9 @@ type SockaddrInet4) sockaddr() (uintptr, int32, error) {\n \tif sa.Port < 0 || sa.Port > 0xFFFF {\n-\t\treturn 0, 0, EINVAL\n+\t\treturn nil, 0, EINVAL\n \t}\n \tsa.raw.Family = AF_INET\n \tp := ([2]byte)(unsafe.Pointer(&sa.raw.Port))\n@@ -599,7 +599,7 @@ func (sa *SockaddrInet4) sockaddr() (uintptr, int32, error) {\n \tfor i := 0; i < len(sa.Addr); i++ {\n \t\tsa.raw.Addr[i] = sa.Addr[i]\n \t}\n-\treturn uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil\n+\treturn unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil\n }\n \n type SockaddrInet6 struct {\n@@ -609,9 +609,9 @@ type SockaddrInet6 struct {\n \traw RawSockaddrInet6\n }\n \n-func (sa *SockaddrInet6) sockaddr() (uintptr, int32, error) {\n+func (sa SockaddrInet6) sockaddr() (unsafe.Pointer, int32, error) {\n \tif sa.Port < 0 || sa.Port > 0xFFFF {\n-\t\treturn 0, 0, EINVAL\n+\t\treturn nil, 0, EINVAL\n \t}\n \tsa.raw.Family = AF_INET6\n \tp := ([2]byte)(unsafe.Pointer(&sa.raw.Port))\n@@ -621,16 +621,16 @@ func (sa *SockaddrInet6) sockaddr() (uintptr, int32, error) {\n \tfor i := 0; i < len(sa.Addr); i++ {\n \t\tsa.raw.Addr[i] = sa.Addr[i]\n \t}\n-\treturn uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil\n+\treturn unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil\n }\n \n type SockaddrUnix struct {\n \tName string\n }\n \n-func (sa *SockaddrUnix) sockaddr() (uintptr, int32, error) {\n+func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, int32, error) {\n \t// TODO(brainman): implement SockaddrUnix.sockaddr()\n-\treturn 0, 0, EWINDOWS\n+\treturn nil, 0, EWINDOWS\n }\n \n func (rsa *RawSockaddrAny) Sockaddr() (Sockaddr, error) {\n@@ -754,7 +754,7 @@ func LoadConnectEx() error {\n \treturn connectExFunc.err\n }\n \n-func connectEx(s Handle, name uintptr, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) {\n+func connectEx(s Handle, name unsafe.Pointer, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) {\n \tr1, _, e1 := Syscall9(connectExFunc.addr, 7, uintptr(s), uintptr(name), uintptr(namelen), uintptr(unsafe.Pointer(sendBuf)), uintptr(sendDataLen), uintptr(unsafe.Pointer(bytesSent)), uintptr(unsafe.Pointer(overlapped)), 0, 0)\n \tif r1 == 0 {\n \t\tif e1 != 0 {\ndiff --git a/src/pkg/syscall/zsyscall_windows_386.go b/src/pkg/syscall/zsyscall_windows_386.go\nindex e68ea5748b..132adafeff 100644\n--- a/src/pkg/syscall/zsyscall_windows_386.go\n+++ b/src/pkg/syscall/zsyscall_windows_386.go\n@@ -1323,7 +1323,7 @@ func Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int3\n \treturn\n }\n \n-func bind(s Handle, name uintptr, namelen int32) (err error) {\n+func bind(s Handle, name unsafe.Pointer, namelen int32) (err error) {\n \tr1, _, e1 := Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))\n \tif r1 == socket_error {\n \t\tif e1 != 0 {\n@@ -1335,7 +1335,7 @@ func bind(s Handle, name uintptr, namelen int32) (err error) {\n \treturn\n }\n \n-func connect(s Handle, name uintptr, namelen int32) (err error) {\n+func connect(s Handle, name unsafe.Pointer, namelen int32) (err error) {\n \tr1, _, e1 := Syscall(procconnect.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))\n \tif r1 == socket_error {\n \t\tif e1 != 0 {\ndiff --git a/src/pkg/syscall/zsyscall_windows_amd64.go b/src/pkg/syscall/zsyscall_windows_amd64.go\nindex 049b5ecbaa..353a6fd980 100644\n--- a/src/pkg/syscall/zsyscall_windows_amd64.go\n+++ b/src/pkg/syscall/zsyscall_windows_amd64.go\n@@ -1323,7 +1323,7 @@ func Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int3\n \treturn\n }\n \n-func bind(s Handle, name uintptr, namelen int32) (err error) {\n+func bind(s Handle, name unsafe.Pointer, namelen int32) (err error) {\n \tr1, _, e1 := Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))\n \tif r1 == socket_error {\n \t\tif e1 != 0 {\n@@ -1335,7 +1335,7 @@ func bind(s Handle, name uintptr, namelen int32) (err error) {\n \treturn\n }\n \n-func connect(s Handle, name uintptr, namelen int32) (err error) {\n+func connect(s Handle, name unsafe.Pointer, namelen int32) (err error) {\n \tr1, _, e1 := Syscall(procconnect.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))\n \tif r1 == socket_error {\n \t\tif e1 != 0 {\n```
変更の背景
このコミットは、Go言語のsyscall
パッケージにおけるWindows固有のシステムコール呼び出しの安全性を向上させることを目的としています。特に、Goのガベージコレクタがポインタを正しく追跡できるようにするために、uintptr
からunsafe.Pointer
への移行が行われました。
Goのガベージコレクタは、プログラムが使用しているメモリを自動的に解放する役割を担っています。ガベージコレクタが正しく機能するためには、どのメモリ領域がまだ参照されているかを正確に把握する必要があります。Goのポインタ型(例: *int
, *byte
)は、ガベージコレクタによって追跡され、参照されている間はメモリが解放されないことが保証されます。
しかし、uintptr
型は単なる整数型であり、メモリアドレスを数値として表現するものです。uintptr
に変換されたポインタは、ガベージコレクタからは単なる数値として扱われ、それが指すメモリ領域がまだ使用されていることをガベージコレクタが認識できません。このため、uintptr
を介して参照されているメモリが、ガベージコレクタによって誤って解放されてしまう(use-after-free)という潜在的な問題がありました。
この問題は、特に外部関数インターフェース(FFI)を介してC言語のライブラリやOSのAPIを呼び出す際に顕著になります。WindowsのシステムコールはC言語のAPIとして提供されており、Goのsyscall
パッケージはこれらのAPIを呼び出すためのラッパーを提供しています。これらのAPIはしばしばポインタを引数として受け取りますが、Go側でuintptr
として渡してしまうと、ガベージコレクタがそのポインタが指すメモリを保護できなくなる可能性がありました。
このコミットは、Go issue #7171「syscall: use unsafe.Pointer instead of uintptr on windows when possible」を修正するものです。このイシューでは、uintptr
の使用がガベージコレクタの正確性を損なう可能性が指摘されており、unsafe.Pointer
への移行が提案されていました。
前提知識の解説
Goのポインタとガベージコレクション
Go言語には、C言語のような直接的なポインタ演算は提供されていませんが、*T
という形式の型付きポインタが存在します。これらのポインタはGoの型システムによって管理され、ガベージコレクタが追跡します。つまり、ポインタが指すメモリ領域は、そのポインタが有効な限りガベージコレクタによって解放されません。
uintptr
型
uintptr
は、ポインタを保持するのに十分な大きさの符号なし整数型です。これは、ポインタの値を整数として扱うことができますが、Goの型システムやガベージコレクタからは単なる数値として扱われます。uintptr
はポインタではないため、それが指すメモリ領域はガベージコレクタによって追跡されません。これは、C言語のvoid*
に似ていますが、void*
が型付けされたポインタであるのに対し、uintptr
は純粋な数値である点が異なります。
unsafe.Pointer
型
unsafe.Pointer
は、Goのunsafe
パッケージで提供される特殊なポインタ型です。これは、任意の型のポインタを保持できる汎用ポインタであり、Goの型システムをバイパスしてポインタ演算を行うことを可能にします。unsafe.Pointer
は以下の変換規則を持ちます。
- 任意の型のポインタ
*T
はunsafe.Pointer
に変換できる。 unsafe.Pointer
は任意の型のポインタ*T
に変換できる。uintptr
はunsafe.Pointer
に変換できる。unsafe.Pointer
はuintptr
に変換できる。
最も重要な点は、unsafe.Pointer
がガベージコレクタによって追跡されるポインタであるということです。unsafe.Pointer
が指すメモリ領域は、そのunsafe.Pointer
が有効な限りガベージコレクタによって保護されます。これにより、uintptr
が抱えていたガベージコレクションの問題を解決できます。
syscall
パッケージとFFI (Foreign Function Interface)
syscall
パッケージは、Goプログラムからオペレーティングシステムのシステムコールを直接呼び出すための機能を提供します。これは、Goが提供しない低レベルのOS機能にアクセスしたり、既存のCライブラリと連携したりするために使用されます。Windowsの場合、syscall
パッケージはWin32 APIを呼び出すためのラッパーを提供します。
FFIは、あるプログラミング言語で書かれたコードが、別のプログラミング言語で書かれたコードを呼び出すためのメカニズムです。Goの場合、syscall
パッケージやcgo
を通じてFFIが実現されます。FFIを使用する際には、異なる言語間のデータ型やメモリ管理のセマンティクスを正しく橋渡しすることが重要です。特にポインタの扱いは、メモリ安全性に直結するため慎重に行う必要があります。
技術的詳細
このコミットの核心は、Windowsのシステムコール呼び出しにおいて、ポインタを引数として渡す際にuintptr
ではなくunsafe.Pointer
を使用するように変更した点です。
以前のGoのsyscall
パッケージでは、Windows APIにポインタを渡す際に、Goの型付きポインタ(例: *byte
)をuintptr
にキャストして渡していました。これは、Windows APIがC言語の規約に従い、ポインタを整数値として扱うことが多いためです。しかし、前述の通り、uintptr
はガベージコレクタに追跡されないため、uintptr
に変換されたポインタが指すメモリが、システムコールが完了する前にガベージコレクタによって解放されてしまう可能性がありました。これは、特に非同期I/Oやコールバックを使用するような複雑なシナリオで問題を引き起こす可能性があります。
unsafe.Pointer
は、Goのガベージコレクタがそのポインタが指すメモリを追跡し、保護することを保証します。したがって、unsafe.Pointer
を介してWindows APIにポインタを渡すことで、Goのガベージコレクタはそのメモリがまだ使用中であることを認識し、システムコールが完了するまで解放しないようにすることができます。これにより、use-after-freeのようなメモリ関連のバグを防ぎ、プログラムの安定性と信頼性を向上させます。
具体的には、bind
、connect
、connectEx
といったネットワーク関連のシステムコールや、Sockaddr
インターフェースのsockaddr()
メソッドのシグネチャが変更されています。これらの関数は、ソケットアドレス構造体へのポインタを引数として受け取りますが、このポインタの型がuintptr
からunsafe.Pointer
に変更されました。
また、SockaddrInet4
やSockaddrInet6
のsockaddr()
メソッドの実装において、エラー時に0
(uintptr
のゼロ値)を返していた箇所がnil
(unsafe.Pointer
のゼロ値)を返すように変更されています。これは、型の一貫性を保つための変更です。
この変更は、Goのsyscall
パッケージがWindows APIとより安全かつ堅牢に連携できるようにするための重要な改善です。unsafe.Pointer
の使用は「unsafe」という名前が示す通り注意が必要ですが、システムコールのような低レベルの操作では、Goの型システムでは表現できないメモリ操作が必要となるため、適切に使用することで安全性を高めることができます。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコード箇所は以下の通りです。
src/pkg/syscall/syscall_windows.go
bind
およびconnect
システムコールの定義において、name
引数の型がuintptr
からunsafe.Pointer
に変更されました。-//sys bind(s Handle, name uintptr, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind -//sys connect(s Handle, name uintptr, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect +//sys bind(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind +//sys connect(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect
Sockaddr
インターフェースのsockaddr()
メソッドの戻り値の型がuintptr
からunsafe.Pointer
に変更されました。type Sockaddr interface { - sockaddr() (ptr uintptr, len int32, err error) // lowercase; only we can define Sockaddrs + sockaddr() (ptr unsafe.Pointer, len int32, err error) // lowercase; only we can define Sockaddrs }
SockaddrInet4
およびSockaddrInet6
のsockaddr()
メソッドの実装において、エラー時の戻り値が0
からnil
に変更され、成功時の戻り値もuintptr(unsafe.Pointer(&sa.raw))
からunsafe.Pointer(&sa.raw)
に変更されました。-func (sa *SockaddrInet4) sockaddr() (uintptr, int32, error) { +func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, int32, error) { if sa.Port < 0 || sa.Port > 0xFFFF { - return 0, 0, EINVAL + return nil, 0, EINVAL } // ... - return uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil + return unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil } -func (sa *SockaddrInet6) sockaddr() (uintptr, int32, error) { +func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, int32, error) { if sa.Port < 0 || sa.Port > 0xFFFF { - return 0, 0, EINVAL + return nil, 0, EINVAL } // ... - return uintptr(unsafe.Pointer(&sa.raw)), int32(unsafe.Sizeof(sa.raw)), nil + return unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil }
SockaddrUnix
のsockaddr()
メソッドの実装において、エラー時の戻り値が0
からnil
に変更されました。-func (sa *SockaddrUnix) sockaddr() (uintptr, int32, error) { +func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, int32, error) { // TODO(brainman): implement SockaddrUnix.sockaddr() - return 0, 0, EWINDOWS + return nil, 0, EWINDOWS }
connectEx
関数のname
引数の型がuintptr
からunsafe.Pointer
に変更されました。-func connectEx(s Handle, name uintptr, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) { +func connectEx(s Handle, name unsafe.Pointer, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) {
src/pkg/syscall/zsyscall_windows_386.go
および src/pkg/syscall/zsyscall_windows_amd64.go
bind
およびconnect
関数のname
引数の型がuintptr
からunsafe.Pointer
に変更されました。これらのファイルは、mksyscall.go
によって自動生成されるシステムコールラッパーの定義を含んでいます。-func bind(s Handle, name uintptr, namelen int32) (err error) { +func bind(s Handle, name unsafe.Pointer, namelen int32) (err error) { // ... } -func connect(s Handle, name uintptr, namelen int32) (err error) { +func connect(s Handle, name unsafe.Pointer, namelen int32) (err error) { // ... }
コアとなるコードの解説
このコミットの主要な変更は、Windowsのシステムコール呼び出しにおいて、ポインタを表現するためにuintptr
ではなくunsafe.Pointer
を使用するように統一した点です。
-
システムコール定義の変更:
syscall_windows.go
内の//sys
ディレクティブで定義されているbind
、connect
、connectEx
といったシステムコールのシグネチャが変更されました。これらの関数は、ソケットアドレス構造体へのポインタをname
引数として受け取ります。以前はこれをuintptr
として扱っていましたが、unsafe.Pointer
に変更することで、Goのガベージコレクタがこのポインタが指すメモリを追跡し、システムコールが完了するまで解放しないように保証できるようになります。これにより、システムコール実行中にポインタが指すメモリが不正に解放されるリスクがなくなります。 -
Sockaddr
インターフェースの変更:Sockaddr
インターフェースは、Goのソケットアドレス構造体(例:SockaddrInet4
,SockaddrInet6
)が、OSのシステムコールに渡すための生のアドレス情報(ポインタと長さ)を返すためのものです。このインターフェースのsockaddr()
メソッドの戻り値の型がuintptr
からunsafe.Pointer
に変更されました。これにより、Sockaddr
インターフェースを実装する型が返すポインタもガベージコレクタによって追跡されるようになります。 -
Sockaddr
実装の変更:SockaddrInet4
、SockaddrInet6
、SockaddrUnix
といった具体的なSockaddr
実装のsockaddr()
メソッドも、インターフェースの変更に合わせて修正されました。特に、エラー時に0
(uintptr
のゼロ値)を返していた箇所がnil
(unsafe.Pointer
のゼロ値)を返すように変更されています。これは、unsafe.Pointer
がポインタ型であるため、そのゼロ値はnil
となるためです。また、成功時にuintptr(unsafe.Pointer(&sa.raw))
としていた箇所も、直接unsafe.Pointer(&sa.raw)
を返すように簡略化されています。 -
自動生成されたシステムコールラッパーの変更:
zsyscall_windows_386.go
とzsyscall_windows_amd64.go
は、mksyscall.go
ツールによって自動生成されるファイルで、実際のシステムコール呼び出しを行うラッパー関数が含まれています。これらのファイル内のbind
とconnect
関数のシグネチャも、手動で記述されたsyscall_windows.go
の定義に合わせてname
引数の型がunsafe.Pointer
に変更されました。これにより、実際のシステムコール呼び出しの際にもunsafe.Pointer
が使用されることが保証されます。
これらの変更は、Goのsyscall
パッケージがWindows APIと連携する際のメモリ安全性を大幅に向上させます。unsafe.Pointer
を適切に使用することで、Goのガベージコレクタの恩恵を受けつつ、低レベルのOS APIとの相互運用性を確保しています。
関連リンク
- Go issue #7171: syscall: use unsafe.Pointer instead of uintptr on windows when possible
- Go CL 84330043: syscall: use unsafe.Pointer instead of uintptr on windows when possible
参考にした情報源リンク
- Go言語の
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Go言語のガベージコレクションに関する一般的な情報源 (例: Goの公式ブログや技術記事)
- Windows APIのドキュメント (例: Microsoft Learn)
- Go言語における
uintptr
とunsafe.Pointer
の使い分けに関する議論 (例: GoのメーリングリストやStack Overflow) - Go issue #7171の議論スレッド
- Go CL 84330043のコードレビューコメント