Commit 3050a0a7 authored by Rob Pike's avatar Rob Pike

encoding/gob: remove unsafe, use reflection.

This removes a major unsafe thorn in our side, a perennial obstacle
to clean garbage collection.
Not coincidentally: In cleaning this up, several bugs were found,
including code that reached inside by-value interfaces to create
pointers for pointer-receiver methods. Unsafe code is just as
advertised.

Performance of course suffers, but not too badly. The Pipe number
is more indicative, since it's doing I/O that simulates a network
connection. Plus these are end-to-end, so each end suffers
only half of this pain.

The edit is pretty much a line-by-line conversion, with a few
simplifications and a couple of new tests. There may be more
performance to gain.

BenchmarkEndToEndByteBuffer     2493          3033          +21.66%
BenchmarkEndToEndPipe           4953          5597          +13.00%

Fixes #5159.

LGTM=rsc
R=rsc
CC=golang-codereviews, khr
https://golang.org/cl/102680045
parent 3e692bec
This diff is collapsed.
...@@ -306,7 +306,7 @@ func (deb *debugger) common() CommonType { ...@@ -306,7 +306,7 @@ func (deb *debugger) common() CommonType {
// Id typeId // Id typeId
id = deb.typeId() id = deb.typeId()
default: default:
errorf("corrupted CommonType") errorf("corrupted CommonType, delta is %d fieldNum is %d", delta, fieldNum)
} }
} }
return CommonType{name, id} return CommonType{name, id}
...@@ -598,11 +598,11 @@ func (deb *debugger) printBuiltin(indent tab, id typeId) { ...@@ -598,11 +598,11 @@ func (deb *debugger) printBuiltin(indent tab, id typeId) {
fmt.Fprintf(os.Stderr, "%s%d\n", indent, x) fmt.Fprintf(os.Stderr, "%s%d\n", indent, x)
case tFloat: case tFloat:
x := deb.uint64() x := deb.uint64()
fmt.Fprintf(os.Stderr, "%s%g\n", indent, floatFromBits(x)) fmt.Fprintf(os.Stderr, "%s%g\n", indent, float64FromBits(x))
case tComplex: case tComplex:
r := deb.uint64() r := deb.uint64()
i := deb.uint64() i := deb.uint64()
fmt.Fprintf(os.Stderr, "%s%g+%gi\n", indent, floatFromBits(r), floatFromBits(i)) fmt.Fprintf(os.Stderr, "%s%g+%gi\n", indent, float64FromBits(r), float64FromBits(i))
case tBytes: case tBytes:
x := int(deb.uint64()) x := int(deb.uint64())
b := make([]byte, x) b := make([]byte, x)
......
This diff is collapsed.
This diff is collapsed.
...@@ -13,6 +13,52 @@ import ( ...@@ -13,6 +13,52 @@ import (
"testing" "testing"
) )
// Test basic operations in a safe manner.
func TestBasicEncoderDecoder(t *testing.T) {
var values = []interface{}{
true,
int(123),
int8(123),
int16(-12345),
int32(123456),
int64(-1234567),
uint(123),
uint8(123),
uint16(12345),
uint32(123456),
uint64(1234567),
uintptr(12345678),
float32(1.2345),
float64(1.2345678),
complex64(1.2345 + 2.3456i),
complex128(1.2345678 + 2.3456789i),
[]byte("hello"),
string("hello"),
}
for _, value := range values {
b := new(bytes.Buffer)
enc := NewEncoder(b)
err := enc.Encode(value)
if err != nil {
t.Error("encoder fail:", err)
}
dec := NewDecoder(b)
result := reflect.New(reflect.TypeOf(value))
err = dec.Decode(result.Interface())
if err != nil {
t.Fatalf("error decoding %T: %v:", reflect.TypeOf(value), err)
}
if !reflect.DeepEqual(value, result.Elem().Interface()) {
t.Fatalf("%T: expected %v got %v", value, value, result.Elem().Interface())
}
}
}
type ET0 struct {
A int
B string
}
type ET2 struct { type ET2 struct {
X string X string
} }
...@@ -40,14 +86,40 @@ type ET4 struct { ...@@ -40,14 +86,40 @@ type ET4 struct {
func TestEncoderDecoder(t *testing.T) { func TestEncoderDecoder(t *testing.T) {
b := new(bytes.Buffer) b := new(bytes.Buffer)
enc := NewEncoder(b) enc := NewEncoder(b)
et0 := new(ET0)
et0.A = 7
et0.B = "gobs of fun"
err := enc.Encode(et0)
if err != nil {
t.Error("encoder fail:", err)
}
//fmt.Printf("% x %q\n", b, b)
//Debug(b)
dec := NewDecoder(b)
newEt0 := new(ET0)
err = dec.Decode(newEt0)
if err != nil {
t.Fatal("error decoding ET0:", err)
}
if !reflect.DeepEqual(et0, newEt0) {
t.Fatalf("invalid data for et0: expected %+v; got %+v", *et0, *newEt0)
}
if b.Len() != 0 {
t.Error("not at eof;", b.Len(), "bytes left")
}
// t.FailNow()
b = new(bytes.Buffer)
enc = NewEncoder(b)
et1 := new(ET1) et1 := new(ET1)
et1.A = 7 et1.A = 7
et1.Et2 = new(ET2) et1.Et2 = new(ET2)
err := enc.Encode(et1) err = enc.Encode(et1)
if err != nil { if err != nil {
t.Error("encoder fail:", err) t.Error("encoder fail:", err)
} }
dec := NewDecoder(b) dec = NewDecoder(b)
newEt1 := new(ET1) newEt1 := new(ET1)
err = dec.Decode(newEt1) err = dec.Decode(newEt1)
if err != nil { if err != nil {
......
...@@ -279,7 +279,7 @@ func TestGobEncoderValueField(t *testing.T) { ...@@ -279,7 +279,7 @@ func TestGobEncoderValueField(t *testing.T) {
b := new(bytes.Buffer) b := new(bytes.Buffer)
// First a field that's a structure. // First a field that's a structure.
enc := NewEncoder(b) enc := NewEncoder(b)
err := enc.Encode(GobTestValueEncDec{17, StringStruct{"HIJKL"}}) err := enc.Encode(&GobTestValueEncDec{17, StringStruct{"HIJKL"}})
if err != nil { if err != nil {
t.Fatal("encode error:", err) t.Fatal("encode error:", err)
} }
...@@ -326,7 +326,7 @@ func TestGobEncoderArrayField(t *testing.T) { ...@@ -326,7 +326,7 @@ func TestGobEncoderArrayField(t *testing.T) {
for i := range a.A.a { for i := range a.A.a {
a.A.a[i] = byte(i) a.A.a[i] = byte(i)
} }
err := enc.Encode(a) err := enc.Encode(&a)
if err != nil { if err != nil {
t.Fatal("encode error:", err) t.Fatal("encode error:", err)
} }
...@@ -589,7 +589,8 @@ func TestGobEncoderStructSingleton(t *testing.T) { ...@@ -589,7 +589,8 @@ func TestGobEncoderStructSingleton(t *testing.T) {
func TestGobEncoderNonStructSingleton(t *testing.T) { func TestGobEncoderNonStructSingleton(t *testing.T) {
b := new(bytes.Buffer) b := new(bytes.Buffer)
enc := NewEncoder(b) enc := NewEncoder(b)
err := enc.Encode(Gobber(1234)) var g Gobber = 1234
err := enc.Encode(&g)
if err != nil { if err != nil {
t.Fatal("encode error:", err) t.Fatal("encode error:", err)
} }
......
...@@ -23,7 +23,7 @@ func benchmarkEndToEnd(r io.Reader, w io.Writer, b *testing.B) { ...@@ -23,7 +23,7 @@ func benchmarkEndToEnd(r io.Reader, w io.Writer, b *testing.B) {
b.StopTimer() b.StopTimer()
enc := NewEncoder(w) enc := NewEncoder(w)
dec := NewDecoder(r) dec := NewDecoder(r)
bench := &Bench{7, 3.2, "now is the time", []byte("for all good men")} bench := &Bench{7, 3.2, "now is the time", bytes.Repeat([]byte("for all good men"), 100)}
b.StartTimer() b.StartTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if enc.Encode(bench) != nil { if enc.Encode(bench) != nil {
...@@ -103,7 +103,7 @@ func TestCountDecodeMallocs(t *testing.T) { ...@@ -103,7 +103,7 @@ func TestCountDecodeMallocs(t *testing.T) {
t.Fatal("decode:", err) t.Fatal("decode:", err)
} }
}) })
if allocs != 3 { if allocs != 4 {
t.Fatalf("mallocs per decode of type Bench: %v; wanted 3\n", allocs) t.Fatalf("mallocs per decode of type Bench: %v; wanted 4\n", allocs)
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment