]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json: Include the offset of a SyntaxError
authorMichael Fraenkel <michael.fraenkel@gmail.com>
Sun, 29 Oct 2017 00:50:57 +0000 (20:50 -0400)
committerJoe Tsai <joetsai@google.com>
Tue, 31 Oct 2017 22:44:14 +0000 (22:44 +0000)
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>

src/encoding/json/stream.go
src/encoding/json/stream_test.go

index f6b62c4cf634f92aa460dfb15a50031361309558..75a4270df7de1c018b6b79907d1395032b894fcb 100644 (file)
@@ -12,12 +12,13 @@ import (
 
 // A Decoder reads and decodes JSON values from an input stream.
 type Decoder struct {
-       r     io.Reader
-       buf   []byte
-       d     decodeState
-       scanp int // start of unread data in buf
-       scan  scanner
-       err   error
+       r       io.Reader
+       buf     []byte
+       d       decodeState
+       scanp   int   // start of unread data in buf
+       scanned int64 // amount of data already scanned
+       scan    scanner
+       err     error
 
        tokenState int
        tokenStack []int
@@ -55,7 +56,7 @@ func (dec *Decoder) Decode(v interface{}) error {
        }
 
        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.
@@ -140,6 +141,7 @@ func (dec *Decoder) refill() error {
        // Make room to read more into the buffer.
        // First slide down data already consumed.
        if dec.scanp > 0 {
+               dec.scanned += int64(dec.scanp)
                n := copy(dec.buf, dec.buf[dec.scanp:])
                dec.buf = dec.buf[:n]
                dec.scanp = 0
@@ -306,7 +308,7 @@ func (dec *Decoder) tokenPrepareForDecode() error {
                        return err
                }
                if c != ',' {
-                       return &SyntaxError{"expected comma after array element", 0}
+                       return &SyntaxError{"expected comma after array element", dec.offset()}
                }
                dec.scanp++
                dec.tokenState = tokenArrayValue
@@ -316,7 +318,7 @@ func (dec *Decoder) tokenPrepareForDecode() error {
                        return err
                }
                if c != ':' {
-                       return &SyntaxError{"expected colon after object key", 0}
+                       return &SyntaxError{"expected colon after object key", dec.offset()}
                }
                dec.scanp++
                dec.tokenState = tokenObjectValue
@@ -433,7 +435,6 @@ func (dec *Decoder) Token() (Token, error) {
                                err := dec.Decode(&x)
                                dec.tokenState = old
                                if err != nil {
-                                       clearOffset(err)
                                        return nil, err
                                }
                                dec.tokenState = tokenObjectColon
@@ -447,7 +448,6 @@ func (dec *Decoder) Token() (Token, error) {
                        }
                        var x interface{}
                        if err := dec.Decode(&x); err != nil {
-                               clearOffset(err)
                                return nil, err
                        }
                        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) {
        var context string
        switch dec.tokenState {
@@ -477,7 +471,7 @@ func (dec *Decoder) tokenError(c byte) (Token, error) {
        case tokenObjectComma:
                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
@@ -505,3 +499,7 @@ func (dec *Decoder) peek() (byte, error) {
                err = dec.refill()
        }
 }
+
+func (dec *Decoder) offset() int64 {
+       return dec.scanned + int64(dec.scanp)
+}
index d0b3ffbce93a921c01897cceccdb3e0c33a49299..83c01d170c0b3405ae52b233f345f4144ec54b72 100644 (file)
@@ -342,11 +342,18 @@ var tokenStreamCases []tokenStreamCase = []tokenStreamCase{
        {json: ` [{"a": 1} {"a": 2}] `, expTokens: []interface{}{
                Delim('['),
                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{}{
-               Delim('{'), "a",
-               decodeThis{&SyntaxError{"expected colon after object key", 0}},
+       {json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []interface{}{
+               Delim('{'), strings.Repeat("a", 513),
+               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()
                        }
                        if experr, ok := etk.(error); ok {
-                               if err == nil || err.Error() != experr.Error() {
-                                       t.Errorf("case %v: Expected error %v in %q, but was %v", ci, experr, tcase.json, err)
+                               if err == nil || !reflect.DeepEqual(err, experr) {
+                                       t.Errorf("case %v: Expected error %#v in %q, but was %#v", ci, experr, tcase.json, err)
                                }
                                break
                        } else if err == io.EOF {
                                t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json)
                                break
                        } 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
                        }
                        if !reflect.DeepEqual(tk, etk) {