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

[インデックス 13950] ファイルの概要

このコミットは、Go言語のWindows向けsyscallパッケージにおける、ネットワーク関連のシステムコール(Winsock API)の結果判定方法の修正に関するものです。具体的には、システムコールからの戻り値(uintptr型)をint型にキャストして-1と比較していた箇所を、uintptr型として直接比較するように変更し、型安全性を向上させています。これにより、特に64ビット環境での潜在的なバグが修正されました。

コミット

commit 0e6f927108dc785812b6e5ce94cea2ff4ca395cc
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Tue Sep 25 17:06:39 2012 +1000

    syscall: do not use int to test network syscall results (on windows)

    Fixes #4147.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6569050

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/0e6f927108dc785812b6e5ce94cea2ff4ca395cc

元コミット内容

syscall: do not use int to test network syscall results (on windows)

Fixes #4147.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6569050

変更の背景

Go言語のsyscallパッケージは、オペレーティングシステム固有のAPIを呼び出すための低レベルなインターフェースを提供します。Windows環境において、多くのWinAPI関数、特にWinsock(Windows Sockets)関連の関数は、成功時には0以外の値や特定のハンドルを返し、失敗時には-1INVALID_HANDLE_VALUEといった特殊な値を返します。

このコミット以前のGoのsyscallパッケージでは、これらのWinAPI関数の戻り値(Goではuintptr型で受け取る)をエラーチェックする際に、明示的にint型にキャストしてから-1と比較している箇所がありました。

if int(r1) == -1 { ... }

このint(r1)というキャストが問題を引き起こす可能性がありました。uintptrは符号なし整数型であり、ポインタのアドレスを保持できる十分な大きさを持つ型です。一方、intは符号付き整数型であり、そのサイズはシステム(32ビットまたは64ビット)によって異なります。

特に、WinAPI関数が返すエラー値-1は、32ビットの符号なし整数としては0xFFFFFFFFとして表現されます。これをuintptrとして受け取った場合、32ビットシステムでは0xFFFFFFFF、64ビットシステムでは0xFFFFFFFFFFFFFFFFとなります。このuintptrの値をintにキャストすると、システムによっては予期せぬ結果となる可能性がありました。例えば、64ビットシステムでuintptr(0xFFFFFFFFFFFFFFFF)を32ビットのintにキャストすると、値が切り捨てられて0xFFFFFFFFとなり、これが符号付き整数として-1と解釈されるため、一見問題ないように見えます。しかし、これは型変換の暗黙的な挙動に依存しており、コードの意図が不明瞭になるだけでなく、将来的なアーキテクチャ変更やコンパイラの最適化によって予期せぬバグにつながるリスクがありました。

この問題はGoのIssue #4147として報告されており、特にネットワーク関連のシステムコールで顕著でした。Winsock APIでは、SOCKET_ERRORという定数が-1として定義されており、多くの関数が失敗時にこの値を返します。このコミットは、このような型変換の危険性を排除し、より堅牢で移植性の高いエラーチェックメカニズムを導入することを目的としています。

前提知識の解説

Go言語の syscall パッケージ

syscallパッケージは、Goプログラムからオペレーティングシステムが提供する低レベルなシステムコールを直接呼び出すための機能を提供します。これにより、ファイル操作、プロセス管理、ネットワーク通信など、OS固有の機能にアクセスできます。このパッケージは、OSのAPIとGoの間のブリッジとして機能し、Goの標準ライブラリの多くの部分で内部的に利用されています。

Windows API (WinAPI)

Windows APIは、Microsoft Windowsオペレーティングシステムが提供するコア機能へのプログラミングインターフェースの集合です。C言語で記述されており、関数呼び出しを通じてOSの機能を利用します。WinAPI関数は、成功時には0以外の値や特定のハンドルを返し、失敗時には0や-1、あるいはINVALID_HANDLE_VALUEのような特殊な値を返すことが一般的です。エラーの詳細はGetLastError()関数で取得できます。

uintptr

Go言語のuintptr型は、ポインタを保持できるだけの大きさを持つ符号なし整数型です。これは、Goのガベージコレクタが管理するメモリ領域外の、OSが管理するメモリやハンドルを扱う際に特に有用です。uintptrは、ポインタと整数を相互に変換する際に使用されますが、それ自体はポインタではなく、算術演算が可能です。

符号付き整数と符号なし整数

