diff --git a/decode.go b/decode.go index 4cee2ac..4b746d7 100644 --- a/decode.go +++ b/decode.go @@ -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)) + + // 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 { - // noescape trick for header.typ ( reflect.*rtype ) - copiedType := (*rtype)(unsafe.Pointer(typeptr)) 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) diff --git a/decode_test.go b/decode_test.go index ca975bb..c53569c 100644 --- a/decode_test.go +++ b/decode_test.go @@ -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) + }) +} diff --git a/encode_compile.go b/encode_compile.go index c3df340..899aae3 100644 --- a/encode_compile.go +++ b/encode_compile.go @@ -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 { diff --git a/encode_vm.go b/encode_vm.go index 1849359..f2235ea 100644 --- a/encode_vm.go +++ b/encode_vm.go @@ -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'}) } diff --git a/error.go b/error.go index 81d4778..359b467 100644 --- a/error.go +++ b/error.go @@ -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) +} diff --git a/json.go b/json.go index 3a81fb3..fea6348 100644 --- a/json.go +++ b/json.go @@ -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) diff --git a/rtype.go b/rtype.go index 9028a91..ca27668 100644 --- a/rtype.go +++ b/rtype.go @@ -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