diff --git a/encode_test.go b/encode_test.go index 83075c5..358e51f 100644 --- a/encode_test.go +++ b/encode_test.go @@ -2108,3 +2108,29 @@ func TestEmbeddedNotFirstField(t *testing.T) { t.Fatalf("failed to encode embedded structure. expected = %q but got %q", expected, got) } } + +type implementedMethodIface interface { + M() +} + +type implementedIfaceType struct { + A int + B string +} + +func (implementedIfaceType) M() {} + +func TestImplementedMethodInterfaceType(t *testing.T) { + data := []implementedIfaceType{implementedIfaceType{}} + expected, err := stdjson.Marshal(data) + if err != nil { + t.Fatal(err) + } + got, err := json.Marshal(data) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(expected, got) { + t.Fatalf("failed to encode implemented method interface type. expected:[%q] but got:[%q]", expected, got) + } +} diff --git a/internal/cmd/generator/vm.go.tmpl b/internal/cmd/generator/vm.go.tmpl index 5c62354..5857249 100644 --- a/internal/cmd/generator/vm.go.tmpl +++ b/internal/cmd/generator/vm.go.tmpl @@ -7,6 +7,7 @@ import ( "unsafe" "github.com/goccy/go-json/internal/encoder" + "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { @@ -185,16 +186,28 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } ctx.SeenPtr = append(ctx.SeenPtr, p) - iface := (*emptyInterface)(ptrToUnsafePtr(p)) - if iface.ptr == nil { + var ( + typ *runtime.Type + ifacePtr unsafe.Pointer + ) + up := ptrToUnsafePtr(p) + if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { + iface := (*nonEmptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.itab.typ + } else { + iface := (*emptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.typ + } + if ifacePtr == nil { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } - - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(iface)) - ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(iface.typ))) + ctx.KeepRefs = append(ctx.KeepRefs, up) + ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } @@ -223,7 +236,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode - store(ctxptr, c.Idx, uintptr(iface.ptr)) + store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent)) diff --git a/internal/encoder/opcode.go b/internal/encoder/opcode.go index c23a90b..7c50eef 100644 --- a/internal/encoder/opcode.go +++ b/internal/encoder/opcode.go @@ -13,15 +13,16 @@ const uintptrSize = 4 << (^uintptr(0) >> 63) type OpFlags uint16 const ( - AnonymousHeadFlags OpFlags = 1 << 0 - AnonymousKeyFlags OpFlags = 1 << 1 - IndirectFlags OpFlags = 1 << 2 - IsTaggedKeyFlags OpFlags = 1 << 3 - NilCheckFlags OpFlags = 1 << 4 - AddrForMarshalerFlags OpFlags = 1 << 5 - IsNextOpPtrTypeFlags OpFlags = 1 << 6 - IsNilableTypeFlags OpFlags = 1 << 7 - MarshalerContextFlags OpFlags = 1 << 8 + AnonymousHeadFlags OpFlags = 1 << 0 + AnonymousKeyFlags OpFlags = 1 << 1 + IndirectFlags OpFlags = 1 << 2 + IsTaggedKeyFlags OpFlags = 1 << 3 + NilCheckFlags OpFlags = 1 << 4 + AddrForMarshalerFlags OpFlags = 1 << 5 + IsNextOpPtrTypeFlags OpFlags = 1 << 6 + IsNilableTypeFlags OpFlags = 1 << 7 + MarshalerContextFlags OpFlags = 1 << 8 + NonEmptyInterfaceFlags OpFlags = 1 << 9 ) type Opcode struct { @@ -743,6 +744,10 @@ func newMapEndCode(ctx *compileContext, head *Opcode) *Opcode { } func newInterfaceCode(ctx *compileContext) *Opcode { + var flag OpFlags + if ctx.typ.NumMethod() > 0 { + flag |= NonEmptyInterfaceFlags + } return &Opcode{ Op: OpInterface, Idx: opcodeOffset(ctx.ptrIndex), @@ -750,6 +755,7 @@ func newInterfaceCode(ctx *compileContext) *Opcode { Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, + Flags: flag, } } diff --git a/internal/encoder/vm/util.go b/internal/encoder/vm/util.go index c559447..f06f9c8 100644 --- a/internal/encoder/vm/util.go +++ b/internal/encoder/vm/util.go @@ -33,6 +33,15 @@ type emptyInterface struct { ptr unsafe.Pointer } +type nonEmptyInterface struct { + itab *struct { + ityp *runtime.Type // static interface type + typ *runtime.Type // dynamic concrete type + // unused fields... + } + ptr unsafe.Pointer +} + func errUnimplementedOp(op encoder.OpType) error { return fmt.Errorf("encoder: opcode %s has not been implemented", op) } diff --git a/internal/encoder/vm/vm.go b/internal/encoder/vm/vm.go index 5c62354..5857249 100644 --- a/internal/encoder/vm/vm.go +++ b/internal/encoder/vm/vm.go @@ -7,6 +7,7 @@ import ( "unsafe" "github.com/goccy/go-json/internal/encoder" + "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { @@ -185,16 +186,28 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } ctx.SeenPtr = append(ctx.SeenPtr, p) - iface := (*emptyInterface)(ptrToUnsafePtr(p)) - if iface.ptr == nil { + var ( + typ *runtime.Type + ifacePtr unsafe.Pointer + ) + up := ptrToUnsafePtr(p) + if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { + iface := (*nonEmptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.itab.typ + } else { + iface := (*emptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.typ + } + if ifacePtr == nil { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } - - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(iface)) - ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(iface.typ))) + ctx.KeepRefs = append(ctx.KeepRefs, up) + ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } @@ -223,7 +236,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode - store(ctxptr, c.Idx, uintptr(iface.ptr)) + store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent)) diff --git a/internal/encoder/vm_color/util.go b/internal/encoder/vm_color/util.go index 516536d..710087f 100644 --- a/internal/encoder/vm_color/util.go +++ b/internal/encoder/vm_color/util.go @@ -26,6 +26,15 @@ type emptyInterface struct { ptr unsafe.Pointer } +type nonEmptyInterface struct { + itab *struct { + ityp *runtime.Type // static interface type + typ *runtime.Type // dynamic concrete type + // unused fields... + } + ptr unsafe.Pointer +} + func errUnimplementedOp(op encoder.OpType) error { return fmt.Errorf("encoder: opcode %s has not been implemented", op) } diff --git a/internal/encoder/vm_color/vm.go b/internal/encoder/vm_color/vm.go index 71aa3f7..73af884 100644 --- a/internal/encoder/vm_color/vm.go +++ b/internal/encoder/vm_color/vm.go @@ -7,6 +7,7 @@ import ( "unsafe" "github.com/goccy/go-json/internal/encoder" + "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { @@ -185,16 +186,28 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } ctx.SeenPtr = append(ctx.SeenPtr, p) - iface := (*emptyInterface)(ptrToUnsafePtr(p)) - if iface.ptr == nil { + var ( + typ *runtime.Type + ifacePtr unsafe.Pointer + ) + up := ptrToUnsafePtr(p) + if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { + iface := (*nonEmptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.itab.typ + } else { + iface := (*emptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.typ + } + if ifacePtr == nil { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } - - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(iface)) - ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(iface.typ))) + ctx.KeepRefs = append(ctx.KeepRefs, up) + ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } @@ -223,7 +236,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode - store(ctxptr, c.Idx, uintptr(iface.ptr)) + store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent)) diff --git a/internal/encoder/vm_color_indent/util.go b/internal/encoder/vm_color_indent/util.go index 9f98781..1399a25 100644 --- a/internal/encoder/vm_color_indent/util.go +++ b/internal/encoder/vm_color_indent/util.go @@ -28,6 +28,15 @@ type emptyInterface struct { ptr unsafe.Pointer } +type nonEmptyInterface struct { + itab *struct { + ityp *runtime.Type // static interface type + typ *runtime.Type // dynamic concrete type + // unused fields... + } + ptr unsafe.Pointer +} + func errUnimplementedOp(op encoder.OpType) error { return fmt.Errorf("encoder (indent): opcode %s has not been implemented", op) } diff --git a/internal/encoder/vm_color_indent/vm.go b/internal/encoder/vm_color_indent/vm.go index 60d0083..7b7844e 100644 --- a/internal/encoder/vm_color_indent/vm.go +++ b/internal/encoder/vm_color_indent/vm.go @@ -7,6 +7,7 @@ import ( "unsafe" "github.com/goccy/go-json/internal/encoder" + "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { @@ -185,16 +186,28 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } ctx.SeenPtr = append(ctx.SeenPtr, p) - iface := (*emptyInterface)(ptrToUnsafePtr(p)) - if iface.ptr == nil { + var ( + typ *runtime.Type + ifacePtr unsafe.Pointer + ) + up := ptrToUnsafePtr(p) + if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { + iface := (*nonEmptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.itab.typ + } else { + iface := (*emptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.typ + } + if ifacePtr == nil { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } - - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(iface)) - ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(iface.typ))) + ctx.KeepRefs = append(ctx.KeepRefs, up) + ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } @@ -223,7 +236,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode - store(ctxptr, c.Idx, uintptr(iface.ptr)) + store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent)) diff --git a/internal/encoder/vm_indent/util.go b/internal/encoder/vm_indent/util.go index 5f5d8a5..2e3c35f 100644 --- a/internal/encoder/vm_indent/util.go +++ b/internal/encoder/vm_indent/util.go @@ -35,6 +35,15 @@ type emptyInterface struct { ptr unsafe.Pointer } +type nonEmptyInterface struct { + itab *struct { + ityp *runtime.Type // static interface type + typ *runtime.Type // dynamic concrete type + // unused fields... + } + ptr unsafe.Pointer +} + func errUnimplementedOp(op encoder.OpType) error { return fmt.Errorf("encoder (indent): opcode %s has not been implemented", op) } diff --git a/internal/encoder/vm_indent/vm.go b/internal/encoder/vm_indent/vm.go index e817484..6e05155 100644 --- a/internal/encoder/vm_indent/vm.go +++ b/internal/encoder/vm_indent/vm.go @@ -7,6 +7,7 @@ import ( "unsafe" "github.com/goccy/go-json/internal/encoder" + "github.com/goccy/go-json/internal/runtime" ) func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { @@ -185,16 +186,28 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } ctx.SeenPtr = append(ctx.SeenPtr, p) - iface := (*emptyInterface)(ptrToUnsafePtr(p)) - if iface.ptr == nil { + var ( + typ *runtime.Type + ifacePtr unsafe.Pointer + ) + up := ptrToUnsafePtr(p) + if code.Flags&encoder.NonEmptyInterfaceFlags != 0 { + iface := (*nonEmptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.itab.typ + } else { + iface := (*emptyInterface)(up) + ifacePtr = iface.ptr + typ = iface.typ + } + if ifacePtr == nil { b = appendNull(ctx, b) b = appendComma(ctx, b) code = code.Next break } - - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(iface)) - ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(iface.typ))) + ctx.KeepRefs = append(ctx.KeepRefs, up) + ifaceCodeSet, err := encoder.CompileToGetCodeSet(uintptr(unsafe.Pointer(typ))) if err != nil { return nil, err } @@ -223,7 +236,7 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b ctxptr = ctx.Ptr() + ptrOffset // assign new ctxptr end := ifaceCodeSet.EndCode - store(ctxptr, c.Idx, uintptr(iface.ptr)) + store(ctxptr, c.Idx, uintptr(ifacePtr)) store(ctxptr, end.Idx, oldOffset) store(ctxptr, end.ElemIdx, uintptr(unsafe.Pointer(code.Next))) storeIndent(ctxptr, end, uintptr(oldBaseIndent))