encoding/json: fix extra data regression under goexperiment.jsonv2

When operating under v1 semantics in the v2 implementation,
a extra data error should take precedence over any semantic error
that could theoretically occur within the value itself.

This change only affects code compiled under goexperiment.jsonv2.

Fixes #74614

Change-Id: I055a606b053fa66b0c766ae205487b8290109285
Reviewed-on: https://go-review.googlesource.com/c/go/+/689919
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Joe Tsai 2025-07-24 12:16:35 -07:00 committed by Gopher Robot
parent a6eec8bdc7
commit 3636ced112
2 changed files with 24 additions and 19 deletions

View file

@ -776,7 +776,8 @@ func (d *decoderState) ReadValue(flags *jsonwire.ValueFlags) (Value, error) {
// CheckNextValue checks whether the next value is syntactically valid, // CheckNextValue checks whether the next value is syntactically valid,
// but does not advance the read offset. // but does not advance the read offset.
func (d *decoderState) CheckNextValue() error { // If last, it verifies that the stream cleanly terminates with [io.EOF].
func (d *decoderState) CheckNextValue(last bool) error {
d.PeekKind() // populates d.peekPos and d.peekErr d.PeekKind() // populates d.peekPos and d.peekErr
pos, err := d.peekPos, d.peekErr pos, err := d.peekPos, d.peekErr
d.peekPos, d.peekErr = 0, nil d.peekPos, d.peekErr = 0, nil
@ -787,13 +788,18 @@ func (d *decoderState) CheckNextValue() error {
var flags jsonwire.ValueFlags var flags jsonwire.ValueFlags
if pos, err := d.consumeValue(&flags, pos, d.Tokens.Depth()); err != nil { if pos, err := d.consumeValue(&flags, pos, d.Tokens.Depth()); err != nil {
return wrapSyntacticError(d, err, pos, +1) return wrapSyntacticError(d, err, pos, +1)
} else if last {
return d.checkEOF(pos)
} }
return nil return nil
} }
// CheckEOF verifies that the input has no more data. // CheckEOF verifies that the input has no more data.
func (d *decoderState) CheckEOF() error { func (d *decoderState) CheckEOF() error {
switch pos, err := d.consumeWhitespace(d.prevEnd); err { return d.checkEOF(d.prevEnd)
}
func (d *decoderState) checkEOF(pos int) error {
switch pos, err := d.consumeWhitespace(pos); err {
case nil: case nil:
err := jsonwire.NewInvalidCharacterError(d.buf[pos:], "after top-level value") err := jsonwire.NewInvalidCharacterError(d.buf[pos:], "after top-level value")
return wrapSyntacticError(d, err, pos, 0) return wrapSyntacticError(d, err, pos, 0)

View file

@ -409,7 +409,7 @@ func Unmarshal(in []byte, out any, opts ...Options) (err error) {
dec := export.GetBufferedDecoder(in, opts...) dec := export.GetBufferedDecoder(in, opts...)
defer export.PutBufferedDecoder(dec) defer export.PutBufferedDecoder(dec)
xd := export.Decoder(dec) xd := export.Decoder(dec)
err = unmarshalFull(dec, out, &xd.Struct) err = unmarshalDecode(dec, out, &xd.Struct, true)
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err) return internal.TransformUnmarshalError(out, err)
} }
@ -426,25 +426,13 @@ func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error) {
dec := export.GetStreamingDecoder(in, opts...) dec := export.GetStreamingDecoder(in, opts...)
defer export.PutStreamingDecoder(dec) defer export.PutStreamingDecoder(dec)
xd := export.Decoder(dec) xd := export.Decoder(dec)
err = unmarshalFull(dec, out, &xd.Struct) err = unmarshalDecode(dec, out, &xd.Struct, true)
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err) return internal.TransformUnmarshalError(out, err)
} }
return err return err
} }
func unmarshalFull(in *jsontext.Decoder, out any, uo *jsonopts.Struct) error {
switch err := unmarshalDecode(in, out, uo); err {
case nil:
return export.Decoder(in).CheckEOF()
case io.EOF:
offset := in.InputOffset() + int64(len(in.UnreadBuffer()))
return &jsontext.SyntacticError{ByteOffset: offset, Err: io.ErrUnexpectedEOF}
default:
return err
}
}
// UnmarshalDecode deserializes a Go value from a [jsontext.Decoder] according to // UnmarshalDecode deserializes a Go value from a [jsontext.Decoder] according to
// the provided unmarshal options (while ignoring marshal, encode, or decode options). // the provided unmarshal options (while ignoring marshal, encode, or decode options).
// Any unmarshal options already specified on the [jsontext.Decoder] // Any unmarshal options already specified on the [jsontext.Decoder]
@ -463,14 +451,14 @@ func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) (err error)
defer func() { xd.Struct = optsOriginal }() defer func() { xd.Struct = optsOriginal }()
xd.Struct.JoinWithoutCoderOptions(opts...) xd.Struct.JoinWithoutCoderOptions(opts...)
} }
err = unmarshalDecode(in, out, &xd.Struct) err = unmarshalDecode(in, out, &xd.Struct, false)
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err) return internal.TransformUnmarshalError(out, err)
} }
return err return err
} }
func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err error) { func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct, last bool) (err error) {
v := reflect.ValueOf(out) v := reflect.ValueOf(out)
if v.Kind() != reflect.Pointer || v.IsNil() { if v.Kind() != reflect.Pointer || v.IsNil() {
return &SemanticError{action: "unmarshal", GoType: reflect.TypeOf(out), Err: internal.ErrNonNilReference} return &SemanticError{action: "unmarshal", GoType: reflect.TypeOf(out), Err: internal.ErrNonNilReference}
@ -481,7 +469,11 @@ func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err er
// In legacy semantics, the entirety of the next JSON value // In legacy semantics, the entirety of the next JSON value
// was validated before attempting to unmarshal it. // was validated before attempting to unmarshal it.
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err := export.Decoder(in).CheckNextValue(); err != nil { if err := export.Decoder(in).CheckNextValue(last); err != nil {
if err == io.EOF {
offset := in.InputOffset() + int64(len(in.UnreadBuffer()))
return &jsontext.SyntacticError{ByteOffset: offset, Err: io.ErrUnexpectedEOF}
}
return err return err
} }
} }
@ -495,8 +487,15 @@ func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err er
if !uo.Flags.Get(jsonflags.AllowDuplicateNames) { if !uo.Flags.Get(jsonflags.AllowDuplicateNames) {
export.Decoder(in).Tokens.InvalidateDisabledNamespaces() export.Decoder(in).Tokens.InvalidateDisabledNamespaces()
} }
if err == io.EOF {
offset := in.InputOffset() + int64(len(in.UnreadBuffer()))
return &jsontext.SyntacticError{ByteOffset: offset, Err: io.ErrUnexpectedEOF}
}
return err return err
} }
if last {
return export.Decoder(in).CheckEOF()
}
return nil return nil
} }