diff --git a/decoder/compiler.go b/decoder/compiler.go index 2779e7df5..bacd5f96c 100644 --- a/decoder/compiler.go +++ b/decoder/compiler.go @@ -679,12 +679,38 @@ func (self *_Compiler) compilePtr(p *_Program, sp int, et reflect.Type) { /* dereference all the way down */ for et.Kind() == reflect.Ptr { + if et.Implements(jsonUnmarshalerType) { + p.rtt(_OP_unmarshal_p, et) + return + } + + if et.Implements(encodingTextUnmarshalerType) { + p.add(_OP_lspace) + self.compileUnmarshalTextPtr(p, et) + return + } + et = et.Elem() p.rtt(_OP_deref, et) } - /* compile the element type */ - self.compileOne(p, sp + 1, et) + /* check for recursive nesting */ + ok := self.tab[et] + if ok { + p.rtt(_OP_recurse, et) + return + } + + /* enter the recursion */ + p.add(_OP_lspace) + self.tab[et] = true + + /* not inline the pointer type + * recursing the defined pointer type's elem will casue issue379. + */ + self.compileOps(p, sp, et) + delete(self.tab, et) + j := p.pc() p.add(_OP_goto) p.pin(i) diff --git a/decoder/decoder.go b/decoder/decoder.go index 5326f9728..19ad71965 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -17,6 +17,7 @@ package decoder import ( + `unsafe` `encoding/json` `reflect` `runtime` @@ -127,8 +128,17 @@ func (self *Decoder) Decode(val interface{}) error { return &json.InvalidUnmarshalError{Type: vv.Type.Pack()} } + etp := rt.PtrElem(vv.Type) + + /* check the defined pointer type for issue 379 */ + if vv.Type.IsNamed() { + newp := vp + etp = vv.Type + vp = unsafe.Pointer(&newp) + } + /* create a new stack, and call the decoder */ - sb, etp := newStack(), rt.PtrElem(vv.Type) + sb := newStack() nb, err := decodeTypedPointer(self.s, self.i, etp, vp, sb, self.f) /* return the stack back */ self.i = nb diff --git a/internal/rt/fastvalue.go b/internal/rt/fastvalue.go index 87df6b94a..2b2757f5b 100644 --- a/internal/rt/fastvalue.go +++ b/internal/rt/fastvalue.go @@ -22,14 +22,23 @@ import ( ) var ( - reflectRtypeItab = findReflectRtypeItab() + reflectRtypeItab = findReflectRtypeItab() ) +// GoType.KindFlags const const ( F_direct = 1 << 5 F_kind_mask = (1 << 5) - 1 ) +// GoType.Flags const +const ( + tflagUncommon uint8 = 1 << 0 + tflagExtraStar uint8 = 1 << 1 + tflagNamed uint8 = 1 << 2 + tflagRegularMemory uint8 = 1 << 3 +) + type GoType struct { Size uintptr PtrData uintptr @@ -44,6 +53,10 @@ type GoType struct { PtrToSelf int32 } +func (self *GoType) IsNamed() bool { + return (self.Flags & tflagNamed) != 0 +} + func (self *GoType) Kind() reflect.Kind { return reflect.Kind(self.KindFlags & F_kind_mask) } diff --git a/issue_test/issue379_test.go b/issue_test/issue379_test.go new file mode 100644 index 000000000..e132b0135 --- /dev/null +++ b/issue_test/issue379_test.go @@ -0,0 +1,99 @@ +/* + * Copyright 2023 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package issue_test + +import ( + `testing` + `encoding/json` + `github.com/bytedance/sonic` + `github.com/stretchr/testify/require` +) + +type Foo struct { + Name string +} + +func (f *Foo) UnmarshalJSON(data []byte) error { + f.Name = "Unmarshaler" + return nil +} + +type MyPtr *Foo + +func TestIssue379(t *testing.T) { + tests := []struct{ + data string + newf func() interface{} + } { + { + data: `{"Name":"MyPtr"}`, + newf: func() interface{} { return &Foo{} }, + }, + { + data: `{"Name":"MyPtr"}`, + newf: func() interface{} { return MyPtr(&Foo{}) }, + }, + { + data: `{"Name":"MyPtr"}`, + newf: func() interface{} { ptr := MyPtr(&Foo{}); return &ptr }, + }, + { + data: `null`, + newf: func() interface{} { return MyPtr(&Foo{}) }, + }, + { + data: `null`, + newf: func() interface{} { return &Foo{} }, + }, + { + data: `null`, + newf: func() interface{} { ptr := MyPtr(&Foo{}); return &ptr }, + }, + { + data: `{"map":{"Name":"MyPtr"}}`, + newf: func() interface{} { return new(map[string]MyPtr) }, + }, + { + data: `{"map":{"Name":"MyPtr"}}`, + newf: func() interface{} { return new(map[string]*Foo) }, + }, + { + data: `{"map":{"Name":"MyPtr"}}`, + newf: func() interface{} { return new(map[string]*MyPtr) }, + }, + { + data: `[{"Name":"MyPtr"}]`, + newf: func() interface{} { return new([]MyPtr) }, + }, + { + data: `[{"Name":"MyPtr"}]`, + newf: func() interface{} { return new([]*MyPtr) }, + }, + { + data: `[{"Name":"MyPtr"}]`, + newf: func() interface{} { return new([]*Foo) }, + }, + } + + for _, tt := range tests { + jv, sv := tt.newf(), tt.newf() + jerr := json.Unmarshal([]byte(tt.data), jv) + serr := sonic.Unmarshal([]byte(tt.data), sv) + require.Equal(t, jv, sv) + require.Equal(t, jerr, serr) + } +}