From 96b9efe8a96cb56be756861f337a9cfce822e25b Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Fri, 29 Oct 2010 15:07:56 -0700 Subject: [PATCH] gob: make the debugging (dumping) code work again. Mostly rewrite it, in fact. It's still not compiled in by default. R=rsc, r2 CC=golang-dev https://golang.org/cl/2754043 --- src/pkg/gob/codec_test.go | 47 +++++++ src/pkg/gob/debug.go | 286 +++++++++++++++++++++++++------------- src/pkg/gob/decoder.go | 7 +- 3 files changed, 239 insertions(+), 101 deletions(-) diff --git a/src/pkg/gob/codec_test.go b/src/pkg/gob/codec_test.go index 9c1815af9b7..1d9ac843889 100644 --- a/src/pkg/gob/codec_test.go +++ b/src/pkg/gob/codec_test.go @@ -1269,3 +1269,50 @@ func TestIgnoreInterface(t *testing.T) { t.Error("normal float did not decode correctly") } } + +// A type that won't be defined in the gob until we send it in an interface value. +type OnTheFly struct { + a int +} + +type DT struct { + // X OnTheFly + a int + b string + c float + i interface{} + j interface{} + i_nil interface{} + m map[string]int + r [3]int + s []string +} + +func TestDebug(t *testing.T) { + if debugFunc == nil { + return + } + Register(OnTheFly{}) + var dt DT + dt.a = 17 + dt.b = "hello" + dt.c = 3.14159 + dt.i = 271828 + dt.j = OnTheFly{3} + dt.i_nil = nil + dt.m = map[string]int{"one": 1, "two": 2} + dt.r = [3]int{11, 22, 33} + dt.s = []string{"hi", "joe"} + b := new(bytes.Buffer) + err := NewEncoder(b).Encode(dt) + if err != nil { + t.Fatal("encode:", err) + } + debugBuffer := bytes.NewBuffer(b.Bytes()) + dt2 := &DT{} + err = NewDecoder(b).Decode(&dt2) + if err != nil { + t.Error("decode:", err) + } + debugFunc(debugBuffer) +} diff --git a/src/pkg/gob/debug.go b/src/pkg/gob/debug.go index 0e07bb588e7..72645bdf075 100644 --- a/src/pkg/gob/debug.go +++ b/src/pkg/gob/debug.go @@ -6,152 +6,227 @@ import ( "bytes" "fmt" "io" - "log" "os" + "reflect" + "runtime" ) +var dump = false // If true, print the remaining bytes in the input buffer at each item. + +// Init installs the debugging facility. If this file is not compiled in the +// package, Debug will be a no-op. +func init() { + debugFunc = Debug +} + // Debug prints a human-readable representation of the gob data read from r. -func Debug(r io.Reader) { NewDecoder(r).debug() } +func Debug(r io.Reader) { + defer func() { + if e := recover(); e != nil { + if _, ok := e.(runtime.Error); ok { + panic(e) + } + fmt.Printf("error during debugging: %v\n", e) + } + }() + NewDecoder(r).debug() +} + +// debugRecv is like recv but prints what it sees. +func (dec *Decoder) debugRecv() { + if dec.byteBuffer != nil && dec.byteBuffer.Len() != 0 { + fmt.Printf("error in recv: %d bytes left in input buffer\n", dec.byteBuffer.Len()) + return + } + // Read a count. + var nbytes uint64 + nbytes, dec.err = decodeUintReader(dec.r, dec.countBuf[0:]) + if dec.err != nil { + fmt.Printf("receiver error on count: %s\n", dec.err) + return + } + // Allocate the buffer. + if nbytes > uint64(len(dec.buf)) { + dec.buf = make([]byte, nbytes+1000) + } + dec.byteBuffer = bytes.NewBuffer(dec.buf[0:nbytes]) + + // Read the data + _, dec.err = io.ReadFull(dec.r, dec.buf[0:nbytes]) + if dec.err != nil { + fmt.Printf("receiver error on data: %s\n", dec.err) + if dec.err == os.EOF { + dec.err = io.ErrUnexpectedEOF + } + return + } + if dump { + fmt.Printf("received %d bytes:\n\t% x\n", nbytes, dec.byteBuffer.Bytes()) + } +} + // debug is like Decode but just prints what it finds. It should be safe even for corrupted data. func (dec *Decoder) debug() { - dec.state.err = nil - for { - // Read a count. - var nbytes uint64 - nbytes, dec.state.err = decodeUintReader(dec.r, dec.countBuf[0:]) - if dec.state.err != nil { - break - } + // Make sure we're single-threaded through here. + dec.mutex.Lock() + defer dec.mutex.Unlock() - // Allocate the buffer. - if nbytes > uint64(len(dec.buf)) { - dec.buf = make([]byte, nbytes+1000) - } - dec.state.b = bytes.NewBuffer(dec.buf[0:nbytes]) - - // Read the data - _, dec.state.err = io.ReadFull(dec.r, dec.buf[0:nbytes]) - if dec.state.err != nil { - if dec.state.err == os.EOF { - dec.state.err = io.ErrUnexpectedEOF - } - break - } + dec.err = nil + dec.debugRecv() + if dec.err != nil { + return + } + dec.debugFromBuffer(0) +} +// printFromBuffer prints the next value. The buffer contains data, but it may +// be a type descriptor and we may need to load more data to see the value; +// printType takes care of that. +func (dec *Decoder) debugFromBuffer(indent int) { + for dec.state.b.Len() > 0 { // Receive a type id. id := typeId(decodeInt(dec.state)) - if dec.state.err != nil { - break - } // Is it a new type? if id < 0 { // 0 is the error state, handled above // If the id is negative, we have a type. - fmt.Printf("new type id %d\n", -id) - dec.printType(-id) - if dec.state.err != nil { + dec.debugRecvType(-id) + if dec.err != nil { break } continue } - fmt.Printf("type id %d\n", id) // No, it's a value. - // Make sure the type has been defined already. - _, ok := dec.wireType[id] - if !ok { - dec.state.err = errBadType + // Make sure the type has been defined already or is a builtin type (for + // top-level singleton values). + if dec.wireType[id] == nil && builtinIdToType[id] == nil { + dec.err = errBadType break } - fmt.Printf("\t%d bytes:\t% x\n", nbytes, dec.state.b.Bytes()) - dec.printData(0, id) + dec.debugPrint(indent, id) break } - if dec.state.err != nil { - log.Print("debug:", dec.state.err) - } } -func (dec *Decoder) printType(id typeId) { +func (dec *Decoder) debugRecvType(id typeId) { // Have we already seen this type? That's an error if _, alreadySeen := dec.wireType[id]; alreadySeen { - dec.state.err = os.ErrorString("gob: duplicate type received") + dec.err = os.ErrorString("gob: duplicate type received") return } // Type: wire := new(wireType) - dec.state.err = dec.decode(tWireType, wire) - if dec.state.err == nil { + dec.err = dec.decode(tWireType, reflect.NewValue(wire)) + if dec.err == nil { printWireType(wire) } // Remember we've seen this type. dec.wireType[id] = wire + + // Load the next parcel. + dec.debugRecv() } func printWireType(wire *wireType) { + fmt.Printf("type definition {\n") switch { - case wire.array != nil: - printCommonType("array", &wire.array.commonType) - fmt.Printf("\tlen %d\n\telemid %d\n", wire.array.Len, wire.array.Elem) - case wire.slice != nil: - printCommonType("slice", &wire.slice.commonType) - fmt.Printf("\telemid %d\n", wire.slice.Elem) - case wire.strct != nil: - printCommonType("struct", &wire.strct.commonType) - for i, field := range wire.strct.field { + case wire.arrayT != nil: + printCommonType("array", &wire.arrayT.commonType) + fmt.Printf("\tlen %d\n\telemid %d\n", wire.arrayT.Len, wire.arrayT.Elem) + case wire.mapT != nil: + printCommonType("map", &wire.mapT.commonType) + fmt.Printf("\tkeyid %d\n", wire.mapT.Key) + fmt.Printf("\telemid %d\n", wire.mapT.Elem) + case wire.sliceT != nil: + printCommonType("slice", &wire.sliceT.commonType) + fmt.Printf("\telemid %d\n", wire.sliceT.Elem) + case wire.structT != nil: + printCommonType("struct", &wire.structT.commonType) + for i, field := range wire.structT.field { fmt.Printf("\tfield %d:\t%s\tid=%d\n", i, field.name, field.id) } } + fmt.Printf("}\n") } func printCommonType(kind string, common *commonType) { - fmt.Printf("\t%s %s\n\tid: %d\n", kind, common.name, common._id) + fmt.Printf("\t%s %q\n\tid: %d\n", kind, common.name, common._id) } -func (dec *Decoder) printData(indent int, id typeId) { - if dec.state.err != nil { - return +func (dec *Decoder) debugPrint(indent int, id typeId) { + wire, ok := dec.wireType[id] + if ok && wire.structT != nil { + dec.debugStruct(indent+1, id, wire) + } else { + dec.debugSingle(indent+1, id, wire) } +} + +func (dec *Decoder) debugSingle(indent int, id typeId, wire *wireType) { // is it a builtin type? _, ok := builtinIdToType[id] + if !ok && wire == nil { + errorf("type id %d not defined\n", id) + } + decodeUint(dec.state) + dec.printItem(indent, id) +} + +func (dec *Decoder) printItem(indent int, id typeId) { + if dump { + fmt.Printf("print item %d bytes: % x\n", dec.state.b.Len(), dec.state.b.Bytes()) + } + _, ok := builtinIdToType[id] if ok { dec.printBuiltin(indent, id) return } wire, ok := dec.wireType[id] if !ok { - fmt.Printf("type id %d not defined\n", id) - return + errorf("type id %d not defined\n", id) } switch { - case wire.array != nil: - dec.printArray(indent+1, wire) - case wire.slice != nil: - dec.printSlice(indent+1, wire) - case wire.strct != nil: - dec.printStruct(indent+1, wire) + case wire.arrayT != nil: + dec.printArray(indent, wire) + case wire.mapT != nil: + dec.printMap(indent, wire) + case wire.sliceT != nil: + dec.printSlice(indent, wire) + case wire.structT != nil: + dec.debugStruct(indent, id, wire) } } func (dec *Decoder) printArray(indent int, wire *wireType) { - elemId := wire.array.Elem + elemId := wire.arrayT.Elem n := int(decodeUint(dec.state)) - for i := 0; i < n && dec.state.err == nil; i++ { - dec.printData(indent, elemId) + for i := 0; i < n && dec.err == nil; i++ { + dec.printItem(indent, elemId) } - if n != wire.array.Len { + if n != wire.arrayT.Len { tab(indent) - fmt.Printf("(wrong length for array: %d should be %d)\n", n, wire.array.Len) + fmt.Printf("(wrong length for array: %d should be %d)\n", n, wire.arrayT.Len) + } +} + +func (dec *Decoder) printMap(indent int, wire *wireType) { + keyId := wire.mapT.Key + elemId := wire.mapT.Elem + n := int(decodeUint(dec.state)) + for i := 0; i < n && dec.err == nil; i++ { + dec.printItem(indent, keyId) + dec.printItem(indent+1, elemId) } } func (dec *Decoder) printSlice(indent int, wire *wireType) { - elemId := wire.slice.Elem + elemId := wire.sliceT.Elem n := int(decodeUint(dec.state)) - for i := 0; i < n && dec.state.err == nil; i++ { - dec.printData(indent, elemId) + for i := 0; i < n && dec.err == nil; i++ { + dec.printItem(indent, elemId) } } @@ -160,57 +235,72 @@ func (dec *Decoder) printBuiltin(indent int, id typeId) { switch id { case tBool: if decodeInt(dec.state) == 0 { - fmt.Printf("false") + fmt.Printf("false\n") } else { - fmt.Printf("true") + fmt.Printf("true\n") } case tInt: - fmt.Printf("%d", decodeInt(dec.state)) + fmt.Printf("%d\n", decodeInt(dec.state)) case tUint: - fmt.Printf("%d", decodeUint(dec.state)) + fmt.Printf("%d\n", decodeUint(dec.state)) case tFloat: - fmt.Printf("%g", floatFromBits(decodeUint(dec.state))) + fmt.Printf("%g\n", floatFromBits(decodeUint(dec.state))) case tBytes: b := make([]byte, decodeUint(dec.state)) dec.state.b.Read(b) - fmt.Printf("% x", b) + fmt.Printf("% x\n", b) case tString: b := make([]byte, decodeUint(dec.state)) dec.state.b.Read(b) - fmt.Printf("%q", b) + fmt.Printf("%q\n", b) + case tInterface: + b := make([]byte, decodeUint(dec.state)) + dec.state.b.Read(b) + if len(b) == 0 { + fmt.Printf("nil interface") + } else { + fmt.Printf("interface value; type %q\n", b) + dec.debugFromBuffer(indent) + } default: - fmt.Print("unknown") + fmt.Print("unknown\n") } - fmt.Print("\n") } -func (dec *Decoder) printStruct(indent int, wire *wireType) { - strct := wire.strct +func (dec *Decoder) debugStruct(indent int, id typeId, wire *wireType) { + tab(indent) + fmt.Printf("%s struct {\n", id.Name()) + strct := wire.structT state := newDecodeState(dec.state.b) state.fieldnum = -1 - for state.err == nil { + for dec.err == nil { delta := int(decodeUint(state)) if delta < 0 { - dec.state.err = os.ErrorString("gob decode: corrupted data: negative delta") - return + errorf("gob decode: corrupted data: negative delta") } - if state.err != nil || delta == 0 { // struct terminator is zero delta fieldnum - return + if delta == 0 { // struct terminator is zero delta fieldnum + break } - fieldnum := state.fieldnum + delta - if fieldnum < 0 || fieldnum >= len(strct.field) { - dec.state.err = os.ErrorString("field number out of range") - return + fieldNum := state.fieldnum + delta + if fieldNum < 0 || fieldNum >= len(strct.field) { + errorf("field number out of range") + break } tab(indent) - fmt.Printf("field %d:\n", fieldnum) - dec.printData(indent+1, strct.field[fieldnum].id) - state.fieldnum = fieldnum + fmt.Printf("%s(%d):\n", wire.structT.field[fieldNum].name, fieldNum) + dec.printItem(indent+1, strct.field[fieldNum].id) + state.fieldnum = fieldNum } + tab(indent) + fmt.Printf(" } // end %s struct\n", id.Name()) } func tab(indent int) { - for i := 0; i < indent; i++ { - fmt.Print("\t") + for i, w := 0, 0; i < indent; i += w { + w = 10 + if i+w > indent { + w = indent - i + } + fmt.Print("\t\t\t\t\t\t\t\t\t\t"[:w]) } } diff --git a/src/pkg/gob/decoder.go b/src/pkg/gob/decoder.go index b86bdf3985e..4237d01aca3 100644 --- a/src/pkg/gob/decoder.go +++ b/src/pkg/gob/decoder.go @@ -108,9 +108,6 @@ func (dec *Decoder) decodeValueFromBuffer(value reflect.Value, ignore bool) { for dec.state.b.Len() > 0 { // Receive a type id. id := typeId(decodeInt(dec.state)) - if dec.err != nil { - break - } // Is it a new type? if id < 0 { // 0 is the error state, handled above @@ -155,3 +152,7 @@ func (dec *Decoder) DecodeValue(value reflect.Value) os.Error { dec.decodeValueFromBuffer(value, false) return dec.err } + +// If enabled, Debug prints a human-readable representation of the gob data read from r. +// If debug.go is compiled into the program it will override this link. +var debugFunc func(io.Reader)