This commit is contained in:
James Craig Burley 2020-01-14 15:59:31 -05:00 committed by GitHub
commit 9314df5bc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 263 additions and 4 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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.

View File

@ -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
}
}
} }
} }

View File

@ -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)
}
}

View File

@ -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