github.com/kortschak/utter@v1.5.0/dump.go (about) 1 /* 2 * Copyright (c) 2013 Dave Collins <dave@davec.name> 3 * Copyright (c) 2015 Dan Kortschak <dan.kortschak@adelaide.edu.au> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 package utter 19 20 import ( 21 "bytes" 22 "fmt" 23 "io" 24 "os" 25 "reflect" 26 "regexp" 27 "strconv" 28 "strings" 29 "unicode" 30 "unicode/utf8" 31 ) 32 33 var ( 34 // uint8Type is a reflect.Type representing a uint8. It is used to 35 // convert cgo types to uint8 slices for hexdumping. 36 uint8Type = reflect.TypeOf(uint8(0)) 37 38 // cCharRE is a regular expression that matches a cgo char. 39 // It is used to detect character arrays to hexdump them. 40 cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`) 41 42 // cUnsignedCharRE is a regular expression that matches a cgo unsigned 43 // char. It is used to detect unsigned character arrays to hexdump 44 // them. 45 cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`) 46 47 // cUint8tCharRE is a regular expression that matches a cgo uint8_t. 48 // It is used to detect uint8_t arrays to hexdump them. 49 cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`) 50 ) 51 52 type addrType struct { 53 addr uintptr 54 typ reflect.Type 55 } 56 57 // dumpState contains information about the state of a dump operation. 58 type dumpState struct { 59 w io.Writer 60 depth int 61 pointers map[uintptr]int 62 nodes map[addrType]struct{} 63 displayed map[addrType]struct{} 64 ignoreNextType bool 65 ignoreNextIndent bool 66 cs *ConfigState 67 } 68 69 // indent performs indentation according to the depth level and cs.Indent 70 // option. 71 func (d *dumpState) indent() { 72 if d.ignoreNextIndent { 73 d.ignoreNextIndent = false 74 return 75 } 76 d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth)) 77 } 78 79 // unpackValue returns values inside of non-nil interfaces when possible. 80 // This is useful for data types like structs, arrays, slices, and maps which 81 // can contain varying types packed inside an interface. 82 func (d *dumpState) unpackValue(v reflect.Value) (val reflect.Value, wasPtr, static, canElideStruct bool, addr uintptr) { 83 if v.CanAddr() { 84 addr = v.Addr().Pointer() 85 } 86 if v.Kind() == reflect.Interface && !v.IsNil() { 87 return v.Elem(), v.Kind() == reflect.Ptr, false, false, addr 88 } 89 return v, v.Kind() == reflect.Ptr, true, false, addr 90 } 91 92 // dumpPtr handles formatting of pointers by indirecting them as necessary. 93 func (d *dumpState) dumpPtr(v reflect.Value) { 94 // Remove pointers below the current depth from map used to detect 95 // circular refs. 96 for k, depth := range d.pointers { 97 if depth > d.depth { 98 delete(d.pointers, k) 99 } 100 } 101 102 // Keep list of all dereferenced pointers to show later. 103 var pointerChain []uintptr 104 105 // Record the value's address. 106 value := addrType{addr: v.Pointer()} 107 108 // Keep the original value in case we have already displayed it. 109 orig := v 110 111 // Figure out how many levels of indirection there are by dereferencing 112 // pointers and unpacking interfaces down the chain while detecting circular 113 // references. 114 var nilFound, cycleFound bool 115 indirects := 0 116 for v.Kind() == reflect.Ptr { 117 if v.IsNil() { 118 nilFound = true 119 break 120 } 121 indirects++ 122 addr := v.Pointer() 123 if d.cs.CommentPointers { 124 pointerChain = append(pointerChain, addr) 125 } 126 if pd, ok := d.pointers[addr]; ok && pd < d.depth { 127 cycleFound = true 128 indirects-- 129 break 130 } 131 d.pointers[addr] = d.depth 132 133 v = v.Elem() 134 if v.Kind() == reflect.Interface { 135 if v.IsNil() { 136 nilFound = true 137 break 138 } 139 v = v.Elem() 140 } 141 } 142 143 // Record the value's element type and check whether it has been displayed 144 value.typ = v.Type() 145 _, displayed := d.displayed[value] 146 147 // Display type information. 148 var typeBytes []byte 149 if displayed { 150 d.w.Write(openParenBytes) 151 typeBytes = []byte(typeString(orig.Type(), d.cs.LocalPackage)) 152 } else { 153 d.w.Write(bytes.Repeat(ampersandBytes, indirects)) 154 typeBytes = []byte(typeString(v.Type(), d.cs.LocalPackage)) 155 } 156 kind := v.Kind() 157 bufferedChan := kind == reflect.Chan && v.Cap() != 0 158 if kind == reflect.Ptr || bufferedChan { 159 d.w.Write(openParenBytes) 160 } 161 d.w.Write(bytes.ReplaceAll(typeBytes, interfaceTypeBytes, interfaceBytes)) 162 if displayed { 163 d.w.Write(closeParenBytes) 164 } 165 switch { 166 case bufferedChan: 167 switch len := v.Len(); len { 168 case 0: 169 fmt.Fprintf(d.w, ", %d", v.Cap()) 170 case 1: 171 fmt.Fprintf(d.w, ", %d /* %d element */", v.Cap(), len) 172 default: 173 fmt.Fprintf(d.w, ", %d /* %d elements */", v.Cap(), len) 174 } 175 fallthrough 176 case kind == reflect.Ptr: 177 d.w.Write(closeParenBytes) 178 } 179 180 // Display pointer information. 181 if len(pointerChain) > 0 { 182 d.w.Write(openCommentBytes) 183 for i, addr := range pointerChain { 184 if i > 0 { 185 d.w.Write(pointerChainBytes) 186 } 187 printHexPtr(d.w, addr, true) 188 } 189 d.w.Write(closeCommentBytes) 190 } 191 192 // Display dereferenced value. 193 switch { 194 case nilFound: 195 d.w.Write(openParenBytes) 196 d.w.Write(nilBytes) 197 d.w.Write(closeParenBytes) 198 199 case cycleFound, displayed: 200 d.w.Write(circularBytes) 201 202 default: 203 d.ignoreNextType = true 204 var addr uintptr 205 if v.CanAddr() { 206 addr = v.Addr().Pointer() 207 } 208 // Mark the value as having been displayed. 209 d.displayed[value] = struct{}{} 210 d.dump(v, true, false, false, addr) 211 } 212 } 213 214 // dumpSlice handles formatting of arrays and slices. Byte (uint8 under 215 // reflection) arrays and slices are dumped in hexdump -C fashion. 216 func (d *dumpState) dumpSlice(v reflect.Value, canElideCompound bool) { 217 // Determine whether this type should be hex dumped or not. Also, 218 // for types which should be hexdumped, try to use the underlying data 219 // first, then fall back to trying to convert them to a uint8 slice. 220 var buf []uint8 221 doConvert := false 222 doHexDump := false 223 nPeriod := 1 224 numEntries := v.Len() 225 vt := v.Type().Elem() 226 if numEntries > 0 { 227 vts := vt.String() 228 switch kind := vt.Kind(); { 229 // C types that need to be converted. 230 case cCharRE.MatchString(vts): 231 fallthrough 232 case cUnsignedCharRE.MatchString(vts): 233 fallthrough 234 case cUint8tCharRE.MatchString(vts): 235 doConvert = true 236 237 // Try to use existing uint8 slices and fall back to converting 238 // and copying if that fails. 239 case kind == reflect.Uint8: 240 // We need an addressable interface to convert the type back 241 // into a byte slice. However, the reflect package won't give 242 // us an interface on certain things like unexported struct 243 // fields in order to enforce visibility rules. We use unsafe 244 // to bypass these restrictions since this package does not 245 // mutate the values. 246 vs := v 247 if !vs.CanInterface() || !vs.CanAddr() { 248 vs = unsafeReflectValue(vs) 249 } 250 vs = vs.Slice(0, numEntries) 251 252 // Use the existing uint8 slice if it can be type 253 // asserted. 254 iface := vs.Interface() 255 if slice, ok := iface.([]uint8); ok { 256 buf = slice 257 doHexDump = true 258 break 259 } 260 261 // The underlying data needs to be converted if it can't 262 // be type asserted to a uint8 slice. 263 doConvert = true 264 265 case isNumeric(kind): 266 nPeriod = d.cs.NumericWidth 267 268 case kind == reflect.String: 269 nPeriod = d.cs.StringWidth 270 } 271 272 // Copy and convert the underlying type if needed. 273 if doConvert && vt.ConvertibleTo(uint8Type) { 274 // Convert and copy each element into a uint8 byte 275 // slice. 276 buf = make([]uint8, numEntries) 277 for i := 0; i < numEntries; i++ { 278 vv := v.Index(i) 279 buf[i] = uint8(vv.Convert(uint8Type).Uint()) 280 } 281 doHexDump = true 282 } 283 } 284 285 // Prepare indenting for slice. 286 if nPeriod == 0 { 287 d.w.Write(openBraceBytes) 288 } else { 289 d.w.Write(openBraceNewlineBytes) 290 } 291 d.depth++ 292 defer func() { 293 d.depth-- 294 if nPeriod != 0 { 295 d.indent() 296 } 297 d.w.Write(closeBraceBytes) 298 }() 299 300 // Hexdump the entire slice as needed. 301 if doHexDump { 302 indent := strings.Repeat(d.cs.Indent, d.depth) 303 hexDump(d.w, buf, indent, d.cs.BytesWidth, d.cs.CommentBytes) 304 return 305 } 306 307 // Recursively call dump for each item. 308 for i := 0; i < numEntries; i++ { 309 vi := v.Index(i) 310 if nPeriod == 0 || i%nPeriod != 0 { 311 d.ignoreNextIndent = true 312 } 313 val, wasPtr, static, _, addr := d.unpackValue(vi) 314 d.dump(val, wasPtr, static, canElideCompound, addr) 315 if nPeriod == 0 || (i%nPeriod != nPeriod-1 && i != numEntries-1) { 316 if i < numEntries-1 { 317 d.w.Write(commaSpaceBytes) 318 continue 319 } 320 break 321 } 322 d.w.Write(commaNewlineBytes) 323 } 324 } 325 326 // isNumeric returns true for all numeric and boolean kinds. 327 func isNumeric(k reflect.Kind) bool { 328 switch k { 329 case reflect.Int, reflect.Uint, 330 reflect.Int8, reflect.Bool, 331 reflect.Int16, reflect.Uint16, 332 reflect.Int32, reflect.Uint32, 333 reflect.Int64, reflect.Uint64, 334 reflect.Float32, reflect.Float64, 335 reflect.Complex64, reflect.Complex128, 336 reflect.Uintptr, reflect.UnsafePointer: 337 return true 338 default: 339 return false 340 } 341 } 342 343 // dump is the main workhorse for dumping a value. It uses the passed reflect 344 // value to figure out what kind of object we are dealing with and formats it 345 // appropriately. It is a recursive function, however circular data structures 346 // are detected and annotated. 347 func (d *dumpState) dump(v reflect.Value, wasPtr, static, canElideCompound bool, addr uintptr) { 348 // Handle invalid reflect values immediately. 349 kind := v.Kind() 350 if kind == reflect.Invalid { 351 d.w.Write(invalidAngleBytes) 352 return 353 } 354 355 // Handle pointers specially. 356 if kind == reflect.Ptr { 357 d.indent() 358 d.dumpPtr(v) 359 return 360 } 361 362 typ := v.Type() 363 wantType := true 364 interfaceContext := kind == reflect.Interface 365 if d.cs.ElideType { 366 defType := !wasPtr && isDefault(typ) 367 wantType = !static && !defType && (!interfaceContext || !v.IsNil()) 368 if !canElideCompound { 369 wantType = wantType || isCompound(kind) 370 } 371 } 372 373 // Print type information unless already handled elsewhere. 374 if !d.ignoreNextType { 375 d.indent() 376 if wantType { 377 bufferedChan := v.Kind() == reflect.Chan && v.Cap() != 0 378 if bufferedChan { 379 d.w.Write(openParenBytes) 380 } 381 typeBytes := []byte(typeString(v.Type(), d.cs.LocalPackage)) 382 d.w.Write(bytes.ReplaceAll(typeBytes, interfaceTypeBytes, interfaceBytes)) 383 if bufferedChan { 384 switch len := v.Len(); len { 385 case 0: 386 fmt.Fprintf(d.w, ", %d)", v.Cap()) 387 case 1: 388 fmt.Fprintf(d.w, ", %d /* %d element */)", v.Cap(), len) 389 default: 390 fmt.Fprintf(d.w, ", %d /* %d elements */)", v.Cap(), len) 391 } 392 } 393 } 394 } 395 d.ignoreNextType = false 396 397 if wantType { 398 switch kind { 399 case reflect.Invalid, reflect.Struct, reflect.Slice, reflect.Array, reflect.Map: 400 default: 401 d.w.Write(openParenBytes) 402 } 403 } 404 405 if _, referenced := d.nodes[addrType{addr, typ}]; !wasPtr && referenced { 406 d.w.Write(openCommentBytes) 407 printHexPtr(d.w, addr, true) 408 d.w.Write(closeCommentBytes) 409 } 410 switch kind { 411 case reflect.Invalid: 412 // We should never get here since invalid has already been handled above. 413 panic("cannot reach") 414 415 case reflect.Bool: 416 printBool(d.w, v.Bool()) 417 418 case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 419 printInt(d.w, v.Int(), 10) 420 421 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 422 d.w.Write(hexZeroBytes) 423 printUint(d.w, v.Uint(), 16) 424 425 case reflect.Float32: 426 printFloat(d.w, v.Float(), 32, !wantType) 427 428 case reflect.Float64: 429 printFloat(d.w, v.Float(), 64, !wantType) 430 431 case reflect.Complex64: 432 printComplex(d.w, v.Complex(), 32) 433 434 case reflect.Complex128: 435 printComplex(d.w, v.Complex(), 64) 436 437 case reflect.Slice: 438 if v.IsNil() { 439 d.w.Write(openParenBytes) 440 d.w.Write(nilBytes) 441 d.w.Write(closeParenBytes) 442 break 443 } 444 if v.Len() == 0 { 445 d.dumpSlice(v, !interfaceContext) 446 break 447 } 448 // Remove pointers below the current depth from map used to detect 449 // circular refs. 450 for k, depth := range d.pointers { 451 if depth > d.depth { 452 delete(d.pointers, k) 453 } 454 } 455 addr = v.Index(0).Addr().Pointer() 456 if pd, ok := d.pointers[addr]; ok && pd < d.depth { 457 d.w.Write(circularBytes) 458 break 459 } 460 d.pointers[addr] = d.depth 461 462 fallthrough 463 464 case reflect.Array: 465 d.dumpSlice(v, !interfaceContext) 466 467 case reflect.String: 468 d.writeQuoted(v.String()) 469 470 case reflect.Interface: 471 // The only time we should get here is for nil interfaces due to 472 // unpackValue calls. 473 if v.IsNil() { 474 d.w.Write(nilBytes) 475 } 476 477 case reflect.Ptr: 478 // We should never get here since pointers have already been handled above. 479 panic("cannot reach") 480 481 case reflect.Map: 482 // nil maps should be indicated as different than empty maps 483 if v.IsNil() { 484 d.w.Write(openParenBytes) 485 d.w.Write(nilBytes) 486 d.w.Write(closeParenBytes) 487 break 488 } 489 490 // Remove pointers below the current depth from map used to detect 491 // circular refs. 492 for k, depth := range d.pointers { 493 if depth > d.depth { 494 delete(d.pointers, k) 495 } 496 } 497 addr := v.Pointer() 498 if pd, ok := d.pointers[addr]; ok && pd < d.depth { 499 d.w.Write(circularBytes) 500 break 501 } 502 d.pointers[addr] = d.depth 503 504 d.w.Write(openBraceNewlineBytes) 505 d.depth++ 506 if d.cs.SortKeys { 507 iter := v.MapRange() 508 keys := make([]reflect.Value, 0, v.Len()) 509 vals := make([]reflect.Value, 0, v.Len()) 510 for iter.Next() { 511 keys = append(keys, iter.Key()) 512 vals = append(vals, iter.Value()) 513 } 514 sortMapByKeyVals(keys, vals) 515 for i, key := range keys { 516 val, wasPtr, static, _, addr := d.unpackValue(key) 517 d.dump(val, wasPtr, static, !interfaceContext, addr) 518 d.w.Write(colonSpaceBytes) 519 d.ignoreNextIndent = true 520 val, wasPtr, static, _, addr = d.unpackValue(vals[i]) 521 d.dump(val, wasPtr, static, !interfaceContext, addr) 522 d.w.Write(commaNewlineBytes) 523 } 524 } else { 525 iter := v.MapRange() 526 for iter.Next() { 527 val, wasPtr, static, _, addr := d.unpackValue(iter.Key()) 528 d.dump(val, wasPtr, static, !interfaceContext, addr) 529 d.w.Write(colonSpaceBytes) 530 d.ignoreNextIndent = true 531 val, wasPtr, static, _, addr = d.unpackValue(iter.Value()) 532 d.dump(val, wasPtr, static, !interfaceContext, addr) 533 d.w.Write(commaNewlineBytes) 534 } 535 } 536 d.depth-- 537 d.indent() 538 d.w.Write(closeBraceBytes) 539 540 case reflect.Struct: 541 d.w.Write(openBraceNewlineBytes) 542 d.depth++ 543 vt := v.Type() 544 numFields := v.NumField() 545 for i := 0; i < numFields; i++ { 546 vtf := vt.Field(i) 547 if d.cs.IgnoreUnexported && vtf.PkgPath != "" { 548 continue 549 } 550 unpacked, wasPtr, static, _, addr := d.unpackValue(v.Field(i)) 551 if d.cs.OmitZero && isZero(unpacked) { 552 continue 553 } 554 d.indent() 555 d.w.Write([]byte(vtf.Name)) 556 d.w.Write(colonSpaceBytes) 557 d.ignoreNextIndent = true 558 d.dump(unpacked, wasPtr, static, false, addr) 559 d.w.Write(commaNewlineBytes) 560 } 561 d.depth-- 562 d.indent() 563 d.w.Write(closeBraceBytes) 564 565 case reflect.Uintptr: 566 printHexPtr(d.w, uintptr(v.Uint()), false) 567 568 case reflect.UnsafePointer, reflect.Chan, reflect.Func: 569 printHexPtr(d.w, v.Pointer(), true) 570 571 // There were not any other types at the time this code was written, but 572 // fall back to letting the default fmt package handle it in case any new 573 // types are added. 574 default: 575 if v.CanInterface() { 576 fmt.Fprintf(d.w, "%v", v.Interface()) 577 } else { 578 fmt.Fprintf(d.w, "%v", v.String()) 579 } 580 } 581 if wantType { 582 switch kind { 583 case reflect.Invalid, reflect.Struct, reflect.Slice, reflect.Array, reflect.Map: 584 default: 585 d.w.Write(closeParenBytes) 586 } 587 } 588 } 589 590 // writeQuoted writes the string s quoted according to the quoting strategy. 591 func (d *dumpState) writeQuoted(s string) { 592 switch d.cs.Quoting { 593 default: 594 fallthrough 595 case DoubleQuote: 596 d.w.Write([]byte(strconv.Quote(s))) 597 598 case AvoidEscapes: 599 if !needsEscape(s) || !canBackquoteString(s) { 600 d.w.Write([]byte(strconv.Quote(s))) 601 return 602 } 603 d.backQuote(s) 604 605 case AvoidEscapes | Force: 606 if !needsEscape(s) { 607 d.w.Write([]byte(strconv.Quote(s))) 608 return 609 } 610 611 fallthrough 612 case Backquote, Backquote | Force: 613 if canBackquoteString(s) { 614 d.backQuote(s) 615 return 616 } 617 618 var last int 619 inBackquote := true 620 for i, r := range s { 621 if canBackquote(r) != inBackquote { 622 if last != 0 { 623 d.w.Write(plusBytes) 624 } 625 if inBackquote { 626 if i != last { 627 d.backQuote(s[last:i]) 628 } 629 } else { 630 d.w.Write([]byte(strconv.Quote(s[last:i]))) 631 } 632 last = i 633 inBackquote = !inBackquote 634 } 635 } 636 if last != len(s) { 637 if last != 0 { 638 d.w.Write(plusBytes) 639 } 640 if !inBackquote { 641 d.w.Write([]byte(strconv.Quote(s[last:]))) 642 return 643 } 644 d.backQuote(s[last:]) 645 } 646 } 647 } 648 649 // backQuote writes s backquoted. 650 func (d *dumpState) backQuote(s string) { 651 d.w.Write(backQuoteBytes) 652 d.w.Write([]byte(s)) 653 d.w.Write(backQuoteBytes) 654 } 655 656 // needsEscape returns whether the string s needs any escape sequence to be 657 // double quote printed. 658 func needsEscape(s string) bool { 659 for _, r := range s { 660 if r == '"' || r == '\\' { 661 return true 662 } 663 if !strconv.IsPrint(r) && !strconv.IsGraphic(r) { 664 return true 665 } 666 } 667 return false 668 } 669 670 // canBackquoteString returns whether the string s can be represented 671 // unchanged as a backquoted string without non-space control characters. 672 func canBackquoteString(s string) bool { 673 for _, r := range s { 674 if !canBackquote(r) { 675 return false 676 } 677 } 678 return true 679 } 680 681 // canBackquote returns whether the rune r can be represented unchanged as a 682 // backquoted string without non-space control characters. 683 func canBackquote(r rune) bool { 684 if r == utf8.RuneError { 685 return false 686 } 687 if utf8.RuneLen(r) > 1 { 688 return r != '\ufeff' 689 } 690 return (unicode.IsSpace(r) || ' ' < r) && r != '`' && r != '\u007f' 691 } 692 693 // typeString returns the string representation of the reflect.Type with the local 694 // package selector removed. 695 func typeString(typ reflect.Type, local string) string { 696 if typ.PkgPath() != "" { 697 return strings.TrimPrefix(strings.TrimPrefix(typ.String(), local), ".") 698 } 699 switch typ.Kind() { 700 case reflect.Ptr: 701 return "*" + strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(typ.String(), "*"), local), ".") 702 case reflect.Array: 703 return fmt.Sprintf("[%d]%s", typ.Len(), typeString(typ.Elem(), local)) 704 case reflect.Chan: 705 return fmt.Sprintf("%s %s", typ.ChanDir(), typeString(typ.Elem(), local)) 706 case reflect.Map: 707 return fmt.Sprintf("map[%s]%s", typeString(typ.Key(), local), typeString(typ.Elem(), local)) 708 case reflect.Slice: 709 return fmt.Sprintf("[]%s", typeString(typ.Elem(), local)) 710 default: 711 return strings.TrimPrefix(strings.TrimPrefix(typ.String(), local), ".") 712 } 713 } 714 715 // isDefault returns whether the type is a default type absent of context. 716 func isDefault(typ reflect.Type) bool { 717 if typ.PkgPath() != "" || typ.Name() == "" { 718 return false 719 } 720 kind := typ.Kind() 721 return kind == reflect.Int || kind == reflect.Float64 || kind == reflect.String || kind == reflect.Bool 722 } 723 724 // isCompound returns whether the kind is a compound data type. 725 func isCompound(kind reflect.Kind) bool { 726 return kind == reflect.Struct || kind == reflect.Slice || kind == reflect.Array || kind == reflect.Map 727 } 728 729 // isZero returns whether v is the zero value of its type safely for all types. 730 // If v is not a kind recognised by reflect it is not zero. See TestAddedReflectValue. 731 // TODO(kortschak): Handle all cases. 732 func isZero(v reflect.Value) bool { 733 if kind := v.Kind(); kind == reflect.Invalid || kind > reflect.UnsafePointer { 734 return false 735 } 736 return v.IsZero() 737 } 738 739 // fdump is a helper function to consolidate the logic from the various public 740 // methods which take varying writers and config states. 741 func fdump(cs *ConfigState, w io.Writer, a interface{}) { 742 if a == nil { 743 w.Write(interfaceBytes) 744 w.Write(openParenBytes) 745 w.Write(nilBytes) 746 w.Write(closeParenBytes) 747 w.Write(newlineBytes) 748 return 749 } 750 751 d := dumpState{w: w, cs: cs} 752 d.pointers = make(map[uintptr]int) 753 v := reflect.ValueOf(a) 754 var addr uintptr 755 if v.CanAddr() { 756 addr = v.Addr().Pointer() 757 } 758 d.displayed = make(map[addrType]struct{}) 759 if cs.CommentPointers { 760 d.nodes = make(map[addrType]struct{}) 761 d.walk(v, false, false, false, addr) 762 } 763 d.dump(v, false, false, false, addr) 764 d.w.Write(newlineBytes) 765 } 766 767 // Fdump formats and displays the passed arguments to io.Writer w. It formats 768 // exactly the same as Dump. 769 func Fdump(w io.Writer, a interface{}) { 770 fdump(&Config, w, a) 771 } 772 773 // Sdump returns a string with the passed arguments formatted exactly the same 774 // as Dump. 775 func Sdump(a interface{}) string { 776 var buf bytes.Buffer 777 fdump(&Config, &buf, a) 778 return buf.String() 779 } 780 781 /* 782 Dump displays the passed parameters to standard out with newlines, customizable 783 indentation, and additional debug information such as complete types and all 784 pointer addresses used to indirect to the final value. It provides the 785 following features over the built-in printing facilities provided by the fmt 786 package: 787 788 * Pointers are dereferenced and followed 789 * Circular data structures are detected and annotated 790 * Byte arrays and slices are dumped in a way similar to the hexdump -C command, 791 which includes byte values in hex, and ASCII output 792 793 The configuration options are controlled by an exported package global, 794 utter.Config. See ConfigState for options documentation. 795 796 See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to 797 get the formatted result as a string. 798 */ 799 func Dump(a interface{}) { 800 fdump(&Config, os.Stdout, a) 801 }