diff --git a/spew/common.go b/spew/common.go index e452260..11b6d56 100644 --- a/spew/common.go +++ b/spew/common.go @@ -120,7 +120,7 @@ func catchPanic(w io.Writer, v reflect.Value) { func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { // We need an interface to check if the type implements the error or // Stringer interface. However, the reflect package won't give us an - // an interface on certain things like unexported struct fields in order + // interface on certain things like unexported struct fields in order // to enforce visibility rules. We use unsafe to bypass these restrictions // since this package does not mutate the values. if !v.CanInterface() { diff --git a/spew/dump.go b/spew/dump.go index 779bace..b47b5db 100644 --- a/spew/dump.go +++ b/spew/dump.go @@ -18,11 +18,13 @@ package spew import ( "bytes" + "encoding/hex" "fmt" "io" "os" "reflect" "strconv" + "strings" ) // dumpState contains information about the state of a dump operation. @@ -134,6 +136,50 @@ func (d *dumpState) dumpPtr(v reflect.Value) { d.w.Write(closeParenBytes) } +// dumpSlice handles formatting of arrays and slices. Byte (uint8 under +// reflection) arrays and slices are dumped in hexdump -C fashion. +func (d *dumpState) dumpSlice(v reflect.Value) { + // Handle byte (uint8 under reflection) arrays and slices uniquely. + numEntries := v.Len() + if (numEntries > 0) && (v.Index(0).Kind() == reflect.Uint8) { + // We need an addressable interface to convert the type back into a byte + // slice. However, the reflect package won't give us an interface on + // certain things like unexported struct fields in order to enforce + // visibility rules. We use unsafe to bypass these restrictions since + // this package does not mutate the values. + vs := v + if !vs.CanInterface() || !vs.CanAddr() { + vs = unsafeReflectValue(vs) + } + vs = vs.Slice(0, numEntries) + + // Type assert a uint8 slice and hexdump it. Also fix indentation + // based on the depth. + iface := vs.Interface() + if buf, ok := iface.([]uint8); ok { + indent := strings.Repeat(d.cs.Indent, d.depth) + str := indent + hex.Dump(buf) + str = strings.Replace(str, "\n", "\n"+indent, -1) + str = strings.TrimRight(str, d.cs.Indent) + d.w.Write([]byte(str)) + return + } + // We shouldn't ever get here, but the return is intentionally in the + // above if statement to ensure we fall through to normal behavior if + // the type assertion fails for some reason. + } + + // Recursively call dump for each item. + for i := 0; i < numEntries; i++ { + d.dump(d.unpackValue(v.Index(i))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } +} + // dump is the main workhorse for dumping a value. It uses the passed reflect // value to figure out what kind of object we are dealing with and formats it // appropriately. It is a recursive function, however circular data structures @@ -206,15 +252,7 @@ func (d *dumpState) dump(v reflect.Value) { d.indent() d.w.Write(maxNewlineBytes) } else { - numEntries := v.Len() - for i := 0; i < numEntries; i++ { - d.dump(d.unpackValue(v.Index(i))) - if i < (numEntries - 1) { - d.w.Write(commaNewlineBytes) - } else { - d.w.Write(newlineBytes) - } - } + d.dumpSlice(v) } d.depth-- d.indent()