]> git.feebdaed.xyz Git - 0xmirror/go.git/commitdiff
encoding/json: report true from v2 Decoder.More when an error is pending
authorDamien Neil <dneil@google.com>
Mon, 8 Dec 2025 21:09:05 +0000 (13:09 -0800)
committerGopher Robot <gobot@golang.org>
Thu, 11 Dec 2025 17:34:26 +0000 (09:34 -0800)
Historically, Decoder.More reports true when the next read will
return an error. Adjust the v2 Decoder to follow this behavior.

Fixes #76467

Change-Id: I03bfa391e4e89ada8ca869db43c1d0bb63cc0413
Reviewed-on: https://go-review.googlesource.com/c/go/+/728300
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/encoding/json/stream_test.go
src/encoding/json/v2_stream.go
src/encoding/json/v2_stream_test.go

index 0e937cfaa13c783387a8468a5a0ecc6ccff62f98..ba3bc4002b1ceb25c8cc148cf2d1d519fff2adb0 100644 (file)
@@ -459,6 +459,9 @@ func TestDecodeInStream(t *testing.T) {
                {CaseName: Name(""), json: ` \a`, expTokens: []any{
                        &SyntaxError{"invalid character '\\\\' looking for beginning of value", 1},
                }},
+               {CaseName: Name(""), json: `,`, expTokens: []any{
+                       &SyntaxError{"invalid character ',' looking for beginning of value", 0},
+               }},
        }
        for _, tt := range tests {
                t.Run(tt.Name, func(t *testing.T) {
@@ -467,6 +470,15 @@ func TestDecodeInStream(t *testing.T) {
                                var got any
                                var err error
 
+                               wantMore := true
+                               switch want {
+                               case Delim(']'), Delim('}'):
+                                       wantMore = false
+                               }
+                               if got := dec.More(); got != wantMore {
+                                       t.Fatalf("%s:\n\tinput: %s\n\tdec.More() = %v, want %v (next token: %T(%v))", tt.Where, tt.json, got, wantMore, want, want)
+                               }
+
                                if dt, ok := want.(decodeThis); ok {
                                        want = dt.v
                                        err = dec.Decode(&got)
index dcee553ee13336e20548d2324fd40c22e3357636..ca0822cb7336cba4d5ddd87b6c58d1475ac4cae4 100644 (file)
@@ -197,6 +197,9 @@ func (d Delim) String() string {
 // to mark the start and end of arrays and objects.
 // Commas and colons are elided.
 func (dec *Decoder) Token() (Token, error) {
+       if dec.err != nil {
+               return nil, dec.err
+       }
        tok, err := dec.dec.ReadToken()
        if err != nil {
                // Historically, v1 would report just [io.EOF] if
@@ -238,7 +241,20 @@ func (dec *Decoder) Token() (Token, error) {
 func (dec *Decoder) More() bool {
        dec.hadPeeked = true
        k := dec.dec.PeekKind()
-       return k > 0 && k != ']' && k != '}'
+       if k == 0 {
+               if dec.err == nil {
+                       // PeekKind doesn't distinguish between EOF and error,
+                       // so read the next token to see which we get.
+                       _, err := dec.dec.ReadToken()
+                       if err == nil {
+                               // This is only possible if jsontext violates its documentation.
+                               err = errors.New("json: successful read after failed peek")
+                       }
+                       dec.err = transformSyntacticError(err)
+               }
+               return dec.err != io.EOF
+       }
+       return k != ']' && k != '}'
 }
 
 // InputOffset returns the input stream byte offset of the current decoder position.
index 0885631fb5937fb663b6886a18b74c38a83ee83f..d7f9f110848add592652adb40f43eab909f3b640 100644 (file)
@@ -439,6 +439,9 @@ func TestDecodeInStream(t *testing.T) {
                {CaseName: Name(""), json: ` \a`, expTokens: []any{
                        &SyntaxError{"invalid character '\\\\' looking for beginning of value", len64(` `)},
                }},
+               {CaseName: Name(""), json: `,`, expTokens: []any{
+                       &SyntaxError{"invalid character ',' looking for beginning of value", 0},
+               }},
        }
        for _, tt := range tests {
                t.Run(tt.Name, func(t *testing.T) {
@@ -447,6 +450,15 @@ func TestDecodeInStream(t *testing.T) {
                                var got any
                                var err error
 
+                               wantMore := true
+                               switch want {
+                               case Delim(']'), Delim('}'):
+                                       wantMore = false
+                               }
+                               if got := dec.More(); got != wantMore {
+                                       t.Fatalf("%s:\n\tinput: %s\n\tdec.More() = %v, want %v (next token: %T(%v)) rem:%q", tt.Where, tt.json, got, wantMore, want, want, tt.json[dec.InputOffset():])
+                               }
+
                                if dt, ok := want.(decodeThis); ok {
                                        want = dt.v
                                        err = dec.Decode(&got)