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

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

このコミットは、Go言語の標準ライブラリである net パッケージにおける、特定のプロトコル(IP、UDP、Unixドメインソケット)の WriteTo および WriteMsg メソッドが、無効なアドレスが指定された場合にパニック(クラッシュ)するのではなく、適切なエラーを返すように修正するものです。これにより、堅牢性が向上し、予期せぬプログラム終了を防ぎます。

コミット

commit 7917b88a06486bfc692ccd07bbdb27fc73e4140d
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sun Aug 18 19:19:36 2013 +0900

    net: make protocol-specific WriteTo, WriteMsg methods return error instead of crash
    
    R=golang-dev, dave, rsc, adg, bradfitz
    CC=golang-dev
    https://golang.org/cl/11809043

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

https://github.com/golang/go/commit/7917b88a06486bfc692ccd07bbdb27fc73e4140d

元コミット内容

net: make protocol-specific WriteTo, WriteMsg methods return error instead of crash

このコミットの目的は、net パッケージ内のプロトコル固有の WriteTo および WriteMsg メソッドが、無効なアドレス(具体的には nil アドレス)を受け取った際に、プログラムがパニックしてクラッシュするのではなく、エラーを返すように変更することです。

変更の背景

Go言語の設計哲学の一つに「エラーは明示的に処理されるべきであり、パニックは予期せぬ、回復不可能なエラーのために予約されるべきである」というものがあります。しかし、このコミット以前の net パッケージの一部の WriteTo および WriteMsg メソッドでは、送信先アドレスが nil の場合に、内部で nil ポインタ参照などが発生し、プログラムがパニックする可能性がありました。

ネットワーク通信において、送信先アドレスが nil であることは、プログラマのミスである可能性が高いですが、それが即座にプログラム全体のクラッシュにつながるのは望ましくありません。より堅牢なアプリケーションを構築するためには、このような不正な入力に対しても、パニックではなく、適切なエラーを返して呼び出し元に処理を委ねるべきです。これにより、アプリケーションはエラーを捕捉し、ログを記録したり、代替の処理を実行したり、ユーザーにエラーを通知したりといった、より適切な回復処理を行うことができるようになります。

この変更は、Goの標準ライブラリ全体でエラーハンドリングの一貫性を保ち、開発者がより予測可能で信頼性の高いネットワークアプリケーションを構築できるようにすることを目的としています。

前提知識の解説

  • net パッケージ: Go言語の標準ライブラリで、ネットワークI/O機能を提供します。TCP/IP、UDP、Unixドメインソケットなどのプロトコルをサポートし、ネットワーク接続の確立、データの送受信、アドレス解決などを行います。
  • WriteTo メソッド: ネットワーク接続において、指定されたアドレスにデータを書き込むためのメソッドです。例えば、UDPソケットでは、WriteToUDP(b []byte, addr *UDPAddr) のように、データと送信先UDPアドレスを引数に取ります。
  • WriteMsg メソッド: WriteTo と同様にデータを書き込むメソッドですが、通常はデータ本体(b)に加えて、補助データ(oob、Out-of-Band data)や制御メッセージも送信できる、より低レベルなインターフェースを提供します。
  • panicrecover:
    • panic: Go言語における実行時エラーの一種で、プログラムの通常の実行フローを中断させます。パニックが発生すると、現在のゴルーチンは実行を停止し、遅延関数(defer)が実行され、その後、呼び出しスタックを遡ってパニックが伝播します。捕捉されないパニックはプログラム全体をクラッシュさせます。
    • recover: defer 関数内で呼び出すことで、パニックから回復し、パニックの値を捕捉することができます。これにより、プログラムのクラッシュを防ぎ、エラーハンドリングを行うことが可能になります。ただし、recover は非常に特殊な状況(例えば、予期せぬプログラマのバグから回復する、または特定のライブラリの内部エラーを捕捉する)でのみ使用されるべきであり、通常のエラーハンドリングには error インターフェースを使用します。
  • syscall.EINVAL: Unix系システムコールでよく使われるエラーコードで、「無効な引数(Invalid argument)」を意味します。Goの syscall パッケージを通じてアクセスできます。
  • net.OpError: net パッケージで定義されているエラー型で、ネットワーク操作中に発生したエラーに関する詳細情報(操作の種類、ネットワークの種類、アドレス、元のエラーなど)を提供します。これにより、エラーの原因をより詳細に特定できます。
  • nil ポインタ: Goにおいて、ポインタが何も指していない状態を表します。nil ポインタをデリファレンス(参照解除)しようとすると、通常はランタイムパニックが発生します。

技術的詳細

このコミットの主要な変更点は、net パッケージ内の IPConnUDPConnUnixConn 型の WriteTo および WriteMsg メソッドにおいて、引数として渡されるアドレスが nil である場合に、パニックを引き起こす前に明示的にエラーを返すようにしたことです。

具体的には、各メソッドの冒頭に以下の形式のチェックが追加されています。

if addr == nil {
    return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress}
}

または、WriteMsg の場合は return 0, 0, ... となります。

  • addr == nil のチェック: 送信先アドレスが nil であるかを検査します。
  • return 0, &OpError{...}: addrnil の場合、書き込まれたバイト数(n)またはバイト数とOOBバイト数(n, oobn)を 0 とし、net.OpError 型のエラーを返します。
  • OpError: OpError は、ネットワーク操作の種類(Op: "write")、ネットワークの種類(Net: c.fd.net または c.fd.dir)、関連するアドレス(Addr: nil)、および根本原因のエラー(Err: errMissingAddress)を含む構造体です。errMissingAddress は、このコミットで導入された、アドレスが不足していることを示す内部エラーです。

