diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4b93f1..e4b1d26f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# v0.9.9 - 2022/07/15 + +### Fix bugs + +* Fix encoding of directed interface with typed nil ( #377 ) +* Fix embedded primitive type encoding using alias ( #378 ) +* Fix slice/array type encoding with types implementing MarshalJSON ( #379 ) +* Fix unicode decoding when the expected buffer state is not met after reading ( #380 ) + # v0.9.8 - 2022/06/30 ### Fix bugs diff --git a/decode_test.go b/decode_test.go index f5e2563a..c276a324 100644 --- a/decode_test.go +++ b/decode_test.go @@ -3968,3 +3968,20 @@ func TestIssue335(t *testing.T) { t.Errorf("unexpected success") } } + +func TestIssue372(t *testing.T) { + type A int + type T struct { + _ int + *A + } + var v T + err := json.Unmarshal([]byte(`{"A":7}`), &v) + assertErr(t, err) + + got := *v.A + expected := A(7) + if got != expected { + t.Errorf("unexpected result: %v != %v", got, expected) + } +} diff --git a/encode_test.go b/encode_test.go index 59bb08ff..4989866e 100644 --- a/encode_test.go +++ b/encode_test.go @@ -7,12 +7,14 @@ import ( stdjson "encoding/json" "errors" "fmt" + "io" "log" "math" "math/big" "reflect" "regexp" "strconv" + "strings" "testing" "time" @@ -2406,3 +2408,63 @@ func TestIssue339(t *testing.T) { t.Errorf("unexpected result: %v != %v", got, expected) } } + +func TestIssue376(t *testing.T) { + type Container struct { + V interface{} `json:"value"` + } + type MapOnly struct { + Map map[string]int64 `json:"map"` + } + b, err := json.Marshal(Container{MapOnly{}}) + if err != nil { + t.Fatal(err) + } + got := string(b) + expected := `{"value":{"map":null}}` + if got != expected { + t.Errorf("unexpected result: %v != %v", got, expected) + } +} + +type Issue370 struct { + String string + Valid bool +} + +func (i *Issue370) MarshalJSON() ([]byte, error) { + if !i.Valid { + return json.Marshal(nil) + } + return json.Marshal(i.String) +} + +func TestIssue370(t *testing.T) { + v := []struct { + V Issue370 + }{ + {V: Issue370{String: "test", Valid: true}}, + } + b, err := json.Marshal(v) + if err != nil { + t.Fatal(err) + } + got := string(b) + expected := `[{"V":"test"}]` + if got != expected { + t.Errorf("unexpected result: %v != %v", got, expected) + } +} + +func TestIssue374(t *testing.T) { + r := io.MultiReader(strings.NewReader(strings.Repeat(" ", 505)+`"\u`), strings.NewReader(`0000"`)) + var v interface{} + if err := json.NewDecoder(r).Decode(&v); err != nil { + t.Fatal(err) + } + got := v.(string) + expected := "\u0000" + if got != expected { + t.Errorf("unexpected result: %q != %q", got, expected) + } +} diff --git a/internal/cmd/generator/vm.go.tmpl b/internal/cmd/generator/vm.go.tmpl index 91b11e1f..645d20f9 100644 --- a/internal/cmd/generator/vm.go.tmpl +++ b/internal/cmd/generator/vm.go.tmpl @@ -3,6 +3,7 @@ package vm import ( "math" + "reflect" "sort" "unsafe" @@ -194,9 +195,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b typ = iface.typ } if ifacePtr == nil { - b = appendNullComma(ctx, b) - code = code.Next - break + isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) + if !isDirectedNil { + b = appendNullComma(ctx, b) + code = code.Next + break + } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) diff --git a/internal/decoder/compile.go b/internal/decoder/compile.go index f13b43b8..48b02176 100644 --- a/internal/decoder/compile.go +++ b/internal/decoder/compile.go @@ -393,6 +393,15 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo } allFields = append(allFields, fieldSet) } + } else { + fieldSet := &structFieldSet{ + dec: pdec, + offset: field.Offset, + isTaggedKey: tag.IsTaggedKey, + key: field.Name, + keyLen: int64(len(field.Name)), + } + allFields = append(allFields, fieldSet) } } else { fieldSet := &structFieldSet{ diff --git a/internal/decoder/string.go b/internal/decoder/string.go index cef6688b..871ab3d7 100644 --- a/internal/decoder/string.go +++ b/internal/decoder/string.go @@ -95,24 +95,30 @@ func unicodeToRune(code []byte) rune { return r } +func readAtLeast(s *Stream, n int64, p *unsafe.Pointer) bool { + for s.cursor+n >= s.length { + if !s.read() { + return false + } + *p = s.bufptr() + } + return true +} + func decodeUnicodeRune(s *Stream, p unsafe.Pointer) (rune, int64, unsafe.Pointer, error) { const defaultOffset = 5 const surrogateOffset = 11 - if s.cursor+defaultOffset >= s.length { - if !s.read() { - return rune(0), 0, nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset()) - } - p = s.bufptr() + if !readAtLeast(s, defaultOffset, &p) { + return rune(0), 0, nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset()) } r := unicodeToRune(s.buf[s.cursor+1 : s.cursor+defaultOffset]) if utf16.IsSurrogate(r) { - if s.cursor+surrogateOffset >= s.length { - s.read() - p = s.bufptr() + if !readAtLeast(s, surrogateOffset, &p) { + return unicode.ReplacementChar, defaultOffset, p, nil } - if s.cursor+surrogateOffset >= s.length || s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' { + if s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' { return unicode.ReplacementChar, defaultOffset, p, nil } r2 := unicodeToRune(s.buf[s.cursor+defaultOffset+2 : s.cursor+surrogateOffset]) diff --git a/internal/encoder/compiler.go b/internal/encoder/compiler.go index de7323c8..64533f44 100644 --- a/internal/encoder/compiler.go +++ b/internal/encoder/compiler.go @@ -487,7 +487,10 @@ func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) { case typ.Kind() == reflect.Map: return c.ptrCode(runtime.PtrTo(typ)) default: - code, err := c.typeToCodeWithPtr(typ, false) + // isPtr was originally used to indicate whether the type of top level is pointer. + // However, since the slice/array element is a specification that can get the pointer address, explicitly set isPtr to true. + // See here for related issues: https://github.com/goccy/go-json/issues/370 + code, err := c.typeToCodeWithPtr(typ, true) if err != nil { return nil, err } diff --git a/internal/encoder/vm/vm.go b/internal/encoder/vm/vm.go index 91b11e1f..645d20f9 100644 --- a/internal/encoder/vm/vm.go +++ b/internal/encoder/vm/vm.go @@ -3,6 +3,7 @@ package vm import ( "math" + "reflect" "sort" "unsafe" @@ -194,9 +195,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b typ = iface.typ } if ifacePtr == nil { - b = appendNullComma(ctx, b) - code = code.Next - break + isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) + if !isDirectedNil { + b = appendNullComma(ctx, b) + code = code.Next + break + } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) diff --git a/internal/encoder/vm_color/vm.go b/internal/encoder/vm_color/vm.go index 5c6c52c3..a63e83e5 100644 --- a/internal/encoder/vm_color/vm.go +++ b/internal/encoder/vm_color/vm.go @@ -3,6 +3,7 @@ package vm_color import ( "math" + "reflect" "sort" "unsafe" @@ -194,9 +195,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b typ = iface.typ } if ifacePtr == nil { - b = appendNullComma(ctx, b) - code = code.Next - break + isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) + if !isDirectedNil { + b = appendNullComma(ctx, b) + code = code.Next + break + } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) diff --git a/internal/encoder/vm_color_indent/vm.go b/internal/encoder/vm_color_indent/vm.go index 42dc11ca..3b4e22e5 100644 --- a/internal/encoder/vm_color_indent/vm.go +++ b/internal/encoder/vm_color_indent/vm.go @@ -3,6 +3,7 @@ package vm_color_indent import ( "math" + "reflect" "sort" "unsafe" @@ -194,9 +195,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b typ = iface.typ } if ifacePtr == nil { - b = appendNullComma(ctx, b) - code = code.Next - break + isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) + if !isDirectedNil { + b = appendNullComma(ctx, b) + code = code.Next + break + } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ))) diff --git a/internal/encoder/vm_indent/vm.go b/internal/encoder/vm_indent/vm.go index dfe0cc64..836c5c8a 100644 --- a/internal/encoder/vm_indent/vm.go +++ b/internal/encoder/vm_indent/vm.go @@ -3,6 +3,7 @@ package vm_indent import ( "math" + "reflect" "sort" "unsafe" @@ -194,9 +195,12 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b typ = iface.typ } if ifacePtr == nil { - b = appendNullComma(ctx, b) - code = code.Next - break + isDirectedNil := typ != nil && typ.Kind() == reflect.Struct && !runtime.IfaceIndir(typ) + if !isDirectedNil { + b = appendNullComma(ctx, b) + code = code.Next + break + } } ctx.KeepRefs = append(ctx.KeepRefs, up) ifaceCodeSet, err := encoder.CompileToGetCodeSet(ctx, uintptr(unsafe.Pointer(typ)))