1
0
mirror of https://github.com/golang/go synced 2024-11-22 21:30:02 -07:00

encoding/json: make error capture logic in recover more type safe

Rather than only ignoring runtime.Error panics, which are a very
narrow set of possible panic values, switch it such that the json
package only captures panic values that have been properly wrapped
in a jsonError struct. This ensures that only intentional panics
originating from the json package are captured.

Fixes #23012

Change-Id: I5e85200259edd2abb1b0512ce6cc288849151a6d
Reviewed-on: https://go-review.googlesource.com/94019
Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Joe Tsai 2017-12-05 22:53:48 -08:00 committed by Joe Tsai
parent 70a04f6880
commit 91a6a2a30f
4 changed files with 43 additions and 12 deletions

View File

@ -14,7 +14,6 @@ import (
"errors"
"fmt"
"reflect"
"runtime"
"strconv"
"unicode"
"unicode/utf16"
@ -168,13 +167,19 @@ func (e *InvalidUnmarshalError) Error() string {
return "json: Unmarshal(nil " + e.Type.String() + ")"
}
// jsonError is an error wrapper type for internal use only.
// Panics with errors are wrapped in jsonError so that the top-level recover
// can distinguish intentional panics from this package.
type jsonError struct{ error }
func (d *decodeState) unmarshal(v interface{}) (err error) {
defer func() {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
if je, ok := r.(jsonError); ok {
err = je.error
} else {
panic(r)
}
err = r.(error)
}
}()
@ -295,9 +300,9 @@ func (d *decodeState) init(data []byte) *decodeState {
return d
}
// error aborts the decoding by panicking with err.
// error aborts the decoding by panicking with err wrapped in jsonError.
func (d *decodeState) error(err error) {
panic(d.addErrorContext(err))
panic(jsonError{d.addErrorContext(err)})
}
// saveError saves the first err it is called with,

View File

@ -2166,3 +2166,17 @@ func TestUnmarshalEmbeddedPointerUnexported(t *testing.T) {
}
}
}
type unmarshalPanic struct{}
func (unmarshalPanic) UnmarshalJSON([]byte) error { panic(0xdead) }
func TestUnmarshalPanic(t *testing.T) {
defer func() {
if got := recover(); !reflect.DeepEqual(got, 0xdead) {
t.Errorf("panic() = (%T)(%v), want 0xdead", got, got)
}
}()
Unmarshal([]byte("{}"), &unmarshalPanic{})
t.Fatalf("Unmarshal should have panicked")
}

View File

@ -17,7 +17,6 @@ import (
"fmt"
"math"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
@ -286,21 +285,20 @@ func newEncodeState() *encodeState {
func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) {
defer func() {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
if je, ok := r.(jsonError); ok {
err = je.error
} else {
panic(r)
}
if s, ok := r.(string); ok {
panic(s)
}
err = r.(error)
}
}()
e.reflectValue(reflect.ValueOf(v), opts)
return nil
}
// error aborts the encoding by panicking with err wrapped in jsonError.
func (e *encodeState) error(err error) {
panic(err)
panic(jsonError{err})
}
func isEmptyValue(v reflect.Value) bool {

View File

@ -981,3 +981,17 @@ func TestMarshalRawMessageValue(t *testing.T) {
}
}
}
type marshalPanic struct{}
func (marshalPanic) MarshalJSON() ([]byte, error) { panic(0xdead) }
func TestMarshalPanic(t *testing.T) {
defer func() {
if got := recover(); !reflect.DeepEqual(got, 0xdead) {
t.Errorf("panic() = (%T)(%v), want 0xdead", got, got)
}
}()
Marshal(&marshalPanic{})
t.Error("Marshal should have panicked")
}