aboutsummaryrefslogtreecommitdiff
path: root/src/encoding/json
diff options
context:
space:
mode:
authorLars Grote <lgr@softwerke.com>2026-02-25 12:07:25 +0100
committerCherry Mui <cherryyz@google.com>2026-03-06 12:26:10 -0800
commitde5c138eef88685442dc71e36dd98d66b885a605 (patch)
tree29f20a547c423a846a4fccaef8248b72c0954c33 /src/encoding/json
parent8933fb98f309443a5ca3aeda2163017737dbe795 (diff)
downloadgo-de5c138eef88685442dc71e36dd98d66b885a605.tar.xz
encoding/json: unwrap IO errors from SyntacticError in transformSyntacticError
When GOEXPERIMENT=jsonv2 is enabled, transformSyntacticError has a case for bare IO errors (export.IsIOError), but this case is never reached when the IO error is wrapped inside a *jsontext.SyntacticError. This happens because consumeObject wraps errors with wrapWithObjectName before they reach ReadValue, which then wraps them in *SyntacticError via wrapSyntacticError. The result is that transformSyntacticError matches the SyntacticError case first, converts it to a v1 *SyntaxError using only the message string, and discards the underlying IO error chain. This breaks errors.As for callers checking IO errors such as *http.MaxBytesError, which is a common pattern for returning HTTP 413 on oversized request bodies. Fix by checking whether the SyntacticError wraps an IO error and unwrapping it directly, matching v1 behavior which returned IO errors without wrapping. Fixes #77789 Change-Id: Idad84a006a0905b4a20125f676634e1000fb5f48 Reviewed-on: https://go-review.googlesource.com/c/go/+/748860 Reviewed-by: Joseph Tsai <joetsai@digital-static.net> Reviewed-by: Cherry Mui <cherryyz@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com>
Diffstat (limited to 'src/encoding/json')
-rw-r--r--src/encoding/json/v2_scanner.go5
-rw-r--r--src/encoding/json/v2_stream_test.go24
2 files changed, 29 insertions, 0 deletions
diff --git a/src/encoding/json/v2_scanner.go b/src/encoding/json/v2_scanner.go
index aef045f466..f08a0bcce3 100644
--- a/src/encoding/json/v2_scanner.go
+++ b/src/encoding/json/v2_scanner.go
@@ -56,6 +56,11 @@ var errUnexpectedEnd = errors.New("unexpected end of JSON input")
func transformSyntacticError(err error) error {
switch serr, ok := err.(*jsontext.SyntacticError); {
case serr != nil:
+ // If the SyntacticError wraps an IO error, unwrap it
+ // to match v1 behavior which returned IO errors directly.
+ if export.IsIOError(serr.Err) {
+ return errors.Unwrap(serr.Err)
+ }
if serr.Err == io.ErrUnexpectedEOF {
serr.Err = errUnexpectedEnd
}
diff --git a/src/encoding/json/v2_stream_test.go b/src/encoding/json/v2_stream_test.go
index d7f9f11084..b185b46acb 100644
--- a/src/encoding/json/v2_stream_test.go
+++ b/src/encoding/json/v2_stream_test.go
@@ -8,6 +8,7 @@ package json
import (
"bytes"
+ "errors"
"io"
"log"
"net"
@@ -613,3 +614,26 @@ func TestDecoderInputOffset(t *testing.T) {
t.Fatal("unconsumed testdata")
}
}
+
+func TestDecoderMaxBytesError(t *testing.T) {
+ // Verify that Decoder.Decode returns the underlying IO error
+ // (not wrapped in *SyntaxError) when http.MaxBytesReader
+ // triggers a read limit, matching v1 behavior.
+ oversized := strings.Repeat("x", 1<<20+1)
+ body := `{"name":"` + oversized + `"}`
+
+ req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
+ rec := httptest.NewRecorder()
+ req.Body = http.MaxBytesReader(rec, req.Body, 1<<20)
+
+ var v map[string]any
+ err := NewDecoder(req.Body).Decode(&v)
+ if err == nil {
+ t.Fatal("expected error, got nil")
+ }
+
+ var maxBytesErr *http.MaxBytesError
+ if !errors.As(err, &maxBytesErr) {
+ t.Errorf("errors.As(err, *http.MaxBytesError) = false, want true\nerror type: %T\nerror: %v", err, err)
+ }
+}