この変更により、nil アドレスが渡された場合でも、プログラムはパニックすることなく、呼び出し元に OpError を返します。呼び出し元は、このエラーを捕捉して適切に処理することができます。

また、src/pkg/net/protoconn_test.go には、この変更が正しく機能することを確認するためのテストが追加されています。これらのテストでは、WriteTo および WriteMsg メソッドに nil アドレスを渡した場合にパニックが発生しないことを deferrecover を用いて検証しています。

defer func() {
    if p := recover(); p != nil {
        t.Fatalf("UDPConn.WriteToUDP or WriteMsgUDP panicked: %v", p)
    }
}()
c.WriteToUDP(wb, nil)
c.WriteMsgUDP(wb, nil, nil)

このテストコードは、WriteToUDPWriteMsgUDPnil アドレスで呼び出されたときにパニックが発生した場合、t.Fatalf を呼び出してテストを失敗させます。パニックが発生しなければ、テストは成功します。これにより、修正が意図通りに機能し、パニックが抑制されていることが保証されます。

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

以下のファイルが変更されています。

  • src/pkg/net/iprawsock_posix.go
  • src/pkg/net/protoconn_test.go
  • src/pkg/net/udpsock_plan9.go
  • src/pkg/net/udpsock_posix.go
  • src/pkg/net/unixsock_posix.go

主な変更は、WriteTo および WriteMsg メソッドの冒頭に addr == nil のチェックとエラーリターンを追加した点、およびテストファイルにパニック防止のテストケースを追加した点です。

コアとなるコードの解説

src/pkg/net/iprawsock_posix.go の変更

IPConn 型の WriteToIP および WriteMsgIP メソッドに addr == nil のチェックが追加されました。

// IPConn.WriteToIP メソッド
func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) {
    if !c.ok() {
        return 0, syscall.EINVAL
    }
    // 追加されたコード
    if addr == nil {
        return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress}
    }
    // ... 既存のコード ...
}

// IPConn.WriteMsgIP メソッド
func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error) {
    if !c.ok() {
        return 0, 0, syscall.EINVAL
    }
    // 追加されたコード
    if addr == nil {
        return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress}
    }
    // ... 既存のコード ...
}

src/pkg/net/udpsock_plan9.go および src/pkg/net/udpsock_posix.go の変更

UDPConn 型の WriteToUDP および WriteMsgUDP メソッドに addr == nil のチェックが追加されました。

// UDPConn.WriteToUDP メソッド (udpsock_plan9.go の例)
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) {
    if !c.ok() || c.fd.data == nil {
        return 0, syscall.EINVAL
    }
    // 追加されたコード
    if addr == nil {
        return 0, &OpError{Op: "write", Net: c.fd.dir, Addr: nil, Err: errMissingAddress}
    }
    // ... 既存のコード ...
}

// UDPConn.WriteMsgUDP メソッド (udpsock_posix.go の例)
func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) {
    if c.fd.isConnected {
        return 0, 0, &OpError{"write", c.fd.net, addr, ErrWriteToConnected}
    }
    // 追加されたコード
    if addr == nil {
        return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress}
    }
    // ... 既存のコード ...
}

src/pkg/net/unixsock_posix.go の変更

UnixConn 型の WriteToUnix メソッドに addr == nil のチェックが追加されました。

// UnixConn.WriteToUnix メソッド
func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (n int, err error) {
    if !c.ok() {
        return 0, syscall.EINVAL
    }
    // 追加されたコード
    if addr == nil {
        return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress}
    }
    // ... 既存のコード ...
}

src/pkg/net/protoconn_test.go の変更

各接続タイプ(UDPConn, IPConn, UnixConn)のテスト関数に、WriteTo および WriteMsg メソッドに nil アドレスを渡した場合にパニックしないことを確認するテストケースが追加されました。

// TestUDPConnSpecificMethods 内の追加
defer func() {
    if p := recover(); p != nil {
        t.Fatalf("UDPConn.WriteToUDP or WriteMsgUDP panicked: %v", p)
    }
}()
c.WriteToUDP(wb, nil)
c.WriteMsgUDP(wb, nil, nil)

// TestIPConnSpecificMethods 内の追加
defer func() {
    if p := recover(); p != nil {
        t.Fatalf("IPConn.WriteToIP or WriteMsgIP panicked: %v", p)
    }
}()
c.WriteToIP(wb, nil)
c.WriteMsgIP(wb, nil, nil)

// TestUnixConnSpecificMethods 内の追加
defer func() {
    if p := recover(); p != nil {
        t.Fatalf("UnixConn.WriteToUnix or WriteMsgUnix panicked: %v", p)
    }
}()
c1.WriteToUnix(wb, nil)
c1.WriteMsgUnix(wb, nil, nil)
c3.WriteToUnix(wb, nil)
c3.WriteMsgUnix(wb, nil, nil)

これらの変更により、net パッケージの堅牢性が向上し、開発者がより安全にネットワークプログラミングを行えるようになりました。

関連リンク

参考にした情報源リンク