Skip to content

Commit 942ec49

Browse files
authored
Guard against panics from typed-nil internal error types (#1452)
This caused some surprising panics in a user's service, and it's not hard to do better.
1 parent 59dcd41 commit 942ec49

File tree

3 files changed

+39
-2
lines changed

3 files changed

+39
-2
lines changed

internal/error.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ const (
190190
errReasonGeneric = "cadenceInternal:Generic"
191191
errReasonCanceled = "cadenceInternal:Canceled"
192192
errReasonTimeout = "cadenceInternal:Timeout"
193+
194+
badNilErrMsgFmt = "cadence received an invalid nil `%T`." +
195+
" this likely means you have a typed nil which is being" +
196+
" converted into an `error` value: https://go.dev/doc/faq#nil_error"
193197
)
194198

195199
// ErrNoData is returned when trying to extract strong typed data while there is no data available.

internal/internal_utils.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ const (
6363

6464
// defaultRPCTimeout is the default tchannel rpc call timeout
6565
defaultRPCTimeout = 10 * time.Second
66-
//minRPCTimeout is minimum rpc call timeout allowed
66+
// minRPCTimeout is minimum rpc call timeout allowed
6767
minRPCTimeout = 1 * time.Second
68-
//maxRPCTimeout is maximum rpc call timeout allowed
68+
// maxRPCTimeout is maximum rpc call timeout allowed
6969
maxRPCTimeout = 5 * time.Second
7070
// maxQueryRPCTimeout is the maximum rpc call timeout allowed for query
7171
maxQueryRPCTimeout = 20 * time.Second
@@ -243,6 +243,9 @@ func getErrorDetails(err error, dataConverter DataConverter) (string, []byte) {
243243
case *CustomError:
244244
var data []byte
245245
var err0 error
246+
if err == nil {
247+
return errReasonGeneric, []byte(fmt.Sprintf(badNilErrMsgFmt, err))
248+
}
246249
switch details := err.details.(type) {
247250
case ErrorDetailsValues:
248251
data, err0 = encodeArgs(dataConverter, details)
@@ -258,6 +261,10 @@ func getErrorDetails(err error, dataConverter DataConverter) (string, []byte) {
258261
case *CanceledError:
259262
var data []byte
260263
var err0 error
264+
if err == nil {
265+
// treat this as a failure, not a cancel, as it likely is not a real cancel
266+
return errReasonGeneric, []byte(fmt.Sprintf(badNilErrMsgFmt, err))
267+
}
261268
switch details := err.details.(type) {
262269
case ErrorDetailsValues:
263270
data, err0 = encodeArgs(dataConverter, details)
@@ -271,6 +278,10 @@ func getErrorDetails(err error, dataConverter DataConverter) (string, []byte) {
271278
}
272279
return errReasonCanceled, data
273280
case *PanicError:
281+
if err == nil {
282+
// treat this as a failure, not a panic, as it likely is not a real panic
283+
return errReasonGeneric, []byte(fmt.Sprintf(badNilErrMsgFmt, err))
284+
}
274285
data, err0 := encodeArgs(dataConverter, []interface{}{err.Error(), err.StackTrace()})
275286
if err0 != nil {
276287
panic(err0)
@@ -279,6 +290,10 @@ func getErrorDetails(err error, dataConverter DataConverter) (string, []byte) {
279290
case *TimeoutError:
280291
var data []byte
281292
var err0 error
293+
if err == nil {
294+
// treat this as a failure, not a timeout, as it likely is not a real timeout
295+
return errReasonGeneric, []byte(fmt.Sprintf(badNilErrMsgFmt, err))
296+
}
282297
switch details := err.details.(type) {
283298
case ErrorDetailsValues:
284299
data, err0 = encodeArgs(dataConverter, details)

internal/internal_utils_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"testing"
2626
"time"
2727

28+
"github.com/stretchr/testify/assert"
2829
"github.com/stretchr/testify/require"
2930

3031
s "go.uber.org/cadence/.gen/go/shared"
@@ -135,6 +136,23 @@ func TestGetErrorDetails_TimeoutError(t *testing.T) {
135136
require.Equal(t, val2, data)
136137
}
137138

139+
func Test_getErrorDetails_nil(t *testing.T) {
140+
// typed nils caused panics, prevent them from coming back
141+
check := func(t *testing.T, err error) {
142+
reason, data := getErrorDetails(err, getDefaultDataConverter())
143+
dataStr := string(data)
144+
assert.Equalf(t, errReasonGeneric, reason, "wrong reason for %T", err)
145+
assert.Contains(t, dataStr, "typed nil", "wrong data for %T", err)
146+
}
147+
check(t, (*CanceledError)(nil))
148+
check(t, (*CustomError)(nil))
149+
check(t, (*PanicError)(nil))
150+
check(t, (*TimeoutError)(nil))
151+
// other types are probably also good to handle, but e.g. handling
152+
// external error types will require panic recovery.
153+
// this might be a good idea, but for now this just covers "we can do better with our types".
154+
}
155+
138156
func TestConstructError_TimeoutError(t *testing.T) {
139157
t.Parallel()
140158
dc := getDefaultDataConverter()

0 commit comments

Comments
 (0)