コンピュータにおける整数型には、符号付き(signed)と符号なし(unsigned)があります。

  • 符号付き整数: 正の値と負の値を表現できます。最上位ビットが符号ビットとして使われます(0なら正、1なら負)。負の数は通常、2の補数表現で格納されます。例えば、32ビット符号付き整数で-10xFFFFFFFFとして表現されます。
  • 符号なし整数: 負の値を表現できず、0以上の値のみを表現します。全てのビットが数値の大きさを表すために使われます。例えば、32ビット符号なし整数で0xFFFFFFFFは最大値(約42億)を表します。

これらの型間でキャストを行うと、ビットパターンは同じでも解釈が異なるため、予期せぬ値になることがあります。特に、符号なしの大きな値を符号付きにキャストすると、負の値として解釈されることがあります。

Winsock (Windows Sockets)

Winsockは、Windows上でネットワークアプリケーションを開発するためのAPIです。TCP/IPなどの標準的なネットワークプロトコルを介した通信機能を提供します。Winsock API関数は、成功時には0を返し、失敗時にはSOCKET_ERROR(値は-1)を返すことが一般的です。エラーの詳細はWSAGetLastError()関数で取得します。

mksyscall_windows.pl スクリプト

Go言語のsyscallパッケージには、OS固有のシステムコールをGoの関数としてラップするためのコード生成スクリプトが含まれています。mksyscall_windows.plは、Windows APIの定義からGoのsyscall関数を自動生成するPerlスクリプトです。このスクリプトは、WinAPI関数の戻り値やエラー条件をGoのコードに適切にマッピングする役割を担っています。

技術的詳細

このコミットの核心は、GoのsyscallパッケージがWindows APIの戻り値を扱う際の型安全性の向上です。

  1. uintptrintの比較問題: WinAPI関数は、成功/失敗を示すために様々な値を返します。特に、失敗を示す値として-1がよく使われます。Goのsyscallパッケージでは、これらの戻り値はuintptr型として受け取られます。 従来のコードでは、if int(r1) == -1のように、uintptr型のr1intにキャストしてから-1と比較していました。

    • 32ビットシステムでは、uintptrintも32ビット幅です。uintptr(0xFFFFFFFF)int(-1)にキャストされます。
    • 64ビットシステムでは、uintptrは64ビット幅ですが、intは通常32ビット幅です。uintptr(0xFFFFFFFFFFFFFFFF)(64ビットの-1)をintにキャストすると、上位32ビットが切り捨てられ、結果としてint(0xFFFFFFFF)、つまりint(-1)となります。 一見するとどちらのケースでも-1になるため問題ないように見えますが、これはGoの型システムが意図する厳密な型チェックを迂回するものであり、潜在的なバグや将来的な互換性の問題を引き起こす可能性があります。特に、uintptrが保持する値が-1以外の大きな値であった場合、intへのキャストによって値が変化し、誤ったエラー判定につながる恐れがありました。
  2. socket_error定数の導入: Winsock APIの関数は、失敗時にSOCKET_ERRORという定数を返します。この定数の値は-1です。このコミットでは、このSOCKET_ERRORuintptr型で正確に表現するために、const socket_error = uintptr(^uint32(0))という定数を導入しました。

    • ^uint32(0)は、32ビットの符号なし整数0のビット反転です。これは0xFFFFFFFFとなります。
    • これをuintptrにキャストすることで、socket_errorはシステムアーキテクチャに関わらず、uintptr型で0xFFFFFFFFという値を持つことになります。 これにより、ネットワーク関連のシステムコールが返すuintptr型の戻り値r1を、r1 == socket_errorという形で直接比較できるようになり、intへのキャストが不要になりました。これは、uintptrが符号なしであるという性質を考慮した、より正確で型安全な比較方法です。
  3. mksyscall_windows.plの修正: mksyscall_windows.plスクリプトは、WinAPIの定義からGoのsyscall関数を生成する際に、戻り値のチェックロジックを生成します。このスクリプトが生成するコードからint()キャストを削除することで、自動生成される全てのsyscallラッパー関数が、uintptr型の戻り値を直接比較するようになります。これにより、手動で記述されたコードと自動生成されたコードの間で一貫性が保たれ、将来的な同様の問題の発生を防ぎます。

これらの変更により、GoのWindows向けsyscallパッケージは、WinAPIの戻り値の処理において、より堅牢で正確なエラーハンドリングを実現しました。

コアとなるコードの変更箇所

