From fd171f3a13df8e666d2ba7c2b39509209d7ba610 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Fri, 20 Jan 2023 06:26:46 +0300 Subject: [PATCH 1/5] fix: handle end of the number correctly Also: re-use `floatDigits` set. --- dec_float.go | 235 ++++++++------------- dec_float_big.go | 80 ++++++++ dec_int.gen.go | 444 ++++++++++++++++++++++++++++++++-------- num.go | 28 ++- num_test.go | 2 +- tools/mkint/decode.tmpl | 62 +++--- 6 files changed, 582 insertions(+), 269 deletions(-) create mode 100644 dec_float_big.go diff --git a/dec_float.go b/dec_float.go index 9736ea4..7dfe680 100644 --- a/dec_float.go +++ b/dec_float.go @@ -2,8 +2,6 @@ package jx import ( "bytes" - "io" - "math/big" "strconv" "github.com/go-faster/errors" @@ -15,57 +13,35 @@ var ( ) const ( - invalidCharForNumber = int8(-1) - endOfNumber = int8(-2) - dotInNumber = int8(-3) - maxFloat64 = 1<<63 - 1 + dotInNumber int8 = -iota - 1 + expInNumber + plusInNumber + minusInNumber + endOfNumber + invalidCharForNumber + + maxFloat64 = 1<<63 - 1 ) func init() { for i := 0; i < len(floatDigits); i++ { floatDigits[i] = invalidCharForNumber } - for i := int8('0'); i <= int8('9'); i++ { - floatDigits[i] = i - int8('0') - } floatDigits[','] = endOfNumber floatDigits[']'] = endOfNumber floatDigits['}'] = endOfNumber floatDigits[' '] = endOfNumber floatDigits['\t'] = endOfNumber floatDigits['\n'] = endOfNumber - floatDigits['.'] = dotInNumber -} -// BigFloat read big.Float -func (d *Decoder) BigFloat() (*big.Float, error) { - str, err := d.numberAppend(nil) - if err != nil { - return nil, errors.Wrap(err, "number") - } - prec := 64 - if len(str) > prec { - prec = len(str) - } - val, _, err := big.ParseFloat(string(str), 10, uint(prec), big.ToZero) - if err != nil { - return nil, errors.Wrap(err, "float") - } - return val, nil -} - -// BigInt read big.Int -func (d *Decoder) BigInt() (*big.Int, error) { - str, err := d.numberAppend(nil) - if err != nil { - return nil, errors.Wrap(err, "number") - } - v := big.NewInt(0) - var ok bool - if v, ok = v.SetString(string(str), 10); !ok { - return nil, errors.New("invalid") + for i := int8('0'); i <= int8('9'); i++ { + floatDigits[i] = i - int8('0') } - return v, nil + floatDigits['.'] = dotInNumber + floatDigits['e'] = expInNumber + floatDigits['E'] = expInNumber + floatDigits['+'] = plusInNumber + floatDigits['-'] = minusInNumber } // Float32 reads float32 value. @@ -97,19 +73,22 @@ func (d *Decoder) positiveFloat32() (float32, error) { i++ ind := floatDigits[c] switch ind { - case invalidCharForNumber: - return d.float32Slow() - case endOfNumber: - return 0, errors.New("empty") - case dotInNumber: - return 0, errors.New("leading dot") + case invalidCharForNumber, endOfNumber: + return 0, badToken(c, d.offset()) + case dotInNumber, plusInNumber, expInNumber: + err := badToken(c, d.offset()) + return 0, errors.Wrapf(err, "leading %q", c) + case minusInNumber: // minus handled by caller + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "double minus") case 0: if i == d.tail { return d.float32Slow() } c = d.buf[i] if floatDigits[c] >= 0 { - return 0, errors.New("leading zero") + err := badToken(c, d.offset()+1) + return 0, errors.Wrap(err, "leading zero") } } value := uint64(ind) @@ -120,11 +99,11 @@ NonDecimalLoop: ind := floatDigits[c] switch ind { case invalidCharForNumber: - return d.float32Slow() + return 0, badToken(c, d.offset()+i) case endOfNumber: d.head = i return float32(value), nil - case dotInNumber: + case dotInNumber, expInNumber: break NonDecimalLoop } if value > uint64SafeToMultiple10 { @@ -150,8 +129,10 @@ NonDecimalLoop: } // too many decimal places return d.float32Slow() - case invalidCharForNumber, dotInNumber: + case dotInNumber, expInNumber, plusInNumber, minusInNumber: return d.float32Slow() + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) } decimalPlaces++ if value > uint64SafeToMultiple10 { @@ -163,67 +144,6 @@ NonDecimalLoop: return d.float32Slow() } -var numberSet = [256]byte{ - '+': 1, - '-': 1, - '.': 1, - 'e': 1, - 'E': 1, - '0': 1, - '1': 1, - '2': 1, - '3': 1, - '4': 1, - '5': 1, - '6': 1, - '7': 1, - '8': 1, - '9': 1, -} - -func (d *Decoder) number() []byte { - start := d.head - buf := d.buf[d.head:d.tail] - for i, c := range buf { - if numberSet[c] == 0 { - // End of number. - d.head += i - return d.buf[start:d.head] - } - } - // Buffer is number within head:tail. - d.head = d.tail - return d.buf[start:d.tail] -} - -func (d *Decoder) numberAppend(b []byte) ([]byte, error) { - for { - b = append(b, d.number()...) - if d.head != d.tail { - return b, nil - } - if err := d.read(); err != nil { - if err == io.EOF { - return b, nil - } - return b, err - } - } -} - -const ( - size32 = 32 - size64 = 64 -) - -func (d *Decoder) float32Slow() (float32, error) { - v, err := d.floatSlow(size32) - if err != nil { - return 0, err - } - return float32(v), err -} - // Float64 read float64 func (d *Decoder) Float64() (float64, error) { c, err := d.more() @@ -256,20 +176,22 @@ func (d *Decoder) positiveFloat64() (float64, error) { i++ ind := floatDigits[c] switch ind { - case invalidCharForNumber: - return d.float64Slow() - case endOfNumber: - return 0, errors.New("empty") - case dotInNumber: - return 0, errors.New("leading dot") + case invalidCharForNumber, endOfNumber: + return 0, badToken(c, d.offset()) + case dotInNumber, plusInNumber, expInNumber: + err := badToken(c, d.offset()) + return 0, errors.Wrapf(err, "leading %q", c) + case minusInNumber: // minus handled by caller + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "double minus") case 0: if i == d.tail { return d.float64Slow() } c = d.buf[i] - switch c { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - return 0, errors.New("leading zero") + if floatDigits[c] >= 0 { + err := badToken(c, d.offset()+1) + return 0, errors.Wrap(err, "leading zero") } } value := uint64(ind) @@ -280,11 +202,11 @@ NonDecimal: ind := floatDigits[c] switch ind { case invalidCharForNumber: - return d.float64Slow() + return 0, badToken(c, d.offset()+i) case endOfNumber: d.head = i return float64(value), nil - case dotInNumber: + case dotInNumber, expInNumber: break NonDecimal } if value > uint64SafeToMultiple10 { @@ -310,8 +232,10 @@ NonDecimal: } // too many decimal places return d.float64Slow() - case invalidCharForNumber, dotInNumber: + case dotInNumber, expInNumber, plusInNumber, minusInNumber: return d.float64Slow() + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) } decimalPlaces++ // Not checking for uint64SafeToMultiple10 here because @@ -326,52 +250,69 @@ NonDecimal: return d.float64Slow() } -func (d *Decoder) float64Slow() (float64, error) { return d.floatSlow(size64) } +func (d *Decoder) float32Slow() (float32, error) { + v, err := d.floatSlow(32) + if err != nil { + return 0, err + } + return float32(v), err +} + +func (d *Decoder) float64Slow() (float64, error) { return d.floatSlow(64) } + +func (d *Decoder) floatSlow(size int) (float64, error) { + var ( + buf [32]byte + offset = d.offset() + ) + + str, err := d.numberAppend(buf[:0]) + if err != nil { + return 0, errors.Wrap(err, "number") + } + + if err := validateFloat(str, offset); err != nil { + return 0, err + } + + val, err := strconv.ParseFloat(string(str), size) + if err != nil { + return 0, err + } + + return val, nil +} -func validateFloat(str []byte) error { +func validateFloat(str []byte, offset int) error { // strconv.ParseFloat is not validating `1.` or `1.e1` if len(str) == 0 { + // FIXME(tdakkota): use io.ErrUnexpectedEOF? return errors.New("empty") } if str[0] == '-' { - return errors.New("double minus") + err := badToken(str[0], offset) + return errors.Wrap(err, "double minus") } if len(str) >= 2 && str[0] == '0' { switch str[1] { case 'e', 'E', '.': default: - return errors.New("leading zero") + return badToken(str[1], offset+1) } } + dotPos := bytes.IndexByte(str, '.') if dotPos != -1 { if dotPos == len(str)-1 { + // FIXME(tdakkota): use io.ErrUnexpectedEOF? return errors.New("dot as last char") } - switch str[dotPos+1] { + switch c := str[dotPos+1]; c { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': default: - return errors.New("no digit after dot") + err := badToken(c, offset+dotPos+1) + return errors.Wrap(err, "no digit after dot") } } return nil } - -func (d *Decoder) floatSlow(size int) (float64, error) { - var buf [32]byte - - str, err := d.numberAppend(buf[:0]) - if err != nil { - return 0, errors.Wrap(err, "number") - } - if err := validateFloat(str); err != nil { - return 0, errors.Wrap(err, "invalid") - } - - val, err := strconv.ParseFloat(string(str), size) - if err != nil { - return 0, err - } - - return val, nil -} diff --git a/dec_float_big.go b/dec_float_big.go new file mode 100644 index 0000000..d91224c --- /dev/null +++ b/dec_float_big.go @@ -0,0 +1,80 @@ +package jx + +import ( + "io" + "math/big" + + "github.com/go-faster/errors" +) + +// BigFloat read big.Float +func (d *Decoder) BigFloat() (*big.Float, error) { + str, err := d.numberAppend(nil) + if err != nil { + return nil, errors.Wrap(err, "number") + } + prec := 64 + if len(str) > prec { + prec = len(str) + } + val, _, err := big.ParseFloat(string(str), 10, uint(prec), big.ToZero) + if err != nil { + return nil, errors.Wrap(err, "float") + } + return val, nil +} + +// BigInt read big.Int +func (d *Decoder) BigInt() (*big.Int, error) { + str, err := d.numberAppend(nil) + if err != nil { + return nil, errors.Wrap(err, "number") + } + v := big.NewInt(0) + var ok bool + if v, ok = v.SetString(string(str), 10); !ok { + return nil, errors.New("invalid") + } + return v, nil +} + +func (d *Decoder) number() ([]byte, error) { + start := d.head + buf := d.buf[d.head:d.tail] + for i, c := range buf { + switch floatDigits[c] { + case invalidCharForNumber: + return nil, badToken(c, d.offset()+i) + case endOfNumber: + // End of number. + d.head += i + return d.buf[start:d.head], nil + default: + continue + } + } + // Buffer is number within head:tail. + d.head = d.tail + return d.buf[start:d.tail], nil +} + +func (d *Decoder) numberAppend(b []byte) ([]byte, error) { + for { + r, err := d.number() + if err != nil { + return nil, err + } + + b = append(b, r...) + if d.head != d.tail { + return b, nil + } + + if err := d.read(); err != nil { + if err == io.EOF { + return b, nil + } + return b, err + } + } +} diff --git a/dec_int.gen.go b/dec_int.gen.go index fdf41c9..cc19fc6 100644 --- a/dec_int.gen.go +++ b/dec_int.gen.go @@ -10,10 +10,7 @@ import ( "github.com/go-faster/errors" ) -var ( - intDigits [256]int8 - errOverflow = strconv.ErrRange -) +var errOverflow = strconv.ErrRange const ( uint8SafeToMultiple10 = uint8(0xff)/10 - 1 @@ -22,15 +19,6 @@ const ( uint64SafeToMultiple10 = uint64(0xffffffffffffffff)/10 - 1 ) -func init() { - for i := 0; i < len(intDigits); i++ { - intDigits[i] = invalidCharForNumber - } - for i := int8('0'); i <= int8('9'); i++ { - intDigits[i] = i - int8('0') - } -} - // UInt8 reads uint8. func (d *Decoder) UInt8() (uint8, error) { c, err := d.more() @@ -41,33 +29,53 @@ func (d *Decoder) UInt8() (uint8, error) { } func (d *Decoder) readUInt8(c byte) (uint8, error) { - ind := intDigits[c] + ind := floatDigits[c] switch ind { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && intDigits[c] != invalidCharForNumber { + if err == nil && floatDigits[c] > endOfNumber { err := badToken(c, d.offset()) return 0, errors.Wrap(err, "digit after leading zero") } return 0, nil // single zero - case invalidCharForNumber: - return 0, badToken(c, d.offset()-1) + default: + if ind < 0 { + return 0, badToken(c, d.offset()-1) + } } value := uint8(ind) if d.tail-d.head > 3 { i := d.head // Iteration 0. - ind2 := intDigits[d.buf[i]] - if ind2 == invalidCharForNumber { + ind2 := floatDigits[d.buf[i]] + switch ind2 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1 return value, nil } i++ // Iteration 1. - ind3 := intDigits[d.buf[i]] - if ind3 == invalidCharForNumber { + ind3 := floatDigits[d.buf[i]] + switch ind3 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10 value += uint8(ind2) * 1 @@ -75,8 +83,17 @@ func (d *Decoder) readUInt8(c byte) (uint8, error) { } i++ // Iteration 2. - ind4 := intDigits[d.buf[i]] - if ind4 == invalidCharForNumber { + ind4 := floatDigits[d.buf[i]] + switch ind4 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 100 value += uint8(ind2) * 10 @@ -91,8 +108,17 @@ func (d *Decoder) readUInt8(c byte) (uint8, error) { for { buf := d.buf[d.head:d.tail] for i, c := range buf { - ind = intDigits[c] - if ind == invalidCharForNumber { + ind = floatDigits[c] + switch ind { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head += i return value, nil } @@ -157,33 +183,53 @@ func (d *Decoder) UInt16() (uint16, error) { } func (d *Decoder) readUInt16(c byte) (uint16, error) { - ind := intDigits[c] + ind := floatDigits[c] switch ind { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && intDigits[c] != invalidCharForNumber { + if err == nil && floatDigits[c] > endOfNumber { err := badToken(c, d.offset()) return 0, errors.Wrap(err, "digit after leading zero") } return 0, nil // single zero - case invalidCharForNumber: - return 0, badToken(c, d.offset()-1) + default: + if ind < 0 { + return 0, badToken(c, d.offset()-1) + } } value := uint16(ind) if d.tail-d.head > 5 { i := d.head // Iteration 0. - ind2 := intDigits[d.buf[i]] - if ind2 == invalidCharForNumber { + ind2 := floatDigits[d.buf[i]] + switch ind2 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1 return value, nil } i++ // Iteration 1. - ind3 := intDigits[d.buf[i]] - if ind3 == invalidCharForNumber { + ind3 := floatDigits[d.buf[i]] + switch ind3 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10 value += uint16(ind2) * 1 @@ -191,8 +237,17 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { } i++ // Iteration 2. - ind4 := intDigits[d.buf[i]] - if ind4 == invalidCharForNumber { + ind4 := floatDigits[d.buf[i]] + switch ind4 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 100 value += uint16(ind2) * 10 @@ -201,8 +256,17 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { } i++ // Iteration 3. - ind5 := intDigits[d.buf[i]] - if ind5 == invalidCharForNumber { + ind5 := floatDigits[d.buf[i]] + switch ind5 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1000 value += uint16(ind2) * 100 @@ -212,8 +276,17 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { } i++ // Iteration 4. - ind6 := intDigits[d.buf[i]] - if ind6 == invalidCharForNumber { + ind6 := floatDigits[d.buf[i]] + switch ind6 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10000 value += uint16(ind2) * 1000 @@ -232,8 +305,17 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { for { buf := d.buf[d.head:d.tail] for i, c := range buf { - ind = intDigits[c] - if ind == invalidCharForNumber { + ind = floatDigits[c] + switch ind { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head += i return value, nil } @@ -298,33 +380,53 @@ func (d *Decoder) UInt32() (uint32, error) { } func (d *Decoder) readUInt32(c byte) (uint32, error) { - ind := intDigits[c] + ind := floatDigits[c] switch ind { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && intDigits[c] != invalidCharForNumber { + if err == nil && floatDigits[c] > endOfNumber { err := badToken(c, d.offset()) return 0, errors.Wrap(err, "digit after leading zero") } return 0, nil // single zero - case invalidCharForNumber: - return 0, badToken(c, d.offset()-1) + default: + if ind < 0 { + return 0, badToken(c, d.offset()-1) + } } value := uint32(ind) if d.tail-d.head > 9 { i := d.head // Iteration 0. - ind2 := intDigits[d.buf[i]] - if ind2 == invalidCharForNumber { + ind2 := floatDigits[d.buf[i]] + switch ind2 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1 return value, nil } i++ // Iteration 1. - ind3 := intDigits[d.buf[i]] - if ind3 == invalidCharForNumber { + ind3 := floatDigits[d.buf[i]] + switch ind3 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10 value += uint32(ind2) * 1 @@ -332,8 +434,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { } i++ // Iteration 2. - ind4 := intDigits[d.buf[i]] - if ind4 == invalidCharForNumber { + ind4 := floatDigits[d.buf[i]] + switch ind4 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 100 value += uint32(ind2) * 10 @@ -342,8 +453,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { } i++ // Iteration 3. - ind5 := intDigits[d.buf[i]] - if ind5 == invalidCharForNumber { + ind5 := floatDigits[d.buf[i]] + switch ind5 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1000 value += uint32(ind2) * 100 @@ -353,8 +473,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { } i++ // Iteration 4. - ind6 := intDigits[d.buf[i]] - if ind6 == invalidCharForNumber { + ind6 := floatDigits[d.buf[i]] + switch ind6 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10000 value += uint32(ind2) * 1000 @@ -365,8 +494,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { } i++ // Iteration 5. - ind7 := intDigits[d.buf[i]] - if ind7 == invalidCharForNumber { + ind7 := floatDigits[d.buf[i]] + switch ind7 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 100000 value += uint32(ind2) * 10000 @@ -378,8 +516,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { } i++ // Iteration 6. - ind8 := intDigits[d.buf[i]] - if ind8 == invalidCharForNumber { + ind8 := floatDigits[d.buf[i]] + switch ind8 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1000000 value += uint32(ind2) * 100000 @@ -392,8 +539,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { } i++ // Iteration 7. - ind9 := intDigits[d.buf[i]] - if ind9 == invalidCharForNumber { + ind9 := floatDigits[d.buf[i]] + switch ind9 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10000000 value += uint32(ind2) * 1000000 @@ -407,8 +563,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { } i++ // Iteration 8. - ind10 := intDigits[d.buf[i]] - if ind10 == invalidCharForNumber { + ind10 := floatDigits[d.buf[i]] + switch ind10 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 100000000 value += uint32(ind2) * 10000000 @@ -435,8 +600,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { for { buf := d.buf[d.head:d.tail] for i, c := range buf { - ind = intDigits[c] - if ind == invalidCharForNumber { + ind = floatDigits[c] + switch ind { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head += i return value, nil } @@ -501,33 +675,53 @@ func (d *Decoder) UInt64() (uint64, error) { } func (d *Decoder) readUInt64(c byte) (uint64, error) { - ind := intDigits[c] + ind := floatDigits[c] switch ind { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && intDigits[c] != invalidCharForNumber { + if err == nil && floatDigits[c] > endOfNumber { err := badToken(c, d.offset()) return 0, errors.Wrap(err, "digit after leading zero") } return 0, nil // single zero - case invalidCharForNumber: - return 0, badToken(c, d.offset()-1) + default: + if ind < 0 { + return 0, badToken(c, d.offset()-1) + } } value := uint64(ind) if d.tail-d.head > 9 { i := d.head // Iteration 0. - ind2 := intDigits[d.buf[i]] - if ind2 == invalidCharForNumber { + ind2 := floatDigits[d.buf[i]] + switch ind2 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1 return value, nil } i++ // Iteration 1. - ind3 := intDigits[d.buf[i]] - if ind3 == invalidCharForNumber { + ind3 := floatDigits[d.buf[i]] + switch ind3 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10 value += uint64(ind2) * 1 @@ -535,8 +729,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { } i++ // Iteration 2. - ind4 := intDigits[d.buf[i]] - if ind4 == invalidCharForNumber { + ind4 := floatDigits[d.buf[i]] + switch ind4 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 100 value += uint64(ind2) * 10 @@ -545,8 +748,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { } i++ // Iteration 3. - ind5 := intDigits[d.buf[i]] - if ind5 == invalidCharForNumber { + ind5 := floatDigits[d.buf[i]] + switch ind5 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1000 value += uint64(ind2) * 100 @@ -556,8 +768,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { } i++ // Iteration 4. - ind6 := intDigits[d.buf[i]] - if ind6 == invalidCharForNumber { + ind6 := floatDigits[d.buf[i]] + switch ind6 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10000 value += uint64(ind2) * 1000 @@ -568,8 +789,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { } i++ // Iteration 5. - ind7 := intDigits[d.buf[i]] - if ind7 == invalidCharForNumber { + ind7 := floatDigits[d.buf[i]] + switch ind7 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 100000 value += uint64(ind2) * 10000 @@ -581,8 +811,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { } i++ // Iteration 6. - ind8 := intDigits[d.buf[i]] - if ind8 == invalidCharForNumber { + ind8 := floatDigits[d.buf[i]] + switch ind8 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 1000000 value += uint64(ind2) * 100000 @@ -595,8 +834,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { } i++ // Iteration 7. - ind9 := intDigits[d.buf[i]] - if ind9 == invalidCharForNumber { + ind9 := floatDigits[d.buf[i]] + switch ind9 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 10000000 value += uint64(ind2) * 1000000 @@ -610,8 +858,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { } i++ // Iteration 8. - ind10 := intDigits[d.buf[i]] - if ind10 == invalidCharForNumber { + ind10 := floatDigits[d.buf[i]] + switch ind10 { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= 100000000 value += uint64(ind2) * 10000000 @@ -638,8 +895,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { for { buf := d.buf[d.head:d.tail] for i, c := range buf { - ind = intDigits[c] - if ind == invalidCharForNumber { + ind = floatDigits[c] + switch ind { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head += i return value, nil } diff --git a/num.go b/num.go index e9430ed..0ce0f07 100644 --- a/num.go +++ b/num.go @@ -22,13 +22,15 @@ type Num []byte func (n Num) dec() Decoder { head := 0 + tail := len(n) if n.Str() { head = 1 + tail-- } return Decoder{ buf: n, - tail: len(n), head: head, + tail: tail, } } @@ -37,33 +39,37 @@ func (n Num) Str() bool { return len(n) > 0 && n[0] == '"' } -func (n Num) floatAsInt() error { +func (n Num) floatAsInt() (dotIdx int, _ error) { // Allow decoding floats with zero fractional, like 1.0 as 1. - var dot bool + dotIdx = -1 for i, c := range n { if c == '.' { - dot = true + dotIdx = i continue } - if !dot { + if dotIdx == -1 { continue } switch c { case '0', '"': // ok default: - return errors.Errorf("non-zero fractional part %q at %d", c, i) + return dotIdx, errors.Errorf("non-zero fractional part %q at %d", c, i) } } - return nil + return dotIdx, nil } // Int64 decodes number as a signed 64-bit integer. // Works on floats with zero fractional part. func (n Num) Int64() (int64, error) { - if err := n.floatAsInt(); err != nil { + dotIdx, err := n.floatAsInt() + if err != nil { return 0, errors.Wrap(err, "float as int") } d := n.dec() + if dotIdx != -1 { + d.tail = dotIdx + } return d.Int64() } @@ -92,10 +98,14 @@ func (n Num) IsInt() bool { // Uint64 decodes number as an unsigned 64-bit integer. // Works on floats with zero fractional part. func (n Num) Uint64() (uint64, error) { - if err := n.floatAsInt(); err != nil { + dotIdx, err := n.floatAsInt() + if err != nil { return 0, errors.Wrap(err, "float as int") } d := n.dec() + if dotIdx != -1 { + d.tail = dotIdx + } return d.UInt64() } diff --git a/num_test.go b/num_test.go index bbf07fb..87670de 100644 --- a/num_test.go +++ b/num_test.go @@ -201,7 +201,7 @@ func BenchmarkNum(b *testing.B) { b.Run("AsInt", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - if err := v.floatAsInt(); err != nil { + if _, err := v.floatAsInt(); err != nil { b.Fatal(err) } } diff --git a/tools/mkint/decode.tmpl b/tools/mkint/decode.tmpl index a01d0fa..596bb69 100644 --- a/tools/mkint/decode.tmpl +++ b/tools/mkint/decode.tmpl @@ -13,10 +13,7 @@ import ( ) -var ( - intDigits [256]int8 - errOverflow = strconv.ErrRange -) +var errOverflow = strconv.ErrRange const ( uint8SafeToMultiple10 = uint8(0xff)/10 - 1 @@ -25,15 +22,6 @@ const ( uint64SafeToMultiple10 = uint64(0xffffffffffffffff)/10 - 1 ) -func init() { - for i := 0; i < len(intDigits); i++ { - intDigits[i] = invalidCharForNumber - } - for i := int8('0'); i <= int8('9'); i++ { - intDigits[i] = i - int8('0') - } -} - {{ range $typ := $.Types }} {{ template "decode_uint" $typ }} {{ template "decode_int" $typ }} @@ -53,26 +41,45 @@ func (d *Decoder) U{{ title $.Name }}() (u{{ $.Name }}, error) { } func (d *Decoder) readU{{ title $.Name }}(c byte) (u{{ $.Name }}, error) { - ind := intDigits[c] + ind := floatDigits[c] switch ind { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && intDigits[c] != invalidCharForNumber { - err := badToken(c, d.offset()) - return 0, errors.Wrap(err, "digit after leading zero") + if err == nil { + switch floatDigits[c] { + case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "digit after leading zero") + case dotInNumber, expInNumber, plusInNumber, minusInNumber: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "unexpected floating point character") + case invalidCharForNumber: + return 0, badToken(c, d.offset()) + } } return 0, nil // single zero - case invalidCharForNumber: - return 0, badToken(c, d.offset()-1) + default: + if ind < 0 { + return 0, badToken(c, d.offset()-1) + } } value := u{{ $.Name }}(ind) if d.tail-d.head > {{ $.DecoderIterations }} { i := d.head {{- range $i, $_ := times $.DecoderIterations }} // Iteration {{ $i }}. - ind{{ add $i 2 }} := intDigits[d.buf[i]] - if ind{{ add $i 2 }} == invalidCharForNumber { + ind{{ add $i 2 }} := floatDigits[d.buf[i]] + switch ind{{ add $i 2 }} { + case invalidCharForNumber: + return 0, badToken(d.buf[i], d.offset()+{{ $i }}) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(d.buf[i], d.offset()+{{ $i }}) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head = i value *= {{ pow10 $i }} {{- range $r, $_ := times $i }} @@ -96,8 +103,17 @@ func (d *Decoder) readU{{ title $.Name }}(c byte) (u{{ $.Name }}, error) { for { buf := d.buf[d.head:d.tail] for i, c := range buf { - ind = intDigits[c] - if ind == invalidCharForNumber { + ind = floatDigits[c] + switch ind { + case invalidCharForNumber: + return 0, badToken(c, d.offset()+i) + case dotInNumber, + expInNumber, + plusInNumber, + minusInNumber: + err := badToken(c, d.offset()+i) + return 0, errors.Wrap(err, "unexpected floating point character") + case endOfNumber: d.head += i return value, nil } From d5bda47aee1d247915569e744636bbc0653044f7 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Mon, 23 Jan 2023 12:48:17 +0300 Subject: [PATCH 2/5] test: check offset for all integer sizes --- dec_int_test.go | 103 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 20 deletions(-) diff --git a/dec_int_test.go b/dec_int_test.go index 3da6dc4..5b5cc63 100644 --- a/dec_int_test.go +++ b/dec_int_test.go @@ -2,9 +2,13 @@ package jx import ( "fmt" + "strconv" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/go-faster/errors" ) func BenchmarkDecoder_Int(b *testing.B) { @@ -113,40 +117,89 @@ func intDecoderOnlyError[T any](fn func(*Decoder) (T, error)) func(*Decoder) err func TestDecoderIntUnexpectedChar(t *testing.T) { type intFunc struct { - name string - fn func(*Decoder) error + name string + bitSize int + fn func(*Decoder) error } signed := []intFunc{ - {"Int", intDecoderOnlyError((*Decoder).Int)}, - {"Int8", intDecoderOnlyError((*Decoder).Int8)}, - {"Int16", intDecoderOnlyError((*Decoder).Int16)}, - {"Int32", intDecoderOnlyError((*Decoder).Int32)}, - {"Int64", intDecoderOnlyError((*Decoder).Int64)}, + {"Int", strconv.IntSize, intDecoderOnlyError((*Decoder).Int)}, + {"Int8", 8, intDecoderOnlyError((*Decoder).Int8)}, + {"Int16", 16, intDecoderOnlyError((*Decoder).Int16)}, + {"Int32", 32, intDecoderOnlyError((*Decoder).Int32)}, + {"Int64", 64, intDecoderOnlyError((*Decoder).Int64)}, } unsigned := []intFunc{ - {"UInt", intDecoderOnlyError((*Decoder).UInt)}, - {"UInt8", intDecoderOnlyError((*Decoder).UInt8)}, - {"UInt16", intDecoderOnlyError((*Decoder).UInt16)}, - {"UInt32", intDecoderOnlyError((*Decoder).UInt32)}, - {"UInt64", intDecoderOnlyError((*Decoder).UInt64)}, + {"UInt", strconv.IntSize, intDecoderOnlyError((*Decoder).UInt)}, + {"UInt8", 8, intDecoderOnlyError((*Decoder).UInt8)}, + {"UInt16", 16, intDecoderOnlyError((*Decoder).UInt16)}, + {"UInt32", 32, intDecoderOnlyError((*Decoder).UInt32)}, + {"UInt64", 64, intDecoderOnlyError((*Decoder).UInt64)}, } tests := []struct { input string unsigned bool + size int // 0 for any errString string }{ // Leading space. - {" 10", true, ""}, - {" 10", true, ""}, - {" -10", false, ""}, + {" 10", true, 0, ""}, + {" 10", true, 0, ""}, + {" -10", false, 0, ""}, // Space in the middle. - {"- 10", false, "unexpected byte 32 ' ' at 1"}, + {"- 10", false, 0, "unexpected byte 32 ' ' at 1"}, // Digit after leading zero. - {"00", true, "digit after leading zero: unexpected byte 48 '0' at 1"}, - {"01", true, "digit after leading zero: unexpected byte 49 '1' at 1"}, + {"00", true, 0, "digit after leading zero: unexpected byte 48 '0' at 1"}, + {"01", true, 0, "digit after leading zero: unexpected byte 49 '1' at 1"}, + + // Unexpected character. + // 8 bits. + {"0a0", true, 0, "unexpected byte 97 'a' at 1"}, + {"1a00000000000", true, 0, "unexpected byte 97 'a' at 1"}, + {"10a0000000000", true, 0, "unexpected byte 97 'a' at 2"}, + {"100a000000000", true, 0, "unexpected byte 97 'a' at 3"}, + // 16 bits. + {"1000a00000000", true, 16, "unexpected byte 97 'a' at 4"}, + {"10000a0000000", true, 16, "unexpected byte 97 'a' at 5"}, + // 32 bits. + {"100000a000000", true, 32, "unexpected byte 97 'a' at 6"}, + {"1000000a00000", true, 32, "unexpected byte 97 'a' at 7"}, + {"10000000a0000", true, 32, "unexpected byte 97 'a' at 8"}, + {"100000000a000", true, 32, "unexpected byte 97 'a' at 9"}, + {"1000000000a00", true, 32, "unexpected byte 97 'a' at 10"}, + // 64 bits. + {"10000000000a0", true, 64, "unexpected byte 97 'a' at 11"}, + + // Dot in integer. + // 8 bits. + {"0.0", true, 0, "unexpected floating point character: unexpected byte 46 '.' at 1"}, + {"1.00000000000", true, 0, "unexpected floating point character: unexpected byte 46 '.' at 1"}, + {"10.0000000000", true, 0, "unexpected floating point character: unexpected byte 46 '.' at 2"}, + {"100.000000000", true, 0, "unexpected floating point character: unexpected byte 46 '.' at 3"}, + // 16 bits. + {"1000.00000000", true, 16, "unexpected floating point character: unexpected byte 46 '.' at 4"}, + {"10000.0000000", true, 16, "unexpected floating point character: unexpected byte 46 '.' at 5"}, + // 32 bits. + {"100000.000000", true, 32, "unexpected floating point character: unexpected byte 46 '.' at 6"}, + {"1000000.00000", true, 32, "unexpected floating point character: unexpected byte 46 '.' at 7"}, + {"10000000.0000", true, 32, "unexpected floating point character: unexpected byte 46 '.' at 8"}, + {"100000000.000", true, 32, "unexpected floating point character: unexpected byte 46 '.' at 9"}, + {"1000000000.00", true, 32, "unexpected floating point character: unexpected byte 46 '.' at 10"}, + // 64 bits. + {"10000000000.0", true, 64, "unexpected floating point character: unexpected byte 46 '.' at 11"}, + + // Exp in integer. + {"0e0", true, 0, "unexpected floating point character: unexpected byte 101 'e' at 1"}, + {"0E0", true, 0, "unexpected floating point character: unexpected byte 69 'E' at 1"}, + {"0e-0", true, 0, "unexpected floating point character: unexpected byte 101 'e' at 1"}, + {"0e+0", true, 0, "unexpected floating point character: unexpected byte 101 'e' at 1"}, + + {"1e0", true, 0, "unexpected floating point character: unexpected byte 101 'e' at 1"}, + {"1E0", true, 0, "unexpected floating point character: unexpected byte 69 'E' at 1"}, + {"1e-0", true, 0, "unexpected floating point character: unexpected byte 101 'e' at 1"}, + {"1e+0", true, 0, "unexpected floating point character: unexpected byte 101 'e' at 1"}, } for i, tt := range tests { @@ -155,14 +208,24 @@ func TestDecoderIntUnexpectedChar(t *testing.T) { check := func(fns []intFunc) { for _, intFn := range fns { intFn := intFn + if tt.size != 0 && tt.size > intFn.bitSize { + continue + } t.Run(intFn.name, func(t *testing.T) { decodeStr(t, tt.input, func(t *testing.T, d *Decoder) { + a := assert.New(t) + err := intFn.fn(d) if e := tt.errString; e != "" { - require.EqualError(t, err, e) + a.EqualError(err, e) + v, ok := errors.Into[*badTokenErr](err) + if !ok { + return + } + a.Equal(v.Token, tt.input[v.Offset]) return } - require.NoError(t, err) + a.NoError(err) }) }) } From 66f56e1b3e358b2e15942e9a592a407c70c13ec9 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Tue, 24 Jan 2023 08:41:32 +0300 Subject: [PATCH 3/5] fix: improve float decoder error handling --- dec_float.go | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/dec_float.go b/dec_float.go index 7dfe680..5a3807f 100644 --- a/dec_float.go +++ b/dec_float.go @@ -150,20 +150,17 @@ func (d *Decoder) Float64() (float64, error) { if err != nil { return 0, err } - if floatDigits[c] >= 0 { + if c != '-' { d.unread() - return d.positiveFloat64() } - switch c { - case '-': - v, err := d.positiveFloat64() - if err != nil { - return 0, err - } - return -v, err - default: - return 0, badToken(c, d.offset()) + v, err := d.positiveFloat64() + if err != nil { + return 0, err + } + if c == '-' { + v *= -1 } + return v, nil } func (d *Decoder) positiveFloat64() (float64, error) { @@ -289,15 +286,22 @@ func validateFloat(str []byte, offset int) error { // FIXME(tdakkota): use io.ErrUnexpectedEOF? return errors.New("empty") } - if str[0] == '-' { - err := badToken(str[0], offset) + + switch c := str[0]; floatDigits[c] { + case dotInNumber, plusInNumber, expInNumber: + err := badToken(c, offset) + return errors.Wrapf(err, "leading %q", c) + case minusInNumber: // minus handled by caller + err := badToken(c, offset) return errors.Wrap(err, "double minus") - } - if len(str) >= 2 && str[0] == '0' { - switch str[1] { - case 'e', 'E', '.': - default: - return badToken(str[1], offset+1) + case 0: + if len(str) >= 2 { + switch str[1] { + case 'e', 'E', '.': + default: + err := badToken(str[1], offset+1) + return errors.Wrap(err, "leading zero") + } } } From 8b520a21433c915e56cf5da7fdeeee712effaf97 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Mon, 23 Jan 2023 13:19:41 +0300 Subject: [PATCH 4/5] test: check offset for all float sizes --- dec_float_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++ dec_int_test.go | 27 +++++--------- dec_skip_cases_test.go | 5 +++ dec_test.go | 7 ++++ 4 files changed, 106 insertions(+), 17 deletions(-) diff --git a/dec_float_test.go b/dec_float_test.go index 42d8d4d..fbb2e94 100644 --- a/dec_float_test.go +++ b/dec_float_test.go @@ -7,7 +7,10 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/go-faster/errors" ) func decodeStr(t *testing.T, s string, f func(t *testing.T, d *Decoder)) { @@ -134,6 +137,87 @@ func TestDecoder_Float64(t *testing.T) { } } +func TestDecoderFloatUnexpectedChar(t *testing.T) { + type floatFunc struct { + name string + bitSize int + fn func(*Decoder) error + } + floatFuncs := []floatFunc{ + {"Float32", 32, decoderOnlyError((*Decoder).Float32)}, + {"Float64", 64, decoderOnlyError((*Decoder).Float64)}, + } + + tests := []struct { + input string + size int // 0 for any + errContains string + }{ + // Leading space. + {" 10", 0, ""}, + {" 10", 0, ""}, + {" -10", 0, ""}, + + // Digit after leading zero. + {"00", 0, "leading zero: unexpected byte 48 '0' at 1"}, + {"01", 0, "leading zero: unexpected byte 49 '1' at 1"}, + {"-00", 0, "leading zero: unexpected byte 48 '0' at 2"}, + {"-01", 0, "leading zero: unexpected byte 49 '1' at 2"}, + + // Double minus. + {"--10", 0, "unexpected byte 45 '-' at 1"}, + + // Leading dot. + {".0", 0, "unexpected byte 46 '.' at 0"}, + // Leading exponent. + {"e0", 0, "unexpected byte 101 'e' at 0"}, + {"E0", 0, "unexpected byte 69 'E' at 0"}, + + // Non-digit after minus. + {"-.0", 0, "unexpected byte 46 '.' at 1"}, + {"-e0", 0, "unexpected byte 101 'e' at 1"}, + {"-E0", 0, "unexpected byte 69 'E' at 1"}, + + // Unexpected character. + {"-a", 0, "unexpected byte 97 'a' at 1"}, + {"0a", 0, "unexpected byte 97 'a' at 1"}, + {"0.a", 0, "unexpected byte 97 'a' at 2"}, + } + + for i, tt := range tests { + tt := tt + t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { + check := func(fns []floatFunc) { + for _, intFn := range fns { + intFn := intFn + if tt.size != 0 && tt.size > intFn.bitSize { + continue + } + t.Run(intFn.name, func(t *testing.T) { + decodeStr(t, tt.input, func(t *testing.T, d *Decoder) { + a := assert.New(t) + + err := intFn.fn(d) + if e := tt.errContains; e != "" { + a.ErrorContains(err, e) + v, ok := errors.Into[*badTokenErr](err) + if !ok { + return + } + a.Equal(v.Token, tt.input[v.Offset]) + return + } + a.NoError(err) + }) + }) + } + } + + check(floatFuncs) + }) + } +} + func BenchmarkDecoder_Float64(b *testing.B) { for _, file := range []string{ "floats.json", diff --git a/dec_int_test.go b/dec_int_test.go index 5b5cc63..cf58096 100644 --- a/dec_int_test.go +++ b/dec_int_test.go @@ -108,13 +108,6 @@ func TestDecoderIntError(t *testing.T) { }) } -func intDecoderOnlyError[T any](fn func(*Decoder) (T, error)) func(*Decoder) error { - return func(d *Decoder) error { - _, err := fn(d) - return err - } -} - func TestDecoderIntUnexpectedChar(t *testing.T) { type intFunc struct { name string @@ -122,18 +115,18 @@ func TestDecoderIntUnexpectedChar(t *testing.T) { fn func(*Decoder) error } signed := []intFunc{ - {"Int", strconv.IntSize, intDecoderOnlyError((*Decoder).Int)}, - {"Int8", 8, intDecoderOnlyError((*Decoder).Int8)}, - {"Int16", 16, intDecoderOnlyError((*Decoder).Int16)}, - {"Int32", 32, intDecoderOnlyError((*Decoder).Int32)}, - {"Int64", 64, intDecoderOnlyError((*Decoder).Int64)}, + {"Int", strconv.IntSize, decoderOnlyError((*Decoder).Int)}, + {"Int8", 8, decoderOnlyError((*Decoder).Int8)}, + {"Int16", 16, decoderOnlyError((*Decoder).Int16)}, + {"Int32", 32, decoderOnlyError((*Decoder).Int32)}, + {"Int64", 64, decoderOnlyError((*Decoder).Int64)}, } unsigned := []intFunc{ - {"UInt", strconv.IntSize, intDecoderOnlyError((*Decoder).UInt)}, - {"UInt8", 8, intDecoderOnlyError((*Decoder).UInt8)}, - {"UInt16", 16, intDecoderOnlyError((*Decoder).UInt16)}, - {"UInt32", 32, intDecoderOnlyError((*Decoder).UInt32)}, - {"UInt64", 64, intDecoderOnlyError((*Decoder).UInt64)}, + {"UInt", strconv.IntSize, decoderOnlyError((*Decoder).UInt)}, + {"UInt8", 8, decoderOnlyError((*Decoder).UInt8)}, + {"UInt16", 16, decoderOnlyError((*Decoder).UInt16)}, + {"UInt32", 32, decoderOnlyError((*Decoder).UInt32)}, + {"UInt64", 64, decoderOnlyError((*Decoder).UInt64)}, } tests := []struct { diff --git a/dec_skip_cases_test.go b/dec_skip_cases_test.go index dae21d5..13da895 100644 --- a/dec_skip_cases_test.go +++ b/dec_skip_cases_test.go @@ -36,11 +36,13 @@ var testNumbers = append([]string{ "0", // valid "-", // invalid "--", // invalid + "-a", // invalid "+", // invalid ".", // invalid "e", // invalid "E", // invalid "-.", // invalid + "-0", // valid "-1", // valid "--1", // invalid "+1", // invalid @@ -49,6 +51,8 @@ var testNumbers = append([]string{ "-0", // valid "00", // invalid "01", // invalid + "-00", // invalid + "-01", // invalid ".00", // invalid "00.1", // invalid "-00", // invalid @@ -100,6 +104,7 @@ var testNumbers = append([]string{ "0..1", // invalid, more dot "0.1.", // invalid, more dot "1..", // invalid, more dot + "1.0a", // invalid "1e+1", // valid "1+1", // invalid "1E1", // valid, e or E diff --git a/dec_test.go b/dec_test.go index 9596cbb..7d1d974 100644 --- a/dec_test.go +++ b/dec_test.go @@ -12,6 +12,13 @@ import ( "github.com/stretchr/testify/require" ) +func decoderOnlyError[T any](fn func(*Decoder) (T, error)) func(*Decoder) error { + return func(d *Decoder) error { + _, err := fn(d) + return err + } +} + // testBufferReader runs the given test function with various decoding modes (buffered, streaming, etc.). func testBufferReader(input string, cb func(t *testing.T, d *Decoder)) func(t *testing.T) { return func(t *testing.T) { From 03c1e5e32df394fa5e25cbcdaaa49cc6f082bd93 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Fri, 20 Jan 2023 06:42:09 +0300 Subject: [PATCH 5/5] chore: commit generated files --- dec_int.gen.go | 160 +++++++++++++++++++++++++++++-------------------- 1 file changed, 96 insertions(+), 64 deletions(-) diff --git a/dec_int.gen.go b/dec_int.gen.go index cc19fc6..dd45498 100644 --- a/dec_int.gen.go +++ b/dec_int.gen.go @@ -34,9 +34,17 @@ func (d *Decoder) readUInt8(c byte) (uint8, error) { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && floatDigits[c] > endOfNumber { - err := badToken(c, d.offset()) - return 0, errors.Wrap(err, "digit after leading zero") + if err == nil { + switch floatDigits[c] { + case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "digit after leading zero") + case dotInNumber, expInNumber, plusInNumber, minusInNumber: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "unexpected floating point character") + case invalidCharForNumber: + return 0, badToken(c, d.offset()) + } } return 0, nil // single zero default: @@ -51,12 +59,12 @@ func (d *Decoder) readUInt8(c byte) (uint8, error) { ind2 := floatDigits[d.buf[i]] switch ind2 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+0) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+0) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -68,12 +76,12 @@ func (d *Decoder) readUInt8(c byte) (uint8, error) { ind3 := floatDigits[d.buf[i]] switch ind3 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+1) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+1) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -86,12 +94,12 @@ func (d *Decoder) readUInt8(c byte) (uint8, error) { ind4 := floatDigits[d.buf[i]] switch ind4 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+2) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+2) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -188,9 +196,17 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && floatDigits[c] > endOfNumber { - err := badToken(c, d.offset()) - return 0, errors.Wrap(err, "digit after leading zero") + if err == nil { + switch floatDigits[c] { + case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "digit after leading zero") + case dotInNumber, expInNumber, plusInNumber, minusInNumber: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "unexpected floating point character") + case invalidCharForNumber: + return 0, badToken(c, d.offset()) + } } return 0, nil // single zero default: @@ -205,12 +221,12 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { ind2 := floatDigits[d.buf[i]] switch ind2 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+0) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+0) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -222,12 +238,12 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { ind3 := floatDigits[d.buf[i]] switch ind3 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+1) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+1) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -240,12 +256,12 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { ind4 := floatDigits[d.buf[i]] switch ind4 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+2) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+2) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -259,12 +275,12 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { ind5 := floatDigits[d.buf[i]] switch ind5 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+3) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+3) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -279,12 +295,12 @@ func (d *Decoder) readUInt16(c byte) (uint16, error) { ind6 := floatDigits[d.buf[i]] switch ind6 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+4) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+4) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -385,9 +401,17 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && floatDigits[c] > endOfNumber { - err := badToken(c, d.offset()) - return 0, errors.Wrap(err, "digit after leading zero") + if err == nil { + switch floatDigits[c] { + case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "digit after leading zero") + case dotInNumber, expInNumber, plusInNumber, minusInNumber: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "unexpected floating point character") + case invalidCharForNumber: + return 0, badToken(c, d.offset()) + } } return 0, nil // single zero default: @@ -402,12 +426,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind2 := floatDigits[d.buf[i]] switch ind2 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+0) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+0) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -419,12 +443,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind3 := floatDigits[d.buf[i]] switch ind3 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+1) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+1) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -437,12 +461,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind4 := floatDigits[d.buf[i]] switch ind4 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+2) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+2) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -456,12 +480,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind5 := floatDigits[d.buf[i]] switch ind5 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+3) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+3) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -476,12 +500,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind6 := floatDigits[d.buf[i]] switch ind6 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+4) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+4) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -497,12 +521,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind7 := floatDigits[d.buf[i]] switch ind7 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+5) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+5) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -519,12 +543,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind8 := floatDigits[d.buf[i]] switch ind8 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+6) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+6) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -542,12 +566,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind9 := floatDigits[d.buf[i]] switch ind9 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+7) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+7) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -566,12 +590,12 @@ func (d *Decoder) readUInt32(c byte) (uint32, error) { ind10 := floatDigits[d.buf[i]] switch ind10 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+8) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+8) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -680,9 +704,17 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { case 0: // Check that next byte is not a digit. c, err := d.peek() - if err == nil && floatDigits[c] > endOfNumber { - err := badToken(c, d.offset()) - return 0, errors.Wrap(err, "digit after leading zero") + if err == nil { + switch floatDigits[c] { + case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "digit after leading zero") + case dotInNumber, expInNumber, plusInNumber, minusInNumber: + err := badToken(c, d.offset()) + return 0, errors.Wrap(err, "unexpected floating point character") + case invalidCharForNumber: + return 0, badToken(c, d.offset()) + } } return 0, nil // single zero default: @@ -697,12 +729,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind2 := floatDigits[d.buf[i]] switch ind2 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+0) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+0) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -714,12 +746,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind3 := floatDigits[d.buf[i]] switch ind3 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+1) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+1) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -732,12 +764,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind4 := floatDigits[d.buf[i]] switch ind4 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+2) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+2) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -751,12 +783,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind5 := floatDigits[d.buf[i]] switch ind5 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+3) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+3) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -771,12 +803,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind6 := floatDigits[d.buf[i]] switch ind6 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+4) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+4) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -792,12 +824,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind7 := floatDigits[d.buf[i]] switch ind7 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+5) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+5) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -814,12 +846,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind8 := floatDigits[d.buf[i]] switch ind8 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+6) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+6) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -837,12 +869,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind9 := floatDigits[d.buf[i]] switch ind9 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+7) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+7) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i @@ -861,12 +893,12 @@ func (d *Decoder) readUInt64(c byte) (uint64, error) { ind10 := floatDigits[d.buf[i]] switch ind10 { case invalidCharForNumber: - return 0, badToken(c, d.offset()+i) + return 0, badToken(d.buf[i], d.offset()+8) case dotInNumber, expInNumber, plusInNumber, minusInNumber: - err := badToken(c, d.offset()+i) + err := badToken(d.buf[i], d.offset()+8) return 0, errors.Wrap(err, "unexpected floating point character") case endOfNumber: d.head = i