Skip to content

Commit c76c3ab

Browse files
dsnetgopherbot
authored andcommitted
encoding/json: fix truncated Token error regression in goexperiment.jsonv2
The jsontext.Decoder.ReadToken method reports a non-EOF error, if the token stream is truncated and does not form a valid JSON value. In contrast, the v1 json.Decoder.Token method would report EOF so long as the input was a prefix of some valid JSON value. Modify json.Decoder.Token to preserve historical behavior. This only modifies code that is compiled in under goexperiment.jsonv2. Updates #69449 Fixes #74750 Change-Id: Ifd281c46f118f0e748076013fefc7659f77c56ed Reviewed-on: https://go-review.googlesource.com/c/go/+/689516 Reviewed-by: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Joseph Tsai <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent ebdbfcc commit c76c3ab

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

src/encoding/json/stream_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,3 +522,38 @@ func TestHTTPDecoding(t *testing.T) {
522522
t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err)
523523
}
524524
}
525+
526+
func TestTokenTruncation(t *testing.T) {
527+
tests := []struct {
528+
in string
529+
err error
530+
}{
531+
{in: ``, err: io.EOF},
532+
{in: `{`, err: io.EOF},
533+
{in: `{"`, err: io.ErrUnexpectedEOF},
534+
{in: `{"k"`, err: io.EOF},
535+
{in: `{"k":`, err: io.EOF},
536+
{in: `{"k",`, err: &SyntaxError{"invalid character ',' after object key", int64(len(`{"k"`))}},
537+
{in: `{"k"}`, err: &SyntaxError{"invalid character '}' after object key", int64(len(`{"k"`))}},
538+
{in: ` [0`, err: io.EOF},
539+
{in: `[0.`, err: io.ErrUnexpectedEOF},
540+
{in: `[0. `, err: &SyntaxError{"invalid character ' ' after decimal point in numeric literal", int64(len(`[0.`))}},
541+
{in: `[0,`, err: io.EOF},
542+
{in: `[0:`, err: &SyntaxError{"invalid character ':' after array element", int64(len(`[0`))}},
543+
{in: `n`, err: io.ErrUnexpectedEOF},
544+
{in: `nul`, err: io.ErrUnexpectedEOF},
545+
{in: `fal `, err: &SyntaxError{"invalid character ' ' in literal false (expecting 's')", int64(len(`fal `))}},
546+
{in: `false`, err: io.EOF},
547+
}
548+
for _, tt := range tests {
549+
d := NewDecoder(strings.NewReader(tt.in))
550+
for i := 0; true; i++ {
551+
if _, err := d.Token(); err != nil {
552+
if !reflect.DeepEqual(err, tt.err) {
553+
t.Errorf("`%s`: %d.Token error = %#v, want %v", tt.in, i, err, tt.err)
554+
}
555+
break
556+
}
557+
}
558+
}
559+
}

src/encoding/json/v2_stream.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package json
88

99
import (
1010
"bytes"
11+
"errors"
1112
"io"
1213

1314
"encoding/json/jsontext"
@@ -193,6 +194,16 @@ func (d Delim) String() string {
193194
func (dec *Decoder) Token() (Token, error) {
194195
tok, err := dec.dec.ReadToken()
195196
if err != nil {
197+
// Historically, v1 would report just [io.EOF] if
198+
// the stream is a prefix of a valid JSON value.
199+
// It reports an unwrapped [io.ErrUnexpectedEOF] if
200+
// truncated within a JSON token such as a literal, number, or string.
201+
if errors.Is(err, io.ErrUnexpectedEOF) {
202+
if len(bytes.Trim(dec.dec.UnreadBuffer(), " \r\n\t,:")) == 0 {
203+
return nil, io.EOF
204+
}
205+
return nil, io.ErrUnexpectedEOF
206+
}
196207
return nil, transformSyntacticError(err)
197208
}
198209
switch k := tok.Kind(); k {

src/encoding/json/v2_stream_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,3 +502,38 @@ func TestHTTPDecoding(t *testing.T) {
502502
t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err)
503503
}
504504
}
505+
506+
func TestTokenTruncation(t *testing.T) {
507+
tests := []struct {
508+
in string
509+
err error
510+
}{
511+
{in: ``, err: io.EOF},
512+
{in: `{`, err: io.EOF},
513+
{in: `{"`, err: io.ErrUnexpectedEOF},
514+
{in: `{"k"`, err: io.EOF},
515+
{in: `{"k":`, err: io.EOF},
516+
{in: `{"k",`, err: &SyntaxError{"invalid character ',' after object key", int64(len(`{"k"`))}},
517+
{in: `{"k"}`, err: &SyntaxError{"invalid character '}' after object key", int64(len(`{"k"`))}},
518+
{in: ` [0`, err: io.EOF},
519+
{in: `[0.`, err: io.ErrUnexpectedEOF},
520+
{in: `[0. `, err: &SyntaxError{"invalid character ' ' in numeric literal", int64(len(`[0.`))}},
521+
{in: `[0,`, err: io.EOF},
522+
{in: `[0:`, err: &SyntaxError{"invalid character ':' after array element", int64(len(`[0`))}},
523+
{in: `n`, err: io.ErrUnexpectedEOF},
524+
{in: `nul`, err: io.ErrUnexpectedEOF},
525+
{in: `fal `, err: &SyntaxError{"invalid character ' ' in literal false (expecting 's')", int64(len(`fal`))}},
526+
{in: `false`, err: io.EOF},
527+
}
528+
for _, tt := range tests {
529+
d := NewDecoder(strings.NewReader(tt.in))
530+
for i := 0; true; i++ {
531+
if _, err := d.Token(); err != nil {
532+
if !reflect.DeepEqual(err, tt.err) {
533+
t.Errorf("`%s`: %d.Token error = %#v, want %v", tt.in, i, err, tt.err)
534+
}
535+
break
536+
}
537+
}
538+
}
539+
}

0 commit comments

Comments
 (0)