diff options
Diffstat (limited to 'libgo/go/encoding/gob')
-rw-r--r-- | libgo/go/encoding/gob/codec_test.go | 15 | ||||
-rw-r--r-- | libgo/go/encoding/gob/decode.go | 2 | ||||
-rw-r--r-- | libgo/go/encoding/gob/decoder.go | 37 | ||||
-rw-r--r-- | libgo/go/encoding/gob/encode.go | 23 | ||||
-rw-r--r-- | libgo/go/encoding/gob/encoder.go | 6 | ||||
-rw-r--r-- | libgo/go/encoding/gob/encoder_test.go | 77 | ||||
-rw-r--r-- | libgo/go/encoding/gob/type.go | 3 | ||||
-rw-r--r-- | libgo/go/encoding/gob/type_test.go | 61 |
8 files changed, 209 insertions, 15 deletions
diff --git a/libgo/go/encoding/gob/codec_test.go b/libgo/go/encoding/gob/codec_test.go index ebcbb78ebe6..482212b7467 100644 --- a/libgo/go/encoding/gob/codec_test.go +++ b/libgo/go/encoding/gob/codec_test.go @@ -7,6 +7,7 @@ package gob import ( "bytes" "errors" + "flag" "math" "math/rand" "reflect" @@ -16,6 +17,8 @@ import ( "unsafe" ) +var doFuzzTests = flag.Bool("gob.fuzz", false, "run the fuzz tests, which are large and very slow") + // Guarantee encoding format by comparing some encodings to hand-written values type EncodeT struct { x uint64 @@ -1434,7 +1437,8 @@ func encFuzzDec(rng *rand.Rand, in interface{}) error { // This does some "fuzz testing" by attempting to decode a sequence of random bytes. func TestFuzz(t *testing.T) { - if testing.Short() { + if !*doFuzzTests { + t.Logf("disabled; run with -gob.fuzz to enable") return } @@ -1453,11 +1457,16 @@ func TestFuzz(t *testing.T) { } func TestFuzzRegressions(t *testing.T) { + if !*doFuzzTests { + t.Logf("disabled; run with -gob.fuzz to enable") + return + } + // An instance triggering a type name of length ~102 GB. testFuzz(t, 1328492090837718000, 100, new(float32)) // An instance triggering a type name of 1.6 GB. - // Commented out because it takes 5m to run. - //testFuzz(t, 1330522872628565000, 100, new(int)) + // Note: can take several minutes to run. + testFuzz(t, 1330522872628565000, 100, new(int)) } func testFuzz(t *testing.T, seed int64, n int, input ...interface{}) { diff --git a/libgo/go/encoding/gob/decode.go b/libgo/go/encoding/gob/decode.go index 8690b35d714..900c69ddb47 100644 --- a/libgo/go/encoding/gob/decode.go +++ b/libgo/go/encoding/gob/decode.go @@ -717,7 +717,9 @@ func (dec *Decoder) decodeInterface(ityp reflect.Type, state *decoderState, p ui errorf("name too long (%d bytes): %.20q...", len(name), name) } // The concrete type must be registered. + registerLock.RLock() typ, ok := nameToConcreteType[name] + registerLock.RUnlock() if !ok { errorf("name not registered for interface: %q", name) } diff --git a/libgo/go/encoding/gob/decoder.go b/libgo/go/encoding/gob/decoder.go index c5c7d3fdb10..04f706ca540 100644 --- a/libgo/go/encoding/gob/decoder.go +++ b/libgo/go/encoding/gob/decoder.go @@ -87,21 +87,38 @@ func (dec *Decoder) recvMessage() bool { // readMessage reads the next nbytes bytes from the input. func (dec *Decoder) readMessage(nbytes int) { - // Allocate the buffer. - if cap(dec.tmp) < nbytes { - dec.tmp = make([]byte, nbytes+100) // room to grow + // Allocate the dec.tmp buffer, up to 10KB. + const maxBuf = 10 * 1024 + nTmp := nbytes + if nTmp > maxBuf { + nTmp = maxBuf } - dec.tmp = dec.tmp[:nbytes] + if cap(dec.tmp) < nTmp { + nAlloc := nTmp + 100 // A little extra for growth. + if nAlloc > maxBuf { + nAlloc = maxBuf + } + dec.tmp = make([]byte, nAlloc) + } + dec.tmp = dec.tmp[:nTmp] // Read the data - _, dec.err = io.ReadFull(dec.r, dec.tmp) - if dec.err != nil { - if dec.err == io.EOF { - dec.err = io.ErrUnexpectedEOF + dec.buf.Grow(nbytes) + for nbytes > 0 { + if nbytes < nTmp { + dec.tmp = dec.tmp[:nbytes] } - return + var nRead int + nRead, dec.err = io.ReadFull(dec.r, dec.tmp) + if dec.err != nil { + if dec.err == io.EOF { + dec.err = io.ErrUnexpectedEOF + } + return + } + dec.buf.Write(dec.tmp) + nbytes -= nRead } - dec.buf.Write(dec.tmp) } // toInt turns an encoded uint64 into an int, according to the marshaling rules. diff --git a/libgo/go/encoding/gob/encode.go b/libgo/go/encoding/gob/encode.go index 168e08b137a..ea37a6cbd58 100644 --- a/libgo/go/encoding/gob/encode.go +++ b/libgo/go/encoding/gob/encode.go @@ -426,6 +426,12 @@ func (enc *Encoder) encodeMap(b *bytes.Buffer, mv reflect.Value, keyOp, elemOp e // by the concrete value. A nil value gets sent as the empty string for the name, // followed by no value. func (enc *Encoder) encodeInterface(b *bytes.Buffer, iv reflect.Value) { + // Gobs can encode nil interface values but not typed interface + // values holding nil pointers, since nil pointers point to no value. + elem := iv.Elem() + if elem.Kind() == reflect.Ptr && elem.IsNil() { + errorf("gob: cannot encode nil pointer of type %s inside interface", iv.Elem().Type()) + } state := enc.newEncoderState(b) state.fieldnum = -1 state.sendZero = true @@ -435,7 +441,9 @@ func (enc *Encoder) encodeInterface(b *bytes.Buffer, iv reflect.Value) { } ut := userType(iv.Elem().Type()) + registerLock.RLock() name, ok := concreteTypeToName[ut.base] + registerLock.RUnlock() if !ok { errorf("type not registered for interface: %s", ut.base) } @@ -454,7 +462,7 @@ func (enc *Encoder) encodeInterface(b *bytes.Buffer, iv reflect.Value) { enc.pushWriter(b) data := new(bytes.Buffer) data.Write(spaceForLength) - enc.encode(data, iv.Elem(), ut) + enc.encode(data, elem, ut) if enc.err != nil { error_(enc.err) } @@ -698,9 +706,20 @@ func (enc *Encoder) getEncEngine(ut *userTypeInfo) *encEngine { error_(err1) } if info.encoder == nil { - // mark this engine as underway before compiling to handle recursive types. + // Assign the encEngine now, so recursive types work correctly. But... info.encoder = new(encEngine) + // ... if we fail to complete building the engine, don't cache the half-built machine. + // Doing this here means we won't cache a type that is itself OK but + // that contains a nested type that won't compile. The result is consistent + // error behavior when Encode is called multiple times on the top-level type. + ok := false + defer func() { + if !ok { + info.encoder = nil + } + }() info.encoder = enc.compileEnc(ut) + ok = true } return info.encoder } diff --git a/libgo/go/encoding/gob/encoder.go b/libgo/go/encoding/gob/encoder.go index a15b5a1f9a1..51444bb5269 100644 --- a/libgo/go/encoding/gob/encoder.go +++ b/libgo/go/encoding/gob/encoder.go @@ -218,6 +218,12 @@ func (enc *Encoder) sendTypeId(state *encoderState, ut *userTypeInfo) { // EncodeValue transmits the data item represented by the reflection value, // guaranteeing that all necessary type information has been transmitted first. func (enc *Encoder) EncodeValue(value reflect.Value) error { + // Gobs contain values. They cannot represent nil pointers, which + // have no value to encode. + if value.Kind() == reflect.Ptr && value.IsNil() { + panic("gob: cannot encode nil pointer of type " + value.Type().String()) + } + // Make sure we're single-threaded through here, so multiple // goroutines can share an encoder. enc.mutex.Lock() diff --git a/libgo/go/encoding/gob/encoder_test.go b/libgo/go/encoding/gob/encoder_test.go index db824d99917..b684772c691 100644 --- a/libgo/go/encoding/gob/encoder_test.go +++ b/libgo/go/encoding/gob/encoder_test.go @@ -737,6 +737,83 @@ func TestPtrToMapOfMap(t *testing.T) { } } +// A top-level nil pointer generates a panic with a helpful string-valued message. +func TestTopLevelNilPointer(t *testing.T) { + errMsg := topLevelNilPanic(t) + if errMsg == "" { + t.Fatal("top-level nil pointer did not panic") + } + if !strings.Contains(errMsg, "nil pointer") { + t.Fatal("expected nil pointer error, got:", errMsg) + } +} + +func topLevelNilPanic(t *testing.T) (panicErr string) { + defer func() { + e := recover() + if err, ok := e.(string); ok { + panicErr = err + } + }() + var ip *int + buf := new(bytes.Buffer) + if err := NewEncoder(buf).Encode(ip); err != nil { + t.Fatal("error in encode:", err) + } + return +} + +func TestNilPointerInsideInterface(t *testing.T) { + var ip *int + si := struct { + I interface{} + }{ + I: ip, + } + buf := new(bytes.Buffer) + err := NewEncoder(buf).Encode(si) + if err == nil { + t.Fatal("expected error, got none") + } + errMsg := err.Error() + if !strings.Contains(errMsg, "nil pointer") || !strings.Contains(errMsg, "interface") { + t.Fatal("expected error about nil pointer and interface, got:", errMsg) + } +} + +type Bug4Public struct { + Name string + Secret Bug4Secret +} + +type Bug4Secret struct { + a int // error: no exported fields. +} + +// Test that a failed compilation doesn't leave around an executable encoder. +// Issue 3273. +func TestMutipleEncodingsOfBadType(t *testing.T) { + x := Bug4Public{ + Name: "name", + Secret: Bug4Secret{1}, + } + buf := new(bytes.Buffer) + enc := NewEncoder(buf) + err := enc.Encode(x) + if err == nil { + t.Fatal("first encoding: expected error") + } + buf.Reset() + enc = NewEncoder(buf) + err = enc.Encode(x) + if err == nil { + t.Fatal("second encoding: expected error") + } + if !strings.Contains(err.Error(), "no exported fields") { + t.Errorf("expected error about no exported fields; got %v", err) + } +} + // There was an error check comparing the length of the input with the // length of the slice being decoded. It was wrong because the next // thing in the input might be a type definition, which would lead to diff --git a/libgo/go/encoding/gob/type.go b/libgo/go/encoding/gob/type.go index a8ee2fa4a5a..ea0db4eac45 100644 --- a/libgo/go/encoding/gob/type.go +++ b/libgo/go/encoding/gob/type.go @@ -712,6 +712,7 @@ type GobDecoder interface { } var ( + registerLock sync.RWMutex nameToConcreteType = make(map[string]reflect.Type) concreteTypeToName = make(map[reflect.Type]string) ) @@ -723,6 +724,8 @@ func RegisterName(name string, value interface{}) { // reserved for nil panic("attempt to register empty name") } + registerLock.Lock() + defer registerLock.Unlock() ut := userType(reflect.TypeOf(value)) // Check for incompatible duplicates. The name must refer to the // same user type, and vice versa. diff --git a/libgo/go/encoding/gob/type_test.go b/libgo/go/encoding/gob/type_test.go index 42bdb4cf7bb..e230d22d431 100644 --- a/libgo/go/encoding/gob/type_test.go +++ b/libgo/go/encoding/gob/type_test.go @@ -5,6 +5,7 @@ package gob import ( + "bytes" "reflect" "testing" ) @@ -159,3 +160,63 @@ func TestRegistration(t *testing.T) { Register(new(T)) Register(new(T)) } + +type N1 struct{} +type N2 struct{} + +// See comment in type.go/Register. +func TestRegistrationNaming(t *testing.T) { + testCases := []struct { + t interface{} + name string + }{ + {&N1{}, "*gob.N1"}, + {N2{}, "encoding/gob.N2"}, + } + + for _, tc := range testCases { + Register(tc.t) + + tct := reflect.TypeOf(tc.t) + registerLock.RLock() + ct := nameToConcreteType[tc.name] + registerLock.RUnlock() + if ct != tct { + t.Errorf("nameToConcreteType[%q] = %v, want %v", tc.name, ct, tct) + } + // concreteTypeToName is keyed off the base type. + if tct.Kind() == reflect.Ptr { + tct = tct.Elem() + } + if n := concreteTypeToName[tct]; n != tc.name { + t.Errorf("concreteTypeToName[%v] got %v, want %v", tct, n, tc.name) + } + } +} + +func TestStressParallel(t *testing.T) { + type T2 struct{ A int } + c := make(chan bool) + const N = 10 + for i := 0; i < N; i++ { + go func() { + p := new(T2) + Register(p) + b := new(bytes.Buffer) + enc := NewEncoder(b) + err := enc.Encode(p) + if err != nil { + t.Error("encoder fail:", err) + } + dec := NewDecoder(b) + err = dec.Decode(p) + if err != nil { + t.Error("decoder fail:", err) + } + c <- true + }() + } + for i := 0; i < N; i++ { + <-c + } +} |