blob: f78d89fc1f6c454df58cd1e346817db6e30c4299 [file] [log] [blame]
Jonathan Hart4b110f62020-03-13 17:36:19 -07001/*
2 * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17package spew
18
19import (
20 "bytes"
21 "encoding/hex"
22 "fmt"
23 "io"
24 "os"
25 "reflect"
26 "regexp"
27 "strconv"
28 "strings"
29)
30
31var (
32 // uint8Type is a reflect.Type representing a uint8. It is used to
33 // convert cgo types to uint8 slices for hexdumping.
34 uint8Type = reflect.TypeOf(uint8(0))
35
36 // cCharRE is a regular expression that matches a cgo char.
37 // It is used to detect character arrays to hexdump them.
38 cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
39
40 // cUnsignedCharRE is a regular expression that matches a cgo unsigned
41 // char. It is used to detect unsigned character arrays to hexdump
42 // them.
43 cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
44
45 // cUint8tCharRE is a regular expression that matches a cgo uint8_t.
46 // It is used to detect uint8_t arrays to hexdump them.
47 cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
48)
49
50// dumpState contains information about the state of a dump operation.
51type dumpState struct {
52 w io.Writer
53 depth int
54 pointers map[uintptr]int
55 ignoreNextType bool
56 ignoreNextIndent bool
57 cs *ConfigState
58}
59
60// indent performs indentation according to the depth level and cs.Indent
61// option.
62func (d *dumpState) indent() {
63 if d.ignoreNextIndent {
64 d.ignoreNextIndent = false
65 return
66 }
67 d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
68}
69
70// unpackValue returns values inside of non-nil interfaces when possible.
71// This is useful for data types like structs, arrays, slices, and maps which
72// can contain varying types packed inside an interface.
73func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
74 if v.Kind() == reflect.Interface && !v.IsNil() {
75 v = v.Elem()
76 }
77 return v
78}
79
80// dumpPtr handles formatting of pointers by indirecting them as necessary.
81func (d *dumpState) dumpPtr(v reflect.Value) {
82 // Remove pointers at or below the current depth from map used to detect
83 // circular refs.
84 for k, depth := range d.pointers {
85 if depth >= d.depth {
86 delete(d.pointers, k)
87 }
88 }
89
90 // Keep list of all dereferenced pointers to show later.
91 pointerChain := make([]uintptr, 0)
92
93 // Figure out how many levels of indirection there are by dereferencing
94 // pointers and unpacking interfaces down the chain while detecting circular
95 // references.
96 nilFound := false
97 cycleFound := false
98 indirects := 0
99 ve := v
100 for ve.Kind() == reflect.Ptr {
101 if ve.IsNil() {
102 nilFound = true
103 break
104 }
105 indirects++
106 addr := ve.Pointer()
107 pointerChain = append(pointerChain, addr)
108 if pd, ok := d.pointers[addr]; ok && pd < d.depth {
109 cycleFound = true
110 indirects--
111 break
112 }
113 d.pointers[addr] = d.depth
114
115 ve = ve.Elem()
116 if ve.Kind() == reflect.Interface {
117 if ve.IsNil() {
118 nilFound = true
119 break
120 }
121 ve = ve.Elem()
122 }
123 }
124
125 // Display type information.
126 d.w.Write(openParenBytes)
127 d.w.Write(bytes.Repeat(asteriskBytes, indirects))
128 d.w.Write([]byte(ve.Type().String()))
129 d.w.Write(closeParenBytes)
130
131 // Display pointer information.
132 if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
133 d.w.Write(openParenBytes)
134 for i, addr := range pointerChain {
135 if i > 0 {
136 d.w.Write(pointerChainBytes)
137 }
138 printHexPtr(d.w, addr)
139 }
140 d.w.Write(closeParenBytes)
141 }
142
143 // Display dereferenced value.
144 d.w.Write(openParenBytes)
145 switch {
146 case nilFound:
147 d.w.Write(nilAngleBytes)
148
149 case cycleFound:
150 d.w.Write(circularBytes)
151
152 default:
153 d.ignoreNextType = true
154 d.dump(ve)
155 }
156 d.w.Write(closeParenBytes)
157}
158
159// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
160// reflection) arrays and slices are dumped in hexdump -C fashion.
161func (d *dumpState) dumpSlice(v reflect.Value) {
162 // Determine whether this type should be hex dumped or not. Also,
163 // for types which should be hexdumped, try to use the underlying data
164 // first, then fall back to trying to convert them to a uint8 slice.
165 var buf []uint8
166 doConvert := false
167 doHexDump := false
168 numEntries := v.Len()
169 if numEntries > 0 {
170 vt := v.Index(0).Type()
171 vts := vt.String()
172 switch {
173 // C types that need to be converted.
174 case cCharRE.MatchString(vts):
175 fallthrough
176 case cUnsignedCharRE.MatchString(vts):
177 fallthrough
178 case cUint8tCharRE.MatchString(vts):
179 doConvert = true
180
181 // Try to use existing uint8 slices and fall back to converting
182 // and copying if that fails.
183 case vt.Kind() == reflect.Uint8:
184 // We need an addressable interface to convert the type
185 // to a byte slice. However, the reflect package won't
186 // give us an interface on certain things like
187 // unexported struct fields in order to enforce
188 // visibility rules. We use unsafe, when available, to
189 // bypass these restrictions since this package does not
190 // mutate the values.
191 vs := v
192 if !vs.CanInterface() || !vs.CanAddr() {
193 vs = unsafeReflectValue(vs)
194 }
195 if !UnsafeDisabled {
196 vs = vs.Slice(0, numEntries)
197
198 // Use the existing uint8 slice if it can be
199 // type asserted.
200 iface := vs.Interface()
201 if slice, ok := iface.([]uint8); ok {
202 buf = slice
203 doHexDump = true
204 break
205 }
206 }
207
208 // The underlying data needs to be converted if it can't
209 // be type asserted to a uint8 slice.
210 doConvert = true
211 }
212
213 // Copy and convert the underlying type if needed.
214 if doConvert && vt.ConvertibleTo(uint8Type) {
215 // Convert and copy each element into a uint8 byte
216 // slice.
217 buf = make([]uint8, numEntries)
218 for i := 0; i < numEntries; i++ {
219 vv := v.Index(i)
220 buf[i] = uint8(vv.Convert(uint8Type).Uint())
221 }
222 doHexDump = true
223 }
224 }
225
226 // Hexdump the entire slice as needed.
227 if doHexDump {
228 indent := strings.Repeat(d.cs.Indent, d.depth)
229 str := indent + hex.Dump(buf)
230 str = strings.Replace(str, "\n", "\n"+indent, -1)
231 str = strings.TrimRight(str, d.cs.Indent)
232 d.w.Write([]byte(str))
233 return
234 }
235
236 // Recursively call dump for each item.
237 for i := 0; i < numEntries; i++ {
238 d.dump(d.unpackValue(v.Index(i)))
239 if i < (numEntries - 1) {
240 d.w.Write(commaNewlineBytes)
241 } else {
242 d.w.Write(newlineBytes)
243 }
244 }
245}
246
247// dump is the main workhorse for dumping a value. It uses the passed reflect
248// value to figure out what kind of object we are dealing with and formats it
249// appropriately. It is a recursive function, however circular data structures
250// are detected and handled properly.
251func (d *dumpState) dump(v reflect.Value) {
252 // Handle invalid reflect values immediately.
253 kind := v.Kind()
254 if kind == reflect.Invalid {
255 d.w.Write(invalidAngleBytes)
256 return
257 }
258
259 // Handle pointers specially.
260 if kind == reflect.Ptr {
261 d.indent()
262 d.dumpPtr(v)
263 return
264 }
265
266 // Print type information unless already handled elsewhere.
267 if !d.ignoreNextType {
268 d.indent()
269 d.w.Write(openParenBytes)
270 d.w.Write([]byte(v.Type().String()))
271 d.w.Write(closeParenBytes)
272 d.w.Write(spaceBytes)
273 }
274 d.ignoreNextType = false
275
276 // Display length and capacity if the built-in len and cap functions
277 // work with the value's kind and the len/cap itself is non-zero.
278 valueLen, valueCap := 0, 0
279 switch v.Kind() {
280 case reflect.Array, reflect.Slice, reflect.Chan:
281 valueLen, valueCap = v.Len(), v.Cap()
282 case reflect.Map, reflect.String:
283 valueLen = v.Len()
284 }
285 if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
286 d.w.Write(openParenBytes)
287 if valueLen != 0 {
288 d.w.Write(lenEqualsBytes)
289 printInt(d.w, int64(valueLen), 10)
290 }
291 if !d.cs.DisableCapacities && valueCap != 0 {
292 if valueLen != 0 {
293 d.w.Write(spaceBytes)
294 }
295 d.w.Write(capEqualsBytes)
296 printInt(d.w, int64(valueCap), 10)
297 }
298 d.w.Write(closeParenBytes)
299 d.w.Write(spaceBytes)
300 }
301
302 // Call Stringer/error interfaces if they exist and the handle methods flag
303 // is enabled
304 if !d.cs.DisableMethods {
305 if (kind != reflect.Invalid) && (kind != reflect.Interface) {
306 if handled := handleMethods(d.cs, d.w, v); handled {
307 return
308 }
309 }
310 }
311
312 switch kind {
313 case reflect.Invalid:
314 // Do nothing. We should never get here since invalid has already
315 // been handled above.
316
317 case reflect.Bool:
318 printBool(d.w, v.Bool())
319
320 case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
321 printInt(d.w, v.Int(), 10)
322
323 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
324 printUint(d.w, v.Uint(), 10)
325
326 case reflect.Float32:
327 printFloat(d.w, v.Float(), 32)
328
329 case reflect.Float64:
330 printFloat(d.w, v.Float(), 64)
331
332 case reflect.Complex64:
333 printComplex(d.w, v.Complex(), 32)
334
335 case reflect.Complex128:
336 printComplex(d.w, v.Complex(), 64)
337
338 case reflect.Slice:
339 if v.IsNil() {
340 d.w.Write(nilAngleBytes)
341 break
342 }
343 fallthrough
344
345 case reflect.Array:
346 d.w.Write(openBraceNewlineBytes)
347 d.depth++
348 if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
349 d.indent()
350 d.w.Write(maxNewlineBytes)
351 } else {
352 d.dumpSlice(v)
353 }
354 d.depth--
355 d.indent()
356 d.w.Write(closeBraceBytes)
357
358 case reflect.String:
359 d.w.Write([]byte(strconv.Quote(v.String())))
360
361 case reflect.Interface:
362 // The only time we should get here is for nil interfaces due to
363 // unpackValue calls.
364 if v.IsNil() {
365 d.w.Write(nilAngleBytes)
366 }
367
368 case reflect.Ptr:
369 // Do nothing. We should never get here since pointers have already
370 // been handled above.
371
372 case reflect.Map:
373 // nil maps should be indicated as different than empty maps
374 if v.IsNil() {
375 d.w.Write(nilAngleBytes)
376 break
377 }
378
379 d.w.Write(openBraceNewlineBytes)
380 d.depth++
381 if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
382 d.indent()
383 d.w.Write(maxNewlineBytes)
384 } else {
385 numEntries := v.Len()
386 keys := v.MapKeys()
387 if d.cs.SortKeys {
388 sortValues(keys, d.cs)
389 }
390 for i, key := range keys {
391 d.dump(d.unpackValue(key))
392 d.w.Write(colonSpaceBytes)
393 d.ignoreNextIndent = true
394 d.dump(d.unpackValue(v.MapIndex(key)))
395 if i < (numEntries - 1) {
396 d.w.Write(commaNewlineBytes)
397 } else {
398 d.w.Write(newlineBytes)
399 }
400 }
401 }
402 d.depth--
403 d.indent()
404 d.w.Write(closeBraceBytes)
405
406 case reflect.Struct:
407 d.w.Write(openBraceNewlineBytes)
408 d.depth++
409 if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
410 d.indent()
411 d.w.Write(maxNewlineBytes)
412 } else {
413 vt := v.Type()
414 numFields := v.NumField()
415 for i := 0; i < numFields; i++ {
416 d.indent()
417 vtf := vt.Field(i)
418 d.w.Write([]byte(vtf.Name))
419 d.w.Write(colonSpaceBytes)
420 d.ignoreNextIndent = true
421 d.dump(d.unpackValue(v.Field(i)))
422 if i < (numFields - 1) {
423 d.w.Write(commaNewlineBytes)
424 } else {
425 d.w.Write(newlineBytes)
426 }
427 }
428 }
429 d.depth--
430 d.indent()
431 d.w.Write(closeBraceBytes)
432
433 case reflect.Uintptr:
434 printHexPtr(d.w, uintptr(v.Uint()))
435
436 case reflect.UnsafePointer, reflect.Chan, reflect.Func:
437 printHexPtr(d.w, v.Pointer())
438
439 // There were not any other types at the time this code was written, but
440 // fall back to letting the default fmt package handle it in case any new
441 // types are added.
442 default:
443 if v.CanInterface() {
444 fmt.Fprintf(d.w, "%v", v.Interface())
445 } else {
446 fmt.Fprintf(d.w, "%v", v.String())
447 }
448 }
449}
450
451// fdump is a helper function to consolidate the logic from the various public
452// methods which take varying writers and config states.
453func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
454 for _, arg := range a {
455 if arg == nil {
456 w.Write(interfaceBytes)
457 w.Write(spaceBytes)
458 w.Write(nilAngleBytes)
459 w.Write(newlineBytes)
460 continue
461 }
462
463 d := dumpState{w: w, cs: cs}
464 d.pointers = make(map[uintptr]int)
465 d.dump(reflect.ValueOf(arg))
466 d.w.Write(newlineBytes)
467 }
468}
469
470// Fdump formats and displays the passed arguments to io.Writer w. It formats
471// exactly the same as Dump.
472func Fdump(w io.Writer, a ...interface{}) {
473 fdump(&Config, w, a...)
474}
475
476// Sdump returns a string with the passed arguments formatted exactly the same
477// as Dump.
478func Sdump(a ...interface{}) string {
479 var buf bytes.Buffer
480 fdump(&Config, &buf, a...)
481 return buf.String()
482}
483
484/*
485Dump displays the passed parameters to standard out with newlines, customizable
486indentation, and additional debug information such as complete types and all
487pointer addresses used to indirect to the final value. It provides the
488following features over the built-in printing facilities provided by the fmt
489package:
490
491 * Pointers are dereferenced and followed
492 * Circular data structures are detected and handled properly
493 * Custom Stringer/error interfaces are optionally invoked, including
494 on unexported types
495 * Custom types which only implement the Stringer/error interfaces via
496 a pointer receiver are optionally invoked when passing non-pointer
497 variables
498 * Byte arrays and slices are dumped like the hexdump -C command which
499 includes offsets, byte values in hex, and ASCII output
500
501The configuration options are controlled by an exported package global,
502spew.Config. See ConfigState for options documentation.
503
504See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
505get the formatted result as a string.
506*/
507func Dump(a ...interface{}) {
508 fdump(&Config, os.Stdout, a...)
509}