Skip to content

Commit 7b3493d

Browse files
maxatomegopherbot
authored andcommitted
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 <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Run-TryBot: Bryan Mills <[email protected]> Auto-Submit: Bryan Mills <[email protected]>
1 parent 853ea24 commit 7b3493d

File tree

2 files changed

+29
-9
lines changed

2 files changed

+29
-9
lines changed

jsonrpc2/conn.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,18 @@ func (c *Connection) Call(ctx context.Context, method string, params interface{}
144144
id: Int64ID(atomic.AddInt64(&c.seq, 1)),
145145
resultBox: make(chan asyncResult, 1),
146146
}
147+
// TODO: rewrite this using the new target/prototype stuff
148+
ctx = event.Start(ctx, method,
149+
Method(method), RPCDirection(Outbound), RPCID(fmt.Sprintf("%q", result.id)))
150+
Started.Record(ctx, 1, Method(method))
151+
result.ctx = ctx
147152
// generate a new request identifier
148153
call, err := NewCall(result.id, method, params)
149154
if err != nil {
150-
//set the result to failed
155+
// set the result to failed
151156
result.resultBox <- asyncResult{err: errors.Errorf("marshaling call parameters: %w", err)}
152157
return result
153158
}
154-
//TODO: rewrite this using the new target/prototype stuff
155-
ctx = event.Start(ctx, method,
156-
Method(method), RPCDirection(Outbound), RPCID(fmt.Sprintf("%q", result.id)))
157-
Started.Record(ctx, 1, Method(method))
158-
result.ctx = ctx
159159
// We have to add ourselves to the pending map before we send, otherwise we
160160
// are racing the response.
161161
// rchan is buffered in case the response arrives without a listener.
@@ -355,7 +355,7 @@ func (c *Connection) manageQueue(ctx context.Context, preempter Preempter, fromR
355355
select {
356356
case nextReq, ok = <-fromRead:
357357
case toDeliver <- q[0]:
358-
//TODO: this causes a lot of shuffling, should we use a growing ring buffer? compaction?
358+
// TODO: this causes a lot of shuffling, should we use a growing ring buffer? compaction?
359359
q = q[1:]
360360
}
361361
}
@@ -413,7 +413,7 @@ func (c *Connection) reply(entry *incoming, result interface{}, rerr error) {
413413
}
414414
if err := c.respond(entry, result, rerr); err != nil {
415415
// no way to propagate this error
416-
//TODO: should we do more than just log it?
416+
// TODO: should we do more than just log it?
417417
event.Error(entry.baseCtx, "jsonrpc2 message delivery failed", err)
418418
}
419419
}
@@ -441,7 +441,7 @@ func (c *Connection) respond(entry *incoming, result interface{}, rerr error) er
441441
err = errors.Errorf("%w: %q notification failed: %v", ErrInternal, entry.request.Method, rerr)
442442
rerr = nil
443443
case result != nil:
444-
//notification produced a response, which is an error
444+
// notification produced a response, which is an error
445445
err = errors.Errorf("%w: %q produced unwanted response", ErrInternal, entry.request.Method)
446446
default:
447447
// normal notification finish

jsonrpc2/jsonrpc2_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"path"
1212
"reflect"
13+
"strings"
1314
"testing"
1415
"time"
1516

@@ -60,6 +61,7 @@ var callTests = []invoker{
6061
notify{"unblock", "a"},
6162
collect{"a", true, false},
6263
}},
64+
callErr{"error", func() {}, "marshaling call parameters: json: unsupported type"},
6365
}
6466

6567
type binder struct {
@@ -90,6 +92,12 @@ type call struct {
9092
expect interface{}
9193
}
9294

95+
type callErr struct {
96+
method string
97+
params interface{}
98+
expectErr string
99+
}
100+
93101
type async struct {
94102
name string
95103
method string
@@ -175,6 +183,18 @@ func (test call) Invoke(t *testing.T, ctx context.Context, h *handler) {
175183
verifyResults(t, test.method, results, test.expect)
176184
}
177185

186+
func (test callErr) Name() string { return test.method }
187+
func (test callErr) Invoke(t *testing.T, ctx context.Context, h *handler) {
188+
var results interface{}
189+
if err := h.conn.Call(ctx, test.method, test.params).Await(ctx, &results); err != nil {
190+
if serr := err.Error(); !strings.Contains(serr, test.expectErr) {
191+
t.Fatalf("%v:Call failed but with unexpected error: %q does not contain %q", test.method, serr, test.expectErr)
192+
}
193+
return
194+
}
195+
t.Fatalf("%v:Call succeeded (%v) but should have failed with error containing %q", test.method, results, test.expectErr)
196+
}
197+
178198
func (test echo) Invoke(t *testing.T, ctx context.Context, h *handler) {
179199
results := newResults(test.expect)
180200
if err := h.conn.Call(ctx, "echo", []interface{}{test.method, test.params}).Await(ctx, results); err != nil {

0 commit comments

Comments
 (0)