update
This commit is contained in:
177
src/labgob/labgob.go
Normal file
177
src/labgob/labgob.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package labgob
|
||||
|
||||
//
|
||||
// trying to send non-capitalized fields over RPC produces a range of
|
||||
// misbehavior, including both mysterious incorrect computation and
|
||||
// outright crashes. so this wrapper around Go's encoding/gob warns
|
||||
// about non-capitalized field names.
|
||||
//
|
||||
|
||||
import "encoding/gob"
|
||||
import "io"
|
||||
import "reflect"
|
||||
import "fmt"
|
||||
import "sync"
|
||||
import "unicode"
|
||||
import "unicode/utf8"
|
||||
|
||||
var mu sync.Mutex
|
||||
var errorCount int // for TestCapital
|
||||
var checked map[reflect.Type]bool
|
||||
|
||||
type LabEncoder struct {
|
||||
gob *gob.Encoder
|
||||
}
|
||||
|
||||
func NewEncoder(w io.Writer) *LabEncoder {
|
||||
enc := &LabEncoder{}
|
||||
enc.gob = gob.NewEncoder(w)
|
||||
return enc
|
||||
}
|
||||
|
||||
func (enc *LabEncoder) Encode(e interface{}) error {
|
||||
checkValue(e)
|
||||
return enc.gob.Encode(e)
|
||||
}
|
||||
|
||||
func (enc *LabEncoder) EncodeValue(value reflect.Value) error {
|
||||
checkValue(value.Interface())
|
||||
return enc.gob.EncodeValue(value)
|
||||
}
|
||||
|
||||
type LabDecoder struct {
|
||||
gob *gob.Decoder
|
||||
}
|
||||
|
||||
func NewDecoder(r io.Reader) *LabDecoder {
|
||||
dec := &LabDecoder{}
|
||||
dec.gob = gob.NewDecoder(r)
|
||||
return dec
|
||||
}
|
||||
|
||||
func (dec *LabDecoder) Decode(e interface{}) error {
|
||||
checkValue(e)
|
||||
checkDefault(e)
|
||||
return dec.gob.Decode(e)
|
||||
}
|
||||
|
||||
func Register(value interface{}) {
|
||||
checkValue(value)
|
||||
gob.Register(value)
|
||||
}
|
||||
|
||||
func RegisterName(name string, value interface{}) {
|
||||
checkValue(value)
|
||||
gob.RegisterName(name, value)
|
||||
}
|
||||
|
||||
func checkValue(value interface{}) {
|
||||
checkType(reflect.TypeOf(value))
|
||||
}
|
||||
|
||||
func checkType(t reflect.Type) {
|
||||
k := t.Kind()
|
||||
|
||||
mu.Lock()
|
||||
// only complain once, and avoid recursion.
|
||||
if checked == nil {
|
||||
checked = map[reflect.Type]bool{}
|
||||
}
|
||||
if checked[t] {
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
checked[t] = true
|
||||
mu.Unlock()
|
||||
|
||||
switch k {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
rune, _ := utf8.DecodeRuneInString(f.Name)
|
||||
if unicode.IsUpper(rune) == false {
|
||||
// ta da
|
||||
fmt.Printf("labgob error: lower-case field %v of %v in RPC or persist/snapshot will break your Raft\n",
|
||||
f.Name, t.Name())
|
||||
mu.Lock()
|
||||
errorCount += 1
|
||||
mu.Unlock()
|
||||
}
|
||||
checkType(f.Type)
|
||||
}
|
||||
return
|
||||
case reflect.Slice, reflect.Array, reflect.Ptr:
|
||||
checkType(t.Elem())
|
||||
return
|
||||
case reflect.Map:
|
||||
checkType(t.Elem())
|
||||
checkType(t.Key())
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// warn if the value contains non-default values,
|
||||
// as it would if one sent an RPC but the reply
|
||||
// struct was already modified. if the RPC reply
|
||||
// contains default values, GOB won't overwrite
|
||||
// the non-default value.
|
||||
//
|
||||
func checkDefault(value interface{}) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
checkDefault1(reflect.ValueOf(value), 1, "")
|
||||
}
|
||||
|
||||
func checkDefault1(value reflect.Value, depth int, name string) {
|
||||
if depth > 3 {
|
||||
return
|
||||
}
|
||||
|
||||
t := value.Type()
|
||||
k := t.Kind()
|
||||
|
||||
switch k {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
vv := value.Field(i)
|
||||
name1 := t.Field(i).Name
|
||||
if name != "" {
|
||||
name1 = name + "." + name1
|
||||
}
|
||||
checkDefault1(vv, depth+1, name1)
|
||||
}
|
||||
return
|
||||
case reflect.Ptr:
|
||||
if value.IsNil() {
|
||||
return
|
||||
}
|
||||
checkDefault1(value.Elem(), depth+1, name)
|
||||
return
|
||||
case reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Uintptr, reflect.Float32, reflect.Float64,
|
||||
reflect.String:
|
||||
if reflect.DeepEqual(reflect.Zero(t).Interface(), value.Interface()) == false {
|
||||
mu.Lock()
|
||||
if errorCount < 1 {
|
||||
what := name
|
||||
if what == "" {
|
||||
what = t.Name()
|
||||
}
|
||||
// this warning typically arises if code re-uses the same RPC reply
|
||||
// variable for multiple RPC calls, or if code restores persisted
|
||||
// state into variable that already have non-default values.
|
||||
fmt.Printf("labgob warning: Decoding into a non-default variable/field %v may not work\n",
|
||||
what)
|
||||
}
|
||||
errorCount += 1
|
||||
mu.Unlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
172
src/labgob/test_test.go
Normal file
172
src/labgob/test_test.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package labgob
|
||||
|
||||
import "testing"
|
||||
|
||||
import "bytes"
|
||||
|
||||
type T1 struct {
|
||||
T1int0 int
|
||||
T1int1 int
|
||||
T1string0 string
|
||||
T1string1 string
|
||||
}
|
||||
|
||||
type T2 struct {
|
||||
T2slice []T1
|
||||
T2map map[int]*T1
|
||||
T2t3 interface{}
|
||||
}
|
||||
|
||||
type T3 struct {
|
||||
T3int999 int
|
||||
}
|
||||
|
||||
//
|
||||
// test that we didn't break GOB.
|
||||
//
|
||||
func TestGOB(t *testing.T) {
|
||||
e0 := errorCount
|
||||
|
||||
w := new(bytes.Buffer)
|
||||
|
||||
Register(T3{})
|
||||
|
||||
{
|
||||
x0 := 0
|
||||
x1 := 1
|
||||
t1 := T1{}
|
||||
t1.T1int1 = 1
|
||||
t1.T1string1 = "6.824"
|
||||
t2 := T2{}
|
||||
t2.T2slice = []T1{T1{}, t1}
|
||||
t2.T2map = map[int]*T1{}
|
||||
t2.T2map[99] = &T1{1, 2, "x", "y"}
|
||||
t2.T2t3 = T3{999}
|
||||
|
||||
e := NewEncoder(w)
|
||||
e.Encode(x0)
|
||||
e.Encode(x1)
|
||||
e.Encode(t1)
|
||||
e.Encode(t2)
|
||||
}
|
||||
data := w.Bytes()
|
||||
|
||||
{
|
||||
var x0 int
|
||||
var x1 int
|
||||
var t1 T1
|
||||
var t2 T2
|
||||
|
||||
r := bytes.NewBuffer(data)
|
||||
d := NewDecoder(r)
|
||||
if d.Decode(&x0) != nil ||
|
||||
d.Decode(&x1) != nil ||
|
||||
d.Decode(&t1) != nil ||
|
||||
d.Decode(&t2) != nil {
|
||||
t.Fatalf("Decode failed")
|
||||
}
|
||||
|
||||
if x0 != 0 {
|
||||
t.Fatalf("wrong x0 %v\n", x0)
|
||||
}
|
||||
if x1 != 1 {
|
||||
t.Fatalf("wrong x1 %v\n", x1)
|
||||
}
|
||||
if t1.T1int0 != 0 {
|
||||
t.Fatalf("wrong t1.T1int0 %v\n", t1.T1int0)
|
||||
}
|
||||
if t1.T1int1 != 1 {
|
||||
t.Fatalf("wrong t1.T1int1 %v\n", t1.T1int1)
|
||||
}
|
||||
if t1.T1string0 != "" {
|
||||
t.Fatalf("wrong t1.T1string0 %v\n", t1.T1string0)
|
||||
}
|
||||
if t1.T1string1 != "6.824" {
|
||||
t.Fatalf("wrong t1.T1string1 %v\n", t1.T1string1)
|
||||
}
|
||||
if len(t2.T2slice) != 2 {
|
||||
t.Fatalf("wrong t2.T2slice len %v\n", len(t2.T2slice))
|
||||
}
|
||||
if t2.T2slice[1].T1int1 != 1 {
|
||||
t.Fatalf("wrong slice value\n")
|
||||
}
|
||||
if len(t2.T2map) != 1 {
|
||||
t.Fatalf("wrong t2.T2map len %v\n", len(t2.T2map))
|
||||
}
|
||||
if t2.T2map[99].T1string1 != "y" {
|
||||
t.Fatalf("wrong map value\n")
|
||||
}
|
||||
t3 := (t2.T2t3).(T3)
|
||||
if t3.T3int999 != 999 {
|
||||
t.Fatalf("wrong t2.T2t3.T3int999\n")
|
||||
}
|
||||
}
|
||||
|
||||
if errorCount != e0 {
|
||||
t.Fatalf("there were errors, but should not have been")
|
||||
}
|
||||
}
|
||||
|
||||
type T4 struct {
|
||||
Yes int
|
||||
no int
|
||||
}
|
||||
|
||||
//
|
||||
// make sure we check capitalization
|
||||
// labgob prints one warning during this test.
|
||||
//
|
||||
func TestCapital(t *testing.T) {
|
||||
e0 := errorCount
|
||||
|
||||
v := []map[*T4]int{}
|
||||
|
||||
w := new(bytes.Buffer)
|
||||
e := NewEncoder(w)
|
||||
e.Encode(v)
|
||||
data := w.Bytes()
|
||||
|
||||
var v1 []map[T4]int
|
||||
r := bytes.NewBuffer(data)
|
||||
d := NewDecoder(r)
|
||||
d.Decode(&v1)
|
||||
|
||||
if errorCount != e0+1 {
|
||||
t.Fatalf("failed to warn about lower-case field")
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// check that we warn when someone sends a default value over
|
||||
// RPC but the target into which we're decoding holds a non-default
|
||||
// value, which GOB seems not to overwrite as you'd expect.
|
||||
//
|
||||
// labgob does not print a warning.
|
||||
//
|
||||
func TestDefault(t *testing.T) {
|
||||
e0 := errorCount
|
||||
|
||||
type DD struct {
|
||||
X int
|
||||
}
|
||||
|
||||
// send a default value...
|
||||
dd1 := DD{}
|
||||
|
||||
w := new(bytes.Buffer)
|
||||
e := NewEncoder(w)
|
||||
e.Encode(dd1)
|
||||
data := w.Bytes()
|
||||
|
||||
// and receive it into memory that already
|
||||
// holds non-default values.
|
||||
reply := DD{99}
|
||||
|
||||
r := bytes.NewBuffer(data)
|
||||
d := NewDecoder(r)
|
||||
d.Decode(&reply)
|
||||
|
||||
if errorCount != e0+1 {
|
||||
t.Fatalf("failed to warn about decoding into non-default value")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user