From b5e14784503b2b5c52d27cc10be101ce89d1d717 Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Sun, 28 Nov 2021 02:14:57 +0900 Subject: [PATCH] Refactor compiler for encoder - Introduced a two phase compilation to calculate Opcode index accurately - Fix display number of Opcode - Improve memory footprint for Opcode --- internal/encoder/code.go | 877 +++++++++++ internal/encoder/compiler.go | 1999 +++++++++---------------- internal/encoder/compiler_norace.go | 40 +- internal/encoder/compiler_race.go | 37 +- internal/encoder/context.go | 56 +- internal/encoder/opcode.go | 299 ++-- size_test.go | 4 +- test/cover/cover_array_test.go | 29 +- test/cover/cover_bool_test.go | 29 +- test/cover/cover_bytes_test.go | 29 +- test/cover/cover_float32_test.go | 29 +- test/cover/cover_float64_test.go | 29 +- test/cover/cover_int16_test.go | 29 +- test/cover/cover_int32_test.go | 29 +- test/cover/cover_int64_test.go | 29 +- test/cover/cover_int8_test.go | 29 +- test/cover/cover_int_test.go | 29 +- test/cover/cover_map_test.go | 29 +- test/cover/cover_marshal_json_test.go | 28 +- test/cover/cover_marshal_text_test.go | 29 +- test/cover/cover_number_test.go | 29 +- test/cover/cover_slice_test.go | 11 +- test/cover/cover_string_test.go | 29 +- test/cover/cover_uint16_test.go | 29 +- test/cover/cover_uint32_test.go | 29 +- test/cover/cover_uint64_test.go | 29 +- test/cover/cover_uint8_test.go | 29 +- test/cover/cover_uint_test.go | 29 +- 28 files changed, 2005 insertions(+), 1897 deletions(-) create mode 100644 internal/encoder/code.go diff --git a/internal/encoder/code.go b/internal/encoder/code.go new file mode 100644 index 0000000..c842b8b --- /dev/null +++ b/internal/encoder/code.go @@ -0,0 +1,877 @@ +package encoder + +import ( + "fmt" + "unsafe" + + "github.com/goccy/go-json/internal/runtime" +) + +type Code interface { + Kind() CodeKind + ToOpcode(*compileContext) Opcodes +} + +type AnonymousCode interface { + ToAnonymousOpcode(*compileContext) Opcodes +} + +type Opcodes []*Opcode + +func (o Opcodes) First() *Opcode { + if len(o) == 0 { + return nil + } + return o[0] +} + +func (o Opcodes) Last() *Opcode { + if len(o) == 0 { + return nil + } + return o[len(o)-1] +} + +func (o Opcodes) Add(codes ...*Opcode) Opcodes { + return append(o, codes...) +} + +type CodeKind int + +const ( + CodeKindInterface CodeKind = iota + CodeKindPtr + CodeKindInt + CodeKindUint + CodeKindFloat + CodeKindString + CodeKindBool + CodeKindStruct + CodeKindMap + CodeKindSlice + CodeKindArray + CodeKindBytes + CodeKindMarshalJSON + CodeKindMarshalText + CodeKindRecursive +) + +type IntCode struct { + typ *runtime.Type + bitSize uint8 + isString bool + isPtr bool +} + +func (c *IntCode) Kind() CodeKind { + return CodeKindInt +} + +func (c *IntCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, c.typ, OpIntPtr) + case c.isString: + code = newOpCode(ctx, c.typ, OpIntString) + default: + code = newOpCode(ctx, c.typ, OpInt) + } + code.NumBitSize = c.bitSize + ctx.incIndex() + return Opcodes{code} +} + +type UintCode struct { + typ *runtime.Type + bitSize uint8 + isString bool + isPtr bool +} + +func (c *UintCode) Kind() CodeKind { + return CodeKindUint +} + +func (c *UintCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, c.typ, OpUintPtr) + case c.isString: + code = newOpCode(ctx, c.typ, OpUintString) + default: + code = newOpCode(ctx, c.typ, OpUint) + } + code.NumBitSize = c.bitSize + ctx.incIndex() + return Opcodes{code} +} + +type FloatCode struct { + typ *runtime.Type + bitSize uint8 + isString bool + isPtr bool +} + +func (c *FloatCode) Kind() CodeKind { + return CodeKindFloat +} + +func (c *FloatCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + switch c.bitSize { + case 32: + code = newOpCode(ctx, c.typ, OpFloat32Ptr) + default: + code = newOpCode(ctx, c.typ, OpFloat64Ptr) + } + default: + switch c.bitSize { + case 32: + code = newOpCode(ctx, c.typ, OpFloat32) + default: + code = newOpCode(ctx, c.typ, OpFloat64) + } + } + ctx.incIndex() + return Opcodes{code} +} + +type StringCode struct { + typ *runtime.Type + isString bool + isPtr bool +} + +func (c *StringCode) Kind() CodeKind { + return CodeKindString +} + +func (c *StringCode) ToOpcode(ctx *compileContext) Opcodes { + isJsonNumberType := c.typ == runtime.Type2RType(jsonNumberType) + var code *Opcode + if c.isPtr { + if isJsonNumberType { + code = newOpCode(ctx, c.typ, OpNumberPtr) + } else { + code = newOpCode(ctx, c.typ, OpStringPtr) + } + } else { + if isJsonNumberType { + code = newOpCode(ctx, c.typ, OpNumber) + } else { + code = newOpCode(ctx, c.typ, OpString) + } + } + ctx.incIndex() + return Opcodes{code} +} + +type BoolCode struct { + typ *runtime.Type + isString bool + isPtr bool +} + +func (c *BoolCode) Kind() CodeKind { + return CodeKindBool +} + +func (c *BoolCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, c.typ, OpBoolPtr) + default: + code = newOpCode(ctx, c.typ, OpBool) + } + ctx.incIndex() + return Opcodes{code} +} + +type BytesCode struct { + typ *runtime.Type + isPtr bool +} + +func (c *BytesCode) Kind() CodeKind { + return CodeKindBytes +} + +func (c *BytesCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, c.typ, OpBytesPtr) + default: + code = newOpCode(ctx, c.typ, OpBytes) + } + ctx.incIndex() + return Opcodes{code} +} + +type SliceCode struct { + typ *runtime.Type + value Code +} + +func (c *SliceCode) Kind() CodeKind { + return CodeKindSlice +} + +func (c *SliceCode) ToOpcode(ctx *compileContext) Opcodes { + // header => opcode => elem => end + // ^ | + // |________| + size := c.typ.Elem().Size() + header := newSliceHeaderCode(ctx, c.typ) + ctx.incIndex() + + ctx.incIndent() + codes := c.value.ToOpcode(ctx) + ctx.decIndent() + + codes.First().Flags |= IndirectFlags + elemCode := newSliceElemCode(ctx, c.typ.Elem(), header, size) + ctx.incIndex() + end := newOpCode(ctx, c.typ, OpSliceEnd) + ctx.incIndex() + header.End = end + header.Next = codes.First() + codes.Last().Next = elemCode + elemCode.Next = codes.First() + elemCode.End = end + return Opcodes{header}.Add(codes...).Add(elemCode).Add(end) +} + +type ArrayCode struct { + typ *runtime.Type + value Code +} + +func (c *ArrayCode) Kind() CodeKind { + return CodeKindArray +} + +func (c *ArrayCode) ToOpcode(ctx *compileContext) Opcodes { + // header => opcode => elem => end + // ^ | + // |________| + elem := c.typ.Elem() + alen := c.typ.Len() + size := elem.Size() + + header := newArrayHeaderCode(ctx, c.typ, alen) + ctx.incIndex() + + ctx.incIndent() + codes := c.value.ToOpcode(ctx) + ctx.decIndent() + + codes.First().Flags |= IndirectFlags + + elemCode := newArrayElemCode(ctx, elem, header, alen, size) + ctx.incIndex() + + end := newOpCode(ctx, c.typ, OpArrayEnd) + ctx.incIndex() + + header.End = end + header.Next = codes.First() + codes.Last().Next = elemCode + elemCode.Next = codes.First() + elemCode.End = end + + return Opcodes{header}.Add(codes...).Add(elemCode).Add(end) +} + +type MapCode struct { + typ *runtime.Type + key Code + value Code +} + +func (c *MapCode) Kind() CodeKind { + return CodeKindMap +} + +func (c *MapCode) ToOpcode(ctx *compileContext) Opcodes { + // header => code => value => code => key => code => value => code => end + // ^ | + // |_______________________| + header := newMapHeaderCode(ctx, c.typ) + ctx.incIndex() + + keyCodes := c.key.ToOpcode(ctx) + + value := newMapValueCode(ctx, c.typ.Elem(), header) + ctx.incIndex() + + ctx.incIndent() + valueCodes := c.value.ToOpcode(ctx) + ctx.decIndent() + + valueCodes.First().Flags |= IndirectFlags + + key := newMapKeyCode(ctx, c.typ.Key(), header) + ctx.incIndex() + + end := newMapEndCode(ctx, c.typ, header) + ctx.incIndex() + + header.Next = keyCodes.First() + keyCodes.Last().Next = value + value.Next = valueCodes.First() + valueCodes.Last().Next = key + key.Next = keyCodes.First() + + header.End = end + key.End = end + value.End = end + return Opcodes{header}.Add(keyCodes...).Add(value).Add(valueCodes...).Add(key).Add(end) +} + +type StructCode struct { + typ *runtime.Type + isPtr bool + fields []*StructFieldCode + disableIndirectConversion bool + isIndirect bool + isRecursive bool + recursiveCodes Opcodes +} + +func (c *StructCode) Kind() CodeKind { + return CodeKindStruct +} + +func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *Opcode { + if field.isAnonymous { + return c.lastAnonymousFieldCode(firstField) + } + lastField := firstField + for lastField.NextField != nil { + lastField = lastField.NextField + } + return lastField +} + +func (c *StructCode) lastAnonymousFieldCode(firstField *Opcode) *Opcode { + // firstField is special StructHead operation for anonymous structure. + // So, StructHead's next operation is truely struct head operation. + lastField := firstField.Next + for lastField.NextField != nil { + lastField = lastField.NextField + } + return lastField +} + +func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes { + // header => code => structField => code => end + // ^ | + // |__________| + if c.isRecursive { + recursive := newRecursiveCode(ctx, c.typ, &CompiledCode{}) + recursive.Type = c.typ + ctx.incIndex() + *ctx.recursiveCodes = append(*ctx.recursiveCodes, recursive) + return Opcodes{recursive} + } + codes := Opcodes{} + var prevField *Opcode + ctx.incIndent() + for idx, field := range c.fields { + isFirstField := idx == 0 + isEndField := idx == len(c.fields)-1 + fieldCodes := field.ToOpcode(ctx, isFirstField, isEndField) + for _, code := range fieldCodes { + if c.isIndirect { + code.Flags |= IndirectFlags + } + } + firstField := fieldCodes.First() + if len(codes) > 0 { + codes.Last().Next = firstField + firstField.Idx = codes.First().Idx + } + if prevField != nil { + prevField.NextField = firstField + } + if isEndField { + endField := fieldCodes.Last() + if len(codes) > 0 { + codes.First().End = endField + } else if field.isAnonymous { + firstField.End = endField + lastField := c.lastAnonymousFieldCode(firstField) + lastField.NextField = endField + } else { + firstField.End = endField + } + codes = codes.Add(fieldCodes...) + break + } + prevField = c.lastFieldCode(field, firstField) + codes = codes.Add(fieldCodes...) + } + if len(codes) == 0 { + head := &Opcode{ + Op: OpStructHead, + Idx: opcodeOffset(ctx.ptrIndex), + Type: c.typ, + DisplayIdx: ctx.opcodeIndex, + Indent: ctx.indent, + } + ctx.incOpcodeIndex() + end := &Opcode{ + Op: OpStructEnd, + Idx: opcodeOffset(ctx.ptrIndex), + DisplayIdx: ctx.opcodeIndex, + Indent: ctx.indent, + } + head.NextField = end + head.Next = end + head.End = end + codes = codes.Add(head, end) + ctx.incIndex() + } + ctx.decIndent() + ctx.structTypeToCodes[uintptr(unsafe.Pointer(c.typ))] = codes + return codes +} + +func (c *StructCode) ToAnonymousOpcode(ctx *compileContext) Opcodes { + // header => code => structField => code => end + // ^ | + // |__________| + if c.isRecursive { + recursive := newRecursiveCode(ctx, c.typ, &CompiledCode{}) + recursive.Type = c.typ + ctx.incIndex() + *ctx.recursiveCodes = append(*ctx.recursiveCodes, recursive) + return Opcodes{recursive} + } + codes := Opcodes{} + var prevField *Opcode + for idx, field := range c.fields { + isFirstField := idx == 0 + isEndField := idx == len(c.fields)-1 + fieldCodes := field.ToAnonymousOpcode(ctx, isFirstField, isEndField) + for _, code := range fieldCodes { + if c.isIndirect { + code.Flags |= IndirectFlags + } + } + firstField := fieldCodes.First() + if len(codes) > 0 { + codes.Last().Next = firstField + firstField.Idx = codes.First().Idx + } + if prevField != nil { + prevField.NextField = firstField + } + if isEndField { + lastField := fieldCodes.Last() + if len(codes) > 0 { + codes.First().End = lastField + } else { + firstField.End = lastField + } + } + prevField = firstField + codes = codes.Add(fieldCodes...) + } + return codes +} + +func (c *StructCode) removeFieldsByTags(tags runtime.StructTags) { + fields := make([]*StructFieldCode, 0, len(c.fields)) + for _, field := range c.fields { + if field.isAnonymous { + structCode := field.getAnonymousStruct() + if structCode != nil && !structCode.isRecursive { + structCode.removeFieldsByTags(tags) + if len(structCode.fields) > 0 { + fields = append(fields, field) + } + continue + } + } + if tags.ExistsKey(field.key) { + continue + } + fields = append(fields, field) + } + c.fields = fields +} + +func (c *StructCode) enableIndirect() { + if c.isIndirect { + return + } + c.isIndirect = true + if len(c.fields) == 0 { + return + } + structCode := c.fields[0].getStruct() + if structCode == nil { + return + } + structCode.enableIndirect() +} + +type StructFieldCode struct { + typ *runtime.Type + key string + tag *runtime.StructTag + value Code + offset uintptr + isAnonymous bool + isTaggedKey bool + isNilableType bool + isNilCheck bool + isAddrForMarshaler bool + isNextOpPtrType bool +} + +func (c *StructFieldCode) getStruct() *StructCode { + value := c.value + ptr, ok := value.(*PtrCode) + if ok { + value = ptr.value + } + structCode, ok := value.(*StructCode) + if ok { + return structCode + } + return nil +} + +func (c *StructFieldCode) getAnonymousStruct() *StructCode { + if !c.isAnonymous { + return nil + } + return c.getStruct() +} + +func optimizeStructHeader(code *Opcode, tag *runtime.StructTag) OpType { + headType := code.ToHeaderType(tag.IsString) + if tag.IsOmitEmpty { + headType = headType.HeadToOmitEmptyHead() + } + return headType +} + +func optimizeStructField(code *Opcode, tag *runtime.StructTag) OpType { + fieldType := code.ToFieldType(tag.IsString) + if tag.IsOmitEmpty { + fieldType = fieldType.FieldToOmitEmptyField() + } + return fieldType +} + +func (c *StructFieldCode) headerOpcodes(ctx *compileContext, field *Opcode, valueCodes Opcodes) Opcodes { + value := valueCodes.First() + op := optimizeStructHeader(value, c.tag) + field.Op = op + field.NumBitSize = value.NumBitSize + field.PtrNum = value.PtrNum + fieldCodes := Opcodes{field} + if op.IsMultipleOpHead() { + field.Next = value + fieldCodes = fieldCodes.Add(valueCodes...) + } else { + ctx.decIndex() + } + return fieldCodes +} + +func (c *StructFieldCode) fieldOpcodes(ctx *compileContext, field *Opcode, valueCodes Opcodes) Opcodes { + value := valueCodes.First() + op := optimizeStructField(value, c.tag) + field.Op = op + field.NumBitSize = value.NumBitSize + field.PtrNum = value.PtrNum + + fieldCodes := Opcodes{field} + if op.IsMultipleOpField() { + field.Next = value + fieldCodes = fieldCodes.Add(valueCodes...) + } else { + ctx.decIndex() + } + return fieldCodes +} + +func (c *StructFieldCode) addStructEndCode(ctx *compileContext, codes Opcodes) Opcodes { + end := &Opcode{ + Op: OpStructEnd, + Idx: opcodeOffset(ctx.ptrIndex), + DisplayIdx: ctx.opcodeIndex, + Indent: ctx.indent, + } + codes.Last().Next = end + codes.First().NextField = end + codes = codes.Add(end) + ctx.incOpcodeIndex() + return codes +} + +func (c *StructFieldCode) structKey(ctx *compileContext) string { + if ctx.escapeKey { + rctx := &RuntimeContext{Option: &Option{Flag: HTMLEscapeOption}} + return fmt.Sprintf(`%s:`, string(AppendString(rctx, []byte{}, c.key))) + } + return fmt.Sprintf(`"%s":`, c.key) +} + +func (c *StructFieldCode) flags() OpFlags { + var flags OpFlags + if c.isTaggedKey { + flags |= IsTaggedKeyFlags + } + if c.isNilableType { + flags |= IsNilableTypeFlags + } + if c.isNilCheck { + flags |= NilCheckFlags + } + if c.isAddrForMarshaler { + flags |= AddrForMarshalerFlags + } + if c.isNextOpPtrType { + flags |= IsNextOpPtrTypeFlags + } + if c.isAnonymous { + flags |= AnonymousKeyFlags + } + return flags +} + +func (c *StructFieldCode) toValueOpcodes(ctx *compileContext) Opcodes { + if c.isAnonymous { + anonymCode, ok := c.value.(AnonymousCode) + if ok { + return anonymCode.ToAnonymousOpcode(ctx) + } + } + return c.value.ToOpcode(ctx) +} + +func (c *StructFieldCode) ToOpcode(ctx *compileContext, isFirstField, isEndField bool) Opcodes { + field := &Opcode{ + Idx: opcodeOffset(ctx.ptrIndex), + Flags: c.flags(), + Key: c.structKey(ctx), + Offset: uint32(c.offset), + Type: c.typ, + DisplayIdx: ctx.opcodeIndex, + Indent: ctx.indent, + DisplayKey: c.key, + } + ctx.incIndex() + valueCodes := c.toValueOpcodes(ctx) + if isFirstField { + codes := c.headerOpcodes(ctx, field, valueCodes) + if isEndField { + codes = c.addStructEndCode(ctx, codes) + } + return codes + } + codes := c.fieldOpcodes(ctx, field, valueCodes) + if isEndField { + if isEnableStructEndOptimizationType(c.value.Kind()) { + field.Op = field.Op.FieldToEnd() + } else { + codes = c.addStructEndCode(ctx, codes) + } + } + return codes +} + +func (c *StructFieldCode) ToAnonymousOpcode(ctx *compileContext, isFirstField, isEndField bool) Opcodes { + field := &Opcode{ + Idx: opcodeOffset(ctx.ptrIndex), + Flags: c.flags() | AnonymousHeadFlags, + Key: c.structKey(ctx), + Offset: uint32(c.offset), + Type: c.typ, + DisplayIdx: ctx.opcodeIndex, + Indent: ctx.indent, + DisplayKey: c.key, + } + ctx.incIndex() + valueCodes := c.toValueOpcodes(ctx) + if isFirstField { + return c.headerOpcodes(ctx, field, valueCodes) + } + return c.fieldOpcodes(ctx, field, valueCodes) +} + +func isEnableStructEndOptimizationType(typ CodeKind) bool { + switch typ { + case CodeKindInt, + CodeKindUint, + CodeKindFloat, + CodeKindString, + CodeKindBool, + CodeKindBytes: + return true + default: + return false + } +} + +type InterfaceCode struct { + typ *runtime.Type + isPtr bool +} + +func (c *InterfaceCode) Kind() CodeKind { + return CodeKindInterface +} + +func (c *InterfaceCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, c.typ, OpInterfacePtr) + default: + code = newOpCode(ctx, c.typ, OpInterface) + } + if c.typ.NumMethod() > 0 { + code.Flags |= NonEmptyInterfaceFlags + } + ctx.incIndex() + return Opcodes{code} +} + +type MarshalJSONCode struct { + typ *runtime.Type + isAddrForMarshaler bool + isNilableType bool + isMarshalerContext bool +} + +func (c *MarshalJSONCode) Kind() CodeKind { + return CodeKindMarshalJSON +} + +func (c *MarshalJSONCode) ToOpcode(ctx *compileContext) Opcodes { + code := newOpCode(ctx, c.typ, OpMarshalJSON) + if c.isAddrForMarshaler { + code.Flags |= AddrForMarshalerFlags + } + if c.isMarshalerContext { + code.Flags |= MarshalerContextFlags + } + if c.isNilableType { + code.Flags |= IsNilableTypeFlags + } else { + code.Flags &= ^IsNilableTypeFlags + } + ctx.incIndex() + return Opcodes{code} +} + +type MarshalTextCode struct { + typ *runtime.Type + isAddrForMarshaler bool + isNilableType bool +} + +func (c *MarshalTextCode) Kind() CodeKind { + return CodeKindMarshalText +} + +func (c *MarshalTextCode) ToOpcode(ctx *compileContext) Opcodes { + code := newOpCode(ctx, c.typ, OpMarshalText) + if c.isAddrForMarshaler { + code.Flags |= AddrForMarshalerFlags + } + if c.isNilableType { + code.Flags |= IsNilableTypeFlags + } else { + code.Flags &= ^IsNilableTypeFlags + } + ctx.incIndex() + return Opcodes{code} +} + +type PtrCode struct { + typ *runtime.Type + value Code + ptrNum uint8 +} + +func (c *PtrCode) Kind() CodeKind { + return CodeKindPtr +} + +func (c *PtrCode) ToOpcode(ctx *compileContext) Opcodes { + codes := c.value.ToOpcode(ctx) + codes.First().Op = convertPtrOp(codes.First()) + codes.First().PtrNum = c.ptrNum + return codes +} + +func (c *PtrCode) ToAnonymousOpcode(ctx *compileContext) Opcodes { + var codes Opcodes + anonymCode, ok := c.value.(AnonymousCode) + if ok { + codes = anonymCode.ToAnonymousOpcode(ctx) + } else { + codes = c.value.ToOpcode(ctx) + } + codes.First().Op = convertPtrOp(codes.First()) + codes.First().PtrNum = c.ptrNum + return codes +} + +func convertPtrOp(code *Opcode) OpType { + ptrHeadOp := code.Op.HeadToPtrHead() + if code.Op != ptrHeadOp { + if code.PtrNum > 0 { + // ptr field and ptr head + code.PtrNum-- + } + return ptrHeadOp + } + switch code.Op { + case OpInt: + return OpIntPtr + case OpUint: + return OpUintPtr + case OpFloat32: + return OpFloat32Ptr + case OpFloat64: + return OpFloat64Ptr + case OpString: + return OpStringPtr + case OpBool: + return OpBoolPtr + case OpBytes: + return OpBytesPtr + case OpNumber: + return OpNumberPtr + case OpArray: + return OpArrayPtr + case OpSlice: + return OpSlicePtr + case OpMap: + return OpMapPtr + case OpMarshalJSON: + return OpMarshalJSONPtr + case OpMarshalText: + return OpMarshalTextPtr + case OpInterface: + return OpInterfacePtr + case OpRecursive: + return OpRecursivePtr + } + return code.Op +} diff --git a/internal/encoder/compiler.go b/internal/encoder/compiler.go index 9fcec21..a7e6e99 100644 --- a/internal/encoder/compiler.go +++ b/internal/encoder/compiler.go @@ -4,9 +4,7 @@ import ( "context" "encoding" "encoding/json" - "fmt" "reflect" - "strings" "sync/atomic" "unsafe" @@ -57,25 +55,43 @@ func compileToGetCodeSetSlowPath(typeptr uintptr) (*OpcodeSet, error) { if codeSet, exists := opcodeMap[typeptr]; exists { return codeSet, nil } + codeSet, err := newCompiler().compile(typeptr) + if err != nil { + return nil, err + } + storeOpcodeSet(typeptr, codeSet, opcodeMap) + return codeSet, nil +} +type Compiler struct { + structTypeToCode map[uintptr]*StructCode +} + +func newCompiler() *Compiler { + return &Compiler{ + structTypeToCode: map[uintptr]*StructCode{}, + } +} + +func (c *Compiler) compile(typeptr uintptr) (*OpcodeSet, error) { // noescape trick for header.typ ( reflect.*rtype ) - copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr)) - - noescapeKeyCode, err := compileHead(&compileContext{ - typ: copiedType, - structTypeToCompiledCode: map[uintptr]*CompiledCode{}, - }) + typ := *(**runtime.Type)(unsafe.Pointer(&typeptr)) + code, err := c.typeToCode(typ) if err != nil { return nil, err } - escapeKeyCode, err := compileHead(&compileContext{ - typ: copiedType, - structTypeToCompiledCode: map[uintptr]*CompiledCode{}, - escapeKey: true, - }) - if err != nil { + noescapeKeyCode := c.codeToOpcode(&compileContext{ + structTypeToCodes: map[uintptr]Opcodes{}, + recursiveCodes: &Opcodes{}, + }, typ, code) + if err := noescapeKeyCode.Validate(); err != nil { return nil, err } + escapeKeyCode := c.codeToOpcode(&compileContext{ + structTypeToCodes: map[uintptr]Opcodes{}, + recursiveCodes: &Opcodes{}, + escapeKey: true, + }, typ, code) noescapeKeyCode = copyOpcode(noescapeKeyCode) escapeKeyCode = copyOpcode(escapeKeyCode) setTotalLengthToInterfaceOp(noescapeKeyCode) @@ -83,26 +99,23 @@ func compileToGetCodeSetSlowPath(typeptr uintptr) (*OpcodeSet, error) { interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode) interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode) codeLength := noescapeKeyCode.TotalLength() - codeSet := &OpcodeSet{ - Type: copiedType, + return &OpcodeSet{ + Type: typ, NoescapeKeyCode: noescapeKeyCode, EscapeKeyCode: escapeKeyCode, InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode, InterfaceEscapeKeyCode: interfaceEscapeKeyCode, CodeLength: codeLength, EndCode: ToEndCode(interfaceNoescapeKeyCode), - } - storeOpcodeSet(typeptr, codeSet, opcodeMap) - return codeSet, nil + }, nil } -func compileHead(ctx *compileContext) (*Opcode, error) { - typ := ctx.typ +func (c *Compiler) typeToCode(typ *runtime.Type) (Code, error) { switch { - case implementsMarshalJSON(typ): - return compileMarshalJSON(ctx) - case implementsMarshalText(typ): - return compileMarshalText(ctx) + case c.implementsMarshalJSON(typ): + return c.marshalJSONCode(typ) + case c.implementsMarshalText(typ): + return c.marshalTextCode(typ) } isPtr := false @@ -112,262 +125,664 @@ func compileHead(ctx *compileContext) (*Opcode, error) { isPtr = true } switch { - case implementsMarshalJSON(typ): - return compileMarshalJSON(ctx) - case implementsMarshalText(typ): - return compileMarshalText(ctx) + case c.implementsMarshalJSON(typ): + return c.marshalJSONCode(orgType) + case c.implementsMarshalText(typ): + return c.marshalTextCode(orgType) } switch typ.Kind() { case reflect.Slice: - ctx := ctx.withType(typ) elem := typ.Elem() if elem.Kind() == reflect.Uint8 { p := runtime.PtrTo(elem) - if !implementsMarshalJSONType(p) && !p.Implements(marshalTextType) { - if isPtr { - return compileBytesPtr(ctx) - } - return compileBytes(ctx) + if !c.implementsMarshalJSONType(p) && !p.Implements(marshalTextType) { + return c.bytesCode(typ, isPtr) } } - code, err := compileSlice(ctx) - if err != nil { - return nil, err - } - optimizeStructEnd(code) - linkRecursiveCode(code) - return code, nil + return c.sliceCode(typ) case reflect.Map: if isPtr { - return compilePtr(ctx.withType(runtime.PtrTo(typ))) + return c.ptrCode(runtime.PtrTo(typ)) } - code, err := compileMap(ctx.withType(typ)) - if err != nil { - return nil, err - } - optimizeStructEnd(code) - linkRecursiveCode(code) - return code, nil + return c.mapCode(typ) case reflect.Struct: - code, err := compileStruct(ctx.withType(typ), isPtr) - if err != nil { - return nil, err - } - optimizeStructEnd(code) - linkRecursiveCode(code) - return code, nil + return c.structCode(typ, isPtr) case reflect.Int: - ctx := ctx.withType(typ) - if isPtr { - return compileIntPtr(ctx) - } - return compileInt(ctx) + return c.intCode(typ, isPtr) case reflect.Int8: - ctx := ctx.withType(typ) - if isPtr { - return compileInt8Ptr(ctx) - } - return compileInt8(ctx) + return c.int8Code(typ, isPtr) case reflect.Int16: - ctx := ctx.withType(typ) - if isPtr { - return compileInt16Ptr(ctx) - } - return compileInt16(ctx) + return c.int16Code(typ, isPtr) case reflect.Int32: - ctx := ctx.withType(typ) - if isPtr { - return compileInt32Ptr(ctx) - } - return compileInt32(ctx) + return c.int32Code(typ, isPtr) case reflect.Int64: - ctx := ctx.withType(typ) - if isPtr { - return compileInt64Ptr(ctx) - } - return compileInt64(ctx) + return c.int64Code(typ, isPtr) case reflect.Uint, reflect.Uintptr: - ctx := ctx.withType(typ) - if isPtr { - return compileUintPtr(ctx) - } - return compileUint(ctx) + return c.uintCode(typ, isPtr) case reflect.Uint8: - ctx := ctx.withType(typ) - if isPtr { - return compileUint8Ptr(ctx) - } - return compileUint8(ctx) + return c.uint8Code(typ, isPtr) case reflect.Uint16: - ctx := ctx.withType(typ) - if isPtr { - return compileUint16Ptr(ctx) - } - return compileUint16(ctx) + return c.uint16Code(typ, isPtr) case reflect.Uint32: - ctx := ctx.withType(typ) - if isPtr { - return compileUint32Ptr(ctx) - } - return compileUint32(ctx) + return c.uint32Code(typ, isPtr) case reflect.Uint64: - ctx := ctx.withType(typ) - if isPtr { - return compileUint64Ptr(ctx) - } - return compileUint64(ctx) + return c.uint64Code(typ, isPtr) case reflect.Float32: - ctx := ctx.withType(typ) - if isPtr { - return compileFloat32Ptr(ctx) - } - return compileFloat32(ctx) + return c.float32Code(typ, isPtr) case reflect.Float64: - ctx := ctx.withType(typ) - if isPtr { - return compileFloat64Ptr(ctx) - } - return compileFloat64(ctx) + return c.float64Code(typ, isPtr) case reflect.String: - ctx := ctx.withType(typ) - if isPtr { - return compileStringPtr(ctx) - } - return compileString(ctx) + return c.stringCode(typ, isPtr) case reflect.Bool: - ctx := ctx.withType(typ) - if isPtr { - return compileBoolPtr(ctx) - } - return compileBool(ctx) + return c.boolCode(typ, isPtr) case reflect.Interface: - ctx := ctx.withType(typ) - if isPtr { - return compileInterfacePtr(ctx) - } - return compileInterface(ctx) + return c.interfaceCode(typ, isPtr) default: if isPtr && typ.Implements(marshalTextType) { typ = orgType } - code, err := compile(ctx.withType(typ), isPtr) + return c.typeToCodeWithPtr(typ, isPtr) + } +} + +func (c *Compiler) typeToCodeWithPtr(typ *runtime.Type, isPtr bool) (Code, error) { + switch { + case c.implementsMarshalJSON(typ): + return c.marshalJSONCode(typ) + case c.implementsMarshalText(typ): + return c.marshalTextCode(typ) + } + switch typ.Kind() { + case reflect.Ptr: + return c.ptrCode(typ) + case reflect.Slice: + elem := typ.Elem() + if elem.Kind() == reflect.Uint8 { + p := runtime.PtrTo(elem) + if !c.implementsMarshalJSONType(p) && !p.Implements(marshalTextType) { + return c.bytesCode(typ, false) + } + } + return c.sliceCode(typ) + case reflect.Array: + return c.arrayCode(typ) + case reflect.Map: + return c.mapCode(typ) + case reflect.Struct: + return c.structCode(typ, isPtr) + case reflect.Interface: + return c.interfaceCode(typ, false) + case reflect.Int: + return c.intCode(typ, false) + case reflect.Int8: + return c.int8Code(typ, false) + case reflect.Int16: + return c.int16Code(typ, false) + case reflect.Int32: + return c.int32Code(typ, false) + case reflect.Int64: + return c.int64Code(typ, false) + case reflect.Uint: + return c.uintCode(typ, false) + case reflect.Uint8: + return c.uint8Code(typ, false) + case reflect.Uint16: + return c.uint16Code(typ, false) + case reflect.Uint32: + return c.uint32Code(typ, false) + case reflect.Uint64: + return c.uint64Code(typ, false) + case reflect.Uintptr: + return c.uintCode(typ, false) + case reflect.Float32: + return c.float32Code(typ, false) + case reflect.Float64: + return c.float64Code(typ, false) + case reflect.String: + return c.stringCode(typ, false) + case reflect.Bool: + return c.boolCode(typ, false) + } + return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)} +} + +const intSize = 32 << (^uint(0) >> 63) + +func (c *Compiler) intCode(typ *runtime.Type, isPtr bool) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: intSize, isPtr: isPtr}, nil +} + +func (c *Compiler) int8Code(typ *runtime.Type, isPtr bool) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: 8, isPtr: isPtr}, nil +} + +func (c *Compiler) int16Code(typ *runtime.Type, isPtr bool) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: 16, isPtr: isPtr}, nil +} + +func (c *Compiler) int32Code(typ *runtime.Type, isPtr bool) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: 32, isPtr: isPtr}, nil +} + +func (c *Compiler) int64Code(typ *runtime.Type, isPtr bool) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: 64, isPtr: isPtr}, nil +} + +func (c *Compiler) uintCode(typ *runtime.Type, isPtr bool) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: intSize, isPtr: isPtr}, nil +} + +func (c *Compiler) uint8Code(typ *runtime.Type, isPtr bool) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: 8, isPtr: isPtr}, nil +} + +func (c *Compiler) uint16Code(typ *runtime.Type, isPtr bool) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: 16, isPtr: isPtr}, nil +} + +func (c *Compiler) uint32Code(typ *runtime.Type, isPtr bool) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: 32, isPtr: isPtr}, nil +} + +func (c *Compiler) uint64Code(typ *runtime.Type, isPtr bool) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: 64, isPtr: isPtr}, nil +} + +func (c *Compiler) float32Code(typ *runtime.Type, isPtr bool) (*FloatCode, error) { + return &FloatCode{typ: typ, bitSize: 32, isPtr: isPtr}, nil +} + +func (c *Compiler) float64Code(typ *runtime.Type, isPtr bool) (*FloatCode, error) { + return &FloatCode{typ: typ, bitSize: 64, isPtr: isPtr}, nil +} + +func (c *Compiler) stringCode(typ *runtime.Type, isPtr bool) (*StringCode, error) { + return &StringCode{typ: typ, isPtr: isPtr}, nil +} + +func (c *Compiler) boolCode(typ *runtime.Type, isPtr bool) (*BoolCode, error) { + return &BoolCode{typ: typ, isPtr: isPtr}, nil +} + +func (c *Compiler) intStringCode(typ *runtime.Type) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: intSize, isString: true}, nil +} + +func (c *Compiler) int8StringCode(typ *runtime.Type) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: 8, isString: true}, nil +} + +func (c *Compiler) int16StringCode(typ *runtime.Type) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: 16, isString: true}, nil +} + +func (c *Compiler) int32StringCode(typ *runtime.Type) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: 32, isString: true}, nil +} + +func (c *Compiler) int64StringCode(typ *runtime.Type) (*IntCode, error) { + return &IntCode{typ: typ, bitSize: 64, isString: true}, nil +} + +func (c *Compiler) uintStringCode(typ *runtime.Type) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: intSize, isString: true}, nil +} + +func (c *Compiler) uint8StringCode(typ *runtime.Type) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: 8, isString: true}, nil +} + +func (c *Compiler) uint16StringCode(typ *runtime.Type) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: 16, isString: true}, nil +} + +func (c *Compiler) uint32StringCode(typ *runtime.Type) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: 32, isString: true}, nil +} + +func (c *Compiler) uint64StringCode(typ *runtime.Type) (*UintCode, error) { + return &UintCode{typ: typ, bitSize: 64, isString: true}, nil +} + +func (c *Compiler) sliceCode(typ *runtime.Type) (*SliceCode, error) { + elem := typ.Elem() + code, err := c.listElemCode(elem) + if err != nil { + return nil, err + } + if code.Kind() == CodeKindStruct { + structCode := code.(*StructCode) + structCode.enableIndirect() + } + return &SliceCode{typ: typ, value: code}, nil +} + +func (c *Compiler) arrayCode(typ *runtime.Type) (*ArrayCode, error) { + elem := typ.Elem() + code, err := c.listElemCode(elem) + if err != nil { + return nil, err + } + if code.Kind() == CodeKindStruct { + structCode := code.(*StructCode) + structCode.enableIndirect() + } + return &ArrayCode{typ: typ, value: code}, nil +} + +func (c *Compiler) mapCode(typ *runtime.Type) (*MapCode, error) { + keyCode, err := c.mapKeyCode(typ.Key()) + if err != nil { + return nil, err + } + valueCode, err := c.mapValueCode(typ.Elem()) + if err != nil { + return nil, err + } + if valueCode.Kind() == CodeKindStruct { + structCode := valueCode.(*StructCode) + structCode.enableIndirect() + } + return &MapCode{typ: typ, key: keyCode, value: valueCode}, nil +} + +func (c *Compiler) bytesCode(typ *runtime.Type, isPtr bool) (*BytesCode, error) { + return &BytesCode{typ: typ, isPtr: isPtr}, nil +} + +func (c *Compiler) interfaceCode(typ *runtime.Type, isPtr bool) (*InterfaceCode, error) { + return &InterfaceCode{typ: typ, isPtr: isPtr}, nil +} + +func (c *Compiler) marshalJSONCode(typ *runtime.Type) (*MarshalJSONCode, error) { + return &MarshalJSONCode{ + typ: typ, + isAddrForMarshaler: c.isPtrMarshalJSONType(typ), + isNilableType: c.isNilableType(typ), + isMarshalerContext: typ.Implements(marshalJSONContextType) || runtime.PtrTo(typ).Implements(marshalJSONContextType), + }, nil +} + +func (c *Compiler) marshalTextCode(typ *runtime.Type) (*MarshalTextCode, error) { + return &MarshalTextCode{ + typ: typ, + isAddrForMarshaler: c.isPtrMarshalTextType(typ), + isNilableType: c.isNilableType(typ), + }, nil +} + +func (c *Compiler) ptrCode(typ *runtime.Type) (*PtrCode, error) { + code, err := c.typeToCodeWithPtr(typ.Elem(), true) + if err != nil { + return nil, err + } + ptr, ok := code.(*PtrCode) + if ok { + return &PtrCode{typ: typ, value: ptr.value, ptrNum: ptr.ptrNum + 1}, nil + } + return &PtrCode{typ: typ, value: code, ptrNum: 1}, nil +} + +func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) { + switch { + case c.isPtrMarshalJSONType(typ): + return c.marshalJSONCode(typ) + case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType): + return c.marshalTextCode(typ) + case typ.Kind() == reflect.Map: + return c.ptrCode(runtime.PtrTo(typ)) + default: + code, err := c.typeToCodeWithPtr(typ, false) if err != nil { return nil, err } - optimizeStructEnd(code) - linkRecursiveCode(code) + ptr, ok := code.(*PtrCode) + if ok { + if ptr.value.Kind() == CodeKindMap { + ptr.ptrNum++ + } + } return code, nil } } -func linkRecursiveCode(c *Opcode) { - for code := c; code.Op != OpEnd && code.Op != OpRecursiveEnd; { - switch code.Op { - case OpRecursive, OpRecursivePtr: - if code.Jmp.Linked { - code = code.Next - continue +func (c *Compiler) mapKeyCode(typ *runtime.Type) (Code, error) { + switch { + case c.implementsMarshalJSON(typ): + return c.marshalJSONCode(typ) + case c.implementsMarshalText(typ): + return c.marshalTextCode(typ) + } + switch typ.Kind() { + case reflect.Ptr: + return c.ptrCode(typ) + case reflect.String: + return c.stringCode(typ, false) + case reflect.Int: + return c.intStringCode(typ) + case reflect.Int8: + return c.int8StringCode(typ) + case reflect.Int16: + return c.int16StringCode(typ) + case reflect.Int32: + return c.int32StringCode(typ) + case reflect.Int64: + return c.int64StringCode(typ) + case reflect.Uint: + return c.uintStringCode(typ) + case reflect.Uint8: + return c.uint8StringCode(typ) + case reflect.Uint16: + return c.uint16StringCode(typ) + case reflect.Uint32: + return c.uint32StringCode(typ) + case reflect.Uint64: + return c.uint64StringCode(typ) + case reflect.Uintptr: + return c.uintStringCode(typ) + } + return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)} +} + +func (c *Compiler) mapValueCode(typ *runtime.Type) (Code, error) { + switch typ.Kind() { + case reflect.Map: + return c.ptrCode(runtime.PtrTo(typ)) + default: + code, err := c.typeToCodeWithPtr(typ, false) + if err != nil { + return nil, err + } + ptr, ok := code.(*PtrCode) + if ok { + if ptr.value.Kind() == CodeKindMap { + ptr.ptrNum++ } - code.Jmp.Code = copyOpcode(code.Jmp.Code) + } + return code, nil + } +} - c := code.Jmp.Code - c.End.Next = newEndOp(&compileContext{}) - c.Op = c.Op.PtrHeadToHead() +func (c *Compiler) structCode(typ *runtime.Type, isPtr bool) (*StructCode, error) { + typeptr := uintptr(unsafe.Pointer(typ)) + if code, exists := c.structTypeToCode[typeptr]; exists { + derefCode := *code + derefCode.isRecursive = true + return &derefCode, nil + } + indirect := runtime.IfaceIndir(typ) + code := &StructCode{typ: typ, isPtr: isPtr, isIndirect: indirect} + c.structTypeToCode[typeptr] = code - beforeLastCode := c.End - lastCode := beforeLastCode.Next + fieldNum := typ.NumField() + tags := c.typeToStructTags(typ) + fields := []*StructFieldCode{} + for i, tag := range tags { + isOnlyOneFirstField := i == 0 && fieldNum == 1 + field, err := c.structFieldCode(code, tag, isPtr, isOnlyOneFirstField) + if err != nil { + return nil, err + } + if field.isAnonymous { + structCode := field.getAnonymousStruct() + if structCode != nil { + structCode.removeFieldsByTags(tags) + if c.isAssignableIndirect(field, isPtr) { + if indirect { + structCode.isIndirect = true + } else { + structCode.isIndirect = false + } + } + } + } else { + structCode := field.getStruct() + if structCode != nil { + if indirect { + // if parent is indirect type, set child indirect property to true + structCode.isIndirect = true + } else { + // if parent is not indirect type, set child indirect property to false. + // but if parent's indirect is false and isPtr is true, then indirect must be true. + // Do this only if indirectConversion is enabled at the end of compileStruct. + structCode.isIndirect = false + } + } + } + fields = append(fields, field) + } + fieldMap := c.getFieldMap(fields) + duplicatedFieldMap := c.getDuplicatedFieldMap(fieldMap) + code.fields = c.filteredDuplicatedFields(fields, duplicatedFieldMap) + if !code.disableIndirectConversion && !indirect && isPtr { + code.enableIndirect() + } + delete(c.structTypeToCode, typeptr) + return code, nil +} - lastCode.Idx = beforeLastCode.Idx + uintptrSize - lastCode.ElemIdx = lastCode.Idx + uintptrSize - lastCode.Length = lastCode.Idx + 2*uintptrSize +func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTag, isPtr, isOnlyOneFirstField bool) (*StructFieldCode, error) { + field := tag.Field + fieldType := runtime.Type2RType(field.Type) + isIndirectSpecialCase := isPtr && isOnlyOneFirstField + fieldCode := &StructFieldCode{ + typ: fieldType, + key: tag.Key, + tag: tag, + offset: field.Offset, + isAnonymous: field.Anonymous && !tag.IsTaggedKey, + isTaggedKey: tag.IsTaggedKey, + isNilableType: c.isNilableType(fieldType), + isNilCheck: true, + } + switch { + case c.isMovePointerPositionFromHeadToFirstMarshalJSONFieldCase(fieldType, isIndirectSpecialCase): + code, err := c.marshalJSONCode(fieldType) + if err != nil { + return nil, err + } + fieldCode.value = code + fieldCode.isAddrForMarshaler = true + fieldCode.isNilCheck = false + structCode.isIndirect = false + structCode.disableIndirectConversion = true + case c.isMovePointerPositionFromHeadToFirstMarshalTextFieldCase(fieldType, isIndirectSpecialCase): + code, err := c.marshalTextCode(fieldType) + if err != nil { + return nil, err + } + fieldCode.value = code + fieldCode.isAddrForMarshaler = true + fieldCode.isNilCheck = false + structCode.isIndirect = false + structCode.disableIndirectConversion = true + case isPtr && c.isPtrMarshalJSONType(fieldType): + // *struct{ field T } + // func (*T) MarshalJSON() ([]byte, error) + code, err := c.marshalJSONCode(fieldType) + if err != nil { + return nil, err + } + fieldCode.value = code + fieldCode.isAddrForMarshaler = true + fieldCode.isNilCheck = false + case isPtr && c.isPtrMarshalTextType(fieldType): + // *struct{ field T } + // func (*T) MarshalText() ([]byte, error) + code, err := c.marshalTextCode(fieldType) + if err != nil { + return nil, err + } + fieldCode.value = code + fieldCode.isAddrForMarshaler = true + fieldCode.isNilCheck = false + default: + code, err := c.typeToCodeWithPtr(fieldType, isPtr) + if err != nil { + return nil, err + } + switch code.Kind() { + case CodeKindPtr, CodeKindInterface: + fieldCode.isNextOpPtrType = true + } + fieldCode.value = code + } + return fieldCode, nil +} - // extend length to alloc slot for elemIdx + length - totalLength := uintptr(code.TotalLength() + 3) - nextTotalLength := uintptr(c.TotalLength() + 3) +func (c *Compiler) isAssignableIndirect(fieldCode *StructFieldCode, isPtr bool) bool { + if isPtr { + return false + } + codeType := fieldCode.value.Kind() + if codeType == CodeKindMarshalJSON { + return false + } + if codeType == CodeKindMarshalText { + return false + } + return true +} - c.End.Next.Op = OpRecursiveEnd - - code.Jmp.CurLen = totalLength - code.Jmp.NextLen = nextTotalLength - code.Jmp.Linked = true - - linkRecursiveCode(code.Jmp.Code) - - code = code.Next +func (c *Compiler) getFieldMap(fields []*StructFieldCode) map[string][]*StructFieldCode { + fieldMap := map[string][]*StructFieldCode{} + for _, field := range fields { + if field.isAnonymous { + for k, v := range c.getAnonymousFieldMap(field) { + fieldMap[k] = append(fieldMap[k], v...) + } continue } - switch code.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - code = code.End - default: - code = code.Next - } + fieldMap[field.key] = append(fieldMap[field.key], field) } + return fieldMap } -func optimizeStructEnd(c *Opcode) { - for code := c; code.Op != OpEnd; { - if code.Op == OpRecursive || code.Op == OpRecursivePtr { - // ignore if exists recursive operation - return - } - switch code.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - code = code.End - default: - code = code.Next - } +func (c *Compiler) getAnonymousFieldMap(field *StructFieldCode) map[string][]*StructFieldCode { + fieldMap := map[string][]*StructFieldCode{} + structCode := field.getAnonymousStruct() + if structCode == nil || structCode.isRecursive { + fieldMap[field.key] = append(fieldMap[field.key], field) + return fieldMap } + for k, v := range c.getFieldMapFromAnonymousParent(structCode.fields) { + fieldMap[k] = append(fieldMap[k], v...) + } + return fieldMap +} - for code := c; code.Op != OpEnd; { - switch code.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - code = code.End - case CodeStructEnd: - switch code.Op { - case OpStructEnd: - prev := code.PrevField - prevOp := prev.Op.String() - if strings.Contains(prevOp, "Head") || - strings.Contains(prevOp, "Slice") || - strings.Contains(prevOp, "Array") || - strings.Contains(prevOp, "Map") || - strings.Contains(prevOp, "MarshalJSON") || - strings.Contains(prevOp, "MarshalText") { - // not exists field - code = code.Next - break +func (c *Compiler) getFieldMapFromAnonymousParent(fields []*StructFieldCode) map[string][]*StructFieldCode { + fieldMap := map[string][]*StructFieldCode{} + for _, field := range fields { + if field.isAnonymous { + for k, v := range c.getAnonymousFieldMap(field) { + // Do not handle tagged key when embedding more than once + for _, vv := range v { + vv.isTaggedKey = false } - if prev.Op != prev.Op.FieldToEnd() { - prev.Op = prev.Op.FieldToEnd() - prev.Next = code.Next - } - code = code.Next - default: - code = code.Next + fieldMap[k] = append(fieldMap[k], v...) } - default: - code = code.Next + continue } + fieldMap[field.key] = append(fieldMap[field.key], field) } + return fieldMap } -func implementsMarshalJSON(typ *runtime.Type) bool { - if !implementsMarshalJSONType(typ) { +func (c *Compiler) getDuplicatedFieldMap(fieldMap map[string][]*StructFieldCode) map[*StructFieldCode]struct{} { + duplicatedFieldMap := map[*StructFieldCode]struct{}{} + for _, fields := range fieldMap { + if len(fields) == 1 { + continue + } + if c.isTaggedKeyOnly(fields) { + for _, field := range fields { + if field.isTaggedKey { + continue + } + duplicatedFieldMap[field] = struct{}{} + } + } else { + for _, field := range fields { + duplicatedFieldMap[field] = struct{}{} + } + } + } + return duplicatedFieldMap +} + +func (c *Compiler) filteredDuplicatedFields(fields []*StructFieldCode, duplicatedFieldMap map[*StructFieldCode]struct{}) []*StructFieldCode { + filteredFields := make([]*StructFieldCode, 0, len(fields)) + for _, field := range fields { + if field.isAnonymous { + structCode := field.getAnonymousStruct() + if structCode != nil && !structCode.isRecursive { + structCode.fields = c.filteredDuplicatedFields(structCode.fields, duplicatedFieldMap) + if len(structCode.fields) > 0 { + filteredFields = append(filteredFields, field) + } + continue + } + } + if _, exists := duplicatedFieldMap[field]; exists { + continue + } + filteredFields = append(filteredFields, field) + } + return filteredFields +} + +func (c *Compiler) isTaggedKeyOnly(fields []*StructFieldCode) bool { + var taggedKeyFieldCount int + for _, field := range fields { + if field.isTaggedKey { + taggedKeyFieldCount++ + } + } + return taggedKeyFieldCount == 1 +} + +func (c *Compiler) typeToStructTags(typ *runtime.Type) runtime.StructTags { + tags := runtime.StructTags{} + fieldNum := typ.NumField() + for i := 0; i < fieldNum; i++ { + field := typ.Field(i) + if runtime.IsIgnoredStructField(field) { + continue + } + tags = append(tags, runtime.StructTagFromField(field)) + } + return tags +} + +// *struct{ field T } => struct { field *T } +// func (*T) MarshalJSON() ([]byte, error) +func (c *Compiler) isMovePointerPositionFromHeadToFirstMarshalJSONFieldCase(typ *runtime.Type, isIndirectSpecialCase bool) bool { + return isIndirectSpecialCase && !c.isNilableType(typ) && c.isPtrMarshalJSONType(typ) +} + +// *struct{ field T } => struct { field *T } +// func (*T) MarshalText() ([]byte, error) +func (c *Compiler) isMovePointerPositionFromHeadToFirstMarshalTextFieldCase(typ *runtime.Type, isIndirectSpecialCase bool) bool { + return isIndirectSpecialCase && !c.isNilableType(typ) && c.isPtrMarshalTextType(typ) +} + +func (c *Compiler) implementsMarshalJSON(typ *runtime.Type) bool { + if !c.implementsMarshalJSONType(typ) { return false } if typ.Kind() != reflect.Ptr { return true } // type kind is reflect.Ptr - if !implementsMarshalJSONType(typ.Elem()) { + if !c.implementsMarshalJSONType(typ.Elem()) { return true } // needs to dereference return false } -func implementsMarshalText(typ *runtime.Type) bool { +func (c *Compiler) implementsMarshalText(typ *runtime.Type) bool { if !typ.Implements(marshalTextType) { return false } @@ -382,884 +797,7 @@ func implementsMarshalText(typ *runtime.Type) bool { return false } -func compile(ctx *compileContext, isPtr bool) (*Opcode, error) { - typ := ctx.typ - switch { - case implementsMarshalJSON(typ): - return compileMarshalJSON(ctx) - case implementsMarshalText(typ): - return compileMarshalText(ctx) - } - switch typ.Kind() { - case reflect.Ptr: - return compilePtr(ctx) - case reflect.Slice: - elem := typ.Elem() - if elem.Kind() == reflect.Uint8 { - p := runtime.PtrTo(elem) - if !implementsMarshalJSONType(p) && !p.Implements(marshalTextType) { - return compileBytes(ctx) - } - } - return compileSlice(ctx) - case reflect.Array: - return compileArray(ctx) - case reflect.Map: - return compileMap(ctx) - case reflect.Struct: - return compileStruct(ctx, isPtr) - case reflect.Interface: - return compileInterface(ctx) - case reflect.Int: - return compileInt(ctx) - case reflect.Int8: - return compileInt8(ctx) - case reflect.Int16: - return compileInt16(ctx) - case reflect.Int32: - return compileInt32(ctx) - case reflect.Int64: - return compileInt64(ctx) - case reflect.Uint: - return compileUint(ctx) - case reflect.Uint8: - return compileUint8(ctx) - case reflect.Uint16: - return compileUint16(ctx) - case reflect.Uint32: - return compileUint32(ctx) - case reflect.Uint64: - return compileUint64(ctx) - case reflect.Uintptr: - return compileUint(ctx) - case reflect.Float32: - return compileFloat32(ctx) - case reflect.Float64: - return compileFloat64(ctx) - case reflect.String: - return compileString(ctx) - case reflect.Bool: - return compileBool(ctx) - } - return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)} -} - -func convertPtrOp(code *Opcode) OpType { - ptrHeadOp := code.Op.HeadToPtrHead() - if code.Op != ptrHeadOp { - if code.PtrNum > 0 { - // ptr field and ptr head - code.PtrNum-- - } - return ptrHeadOp - } - switch code.Op { - case OpInt: - return OpIntPtr - case OpUint: - return OpUintPtr - case OpFloat32: - return OpFloat32Ptr - case OpFloat64: - return OpFloat64Ptr - case OpString: - return OpStringPtr - case OpBool: - return OpBoolPtr - case OpBytes: - return OpBytesPtr - case OpNumber: - return OpNumberPtr - case OpArray: - return OpArrayPtr - case OpSlice: - return OpSlicePtr - case OpMap: - return OpMapPtr - case OpMarshalJSON: - return OpMarshalJSONPtr - case OpMarshalText: - return OpMarshalTextPtr - case OpInterface: - return OpInterfacePtr - case OpRecursive: - return OpRecursivePtr - } - return code.Op -} - -func compileKey(ctx *compileContext) (*Opcode, error) { - typ := ctx.typ - switch { - case implementsMarshalJSON(typ): - return compileMarshalJSON(ctx) - case implementsMarshalText(typ): - return compileMarshalText(ctx) - } - switch typ.Kind() { - case reflect.Ptr: - return compilePtr(ctx) - case reflect.String: - return compileString(ctx) - case reflect.Int: - return compileIntString(ctx) - case reflect.Int8: - return compileInt8String(ctx) - case reflect.Int16: - return compileInt16String(ctx) - case reflect.Int32: - return compileInt32String(ctx) - case reflect.Int64: - return compileInt64String(ctx) - case reflect.Uint: - return compileUintString(ctx) - case reflect.Uint8: - return compileUint8String(ctx) - case reflect.Uint16: - return compileUint16String(ctx) - case reflect.Uint32: - return compileUint32String(ctx) - case reflect.Uint64: - return compileUint64String(ctx) - case reflect.Uintptr: - return compileUintString(ctx) - } - return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)} -} - -func compilePtr(ctx *compileContext) (*Opcode, error) { - code, err := compile(ctx.withType(ctx.typ.Elem()), true) - if err != nil { - return nil, err - } - code.Op = convertPtrOp(code) - code.PtrNum++ - return code, nil -} - -func compileMarshalJSON(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpMarshalJSON) - typ := ctx.typ - if isPtrMarshalJSONType(typ) { - code.Flags |= AddrForMarshalerFlags - } - if typ.Implements(marshalJSONContextType) || runtime.PtrTo(typ).Implements(marshalJSONContextType) { - code.Flags |= MarshalerContextFlags - } - if isNilableType(typ) { - code.Flags |= IsNilableTypeFlags - } else { - code.Flags &= ^IsNilableTypeFlags - } - ctx.incIndex() - return code, nil -} - -func compileMarshalText(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpMarshalText) - typ := ctx.typ - if !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType) { - code.Flags |= AddrForMarshalerFlags - } - if isNilableType(typ) { - code.Flags |= IsNilableTypeFlags - } else { - code.Flags &= ^IsNilableTypeFlags - } - ctx.incIndex() - return code, nil -} - -const intSize = 32 << (^uint(0) >> 63) - -func compileInt(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpInt) - code.NumBitSize = intSize - ctx.incIndex() - return code, nil -} - -func compileIntPtr(ctx *compileContext) (*Opcode, error) { - code, err := compileInt(ctx) - if err != nil { - return nil, err - } - code.Op = OpIntPtr - return code, nil -} - -func compileInt8(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpInt) - code.NumBitSize = 8 - ctx.incIndex() - return code, nil -} - -func compileInt8Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileInt8(ctx) - if err != nil { - return nil, err - } - code.Op = OpIntPtr - return code, nil -} - -func compileInt16(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpInt) - code.NumBitSize = 16 - ctx.incIndex() - return code, nil -} - -func compileInt16Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileInt16(ctx) - if err != nil { - return nil, err - } - code.Op = OpIntPtr - return code, nil -} - -func compileInt32(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpInt) - code.NumBitSize = 32 - ctx.incIndex() - return code, nil -} - -func compileInt32Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileInt32(ctx) - if err != nil { - return nil, err - } - code.Op = OpIntPtr - return code, nil -} - -func compileInt64(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpInt) - code.NumBitSize = 64 - ctx.incIndex() - return code, nil -} - -func compileInt64Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileInt64(ctx) - if err != nil { - return nil, err - } - code.Op = OpIntPtr - return code, nil -} - -func compileUint(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUint) - code.NumBitSize = intSize - ctx.incIndex() - return code, nil -} - -func compileUintPtr(ctx *compileContext) (*Opcode, error) { - code, err := compileUint(ctx) - if err != nil { - return nil, err - } - code.Op = OpUintPtr - return code, nil -} - -func compileUint8(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUint) - code.NumBitSize = 8 - ctx.incIndex() - return code, nil -} - -func compileUint8Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileUint8(ctx) - if err != nil { - return nil, err - } - code.Op = OpUintPtr - return code, nil -} - -func compileUint16(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUint) - code.NumBitSize = 16 - ctx.incIndex() - return code, nil -} - -func compileUint16Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileUint16(ctx) - if err != nil { - return nil, err - } - code.Op = OpUintPtr - return code, nil -} - -func compileUint32(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUint) - code.NumBitSize = 32 - ctx.incIndex() - return code, nil -} - -func compileUint32Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileUint32(ctx) - if err != nil { - return nil, err - } - code.Op = OpUintPtr - return code, nil -} - -func compileUint64(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUint) - code.NumBitSize = 64 - ctx.incIndex() - return code, nil -} - -func compileUint64Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileUint64(ctx) - if err != nil { - return nil, err - } - code.Op = OpUintPtr - return code, nil -} - -func compileIntString(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpIntString) - code.NumBitSize = intSize - ctx.incIndex() - return code, nil -} - -func compileInt8String(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpIntString) - code.NumBitSize = 8 - ctx.incIndex() - return code, nil -} - -func compileInt16String(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpIntString) - code.NumBitSize = 16 - ctx.incIndex() - return code, nil -} - -func compileInt32String(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpIntString) - code.NumBitSize = 32 - ctx.incIndex() - return code, nil -} - -func compileInt64String(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpIntString) - code.NumBitSize = 64 - ctx.incIndex() - return code, nil -} - -func compileUintString(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUintString) - code.NumBitSize = intSize - ctx.incIndex() - return code, nil -} - -func compileUint8String(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUintString) - code.NumBitSize = 8 - ctx.incIndex() - return code, nil -} - -func compileUint16String(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUintString) - code.NumBitSize = 16 - ctx.incIndex() - return code, nil -} - -func compileUint32String(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUintString) - code.NumBitSize = 32 - ctx.incIndex() - return code, nil -} - -func compileUint64String(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpUintString) - code.NumBitSize = 64 - ctx.incIndex() - return code, nil -} - -func compileFloat32(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpFloat32) - ctx.incIndex() - return code, nil -} - -func compileFloat32Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileFloat32(ctx) - if err != nil { - return nil, err - } - code.Op = OpFloat32Ptr - return code, nil -} - -func compileFloat64(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpFloat64) - ctx.incIndex() - return code, nil -} - -func compileFloat64Ptr(ctx *compileContext) (*Opcode, error) { - code, err := compileFloat64(ctx) - if err != nil { - return nil, err - } - code.Op = OpFloat64Ptr - return code, nil -} - -func compileString(ctx *compileContext) (*Opcode, error) { - var op OpType - if ctx.typ == runtime.Type2RType(jsonNumberType) { - op = OpNumber - } else { - op = OpString - } - code := newOpCode(ctx, op) - ctx.incIndex() - return code, nil -} - -func compileStringPtr(ctx *compileContext) (*Opcode, error) { - code, err := compileString(ctx) - if err != nil { - return nil, err - } - if code.Op == OpNumber { - code.Op = OpNumberPtr - } else { - code.Op = OpStringPtr - } - return code, nil -} - -func compileBool(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpBool) - ctx.incIndex() - return code, nil -} - -func compileBoolPtr(ctx *compileContext) (*Opcode, error) { - code, err := compileBool(ctx) - if err != nil { - return nil, err - } - code.Op = OpBoolPtr - return code, nil -} - -func compileBytes(ctx *compileContext) (*Opcode, error) { - code := newOpCode(ctx, OpBytes) - ctx.incIndex() - return code, nil -} - -func compileBytesPtr(ctx *compileContext) (*Opcode, error) { - code, err := compileBytes(ctx) - if err != nil { - return nil, err - } - code.Op = OpBytesPtr - return code, nil -} - -func compileInterface(ctx *compileContext) (*Opcode, error) { - code := newInterfaceCode(ctx) - ctx.incIndex() - return code, nil -} - -func compileInterfacePtr(ctx *compileContext) (*Opcode, error) { - code, err := compileInterface(ctx) - if err != nil { - return nil, err - } - code.Op = OpInterfacePtr - return code, nil -} - -func compileSlice(ctx *compileContext) (*Opcode, error) { - elem := ctx.typ.Elem() - size := elem.Size() - - header := newSliceHeaderCode(ctx) - ctx.incIndex() - - code, err := compileListElem(ctx.withType(elem).incIndent()) - if err != nil { - return nil, err - } - code.Flags |= IndirectFlags - - // header => opcode => elem => end - // ^ | - // |________| - - elemCode := newSliceElemCode(ctx, header, size) - ctx.incIndex() - - end := newOpCode(ctx, OpSliceEnd) - ctx.incIndex() - - header.End = end - header.Next = code - code.BeforeLastCode().Next = (*Opcode)(unsafe.Pointer(elemCode)) - elemCode.Next = code - elemCode.End = end - return (*Opcode)(unsafe.Pointer(header)), nil -} - -func compileListElem(ctx *compileContext) (*Opcode, error) { - typ := ctx.typ - switch { - case isPtrMarshalJSONType(typ): - return compileMarshalJSON(ctx) - case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType): - return compileMarshalText(ctx) - case typ.Kind() == reflect.Map: - return compilePtr(ctx.withType(runtime.PtrTo(typ))) - default: - code, err := compile(ctx, false) - if err != nil { - return nil, err - } - if code.Op == OpMapPtr { - code.PtrNum++ - } - return code, nil - } -} - -func compileArray(ctx *compileContext) (*Opcode, error) { - typ := ctx.typ - elem := typ.Elem() - alen := typ.Len() - size := elem.Size() - - header := newArrayHeaderCode(ctx, alen) - ctx.incIndex() - - code, err := compileListElem(ctx.withType(elem).incIndent()) - if err != nil { - return nil, err - } - code.Flags |= IndirectFlags - // header => opcode => elem => end - // ^ | - // |________| - - elemCode := newArrayElemCode(ctx, header, alen, size) - ctx.incIndex() - - end := newOpCode(ctx, OpArrayEnd) - ctx.incIndex() - - header.End = end - header.Next = code - code.BeforeLastCode().Next = (*Opcode)(unsafe.Pointer(elemCode)) - elemCode.Next = code - elemCode.End = end - return (*Opcode)(unsafe.Pointer(header)), nil -} - -func compileMap(ctx *compileContext) (*Opcode, error) { - // header => code => value => code => key => code => value => code => end - // ^ | - // |_______________________| - ctx = ctx.incIndent() - header := newMapHeaderCode(ctx) - ctx.incIndex() - - typ := ctx.typ - keyType := ctx.typ.Key() - keyCode, err := compileKey(ctx.withType(keyType)) - if err != nil { - return nil, err - } - - value := newMapValueCode(ctx, header) - ctx.incIndex() - - valueCode, err := compileMapValue(ctx.withType(typ.Elem())) - if err != nil { - return nil, err - } - valueCode.Flags |= IndirectFlags - - key := newMapKeyCode(ctx, header) - ctx.incIndex() - - ctx = ctx.decIndent() - - end := newMapEndCode(ctx, header) - ctx.incIndex() - - header.Next = keyCode - keyCode.BeforeLastCode().Next = (*Opcode)(unsafe.Pointer(value)) - value.Next = valueCode - valueCode.BeforeLastCode().Next = (*Opcode)(unsafe.Pointer(key)) - key.Next = keyCode - - header.End = end - key.End = end - value.End = end - - return (*Opcode)(unsafe.Pointer(header)), nil -} - -func compileMapValue(ctx *compileContext) (*Opcode, error) { - switch ctx.typ.Kind() { - case reflect.Map: - return compilePtr(ctx.withType(runtime.PtrTo(ctx.typ))) - default: - code, err := compile(ctx, false) - if err != nil { - return nil, err - } - if code.Op == OpMapPtr { - code.PtrNum++ - } - return code, nil - } -} - -func optimizeStructHeader(code *Opcode, tag *runtime.StructTag) OpType { - headType := code.ToHeaderType(tag.IsString) - if tag.IsOmitEmpty { - headType = headType.HeadToOmitEmptyHead() - } - return headType -} - -func optimizeStructField(code *Opcode, tag *runtime.StructTag) OpType { - fieldType := code.ToFieldType(tag.IsString) - if tag.IsOmitEmpty { - fieldType = fieldType.FieldToOmitEmptyField() - } - return fieldType -} - -func recursiveCode(ctx *compileContext, jmp *CompiledCode) *Opcode { - code := newRecursiveCode(ctx, jmp) - ctx.incIndex() - return code -} - -func compiledCode(ctx *compileContext) *Opcode { - typ := ctx.typ - typeptr := uintptr(unsafe.Pointer(typ)) - if cc, exists := ctx.structTypeToCompiledCode[typeptr]; exists { - return recursiveCode(ctx, cc) - } - return nil -} - -func structHeader(ctx *compileContext, fieldCode *Opcode, valueCode *Opcode, tag *runtime.StructTag) *Opcode { - op := optimizeStructHeader(valueCode, tag) - fieldCode.Op = op - fieldCode.NumBitSize = valueCode.NumBitSize - fieldCode.PtrNum = valueCode.PtrNum - if op.IsMultipleOpHead() { - return valueCode.BeforeLastCode() - } - ctx.decOpcodeIndex() - return fieldCode -} - -func structField(ctx *compileContext, fieldCode *Opcode, valueCode *Opcode, tag *runtime.StructTag) *Opcode { - op := optimizeStructField(valueCode, tag) - fieldCode.Op = op - fieldCode.NumBitSize = valueCode.NumBitSize - fieldCode.PtrNum = valueCode.PtrNum - if op.IsMultipleOpField() { - return valueCode.BeforeLastCode() - } - ctx.decIndex() - return fieldCode -} - -type structFieldPair struct { - prevField *Opcode - curField *Opcode - isTaggedKey bool - linked bool -} - -func filterAnonymousStructFieldsByTags(value *Opcode, tags runtime.StructTags) *Opcode { - head := value - curField := head - removedFields := map[*Opcode]struct{}{} - for curField != nil { - existsKey := tags.ExistsKey(curField.DisplayKey) - if !existsKey || curField.Next.IsRecursiveOp() { - curField = curField.NextField - continue - } - diff := curField.NextField.DisplayIdx - curField.DisplayIdx - for i := uint32(0); i < diff; i++ { - curField.NextField.decOpcodeIndex() - } - if curField.IsStructHeadOp() || head == curField { - head = curField.NextField - } else { - linkPrevToNextField(curField, removedFields) - } - curField = curField.NextField - } - return head -} - -func anonymousStructFieldPairMap(named string, valueCode *Opcode) map[string][]structFieldPair { - anonymousFields := map[string][]structFieldPair{} - f := valueCode - var prevAnonymousField *Opcode - for { - isHeadOp := strings.Contains(f.Op.String(), "Head") - if isHeadOp && (f.Flags&AnonymousHeadFlags) == 0 { - if named == "" { - f.Flags |= AnonymousHeadFlags - } - } else if named == "" && f.Op == OpStructEnd { - f.Op = OpStructAnonymousEnd - } - if f.DisplayKey == "" { - if f.NextField == nil { - break - } - prevAnonymousField = f - f = f.NextField - continue - } - - key := fmt.Sprintf("%s.%s", named, f.DisplayKey) - anonymousFields[key] = append(anonymousFields[key], structFieldPair{ - prevField: prevAnonymousField, - curField: f, - isTaggedKey: (f.Flags & IsTaggedKeyFlags) != 0, - }) - if f.Next != nil && f.NextField != f.Next && f.Next.Op.CodeType() == CodeStructField { - for k, v := range anonymousFieldPairRecursively(named, f.Next) { - anonymousFields[k] = append(anonymousFields[k], v...) - } - } - if f.NextField == nil { - break - } - prevAnonymousField = f - f = f.NextField - } - return anonymousFields -} - -func anonymousFieldPairRecursively(named string, valueCode *Opcode) map[string][]structFieldPair { - anonymousFields := map[string][]structFieldPair{} - f := valueCode - var prevAnonymousField *Opcode - for { - if f.DisplayKey != "" && (f.Flags&AnonymousHeadFlags) != 0 { - key := fmt.Sprintf("%s.%s", named, f.DisplayKey) - anonymousFields[key] = append(anonymousFields[key], structFieldPair{ - prevField: prevAnonymousField, - curField: f, - isTaggedKey: (f.Flags & IsTaggedKeyFlags) != 0, - }) - if f.Next != nil && f.NextField != f.Next && f.Next.Op.CodeType() == CodeStructField { - for k, v := range anonymousFieldPairRecursively(named, f.Next) { - anonymousFields[k] = append(anonymousFields[k], v...) - } - } - } - if f.NextField == nil { - break - } - prevAnonymousField = f - f = f.NextField - } - return anonymousFields -} - -func optimizeConflictAnonymousFields(anonymousFields map[string][]structFieldPair) { - removedFields := map[*Opcode]struct{}{} - for _, fieldPairs := range anonymousFields { - if len(fieldPairs) == 1 { - continue - } - // conflict anonymous fields - taggedPairs := []structFieldPair{} - for _, fieldPair := range fieldPairs { - if fieldPair.isTaggedKey { - taggedPairs = append(taggedPairs, fieldPair) - } else { - if !fieldPair.linked { - if fieldPair.prevField == nil { - // head operation - fieldPair.curField.Op = OpStructHead - fieldPair.curField.Flags |= AnonymousHeadFlags - fieldPair.curField.Flags |= AnonymousKeyFlags - } else { - diff := fieldPair.curField.NextField.DisplayIdx - fieldPair.curField.DisplayIdx - for i := uint32(0); i < diff; i++ { - fieldPair.curField.NextField.decOpcodeIndex() - } - removedFields[fieldPair.curField] = struct{}{} - linkPrevToNextField(fieldPair.curField, removedFields) - } - fieldPair.linked = true - } - } - } - if len(taggedPairs) > 1 { - for _, fieldPair := range taggedPairs { - if !fieldPair.linked { - if fieldPair.prevField == nil { - // head operation - fieldPair.curField.Op = OpStructHead - fieldPair.curField.Flags |= AnonymousHeadFlags - fieldPair.curField.Flags |= AnonymousKeyFlags - } else { - diff := fieldPair.curField.NextField.DisplayIdx - fieldPair.curField.DisplayIdx - removedFields[fieldPair.curField] = struct{}{} - for i := uint32(0); i < diff; i++ { - fieldPair.curField.NextField.decOpcodeIndex() - } - linkPrevToNextField(fieldPair.curField, removedFields) - } - fieldPair.linked = true - } - } - } else { - for _, fieldPair := range taggedPairs { - fieldPair.curField.Flags &= ^IsTaggedKeyFlags - } - } - } -} - -func isNilableType(typ *runtime.Type) bool { +func (c *Compiler) isNilableType(typ *runtime.Type) bool { switch typ.Kind() { case reflect.Ptr: return true @@ -1272,252 +810,49 @@ func isNilableType(typ *runtime.Type) bool { } } -func compileStruct(ctx *compileContext, isPtr bool) (*Opcode, error) { - if code := compiledCode(ctx); code != nil { - return code, nil - } - typ := ctx.typ - typeptr := uintptr(unsafe.Pointer(typ)) - compiled := &CompiledCode{} - ctx.structTypeToCompiledCode[typeptr] = compiled - // header => code => structField => code => end - // ^ | - // |__________| - fieldNum := typ.NumField() - indirect := runtime.IfaceIndir(typ) - fieldIdx := 0 - disableIndirectConversion := false - var ( - head *Opcode - code *Opcode - prevField *Opcode - ) - ctx = ctx.incIndent() - tags := runtime.StructTags{} - anonymousFields := map[string][]structFieldPair{} - for i := 0; i < fieldNum; i++ { - field := typ.Field(i) - if runtime.IsIgnoredStructField(field) { - continue - } - tags = append(tags, runtime.StructTagFromField(field)) - } - for i, tag := range tags { - field := tag.Field - fieldType := runtime.Type2RType(field.Type) - fieldOpcodeIndex := ctx.opcodeIndex - fieldPtrIndex := ctx.ptrIndex - ctx.incIndex() - - nilcheck := true - addrForMarshaler := false - isIndirectSpecialCase := isPtr && i == 0 && fieldNum == 1 - isNilableType := isNilableType(fieldType) - - var valueCode *Opcode - switch { - case isIndirectSpecialCase && !isNilableType && isPtrMarshalJSONType(fieldType): - // *struct{ field T } => struct { field *T } - // func (*T) MarshalJSON() ([]byte, error) - // move pointer position from head to first field - code, err := compileMarshalJSON(ctx.withType(fieldType)) - if err != nil { - return nil, err - } - addrForMarshaler = true - valueCode = code - nilcheck = false - indirect = false - disableIndirectConversion = true - case isIndirectSpecialCase && !isNilableType && isPtrMarshalTextType(fieldType): - // *struct{ field T } => struct { field *T } - // func (*T) MarshalText() ([]byte, error) - // move pointer position from head to first field - code, err := compileMarshalText(ctx.withType(fieldType)) - if err != nil { - return nil, err - } - addrForMarshaler = true - valueCode = code - nilcheck = false - indirect = false - disableIndirectConversion = true - case isPtr && isPtrMarshalJSONType(fieldType): - // *struct{ field T } - // func (*T) MarshalJSON() ([]byte, error) - code, err := compileMarshalJSON(ctx.withType(fieldType)) - if err != nil { - return nil, err - } - addrForMarshaler = true - nilcheck = false - valueCode = code - case isPtr && isPtrMarshalTextType(fieldType): - // *struct{ field T } - // func (*T) MarshalText() ([]byte, error) - code, err := compileMarshalText(ctx.withType(fieldType)) - if err != nil { - return nil, err - } - addrForMarshaler = true - nilcheck = false - valueCode = code - default: - code, err := compile(ctx.withType(fieldType), isPtr) - if err != nil { - return nil, err - } - valueCode = code - } - - if field.Anonymous && !tag.IsTaggedKey { - tagKey := "" - if tag.IsTaggedKey { - tagKey = tag.Key - } - valueCode = filterAnonymousStructFieldsByTags(valueCode, tags) - for k, v := range anonymousStructFieldPairMap(tagKey, valueCode) { - anonymousFields[k] = append(anonymousFields[k], v...) - } - - valueCode.decIndent() - - // fix issue144 - if !(isPtr && strings.Contains(valueCode.Op.String(), "Marshal")) { - if indirect { - valueCode.Flags |= IndirectFlags - } else { - valueCode.Flags &= ^IndirectFlags - } - } - } else { - if indirect { - // if parent is indirect type, set child indirect property to true - valueCode.Flags |= IndirectFlags - } else { - // if parent is not indirect type, set child indirect property to false. - // but if parent's indirect is false and isPtr is true, then indirect must be true. - // Do this only if indirectConversion is enabled at the end of compileStruct. - if i == 0 { - valueCode.Flags &= ^IndirectFlags - } - } - } - var flags OpFlags - if indirect { - flags |= IndirectFlags - } - if field.Anonymous { - flags |= AnonymousKeyFlags - } - if tag.IsTaggedKey { - flags |= IsTaggedKeyFlags - } - if nilcheck { - flags |= NilCheckFlags - } - if addrForMarshaler { - flags |= AddrForMarshalerFlags - } - if strings.Contains(valueCode.Op.String(), "Ptr") || valueCode.Op == OpInterface { - flags |= IsNextOpPtrTypeFlags - } - if isNilableType { - flags |= IsNilableTypeFlags - } - var key string - if ctx.escapeKey { - rctx := &RuntimeContext{Option: &Option{Flag: HTMLEscapeOption}} - key = fmt.Sprintf(`%s:`, string(AppendString(rctx, []byte{}, tag.Key))) - } else { - key = fmt.Sprintf(`"%s":`, tag.Key) - } - fieldCode := &Opcode{ - Idx: opcodeOffset(fieldPtrIndex), - Next: valueCode, - Flags: flags, - Key: key, - Offset: uint32(field.Offset), - Type: valueCode.Type, - DisplayIdx: fieldOpcodeIndex, - Indent: ctx.indent, - DisplayKey: tag.Key, - } - if fieldIdx == 0 { - code = structHeader(ctx, fieldCode, valueCode, tag) - head = fieldCode - prevField = fieldCode - } else { - fieldCode.Idx = head.Idx - code.Next = fieldCode - code = structField(ctx, fieldCode, valueCode, tag) - prevField.NextField = fieldCode - fieldCode.PrevField = prevField - prevField = fieldCode - } - fieldIdx++ - } - - structEndCode := &Opcode{ - Op: OpStructEnd, - Type: nil, - Indent: ctx.indent, - } - - ctx = ctx.decIndent() - - // no struct field - if head == nil { - head = &Opcode{ - Op: OpStructHead, - Idx: opcodeOffset(ctx.ptrIndex), - NextField: structEndCode, - Type: typ, - DisplayIdx: ctx.opcodeIndex, - Indent: ctx.indent, - } - structEndCode.PrevField = head - ctx.incIndex() - code = head - } - - structEndCode.DisplayIdx = ctx.opcodeIndex - structEndCode.Idx = opcodeOffset(ctx.ptrIndex) - ctx.incIndex() - structEndCode.Next = newEndOp(ctx) - - if prevField != nil && prevField.NextField == nil { - prevField.NextField = structEndCode - structEndCode.PrevField = prevField - } - - head.End = structEndCode - code.Next = structEndCode - optimizeConflictAnonymousFields(anonymousFields) - ret := (*Opcode)(unsafe.Pointer(head)) - compiled.Code = ret - - delete(ctx.structTypeToCompiledCode, typeptr) - - if !disableIndirectConversion && (head.Flags&IndirectFlags == 0) && isPtr { - headCode := head - for strings.Contains(headCode.Op.String(), "Head") { - headCode.Flags |= IndirectFlags - headCode = headCode.Next - } - } - - return ret, nil -} - -func implementsMarshalJSONType(typ *runtime.Type) bool { +func (c *Compiler) implementsMarshalJSONType(typ *runtime.Type) bool { return typ.Implements(marshalJSONType) || typ.Implements(marshalJSONContextType) } -func isPtrMarshalJSONType(typ *runtime.Type) bool { - return !implementsMarshalJSONType(typ) && implementsMarshalJSONType(runtime.PtrTo(typ)) +func (c *Compiler) isPtrMarshalJSONType(typ *runtime.Type) bool { + return !c.implementsMarshalJSONType(typ) && c.implementsMarshalJSONType(runtime.PtrTo(typ)) } -func isPtrMarshalTextType(typ *runtime.Type) bool { +func (c *Compiler) isPtrMarshalTextType(typ *runtime.Type) bool { return !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType) } + +func (c *Compiler) codeToOpcode(ctx *compileContext, typ *runtime.Type, code Code) *Opcode { + codes := code.ToOpcode(ctx) + codes.Last().Next = newEndOp(ctx, typ) + c.linkRecursiveCode(ctx) + return codes.First() +} + +func (c *Compiler) linkRecursiveCode(ctx *compileContext) { + for _, recursive := range *ctx.recursiveCodes { + typeptr := uintptr(unsafe.Pointer(recursive.Type)) + codes := ctx.structTypeToCodes[typeptr] + compiled := recursive.Jmp + compiled.Code = copyOpcode(codes.First()) + code := compiled.Code + code.End.Next = newEndOp(&compileContext{}, recursive.Type) + code.Op = code.Op.PtrHeadToHead() + + beforeLastCode := code.End + lastCode := beforeLastCode.Next + + totalLength := code.TotalLength() + lastCode.Idx = uint32((totalLength + 1) * uintptrSize) + lastCode.ElemIdx = lastCode.Idx + uintptrSize + lastCode.Length = lastCode.Idx + 2*uintptrSize + code.End.Next.Op = OpRecursiveEnd + + // extend length to alloc slot for elemIdx + length + curTotalLength := uintptr(recursive.TotalLength()) + 3 + nextTotalLength := uintptr(totalLength) + 3 + compiled.CurLen = curTotalLength + compiled.NextLen = nextTotalLength + compiled.Linked = true + } +} diff --git a/internal/encoder/compiler_norace.go b/internal/encoder/compiler_norace.go index 9d337f1..afc5b66 100644 --- a/internal/encoder/compiler_norace.go +++ b/internal/encoder/compiler_norace.go @@ -1,13 +1,8 @@ +//go:build !race // +build !race package encoder -import ( - "unsafe" - - "github.com/goccy/go-json/internal/runtime" -) - func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) { if typeptr > typeAddr.MaxTypeAddr { return compileToGetCodeSetSlowPath(typeptr) @@ -16,41 +11,10 @@ func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) { if codeSet := cachedOpcodeSets[index]; codeSet != nil { return codeSet, nil } - - // noescape trick for header.typ ( reflect.*rtype ) - copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr)) - - noescapeKeyCode, err := compileHead(&compileContext{ - typ: copiedType, - structTypeToCompiledCode: map[uintptr]*CompiledCode{}, - }) + codeSet, err := newCompiler().compile(typeptr) if err != nil { return nil, err } - escapeKeyCode, err := compileHead(&compileContext{ - typ: copiedType, - structTypeToCompiledCode: map[uintptr]*CompiledCode{}, - escapeKey: true, - }) - if err != nil { - return nil, err - } - noescapeKeyCode = copyOpcode(noescapeKeyCode) - escapeKeyCode = copyOpcode(escapeKeyCode) - setTotalLengthToInterfaceOp(noescapeKeyCode) - setTotalLengthToInterfaceOp(escapeKeyCode) - interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode) - interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode) - codeLength := noescapeKeyCode.TotalLength() - codeSet := &OpcodeSet{ - Type: copiedType, - NoescapeKeyCode: noescapeKeyCode, - EscapeKeyCode: escapeKeyCode, - InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode, - InterfaceEscapeKeyCode: interfaceEscapeKeyCode, - CodeLength: codeLength, - EndCode: ToEndCode(interfaceNoescapeKeyCode), - } cachedOpcodeSets[index] = codeSet return codeSet, nil } diff --git a/internal/encoder/compiler_race.go b/internal/encoder/compiler_race.go index 3a239e9..846a898 100644 --- a/internal/encoder/compiler_race.go +++ b/internal/encoder/compiler_race.go @@ -1,12 +1,10 @@ +//go:build race // +build race package encoder import ( "sync" - "unsafe" - - "github.com/goccy/go-json/internal/runtime" ) var setsMu sync.RWMutex @@ -23,41 +21,10 @@ func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) { } setsMu.RUnlock() - // noescape trick for header.typ ( reflect.*rtype ) - copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr)) - - noescapeKeyCode, err := compileHead(&compileContext{ - typ: copiedType, - structTypeToCompiledCode: map[uintptr]*CompiledCode{}, - }) + codeSet, err := newCompiler().compile(typeptr) if err != nil { return nil, err } - escapeKeyCode, err := compileHead(&compileContext{ - typ: copiedType, - structTypeToCompiledCode: map[uintptr]*CompiledCode{}, - escapeKey: true, - }) - if err != nil { - return nil, err - } - - noescapeKeyCode = copyOpcode(noescapeKeyCode) - escapeKeyCode = copyOpcode(escapeKeyCode) - setTotalLengthToInterfaceOp(noescapeKeyCode) - setTotalLengthToInterfaceOp(escapeKeyCode) - interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode) - interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode) - codeLength := noescapeKeyCode.TotalLength() - codeSet := &OpcodeSet{ - Type: copiedType, - NoescapeKeyCode: noescapeKeyCode, - EscapeKeyCode: escapeKeyCode, - InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode, - InterfaceEscapeKeyCode: interfaceEscapeKeyCode, - CodeLength: codeLength, - EndCode: ToEndCode(interfaceNoescapeKeyCode), - } setsMu.Lock() cachedOpcodeSets[index] = codeSet setsMu.Unlock() diff --git a/internal/encoder/context.go b/internal/encoder/context.go index 61b8908..3833d0c 100644 --- a/internal/encoder/context.go +++ b/internal/encoder/context.go @@ -9,44 +9,20 @@ import ( ) type compileContext struct { - typ *runtime.Type - opcodeIndex uint32 - ptrIndex int - indent uint32 - escapeKey bool - structTypeToCompiledCode map[uintptr]*CompiledCode - - parent *compileContext + opcodeIndex uint32 + ptrIndex int + indent uint32 + escapeKey bool + structTypeToCodes map[uintptr]Opcodes + recursiveCodes *Opcodes } -func (c *compileContext) context() *compileContext { - return &compileContext{ - typ: c.typ, - opcodeIndex: c.opcodeIndex, - ptrIndex: c.ptrIndex, - indent: c.indent, - escapeKey: c.escapeKey, - structTypeToCompiledCode: c.structTypeToCompiledCode, - parent: c, - } +func (c *compileContext) incIndent() { + c.indent++ } -func (c *compileContext) withType(typ *runtime.Type) *compileContext { - ctx := c.context() - ctx.typ = typ - return ctx -} - -func (c *compileContext) incIndent() *compileContext { - ctx := c.context() - ctx.indent++ - return ctx -} - -func (c *compileContext) decIndent() *compileContext { - ctx := c.context() - ctx.indent-- - return ctx +func (c *compileContext) decIndent() { + c.indent-- } func (c *compileContext) incIndex() { @@ -61,30 +37,18 @@ func (c *compileContext) decIndex() { func (c *compileContext) incOpcodeIndex() { c.opcodeIndex++ - if c.parent != nil { - c.parent.incOpcodeIndex() - } } func (c *compileContext) decOpcodeIndex() { c.opcodeIndex-- - if c.parent != nil { - c.parent.decOpcodeIndex() - } } func (c *compileContext) incPtrIndex() { c.ptrIndex++ - if c.parent != nil { - c.parent.incPtrIndex() - } } func (c *compileContext) decPtrIndex() { c.ptrIndex-- - if c.parent != nil { - c.parent.decPtrIndex() - } } const ( diff --git a/internal/encoder/opcode.go b/internal/encoder/opcode.go index 446baa2..3601f2b 100644 --- a/internal/encoder/opcode.go +++ b/internal/encoder/opcode.go @@ -38,7 +38,6 @@ type Opcode struct { Flags OpFlags Type *runtime.Type // go type - PrevField *Opcode // prev struct field Jmp *CompiledCode // for recursive call ElemIdx uint32 // offset to access array/slice/map elem Length uint32 // offset to access slice/map length or array length @@ -50,18 +49,40 @@ type Opcode struct { DisplayKey string // key text to display } -func (c *Opcode) IsStructHeadOp() bool { - if c == nil { - return false +func (c *Opcode) Validate() error { + var prevIdx uint32 + for code := c; !code.IsEnd(); { + if prevIdx != 0 { + if code.DisplayIdx != prevIdx+1 { + return fmt.Errorf( + "invalid index. previous display index is %d but next is %d. dump = %s", + prevIdx, code.DisplayIdx, c.Dump(), + ) + } + } + prevIdx = code.DisplayIdx + code = code.IterNext() } - return strings.Contains(c.Op.String(), "Head") + return nil } -func (c *Opcode) IsRecursiveOp() bool { +func (c *Opcode) IterNext() *Opcode { if c == nil { - return false + return nil } - return strings.Contains(c.Op.String(), "Recursive") + switch c.Op.CodeType() { + case CodeArrayElem, CodeSliceElem, CodeMapKey: + return c.End + default: + return c.Next + } +} + +func (c *Opcode) IsEnd() bool { + if c == nil { + return true + } + return c.Op == OpEnd || c.Op == OpInterfaceEnd || c.Op == OpRecursiveEnd } func (c *Opcode) MaxIdx() uint32 { @@ -287,43 +308,76 @@ func (c *Opcode) ToFieldType(isString bool) OpType { return OpStructField } -func newOpCode(ctx *compileContext, op OpType) *Opcode { - return newOpCodeWithNext(ctx, op, newEndOp(ctx)) +func newOpCode(ctx *compileContext, typ *runtime.Type, op OpType) *Opcode { + return newOpCodeWithNext(ctx, typ, op, newEndOp(ctx, typ)) } func opcodeOffset(idx int) uint32 { return uint32(idx) * uintptrSize } +func getCodeAddrByIdx(head *Opcode, idx uint32) *Opcode { + addr := uintptr(unsafe.Pointer(head)) + uintptr(idx)*unsafe.Sizeof(Opcode{}) + return *(**Opcode)(unsafe.Pointer(&addr)) +} + func copyOpcode(code *Opcode) *Opcode { - codeMap := map[uintptr]*Opcode{} - return code.copy(codeMap) + codeNum := ToEndCode(code).DisplayIdx + 1 + codeSlice := make([]Opcode, codeNum) + head := (*Opcode)((*runtime.SliceHeader)(unsafe.Pointer(&codeSlice)).Data) + ptr := head + c := code + for { + *ptr = Opcode{ + Op: c.Op, + Key: c.Key, + PtrNum: c.PtrNum, + NumBitSize: c.NumBitSize, + Flags: c.Flags, + Idx: c.Idx, + Offset: c.Offset, + Type: c.Type, + DisplayIdx: c.DisplayIdx, + DisplayKey: c.DisplayKey, + ElemIdx: c.ElemIdx, + Length: c.Length, + MapIter: c.MapIter, + MapPos: c.MapPos, + Size: c.Size, + Indent: c.Indent, + Jmp: c.Jmp, + } + if c.End != nil { + ptr.End = getCodeAddrByIdx(head, c.End.DisplayIdx) + } + if c.NextField != nil { + ptr.NextField = getCodeAddrByIdx(head, c.NextField.DisplayIdx) + } + if c.Next != nil { + ptr.Next = getCodeAddrByIdx(head, c.Next.DisplayIdx) + } + if c.IsEnd() { + break + } + ptr = getCodeAddrByIdx(head, c.DisplayIdx+1) + c = c.IterNext() + } + return head } func setTotalLengthToInterfaceOp(code *Opcode) { - c := code - for c.Op != OpEnd && c.Op != OpInterfaceEnd { + for c := code; !c.IsEnd(); { if c.Op == OpInterface { c.Length = uint32(code.TotalLength()) } - switch c.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - c = c.End - default: - c = c.Next - } + c = c.IterNext() } } func ToEndCode(code *Opcode) *Opcode { c := code - for c.Op != OpEnd && c.Op != OpInterfaceEnd { - switch c.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - c = c.End - default: - c = c.Next - } + for !c.IsEnd() { + c = c.IterNext() } return c } @@ -339,77 +393,25 @@ func copyToInterfaceOpcode(code *Opcode) *Opcode { return copied } -func newOpCodeWithNext(ctx *compileContext, op OpType, next *Opcode) *Opcode { +func newOpCodeWithNext(ctx *compileContext, typ *runtime.Type, op OpType, next *Opcode) *Opcode { return &Opcode{ Op: op, Idx: opcodeOffset(ctx.ptrIndex), Next: next, - Type: ctx.typ, + Type: typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } } -func newEndOp(ctx *compileContext) *Opcode { - return newOpCodeWithNext(ctx, OpEnd, nil) -} - -func (c *Opcode) copy(codeMap map[uintptr]*Opcode) *Opcode { - if c == nil { - return nil - } - addr := uintptr(unsafe.Pointer(c)) - if code, exists := codeMap[addr]; exists { - return code - } - copied := &Opcode{ - Op: c.Op, - Key: c.Key, - PtrNum: c.PtrNum, - NumBitSize: c.NumBitSize, - Flags: c.Flags, - Idx: c.Idx, - Offset: c.Offset, - Type: c.Type, - DisplayIdx: c.DisplayIdx, - DisplayKey: c.DisplayKey, - ElemIdx: c.ElemIdx, - Length: c.Length, - MapIter: c.MapIter, - MapPos: c.MapPos, - Size: c.Size, - Indent: c.Indent, - } - codeMap[addr] = copied - copied.End = c.End.copy(codeMap) - copied.PrevField = c.PrevField.copy(codeMap) - copied.NextField = c.NextField.copy(codeMap) - copied.Next = c.Next.copy(codeMap) - copied.Jmp = c.Jmp - return copied -} - -func (c *Opcode) BeforeLastCode() *Opcode { - code := c - for { - var nextCode *Opcode - switch code.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - nextCode = code.End - default: - nextCode = code.Next - } - if nextCode.Op == OpEnd { - return code - } - code = nextCode - } +func newEndOp(ctx *compileContext, typ *runtime.Type) *Opcode { + return newOpCodeWithNext(ctx, typ, OpEnd, nil) } func (c *Opcode) TotalLength() int { var idx int code := c - for code.Op != OpEnd && code.Op != OpInterfaceEnd { + for !code.IsEnd() { maxIdx := int(code.MaxIdx() / uintptrSize) if idx < maxIdx { idx = maxIdx @@ -417,12 +419,7 @@ func (c *Opcode) TotalLength() int { if code.Op == OpRecursiveEnd { break } - switch code.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - code = code.End - default: - code = code.Next - } + code = code.IterNext() } maxIdx := int(code.MaxIdx() / uintptrSize) if idx < maxIdx { @@ -432,7 +429,7 @@ func (c *Opcode) TotalLength() int { } func (c *Opcode) decOpcodeIndex() { - for code := c; code.Op != OpEnd; { + for code := c; !code.IsEnd(); { code.DisplayIdx-- if code.Idx > 0 { code.Idx -= uintptrSize @@ -446,24 +443,14 @@ func (c *Opcode) decOpcodeIndex() { if code.Length > 0 && code.Op.CodeType() != CodeArrayHead && code.Op.CodeType() != CodeArrayElem { code.Length -= uintptrSize } - switch code.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - code = code.End - default: - code = code.Next - } + code = code.IterNext() } } func (c *Opcode) decIndent() { - for code := c; code.Op != OpEnd; { + for code := c; !code.IsEnd(); { code.Indent-- - switch code.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - code = code.End - default: - code = code.Next - } + code = code.IterNext() } } @@ -475,7 +462,7 @@ func (c *Opcode) dumpHead(code *Opcode) string { length = code.Length / uintptrSize } return fmt.Sprintf( - `[%d]%s%s ([idx:%d][elemIdx:%d][length:%d])`, + `[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, @@ -487,7 +474,7 @@ func (c *Opcode) dumpHead(code *Opcode) string { func (c *Opcode) dumpMapHead(code *Opcode) string { return fmt.Sprintf( - `[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`, + `[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, @@ -500,7 +487,7 @@ func (c *Opcode) dumpMapHead(code *Opcode) string { func (c *Opcode) dumpMapEnd(code *Opcode) string { return fmt.Sprintf( - `[%d]%s%s ([idx:%d][mapPos:%d][length:%d])`, + `[%03d]%s%s ([idx:%d][mapPos:%d][length:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, @@ -518,7 +505,7 @@ func (c *Opcode) dumpElem(code *Opcode) string { length = code.Length / uintptrSize } return fmt.Sprintf( - `[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][size:%d])`, + `[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][size:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, @@ -531,7 +518,7 @@ func (c *Opcode) dumpElem(code *Opcode) string { func (c *Opcode) dumpField(code *Opcode) string { return fmt.Sprintf( - `[%d]%s%s ([idx:%d][key:%s][offset:%d])`, + `[%03d]%s%s ([idx:%d][key:%s][offset:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, @@ -543,7 +530,7 @@ func (c *Opcode) dumpField(code *Opcode) string { func (c *Opcode) dumpKey(code *Opcode) string { return fmt.Sprintf( - `[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`, + `[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, @@ -556,7 +543,7 @@ func (c *Opcode) dumpKey(code *Opcode) string { func (c *Opcode) dumpValue(code *Opcode) string { return fmt.Sprintf( - `[%d]%s%s ([idx:%d][mapIter:%d])`, + `[%03d]%s%s ([idx:%d][mapIter:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, @@ -567,7 +554,7 @@ func (c *Opcode) dumpValue(code *Opcode) string { func (c *Opcode) Dump() string { codes := []string{} - for code := c; code.Op != OpEnd && code.Op != OpInterfaceEnd; { + for code := c; !code.IsEnd(); { switch code.Op.CodeType() { case CodeSliceHead: codes = append(codes, c.dumpHead(code)) @@ -595,7 +582,7 @@ func (c *Opcode) Dump() string { code = code.Next default: codes = append(codes, fmt.Sprintf( - "[%d]%s%s ([idx:%d])", + "[%03d]%s%s ([idx:%d])", code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, @@ -607,44 +594,7 @@ func (c *Opcode) Dump() string { return strings.Join(codes, "\n") } -func prevField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode { - if _, exists := removedFields[code]; exists { - return prevField(code.PrevField, removedFields) - } - return code -} - -func nextField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode { - if _, exists := removedFields[code]; exists { - return nextField(code.NextField, removedFields) - } - return code -} - -func linkPrevToNextField(cur *Opcode, removedFields map[*Opcode]struct{}) { - prev := prevField(cur.PrevField, removedFields) - prev.NextField = nextField(cur.NextField, removedFields) - code := prev - fcode := cur - for { - var nextCode *Opcode - switch code.Op.CodeType() { - case CodeArrayElem, CodeSliceElem, CodeMapKey: - nextCode = code.End - default: - nextCode = code.Next - } - if nextCode == fcode { - code.Next = fcode.NextField - break - } else if nextCode.Op == OpEnd { - break - } - code = nextCode - } -} - -func newSliceHeaderCode(ctx *compileContext) *Opcode { +func newSliceHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) @@ -652,6 +602,7 @@ func newSliceHeaderCode(ctx *compileContext) *Opcode { length := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpSlice, + Type: typ, Idx: idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: elemIdx, @@ -660,9 +611,10 @@ func newSliceHeaderCode(ctx *compileContext) *Opcode { } } -func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode { +func newSliceElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, size uintptr) *Opcode { return &Opcode{ Op: OpSliceElem, + Type: typ, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, @@ -672,12 +624,13 @@ func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode { } } -func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode { +func newArrayHeaderCode(ctx *compileContext, typ *runtime.Type, alen int) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpArray, + Type: typ, Idx: idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: elemIdx, @@ -686,9 +639,10 @@ func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode { } } -func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintptr) *Opcode { +func newArrayElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, length int, size uintptr) *Opcode { return &Opcode{ Op: OpArrayElem, + Type: typ, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, @@ -698,7 +652,7 @@ func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintpt } } -func newMapHeaderCode(ctx *compileContext) *Opcode { +func newMapHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) @@ -708,8 +662,8 @@ func newMapHeaderCode(ctx *compileContext) *Opcode { mapIter := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpMap, + Type: typ, Idx: idx, - Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, ElemIdx: elemIdx, Length: length, @@ -718,9 +672,10 @@ func newMapHeaderCode(ctx *compileContext) *Opcode { } } -func newMapKeyCode(ctx *compileContext, head *Opcode) *Opcode { +func newMapKeyCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode { return &Opcode{ Op: OpMapKey, + Type: typ, Idx: opcodeOffset(ctx.ptrIndex), DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, @@ -730,9 +685,10 @@ func newMapKeyCode(ctx *compileContext, head *Opcode) *Opcode { } } -func newMapValueCode(ctx *compileContext, head *Opcode) *Opcode { +func newMapValueCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode { return &Opcode{ Op: OpMapValue, + Type: typ, Idx: opcodeOffset(ctx.ptrIndex), DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, @@ -742,14 +698,15 @@ func newMapValueCode(ctx *compileContext, head *Opcode) *Opcode { } } -func newMapEndCode(ctx *compileContext, head *Opcode) *Opcode { +func newMapEndCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode { mapPos := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() idx := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpMapEnd, + Type: typ, Idx: idx, - Next: newEndOp(ctx), + Next: newEndOp(ctx, typ), DisplayIdx: ctx.opcodeIndex, Length: head.Length, MapPos: mapPos, @@ -757,28 +714,12 @@ 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), - Next: newEndOp(ctx), - Type: ctx.typ, - DisplayIdx: ctx.opcodeIndex, - Indent: ctx.indent, - Flags: flag, - } -} - -func newRecursiveCode(ctx *compileContext, jmp *CompiledCode) *Opcode { +func newRecursiveCode(ctx *compileContext, typ *runtime.Type, jmp *CompiledCode) *Opcode { return &Opcode{ Op: OpRecursive, + Type: typ, Idx: opcodeOffset(ctx.ptrIndex), - Next: newEndOp(ctx), - Type: ctx.typ, + Next: newEndOp(ctx, typ), DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, Jmp: jmp, diff --git a/size_test.go b/size_test.go index a57d663..3a47195 100644 --- a/size_test.go +++ b/size_test.go @@ -11,8 +11,8 @@ func TestOpcodeSize(t *testing.T) { const uintptrSize = 4 << (^uintptr(0) >> 63) if uintptrSize == 8 { size := unsafe.Sizeof(encoder.Opcode{}) - if size != 128 { - t.Fatalf("unexpected opcode size: expected 128bytes but got %dbytes", size) + if size != 120 { + t.Fatalf("unexpected opcode size: expected 120bytes but got %dbytes", size) } } } diff --git a/test/cover/cover_array_test.go b/test/cover/cover_array_test.go index 336ef39..1614078 100644 --- a/test/cover/cover_array_test.go +++ b/test/cover/cover_array_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1840,19 +1841,21 @@ func TestCoverArray(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_bool_test.go b/test/cover/cover_bool_test.go index 2bc3f59..8dea174 100644 --- a/test/cover/cover_bool_test.go +++ b/test/cover/cover_bool_test.go @@ -3,6 +3,7 @@ package json_test import ( "bytes" stdjson "encoding/json" + "fmt" "testing" "github.com/goccy/go-json" @@ -2653,19 +2654,21 @@ func TestCoverBool(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_bytes_test.go b/test/cover/cover_bytes_test.go index 49eff88..28ab9db 100644 --- a/test/cover/cover_bytes_test.go +++ b/test/cover/cover_bytes_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1807,19 +1808,21 @@ func TestCoverBytes(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_float32_test.go b/test/cover/cover_float32_test.go index 961d8af..44fecdf 100644 --- a/test/cover/cover_float32_test.go +++ b/test/cover/cover_float32_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -2334,19 +2335,21 @@ func TestCoverFloat32(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_float64_test.go b/test/cover/cover_float64_test.go index 39235d0..c94644e 100644 --- a/test/cover/cover_float64_test.go +++ b/test/cover/cover_float64_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -2334,19 +2335,21 @@ func TestCoverFloat64(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_int16_test.go b/test/cover/cover_int16_test.go index f16a944..cc7867d 100644 --- a/test/cover/cover_int16_test.go +++ b/test/cover/cover_int16_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1811,19 +1812,21 @@ func TestCoverInt16(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_int32_test.go b/test/cover/cover_int32_test.go index 71cd397..195301b 100644 --- a/test/cover/cover_int32_test.go +++ b/test/cover/cover_int32_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1788,19 +1789,21 @@ func TestCoverInt32(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_int64_test.go b/test/cover/cover_int64_test.go index 0879cbf..612fa8e 100644 --- a/test/cover/cover_int64_test.go +++ b/test/cover/cover_int64_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1788,19 +1789,21 @@ func TestCoverInt64(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_int8_test.go b/test/cover/cover_int8_test.go index 673b67b..b609e98 100644 --- a/test/cover/cover_int8_test.go +++ b/test/cover/cover_int8_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1788,19 +1789,21 @@ func TestCoverInt8(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_int_test.go b/test/cover/cover_int_test.go index b21a528..c2377d4 100644 --- a/test/cover/cover_int_test.go +++ b/test/cover/cover_int_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -2410,19 +2411,21 @@ func TestCoverInt(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_map_test.go b/test/cover/cover_map_test.go index 2cf08d2..0b9a5fe 100644 --- a/test/cover/cover_map_test.go +++ b/test/cover/cover_map_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1881,19 +1882,21 @@ func TestCoverMap(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_marshal_json_test.go b/test/cover/cover_marshal_json_test.go index 6931bf3..95d92b8 100644 --- a/test/cover/cover_marshal_json_test.go +++ b/test/cover/cover_marshal_json_test.go @@ -3690,19 +3690,21 @@ func TestCoverMarshalJSON(t *testing.T) { t.Run(test.name, func(t *testing.T) { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } }) diff --git a/test/cover/cover_marshal_text_test.go b/test/cover/cover_marshal_text_test.go index 62022ae..9cee6d4 100644 --- a/test/cover/cover_marshal_text_test.go +++ b/test/cover/cover_marshal_text_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -3527,19 +3528,21 @@ func TestCoverMarshalText(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_number_test.go b/test/cover/cover_number_test.go index dc4b1d3..5b98a08 100644 --- a/test/cover/cover_number_test.go +++ b/test/cover/cover_number_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -2334,19 +2335,21 @@ func TestCoverNumber(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_slice_test.go b/test/cover/cover_slice_test.go index eb60d94..61de9a5 100644 --- a/test/cover/cover_slice_test.go +++ b/test/cover/cover_slice_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -2047,9 +2048,9 @@ func TestCoverSlice(t *testing.T) { }, } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - for _, indent := range []bool{true, false} { - for _, htmlEscape := range []bool{true, false} { + for _, indent := range []bool{true, false} { + for _, htmlEscape := range []bool{true, false} { + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { var buf bytes.Buffer enc := json.NewEncoder(&buf) enc.SetEscapeHTML(htmlEscape) @@ -2063,8 +2064,8 @@ func TestCoverSlice(t *testing.T) { if buf.String() != stdresult { t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) } - } + }) } - }) + } } } diff --git a/test/cover/cover_string_test.go b/test/cover/cover_string_test.go index 591daaa..41904e4 100644 --- a/test/cover/cover_string_test.go +++ b/test/cover/cover_string_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -2370,19 +2371,21 @@ func TestCoverString(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_uint16_test.go b/test/cover/cover_uint16_test.go index f14470a..4514bb0 100644 --- a/test/cover/cover_uint16_test.go +++ b/test/cover/cover_uint16_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1788,19 +1789,21 @@ func TestCoverUint16(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%v,indent:%v): %+v: %s", test.name, htmlEscape, indent, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_uint32_test.go b/test/cover/cover_uint32_test.go index a0fb2f5..8d852b9 100644 --- a/test/cover/cover_uint32_test.go +++ b/test/cover/cover_uint32_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1788,19 +1789,21 @@ func TestCoverUint32(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_uint64_test.go b/test/cover/cover_uint64_test.go index 4b56d54..bc074ce 100644 --- a/test/cover/cover_uint64_test.go +++ b/test/cover/cover_uint64_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1788,19 +1789,21 @@ func TestCoverUint64(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_uint8_test.go b/test/cover/cover_uint8_test.go index 640e8f6..fab9403 100644 --- a/test/cover/cover_uint8_test.go +++ b/test/cover/cover_uint8_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -1788,19 +1789,21 @@ func TestCoverUint8(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } } diff --git a/test/cover/cover_uint_test.go b/test/cover/cover_uint_test.go index 35781ec..e33064a 100644 --- a/test/cover/cover_uint_test.go +++ b/test/cover/cover_uint_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + "fmt" "testing" "github.com/goccy/go-json" @@ -2334,19 +2335,21 @@ func TestCoverUint(t *testing.T) { for _, test := range tests { for _, indent := range []bool{true, false} { for _, htmlEscape := range []bool{true, false} { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(htmlEscape) - if indent { - enc.SetIndent("", " ") - } - if err := enc.Encode(test.data); err != nil { - t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) - } - stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) - if buf.String() != stdresult { - t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) - } + t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if indent { + enc.SetIndent("", " ") + } + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %+v: %s", test.name, htmlEscape, test.data, err) + } + stdresult := encodeByEncodingJSON(test.data, indent, htmlEscape) + if buf.String() != stdresult { + t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String()) + } + }) } } }