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 } }