Skip to content

Commit

Permalink
opt: optimize decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
liuq19 committed May 31, 2024
1 parent 381aa24 commit 461732e
Show file tree
Hide file tree
Showing 256 changed files with 176,943 additions and 110,645 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/compatibility_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
build:
strategy:
matrix:
go-version: [1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x]
go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x]
os: [arm, X64]
runs-on: ${{ matrix.os }}
steps:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ jobs:
strategy:
matrix:
go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x]
runs-on: [self-hosted, X64]
os: [X64, arm]
runs-on: ${{ matrix.os }}
steps:
- name: Clear repository
run: sudo rm -fr $GITHUB_WORKSPACE && mkdir $GITHUB_WORKSPACE
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ ast/bench.sh

!testdata/*.json.gz
fuzz/testdata
*__debug_bin
*__debug_bin*
*pprof
*coverage.txt
3 changes: 2 additions & 1 deletion decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,8 @@ var unmarshalTests = []unmarshalTest{
{in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: json.Number("1"), F2: int32(2), F3: json.Number("3")}, useNumber: true},
{in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64},
{in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsNumber, useNumber: true},
{in: `{"":""}`, ptr: new(struct{}), out: struct{}{}},
{in: `{"x":""}`, ptr: new(struct{ X json.Number }), err: errors.New("empty string into json number")},

// raw values with whitespace
{in: "\n true ", ptr: new(bool), out: true},
Expand Down Expand Up @@ -1116,7 +1118,6 @@ func TestMarshalEmbeds(t *testing.T) {

func TestUnmarshal(t *testing.T) {
for i, tt := range unmarshalTests {
t.Log(i, tt.in)
if !json.Valid([]byte(tt.in)) {
continue
}
Expand Down
4 changes: 2 additions & 2 deletions decoder/decoder_compat.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build !amd64 !go1.16 go1.23
// +build !amd64,!arm64 go1.23 !go1.16 arm64,!go1.20

/*
* Copyright 2023 ByteDance Inc.
Expand Down Expand Up @@ -30,7 +30,7 @@ import (
)

func init() {
println("WARNING: sonic only supports Go1.16~1.22 && CPU amd64, but your environment is not suitable")
println("WARNING: sonic/decoder only supports (Go1.16~1.22 && CPU amd64) or (go1.20~1.22 && CPU arm64), but your environment is not suitable")
}

const (
Expand Down
36 changes: 19 additions & 17 deletions decoder/decoder_amd64.go → decoder/decoder_native.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// +build amd64,go1.16,!go1.23
//go:build (amd64 && go1.16 && !go1.23) || (arm64 && go1.20 && !go1.23)
// +build amd64,go1.16,!go1.23 arm64,go1.20,!go1.23


/*
* Copyright 2023 ByteDance Inc.
Expand All @@ -19,50 +21,50 @@
package decoder

import (
`github.com/bytedance/sonic/internal/decoder`
`github.com/bytedance/sonic/internal/decoder/api`
)

// Decoder is the decoder context object
type Decoder = decoder.Decoder
type Decoder = api.Decoder

// SyntaxError represents json syntax error
type SyntaxError = decoder.SyntaxError
type SyntaxError = api.SyntaxError

// MismatchTypeError represents dismatching between json and object
type MismatchTypeError = decoder.MismatchTypeError
type MismatchTypeError = api.MismatchTypeError

// Options for decode.
type Options = decoder.Options
type Options = api.Options

const (
OptionUseInt64 Options = decoder.OptionUseInt64
OptionUseNumber Options = decoder.OptionUseNumber
OptionUseUnicodeErrors Options = decoder.OptionUseUnicodeErrors
OptionDisableUnknown Options = decoder.OptionDisableUnknown
OptionCopyString Options = decoder.OptionCopyString
OptionValidateString Options = decoder.OptionValidateString
OptionUseInt64 Options = api.OptionUseInt64
OptionUseNumber Options = api.OptionUseNumber
OptionUseUnicodeErrors Options = api.OptionUseUnicodeErrors
OptionDisableUnknown Options = api.OptionDisableUnknown
OptionCopyString Options = api.OptionCopyString
OptionValidateString Options = api.OptionValidateString
)

// StreamDecoder is the decoder context object for streaming input.
type StreamDecoder = decoder.StreamDecoder
type StreamDecoder = api.StreamDecoder

var (
// NewDecoder creates a new decoder instance.
NewDecoder = decoder.NewDecoder
NewDecoder = api.NewDecoder

// NewStreamDecoder adapts to encoding/json.NewDecoder API.
//
// NewStreamDecoder returns a new decoder that reads from r.
NewStreamDecoder = decoder.NewStreamDecoder
NewStreamDecoder = api.NewStreamDecoder

// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
// order to reduce the first-hit latency.
//
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
// a compile option to set the depth of recursive compile for the nested struct type.
Pretouch = decoder.Pretouch
Pretouch = api.Pretouch

// Skip skips only one json value, and returns first non-blank character position and its ending position if it is valid.
// Otherwise, returns negative error code using start and invalid character position using end
Skip = decoder.Skip
Skip = api.Skip
)
82 changes: 42 additions & 40 deletions decoder/decoder_amd64_test.go → decoder/decoder_native_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// +build amd64,go1.16,!go1.23
//go:build (amd64 && go1.16 && !go1.23) || (arm64 && go1.20 && !go1.23)
// +build amd64,go1.16,!go1.23 arm64,go1.20,!go1.23


/*
* Copyright 2021 ByteDance Inc.
Expand All @@ -20,61 +22,61 @@ package decoder

import (
`encoding/json`
`strings`
_`strings`
`testing`
`reflect`
_`reflect`

`github.com/bytedance/sonic/internal/rt`
`github.com/stretchr/testify/assert`
)

func TestSkipMismatchTypeAmd64Error(t *testing.T) {
t.Run("struct", func(t *testing.T) {
println("TestSkipError")
type skiptype struct {
A int `json:"a"`
B string `json:"b"`
// t.Run("struct", func(t *testing.T) {
// println("TestSkipError")
// type skiptype struct {
// A int `json:"a"`
// B string `json:"b"`

Pass *int `json:"pass"`
// Pass *int `json:"pass"`

C struct{
// C struct{

Pass4 interface{} `json:"pass4"`
// Pass4 interface{} `json:"pass4"`

D struct{
E float32 `json:"e"`
} `json:"d"`
// D struct{
// E float32 `json:"e"`
// } `json:"d"`

Pass2 int `json:"pass2"`
// Pass2 int `json:"pass2"`

} `json:"c"`
// } `json:"c"`

E bool `json:"e"`
F []int `json:"f"`
G map[string]int `json:"g"`
H bool `json:"h,string"`
// E bool `json:"e"`
// F []int `json:"f"`
// G map[string]int `json:"g"`
// H bool `json:"h,string"`

Pass3 int `json:"pass2"`
// Pass3 int `json:"pass2"`

I json.Number `json:"i"`
}
var obj, obj2 = &skiptype{Pass:new(int)}, &skiptype{Pass:new(int)}
var data = `{"a":"","b":1,"c":{"d":true,"pass2":1,"pass4":true},"e":{},"f":"","g":[],"pass":null,"h":"1.0","i":true,"pass3":1}`
d := NewDecoder(data)
err := d.Decode(obj)
err2 := json.Unmarshal([]byte(data), obj2)
println(err2.Error())
assert.Equal(t, err2 == nil, err == nil)
// assert.Equal(t, len(data), d.i)
assert.Equal(t, obj2, obj)
if te, ok := err.(*MismatchTypeError); ok {
assert.Equal(t, reflect.TypeOf(obj.I), te.Type)
assert.Equal(t, strings.Index(data, `"i":t`)+4, te.Pos)
println(err.Error())
} else {
t.Fatal("invalid error")
}
})
// I json.Number `json:"i"`
// }
// var obj, obj2 = &skiptype{Pass:new(int)}, &skiptype{Pass:new(int)}
// var data = `{"a":"","b":1,"c":{"d":true,"pass2":1,"pass4":true},"e":{},"f":"","g":[],"pass":null,"h":"1.0","i":true,"pass3":1}`
// d := NewDecoder(data)
// err := d.Decode(obj)
// err2 := json.Unmarshal([]byte(data), obj2)
// println(err2.Error())
// assert.Equal(t, err2 == nil, err == nil)
// // assert.Equal(t, len(data), d.i)
// assert.Equal(t, obj2, obj)
// if te, ok := err.(*MismatchTypeError); ok {
// assert.Equal(t, reflect.TypeOf(obj.I), te.Type)
// assert.Equal(t, strings.Index(data, `"i":t`)+4, te.Pos)
// println(err.Error())
// } else {
// t.Fatal("invalid error")
// }
// })
t.Run("short array", func(t *testing.T) {
var obj, obj2 = &[]int{}, &[]int{}
var data = `[""]`
Expand Down
2 changes: 1 addition & 1 deletion decoder/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func TestDecodeCorrupt(t *testing.T) {
if err == nil {
t.Fatalf("%#v", d)
}
if !strings.Contains(err.Error(), "invalid char"){
if !strings.Contains(err.Error(), "Syntax error"){
t.Fatal(err.Error())
}
}
Expand Down
49 changes: 45 additions & 4 deletions fuzz/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,50 @@ func FuzzMain(f *testing.F) {
f.Fuzz(fuzzMain)
}

type testFuzzCase struct {
data []byte
newf func () interface{}
}

func testJson(t *testing.T, data []byte, newf func() interface{}) {
jv := newf()
jerr := json.Unmarshal(data, jv)
sv := newf()
serr := sonic.Unmarshal(data, sv)
require.Equal(t, jerr == nil, serr == nil)
spew.Dump(jv, sv)
require.Equal(t, jv, sv)
}

var testFuzzCases = []testFuzzCase{
{
data: []byte(`{"x":"","":"","$$$$$ſ":"","RRRRRſ":"","ppppſ":"","ŝ":"","Ţ":"","ţ":"","Ť":"","Ũ":"","Ŭ":"","Ű":"","ų":"","Ŷ":"","Ÿ":"","ź":"","Ż":"","ſ":"","ſſ":"","ǿ":"","ɿ":"","տ":"","ٿſ":"","ڵ":""}`),
newf: func() interface{} {
return new(struct { F0 ***string; F1 string "json:\"ڵ,omitempty\""; F2 *string; F3 string; p4 string; F4 **string; F5 string; F6 *string "json:\"-\""; F7 ***string; F8 string; p9 string; F9 string; p10 string; F10 **string "json:\"Ŷ,\""; F11 **string "json:\"Ż,omitempty\""; F12 **string "json:\"ſ,\""; F13 ***string; F14 *string; p15 *string; F15 string "json:\"-\""; p16 string; F16 **string "json:\"ſſ,omitempty\""; F17 **string "json:\"ɿ,omitempty\""; p18 **string; F18 *string "json:\"-\""; F19 **string "json:\"RRRRRſ,omitempty\""; F20 ***string; p21 ***string; F21 string "json:\"ź,omitempty\""; p22 string })
},
},
{
data: []byte(`{"":"","$$$$$ſ":"","RRRRRſ":"","ppppſ":"","ŝ":"","Ţ":"","ţ":"","Ť":"","Ũ":"","Ŭ":"","Ű":"","ų":"","Ŷ":"","Ÿ":"","ź":"","Ż":"","ſ":"","ſſ":"","ǿ":"","ɿ":"","տ":"","ٿſ":"","ڵ":""}`),
newf: func() interface{} {
return new(struct { F6 **string "json:\"x,\""; F7 string; p8 string; F8 *string; F9 string "json:\"ٿſ,omitempty\""; p10 string; F10 **string "json:\"-\""; F11 string "json:\"$$$$$ſ,\""; p12 string; F12 *string; p13 *string; F13 *string "json:\"Ű,\""; p14 *string; F14 **string; F15 string "json:\"Ż,omitempty\""; F16 string "json:\"-\""; p17 string; F17 **string "json:\"-\""; F18 string "json:\"ppppſ,\""; F19 ***string "json:\"Ţ,omitempty\""; p20 ***string; F20 ***string; F21 *string })
},
},
{
// FIXME: encoding/json has bugs because the limited dbuf capcaity is 800?
data: []byte("[53333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333353333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333e-913]"),
newf: func() interface{} { return new([]interface{}) },
},
{
data: []byte("10000000000000000000"),
newf: func () interface{} { return new(uint64) },
},
}

// Used for debug falied fuzz corpus
func TestCorpus(t *testing.T) {
func TestFuzzCases(t *testing.T) {
for _, c := range testFuzzCases {
testJson(t, c.data, c.newf)
}
fuzzMain(t, []byte("[1\x00"))
fuzzMain(t, []byte("\"\\uDE1D\\uDE1D\\uDEDD\\uDE1D\\uDE1D\\uDE1D\\uDE1D\\uDEDD\\uDE1D\""))
// fuzzMain(t, []byte(`{"":null}`))
Expand Down Expand Up @@ -82,7 +124,7 @@ func fuzzMain(t *testing.T, data []byte) {
if jerr != nil {
continue
}
require.Equal(t, sv, jv, dump(data, jv, jerr, sv, serr))
require.Equal(t, sv, jv, dump(string(data), jv, jerr, sv, serr))

v := jv
sout, serr := target.Marshal(v)
Expand Down Expand Up @@ -116,14 +158,13 @@ func fuzzMain(t *testing.T, data []byte) {
}

if m, ok := sv.(*map[string]interface{}); ok {
fuzzDynamicStruct(t, jout, *m)
fuzzASTGetFromObject(t, jout, *m)
fuzzDynamicStruct(t, jout, *m)
}
if a, ok := sv.(*[]interface{}); ok {
fuzzASTGetFromArray(t, jout, *a)
}
}

}


Expand Down
Loading

0 comments on commit 461732e

Please sign in to comment.