[インデックス 15718] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/rpc/jsonrpc
パッケージにおける、無効なRPC応答(リプライ)が原因で発生するnilポインタデリファレンス(nil pointer dereference)のバグを修正するものです。具体的には、JSON-RPCクライアントがサーバーからの応答を処理する際に、result
フィールドがnull
である場合に発生する問題を解決し、関連するテストケースを追加しています。
コミット
- コミットハッシュ:
0d559f7b92c1425b9a78ff67933a9baaa0534928
- Author: Adrian Nos nos.adrian@gmail.com
- Date: Tue Mar 12 11:45:56 2013 -0400
- コミットメッセージ:
net/rpc/jsonrpc: nil pointer deference on invalid reply. Fixes #5006. R=golang-dev, rsc CC=golang-dev https://golang.org/cl/7691045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d559f7b92c1425b9a78ff67933a9baaa0534928
元コミット内容
net/rpc/jsonrpc: nil pointer deference on invalid reply.
Fixes #5006.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7691045
変更の背景
この変更は、Go言語のIssue #5006「net/rpc/jsonrpc
: nil pointer dereference on invalid reply」を修正するために行われました。このバグは、net/rpc/jsonrpc
クライアントがJSON-RPCサーバーからの応答を処理する際に、特定の無効な応答形式(特にresult
フィールドがnull
である場合)を受け取ると、nilポインタデリファレンスを引き起こし、プログラムがクラッシュする可能性がありました。
JSON-RPCプロトコルでは、成功した応答にはresult
フィールドが含まれ、エラー応答にはerror
フィールドが含まれます。しかし、プロトコルに厳密に従わない、あるいは予期せぬ形式の応答が返された場合、クライアント側で適切に処理されないと問題が発生します。このコミットは、このようなエッジケースを適切にハンドリングし、クライアントの堅牢性を向上させることを目的としています。
前提知識の解説
RPC (Remote Procedure Call)
RPCは、ネットワーク上の別のコンピュータにあるプログラムのサブルーチンや関数を、あたかもローカルにあるかのように呼び出すための技術です。これにより、分散システムにおけるプロセス間通信が容易になります。クライアントはリモートの関数を呼び出し、サーバーはその呼び出しを受け取って処理し、結果をクライアントに返します。
JSON-RPC
JSON-RPCは、RPCプロトコルの一種で、メッセージのエンコーディングにJSONを使用します。HTTPなどのトランスポートプロトコル上で動作することが多く、シンプルで人間が読みやすい形式が特徴です。JSON-RPCのメッセージは、jsonrpc
(プロトコルバージョン)、method
(呼び出すメソッド名)、params
(引数)、id
(リクエストID)などのフィールドで構成されます。応答メッセージには、result
(成功時の結果)、error
(エラー情報)、id
が含まれます。
net/rpc
パッケージ
Go言語の標準ライブラリ net/rpc
は、RPCクライアントとサーバーを実装するためのフレームワークを提供します。これは、Goのencoding/gob
形式をデフォルトのエンコーディングとして使用しますが、net/rpc/jsonrpc
パッケージのように、他のエンコーディング(JSON)を使用するクライアント/サーバーを構築するためのインターフェースも提供しています。
net/rpc/jsonrpc
パッケージ
net/rpc
パッケージのサブパッケージであり、JSON-RPCプロトコルを使用してRPC通信を行うためのクライアントとサーバーの実装を提供します。このパッケージを使用することで、GoアプリケーションはJSON-RPCを話す他のシステムと容易に連携できます。
nilポインタデリファレンス (Nil Pointer Dereference)
nilポインタデリファレンスは、プログラミングにおける一般的な実行時エラーの一つです。ポインタが何も指していない(nil
またはnull
)状態で、そのポインタが指すメモリ領域にアクセスしようとすると発生します。Go言語では、nilポインタデリファレンスが発生すると、パニック(panic)が発生し、プログラムがクラッシュします。これは、プログラムの安定性にとって非常に危険な状態です。
Go言語のエラーハンドリング
Go言語では、エラーは戻り値として明示的に扱われます。関数は通常、最後の戻り値としてerror
型の値を返します。エラーが発生しなかった場合はnil
を返します。呼び出し元は、このerror
値をチェックして、処理を続行するか、エラーを上位に伝播させるかを決定します。
技術的詳細
このコミットが修正する問題は、net/rpc/jsonrpc
クライアントがサーバーからの応答を解析する際に、JSON-RPCの応答オブジェクトのresult
フィールドがnull
である場合に発生していました。
JSON-RPC 2.0の仕様では、成功応答はresult
フィールドを持ち、エラー応答はerror
フィールドを持ちます。しかし、result
がnull
であるケースは、エラーではないが結果がない、あるいは特定の状況下で有効な応答として扱われることがあります。
元のコードでは、clientCodec
のReadResponseHeader
メソッド内で、c.resp.Error != nil
の場合にのみエラー処理を行っていました。しかし、c.resp.Error
がnil
であっても、c.resp.Result
がnil
である場合に、後続の処理でc.resp.Result
が指す値にアクセスしようとすると、nilポインタデリファレンスが発生する可能性がありました。
この修正では、client.go
のReadResponseHeader
メソッドにおいて、エラーチェックの条件にc.resp.Result == nil
を追加することで、この問題を解決しています。これにより、error
フィールドがnil
であってもresult
フィールドがnil
であるような無効な応答が来た場合に、明示的にエラーとして扱うようになります。
また、all_test.go
にTestMalformedOutput
という新しいテストケースが追加されました。このテストは、意図的に{"id":0,"result":null,"error":null}
という形式の不正なJSON-RPC応答をクライアントに送りつけ、クライアントがエラーを返すことを検証します。これにより、修正が正しく機能していることが確認されます。
コアとなるコードの変更箇所
--- a/src/pkg/net/rpc/jsonrpc/all_test.go
+++ b/src/pkg/net/rpc/jsonrpc/all_test.go
@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
+ "io/ioutil"
"net"
"net/rpc"
"testing"
@@ -185,6 +186,22 @@ func TestMalformedInput(t *testing.T) {
ServeConn(srv) // must return, not loop
}
+func TestMalformedOutput(t *testing.T) {
+ cli, srv := net.Pipe()
+ go srv.Write([]byte(`{"id":0,"result":null,"error":null}`))
+ go ioutil.ReadAll(srv)
+
+ client := NewClient(cli)
+ defer client.Close()
+
+ args := &Args{7, 8}
+ reply := new(Reply)
+ err := client.Call("Arith.Add", args, reply)
+ if err == nil {
+ t.Error("expected error")
+ }
+}
+
func TestUnexpectedError(t *testing.T) {
cli, srv := myPipe()
go cli.PipeWriter.CloseWithError(errors.New("unexpected error!")) // reader will get this error
diff --git a/src/pkg/net/rpc/jsonrpc/client.go b/src/pkg/net/rpc/jsonrpc/client.go
index 3fa8cbf08a..2194f21257 100644
--- a/src/pkg/net/rpc/jsonrpc/client.go
+++ b/src/pkg/net/rpc/jsonrpc/client.go
@@ -83,7 +83,7 @@ func (c *clientCodec) ReadResponseHeader(r *rpc.Response) error {
r.Error = ""
r.Seq = c.resp.Id
- if c.resp.Error != nil {
+ if c.resp.Error != nil || c.resp.Result == nil {
x, ok := c.resp.Error.(string)
if !ok {
return fmt.Errorf("invalid error %v", c.resp.Error)
コアとなるコードの解説
src/pkg/net/rpc/jsonrpc/client.go
の変更
if c.resp.Error != nil || c.resp.Result == nil {
x, ok := c.resp.Error.(string)
if !ok {
return fmt.Errorf("invalid error %v", c.resp.Error)
}
r.Error = x
}
この変更は、clientCodec
構造体のReadResponseHeader
メソッド内で行われています。このメソッドは、JSON-RPCサーバーからの応答ヘッダーを読み取り、rpc.Response
構造体に解析する役割を担っています。
元のコードでは、if c.resp.Error != nil
という条件のみでエラーをチェックしていました。これは、JSON-RPC応答に明示的なエラーフィールドが含まれている場合にのみ、それをエラーとして認識するという意味です。
しかし、この修正では条件がif c.resp.Error != nil || c.resp.Result == nil
に変更されました。
c.resp.Error != nil
: これは従来通り、応答にエラー情報が含まれている場合を捕捉します。c.resp.Result == nil
: これが追加された新しい条件です。これは、応答にerror
フィールドがない(nil
である)にもかかわらず、result
フィールドもnil
である場合を捕捉します。JSON-RPCの仕様では、成功応答にはresult
フィールドが、エラー応答にはerror
フィールドが含まれるべきです。result
もerror
もnil
であるような応答は、通常、不正な形式と見なされます。
この変更により、クライアントは、result
がnull
であるような不正な応答を受け取った場合でも、それをエラーとして適切に処理し、後続のnilポインタデリファレンスを防ぐことができます。r.Error = x
によって、rpc.Response
オブジェクトのエラーフィールドに適切なエラーメッセージが設定され、クライアントのCall
メソッドにエラーが返されるようになります。
src/pkg/net/rpc/jsonrpc/all_test.go
の変更
func TestMalformedOutput(t *testing.T) {
cli, srv := net.Pipe()
go srv.Write([]byte(`{"id":0,"result":null,"error":null}`))
go ioutil.ReadAll(srv)
client := NewClient(cli)
defer client.Close()
args := &Args{7, 8}
reply := new(Reply)
err := client.Call("Arith.Add", args, reply)
if err == nil {
t.Error("expected error")
}
}
この新しいテストケースは、修正が正しく機能することを検証するために追加されました。
net.Pipe()
を使用して、クライアントとサーバー間のインメモリパイプを作成します。これにより、実際のネットワーク接続なしに通信をシミュレートできます。go srv.Write([]byte(
{"id":0,"result":null,"error":null}))
: サーバー側(srv
)から、意図的にresult
とerror
の両方がnull
である不正なJSON-RPC応答を書き込みます。これは、修正が対処しようとしているエッジケースを再現します。go ioutil.ReadAll(srv)
: サーバーからの残りの出力を読み捨てます。これは、パイプがブロックされないようにするための一般的なパターンです。client := NewClient(cli)
: クライアント側(cli
)で新しいJSON-RPCクライアントを作成します。client.Call("Arith.Add", args, reply)
: クライアントがリモートメソッドArith.Add
を呼び出します。この呼び出しは、サーバーから不正な応答を受け取ることを期待しています。if err == nil { t.Error("expected error") }
: ここがテストの核心です。もしclient.Call
がエラーを返さなかった場合(つまりerr
がnil
だった場合)、それはバグが修正されていないことを意味します。テストは「エラーが期待された」と報告し、失敗します。
このテストは、result
がnull
である不正な応答が来た場合に、クライアントが適切にエラーを検出し、nilポインタデリファレンスを起こさずにエラーを返すことを保証します。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/0d559f7b92c1425b9a78ff67933a9baaa0534928
- Go Issue #5006: https://golang.org/issue/5006
- Go Code Review (CL) 7691045: https://golang.org/cl/7691045
参考にした情報源リンク
- Go言語の公式ドキュメント (
net/rpc
,net/rpc/jsonrpc
): https://pkg.go.dev/net/rpc, https://pkg.go.dev/net/rpc/jsonrpc - JSON-RPC 2.0 Specification: https://www.jsonrpc.org/specification
- Go言語におけるnilポインタデリファレンスに関する一般的な情報源 (例: Go言語の公式ブログ、技術記事など)
- (具体的なURLは検索結果によるため、ここでは一般的な説明に留めます)
- 例: "Go nil pointer dereference", "Go error handling" などのキーワードで検索。
- Go言語の公式ブログやEffective Goなどが参考になります。
net.Pipe()
の使用例に関する情報源 (例: Go言語のテストに関する記事)- (具体的なURLは検索結果によるため、ここでは一般的な説明に留めます)
- 例: "Go net.Pipe example", "Go testing network" などのキーワードで検索。
[インデックス 15718] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/rpc/jsonrpc
パッケージにおける、無効なRPC応答(リプライ)が原因で発生するnilポインタデリファレンス(nil pointer dereference)のバグを修正するものです。具体的には、JSON-RPCクライアントがサーバーからの応答を処理する際に、result
フィールドがnull
である場合に発生する問題を解決し、関連するテストケースを追加しています。
コミット
- コミットハッシュ:
0d559f7b92c1425b9a78ff67933a9baaa0534928
- Author: Adrian Nos nos.adrian@gmail.com
- Date: Tue Mar 12 11:45:56 2013 -0400
- コミットメッセージ:
net/rpc/jsonrpc: nil pointer deference on invalid reply. Fixes #5006. R=golang-dev, rsc CC=golang-dev https://golang.org/cl/7691045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d559f7b92c1425b9a78ff67933a9baaa0534928
元コミット内容
net/rpc/jsonrpc: nil pointer deference on invalid reply.
Fixes #5006.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7691045
変更の背景
この変更は、Go言語のIssue #5006「net/rpc/jsonrpc
: nil pointer dereference on invalid reply」を修正するために行われました。このバグは、net/rpc/jsonrpc
クライアントがJSON-RPCサーバーからの応答を処理する際に、特定の無効な応答形式(特にresult
フィールドがnull
である場合)を受け取ると、nilポインタデリファレンスを引き起こし、プログラムがクラッシュする可能性がありました。
JSON-RPCプロトコルでは、成功した応答にはresult
フィールドが含まれ、エラー応答にはerror
フィールドが含まれます。しかし、プロトコルに厳密に従わない、あるいは予期せぬ形式の応答が返された場合、クライアント側で適切に処理されないと問題が発生します。このコミットは、このようなエッジケースを適切にハンドリングし、クライアントの堅牢性を向上させることを目的としています。
前提知識の解説
RPC (Remote Procedure Call)
RPCは、ネットワーク上の別のコンピュータにあるプログラムのサブルーチンや関数を、あたかもローカルにあるかのように呼び出すための技術です。これにより、分散システムにおけるプロセス間通信が容易になります。クライアントはリモートの関数を呼び出し、サーバーはその呼び出しを受け取って処理し、結果をクライアントに返します。
JSON-RPC
JSON-RPCは、RPCプロトコルの一種で、メッセージのエンコーディングにJSONを使用します。HTTPなどのトランスポートプロトコル上で動作することが多く、シンプルで人間が読みやすい形式が特徴です。JSON-RPCのメッセージは、jsonrpc
(プロトコルバージョン)、method
(呼び出すメソッド名)、params
(引数)、id
(リクエストID)などのフィールドで構成されます。応答メッセージには、result
(成功時の結果)、error
(エラー情報)、id
が含まれます。
net/rpc
パッケージ
Go言語の標準ライブラリ net/rpc
は、RPCクライアントとサーバーを実装するためのフレームワークを提供します。これは、Goのencoding/gob
形式をデフォルトのエンコーディングとして使用しますが、net/rpc/jsonrpc
パッケージのように、他のエンコーディング(JSON)を使用するクライアント/サーバーを構築するためのインターフェースも提供しています。
net/rpc/jsonrpc
パッケージ
net/rpc
パッケージのサブパッケージであり、JSON-RPCプロトコルを使用してRPC通信を行うためのクライアントとサーバーの実装を提供します。このパッケージを使用することで、GoアプリケーションはJSON-RPCを話す他のシステムと容易に連携できます。
nilポインタデリファレンス (Nil Pointer Dereference)
nilポインタデリファレンスは、プログラミングにおける一般的な実行時エラーの一つです。ポインタが何も指していない(nil
またはnull
)状態で、そのポインタが指すメモリ領域にアクセスしようとすると発生します。Go言語では、nilポインタデリファレンスが発生すると、パニック(panic)が発生し、プログラムがクラッシュします。これは、プログラムの安定性にとって非常に危険な状態です。
Go言語のエラーハンドリング
Go言語では、エラーは戻り値として明示的に扱われます。関数は通常、最後の戻り値としてerror
型の値を返します。エラーが発生しなかった場合はnil
を返します。呼び出し元は、このerror
値をチェックして、処理を続行するか、エラーを上位に伝播させるかを決定します。
技術的詳細
このコミットが修正する問題は、net/rpc/jsonrpc
クライアントがサーバーからの応答を解析する際に、JSON-RPCの応答オブジェクトのresult
フィールドがnull
である場合に発生していました。
JSON-RPC 2.0の仕様では、成功応答はresult
フィールドを持ち、エラー応答はerror
フィールドを持ちます。しかし、result
がnull
であるケースは、エラーではないが結果がない、あるいは特定の状況下で有効な応答として扱われることがあります。
元のコードでは、clientCodec
のReadResponseHeader
メソッド内で、c.resp.Error != nil
の場合にのみエラー処理を行っていました。しかし、c.resp.Error
がnil
であっても、c.resp.Result
がnil
である場合に、後続の処理でc.resp.Result
が指す値にアクセスしようとすると、nilポインタデリファレンスが発生する可能性がありました。
この修正では、client.go
のReadResponseHeader
メソッドにおいて、エラーチェックの条件にc.resp.Result == nil
を追加することで、この問題を解決しています。これにより、error
フィールドがnil
であってもresult
フィールドがnil
であるような無効な応答が来た場合に、明示的にエラーとして扱うようになります。
また、all_test.go
にTestMalformedOutput
という新しいテストケースが追加されました。このテストは、意図的に{"id":0,"result":null,"error":null}
という形式の不正なJSON-RPC応答をクライアントに送りつけ、クライアントがエラーを返すことを検証します。これにより、修正が正しく機能していることが確認されます。
コアとなるコードの変更箇所
--- a/src/pkg/net/rpc/jsonrpc/all_test.go
+++ b/src/pkg/net/rpc/jsonrpc/all_test.go
@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
+ "io/ioutil"
"net"
"net/rpc"
"testing"
@@ -185,6 +186,22 @@ func TestMalformedInput(t *testing.T) {
ServeConn(srv) // must return, not loop
}
+func TestMalformedOutput(t *testing.T) {
+ cli, srv := net.Pipe()
+ go srv.Write([]byte(`{"id":0,"result":null,"error":null}`))
+ go ioutil.ReadAll(srv)
+
+ client := NewClient(cli)
+ defer client.Close()
+
+ args := &Args{7, 8}
+ reply := new(Reply)
+ err := client.Call("Arith.Add", args, reply)
+ if err == nil {
+ t.Error("expected error")
+ }
+}
+
func TestUnexpectedError(t *testing.T) {
cli, srv := myPipe()
go cli.PipeWriter.CloseWithError(errors.New("unexpected error!")) // reader will get this error
diff --git a/src/pkg/net/rpc/jsonrpc/client.go b/src/pkg/net/rpc/jsonrpc/client.go
index 3fa8cbf08a..2194f21257 100644
--- a/src/pkg/net/rpc/jsonrpc/client.go
+++ b/src/pkg/net/rpc/jsonrpc/client.go
@@ -83,7 +83,7 @@ func (c *clientCodec) ReadResponseHeader(r *rpc.Response) error {
r.Error = ""
r.Seq = c.resp.Id
- if c.resp.Error != nil {
+ if c.resp.Error != nil || c.resp.Result == nil {
x, ok := c.resp.Error.(string)
if !ok {
return fmt.Errorf("invalid error %v", c.resp.Error)
コアとなるコードの解説
src/pkg/net/rpc/jsonrpc/client.go
の変更
if c.resp.Error != nil || c.resp.Result == nil {
x, ok := c.resp.Error.(string)
if !ok {
return fmt.Errorf("invalid error %v", c.resp.Error)
}
r.Error = x
}
この変更は、clientCodec
構造体のReadResponseHeader
メソッド内で行われています。このメソッドは、JSON-RPCサーバーからの応答ヘッダーを読み取り、rpc.Response
構造体に解析する役割を担っています。
元のコードでは、if c.resp.Error != nil
という条件のみでエラーをチェックしていました。これは、JSON-RPC応答に明示的なエラーフィールドが含まれている場合にのみ、それをエラーとして認識するという意味です。
しかし、この修正では条件がif c.resp.Error != nil || c.resp.Result == nil
に変更されました。
c.resp.Error != nil
: これは従来通り、応答にエラー情報が含まれている場合を捕捉します。c.resp.Result == nil
: これが追加された新しい条件です。これは、応答にerror
フィールドがない(nil
である)にもかかわらず、result
フィールドもnil
である場合を捕捉します。JSON-RPCの仕様では、成功応答にはresult
フィールドが、エラー応答にはerror
フィールドが含まれるべきです。result
もerror
もnil
であるような応答は、通常、不正な形式と見なされます。
この変更により、クライアントは、result
がnull
であるような不正な応答を受け取った場合でも、それをエラーとして適切に処理し、後続のnilポインタデリファレンスを防ぐことができます。r.Error = x
によって、rpc.Response
オブジェクトのエラーフィールドに適切なエラーメッセージが設定され、クライアントのCall
メソッドにエラーが返されるようになります。
src/pkg/net/rpc/jsonrpc/all_test.go
の変更
func TestMalformedOutput(t *testing.T) {
cli, srv := net.Pipe()
go srv.Write([]byte(`{"id":0,"result":null,"error":null}`))
go ioutil.ReadAll(srv)
client := NewClient(cli)
defer client.Close()
args := &Args{7, 8}
reply := new(Reply)
err := client.Call("Arith.Add", args, reply)
if err == nil {
t.Error("expected error")
}
}
この新しいテストケースは、修正が正しく機能することを検証するために追加されました。
net.Pipe()
を使用して、クライアントとサーバー間のインメモリパイプを作成します。これにより、実際のネットワーク接続なしに通信をシミュレートできます。go srv.Write([]byte(
{"id":0,"result":null,"error":null}))
: サーバー側(srv
)から、意図的にresult
とerror
の両方がnull
である不正なJSON-RPC応答を書き込みます。これは、修正が対処しようとしているエッジケースを再現します。go ioutil.ReadAll(srv)
: サーバーからの残りの出力を読み捨てます。これは、パイプがブロックされないようにするための一般的なパターンです。client := NewClient(cli)
: クライアント側(cli
)で新しいJSON-RPCクライアントを作成します。client.Call("Arith.Add", args, reply)
: クライアントがリモートメソッドArith.Add
を呼び出します。この呼び出しは、サーバーから不正な応答を受け取ることを期待しています。if err == nil { t.Error("expected error") }
: ここがテストの核心です。もしclient.Call
がエラーを返さなかった場合(つまりerr
がnil
だった場合)、それはバグが修正されていないことを意味します。テストは「エラーが期待された」と報告し、失敗します。
このテストは、result
がnull
である不正な応答が来た場合に、クライアントが適切にエラーを検出し、nilポインタデリファレンスを起こさずにエラーを返すことを保証します。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/0d559f7b92c1425b9a78ff67933a9baaa0534928
- Go Issue #5006: https://golang.org/issue/5006
- Go Code Review (CL) 7691045: https://golang.org/cl/7691045
参考にした情報源リンク
- Go言語の公式ドキュメント (
net/rpc
,net/rpc/jsonrpc
): https://pkg.go.dev/net/rpc, https://pkg.go.dev/net/rpc/jsonrpc - JSON-RPC 2.0 Specification: https://www.jsonrpc.org/specification
- Go言語におけるnilポインタデリファレンスに関する一般的な情報源 (例: Go言語の公式ブログ、技術記事など)
- (具体的なURLは検索結果によるため、ここでは一般的な説明に留めます)
- 例: "Go nil pointer dereference", "Go error handling" などのキーワードで検索。
- Go言語の公式ブログやEffective Goなどが参考になります。
net.Pipe()
の使用例に関する情報源 (例: Go言語のテストに関する記事)- (具体的なURLは検索結果によるため、ここでは一般的な説明に留めます)
- 例: "Go net.Pipe example", "Go testing network" などのキーワードで検索。