diff options
author | Daniel Martí <mvdan@mvdan.cc> | 2021-10-11 11:12:38 +0100 |
---|---|---|
committer | Daniel Martí <mvdan@mvdan.cc> | 2021-10-11 21:58:33 +0000 |
commit | d973bb107e9142cf17e4a7f2666a71ed2d457e91 (patch) | |
tree | 5ce64142a65ea2c3b8f634ec32788ee0e1fe84c4 /src/encoding | |
parent | 70235351263caee1fd9840d1d652bf28778cd51d (diff) | |
download | go-d973bb107e9142cf17e4a7f2666a71ed2d457e91.tar.gz go-d973bb107e9142cf17e4a7f2666a71ed2d457e91.zip |
encoding/gob: follow documented io.EOF semantics
The docs say:
If the input is at EOF, Decode returns io.EOF and does not modify e.
However, the added test fails:
--- FAIL: TestDecodePartial (0.00s)
encoder_test.go:1263: 31/81: expected io.ErrUnexpectedEOF: EOF
encoder_test.go:1263: 51/81: expected io.ErrUnexpectedEOF: EOF
In particular, the decoder would return io.EOF after reading a valid
message for a type specification, and then hit EOF before reading a data
item message.
Fix that by only allowing a Decode call to return io.EOF if the reader
hits EOF immediately, without successfully reading any message.
Otherwise, hitting EOF is an ErrUnexpectedEOF, like in other cases.
Also fix a net/rpc test that, coincidentally, expected an io.EOF
as an error when feeding bad non-zero data to a gob decoder.
An io.ErrUnexpectedEOF is clearly better in that scenario.
Fixes #48905.
Change-Id: Ied6a0d8ac8377f89646319a18c0380c4f2b09b85
Reviewed-on: https://go-review.googlesource.com/c/go/+/354972
Trust: Daniel Martí <mvdan@mvdan.cc>
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
Diffstat (limited to 'src/encoding')
-rw-r--r-- | src/encoding/gob/decoder.go | 9 | ||||
-rw-r--r-- | src/encoding/gob/encoder_test.go | 63 |
2 files changed, 72 insertions, 0 deletions
diff --git a/src/encoding/gob/decoder.go b/src/encoding/gob/decoder.go index b476aaac93..5e4ed5a7d7 100644 --- a/src/encoding/gob/decoder.go +++ b/src/encoding/gob/decoder.go @@ -138,9 +138,17 @@ func (dec *Decoder) nextUint() uint64 { // decoded. If this is an interface value, it can be ignored by // resetting that buffer. func (dec *Decoder) decodeTypeSequence(isInterface bool) typeId { + firstMessage := true for dec.err == nil { if dec.buf.Len() == 0 { if !dec.recvMessage() { + // We can only return io.EOF if the input was empty. + // If we read one or more type spec messages, + // require a data item message to follow. + // If we hit an EOF before that, then give ErrUnexpectedEOF. + if !firstMessage && dec.err == io.EOF { + dec.err = io.ErrUnexpectedEOF + } break } } @@ -166,6 +174,7 @@ func (dec *Decoder) decodeTypeSequence(isInterface bool) typeId { } dec.nextUint() } + firstMessage = false } return -1 } diff --git a/src/encoding/gob/encoder_test.go b/src/encoding/gob/encoder_test.go index 6d50b82573..a358d5bc30 100644 --- a/src/encoding/gob/encoder_test.go +++ b/src/encoding/gob/encoder_test.go @@ -1202,3 +1202,66 @@ func TestMarshalFloatMap(t *testing.T) { t.Fatalf("\nEncode: %v\nDecode: %v", want, got) } } + +func TestDecodePartial(t *testing.T) { + type T struct { + X []int + Y string + } + + var buf bytes.Buffer + t1 := T{X: []int{1, 2, 3}, Y: "foo"} + t2 := T{X: []int{4, 5, 6}, Y: "bar"} + enc := NewEncoder(&buf) + + t1start := 0 + if err := enc.Encode(&t1); err != nil { + t.Fatal(err) + } + + t2start := buf.Len() + if err := enc.Encode(&t2); err != nil { + t.Fatal(err) + } + + data := buf.Bytes() + for i := 0; i <= len(data); i++ { + bufr := bytes.NewReader(data[:i]) + + // Decode both values, stopping at the first error. + var t1b, t2b T + dec := NewDecoder(bufr) + var err error + err = dec.Decode(&t1b) + if err == nil { + err = dec.Decode(&t2b) + } + + switch i { + case t1start, t2start: + // Either the first or the second Decode calls had zero input. + if err != io.EOF { + t.Errorf("%d/%d: expected io.EOF: %v", i, len(data), err) + } + case len(data): + // We reached the end of the entire input. + if err != nil { + t.Errorf("%d/%d: unexpected error: %v", i, len(data), err) + } + if !reflect.DeepEqual(t1b, t1) { + t.Fatalf("t1 value mismatch: got %v, want %v", t1b, t1) + } + if !reflect.DeepEqual(t2b, t2) { + t.Fatalf("t2 value mismatch: got %v, want %v", t2b, t2) + } + default: + // In between, we must see io.ErrUnexpectedEOF. + // The decoder used to erroneously return io.EOF in some cases here, + // such as if the input was cut off right after some type specs, + // but before any value was actually transmitted. + if err != io.ErrUnexpectedEOF { + t.Errorf("%d/%d: expected io.ErrUnexpectedEOF: %v", i, len(data), err) + } + } + } +} |