Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
509 lines
13 KiB
509 lines
13 KiB
/* |
|
* Copyright (c) 2013 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 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 || valueCap != 0 { |
|
d.w.Write(openParenBytes) |
|
if valueLen != 0 { |
|
d.w.Write(lenEqualsBytes) |
|
printInt(d.w, int64(valueLen), 10) |
|
} |
|
if 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...) |
|
}
|
|
|