jsonrpc2: do not panic when sending unmarshalable params
If params given to (*Connection).Call is not JSON-marshalable, Call
returns immediately an *AsyncCall instance containing the
corresponding error, but its ctx field is not initialized. This
produces a panic later in golang.org/x/exp/event.New, as it does not
expect a nil context.Context.
Fixes golang/go#61654.
Change-Id: I6464ab5fde85670267b25821e0b57af04c331c8c
Reviewed-on: https://go-review.googlesource.com/c/exp/+/516556
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
diff --git a/jsonrpc2/conn.go b/jsonrpc2/conn.go
index 882d15b..f0e527c 100644
--- a/jsonrpc2/conn.go
+++ b/jsonrpc2/conn.go
@@ -144,18 +144,18 @@
id: Int64ID(atomic.AddInt64(&c.seq, 1)),
resultBox: make(chan asyncResult, 1),
}
- // generate a new request identifier
- call, err := NewCall(result.id, method, params)
- if err != nil {
- //set the result to failed
- result.resultBox <- asyncResult{err: errors.Errorf("marshaling call parameters: %w", err)}
- return result
- }
- //TODO: rewrite this using the new target/prototype stuff
+ // TODO: rewrite this using the new target/prototype stuff
ctx = event.Start(ctx, method,
Method(method), RPCDirection(Outbound), RPCID(fmt.Sprintf("%q", result.id)))
Started.Record(ctx, 1, Method(method))
result.ctx = ctx
+ // generate a new request identifier
+ call, err := NewCall(result.id, method, params)
+ if err != nil {
+ // set the result to failed
+ result.resultBox <- asyncResult{err: errors.Errorf("marshaling call parameters: %w", err)}
+ return result
+ }
// We have to add ourselves to the pending map before we send, otherwise we
// are racing the response.
// rchan is buffered in case the response arrives without a listener.
@@ -355,7 +355,7 @@
select {
case nextReq, ok = <-fromRead:
case toDeliver <- q[0]:
- //TODO: this causes a lot of shuffling, should we use a growing ring buffer? compaction?
+ // TODO: this causes a lot of shuffling, should we use a growing ring buffer? compaction?
q = q[1:]
}
}
@@ -413,7 +413,7 @@
}
if err := c.respond(entry, result, rerr); err != nil {
// no way to propagate this error
- //TODO: should we do more than just log it?
+ // TODO: should we do more than just log it?
event.Error(entry.baseCtx, "jsonrpc2 message delivery failed", err)
}
}
@@ -441,7 +441,7 @@
err = errors.Errorf("%w: %q notification failed: %v", ErrInternal, entry.request.Method, rerr)
rerr = nil
case result != nil:
- //notification produced a response, which is an error
+ // notification produced a response, which is an error
err = errors.Errorf("%w: %q produced unwanted response", ErrInternal, entry.request.Method)
default:
// normal notification finish
diff --git a/jsonrpc2/jsonrpc2_test.go b/jsonrpc2/jsonrpc2_test.go
index 85a4981..40df6ed 100644
--- a/jsonrpc2/jsonrpc2_test.go
+++ b/jsonrpc2/jsonrpc2_test.go
@@ -10,6 +10,7 @@
"fmt"
"path"
"reflect"
+ "strings"
"testing"
"time"
@@ -60,6 +61,7 @@
notify{"unblock", "a"},
collect{"a", true, false},
}},
+ callErr{"error", func() {}, "marshaling call parameters: json: unsupported type"},
}
type binder struct {
@@ -90,6 +92,12 @@
expect interface{}
}
+type callErr struct {
+ method string
+ params interface{}
+ expectErr string
+}
+
type async struct {
name string
method string
@@ -175,6 +183,18 @@
verifyResults(t, test.method, results, test.expect)
}
+func (test callErr) Name() string { return test.method }
+func (test callErr) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ var results interface{}
+ if err := h.conn.Call(ctx, test.method, test.params).Await(ctx, &results); err != nil {
+ if serr := err.Error(); !strings.Contains(serr, test.expectErr) {
+ t.Fatalf("%v:Call failed but with unexpected error: %q does not contain %q", test.method, serr, test.expectErr)
+ }
+ return
+ }
+ t.Fatalf("%v:Call succeeded (%v) but should have failed with error containing %q", test.method, results, test.expectErr)
+}
+
func (test echo) Invoke(t *testing.T, ctx context.Context, h *handler) {
results := newResults(test.expect)
if err := h.conn.Call(ctx, "echo", []interface{}{test.method, test.params}).Await(ctx, results); err != nil {