]>
Commit | Line | Data |
---|---|---|
1 | /* | |
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 | ||
17 | package spew | |
18 | ||
19 | import ( | |
20 | "bytes" | |
21 | "encoding/hex" | |
22 | "fmt" | |
23 | "io" | |
24 | "os" | |
25 | "reflect" | |
26 | "regexp" | |
27 | "strconv" | |
28 | "strings" | |
29 | ) | |
30 | ||
31 | var ( | |
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. | |
51 | type 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. | |
62 | func (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. | |
73 | func (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. | |
81 | func (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 == true: | |
147 | d.w.Write(nilAngleBytes) | |
148 | ||
149 | case cycleFound == true: | |
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. | |
161 | func (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. | |
251 | func (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. | |
453 | func 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. | |
472 | func 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. | |
478 | func Sdump(a ...interface{}) string { | |
479 | var buf bytes.Buffer | |
480 | fdump(&Config, &buf, a...) | |
481 | return buf.String() | |
482 | } | |
483 | ||
484 | /* | |
485 | Dump displays the passed parameters to standard out with newlines, customizable | |
486 | indentation, and additional debug information such as complete types and all | |
487 | pointer addresses used to indirect to the final value. It provides the | |
488 | following features over the built-in printing facilities provided by the fmt | |
489 | package: | |
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 | ||
501 | The configuration options are controlled by an exported package global, | |
502 | spew.Config. See ConfigState for options documentation. | |
503 | ||
504 | See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to | |
505 | get the formatted result as a string. | |
506 | */ | |
507 | func Dump(a ...interface{}) { | |
508 | fdump(&Config, os.Stdout, a...) | |
509 | } |