2
0
Fork 0
mirror of https://github.com/ii64/sonic.git synced 2026-06-21 00:46:43 +08:00

fix: marshal struct field with omitempty tag as encoding/json (#114)

This commit is contained in:
liu 2021-10-12 15:14:39 +08:00 committed by GitHub
parent 530d423243
commit 3eca433cb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 180 deletions

View file

@ -232,8 +232,6 @@ var _OpFuncTab = [256]func(*_Assembler, *_Instr) {
_OP_is_zero_4 : (*_Assembler)._asm_OP_is_zero_4,
_OP_is_zero_8 : (*_Assembler)._asm_OP_is_zero_8,
_OP_is_zero_map : (*_Assembler)._asm_OP_is_zero_map,
_OP_is_zero_mem : (*_Assembler)._asm_OP_is_zero_mem,
_OP_is_zero_safe : (*_Assembler)._asm_OP_is_zero_safe,
_OP_goto : (*_Assembler)._asm_OP_goto,
_OP_map_iter : (*_Assembler)._asm_OP_map_iter,
_OP_map_stop : (*_Assembler)._asm_OP_map_stop,
@ -698,79 +696,6 @@ func (self *_Assembler) encode_string(doubleQuote bool) {
}
}
/** Zero Value Check Routine **/
func (self *_Assembler) check_zero(nb int, dest int) {
i := int64(0)
e := int64(nb)
/* special case: zero-sized value, always empty */
if e == 0 {
return
}
/* 32-byte test */
for i <= e - 32 {
self.Emit("VMOVDQU", jit.Ptr(_SP_p, i), _Y0) // VMOVDQU (SP.p), Y0
self.Emit("VPTEST" , _Y0, _Y0) // VPTEST Y0, Y0
self.Sjmp("JNZ" , "_not_zero_z_{n}") // JNZ _not_zero_z_{n}
i += 32
}
/* VZEROUPPER to avoid AVX-SSE transition penalty */
if e >= 32 {
self.Emit("VZEROUPPER")
}
/* 16-byte test */
if i <= e - 16 {
self.Emit("MOVOU", jit.Ptr(_SP_p, i), _X0) // MOVOU (SP.p), X0
self.Emit("PTEST", _X0, _X0) // PTEST X0, X0
self.Sjmp("JNZ" , "_not_zero_{n}") // JNZ _not_zero_{n}
i += 16
}
/* 8-byte test */
if i <= e - 8 {
self.Emit("CMPQ", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPQ i(SP.p), $0
self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n}
i += 8
}
/* 4 byte test */
if i <= e - 4 {
self.Emit("CMPL", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPL i(SP.p), $0
self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n}
i += 4
}
/* 2 byte test */
if i <= e - 2 {
self.Emit("CMPW", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPW i(SP.p), $0
self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n}
i += 2
}
/* the last byte */
if i < e {
self.Emit("CMPB", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPB i(SP.p), $0
self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n}
}
/* value is not zero */
if e < 32 {
self.Xjmp("JMP", dest)
self.Link("_not_zero_{n}")
return
}
/* VZEROUPPER to avoid AVX-SSE transition penalty */
self.Xjmp("JMP", dest)
self.Link("_not_zero_z_{n}")
self.Emit("VZEROUPPER")
self.Link("_not_zero_{n}")
}
/** OpCode Assembler Functions **/
var (
@ -787,7 +712,6 @@ var (
var (
_F_memmove = jit.Func(memmove)
_F_isZeroTyped = jit.Func(isZeroTyped)
_F_error_number = jit.Func(error_number)
_F_isValidNumber = jit.Func(isValidNumber)
)
@ -1092,20 +1016,6 @@ func (self *_Assembler) _asm_OP_is_zero_map(p *_Instr) {
self.Xjmp("JE" , p.vi()) // JE p.vi()
}
func (self *_Assembler) _asm_OP_is_zero_mem(p *_Instr) {
self.check_zero(p.vlen(), p.vi())
}
func (self *_Assembler) _asm_OP_is_zero_safe(p *_Instr) {
self.check_zero(p.vlen(), p.vi()) // CHECKZ $p.vlen(), p.vi()
self.Emit("MOVQ", jit.Type(p.vt()), _AX) // MOVQ $p.vt(), AX
self.Emit("MOVQ", _SP_p, jit.Ptr(_SP, 0)) // MOVQ SP.p, (SP)
self.Emit("MOVQ", _AX, jit.Ptr(_SP, 8)) // MOVQ AX, 8(SP)
self.call_go(_F_isZeroTyped) // CALL_GO isZeroTyped
self.Emit("CMPQ", jit.Ptr(_SP, 16), jit.Imm(0)) // CMPQ 16(SP), $0
self.Xjmp("JNE" , p.vi()) // JNE p.vi()
}
func (self *_Assembler) _asm_OP_goto(p *_Instr) {
self.Xjmp("JMP", p.vi())
}

View file

@ -64,8 +64,6 @@ const (
_OP_is_zero_4
_OP_is_zero_8
_OP_is_zero_map
_OP_is_zero_mem
_OP_is_zero_safe
_OP_goto
_OP_map_iter
_OP_map_stop
@ -127,8 +125,6 @@ var _OpNames = [256]string {
_OP_is_zero_4 : "is_zero_4",
_OP_is_zero_8 : "is_zero_8",
_OP_is_zero_map : "is_zero_map",
_OP_is_zero_mem : "is_zero_mem",
_OP_is_zero_safe : "is_zero_safe",
_OP_goto : "goto",
_OP_map_iter : "map_iter",
_OP_map_stop : "map_stop",
@ -259,8 +255,6 @@ func (self _Instr) isBranch() bool {
case _OP_is_zero_2 : fallthrough
case _OP_is_zero_4 : fallthrough
case _OP_is_zero_8 : fallthrough
case _OP_is_zero_mem : fallthrough
case _OP_is_zero_safe : fallthrough
case _OP_map_check_key : fallthrough
case _OP_map_write_key : fallthrough
case _OP_slice_next : fallthrough
@ -291,8 +285,6 @@ func (self _Instr) disassemble() string {
case _OP_cond_testc : fallthrough
case _OP_map_check_key : fallthrough
case _OP_map_write_key : return fmt.Sprintf("%-18sL_%d", self.op().String(), self.vi())
case _OP_is_zero_mem : fallthrough
case _OP_is_zero_safe : fallthrough
case _OP_slice_next : return fmt.Sprintf("%-18sL_%d, %s", self.op().String(), self.vi(), self.vt())
default : return self.op().String()
}
@ -686,7 +678,7 @@ func (self *_Compiler) compileStructBody(p *_Program, sp int, vt reflect.Type) {
}
/* check for "omitempty" option */
if (fv.Opts & resolver.F_omitempty) != 0 {
if fv.Type.Kind() != reflect.Struct && fv.Type.Kind() != reflect.Array && (fv.Opts & resolver.F_omitempty) != 0 {
s = append(s, p.pc())
self.compileStructFieldZero(p, fv.Type)
}
@ -799,7 +791,6 @@ func (self *_Compiler) compileStructFieldZero(p *_Program, vt reflect.Type) {
case reflect.Map : p.add(_OP_is_zero_map)
case reflect.Ptr : p.add(_OP_is_nil)
case reflect.Slice : p.add(_OP_is_nil_p1)
case reflect.Struct : self.compileStructFieldNonTrivialZero(p, vt)
default : panic(error_type(vt))
}
}
@ -810,14 +801,6 @@ func (self *_Compiler) compileStructFieldQuoted(p *_Program, sp int, vt reflect.
p.int(_OP_byte, '"')
}
func (self *_Compiler) compileStructFieldNonTrivialZero(p *_Program, vt reflect.Type) {
if isTrivialZeroable(vt) {
p.rtt(_OP_is_zero_mem, vt)
} else {
p.rtt(_OP_is_zero_safe, vt)
}
}
func (self *_Compiler) compileInterface(p *_Program, vt reflect.Type) {
x := p.pc()
p.add(_OP_is_nil_p1)

View file

@ -19,7 +19,6 @@ package encoder
import (
`encoding`
`encoding/json`
`reflect`
`unsafe`
`github.com/bytedance/sonic/internal/native`
@ -92,74 +91,4 @@ func encodeTextMarshaler(buf *[]byte, val encoding.TextMarshaler) error {
} else {
return encodeString(buf, rt.Mem2Str(ret))
}
}
func isZeroSafe(p unsafe.Pointer, vt *rt.GoType) bool {
if native.Lzero(p, int(vt.Size)) == 0 {
return true
} else {
return isZeroTyped(p, vt)
}
}
func isZeroTyped(p unsafe.Pointer, vt *rt.GoType) bool {
switch vt.Kind() {
case reflect.Map : return (*(**rt.GoMap)(p)).Count == 0
case reflect.Slice : return (*rt.GoSlice)(p).Len == 0
case reflect.String : return (*rt.GoString)(p).Len == 0
case reflect.Struct : return isZeroStruct(p, vt)
case reflect.Interface : return (*rt.GoEface)(p).Value == nil
default : return false
}
}
func isZeroStruct(p unsafe.Pointer, vt *rt.GoType) bool {
var dp uintptr
var fp unsafe.Pointer
/* check for each field */
for _, fv := range (*rt.GoStructType)(unsafe.Pointer(vt)).Fields {
dp = fv.OffEmbed >> 1
fp = unsafe.Pointer(uintptr(p) + dp)
/* check for the field */
if !isZeroSafe(fp, fv.Type) {
return false
}
}
/* all tests are passed */
return true
}
func isTrivialZeroable(vt reflect.Type) bool {
switch vt.Kind() {
case reflect.Bool : return true
case reflect.Int : return true
case reflect.Int8 : return true
case reflect.Int16 : return true
case reflect.Int32 : return true
case reflect.Int64 : return true
case reflect.Uint : return true
case reflect.Uint8 : return true
case reflect.Uint16 : return true
case reflect.Uint32 : return true
case reflect.Uint64 : return true
case reflect.Uintptr : return true
case reflect.Float32 : return true
case reflect.Float64 : return true
case reflect.String : return false
case reflect.Array : return true
case reflect.Interface : return false
case reflect.Map : return false
case reflect.Ptr : return true
case reflect.Slice : return false
case reflect.Struct : return isStructTrivialZeroable(vt)
default : return false
}
}
func isStructTrivialZeroable(vt reflect.Type) bool {
for i := 0; i < vt.NumField(); i++ { if !isTrivialZeroable(vt.Field(i).Type) { return false } }
return true
}
}

160
issue_test/issue113_test.go Normal file
View file

@ -0,0 +1,160 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package issue_test
import (
`testing`
`github.com/bytedance/sonic/encoder`
`github.com/stretchr/testify/require`
)
type Issue113_OmitemptyOpt struct {
Bool bool `json:"bool,omitempty"`
Int int `json:"int,omitempty"`
Int8 int8 `json:"int8,omitempty"`
Int16 int16 `json:"int16,omitempty"`
Int32 int32 `json:"int32,omitempty"`
Int64 int64 `json:"int64,omitempty"`
Uint uint `json:"uint,omitempty"`
Uint8 uint8 `json:"uint8,omitempty"`
Uint16 uint16 `json:"uint16,omitempty"`
Uint32 uint32 `json:"uint32,omitempty"`
Uint64 uint64 `json:"uint64,omitempty"`
Float32 float32 `json:"float32,omitempty"`
Float64 float64 `json:"float64,omitempty"`
Uintptr uintptr `json:"uintptr,omitempty"`
String string `json:"string,omitempty"`
Array0 [0]uint `json:"array0,omitempty"`
Array [2]int `json:"array,omitempty"`
Interface interface{} `json:"interface,omitempty"`
Map0 map[int]interface{} `json:"map0,omitempty"`
Map map[string]float64 `json:"map,omitempty"`
Slice0 []int `json:"slice0,omitempty"`
Slice []byte `json:"slice,omitempty"`
Ptr * Issue113_Inner `json:"ptr,omitempty"`
Struct1 Issue113_Inner `json:"struct1,omitempty"`
Struct2 struct{} `json:"struct2,omitempty"`
}
type Issue113_Inner struct {
S string `json:"s,"`
So string `json:"so,omitempty"`
}
var issue13ExpectedEmptyOpt = `{
"array": [
0,
0
],
"struct1": {
"s": ""
},
"struct2": {}
}`
var issue13ExpectedNonemptyOpt = `{
"bool": true,
"int": 1,
"int8": -1,
"int16": 1,
"int32": 2,
"int64": 64,
"uint": 1,
"uint8": 8,
"uint16": 16,
"uint32": 32,
"uint64": 64,
"float32": 1,
"float64": -2.34e64,
"uintptr": 1,
"string": "string",
"array": [
0,
-1
],
"interface": {
"s": "not omit"
},
"map0": {
"0": "zero"
},
"map": {
"key": 0
},
"slice0": [
0
],
"slice": "Yg==",
"ptr": {
"s": "not omit"
},
"struct1": {
"s": "not omit"
},
"struct2": {}
}`
func TestIssue113_MarshalEmptyFieldsWithOmitemptyOpt(t *testing.T) {
var obj Issue113_OmitemptyOpt
obj.Slice0 = make([]int, 0, 100) // empty slice
obj.Map0 = make(map[int]interface{}) // empty map
got, err := encoder.EncodeIndented(&obj, "", " ", 0)
require.NoError(t, err)
require.Equal(t, issue13ExpectedEmptyOpt, string(got))
}
func TestIssue113_MarshalNonemptyFieldsWithOmitemptyOpt(t *testing.T) {
var inner = & Issue113_Inner {
S : "not omit",
}
var obj = & Issue113_OmitemptyOpt{
Bool : true,
Int : 1,
Int8 : -1,
Int16 : 1,
Int32 : 2,
Int64 : 64,
Uint : 1,
Uint8 : 8,
Uint16 : 16,
Uint32 : 32,
Uint64 : 64,
Float32 : 1.0,
Float64 : -2.34e+64,
Uintptr : uintptr(0x1),
String : "string",
Array0 : [0]uint{},
Array : [2]int{0, -1},
Interface : *inner,
Map0 : map[int]interface{}{0 : "zero"},
Map : map[string]float64{"key" : 0.0},
Slice0 : make([]int, 1, 1),
Slice : []byte("b"),
Ptr : inner,
Struct1 : *inner,
Struct2 : struct{}{},
}
got, err := encoder.EncodeIndented(&obj, "", " ", 0)
require.NoError(t, err)
require.Equal(t, issue13ExpectedNonemptyOpt, string(got))
}