[インデックス 11464] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージ内の Client
型の Post
メソッドにおける、エラー発生時のnilポインタデリファレンス(nil pointer dereference)のバグを修正するものです。具体的には、send
関数がエラーを返した場合に、http.CookieJar
へのアクセスがnilポインタデリファレンスを引き起こす可能性があった問題を解決しています。
コミット
- コミットハッシュ:
ed7a8f71590bcd704335bab5c07e3164431e43e1
- 作者: Volker Dobler dr.volker.dobler@gmail.com
- コミット日時: 2012年1月30日 月曜日 07:57:50 -0800
- コミットメッセージ:
net/http: Fix nil pointer dereference in error case. R=golang-dev CC=bradfitz, golang-dev https://golang.org/cl/5598044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ed7a8f71590bcd704335bab5c07e3164431e43e1
元コミット内容
net/http: Fix nil pointer dereference in error case.
R=golang-dev
CC=bradfitz, golang-dev
https://golang.org/cl/5598044
変更の背景
この変更は、net/http
パッケージの Client.Post
メソッドにおいて、HTTPリクエストの送信中にエラーが発生した場合に、プログラムがクラッシュする可能性があったバグを修正するために行われました。
具体的には、Client.Post
メソッド内で send
関数が呼び出され、この send
関数がHTTPリクエストの送信処理を行います。send
関数は *Response
と error
の2つの値を返します。通常、エラーが発生しなかった場合は *Response
オブジェクトが返され、エラーが発生した場合は *Response
が nil
となり、error
オブジェクトにエラー情報が格納されます。
問題は、send
関数がエラーを返した場合(つまり r
が nil
の場合)でも、その後のコードで r.Cookies()
が呼び出される可能性があった点です。r
が nil
であるにもかかわらず、そのメソッドである Cookies()
を呼び出そうとすると、Go言語では「nilポインタデリファレンス」というランタイムパニックが発生し、プログラムが異常終了してしまいます。
このバグは、特にネットワークの問題やサーバーからの不正な応答など、HTTPリクエストが正常に完了しないシナリオで顕在化する可能性がありました。安定したHTTPクライアントを提供するためには、このようなエラーケースでの堅牢性が不可欠であり、この修正はそのための重要なステップでした。
前提知識の解説
Go言語の net/http
パッケージ
net/http
パッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための標準ライブラリです。Webアプリケーションの構築や、外部のHTTPサービスとの連携に広く利用されます。
http.Client
: HTTPリクエストを送信するためのクライアントを表す構造体です。タイムアウト設定、リダイレクトポリシー、Cookie管理などをカスタマイズできます。http.Request
: 送信するHTTPリクエストを表す構造体です。URL、メソッド(GET, POSTなど)、ヘッダー、ボディなどの情報を含みます。http.Response
: HTTPリクエストに対するサーバーからの応答を表す構造体です。ステータスコード、ヘッダー、ボディなどの情報を含みます。io.Reader
: データを読み出すためのインターフェースです。Client.Post
メソッドのbody
引数に利用され、リクエストボディのデータソースとして機能します。http.Transport
:http.Client
が実際にHTTPリクエストを送信する際の低レベルな詳細(TCP接続の確立、TLSハンドシェイクなど)を処理するインターフェースです。http.CookieJar
: HTTPクライアントがCookieを保存・管理するためのインターフェースです。これにより、セッション管理などが可能になります。Client
構造体のJar
フィールドに設定することで利用されます。
Go言語のエラーハンドリング
Go言語では、エラーは戻り値として明示的に扱われます。関数は通常、最後の戻り値として error
型の値を返します。エラーが発生しなかった場合は nil
が返され、エラーが発生した場合は nil
ではない error
オブジェクトが返されます。開発者はこの error
の戻り値をチェックし、適切なエラー処理を行う責任があります。
result, err := someFunction()
if err != nil {
// エラー処理
}
// 正常処理
nilポインタデリファレンス (Nil Pointer Dereference)
nilポインタデリファレンスは、プログラミングにおいて、nil
(または null
)値を持つポインタが指すメモリ領域にアクセスしようとしたときに発生するランタイムエラーです。Go言語では、これは「panic」(パニック)として扱われ、プログラムの実行が停止します。
例えば、以下のようなコードで発生します。
var s *MyStruct // s は nil
s.DoSomething() // ここでnilポインタデリファレンスが発生
このコミットのケースでは、send
関数がエラーを返した際に r
が nil
になるにもかかわらず、r.Cookies()
を呼び出そうとしたことが問題でした。
技術的詳細
修正前の Client.Post
メソッドの関連部分は以下のようになっていました。
r, err = send(req, c.Transport)
if c.Jar != nil { // ここで c.Jar がnilでないかだけをチェック
c.Jar.SetCookies(req.URL, r.Cookies()) // r がnilの場合、ここでパニック
}
return r, err
ここで問題となるのは、send(req, c.Transport)
の呼び出しです。この関数は (*Response, error)
のペアを返します。
- 成功ケース:
send
が成功した場合、r
は有効な*Response
オブジェクトを指し、err
はnil
です。この場合、c.Jar != nil
の条件が真であれば、c.Jar.SetCookies(req.URL, r.Cookies())
は問題なく実行されます。 - エラーケース:
send
が失敗した場合、r
はnil
となり、err
は非nil
のエラーオブジェクトを指します。このとき、if c.Jar != nil
の条件はc.Jar
自体がnil
でない限り真となります。しかし、r
はnil
であるため、r.Cookies()
を呼び出そうとすると、nil
ポインタデリファレンスが発生し、プログラムがパニックに陥ります。
この修正は、c.Jar.SetCookies
を呼び出す前に、send
関数がエラーを返していないこと(つまり r
が有効な *Response
オブジェクトであること)を確実にチェックすることで、この脆弱性を解消しています。
コアとなるコードの変更箇所
--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -275,7 +275,7 @@ func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response,
}\n \treq.Header.Set(\"Content-Type\", bodyType)\n \tr, err = send(req, c.Transport)\n-\tif c.Jar != nil {\n+\tif err == nil && c.Jar != nil {\n \t\tc.Jar.SetCookies(req.URL, r.Cookies())\n \t}\n \treturn r, err
コアとなるコードの解説
変更は src/pkg/net/http/client.go
ファイルの Client.Post
メソッド内の一行です。
修正前:
if c.Jar != nil {
c.Jar.SetCookies(req.URL, r.Cookies())
}
修正後:
if err == nil && c.Jar != nil {
c.Jar.SetCookies(req.URL, r.Cookies())
}
この変更の核心は、if
文の条件に err == nil
を追加した点です。
err == nil
: これは、send
関数がエラーを返さずに正常に完了したことを意味します。send
関数が正常に完了した場合にのみ、r
(Responseオブジェクト) は有効なポインタを保持します。c.Jar != nil
: これは、Client
にCookieJar
が設定されているかどうかをチェックします。CookieJar
が設定されていない場合は、Cookieの処理は不要です。
この二つの条件を &&
(論理AND) で結合することで、以下の論理が保証されます。
「send
関数がエラーなく成功し、かつ Client
に CookieJar
が設定されている場合にのみ、c.Jar.SetCookies(req.URL, r.Cookies())
を実行する。」
これにより、send
関数がエラーを返して r
が nil
になった場合でも、r.Cookies()
が呼び出されることがなくなり、nilポインタデリファレンスによるパニックが回避されます。これは、Go言語における堅牢なエラーハンドリングの原則に則った、シンプルかつ効果的な修正です。
関連リンク
- Go CL (Change List) 5598044: https://golang.org/cl/5598044
参考にした情報源リンク
- Go言語
net/http
パッケージ公式ドキュメント: https://pkg.go.dev/net/http - Go言語におけるエラーハンドリングの基本: https://go.dev/blog/error-handling-and-go
- Go言語のポインタとnil: https://go.dev/tour/moretypes/1 (Go Tourのポインタに関するセクション)
- nilポインタデリファレンスに関する一般的な情報 (Go言語に特化しない): https://ja.wikipedia.org/wiki/%E3%83%8C%E3%83%AB%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%87%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9