Merge 8a5623ace0
into d8f796af33
This commit is contained in:
commit
9314df5bc5
24
README.md
24
README.md
|
@ -185,6 +185,30 @@ options. See the ConfigState documentation for more details.
|
||||||
spewed to strings and sorted by those strings. This is only considered
|
spewed to strings and sorted by those strings. This is only considered
|
||||||
if SortKeys is true.
|
if SortKeys is true.
|
||||||
|
|
||||||
|
* NoDuplicates
|
||||||
|
NoDuplicates specifies that any given pointer should have
|
||||||
|
its dereference dumped only once. This is similar to
|
||||||
|
circularity detection, but applies to all pointers across a
|
||||||
|
given dump action (or SpewState). This can consume much memory.
|
||||||
|
|
||||||
|
* UseOrdinals
|
||||||
|
UseOrdinals specifies that pointer values are to be
|
||||||
|
replaced with monotonically increasing integers. It has no
|
||||||
|
effect if DisablePointerAddresses is true; else, it
|
||||||
|
provides some degree of stability across runs versus
|
||||||
|
printing out raw pointers. This can consume much memory.
|
||||||
|
|
||||||
|
* PreserveSpewState
|
||||||
|
Preserve state of a spew (dump, format) operation for use
|
||||||
|
by the next such operation (including across multiple
|
||||||
|
arguments to a single API call as well as across API
|
||||||
|
calls). Currently useful only when NoDuplicates is true.
|
||||||
|
|
||||||
|
* SpewState
|
||||||
|
The state of the last spew (dump, format) operation, if
|
||||||
|
PreserveSpewState was true when that operation was started.
|
||||||
|
Can be copied to a different ConfigState object.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Unsafe Package Dependency
|
## Unsafe Package Dependency
|
||||||
|
|
|
@ -51,6 +51,8 @@ var (
|
||||||
maxShortBytes = []byte("<max>")
|
maxShortBytes = []byte("<max>")
|
||||||
circularBytes = []byte("<already shown>")
|
circularBytes = []byte("<already shown>")
|
||||||
circularShortBytes = []byte("<shown>")
|
circularShortBytes = []byte("<shown>")
|
||||||
|
duplicateBytes = []byte("<already seen>")
|
||||||
|
duplicateShortBytes = []byte("<seen>")
|
||||||
invalidAngleBytes = []byte("<invalid>")
|
invalidAngleBytes = []byte("<invalid>")
|
||||||
openBracketBytes = []byte("[")
|
openBracketBytes = []byte("[")
|
||||||
closeBracketBytes = []byte("]")
|
closeBracketBytes = []byte("]")
|
||||||
|
@ -214,6 +216,24 @@ func printHexPtr(w io.Writer, p uintptr) {
|
||||||
w.Write(buf)
|
w.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printOrdinal(w io.Writer, ord uintptr) {
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
base := uintptr(10)
|
||||||
|
i := len(buf) - 1
|
||||||
|
for ord >= base {
|
||||||
|
buf[i] = hexDigits[ord%base]
|
||||||
|
ord /= base
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
buf[i] = hexDigits[ord]
|
||||||
|
|
||||||
|
i--
|
||||||
|
buf[i] = '#'
|
||||||
|
|
||||||
|
buf = buf[i:]
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||||
// elements to be sorted.
|
// elements to be sorted.
|
||||||
type valuesSorter struct {
|
type valuesSorter struct {
|
||||||
|
|
|
@ -98,6 +98,30 @@ type ConfigState struct {
|
||||||
// be spewed to strings and sorted by those strings. This is only
|
// be spewed to strings and sorted by those strings. This is only
|
||||||
// considered if SortKeys is true.
|
// considered if SortKeys is true.
|
||||||
SpewKeys bool
|
SpewKeys bool
|
||||||
|
|
||||||
|
// NoDuplicates specifies that any given pointer should have
|
||||||
|
// its dereference dumped only once. This is similar to
|
||||||
|
// circularity detection, but applies to all pointers across a
|
||||||
|
// given dump action (or SpewState). This can consume much memory.
|
||||||
|
NoDuplicates bool
|
||||||
|
|
||||||
|
// UseOrdinals specifies that pointer values are to be
|
||||||
|
// replaced with monotonically increasing integers. It has no
|
||||||
|
// effect if DisablePointerAddresses is true; else, it
|
||||||
|
// provides some degree of stability across runs versus
|
||||||
|
// printing out raw pointers. This can consume much memory.
|
||||||
|
UseOrdinals bool
|
||||||
|
|
||||||
|
// Preserve state of a spew (dump, format) operation for use
|
||||||
|
// by the next such operation (including across multiple
|
||||||
|
// arguments to a single API call as well as across API
|
||||||
|
// calls). Currently useful only when NoDuplicates is true.
|
||||||
|
PreserveSpewState bool
|
||||||
|
|
||||||
|
// The state of the last spew (dump, format) operation, if
|
||||||
|
// PreserveSpewState was true when that operation was started.
|
||||||
|
// Can be copied to a different ConfigState object.
|
||||||
|
SpewState SpewState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the active configuration of the top-level functions.
|
// Config is the active configuration of the top-level functions.
|
||||||
|
|
79
spew/dump.go
79
spew/dump.go
|
@ -52,9 +52,11 @@ type dumpState struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
depth int
|
depth int
|
||||||
pointers map[uintptr]int
|
pointers map[uintptr]int
|
||||||
|
allPointers map[uintptr]uintptr
|
||||||
ignoreNextType bool
|
ignoreNextType bool
|
||||||
ignoreNextIndent bool
|
ignoreNextIndent bool
|
||||||
cs *ConfigState
|
cs *ConfigState
|
||||||
|
nextOrdinal uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
// indent performs indentation according to the depth level and cs.Indent
|
// indent performs indentation according to the depth level and cs.Indent
|
||||||
|
@ -77,6 +79,50 @@ func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dumpState) isPointerSeen(addr uintptr) (seen bool, ordinal uintptr) {
|
||||||
|
ordinal = uintptr(addr)
|
||||||
|
|
||||||
|
if d.allPointers == nil {
|
||||||
|
return false, ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
// d.cs.NoDuplicates || d.cs.UseOrdinals
|
||||||
|
duplicateFound := false
|
||||||
|
if ord, ok := d.allPointers[addr]; ok {
|
||||||
|
if d.cs.UseOrdinals {
|
||||||
|
ordinal = ord
|
||||||
|
}
|
||||||
|
if d.cs.NoDuplicates {
|
||||||
|
duplicateFound = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if d.cs.UseOrdinals {
|
||||||
|
d.nextOrdinal++
|
||||||
|
ordinal = d.nextOrdinal
|
||||||
|
}
|
||||||
|
d.allPointers[addr] = ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
return duplicateFound, ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dumpState) printPtrOrOrdinal(addr uintptr) {
|
||||||
|
if d.cs.UseOrdinals {
|
||||||
|
printOrdinal(d.w, addr)
|
||||||
|
} else {
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dumpState) printPtr(addr uintptr) {
|
||||||
|
if d.cs.UseOrdinals {
|
||||||
|
_, addr := d.isPointerSeen(addr)
|
||||||
|
printOrdinal(d.w, addr)
|
||||||
|
} else {
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
@ -95,6 +141,7 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
// references.
|
// references.
|
||||||
nilFound := false
|
nilFound := false
|
||||||
cycleFound := false
|
cycleFound := false
|
||||||
|
duplicateFound := false
|
||||||
indirects := 0
|
indirects := 0
|
||||||
ve := v
|
ve := v
|
||||||
for ve.Kind() == reflect.Ptr {
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
@ -104,12 +151,21 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
}
|
}
|
||||||
indirects++
|
indirects++
|
||||||
addr := ve.Pointer()
|
addr := ve.Pointer()
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||||
cycleFound = true
|
cycleFound = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dup, ordinal := d.isPointerSeen(addr)
|
||||||
|
|
||||||
|
pointerChain = append(pointerChain, ordinal)
|
||||||
|
|
||||||
|
if cycleFound || dup {
|
||||||
indirects--
|
indirects--
|
||||||
|
duplicateFound = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
d.pointers[addr] = d.depth
|
d.pointers[addr] = d.depth
|
||||||
|
|
||||||
ve = ve.Elem()
|
ve = ve.Elem()
|
||||||
|
@ -135,7 +191,7 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
d.w.Write(pointerChainBytes)
|
d.w.Write(pointerChainBytes)
|
||||||
}
|
}
|
||||||
printHexPtr(d.w, addr)
|
d.printPtrOrOrdinal(addr)
|
||||||
}
|
}
|
||||||
d.w.Write(closeParenBytes)
|
d.w.Write(closeParenBytes)
|
||||||
}
|
}
|
||||||
|
@ -149,6 +205,9 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
case cycleFound:
|
case cycleFound:
|
||||||
d.w.Write(circularBytes)
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
|
case duplicateFound:
|
||||||
|
d.w.Write(duplicateBytes)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
d.ignoreNextType = true
|
d.ignoreNextType = true
|
||||||
d.dump(ve)
|
d.dump(ve)
|
||||||
|
@ -431,10 +490,10 @@ func (d *dumpState) dump(v reflect.Value) {
|
||||||
d.w.Write(closeBraceBytes)
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
case reflect.Uintptr:
|
case reflect.Uintptr:
|
||||||
printHexPtr(d.w, uintptr(v.Uint()))
|
d.printPtr(uintptr(v.Uint()))
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
printHexPtr(d.w, v.Pointer())
|
d.printPtr(v.Pointer())
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
// 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
|
// fall back to letting the default fmt package handle it in case any new
|
||||||
|
@ -462,8 +521,20 @@ func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||||
|
|
||||||
d := dumpState{w: w, cs: cs}
|
d := dumpState{w: w, cs: cs}
|
||||||
d.pointers = make(map[uintptr]int)
|
d.pointers = make(map[uintptr]int)
|
||||||
|
if cs.NoDuplicates || cs.UseOrdinals {
|
||||||
|
if cs.SpewState.allPointers == nil {
|
||||||
|
d.allPointers = make(map[uintptr]uintptr)
|
||||||
|
} else {
|
||||||
|
d.allPointers = cs.SpewState.allPointers
|
||||||
|
}
|
||||||
|
}
|
||||||
d.dump(reflect.ValueOf(arg))
|
d.dump(reflect.ValueOf(arg))
|
||||||
d.w.Write(newlineBytes)
|
d.w.Write(newlineBytes)
|
||||||
|
if d.allPointers != nil { // cs.NoDuplicates || cs.UseOrdinals
|
||||||
|
if cs.PreserveSpewState {
|
||||||
|
cs.SpewState.allPointers = d.allPointers
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1040,3 +1040,117 @@ func TestDumpSortedKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDumpDuplicatePointers(t *testing.T) {
|
||||||
|
cfg := spew.ConfigState{NoDuplicates: true}
|
||||||
|
type info struct {
|
||||||
|
a int
|
||||||
|
b int
|
||||||
|
}
|
||||||
|
type twice struct {
|
||||||
|
info1 *info
|
||||||
|
info2 *info
|
||||||
|
info3 *info
|
||||||
|
}
|
||||||
|
i1 := &info{a: 1, b: 2}
|
||||||
|
ip1 := fmt.Sprintf("%p", i1)
|
||||||
|
i2 := &info{a: 3, b: 4}
|
||||||
|
ip2 := fmt.Sprintf("%p", i2)
|
||||||
|
v := twice{info1: i1, info2: i2, info3: i1}
|
||||||
|
// vt := "spew_test.twice"
|
||||||
|
s := cfg.Sdump(v)
|
||||||
|
expected := `(spew_test.twice) {
|
||||||
|
info1: (*spew_test.info)(` + ip1 + `)({
|
||||||
|
a: (int) 1,
|
||||||
|
b: (int) 2
|
||||||
|
}),
|
||||||
|
info2: (*spew_test.info)(` + ip2 + `)({
|
||||||
|
a: (int) 3,
|
||||||
|
b: (int) 4
|
||||||
|
}),
|
||||||
|
info3: (*spew_test.info)(` + ip1 + `)(<already seen>)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if s != expected {
|
||||||
|
t.Errorf("Duplicate-pointers mismatch:\n %v %v", s, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDumpOrdinals(t *testing.T) {
|
||||||
|
cfg := spew.ConfigState{UseOrdinals: true}
|
||||||
|
type info struct {
|
||||||
|
a int
|
||||||
|
b int
|
||||||
|
}
|
||||||
|
type twice struct {
|
||||||
|
info1 *info
|
||||||
|
info2 *info
|
||||||
|
info3 *info
|
||||||
|
}
|
||||||
|
i := &info{a: 1, b: 2}
|
||||||
|
v := twice{info1: i, info2: &info{a: 3, b: 4}, info3: i}
|
||||||
|
// vt := "spew_test.twice"
|
||||||
|
s := cfg.Sdump(v)
|
||||||
|
expected := `(spew_test.twice) {
|
||||||
|
info1: (*spew_test.info)(#1)({
|
||||||
|
a: (int) 1,
|
||||||
|
b: (int) 2
|
||||||
|
}),
|
||||||
|
info2: (*spew_test.info)(#2)({
|
||||||
|
a: (int) 3,
|
||||||
|
b: (int) 4
|
||||||
|
}),
|
||||||
|
info3: (*spew_test.info)(#1)({
|
||||||
|
a: (int) 1,
|
||||||
|
b: (int) 2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if s != expected {
|
||||||
|
t.Errorf("Ordinals mismatch:\n %v %v", s, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func a() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func b() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDumpDuplicateOrdinals(t *testing.T) {
|
||||||
|
cfg := spew.ConfigState{NoDuplicates: true, UseOrdinals: true}
|
||||||
|
type info struct {
|
||||||
|
a int
|
||||||
|
b int
|
||||||
|
}
|
||||||
|
type twice struct {
|
||||||
|
info1 *info
|
||||||
|
info2 *info
|
||||||
|
info3 *info
|
||||||
|
fn1 func()
|
||||||
|
fn2 func()
|
||||||
|
fn3 func()
|
||||||
|
}
|
||||||
|
i := &info{a: 1, b: 2}
|
||||||
|
v := twice{info1: i, info2: &info{a: 3, b: 4}, info3: i, fn1: a, fn2: b, fn3: a}
|
||||||
|
// vt := "spew_test.twice"
|
||||||
|
s := cfg.Sdump(v)
|
||||||
|
expected := `(spew_test.twice) {
|
||||||
|
info1: (*spew_test.info)(#1)({
|
||||||
|
a: (int) 1,
|
||||||
|
b: (int) 2
|
||||||
|
}),
|
||||||
|
info2: (*spew_test.info)(#2)({
|
||||||
|
a: (int) 3,
|
||||||
|
b: (int) 4
|
||||||
|
}),
|
||||||
|
info3: (*spew_test.info)(#1)(<already seen>),
|
||||||
|
fn1: (func()) #3,
|
||||||
|
fn2: (func()) #4,
|
||||||
|
fn3: (func()) #3
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if s != expected {
|
||||||
|
t.Errorf("Duplicate-pointers+ordinals mismatch:\n %v %v", s, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,12 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Wraps the end state of a spew (dump or format) operation for use by
|
||||||
|
// the next operation, if so configured.
|
||||||
|
type SpewState struct {
|
||||||
|
allPointers map[uintptr]uintptr
|
||||||
|
}
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
// returns the formatted string as a value that satisfies error. See
|
// returns the formatted string as a value that satisfies error. See
|
||||||
|
|
Loading…
Reference in New Issue