Skip to content

Commit 3df3888

Browse files
encoding/json: calculate correct SyntaxError.Offset in the stream
Stream decoder does not count whitespaces, empty objects and arrays in syntax error offset. This change removes offset tracking from the scanner and relies on the calling code to provide the correct value. Fixes #44811
1 parent 4a3daee commit 3df3888

File tree

6 files changed

+46
-31
lines changed

6 files changed

+46
-31
lines changed

src/encoding/json/decode_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ var unmarshalTests = []unmarshalTest{
452452
{in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}},
453453
{in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}},
454454
{in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true},
455+
{in: ``, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 0}},
456+
{in: ` `, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 1}},
455457
{in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 5}},
456458
{in: `{"F3": -}`, ptr: new(V), out: V{F3: Number("-")}, err: &SyntaxError{msg: "invalid character '}' in numeric literal", Offset: 9}},
457459

@@ -1085,6 +1087,11 @@ func equalError(a, b error) bool {
10851087
if b == nil {
10861088
return a == nil
10871089
}
1090+
ase, aok := a.(*SyntaxError)
1091+
bse, bok := b.(*SyntaxError)
1092+
if aok || bok {
1093+
return aok && bok && *ase == *bse
1094+
}
10881095
return a.Error() == b.Error()
10891096
}
10901097

