gojay

high performance JSON encoder/decoder with stream API for Golang
git clone git://git.lair.cx/gojay
Log | Files | Refs | README | LICENSE

commit 76ee8ffba42b86b46b71bf32ac0d15ccd5f871f3
parent 0298226a77a70f5ad3ac2b0f45a6a9dbe2837597
Author: francoispqt <francois@parquet.ninja>
Date:   Mon, 21 May 2018 08:59:11 +0800

add fuzz testing and start fixing crashers found

Diffstat:
Mdecode_number.go | 10+++++++---
Mdecode_number_int.go | 55++++++++++++++++++++++++++++++++++++++++++++-----------
Mdecode_number_int_test.go | 42++++++++++++++++++++++++++++++++++++++++++
Aexamples/fuzz/main.go | 36++++++++++++++++++++++++++++++++++++
4 files changed, 129 insertions(+), 14 deletions(-)

diff --git a/decode_number.go b/decode_number.go @@ -85,7 +85,7 @@ func (dec *Decoder) getExponent() int64 { for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { switch dec.data[dec.cursor] { // is positive case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = dec.cursor + end = dec.cursor + 1 case '-': dec.cursor++ return -dec.getExponent() @@ -99,8 +99,12 @@ func (dec *Decoder) getExponent() int64 { dec.raiseInvalidJSONErr(dec.cursor) return 0 } - return dec.atoi64(start, end) + return dec.atoi64(start, end-1) } } - return dec.atoi64(start, end) + if start == end { + dec.raiseInvalidJSONErr(dec.cursor) + return 0 + } + return dec.atoi64(start, end-1) } diff --git a/decode_number_int.go b/decode_number_int.go @@ -21,7 +21,7 @@ func (dec *Decoder) decodeInt(v *int) error { continue // we don't look for 0 as leading zeros are invalid per RFC case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getInt64(c) + val, err := dec.getInt64() if err != nil { return err } @@ -29,7 +29,7 @@ func (dec *Decoder) decodeInt(v *int) error { return nil case '-': dec.cursor = dec.cursor + 1 - val, err := dec.getInt64(dec.data[dec.cursor]) + val, err := dec.getInt64() if err != nil { return err } @@ -151,6 +151,9 @@ func (dec *Decoder) getInt16(b byte) (int16, error) { floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) // we have the floating value, now multiply by the exponent exp := dec.getExponent() + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } val := floatVal * float64(pow10uint64[exp+1]) return int16(val), nil case ' ', '\t', '\n', ',', ']', '}': @@ -194,6 +197,9 @@ func (dec *Decoder) getInt16WithExp(init int16, cursor int) (int16, error) { uintv := uint16(digits[dec.data[cursor]]) exp = (exp << 3) + (exp << 1) + uintv case ' ', '\t', '\n', '}', ',', ']': + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } if sign == -1 { return init * (1 / int16(pow10uint64[exp+1])), nil } @@ -202,6 +208,9 @@ func (dec *Decoder) getInt16WithExp(init int16, cursor int) (int16, error) { return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) } } + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } if sign == -1 { return init * (1 / int16(pow10uint64[exp+1])), nil } @@ -303,6 +312,9 @@ func (dec *Decoder) getInt8(b byte) (int8, error) { floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) // we have the floating value, now multiply by the exponent exp := dec.getExponent() + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } val := floatVal * float64(pow10uint64[exp+1]) return int8(val), nil case ' ', '\t', '\n', ',', ']', '}': @@ -310,7 +322,7 @@ func (dec *Decoder) getInt8(b byte) (int8, error) { return dec.atoi8(start, end), nil default: dec.cursor = j - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return 0, dec.raiseInvalidJSONErr(dec.cursor) } } return dec.atoi8(start, end), nil @@ -346,14 +358,20 @@ func (dec *Decoder) getInt8WithExp(init int8, cursor int) (int8, error) { uintv := uint8(digits[dec.data[cursor]]) exp = (exp << 3) + (exp << 1) + uintv case ' ', '\t', '\n', '}', ',', ']': + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } if sign == -1 { return init * (1 / int8(pow10uint64[exp+1])), nil } return init * int8(pow10uint64[exp+1]), nil default: - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return 0, dec.raiseInvalidJSONErr(dec.cursor) } } + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } if sign == -1 { return init * (1 / int8(pow10uint64[exp+1])), nil } @@ -461,7 +479,7 @@ func (dec *Decoder) getInt32(b byte) (int32, error) { return dec.atoi32(start, end), nil default: dec.cursor = j - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return 0, dec.raiseInvalidJSONErr(dec.cursor) } } return dec.atoi32(start, end), nil @@ -497,14 +515,20 @@ func (dec *Decoder) getInt32WithExp(init int32, cursor int) (int32, error) { uintv := uint32(digits[dec.data[cursor]]) exp = (exp << 3) + (exp << 1) + uintv case ' ', '\t', '\n', '}', ',', ']': + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } if sign == -1 { return init * (1 / int32(pow10uint64[exp+1])), nil } return init * int32(pow10uint64[exp+1]), nil default: - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return 0, dec.raiseInvalidJSONErr(dec.cursor) } } + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } if sign == -1 { return init * (1 / int32(pow10uint64[exp+1])), nil } @@ -533,7 +557,7 @@ func (dec *Decoder) decodeInt64(v *int64) error { case ' ', '\n', '\t', '\r', ',': continue case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getInt64(c) + val, err := dec.getInt64() if err != nil { return err } @@ -541,7 +565,7 @@ func (dec *Decoder) decodeInt64(v *int64) error { return nil case '-': dec.cursor = dec.cursor + 1 - val, err := dec.getInt64(dec.data[dec.cursor]) + val, err := dec.getInt64() if err != nil { return err } @@ -566,7 +590,7 @@ func (dec *Decoder) decodeInt64(v *int64) error { return dec.raiseInvalidJSONErr(dec.cursor) } -func (dec *Decoder) getInt64(b byte) (int64, error) { +func (dec *Decoder) getInt64() (int64, error) { var end = dec.cursor var start = dec.cursor // look for following numbers @@ -609,6 +633,9 @@ func (dec *Decoder) getInt64(b byte) (int64, error) { floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) // we have the floating value, now multiply by the exponent exp := dec.getExponent() + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } val := floatVal * float64(pow10uint64[exp+1]) return int64(val), nil case ' ', '\t', '\n', ',', ']', '}': @@ -616,7 +643,7 @@ func (dec *Decoder) getInt64(b byte) (int64, error) { return dec.atoi64(start, end), nil default: dec.cursor = j - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return 0, dec.raiseInvalidJSONErr(dec.cursor) } } return dec.atoi64(start, end), nil @@ -649,14 +676,20 @@ func (dec *Decoder) getInt64WithExp(init int64, cursor int) (int64, error) { uintv := uint64(digits[dec.data[cursor]]) exp = (exp << 3) + (exp << 1) + uintv case ' ', '\t', '\n', '}', ',', ']': + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } if sign == -1 { return init * (1 / int64(pow10uint64[exp+1])), nil } return init * int64(pow10uint64[exp+1]), nil default: - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + return 0, dec.raiseInvalidJSONErr(dec.cursor) } } + if int(exp+1) >= len(pow10uint64) { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } if sign == -1 { return init * (1 / int64(pow10uint64[exp+1])), nil } diff --git a/decode_number_int_test.go b/decode_number_int_test.go @@ -189,6 +189,20 @@ func TestDecoderInt(t *testing.T) { errType: InvalidJSONError(""), }, { + name: "error3", + json: "0.E----", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error3", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "invalid-type", json: `"string"`, expectedResult: 0, @@ -383,6 +397,13 @@ func TestDecoderInt64(t *testing.T) { expectedResult: 0, }, { + name: "error3", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "basic-exponent-positive-negative-exp4", json: "8e-005", expectedResult: 0, @@ -661,6 +682,13 @@ func TestDecoderInt32(t *testing.T) { err: true, }, { + name: "error3", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "basic-float", json: "8.32 ", expectedResult: 8, @@ -947,6 +975,13 @@ func TestDecoderInt16(t *testing.T) { err: true, }, { + name: "error3", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "invalid-type", json: `"string"`, expectedResult: 0, @@ -1172,6 +1207,13 @@ func TestDecoderInt8(t *testing.T) { expectedResult: -30, }, { + name: "error3", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "basic-exponent-negative-positive-exp4", json: "-8e+001", expectedResult: -80, diff --git a/examples/fuzz/main.go b/examples/fuzz/main.go @@ -0,0 +1,36 @@ +package fuzz + +import ( + "github.com/francoispqt/gojay" +) + +type user struct { + id int + name string + email string +} + +// implement gojay.UnmarshalerJSONObject +func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { + switch key { + case "id": + return dec.Int(&u.id) + case "name": + return dec.String(&u.name) + case "email": + return dec.String(&u.email) + } + return nil +} +func (u *user) NKeys() int { + return 3 +} + +func Fuzz(input []byte) int { + u := &user{} + err := gojay.UnmarshalJSONObject(input, u) + if err != nil { + return 0 + } + return 1 +}