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

View File

@ -51,6 +51,8 @@ var (
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
duplicateBytes = []byte("<already seen>")
duplicateShortBytes = []byte("<seen>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
@ -214,6 +216,24 @@ func printHexPtr(w io.Writer, p uintptr) {
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
// elements to be sorted.
type valuesSorter struct {

View File

@ -98,6 +98,30 @@ type ConfigState struct {
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
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.

View File

@ -52,9 +52,11 @@ type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
allPointers map[uintptr]uintptr
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
nextOrdinal uintptr
}
// 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
}
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.
func (d *dumpState) dumpPtr(v reflect.Value) {
// 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.
nilFound := false
cycleFound := false
duplicateFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
@ -104,12 +151,21 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
}
dup, ordinal := d.isPointerSeen(addr)
pointerChain = append(pointerChain, ordinal)
if cycleFound || dup {
indirects--
duplicateFound = true
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
@ -135,7 +191,7 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
d.printPtrOrOrdinal(addr)
}
d.w.Write(closeParenBytes)
}
@ -149,6 +205,9 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
case cycleFound:
d.w.Write(circularBytes)
case duplicateFound:
d.w.Write(duplicateBytes)
default:
d.ignoreNextType = true
d.dump(ve)
@ -431,10 +490,10 @@ func (d *dumpState) dump(v reflect.Value) {
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
d.printPtr(uintptr(v.Uint()))
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
// 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.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.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"
)
// 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
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See