encoding/json: Include the offset of a SyntaxError

When a SyntaxError occurs, report the current offset within the stream.
The code already accounted for the offset within the current buffer
being scanned. By including how much data was already scanned, the
current offset can be computed.

Fixes #22478

Change-Id: I91ecd4cad0b85a5c1556bc597f3ee914e769af01
Reviewed-on: https://go-review.googlesource.com/74251
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Michael Fraenkel 2017-10-28 20:50:57 -04:00 committed by Joe Tsai
parent 6fac139830
commit f1ce59d988
2 changed files with 30 additions and 25 deletions

View File

@ -16,6 +16,7 @@ type Decoder struct {
buf []byte buf []byte
d decodeState d decodeState
scanp int // start of unread data in buf scanp int // start of unread data in buf
scanned int64 // amount of data already scanned
scan scanner scan scanner
err error err error
@ -55,7 +56,7 @@ func (dec *Decoder) Decode(v interface{}) error {
} }
if !dec.tokenValueAllowed() { if !dec.tokenValueAllowed() {
return &SyntaxError{msg: "not at beginning of value"} return &SyntaxError{msg: "not at beginning of value", Offset: dec.offset()}
} }
// Read whole value into buffer. // Read whole value into buffer.
@ -140,6 +141,7 @@ func (dec *Decoder) refill() error {
// Make room to read more into the buffer. // Make room to read more into the buffer.
// First slide down data already consumed. // First slide down data already consumed.
if dec.scanp > 0 { if dec.scanp > 0 {
dec.scanned += int64(dec.scanp)
n := copy(dec.buf, dec.buf[dec.scanp:]) n := copy(dec.buf, dec.buf[dec.scanp:])
dec.buf = dec.buf[:n] dec.buf = dec.buf[:n]
dec.scanp = 0 dec.scanp = 0
@ -306,7 +308,7 @@ func (dec *Decoder) tokenPrepareForDecode() error {
return err return err
} }
if c != ',' { if c != ',' {
return &SyntaxError{"expected comma after array element", 0} return &SyntaxError{"expected comma after array element", dec.offset()}
} }
dec.scanp++ dec.scanp++
dec.tokenState = tokenArrayValue dec.tokenState = tokenArrayValue
@ -316,7 +318,7 @@ func (dec *Decoder) tokenPrepareForDecode() error {
return err return err
} }
if c != ':' { if c != ':' {
return &SyntaxError{"expected colon after object key", 0} return &SyntaxError{"expected colon after object key", dec.offset()}
} }
dec.scanp++ dec.scanp++
dec.tokenState = tokenObjectValue dec.tokenState = tokenObjectValue
@ -433,7 +435,6 @@ func (dec *Decoder) Token() (Token, error) {
err := dec.Decode(&x) err := dec.Decode(&x)
dec.tokenState = old dec.tokenState = old
if err != nil { if err != nil {
clearOffset(err)
return nil, err return nil, err
} }
dec.tokenState = tokenObjectColon dec.tokenState = tokenObjectColon
@ -447,7 +448,6 @@ func (dec *Decoder) Token() (Token, error) {
} }
var x interface{} var x interface{}
if err := dec.Decode(&x); err != nil { if err := dec.Decode(&x); err != nil {
clearOffset(err)
return nil, err return nil, err
} }
return x, nil return x, nil
@ -455,12 +455,6 @@ func (dec *Decoder) Token() (Token, error) {
} }
} }
func clearOffset(err error) {
if s, ok := err.(*SyntaxError); ok {
s.Offset = 0
}
}
func (dec *Decoder) tokenError(c byte) (Token, error) { func (dec *Decoder) tokenError(c byte) (Token, error) {
var context string var context string
switch dec.tokenState { switch dec.tokenState {
@ -477,7 +471,7 @@ func (dec *Decoder) tokenError(c byte) (Token, error) {
case tokenObjectComma: case tokenObjectComma:
context = " after object key:value pair" context = " after object key:value pair"
} }
return nil, &SyntaxError{"invalid character " + quoteChar(c) + " " + context, 0} return nil, &SyntaxError{"invalid character " + quoteChar(c) + " " + context, dec.offset()}
} }
// More reports whether there is another element in the // More reports whether there is another element in the
@ -505,3 +499,7 @@ func (dec *Decoder) peek() (byte, error) {
err = dec.refill() err = dec.refill()
} }
} }
func (dec *Decoder) offset() int64 {
return dec.scanned + int64(dec.scanp)
}

View File

@ -342,11 +342,18 @@ var tokenStreamCases []tokenStreamCase = []tokenStreamCase{
{json: ` [{"a": 1} {"a": 2}] `, expTokens: []interface{}{ {json: ` [{"a": 1} {"a": 2}] `, expTokens: []interface{}{
Delim('['), Delim('['),
decodeThis{map[string]interface{}{"a": float64(1)}}, decodeThis{map[string]interface{}{"a": float64(1)}},
decodeThis{&SyntaxError{"expected comma after array element", 0}}, decodeThis{&SyntaxError{"expected comma after array element", 11}},
}}, }},
{json: `{ "a" 1 }`, expTokens: []interface{}{ {json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []interface{}{
Delim('{'), "a", Delim('{'), strings.Repeat("a", 513),
decodeThis{&SyntaxError{"expected colon after object key", 0}}, decodeThis{&SyntaxError{"expected colon after object key", 518}},
}},
{json: `{ "\a" }`, expTokens: []interface{}{
Delim('{'),
&SyntaxError{"invalid character 'a' in string escape code", 3},
}},
{json: ` \a`, expTokens: []interface{}{
&SyntaxError{"invalid character '\\\\' looking for beginning of value", 1},
}}, }},
} }
@ -367,15 +374,15 @@ func TestDecodeInStream(t *testing.T) {
tk, err = dec.Token() tk, err = dec.Token()
} }
if experr, ok := etk.(error); ok { if experr, ok := etk.(error); ok {
if err == nil || err.Error() != experr.Error() { if err == nil || !reflect.DeepEqual(err, experr) {
t.Errorf("case %v: Expected error %v in %q, but was %v", ci, experr, tcase.json, err) t.Errorf("case %v: Expected error %#v in %q, but was %#v", ci, experr, tcase.json, err)
} }
break break
} else if err == io.EOF { } else if err == io.EOF {
t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json) t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json)
break break
} else if err != nil { } else if err != nil {
t.Errorf("case %v: Unexpected error '%v' in %q", ci, err, tcase.json) t.Errorf("case %v: Unexpected error '%#v' in %q", ci, err, tcase.json)
break break
} }
if !reflect.DeepEqual(tk, etk) { if !reflect.DeepEqual(tk, etk) {