このコミットによる主要なコード変更は以下のファイルに集中しています。

  1. src/pkg/syscall/mksyscall_windows.pl:

    • $failexpr = "int(r1) $failcond";
    • $failexpr = "r1 $failcond"; この変更は、システムコールラッパーを生成するPerlスクリプトにおいて、戻り値r1intにキャストする処理を削除しています。これにより、生成されるGoコードがuintptrを直接比較するようになります。
  2. src/pkg/syscall/syscall_windows.go:

    • const socket_error = uintptr(^uint32(0)) の追加。
    • ネットワーク関連のシステムコール定義(//sysディレクティブ)において、[failretval==-1][failretval==socket_error]に変更されています。 例:
      • -sys WSACleanup() (err error) [failretval==-1] = ws2_32.WSACleanup
      • +sys WSACleanup() (err error) [failretval==socket_error] = ws2_32.WSACleanup これは、Winsock APIのエラーを示す-1を、uintptr型で正しく表現するsocket_error定数に置き換え、システムコール定義でその定数を使用するように変更しています。
  3. src/pkg/syscall/zsyscall_windows_386.go および src/pkg/syscall/zsyscall_windows_amd64.go: これらのファイルはmksyscall_windows.plによって自動生成されるファイルであり、多くのシステムコールラッパー関数が含まれています。

    • if int(r1) == 0 { ... }if r1 == 0 { ... } に変更。
    • if int(r1) == -1 { ... }if r1 == socket_error { ... } に変更(ネットワーク関連関数)。 これらの変更は、r1uintptr型)をintにキャストする処理を削除し、直接uintptr型として比較するように修正しています。特にネットワーク関連の関数では、新しく定義されたsocket_error定数との比較に置き換えられています。

コアとなるコードの解説

src/pkg/syscall/mksyscall_windows.pl の変更

--- a/src/pkg/syscall/mksyscall_windows.pl
+++ b/src/pkg/syscall/mksyscall_windows.pl
@@ -266,7 +266,7 @@ while(<>) {
 				$failexpr = "!$name";
 			} elsif($name eq "err") {
 				$ret[$i] = "r1";
-				$failexpr = "int(r1) $failcond";
+				$failexpr = "r1 $failcond";
 			} else {
 				$failexpr = "$name $failcond";
 			}

このPerlスクリプトは、GoのsyscallパッケージのWindows固有の関数を生成する際に使用されます。変更前は、システムコールからの戻り値r1intにキャストしてからエラー条件($failcond)と比較するコードを生成していました。この変更により、int()キャストが削除され、生成されるGoコードがr1uintptr型)を直接比較するようになります。これは、Goの型システムに沿った、より正確な比較を保証するための基盤となる変更です。

src/pkg/syscall/syscall_windows.go の変更

