Skip to content

Commit

Permalink
utter: don't re-render values that have already been displayed
Browse files Browse the repository at this point in the history
This reflects the behaviour of cycles, but works for the case that a
shared field or element exists in non-cyclic data.
  • Loading branch information
kortschak committed Aug 27, 2021
1 parent 4308090 commit 8289a1b
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
29 changes: 26 additions & 3 deletions dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type dumpState struct {
depth int
pointers map[uintptr]int
nodes map[addrType]struct{}
displayed map[addrType]struct{}
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
Expand Down Expand Up @@ -99,6 +100,12 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
// Keep list of all dereferenced pointers to show later.
var pointerChain []uintptr

// Record the value's address.
value := addrType{addr: v.Pointer()}

// Keep the original value in case we have already displayed it.
orig := v

// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
Expand Down Expand Up @@ -131,15 +138,28 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
}
}

// Record the value's element type and check whether it has been displayed
value.typ = v.Type()
_, displayed := d.displayed[value]

// Display type information.
d.w.Write(bytes.Repeat(ampersandBytes, indirects))
var typeBytes []byte
if displayed {
d.w.Write(openParenBytes)
typeBytes = []byte(orig.Type().String())
} else {
d.w.Write(bytes.Repeat(ampersandBytes, indirects))
typeBytes = []byte(v.Type().String())
}
kind := v.Kind()
bufferedChan := kind == reflect.Chan && v.Cap() != 0
if kind == reflect.Ptr || bufferedChan {
d.w.Write(openParenBytes)
}
typeBytes := []byte(v.Type().String())
d.w.Write(bytes.ReplaceAll(typeBytes, interfaceTypeBytes, interfaceBytes))
if displayed {
d.w.Write(closeParenBytes)
}
switch {
case bufferedChan:
switch len := v.Len(); len {
Expand Down Expand Up @@ -174,7 +194,7 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
d.w.Write(nilBytes)
d.w.Write(closeParenBytes)

case cycleFound:
case cycleFound, displayed:
d.w.Write(circularBytes)

default:
Expand All @@ -183,6 +203,8 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
if v.CanAddr() {
addr = v.Addr().Pointer()
}
// Mark the value as having been displayed.
d.displayed[value] = struct{}{}
d.dump(v, true, false, addr)
}
}
Expand Down Expand Up @@ -597,6 +619,7 @@ func fdump(cs *ConfigState, w io.Writer, a interface{}) {
if v.CanAddr() {
addr = v.Addr().Pointer()
}
d.displayed = make(map[addrType]struct{})
if cs.CommentPointers {
d.nodes = make(map[addrType]struct{})
d.walk(v, false, false, addr)
Expand Down
26 changes: 25 additions & 1 deletion dump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,11 +596,35 @@ func addStructDumpTests() {
v4t := "utter_test.embedwrap"
v4t2 := "utter_test.embed"
v4t3 := "string"
v4s := "{\n embed: &" + v4t2 + "{\n a: " + v4t3 + "(\"embedstr\"),\n },\n e: &" + v4t2 + "{\n a: " + v4t3 + "(\"embedstr\"),\n },\n}"
v4s := "{\n embed: &" + v4t2 + "{\n a: " + v4t3 + "(\"embedstr\"),\n },\n e: (*" + v4t2 + ")(<already shown>),\n}"
addDumpTest(v4, v4t+v4s+"\n")
addDumpTest(pv4, "&"+v4t+v4s+"\n")
addDumpTest(&pv4, "&&"+v4t+v4s+"\n")
addDumpTest(nv4, "(*"+v4t+")(nil)\n")

// Struct that has fields that share a value in pointer that is not a cycle.
type ss5 struct{ s string }
type s5 struct {
p1 *ss5
p2 *ss5
}
ip1 := &ss5{"shared"}
v5 := s5{ip1, ip1}
v5t := "utter_test.s5"
v5s := "utter_test.ss5"
addDumpTest(v5, v5t+"{\n p1: &"+v5s+"{\n s: string(\"shared\"),\n },\n p2: (*"+v5s+")(<already shown>),\n}\n")

// Struct that has fields that share a value in pointer chain that is not a cycle.
type ss6 struct{ s string }
type s6 struct {
p1 **ss6
p2 **ss6
}
ipp1 := &ss6{"shared"}
v6 := s6{&ipp1, &ipp1}
v6t := "utter_test.s6"
v6s := "utter_test.ss6"
addDumpTest(v6, v6t+"{\n p1: &&"+v6s+"{\n s: string(\"shared\"),\n },\n p2: (**"+v6s+")(<already shown>),\n}\n")
}

func addUintptrDumpTests() {
Expand Down

0 comments on commit 8289a1b

Please sign in to comment.