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

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

このコミットは、src/pkg/net/sock.go ファイルにおける意図しないエラー変数のシャドウイング(変数隠蔽)の問題を修正します。具体的には、socket 関数内でsyscall.Socket および syscall.Bind の呼び出しから返されるエラー変数が、関数の戻り値として定義されている err 変数をシャドウイングしていた問題を解決しています。

コミット

commit a5aa4d3307ccc557127d333b7b084b52d5097979
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Tue Jan 17 10:59:39 2012 +0900

    net: fix unintentional error variable shadowing

    R=rsc
    CC=golang-dev
    https://golang.org/cl/5543065

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

https://github.com/golang/go/commit/a5aa4d3307ccc557127d333b7b084b52d5097979

元コミット内容

net: fix unintentional error variable shadowing

R=rsc
CC=golang-dev
https://golang.org/cl/5543065

変更の背景

この変更の背景には、Go言語における変数のシャドウイングという一般的なプログラミング上の落とし穴があります。特にエラーハンドリングにおいて、このシャドウイングが発生すると、関数がエラーを返すべき状況で nil を返してしまうという、非常に発見しにくいバグを引き起こす可能性があります。

src/pkg/net/sock.gosocket 関数では、関数のシグネチャで (fd *netFD, err error) とエラー変数 err が定義されていました。しかし、関数内部で s, e := syscall.Socket(...)e = syscall.Bind(...) のように、短い変数宣言 := を使用して e という新しいエラー変数を宣言・代入していました。これにより、外側のスコープで定義された err 変数ではなく、内側のスコープで新しく宣言された e 変数にエラーが代入されていました。結果として、syscall.Socketsyscall.Bind がエラーを返しても、関数の最終的な戻り値である errnil のままであり、呼び出し元がエラーを適切に処理できない状態になっていました。

このコミットは、この意図しないシャドウイングを修正し、エラーが常に正しい err 変数に代入されるようにすることで、ネットワーク操作の信頼性を向上させています。

前提知識の解説

Go言語におけるエラーハンドリング

Go言語では、エラーは多値戻り値の最後の値として返されるのが一般的です。慣習として、エラー変数は err と命名されます。呼び出し元は、返された errnil でない場合にエラーが発生したと判断し、適切なエラー処理を行います。

result, err := someFunction()
if err != nil {
    // エラー処理
    return nil, err
}
// 正常処理

変数のシャドウイング(Variable Shadowing)

変数のシャドウイングとは、内側のスコープで宣言された変数が、外側のスコープで同じ名前を持つ変数を「隠す」現象を指します。Go言語では、短い変数宣言 := を使用する際に、このシャドウイングが意図せず発生することがあります。

例えば、以下のコードを見てください。

package main

import "fmt"

func main() {
    err := fmt.Errorf("outer error") // 外側のerrを宣言

    if true {
        err, _ := fmt.Errorf("inner error"), 1 // 新しいerrを宣言(シャドウイング)
        fmt.Println("Inner err:", err)
    }

    fmt.Println("Outer err:", err) // ここではouter errorが出力される
}

この例では、if ブロック内で err, _ := ... とすることで、新しい err 変数が宣言され、外側の err は変更されません。これが意図しない動作につながることがあります。

syscall パッケージ

syscall パッケージは、Goプログラムからオペレーティングシステムのシステムコールを直接呼び出すための低レベルなインターフェースを提供します。ネットワーク操作(ソケットの作成、バインド、接続など)は、通常、OSのシステムコールを介して行われるため、net パッケージの内部では syscall パッケージが利用されています。

  • syscall.Socket(domain, typ, proto int): 指定されたドメイン(例: syscall.AF_INET)、タイプ(例: syscall.SOCK_STREAM)、プロトコル(例: syscall.IPPROTO_TCP)で新しいソケットを作成します。成功するとソケットのファイルディスクリプタとエラーを返します。
  • syscall.Bind(fd int, sa syscall.Sockaddr): 指定されたソケットファイルディスクリプタ fd を、ローカルアドレス sa にバインドします。成功すると nil、失敗するとエラーを返します。

netFD 構造体

netFD は、Goの net パッケージ内部でネットワークファイルディスクリプタ(ソケット)を抽象化するために使用される構造体です。これは、OSレベルのソケットとGoのI/Oモデルとの間の橋渡しをします。

技術的詳細

このコミットが修正している問題は、Go言語の短い変数宣言 := の挙動と、関数の戻り値としてのエラー変数の扱いに起因します。

