Add error type compatible with encoding/json

This commit is contained in:
Masaaki Goshima 2020-05-09 01:07:33 +09:00
parent a979b83d27
commit d8d711ecf9
7 changed files with 297 additions and 23 deletions

View File

@ -73,16 +73,26 @@ func (d *Decoder) Buffered() io.Reader {
return d.buffered()
}
func (d *Decoder) validateType(typ *rtype, p uintptr) error {
if typ.Kind() != reflect.Ptr || p == 0 {
return &InvalidUnmarshalError{Type: rtype2type(typ)}
}
return nil
}
func (d *Decoder) decode(src []byte, header *interfaceHeader) error {
typ := header.typ
if typ.Kind() != reflect.Ptr {
return ErrDecodePointer
}
typeptr := uintptr(unsafe.Pointer(typ))
dec := cachedDecoder.get(typeptr)
if dec == nil {
// noescape trick for header.typ ( reflect.*rtype )
copiedType := (*rtype)(unsafe.Pointer(typeptr))
ptr := uintptr(header.ptr)
if err := d.validateType(copiedType, ptr); err != nil {
return err
}
dec := cachedDecoder.get(typeptr)
if dec == nil {
compiledDec, err := d.compileHead(copiedType)
if err != nil {
@ -91,7 +101,6 @@ func (d *Decoder) decode(src []byte, header *interfaceHeader) error {
cachedDecoder.set(typeptr, compiledDec)
dec = compiledDec
}
ptr := uintptr(header.ptr)
if _, err := dec.decode(src, 0, ptr); err != nil {
return err
}
@ -117,10 +126,15 @@ func (d *Decoder) decodeForUnmarshalNoEscape(src []byte, v interface{}) error {
func (d *Decoder) Decode(v interface{}) error {
header := (*interfaceHeader)(unsafe.Pointer(&v))
typ := header.typ
if typ.Kind() != reflect.Ptr {
return ErrDecodePointer
}
ptr := uintptr(header.ptr)
typeptr := uintptr(unsafe.Pointer(typ))
// noescape trick for header.typ ( reflect.*rtype )
copiedType := (*rtype)(unsafe.Pointer(typeptr))
if err := d.validateType(copiedType, ptr); err != nil {
return err
}
dec := cachedDecoder.get(typeptr)
if dec == nil {
compiledDec, err := d.compileHead(typ)
@ -130,7 +144,7 @@ func (d *Decoder) Decode(v interface{}) error {
cachedDecoder.set(typeptr, compiledDec)
dec = compiledDec
}
ptr := uintptr(header.ptr)
for {
buf := make([]byte, 1024)
n, err := d.r.Read(buf)

View File

@ -218,3 +218,16 @@ func Test_UnmarshalText(t *testing.T) {
assertEq(t, "unmarshal", v.v, 11)
})
}
func Test_InvalidUnmarshalError(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var v *struct{}
err := fmt.Sprint(json.Unmarshal([]byte(`{}`), v))
assertEq(t, "invalid unmarshal error", "json: Unmarshal(nil *struct {})", err)
})
t.Run("non pointer", func(t *testing.T) {
var v int
err := fmt.Sprint(json.Unmarshal([]byte(`{}`), v))
assertEq(t, "invalid unmarshal error", "json: Unmarshal(non-pointer int)", err)
})
}

View File

@ -5,8 +5,6 @@ import (
"reflect"
"strings"
"unsafe"
"golang.org/x/xerrors"
)
func (e *Encoder) compileHead(typ *rtype, withIndent bool) (*opcode, error) {
@ -71,7 +69,7 @@ func (e *Encoder) compile(typ *rtype, withIndent bool) (*opcode, error) {
case reflect.Interface:
return e.compileInterface(typ)
}
return nil, xerrors.Errorf("failed to encode type %s: %w", typ.String(), ErrUnsupportedType)
return nil, &UnsupportedTypeError{Type: rtype2type(typ)}
}
func (e *Encoder) optimizeStructFieldPtrHead(typ *rtype, code *opcode) *opcode {

View File

@ -2,7 +2,9 @@ package json
import (
"encoding"
"math"
"reflect"
"strconv"
"unsafe"
)
@ -47,7 +49,14 @@ func (e *Encoder) run(code *opcode) error {
e.encodeFloat32(e.ptrToFloat32(code.ptr))
code = code.next
case opFloat64:
e.encodeFloat64(e.ptrToFloat64(code.ptr))
v := e.ptrToFloat64(code.ptr)
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
e.encodeFloat64(v)
code = code.next
case opString:
e.encodeString(e.ptrToString(code.ptr))
@ -519,9 +528,16 @@ func (e *Encoder) run(code *opcode) error {
e.encodeNull()
code = field.end
} else {
v := e.ptrToFloat64(field.ptr + field.offset)
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
e.encodeByte('{')
e.encodeBytes(field.key)
e.encodeFloat64(e.ptrToFloat64(field.ptr + field.offset))
e.encodeFloat64(v)
field.nextField.ptr = field.ptr
code = field.next
}
@ -809,12 +825,19 @@ func (e *Encoder) run(code *opcode) error {
e.encodeNull()
code = field.end
} else {
v := e.ptrToFloat64(field.ptr + field.offset)
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
e.encodeIndent(code.indent)
e.encodeBytes([]byte{'{', '\n'})
e.encodeIndent(code.indent + 1)
e.encodeBytes(field.key)
e.encodeByte(' ')
e.encodeFloat64(e.ptrToFloat64(field.ptr + field.offset))
e.encodeFloat64(v)
field.nextField.ptr = field.ptr
code = field.next
}
@ -1176,6 +1199,12 @@ func (e *Encoder) run(code *opcode) error {
if v == 0 {
code = field.nextField
} else {
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
e.encodeIndent(code.indent + 1)
e.encodeBytes(field.key)
e.encodeFloat64(v)
@ -1577,6 +1606,12 @@ func (e *Encoder) run(code *opcode) error {
if v == 0 {
code = field.nextField
} else {
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
e.encodeIndent(code.indent + 1)
e.encodeBytes(field.key)
e.encodeByte(' ')
@ -1728,7 +1763,14 @@ func (e *Encoder) run(code *opcode) error {
c := code.toStructFieldCode()
c.nextField.ptr = c.ptr
e.encodeBytes(c.key)
e.encodeFloat64(e.ptrToFloat64(c.ptr + c.offset))
v := e.ptrToFloat64(c.ptr + c.offset)
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
e.encodeFloat64(v)
code = code.next
case opStructFieldString:
e.encodeByte(',')
@ -1859,7 +1901,14 @@ func (e *Encoder) run(code *opcode) error {
e.encodeIndent(c.indent)
e.encodeBytes(c.key)
e.encodeByte(' ')
e.encodeFloat64(e.ptrToFloat64(c.ptr + c.offset))
v := e.ptrToFloat64(c.ptr + c.offset)
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
e.encodeFloat64(v)
code = code.next
c.nextField.ptr = c.ptr
case opStructFieldStringIndent:
@ -2030,6 +2079,12 @@ func (e *Encoder) run(code *opcode) error {
c := code.toStructFieldCode()
v := e.ptrToFloat64(c.ptr + c.offset)
if v != 0 {
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
if e.buf[len(e.buf)-1] != '{' {
e.encodeByte(',')
}
@ -2237,6 +2292,12 @@ func (e *Encoder) run(code *opcode) error {
c := code.toStructFieldCode()
v := e.ptrToFloat64(c.ptr + c.offset)
if v != 0 {
if math.IsInf(v, 0) || math.IsNaN(v) {
return &UnsupportedValueError{
Value: reflect.ValueOf(v),
Str: strconv.FormatFloat(v, 'g', -1, 64),
}
}
if e.buf[len(e.buf)-2] != '{' {
e.encodeBytes([]byte{',', '\n'})
}

120
error.go
View File

@ -1,8 +1,118 @@
package json
import "golang.org/x/xerrors"
var (
ErrUnsupportedType = xerrors.New("json: unsupported type")
ErrDecodePointer = xerrors.New("json: required pointer type")
import (
"fmt"
"reflect"
"strconv"
)
// Before Go 1.2, an InvalidUTF8Error was returned by Marshal when
// attempting to encode a string value with invalid UTF-8 sequences.
// As of Go 1.2, Marshal instead coerces the string to valid UTF-8 by
// replacing invalid bytes with the Unicode replacement rune U+FFFD.
//
// Deprecated: No longer used; kept for compatibility.
type InvalidUTF8Error struct {
S string // the whole string value that caused the error
}
func (e *InvalidUTF8Error) Error() string {
return fmt.Sprintf("json: invalid UTF-8 in string: %s", strconv.Quote(e.S))
}
// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
// (The argument to Unmarshal must be a non-nil pointer.)
type InvalidUnmarshalError struct {
Type reflect.Type
}
func (e *InvalidUnmarshalError) Error() string {
if e.Type == nil {
return "json: Unmarshal(nil)"
}
if e.Type.Kind() != reflect.Ptr {
return fmt.Sprintf("json: Unmarshal(non-pointer %s)", e.Type)
}
return fmt.Sprintf("json: Unmarshal(nil %s)", e.Type)
}
// A MarshalerError represents an error from calling a MarshalJSON or MarshalText method.
type MarshalerError struct {
Type reflect.Type
Err error
sourceFunc string
}
func (e *MarshalerError) Error() string {
srcFunc := e.sourceFunc
if srcFunc == "" {
srcFunc = "MarshalJSON"
}
return fmt.Sprintf("json: error calling %s for type %s: %s", srcFunc, e.Type, e.Err.Error())
}
// Unwrap returns the underlying error.
func (e *MarshalerError) Unwrap() error { return e.Err }
// A SyntaxError is a description of a JSON syntax error.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
// An UnmarshalFieldError describes a JSON object key that
// led to an unexported (and therefore unwritable) struct field.
//
// Deprecated: No longer used; kept for compatibility.
type UnmarshalFieldError struct {
Key string
Type reflect.Type
Field reflect.StructField
}
func (e *UnmarshalFieldError) Error() string {
return fmt.Sprintf("json: cannot unmarshal object key %s into unexported field %s of type %s",
strconv.Quote(e.Key), e.Field.Name, e.Type.String(),
)
}
// An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
Value string // description of JSON value - "bool", "array", "number -5"
Type reflect.Type // type of Go value it could not be assigned to
Offset int64 // error occurred after reading Offset bytes
Struct string // name of the struct type containing the field
Field string // the full path from root node to the field
}
func (e *UnmarshalTypeError) Error() string {
if e.Struct != "" || e.Field != "" {
return fmt.Sprintf("json: cannot unmarshal %s into Go struct field %s.%s of type %s",
e.Value, e.Struct, e.Field, e.Type,
)
}
return fmt.Sprintf("json: cannot unmarshal %s into Go value of type %s", e.Value, e.Type)
}
// An UnsupportedTypeError is returned by Marshal when attempting
// to encode an unsupported value type.
type UnsupportedTypeError struct {
Type reflect.Type
}
func (e *UnsupportedTypeError) Error() string {
return fmt.Sprintf("json: unsupported type: %s", e.Type)
}
type UnsupportedValueError struct {
Value reflect.Value
Str string
}
func (e *UnsupportedValueError) Error() string {
return fmt.Sprintf("json: unsupported value: %s", e.Str)
}

74
json.go
View File

@ -177,6 +177,80 @@ func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
return bytes, nil
}
// Unmarshal parses the JSON-encoded data and stores the result
// in the value pointed to by v. If v is nil or not a pointer,
// Unmarshal returns an InvalidUnmarshalError.
//
// Unmarshal uses the inverse of the encodings that
// Marshal uses, allocating maps, slices, and pointers as necessary,
// with the following additional rules:
//
// To unmarshal JSON into a pointer, Unmarshal first handles the case of
// the JSON being the JSON literal null. In that case, Unmarshal sets
// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into
// the value pointed at by the pointer. If the pointer is nil, Unmarshal
// allocates a new value for it to point to.
//
// To unmarshal JSON into a value implementing the Unmarshaler interface,
// Unmarshal calls that value's UnmarshalJSON method, including
// when the input is a JSON null.
// Otherwise, if the value implements encoding.TextUnmarshaler
// and the input is a JSON quoted string, Unmarshal calls that value's
// UnmarshalText method with the unquoted form of the string.
//
// To unmarshal JSON into a struct, Unmarshal matches incoming object
// keys to the keys used by Marshal (either the struct field name or its tag),
// preferring an exact match but also accepting a case-insensitive match. By
// default, object keys which don't have a corresponding struct field are
// ignored (see Decoder.DisallowUnknownFields for an alternative).
//
// To unmarshal JSON into an interface value,
// Unmarshal stores one of these in the interface value:
//
// bool, for JSON booleans
// float64, for JSON numbers
// string, for JSON strings
// []interface{}, for JSON arrays
// map[string]interface{}, for JSON objects
// nil for JSON null
//
// To unmarshal a JSON array into a slice, Unmarshal resets the slice length
// to zero and then appends each element to the slice.
// As a special case, to unmarshal an empty JSON array into a slice,
// Unmarshal replaces the slice with a new empty slice.
//
// To unmarshal a JSON array into a Go array, Unmarshal decodes
// JSON array elements into corresponding Go array elements.
// If the Go array is smaller than the JSON array,
// the additional JSON array elements are discarded.
// If the JSON array is smaller than the Go array,
// the additional Go array elements are set to zero values.
//
// To unmarshal a JSON object into a map, Unmarshal first establishes a map to
// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal
// reuses the existing map, keeping existing entries. Unmarshal then stores
// key-value pairs from the JSON object into the map. The map's key type must
// either be any string type, an integer, implement json.Unmarshaler, or
// implement encoding.TextUnmarshaler.
//
// If a JSON value is not appropriate for a given target type,
// or if a JSON number overflows the target type, Unmarshal
// skips that field and completes the unmarshaling as best it can.
// If no more serious errors are encountered, Unmarshal returns
// an UnmarshalTypeError describing the earliest such error. In any
// case, it's not guaranteed that all the remaining fields following
// the problematic one will be unmarshaled into the target object.
//
// The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean
// ``not present,'' unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error.
//
// When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement
// character U+FFFD.
//
func Unmarshal(data []byte, v interface{}) error {
src := make([]byte, len(data)+1) // append nul byte to end
copy(src, data)

View File

@ -246,6 +246,10 @@ func (t *rtype) escape() {
rtype_escape(t)
}
//go:linkname rtype2type reflect.toType
//go:noescape
func rtype2type(t *rtype) reflect.Type
type interfaceHeader struct {
typ *rtype
ptr unsafe.Pointer