Support omitting duplicates and using ordinals instead of pointers when dumping

If this is considered reasonable, I'll be happy to make the corresponding
changes to the formatting code as well.
This commit is contained in:
James Craig Burley 2019-11-30 14:51:11 -05:00
parent d8f796af33
commit ba57249c50
4 changed files with 178 additions and 2 deletions

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 uint) {
buf := make([]byte, 18)
base := uint(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,19 @@ 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. 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
}
// 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]uint
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
nextOrdinal uint
}
// indent performs indentation according to the depth level and cs.Indent
@ -95,7 +97,9 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
// references.
nilFound := false
cycleFound := false
duplicateFound := false
indirects := 0
ordinal := uint(0)
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
@ -104,12 +108,39 @@ 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
}
if d.allPointers != nil { // d.cs.NoDuplicates || d.cs.UseOrdinals
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
}
}
if ordinal > 0 {
pointerChain = append(pointerChain, uintptr(ordinal))
} else {
pointerChain = append(pointerChain, addr)
}
if cycleFound || duplicateFound {
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
@ -135,7 +166,11 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
if d.cs.UseOrdinals {
printOrdinal(d.w, uint(addr))
} else {
printHexPtr(d.w, addr)
}
}
d.w.Write(closeParenBytes)
}
@ -149,6 +184,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)
@ -462,6 +500,9 @@ 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 {
d.allPointers = make(map[uintptr]uint)
}
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}

View File

@ -1040,3 +1040,105 @@ 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 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
}
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)(<already seen>)
}
`
if s != expected {
t.Errorf("Duplicate-pointers mismatch:\n %v %v", s, expected)
}
}