src/pkg/net/sock.gosocket 関数は、以下のようなシグネチャを持っていました。

func socket(net string, f, p, t int, la, ra syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
    // ...
}

ここで、err error は関数の戻り値として名前付きで宣言されており、関数内で err = ... のように代入することで、その値が呼び出し元に返されることを期待しています。

しかし、元のコードでは以下のような行がありました。

s, e := syscall.Socket(f, p, t) // ここで新しい変数 'e' が宣言される
if e != nil {
    // ...
    return nil, e // ここで 'e' が返されるが、これは外側の 'err' とは別の変数
}
// ...
e = syscall.Bind(s, la) // ここでも 'e' に代入
if e != nil {
    // ...
    return nil, e // ここでも 'e' が返される
}

このコードでは、syscall.Socket の呼び出しで s, e := ... と短い変数宣言を使用しています。Goのルールでは、:= は左辺の少なくとも1つの変数が新しい変数である場合にのみ新しい変数を宣言します。この場合、e は新しい変数として宣言され、関数のシグネチャで定義された err とは異なる、局所的な e 変数が作成されていました。

その結果、syscall.Socketsyscall.Bind がエラーを返しても、そのエラーは局所的な e に格納され、関数の戻り値として期待される err 変数には何も代入されませんでした。if e != nil のチェックは正しく機能しますが、エラーが発生した場合に return nil, e とすることで、局所的な e の値が返されます。しかし、もし enil で、その後の処理で err が使われる場合、err は初期値の nil のままとなり、エラーが適切に伝播しない可能性がありました。

このコミットは、eerr に変更し、:= ではなく = を使用することで、常に外側のスコープで宣言された err 変数にエラーが代入されるように修正しています。これにより、エラーが正しく伝播し、呼び出し元が期待通りにエラーを処理できるようになります。

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

src/pkg/net/sock.go ファイルの以下の部分が変更されました。

--- a/src/pkg/net/sock.go
+++ b/src/pkg/net/sock.go
@@ -20,7 +20,7 @@ var listenerBacklog = maxListenerBacklog()
 func socket(net string, f, p, t int, la, ra syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
  	// See ../syscall/exec.go for description of ForkLock.
  	syscall.ForkLock.RLock()
-	s, e := syscall.Socket(f, p, t)
+	s, err := syscall.Socket(f, p, t)
  	if err != nil {
  		syscall.ForkLock.RUnlock()
  		return nil, err
@@ -31,10 +31,10 @@ func socket(net string, f, p, t int, la, ra syscall.Sockaddr, toAddr func(syscal
  	setDefaultSockopts(s, f, p)
  
  	if la != nil {
-		e = syscall.Bind(s, la)
-		if e != nil {
+		err = syscall.Bind(s, la)
+		if err != nil {
  			closesocket(s)
-			return nil, e
+			return nil, err
  		}
  	}
  

コアとなるコードの解説

変更は主に2箇所です。

  1. s, e := syscall.Socket(f, p, t) から s, err := syscall.Socket(f, p, t) への変更:

    • 元のコードでは、syscall.Socket の戻り値を受け取る際に e という新しい変数を短い変数宣言 := で宣言していました。これにより、関数の戻り値として定義されている err 変数とは別の、局所的な e 変数が作成され、err がシャドウイングされていました。
    • 修正後のコードでは、eerr に変更しています。この行では s が新しい変数であるため、err は既存の関数戻り値の err 変数に代入されます。これにより、syscall.Socket が返すエラーが正しく関数の err 戻り値に格納されるようになります。
  2. e = syscall.Bind(s, la) から err = syscall.Bind(s, la) への変更:

    • 同様に、syscall.Bind の戻り値を受け取る際も、元のコードでは局所的な e 変数に代入していました。
    • 修正後のコードでは、err = とすることで、関数の戻り値として定義されている err 変数に直接エラーが代入されるようになります。

これらの変更により、socket 関数内で発生したエラーが、関数の戻り値として定義された err 変数に確実に代入されるようになり、呼び出し元がエラーを適切にハンドリングできるようになります。これは、Go言語におけるエラーハンドリングのベストプラクティスに沿った修正であり、意図しないバグの発生を防ぎます。

関連リンク

参考にした情報源リンク

  • Go言語の変数シャドウイングに関する一般的な情報
  • Go言語のエラーハンドリングの慣習とベストプラクティス
  • Go言語の syscall パッケージのドキュメント
  • Go言語の net パッケージのソースコード (src/pkg/net/sock.go の変更前後の比較)