Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: float decoding, improve coverage #25

Merged
merged 6 commits into from Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 10 additions & 6 deletions bench_test.go
Expand Up @@ -27,13 +27,17 @@ func runTestdata(fatal func(...interface{}), cb func(name string, data []byte))
if e.IsDir() {
continue
}
name := path.Join("testdata", e.Name())
data, err := testdata.ReadFile(name)
if err != nil {
fatal(err)
}
cb(e.Name(), data)
runTestdataFile(e.Name(), fatal, cb)
}
}

func runTestdataFile(file string, fatal func(...interface{}), cb func(name string, data []byte)) {
name := path.Join("testdata", file)
data, err := testdata.ReadFile(name)
if err != nil {
fatal(err)
}
cb(file, data)
}

func BenchmarkFile_Decode(b *testing.B) {
Expand Down
143 changes: 0 additions & 143 deletions dec.go
Expand Up @@ -2,8 +2,6 @@ package jx

import (
"io"

"github.com/go-faster/errors"
)

// Type of json value.
Expand Down Expand Up @@ -157,144 +155,3 @@ func (d *Decoder) ResetBytes(input []byte) {

d.buf = input
}

// Next gets Type of relatively next json element
func (d *Decoder) Next() Type {
v, _ := d.next()
d.unread()
return types[v]
}

var spaceSet = [256]byte{
' ': 1, '\n': 1, '\t': 1, '\r': 1,
}

func (d *Decoder) consume(c byte) (err error) {
for {
buf := d.buf[d.head:d.tail]
for i, got := range buf {
switch spaceSet[got] {
default:
d.head += i + 1
if c != got {
return badToken(got)
}
return nil
case 1:
continue
}
}
if err = d.read(); err != nil {
if err == io.EOF {
return io.ErrUnexpectedEOF
}
return err
}
}
}

// more is next but io.EOF is unexpected.
func (d *Decoder) more() (byte, error) {
c, err := d.next()
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return c, err
}

// next reads next non-whitespace token or error.
func (d *Decoder) next() (byte, error) {
for {
buf := d.buf[d.head:d.tail]
for i, c := range buf {
switch spaceSet[c] {
default:
d.head += i + 1
return c, nil
case 1:
continue
}
}
if err := d.read(); err != nil {
return 0, err
}
}
}

func (d *Decoder) byte() (byte, error) {
if d.head == d.tail {
err := d.read()
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
if err != nil {
return 0, err
}
}
c := d.buf[d.head]
d.head++
return c, nil
}

func (d *Decoder) read() error {
if d.reader == nil {
d.head = d.tail
return io.EOF
}

n, err := d.reader.Read(d.buf)
if err != nil {
return err
}

d.head = 0
d.tail = n
return nil
}

func (d *Decoder) readAtLeast(min int) error {
if d.reader == nil {
d.head = d.tail
return io.ErrUnexpectedEOF
}

if need := min - len(d.buf); need > 0 {
d.buf = append(d.buf, make([]byte, need)...)
}
n, err := io.ReadAtLeast(d.reader, d.buf, min)
if err != nil {
if err == io.EOF && n == 0 {
return io.ErrUnexpectedEOF
}
return err
}

d.head = 0
d.tail = n
return nil
}

func (d *Decoder) unread() { d.head-- }

// limit maximum depth of nesting, as allowed by https://tools.ietf.org/html/rfc7159#section-9
const maxDepth = 10000

var errMaxDepth = errors.New("depth: maximum")

func (d *Decoder) incDepth() error {
d.depth++
if d.depth > maxDepth {
return errMaxDepth
}
return nil
}

var errNegativeDepth = errors.New("depth: negative")

func (d *Decoder) decDepth() error {
d.depth--
if d.depth < 0 {
return errNegativeDepth
}
return nil
}
34 changes: 34 additions & 0 deletions dec_bool.go
@@ -0,0 +1,34 @@
package jx

// Bool reads a json object as Bool
func (d *Decoder) Bool() (bool, error) {
var buf [4]byte
if err := d.readExact4(&buf); err != nil {
return false, err
}

switch string(buf[:]) {
case "true":
return true, nil
case "fals":
c, err := d.byte()
if err != nil {
return false, err
}
if c != 'e' {
return false, badToken(c)
}
return false, nil
default:
switch c := buf[0]; c {
case 't':
const encodedTrue = 't' | 'r'<<8 | 'u'<<16 | 'e'<<24
return false, findInvalidToken4(buf, encodedTrue)
case 'f':
const encodedAlse = 'a' | 'l'<<8 | 's'<<16 | 'e'<<24
return false, findInvalidToken4(buf, encodedAlse)
default:
return false, badToken(c)
}
}
}
10 changes: 10 additions & 0 deletions dec_bool_test.go
@@ -0,0 +1,10 @@
package jx

import "testing"

func TestDecoder_Bool(t *testing.T) {
runTestCases(t, testBools, func(t *testing.T, d *Decoder) error {
_, err := d.Bool()
return err
})
}
26 changes: 26 additions & 0 deletions dec_depth.go
@@ -0,0 +1,26 @@
package jx

import "github.com/go-faster/errors"

// limit maximum depth of nesting, as allowed by https://tools.ietf.org/html/rfc7159#section-9
const maxDepth = 10000

var errMaxDepth = errors.New("depth: maximum")

func (d *Decoder) incDepth() error {
d.depth++
if d.depth > maxDepth {
return errMaxDepth
}
return nil
}

var errNegativeDepth = errors.New("depth: negative")

func (d *Decoder) decDepth() error {
d.depth--
if d.depth < 0 {
return errNegativeDepth
}
return nil
}
19 changes: 14 additions & 5 deletions dec_float.go
Expand Up @@ -16,6 +16,7 @@ var floatDigits []int8
const invalidCharForNumber = int8(-1)
const endOfNumber = int8(-2)
const dotInNumber = int8(-3)
const maxFloat64 = 1<<63 - 1

func init() {
floatDigits = make([]int8, 256)
Expand Down Expand Up @@ -213,15 +214,19 @@ func (d *Decoder) Float64() (float64, error) {
if err != nil {
return 0, errors.Wrap(err, "byte")
}
if c == '-' {
switch c {
case '-':
v, err := d.positiveFloat64()
if err != nil {
return 0, err
}
return -v, err
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
d.unread()
return d.positiveFloat64()
default:
return 0, badToken(c)
}
d.unread()
return d.positiveFloat64()
}

func (d *Decoder) positiveFloat64() (float64, error) {
Expand Down Expand Up @@ -333,8 +338,12 @@ func validateFloat(str []byte) error {
if str[0] == '-' {
return errors.New("double minus")
}
if len(str) >= 2 && str[0] == '0' && str[1] == '0' {
return errors.New("leading zero")
if len(str) >= 2 && str[0] == '0' {
switch str[1] {
case 'e', 'E', '.':
default:
return errors.New("leading zero")
}
}
dotPos := bytes.IndexByte(str, '.')
if dotPos != -1 {
Expand Down
60 changes: 35 additions & 25 deletions dec_float_test.go
Expand Up @@ -2,6 +2,7 @@ package jx

import (
"bytes"
"io"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -40,31 +41,18 @@ func decodeStr(t *testing.T, s string, f func(d *Decoder)) {

func TestDecoder_Float(t *testing.T) {
t.Run("Invalid", func(t *testing.T) {
for _, s := range []string{
``,
`-`,
`-.`,
`.`,
`.-`,
`00`,
`.00`,
`00.1`,
} {
t.Run(s, func(t *testing.T) {
t.Run("64", func(t *testing.T) {
decodeStr(t, s, func(d *Decoder) {
_, err := d.Float64()
require.Error(t, err, s)
})
})
t.Run("32", func(t *testing.T) {
decodeStr(t, s, func(d *Decoder) {
_, err := d.Float32()
require.Error(t, err, s)
})
})
})
}
runTestCases(t, testNumbers, func(t *testing.T, d *Decoder) error {
_, err := d.Float64()
if err != nil {
return err
}
if err := d.Skip(); err != nil {
if err != io.EOF && err != io.ErrUnexpectedEOF {
return err
}
}
return nil
})
})
t.Run("Slow", func(t *testing.T) {
s := `,0.1`
Expand Down Expand Up @@ -143,3 +131,25 @@ func TestDecoder_Float64(t *testing.T) {
})
}
}

func BenchmarkDecoder_Float64(b *testing.B) {
runTestdataFile("floats.json", b.Fatal, func(name string, data []byte) {
b.Run(name, func(b *testing.B) {
d := GetDecoder()
cb := func(d *Decoder) error {
_, err := d.Float64()
return err
}
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
d.ResetBytes(data)

if err := d.Arr(cb); err != nil {
b.Fatal(err)
}
}
})
})
}
1 change: 0 additions & 1 deletion dec_int.go
Expand Up @@ -12,7 +12,6 @@ var intDigits []int8

const uint32SafeToMultiply10 = uint32(0xffffffff)/10 - 1
const uint64SafeToMultiple10 = uint64(0xffffffffffffffff)/10 - 1
const maxFloat64 = 1<<53 - 1

func init() {
intDigits = make([]int8, 256)
Expand Down
16 changes: 16 additions & 0 deletions dec_null.go
@@ -0,0 +1,16 @@
package jx

// Null reads a json object as null and
// returns whether it's a null or not.
func (d *Decoder) Null() error {
var buf [4]byte
if err := d.readExact4(&buf); err != nil {
return err
}

if string(buf[:]) != "null" {
const encodedNull = 'n' | 'u'<<8 | 'l'<<16 | 'l'<<24
return findInvalidToken4(buf, encodedNull)
}
return nil
}