diff --git a/coverage_test.go b/coverage_test.go index bc94e55..6fbbecc 100644 --- a/coverage_test.go +++ b/coverage_test.go @@ -18,6 +18,7 @@ func uint32ptr(v uint32) *uint32 { return &v } func uint64ptr(v uint64) *uint64 { return &v } func float32ptr(v float32) *float32 { return &v } func float64ptr(v float64) *float64 { return &v } +func stringptr(v string) *string { return &v } func TestCoverStructHeadInt(t *testing.T) { type structInt struct { @@ -7472,3 +7473,624 @@ func TestCoverStructHeadFloat64(t *testing.T) { } } } + +func TestCoverStructHeadString(t *testing.T) { + type structString struct { + A string `json:"a"` + } + type structStringPtr struct { + A *string `json:"a"` + } + + tests := []struct { + name string + expected string + data interface{} + }{ + { + name: "HeadStringZero", + expected: `{"a":""}`, + data: struct { + A string `json:"a"` + }{}, + }, + { + name: "HeadString", + expected: `{"a":"foo"}`, + data: struct { + A string `json:"a"` + }{A: "foo"}, + }, + { + name: "HeadStringPtr", + expected: `{"a":"foo"}`, + data: struct { + A *string `json:"a"` + }{A: stringptr("foo")}, + }, + { + name: "HeadStringPtrNil", + expected: `{"a":null}`, + data: struct { + A *string `json:"a"` + }{A: nil}, + }, + { + name: "PtrHeadStringZero", + expected: `{"a":""}`, + data: &struct { + A string `json:"a"` + }{}, + }, + { + name: "PtrHeadString", + expected: `{"a":"foo"}`, + data: &struct { + A string `json:"a"` + }{A: "foo"}, + }, + { + name: "PtrHeadStringPtr", + expected: `{"a":"foo"}`, + data: &struct { + A *string `json:"a"` + }{A: stringptr("foo")}, + }, + { + name: "PtrHeadStringPtrNil", + expected: `{"a":null}`, + data: &struct { + A *string `json:"a"` + }{A: nil}, + }, + { + name: "PtrHeadStringNil", + expected: `null`, + data: (*struct { + A *string `json:"a"` + })(nil), + }, + { + name: "HeadStringZeroMultiFields", + expected: `{"a":"","b":""}`, + data: struct { + A string `json:"a"` + B string `json:"b"` + }{}, + }, + { + name: "HeadStringMultiFields", + expected: `{"a":"foo","b":"bar"}`, + data: struct { + A string `json:"a"` + B string `json:"b"` + }{A: "foo", B: "bar"}, + }, + { + name: "HeadStringPtrMultiFields", + expected: `{"a":"foo","b":"bar"}`, + data: struct { + A *string `json:"a"` + B *string `json:"b"` + }{A: stringptr("foo"), B: stringptr("bar")}, + }, + { + name: "HeadStringPtrNilMultiFields", + expected: `{"a":null,"b":null}`, + data: struct { + A *string `json:"a"` + B *string `json:"b"` + }{A: nil, B: nil}, + }, + { + name: "PtrHeadStringZeroMultiFields", + expected: `{"a":"","b":""}`, + data: &struct { + A string `json:"a"` + B string `json:"b"` + }{}, + }, + { + name: "PtrHeadStringMultiFields", + expected: `{"a":"foo","b":"bar"}`, + data: &struct { + A string `json:"a"` + B string `json:"b"` + }{A: "foo", B: "bar"}, + }, + { + name: "PtrHeadStringPtrMultiFields", + expected: `{"a":"foo","b":"bar"}`, + data: &struct { + A *string `json:"a"` + B *string `json:"b"` + }{A: stringptr("foo"), B: stringptr("bar")}, + }, + { + name: "PtrHeadStringPtrNilMultiFields", + expected: `{"a":null,"b":null}`, + data: &struct { + A *string `json:"a"` + B *string `json:"b"` + }{A: nil, B: nil}, + }, + { + name: "PtrHeadStringNilMultiFields", + expected: `null`, + data: (*struct { + A *string `json:"a"` + B *string `json:"b"` + })(nil), + }, + { + name: "HeadStringZeroNotRoot", + expected: `{"A":{"a":""}}`, + data: struct { + A struct { + A string `json:"a"` + } + }{}, + }, + { + name: "HeadStringNotRoot", + expected: `{"A":{"a":"foo"}}`, + data: struct { + A struct { + A string `json:"a"` + } + }{A: struct { + A string `json:"a"` + }{A: "foo"}}, + }, + { + name: "HeadStringPtrNotRoot", + expected: `{"A":{"a":"foo"}}`, + data: struct { + A struct { + A *string `json:"a"` + } + }{A: struct { + A *string `json:"a"` + }{stringptr("foo")}}, + }, + { + name: "HeadStringPtrNilNotRoot", + expected: `{"A":{"a":null}}`, + data: struct { + A struct { + A *string `json:"a"` + } + }{}, + }, + { + name: "PtrHeadStringZeroNotRoot", + expected: `{"A":{"a":""}}`, + data: struct { + A *struct { + A string `json:"a"` + } + }{A: new(struct { + A string `json:"a"` + })}, + }, + { + name: "PtrHeadStringNotRoot", + expected: `{"A":{"a":"foo"}}`, + data: struct { + A *struct { + A string `json:"a"` + } + }{A: &(struct { + A string `json:"a"` + }{A: "foo"})}, + }, + { + name: "PtrHeadStringPtrNotRoot", + expected: `{"A":{"a":"foo"}}`, + data: struct { + A *struct { + A *string `json:"a"` + } + }{A: &(struct { + A *string `json:"a"` + }{A: stringptr("foo")})}, + }, + { + name: "PtrHeadStringPtrNilNotRoot", + expected: `{"A":{"a":null}}`, + data: struct { + A *struct { + A *string `json:"a"` + } + }{A: &(struct { + A *string `json:"a"` + }{A: nil})}, + }, + { + name: "PtrHeadStringNilNotRoot", + expected: `{"A":null}`, + data: struct { + A *struct { + A *string `json:"a"` + } + }{A: nil}, + }, + { + name: "HeadStringZeroMultiFieldsNotRoot", + expected: `{"A":{"a":""},"B":{"b":""}}`, + data: struct { + A struct { + A string `json:"a"` + } + B struct { + B string `json:"b"` + } + }{}, + }, + { + name: "HeadStringMultiFieldsNotRoot", + expected: `{"A":{"a":"foo"},"B":{"b":"bar"}}`, + data: struct { + A struct { + A string `json:"a"` + } + B struct { + B string `json:"b"` + } + }{A: struct { + A string `json:"a"` + }{A: "foo"}, B: struct { + B string `json:"b"` + }{B: "bar"}}, + }, + { + name: "HeadStringPtrMultiFieldsNotRoot", + expected: `{"A":{"a":"foo"},"B":{"b":"bar"}}`, + data: struct { + A struct { + A *string `json:"a"` + } + B struct { + B *string `json:"b"` + } + }{A: struct { + A *string `json:"a"` + }{A: stringptr("foo")}, B: struct { + B *string `json:"b"` + }{B: stringptr("bar")}}, + }, + { + name: "HeadStringPtrNilMultiFieldsNotRoot", + expected: `{"A":{"a":null},"B":{"b":null}}`, + data: struct { + A struct { + A *string `json:"a"` + } + B struct { + B *string `json:"b"` + } + }{A: struct { + A *string `json:"a"` + }{A: nil}, B: struct { + B *string `json:"b"` + }{B: nil}}, + }, + { + name: "PtrHeadStringZeroMultiFieldsNotRoot", + expected: `{"A":{"a":""},"B":{"b":""}}`, + data: &struct { + A struct { + A string `json:"a"` + } + B struct { + B string `json:"b"` + } + }{}, + }, + { + name: "PtrHeadStringMultiFieldsNotRoot", + expected: `{"A":{"a":"foo"},"B":{"b":"bar"}}`, + data: &struct { + A struct { + A string `json:"a"` + } + B struct { + B string `json:"b"` + } + }{A: struct { + A string `json:"a"` + }{A: "foo"}, B: struct { + B string `json:"b"` + }{B: "bar"}}, + }, + { + name: "PtrHeadStringPtrMultiFieldsNotRoot", + expected: `{"A":{"a":"foo"},"B":{"b":"bar"}}`, + data: &struct { + A *struct { + A *string `json:"a"` + } + B *struct { + B *string `json:"b"` + } + }{A: &(struct { + A *string `json:"a"` + }{A: stringptr("foo")}), B: &(struct { + B *string `json:"b"` + }{B: stringptr("bar")})}, + }, + { + name: "PtrHeadStringPtrNilMultiFieldsNotRoot", + expected: `{"A":null,"B":null}`, + data: &struct { + A *struct { + A *string `json:"a"` + } + B *struct { + B *string `json:"b"` + } + }{A: nil, B: nil}, + }, + { + name: "PtrHeadStringNilMultiFieldsNotRoot", + expected: `null`, + data: (*struct { + A *struct { + A *string `json:"a"` + } + B *struct { + B *string `json:"b"` + } + })(nil), + }, + { + name: "PtrHeadStringDoubleMultiFieldsNotRoot", + expected: `{"A":{"a":"foo","b":"bar"},"B":{"a":"foo","b":"bar"}}`, + data: &struct { + A *struct { + A string `json:"a"` + B string `json:"b"` + } + B *struct { + A string `json:"a"` + B string `json:"b"` + } + }{A: &(struct { + A string `json:"a"` + B string `json:"b"` + }{A: "foo", B: "bar"}), B: &(struct { + A string `json:"a"` + B string `json:"b"` + }{A: "foo", B: "bar"})}, + }, + { + name: "PtrHeadStringNilDoubleMultiFieldsNotRoot", + expected: `{"A":null,"B":null}`, + data: &struct { + A *struct { + A string `json:"a"` + B string `json:"b"` + } + B *struct { + A string `json:"a"` + B string `json:"b"` + } + }{A: nil, B: nil}, + }, + { + name: "PtrHeadStringNilDoubleMultiFieldsNotRoot", + expected: `null`, + data: (*struct { + A *struct { + A string `json:"a"` + B string `json:"b"` + } + B *struct { + A string `json:"a"` + B string `json:"b"` + } + })(nil), + }, + { + name: "PtrHeadStringPtrDoubleMultiFieldsNotRoot", + expected: `{"A":{"a":"foo","b":"bar"},"B":{"a":"foo","b":"bar"}}`, + data: &struct { + A *struct { + A *string `json:"a"` + B *string `json:"b"` + } + B *struct { + A *string `json:"a"` + B *string `json:"b"` + } + }{A: &(struct { + A *string `json:"a"` + B *string `json:"b"` + }{A: stringptr("foo"), B: stringptr("bar")}), B: &(struct { + A *string `json:"a"` + B *string `json:"b"` + }{A: stringptr("foo"), B: stringptr("bar")})}, + }, + { + name: "PtrHeadStringPtrNilDoubleMultiFieldsNotRoot", + expected: `{"A":null,"B":null}`, + data: &struct { + A *struct { + A *string `json:"a"` + B *string `json:"b"` + } + B *struct { + A *string `json:"a"` + B *string `json:"b"` + } + }{A: nil, B: nil}, + }, + { + name: "PtrHeadStringPtrNilDoubleMultiFieldsNotRoot", + expected: `null`, + data: (*struct { + A *struct { + A *string `json:"a"` + B *string `json:"b"` + } + B *struct { + A *string `json:"a"` + B *string `json:"b"` + } + })(nil), + }, + { + name: "AnonymousHeadString", + expected: `{"a":"foo","b":"bar"}`, + data: struct { + structString + B string `json:"b"` + }{ + structString: structString{A: "foo"}, + B: "bar", + }, + }, + { + name: "PtrAnonymousHeadString", + expected: `{"a":"foo","b":"bar"}`, + data: struct { + *structString + B string `json:"b"` + }{ + structString: &structString{A: "foo"}, + B: "bar", + }, + }, + { + name: "NilPtrAnonymousHeadString", + expected: `{"b":"baz"}`, + data: struct { + *structString + B string `json:"b"` + }{ + structString: nil, + B: "baz", + }, + }, + { + name: "AnonymousHeadStringPtr", + expected: `{"a":"foo","b":"bar"}`, + data: struct { + structStringPtr + B *string `json:"b"` + }{ + structStringPtr: structStringPtr{A: stringptr("foo")}, + B: stringptr("bar"), + }, + }, + { + name: "AnonymousHeadStringPtrNil", + expected: `{"a":null,"b":"foo"}`, + data: struct { + structStringPtr + B *string `json:"b"` + }{ + structStringPtr: structStringPtr{A: nil}, + B: stringptr("foo"), + }, + }, + { + name: "PtrAnonymousHeadStringPtr", + expected: `{"a":"foo","b":"bar"}`, + data: struct { + *structStringPtr + B *string `json:"b"` + }{ + structStringPtr: &structStringPtr{A: stringptr("foo")}, + B: stringptr("bar"), + }, + }, + { + name: "NilPtrAnonymousHeadStringPtr", + expected: `{"b":"foo"}`, + data: struct { + *structStringPtr + B *string `json:"b"` + }{ + structStringPtr: nil, + B: stringptr("foo"), + }, + }, + { + name: "AnonymousHeadStringOnly", + expected: `{"a":"foo"}`, + data: struct { + structString + }{ + structString: structString{A: "foo"}, + }, + }, + { + name: "PtrAnonymousHeadStringOnly", + expected: `{"a":"foo"}`, + data: struct { + *structString + }{ + structString: &structString{A: "foo"}, + }, + }, + { + name: "NilPtrAnonymousHeadStringOnly", + expected: `{}`, + data: struct { + *structString + }{ + structString: nil, + }, + }, + { + name: "AnonymousHeadStringPtrOnly", + expected: `{"a":"foo"}`, + data: struct { + structStringPtr + }{ + structStringPtr: structStringPtr{A: stringptr("foo")}, + }, + }, + { + name: "AnonymousHeadStringPtrNilOnly", + expected: `{"a":null}`, + data: struct { + structStringPtr + }{ + structStringPtr: structStringPtr{A: nil}, + }, + }, + { + name: "PtrAnonymousHeadStringPtrOnly", + expected: `{"a":"foo"}`, + data: struct { + *structStringPtr + }{ + structStringPtr: &structStringPtr{A: stringptr("foo")}, + }, + }, + { + name: "NilPtrAnonymousHeadStringPtrOnly", + expected: `{}`, + data: struct { + *structStringPtr + }{ + structStringPtr: nil, + }, + }, + } + for _, test := range tests { + for _, htmlEscape := range []bool{true, false} { + var buf bytes.Buffer + enc := NewEncoder(&buf) + enc.SetEscapeHTML(htmlEscape) + if err := enc.Encode(test.data); err != nil { + t.Fatalf("%s(htmlEscape:%T): %s: %s", test.name, htmlEscape, test.expected, err) + } + if strings.TrimRight(buf.String(), "\n") != test.expected { + t.Fatalf("%s(htmlEscape:%T): expected %q but got %q", test.name, htmlEscape, test.expected, buf.String()) + } + } + } +} diff --git a/encode_vm.go b/encode_vm.go index 682e853..d9eed79 100644 --- a/encode_vm.go +++ b/encode_vm.go @@ -4395,24 +4395,13 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte b = encodeComma(b) code = code.next case opStructFieldPtrHeadString: - p := load(ctxptr, code.idx) - if p == 0 { - b = encodeNull(b) - b = encodeComma(b) - code = code.end.next - break - } - store(ctxptr, code.idx, e.ptrToPtr(p)) + store(ctxptr, code.idx, e.ptrToPtr(load(ctxptr, code.idx))) fallthrough case opStructFieldHeadString: ptr := load(ctxptr, code.idx) if ptr == 0 { - if code.op == opStructFieldPtrHeadString { - b = encodeNull(b) - b = encodeComma(b) - } else { - b = append(b, '{', '}', ',') - } + b = encodeNull(b) + b = encodeComma(b) code = code.end.next } else { b = append(b, '{') @@ -4421,20 +4410,11 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte b = encodeComma(b) code = code.next } - case opStructFieldPtrHeadStringOnly: + case opStructFieldPtrHeadStringOnly, opStructFieldHeadStringOnly: p := load(ctxptr, code.idx) - if p == 0 { - b = encodeNull(b) - b = encodeComma(b) - code = code.end.next - break - } - fallthrough - case opStructFieldHeadStringOnly: - ptr := load(ctxptr, code.idx) b = append(b, '{') b = append(b, code.key...) - b = encodeEscapedString(b, e.ptrToString(ptr+code.offset)) + b = encodeNoEscapedString(b, e.ptrToString(p)) b = encodeComma(b) code = code.next case opStructEscapedFieldPtrHeadEscapedString: @@ -4450,12 +4430,8 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte case opStructEscapedFieldHeadEscapedString: ptr := load(ctxptr, code.idx) if ptr == 0 { - if code.op == opStructEscapedFieldPtrHeadEscapedString { - b = encodeNull(b) - b = encodeComma(b) - } else { - b = append(b, '{', '}', ',') - } + b = encodeNull(b) + b = encodeComma(b) code = code.end.next } else { b = append(b, '{') @@ -4464,7 +4440,36 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte b = encodeComma(b) code = code.next } - case opStructEscapedFieldPtrHeadEscapedStringOnly: + case opStructEscapedFieldPtrHeadEscapedStringOnly, opStructEscapedFieldHeadEscapedStringOnly: + p := load(ctxptr, code.idx) + b = append(b, '{') + b = append(b, code.escapedKey...) + b = encodeEscapedString(b, e.ptrToString(p+code.offset)) + b = encodeComma(b) + code = code.next + case opStructFieldPtrHeadStringPtr: + store(ctxptr, code.idx, e.ptrToPtr(load(ctxptr, code.idx))) + fallthrough + case opStructFieldHeadStringPtr: + p := load(ctxptr, code.idx) + if p == 0 { + b = encodeNull(b) + b = encodeComma(b) + code = code.end.next + break + } else { + b = append(b, '{') + b = append(b, code.key...) + p = e.ptrToPtr(p) + if p == 0 { + b = encodeNull(b) + } else { + b = encodeNoEscapedString(b, e.ptrToString(p+code.offset)) + } + } + b = encodeComma(b) + code = code.next + case opStructFieldPtrHeadStringPtrOnly: p := load(ctxptr, code.idx) if p == 0 { b = encodeNull(b) @@ -4472,12 +4477,60 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte code = code.end.next break } + store(ctxptr, code.idx, e.ptrToPtr(p)) fallthrough - case opStructEscapedFieldHeadEscapedStringOnly: - ptr := load(ctxptr, code.idx) + case opStructFieldHeadStringPtrOnly: + p := load(ctxptr, code.idx) + b = append(b, '{') + b = append(b, code.key...) + if p == 0 { + b = encodeNull(b) + } else { + b = encodeNoEscapedString(b, e.ptrToString(p+code.offset)) + } + b = encodeComma(b) + code = code.next + case opStructEscapedFieldPtrHeadEscapedStringPtr: + store(ctxptr, code.idx, e.ptrToPtr(load(ctxptr, code.idx))) + fallthrough + case opStructEscapedFieldHeadEscapedStringPtr: + p := load(ctxptr, code.idx) + if p == 0 { + b = encodeNull(b) + b = encodeComma(b) + code = code.end.next + break + } else { + b = append(b, '{') + b = append(b, code.escapedKey...) + p = e.ptrToPtr(p) + if p == 0 { + b = encodeNull(b) + } else { + b = encodeEscapedString(b, e.ptrToString(p+code.offset)) + } + } + b = encodeComma(b) + code = code.next + case opStructEscapedFieldPtrHeadEscapedStringPtrOnly: + p := load(ctxptr, code.idx) + if p == 0 { + b = encodeNull(b) + b = encodeComma(b) + code = code.end.next + break + } + store(ctxptr, code.idx, e.ptrToPtr(p)) + fallthrough + case opStructEscapedFieldHeadEscapedStringPtrOnly: + p := load(ctxptr, code.idx) b = append(b, '{') b = append(b, code.escapedKey...) - b = encodeEscapedString(b, e.ptrToString(ptr+code.offset)) + if p == 0 { + b = encodeNull(b) + } else { + b = encodeEscapedString(b, e.ptrToString(p+code.offset)) + } b = encodeComma(b) code = code.next case opStructFieldPtrAnonymousHeadString: @@ -4506,18 +4559,96 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, b []byte, code *opcode) ([]byte b = encodeComma(b) code = code.next } - - case opStructEscapedFieldPtrAnonymousHeadEscapedStringOnly: + case opStructFieldPtrAnonymousHeadStringOnly, opStructFieldAnonymousHeadStringOnly: + ptr := load(ctxptr, code.idx) + if ptr == 0 { + code = code.end.next + } else { + b = append(b, code.key...) + b = encodeNoEscapedString(b, e.ptrToString(ptr+code.offset)) + b = encodeComma(b) + code = code.next + } + case opStructEscapedFieldPtrAnonymousHeadEscapedStringOnly, opStructEscapedFieldAnonymousHeadEscapedStringOnly: + ptr := load(ctxptr, code.idx) + if ptr == 0 { + code = code.end.next + } else { + b = append(b, code.escapedKey...) + b = encodeEscapedString(b, e.ptrToString(ptr+code.offset)) + b = encodeComma(b) + code = code.next + } + case opStructFieldPtrAnonymousHeadStringPtr: + store(ctxptr, code.idx, e.ptrToPtr(load(ctxptr, code.idx))) + fallthrough + case opStructFieldAnonymousHeadStringPtr: p := load(ctxptr, code.idx) if p == 0 { code = code.end.next break } + b = append(b, code.key...) + p = e.ptrToPtr(p) + if p == 0 { + b = encodeNull(b) + } else { + b = encodeNoEscapedString(b, e.ptrToString(p+code.offset)) + } + b = encodeComma(b) + code = code.next + case opStructEscapedFieldPtrAnonymousHeadEscapedStringPtr: + store(ctxptr, code.idx, e.ptrToPtr(load(ctxptr, code.idx))) fallthrough - case opStructEscapedFieldAnonymousHeadEscapedStringOnly: + case opStructEscapedFieldAnonymousHeadEscapedStringPtr: + p := load(ctxptr, code.idx) + if p == 0 { + code = code.end.next + break + } + b = append(b, code.escapedKey...) + p = e.ptrToPtr(p) + if p == 0 { + b = encodeNull(b) + } else { + b = encodeEscapedString(b, e.ptrToString(p+code.offset)) + } + b = encodeComma(b) + code = code.next + case opStructFieldPtrAnonymousHeadStringPtrOnly: + p := load(ctxptr, code.idx) + if p == 0 { + code = code.end.next + break + } + store(ctxptr, code.idx, e.ptrToPtr(p)) + fallthrough + case opStructFieldAnonymousHeadStringPtrOnly: + p := load(ctxptr, code.idx) + b = append(b, code.key...) + if p == 0 { + b = encodeNull(b) + } else { + b = encodeNoEscapedString(b, e.ptrToString(p+code.offset)) + } + b = encodeComma(b) + code = code.next + case opStructEscapedFieldPtrAnonymousHeadEscapedStringPtrOnly: + p := load(ctxptr, code.idx) + if p == 0 { + code = code.end.next + break + } + store(ctxptr, code.idx, e.ptrToPtr(p)) + fallthrough + case opStructEscapedFieldAnonymousHeadEscapedStringPtrOnly: p := load(ctxptr, code.idx) b = append(b, code.escapedKey...) - b = encodeEscapedString(b, e.ptrToString(p+code.offset)) + if p == 0 { + b = encodeNull(b) + } else { + b = encodeEscapedString(b, e.ptrToString(p+code.offset)) + } b = encodeComma(b) code = code.next case opStructFieldPtrHeadBool: