Skip to content

Commit cd30fdf

Browse files
committed
jsoniter: Fix errors during reading integers from chunked io.Reader
This commit fixes bug in Iterator.assertInteger method if the next conditions are met: - Iterator reads data from `io.Reader`, - expected value is `0` (zero) - `Iterator.tail == Iterator.head + 1` - `Iterator.tail < len(Iterator.buf)` - value in the buffer after `Iterator.tail` is presented from the previous read and has '.' character. Typical error which user cal see is: - assertInteger: can not decode float as int, error found in #X byte of ... Regression test added for checking the correct behaviour. Fixes #476
1 parent 9461257 commit cd30fdf

File tree

2 files changed

+92
-1
lines changed

2 files changed

+92
-1
lines changed

iter_int.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ func (iter *Iterator) readUint64(c byte) (ret uint64) {
339339
}
340340

341341
func (iter *Iterator) assertInteger() {
342-
if iter.head < len(iter.buf) && iter.buf[iter.head] == '.' {
342+
if iter.head < iter.tail && iter.buf[iter.head] == '.' {
343343
iter.ReportError("assertInteger", "can not decode float as int")
344344
}
345345
}

misc_tests/jsoniter_int_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ package misc_tests
55
import (
66
"bytes"
77
"encoding/json"
8+
"io"
89
"io/ioutil"
10+
"math/rand"
911
"strconv"
1012
"testing"
1113

@@ -70,6 +72,95 @@ func Test_float_as_int(t *testing.T) {
7072
should.NotNil(jsoniter.Unmarshal([]byte(`1.1`), &i))
7173
}
7274

75+
// chunkedData is io.Reader which returns random amount of data in range [1, chunkedData.chunkSize].
76+
// It simulates chunked data on from HTTP server, which is commonly used by net/http package.
77+
type chunkedData struct {
78+
chunkSize int
79+
data []byte
80+
head int
81+
}
82+
83+
// Read is implementation of the io.Reader which returns random amount of data in range [1, chunkedData.chunkSize].
84+
func (c *chunkedData) Read(p []byte) (n int, err error) {
85+
to := c.head + int(rand.Int31n(int32(c.chunkSize))+1)
86+
87+
// copy does not copy more data then p can consume
88+
n = copy(p, c.data[c.head:to])
89+
c.head = c.head + n
90+
if c.head >= len(c.data) {
91+
err = io.EOF
92+
}
93+
return n, err
94+
}
95+
96+
// TestIterator_ReadInt_chunkedInput validates the behaviour of Iterator.ReadInt() method in where:
97+
// - it reads data from io.Reader,
98+
// - expected value is 0 (zero)
99+
// - Iterator.tail == Iterator.head
100+
// - Iterator.tail < len(Iterator.buf)
101+
// - value in buffer after Iterator.tail is presented from previous read and has '.' character.
102+
func TestIterator_ReadInt_chunkedInput(t *testing.T) {
103+
should := require.New(t)
104+
105+
data := &chunkedData{
106+
data: jsonFloatIntArray(t, 10),
107+
}
108+
109+
// because this test is rely on randomness of chunkedData, we are doing multiple iterations to
110+
// be sure, that we can hit a required case.
111+
for data.chunkSize = 3; data.chunkSize <= len(data.data); data.chunkSize++ {
112+
data.head = 0
113+
114+
iter := jsoniter.Parse(jsoniter.ConfigDefault, data, data.chunkSize)
115+
i := 0
116+
for iter.ReadArray() {
117+
// every even item is float, let's just skip it.
118+
if i%2 == 0 {
119+
iter.Skip()
120+
i++
121+
continue
122+
}
123+
124+
should.Zero(iter.ReadInt())
125+
should.NoError(iter.Error)
126+
127+
i++
128+
}
129+
}
130+
}
131+
132+
// jsonFloatIntArray generates JSON array where every
133+
// - even item is float 0.1
134+
// - odd item is integer 0
135+
//
136+
// [0.1, 0, 0.1, 0]
137+
func jsonFloatIntArray(t *testing.T, numberOfItems int) []byte {
138+
t.Helper()
139+
numbers := make([]jsoniter.Any, numberOfItems)
140+
for i := range numbers {
141+
switch i % 2 {
142+
case 0:
143+
numbers[i] = jsoniter.WrapFloat64(0.1)
144+
default:
145+
numbers[i] = jsoniter.WrapInt64(0)
146+
}
147+
}
148+
149+
fixture, err := jsoniter.ConfigFastest.Marshal(numbers)
150+
if err != nil {
151+
panic(err)
152+
}
153+
154+
b := &bytes.Buffer{}
155+
156+
require.NoError(
157+
t,
158+
json.Compact(b, fixture),
159+
"json should be compactable",
160+
)
161+
return b.Bytes()
162+
}
163+
73164
func Benchmark_jsoniter_encode_int(b *testing.B) {
74165
stream := jsoniter.NewStream(jsoniter.ConfigDefault, ioutil.Discard, 64)
75166
for n := 0; n < b.N; n++ {

0 commit comments

Comments
 (0)