| /* |
| * Copyright (c) 2013-2016 Dave Collins <dave@davec.name> |
| * |
| * Permission to use, copy, modify, and distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| package spew |
| |
| import ( |
| "bytes" |
| "encoding/hex" |
| "fmt" |
| "io" |
| "os" |
| "reflect" |
| "regexp" |
| "strconv" |
| "strings" |
| ) |
| |
| var ( |
| // uint8Type is a reflect.Type representing a uint8. It is used to |
| // convert cgo types to uint8 slices for hexdumping. |
| uint8Type = reflect.TypeOf(uint8(0)) |
| |
| // cCharRE is a regular expression that matches a cgo char. |
| // It is used to detect character arrays to hexdump them. |
| cCharRE = regexp.MustCompile("^.*\\._Ctype_char$") |
| |
| // cUnsignedCharRE is a regular expression that matches a cgo unsigned |
| // char. It is used to detect unsigned character arrays to hexdump |
| // them. |
| cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$") |
| |
| // cUint8tCharRE is a regular expression that matches a cgo uint8_t. |
| // It is used to detect uint8_t arrays to hexdump them. |
| cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$") |
| ) |
| |
| // dumpState contains information about the state of a dump operation. |
| type dumpState struct { |
| w io.Writer |
| depth int |
| pointers map[uintptr]int |
| ignoreNextType bool |
| ignoreNextIndent bool |
| cs *ConfigState |
| } |
| |
| // indent performs indentation according to the depth level and cs.Indent |
| // option. |
| func (d *dumpState) indent() { |
| if d.ignoreNextIndent { |
| d.ignoreNextIndent = false |
| return |
| } |
| d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth)) |
| } |
| |
| // unpackValue returns values inside of non-nil interfaces when possible. |
| // This is useful for data types like structs, arrays, slices, and maps which |
| // can contain varying types packed inside an interface. |
| func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { |
| if v.Kind() == reflect.Interface && !v.IsNil() { |
| v = v.Elem() |
| } |
| return v |
| } |
| |
| // dumpPtr handles formatting of pointers by indirecting them as necessary. |
| func (d *dumpState) dumpPtr(v reflect.Value) { |
| // Remove pointers at or below the current depth from map used to detect |
| // circular refs. |
| for k, depth := range d.pointers { |
| if depth >= d.depth { |
| delete(d.pointers, k) |
| } |
| } |
| |
| // Keep list of all dereferenced pointers to show later. |
| pointerChain := make([]uintptr, 0) |
| |
| // Figure out how many levels of indirection there are by dereferencing |
| // pointers and unpacking interfaces down the chain while detecting circular |
| // references. |
| nilFound := false |
| cycleFound := false |
| indirects := 0 |
| ve := v |
| for ve.Kind() == reflect.Ptr { |
| if ve.IsNil() { |
| nilFound = true |
| break |
| } |
| indirects++ |
| addr := ve.Pointer() |
| pointerChain = append(pointerChain, addr) |
| if pd, ok := d.pointers[addr]; ok && pd < d.depth { |
| cycleFound = true |
| indirects-- |
| break |
| } |
| d.pointers[addr] = d.depth |
| |
| ve = ve.Elem() |
| if ve.Kind() == reflect.Interface { |
| if ve.IsNil() { |
| nilFound = true |
| break |
| } |
| ve = ve.Elem() |
| } |
| } |
| |
| // Display type information. |
| d.w.Write(openParenBytes) |
| d.w.Write(bytes.Repeat(asteriskBytes, indirects)) |
| d.w.Write([]byte(ve.Type().String())) |
| d.w.Write(closeParenBytes) |
| |
| // Display pointer information. |
| if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 { |
| d.w.Write(openParenBytes) |
| for i, addr := range pointerChain { |
| if i > 0 { |
| d.w.Write(pointerChainBytes) |
| } |
| printHexPtr(d.w, addr) |
| } |
| d.w.Write(closeParenBytes) |
| } |
| |
| // Display dereferenced value. |
| d.w.Write(openParenBytes) |
| switch { |
| case nilFound == true: |
| d.w.Write(nilAngleBytes) |
| |
| case cycleFound == true: |
| d.w.Write(circularBytes) |
| |
| default: |
| d.ignoreNextType = true |
| d.dump(ve) |
| } |
| 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) { |
| // Determine whether this type should be hex dumped or not. Also, |
| // for types which should be hexdumped, try to use the underlying data |
| // first, then fall back to trying to convert them to a uint8 slice. |
| var buf []uint8 |
| doConvert := false |
| doHexDump := false |
| numEntries := v.Len() |
| if numEntries > 0 { |
| vt := v.Index(0).Type() |
| vts := vt.String() |
| switch { |
| // C types that need to be converted. |
| case cCharRE.MatchString(vts): |
| fallthrough |
| case cUnsignedCharRE.MatchString(vts): |
| fallthrough |
| case cUint8tCharRE.MatchString(vts): |
| doConvert = true |
| |
| // Try to use existing uint8 slices and fall back to converting |
| // and copying if that fails. |
| case vt.Kind() == reflect.Uint8: |
| // We need an addressable interface to convert the type |
| // to 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, when available, to |
| // bypass these restrictions since this package does not |
| // mutate the values. |
| vs := v |
| if !vs.CanInterface() || !vs.CanAddr() { |
| vs = unsafeReflectValue(vs) |
| } |
| if !UnsafeDisabled { |
| vs = vs.Slice(0, numEntries) |
| |
| // Use the existing uint8 slice if it can be |
| // type asserted. |
| iface := vs.Interface() |
| if slice, ok := iface.([]uint8); ok { |
| buf = slice |
| doHexDump = true |
| break |
| } |
| } |
| |
| // The underlying data needs to be converted if it can't |
| // be type asserted to a uint8 slice. |
| doConvert = true |
| } |
| |
| // Copy and convert the underlying type if needed. |
| if doConvert && vt.ConvertibleTo(uint8Type) { |
| // Convert and copy each element into a uint8 byte |
| // slice. |
| buf = make([]uint8, numEntries) |
| for i := 0; i < numEntries; i++ { |
| vv := v.Index(i) |
| buf[i] = uint8(vv.Convert(uint8Type).Uint()) |
| } |
| doHexDump = true |
| } |
| } |
| |
| // Hexdump the entire slice as needed. |
| if doHexDump { |
| 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 |
| } |
| |
| // 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 |
| // are detected and handled properly. |
| func (d *dumpState) dump(v reflect.Value) { |
| // Handle invalid reflect values immediately. |
| kind := v.Kind() |
| if kind == reflect.Invalid { |
| d.w.Write(invalidAngleBytes) |
| return |
| } |
| |
| // Handle pointers specially. |
| if kind == reflect.Ptr { |
| d.indent() |
| d.dumpPtr(v) |
| return |
| } |
| |
| // Print type information unless already handled elsewhere. |
| if !d.ignoreNextType { |
| d.indent() |
| d.w.Write(openParenBytes) |
| d.w.Write([]byte(v.Type().String())) |
| d.w.Write(closeParenBytes) |
| d.w.Write(spaceBytes) |
| } |
| d.ignoreNextType = false |
| |
| // Display length and capacity if the built-in len and cap functions |
| // work with the value's kind and the len/cap itself is non-zero. |
| valueLen, valueCap := 0, 0 |
| switch v.Kind() { |
| case reflect.Array, reflect.Slice, reflect.Chan: |
| valueLen, valueCap = v.Len(), v.Cap() |
| case reflect.Map, reflect.String: |
| valueLen = v.Len() |
| } |
| if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 { |
| d.w.Write(openParenBytes) |
| if valueLen != 0 { |
| d.w.Write(lenEqualsBytes) |
| printInt(d.w, int64(valueLen), 10) |
| } |
| if !d.cs.DisableCapacities && valueCap != 0 { |
| if valueLen != 0 { |
| d.w.Write(spaceBytes) |
| } |
| d.w.Write(capEqualsBytes) |
| printInt(d.w, int64(valueCap), 10) |
| } |
| d.w.Write(closeParenBytes) |
| d.w.Write(spaceBytes) |
| } |
| |
| // Call Stringer/error interfaces if they exist and the handle methods flag |
| // is enabled |
| if !d.cs.DisableMethods { |
| if (kind != reflect.Invalid) && (kind != reflect.Interface) { |
| if handled := handleMethods(d.cs, d.w, v); handled { |
| return |
| } |
| } |
| } |
| |
| switch kind { |
| case reflect.Invalid: |
| // Do nothing. We should never get here since invalid has already |
| // been handled above. |
| |
| case reflect.Bool: |
| printBool(d.w, v.Bool()) |
| |
| case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: |
| printInt(d.w, v.Int(), 10) |
| |
| case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: |
| printUint(d.w, v.Uint(), 10) |
| |
| case reflect.Float32: |
| printFloat(d.w, v.Float(), 32) |
| |
| case reflect.Float64: |
| printFloat(d.w, v.Float(), 64) |
| |
| case reflect.Complex64: |
| printComplex(d.w, v.Complex(), 32) |
| |
| case reflect.Complex128: |
| printComplex(d.w, v.Complex(), 64) |
| |
| case reflect.Slice: |
| if v.IsNil() { |
| d.w.Write(nilAngleBytes) |
| break |
| } |
| fallthrough |
| |
| case reflect.Array: |
| d.w.Write(openBraceNewlineBytes) |
| d.depth++ |
| if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { |
| d.indent() |
| d.w.Write(maxNewlineBytes) |
| } else { |
| d.dumpSlice(v) |
| } |
| d.depth-- |
| d.indent() |
| d.w.Write(closeBraceBytes) |
| |
| case reflect.String: |
| d.w.Write([]byte(strconv.Quote(v.String()))) |
| |
| case reflect.Interface: |
| // The only time we should get here is for nil interfaces due to |
| // unpackValue calls. |
| if v.IsNil() { |
| d.w.Write(nilAngleBytes) |
| } |
| |
| case reflect.Ptr: |
| // Do nothing. We should never get here since pointers have already |
| // been handled above. |
| |
| case reflect.Map: |
| // nil maps should be indicated as different than empty maps |
| if v.IsNil() { |
| d.w.Write(nilAngleBytes) |
| break |
| } |
| |
| d.w.Write(openBraceNewlineBytes) |
| d.depth++ |
| if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { |
| d.indent() |
| d.w.Write(maxNewlineBytes) |
| } else { |
| numEntries := v.Len() |
| keys := v.MapKeys() |
| if d.cs.SortKeys { |
| sortValues(keys, d.cs) |
| } |
| for i, key := range keys { |
| d.dump(d.unpackValue(key)) |
| d.w.Write(colonSpaceBytes) |
| d.ignoreNextIndent = true |
| d.dump(d.unpackValue(v.MapIndex(key))) |
| if i < (numEntries - 1) { |
| d.w.Write(commaNewlineBytes) |
| } else { |
| d.w.Write(newlineBytes) |
| } |
| } |
| } |
| d.depth-- |
| d.indent() |
| d.w.Write(closeBraceBytes) |
| |
| case reflect.Struct: |
| d.w.Write(openBraceNewlineBytes) |
| d.depth++ |
| if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { |
| d.indent() |
| d.w.Write(maxNewlineBytes) |
| } else { |
| vt := v.Type() |
| numFields := v.NumField() |
| for i := 0; i < numFields; i++ { |
| d.indent() |
| vtf := vt.Field(i) |
| d.w.Write([]byte(vtf.Name)) |
| d.w.Write(colonSpaceBytes) |
| d.ignoreNextIndent = true |
| d.dump(d.unpackValue(v.Field(i))) |
| if i < (numFields - 1) { |
| d.w.Write(commaNewlineBytes) |
| } else { |
| d.w.Write(newlineBytes) |
| } |
| } |
| } |
| d.depth-- |
| d.indent() |
| d.w.Write(closeBraceBytes) |
| |
| case reflect.Uintptr: |
| printHexPtr(d.w, uintptr(v.Uint())) |
| |
| case reflect.UnsafePointer, reflect.Chan, reflect.Func: |
| printHexPtr(d.w, v.Pointer()) |
| |
| // There were not any other types at the time this code was written, but |
| // fall back to letting the default fmt package handle it in case any new |
| // types are added. |
| default: |
| if v.CanInterface() { |
| fmt.Fprintf(d.w, "%v", v.Interface()) |
| } else { |
| fmt.Fprintf(d.w, "%v", v.String()) |
| } |
| } |
| } |
| |
| // fdump is a helper function to consolidate the logic from the various public |
| // methods which take varying writers and config states. |
| func fdump(cs *ConfigState, w io.Writer, a ...interface{}) { |
| for _, arg := range a { |
| if arg == nil { |
| w.Write(interfaceBytes) |
| w.Write(spaceBytes) |
| w.Write(nilAngleBytes) |
| w.Write(newlineBytes) |
| continue |
| } |
| |
| d := dumpState{w: w, cs: cs} |
| d.pointers = make(map[uintptr]int) |
| d.dump(reflect.ValueOf(arg)) |
| d.w.Write(newlineBytes) |
| } |
| } |
| |
| // Fdump formats and displays the passed arguments to io.Writer w. It formats |
| // exactly the same as Dump. |
| func Fdump(w io.Writer, a ...interface{}) { |
| fdump(&Config, w, a...) |
| } |
| |
| // Sdump returns a string with the passed arguments formatted exactly the same |
| // as Dump. |
| func Sdump(a ...interface{}) string { |
| var buf bytes.Buffer |
| fdump(&Config, &buf, a...) |
| return buf.String() |
| } |
| |
| /* |
| Dump displays the passed parameters to standard out with newlines, customizable |
| indentation, and additional debug information such as complete types and all |
| pointer addresses used to indirect to the final value. It provides the |
| following features over the built-in printing facilities provided by the fmt |
| package: |
| |
| * Pointers are dereferenced and followed |
| * Circular data structures are detected and handled properly |
| * Custom Stringer/error interfaces are optionally invoked, including |
| on unexported types |
| * Custom types which only implement the Stringer/error interfaces via |
| a pointer receiver are optionally invoked when passing non-pointer |
| variables |
| * Byte arrays and slices are dumped like the hexdump -C command which |
| includes offsets, byte values in hex, and ASCII output |
| |
| The configuration options are controlled by an exported package global, |
| spew.Config. See ConfigState for options documentation. |
| |
| See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to |
| get the formatted result as a string. |
| */ |
| func Dump(a ...interface{}) { |
| fdump(&Config, os.Stdout, a...) |
| } |