|
|
- /*
- * 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:
- d.w.Write(nilAngleBytes)
-
- case cycleFound:
- 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...)
- }
|