src/encoding/json/indent.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ func compact(dst *bytes.Buffer, src []byte, escape bool) error {
1919
scan := newScanner()
2020
defer freeScanner(scan)
2121
start := 0
22+
var offset int64
2223
for i, c := range src {
24+
offset++
2325
if escape && (c == '<' || c == '>' || c == '&') {
2426
if start < i {
2527
dst.Write(src[start:i])
@@ -51,7 +53,7 @@ func compact(dst *bytes.Buffer, src []byte, escape bool) error {
5153
}
5254
if scan.eof() == scanError {
5355
dst.Truncate(origLen)
54-
return scan.err
56+
return &SyntaxError{msg: scan.errMsg, Offset: offset}
5557
}
5658
if start < len(src) {
5759
dst.Write(src[start:])
@@ -84,8 +86,9 @@ func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
8486
defer freeScanner(scan)
8587
needIndent := false
8688
depth := 0
89+
var offset int64
8790
for _, c := range src {
88-
scan.bytes++
91+
offset++
8992
v := scan.step(scan, c)
9093
if v == scanSkipSpace {
9194
continue
@@ -137,7 +140,7 @@ func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
137140
}
138141
if scan.eof() == scanError {
139142
dst.Truncate(origLen)
140-
return scan.err
143+
return &SyntaxError{msg: scan.errMsg, Offset: offset}
141144
}
142145
return nil
143146
}

src/encoding/json/scanner.go

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,13 @@ func Valid(data []byte) bool {
2929
// scan is passed in for use by checkValid to avoid an allocation.
3030
func checkValid(data []byte, scan *scanner) error {
3131
scan.reset()
32-
for _, c := range data {
33-
scan.bytes++
32+
for i, c := range data {
3433
if scan.step(scan, c) == scanError {
35-
return scan.err
34+
return &SyntaxError{msg: scan.errMsg, Offset: int64(i + 1)}
3635
}
3736
}
3837
if scan.eof() == scanError {
39-
return scan.err
38+
return &SyntaxError{msg: scan.errMsg, Offset: int64(len(data))}
4039
}
4140
return nil
4241
}
@@ -75,11 +74,7 @@ type scanner struct {
7574
parseState []int
7675

7776
// Error that happened, if any.
78-
err error
79-
80-
// total bytes consumed, updated by decoder.Decode (and deliberately
81-
// not set to zero by scan.reset)
82-
bytes int64
77+
errMsg string
8378
}
8479

8580
var scannerPool = sync.Pool{
@@ -90,8 +85,6 @@ var scannerPool = sync.Pool{
9085

9186
func newScanner() *scanner {
9287
scan := scannerPool.Get().(*scanner)
93-
// scan.reset by design doesn't set bytes to zero
94-
scan.bytes = 0
9588
scan.reset()
9689
return scan
9790
}
@@ -148,14 +141,14 @@ const maxNestingDepth = 10000
148141
func (s *scanner) reset() {
149142
s.step = stateBeginValue
150143
s.parseState = s.parseState[0:0]
151-
s.err = nil
144+
s.errMsg = ""
152145
s.endTop = false
153146
}
154147

155148
// eof tells the scanner that the end of input has been reached.
156149
// It returns a scan status just as s.step does.
157150
func (s *scanner) eof() int {
158-
if s.err != nil {
151+
if s.errMsg != "" {
159152
return scanError
160153
}
161154
if s.endTop {
@@ -165,8 +158,8 @@ func (s *scanner) eof() int {
165158
if s.endTop {
166159
return scanEnd
167160
}
168-
if s.err == nil {
169-
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
161+
if s.errMsg == "" {
162+
s.errMsg = "unexpected end of JSON input"
170163
}
171164
return scanError
172165
}
@@ -588,7 +581,7 @@ func stateError(s *scanner, c byte) int {
588581
// error records an error and switches to the error state.
589582
func (s *scanner) error(c byte, context string) int {
590583
s.step = stateError
591-
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
584+
s.errMsg = "invalid character " + quoteChar(c) + " " + context
592585
return scanError
593586
}
594587

src/encoding/json/scanner_test.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,18 +178,20 @@ func TestIndentBig(t *testing.T) {
178178
}
179179
}
180180

181-
type indentErrorTest struct {
181+
type errorTest struct {
182182
in string
183183
err error
184184
}
185185

186-
var indentErrorTests = []indentErrorTest{
186+
var errorTests = []errorTest{
187187
{`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}},
188188
{`{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}},
189+
{``, &SyntaxError{"unexpected end of JSON input", 0}},
190+
{` [ `, &SyntaxError{"unexpected end of JSON input", 5}},
189191
}
190192

191193
func TestIndentErrors(t *testing.T) {
192-
for i, tt := range indentErrorTests {
194+
for i, tt := range errorTests {
193195
slice := make([]uint8, 0)
194196
buf := bytes.NewBuffer(slice)
195197
if err := Indent(buf, []uint8(tt.in), "", ""); err != nil {
@@ -201,6 +203,17 @@ func TestIndentErrors(t *testing.T) {
201203
}
202204
}
203205

206+
func TestCompactErrors(t *testing.T) {
207+
for i, tt := range errorTests {
208+
var buf bytes.Buffer
209+
err := Compact(&buf, []byte(tt.in))
210+
if !reflect.DeepEqual(err, tt.err) {
211+
t.Errorf("#%d: Compact: expected %#v, got: %#v", i, tt.err, err)
212+
continue
213+
}
214+
}
215+
}
216+
204217
func diff(t *testing.T, a, b []byte) {
205218
for i := 0; ; i++ {
206219
if i >= len(a) || i >= len(b) || a[i] != b[i] {

src/encoding/json/stream.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,8 @@ Input:
9999
// Look in the buffer for a new value.
100100
for ; scanp < len(dec.buf); scanp++ {
101101
c := dec.buf[scanp]
102-
dec.scan.bytes++
103102
switch dec.scan.step(&dec.scan, c) {
104103
case scanEnd:
105-
// scanEnd is delayed one byte so we decrement
106-
// the scanner bytes count by 1 to ensure that
107-
// this value is correct in the next call of Decode.
108-
dec.scan.bytes--
109104
break Input
110105
case scanEndObject, scanEndArray:
111106
// scanEnd is delayed one byte.
@@ -116,8 +111,8 @@ Input:
116111
break Input
117112
}
118113
case scanError:
119-
dec.err = dec.scan.err
120-
return 0, dec.scan.err
114+
dec.err = &SyntaxError{msg: dec.scan.errMsg, Offset: dec.scanned + int64(scanp) + 1}
115+
return 0, dec.err
121116
}
122117
}
123118

src/encoding/json/stream_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,14 @@ var tokenStreamCases = []tokenStreamCase{
397397
}},
398398
{json: `{ "\a" }`, expTokens: []interface{}{
399399
Delim('{'),
400-
&SyntaxError{"invalid character 'a' in string escape code", 3},
400+
&SyntaxError{"invalid character 'a' in string escape code", 5},
401401
}},
402402
{json: ` \a`, expTokens: []interface{}{
403-
&SyntaxError{"invalid character '\\\\' looking for beginning of value", 1},
403+
&SyntaxError{"invalid character '\\\\' looking for beginning of value", 2},
404+
}},
405+
{json: `{ } [] nil`, expTokens: []interface{}{
406+
Delim('{'), Delim('}'), Delim('['), Delim(']'),
407+
&SyntaxError{"invalid character 'i' in literal null (expecting 'u')", 9},
404408
}},
405409
}
406410

0 commit comments

Comments
 (0)