diff options
| author | Lars Grote <lgr@softwerke.com> | 2026-02-25 12:07:25 +0100 |
|---|---|---|
| committer | Cherry Mui <cherryyz@google.com> | 2026-03-06 12:26:10 -0800 |
| commit | de5c138eef88685442dc71e36dd98d66b885a605 (patch) | |
| tree | 29f20a547c423a846a4fccaef8248b72c0954c33 /src/encoding | |
| parent | 8933fb98f309443a5ca3aeda2163017737dbe795 (diff) | |
| download | go-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')
| -rw-r--r-- | src/encoding/json/v2_scanner.go | 5 | ||||
| -rw-r--r-- | src/encoding/json/v2_stream_test.go | 24 |
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) + } +} |