--- a/src/pkg/syscall/syscall_windows.go
+++ b/src/pkg/syscall/syscall_windows.go
@@ -468,25 +468,27 @@ func Chmod(path string, mode uint32) (err error) {

 // net api calls

+const socket_error = uintptr(^uint32(0))
+
 //sys	WSAStartup(verreq uint32, data *WSAData) (sockerr error) = ws2_32.WSAStartup
-//sys	WSACleanup() (err error) [failretval==-1] = ws2_32.WSACleanup
-//sys	WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbob uint32, cbbr *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) [failretval==-1] = ws2_32.WSAIoctl
+//sys	WSACleanup() (err error) [failretval==socket_error] = ws2_32.WSACleanup
+//sys	WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbob uint32, cbbr *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) [failretval==socket_error] = ws2_32.WSAIoctl
 //sys	socket(af int32, typ int32, protocol int32) (handle Handle, err error) [failretval==InvalidHandle] = ws2_32.socket
-//sys	Setsockopt(s Handle, level int32, optname int32, optval *byte, optlen int32) (err error) [failretval==-1] = ws2_32.setsockopt
-//sys	Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) [failretval==-1] = ws2_32.getsockopt
-//sys	bind(s Handle, name uintptr, namelen int32) (err error) [failretval==-1] = ws2_32.bind
-//sys	connect(s Handle, name uintptr, namelen int32) (err error) [failretval==-1] = ws2_32.connect
-//sys	getsockname(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==-1] = ws2_32.getsockname
-//sys	getpeername(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==-1] = ws2_32.getpeername
-//sys	listen(s Handle, backlog int32) (err error) [failretval==-1] = ws2_32.listen
-//sys	shutdown(s Handle, how int32) (err error) [failretval==-1] = ws2_32.shutdown
-//sys	Closesocket(s Handle) (err error) [failretval==-1] = ws2_32.closesocket
+//sys	Setsockopt(s Handle, level int32, optname int32, optval *byte, optlen int32) (err error) [failretval==socket_error] = ws2_32.setsockopt
+//sys	Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) [failretval==socket_error] = ws2_32.getsockopt
+//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	getsockname(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==socket_error] = ws2_32.getsockname
+//sys	getpeername(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==socket_error] = ws2_32.getpeername
+//sys	listen(s Handle, backlog int32) (err error) [failretval==socket_error] = ws2_32.listen
+//sys	shutdown(s Handle, how int32) (err error) [failretval==socket_error] = ws2_32.shutdown
+//sys	Closesocket(s Handle) (err error) [failretval==socket_error] = ws2_32.closesocket
 //sys	AcceptEx(ls Handle, as Handle, buf *byte, rxdatalen uint32, laddrlen uint32, raddrlen uint32, recvd *uint32, overlapped *Overlapped) (err error) = mswsock.AcceptEx
 //sys	GetAcceptExSockaddrs(buf *byte, rxdatalen uint32, laddrlen uint32, raddrlen uint32, lrsa **RawSockaddrAny, lrsalen *int32, rrsa **RawSockaddrAny, rrsalen *int32) = mswsock.GetAcceptExSockaddrs
-//sys	WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==-1] = ws2_32.WSARecv
-//sys	WSASend(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==-1] = ws2_32.WSASend
-//sys	WSARecvFrom(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32,  from *RawSockaddrAny, fromlen *int32, overlapped *Overlapped, croutine *byte) (err error) [failretval==-1] = ws2_32.WSARecvFrom
-//sys	WSASendTo(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, to *RawSockaddrAny, tolen int32,  overlapped *Overlapped, croutine *byte) (err error) [failretval==-1] = ws2_32.WSASendTo
+//sys	WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecv
+//sys	WSASend(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSASend
+//sys	WSARecvFrom(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32,  from *RawSockaddrAny, fromlen *int32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecvFrom
+//sys	WSASendTo(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, to *RawSockaddrAny, tolen int32,  overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSASendTo
 //sys	GetHostByName(name string) (h *Hostent, err error) [failretval==nil] = ws2_32.gethostbyname
 //sys	GetServByName(name string, proto string) (s *Servent, err error) [failretval==nil] = ws2_32.getservbyname
 //sys	Ntohs(netshort uint16) (u uint16) = ws2_32.ntohs

このファイルでは、まずsocket_errorという新しい定数が定義されています。uintptr(^uint32(0))は、32ビットの符号なし整数0のビットを反転させた値(0xFFFFFFFF)をuintptr型として表現します。これは、Winsock APIがエラー時に返す-1SOCKET_ERROR)をuintptr型で正確に表現するためのものです。 次に、//sysディレクティブで定義されている多くのネットワーク関連システムコール(WSACleanup, WSAIoctl, Setsockoptなど)のfailretval(失敗時の戻り値)が、これまでの-1から新しく定義されたsocket_errorに変更されています。これにより、これらの関数が返すuintptr型の値がsocket_errorと一致するかどうかでエラーを判定するようになります。

src/pkg/syscall/zsyscall_windows_386.go および src/pkg/syscall/zsyscall_windows_amd64.go の変更

--- a/src/pkg/syscall/zsyscall_windows_386.go
+++ b/src/pkg/syscall/zsyscall_windows_386.go
@@ -176,7 +176,7 @@ func LoadLibrary(libname string) (handle Handle, err error) {

 func FreeLibrary(handle Handle) (err error) {
 	r1, _, e1 := Syscall(procFreeLibrary.Addr(), 1, uintptr(handle), 0, 0)
-	if int(r1) == 0 {
+	if r1 == 0 {
 		if e1 != 0 {
 			err = error(e1)
 		} else {
@@ -1233,7 +1233,7 @@ func WSAStartup(verreq uint32, data *WSAData) (sockerr error) {

 func WSACleanup() (err error) {
 	r1, _, e1 := Syscall(procWSACleanup.Addr(), 0, 0, 0, 0)
-	if int(r1) == -1 {
+	if r1 == socket_error {
 		if e1 != 0 {
 			err = error(e1)
 		} else {

これらのファイルは、mksyscall_windows.plスクリプトによって自動生成されるGoのソースコードです。変更前は、Syscall関数の戻り値r1uintptr型)をintにキャストしてから0-1と比較していました。 このコミットでは、int(r1) == 0という形式がr1 == 0に、そしてネットワーク関連の関数ではint(r1) == -1という形式がr1 == socket_errorにそれぞれ変更されています。 これにより、uintptr型のr1が直接uintptr型の0socket_error定数と比較されるようになります。これは、uintptrが符号なし整数であることを考慮し、型変換による潜在的な問題を回避するための重要な修正です。特に、socket_error定数を使用することで、Winsock APIのSOCKET_ERRORという概念がGoのコード内で明確かつ型安全に扱われるようになります。

これらの変更は、GoのWindows向けsyscallパッケージが、より正確で堅牢なエラーハンドリングを行うための基盤を確立するものです。

関連リンク

参考にした情報源リンク