handle cyclic references
This commit is contained in:
parent
67225d3ae5
commit
7c66a3e382
@ -9,7 +9,7 @@ import (
|
|||||||
func TestDebugNode(t *testing.T) {
|
func TestDebugNode(t *testing.T) {
|
||||||
const expect = `"foobarbaz"`
|
const expect = `"foobarbaz"`
|
||||||
o := "foobarbaz"
|
o := "foobarbaz"
|
||||||
n := reflectValue(none, reflect.ValueOf(o))
|
n := reflectValue(none, &pending{values: make(map[valueKey]nodeRef)}, reflect.ValueOf(o))
|
||||||
s := fmt.Sprint(n)
|
s := fmt.Sprint(n)
|
||||||
if s != expect {
|
if s != expect {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
|
21
notation.go
21
notation.go
@ -26,6 +26,20 @@ type wrapLen struct {
|
|||||||
first, max, last int
|
first, max, last int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type valueKey struct {
|
||||||
|
typ reflect.Type
|
||||||
|
ptr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeRef struct {
|
||||||
|
id, refCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
type pending struct {
|
||||||
|
values map[valueKey]nodeRef
|
||||||
|
idCounter int
|
||||||
|
}
|
||||||
|
|
||||||
type node struct {
|
type node struct {
|
||||||
len int
|
len int
|
||||||
wrapLen wrapLen
|
wrapLen wrapLen
|
||||||
@ -151,7 +165,12 @@ func fprintValues(w io.Writer, o opts, v []interface{}) (int, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
n := reflectValue(o, reflect.ValueOf(vi))
|
n := reflectValue(
|
||||||
|
o,
|
||||||
|
&pending{values: make(map[valueKey]nodeRef)},
|
||||||
|
reflect.ValueOf(vi),
|
||||||
|
)
|
||||||
|
|
||||||
if o&wrap != 0 {
|
if o&wrap != 0 {
|
||||||
n = nodeLen(tab, n)
|
n = nodeLen(tab, n)
|
||||||
n = wrapNode(tab, cols0, cols0, cols1, n)
|
n = wrapNode(tab, cols0, cols0, cols1, n)
|
||||||
|
110
reflect.go
110
reflect.go
@ -60,7 +60,7 @@ func reflectNil(o opts, groupUnnamedType bool, r reflect.Value) node {
|
|||||||
return nodeOf(reflectType(rt), "(nil)")
|
return nodeOf(reflectType(rt), "(nil)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectItems(o opts, prefix string, r reflect.Value) node {
|
func reflectItems(o opts, p *pending, prefix string, r reflect.Value) node {
|
||||||
typ := r.Type()
|
typ := r.Type()
|
||||||
var items wrapper
|
var items wrapper
|
||||||
if typ.Elem().Name() == "uint8" {
|
if typ.Elem().Name() == "uint8" {
|
||||||
@ -77,7 +77,7 @@ func reflectItems(o opts, prefix string, r reflect.Value) node {
|
|||||||
for i := 0; i < r.Len(); i++ {
|
for i := 0; i < r.Len(); i++ {
|
||||||
items.items = append(
|
items.items = append(
|
||||||
items.items,
|
items.items,
|
||||||
reflectValue(itemOpts, r.Index(i)),
|
reflectValue(itemOpts, p, r.Index(i)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,8 +101,8 @@ func reflectHidden(o opts, hidden string, r reflect.Value) node {
|
|||||||
return reflectType(r.Type())
|
return reflectType(r.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectArray(o opts, r reflect.Value) node {
|
func reflectArray(o opts, p *pending, r reflect.Value) node {
|
||||||
return reflectItems(o, fmt.Sprintf("[%d]", r.Len()), r)
|
return reflectItems(o, p, fmt.Sprintf("[%d]", r.Len()), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectChan(o opts, r reflect.Value) node {
|
func reflectChan(o opts, r reflect.Value) node {
|
||||||
@ -113,12 +113,12 @@ func reflectFunc(o opts, r reflect.Value) node {
|
|||||||
return reflectHidden(o, "func()", r)
|
return reflectHidden(o, "func()", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectInterface(o opts, r reflect.Value) node {
|
func reflectInterface(o opts, p *pending, r reflect.Value) node {
|
||||||
if r.IsNil() {
|
if r.IsNil() {
|
||||||
return reflectNil(o, false, r)
|
return reflectNil(o, false, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
e := reflectValue(o, r.Elem())
|
e := reflectValue(o, p, r.Elem())
|
||||||
if _, t, _ := withType(o); !t {
|
if _, t, _ := withType(o); !t {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ func reflectInterface(o opts, r reflect.Value) node {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectMap(o opts, r reflect.Value) node {
|
func reflectMap(o opts, p *pending, r reflect.Value) node {
|
||||||
if r.IsNil() {
|
if r.IsNil() {
|
||||||
return reflectNil(o, true, r)
|
return reflectNil(o, true, r)
|
||||||
}
|
}
|
||||||
@ -149,7 +149,7 @@ func reflectMap(o opts, r reflect.Value) node {
|
|||||||
sn := make(map[string]node)
|
sn := make(map[string]node)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
nk := reflectValue(itemOpts, key)
|
nk := reflectValue(itemOpts, p, key)
|
||||||
nkeys = append(nkeys, nk)
|
nkeys = append(nkeys, nk)
|
||||||
wr := writer{w: &b}
|
wr := writer{w: &b}
|
||||||
fprint(&wr, 0, nk)
|
fprint(&wr, 0, nk)
|
||||||
@ -169,7 +169,7 @@ func reflectMap(o opts, r reflect.Value) node {
|
|||||||
nodeOf(
|
nodeOf(
|
||||||
sn[skey],
|
sn[skey],
|
||||||
": ",
|
": ",
|
||||||
reflectValue(itemOpts, r.MapIndex(sv[skey])),
|
reflectValue(itemOpts, p, r.MapIndex(sv[skey])),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -181,12 +181,12 @@ func reflectMap(o opts, r reflect.Value) node {
|
|||||||
return nodeOf(reflectType(r.Type()), "{", items, "}")
|
return nodeOf(reflectType(r.Type()), "{", items, "}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectPointer(o opts, r reflect.Value) node {
|
func reflectPointer(o opts, p *pending, r reflect.Value) node {
|
||||||
if r.IsNil() {
|
if r.IsNil() {
|
||||||
return reflectNil(o, true, r)
|
return reflectNil(o, true, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
e := reflectValue(o, r.Elem())
|
e := reflectValue(o, p, r.Elem())
|
||||||
if _, t, _ := withType(o); !t {
|
if _, t, _ := withType(o); !t {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@ -194,12 +194,12 @@ func reflectPointer(o opts, r reflect.Value) node {
|
|||||||
return nodeOf("*", e)
|
return nodeOf("*", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectList(o opts, r reflect.Value) node {
|
func reflectList(o opts, p *pending, r reflect.Value) node {
|
||||||
if r.IsNil() {
|
if r.IsNil() {
|
||||||
return reflectNil(o, true, r)
|
return reflectNil(o, true, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
return reflectItems(o, "[]", r)
|
return reflectItems(o, p, "[]", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectString(o opts, r reflect.Value) node {
|
func reflectString(o opts, r reflect.Value) node {
|
||||||
@ -248,7 +248,7 @@ func reflectString(o opts, r reflect.Value) node {
|
|||||||
return nodeOf(tn, "(", wrapper{items: []node{n}}, ")")
|
return nodeOf(tn, "(", wrapper{items: []node{n}}, ")")
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectStruct(o opts, r reflect.Value) node {
|
func reflectStruct(o opts, p *pending, r reflect.Value) node {
|
||||||
wr := wrapper{sep: ", ", suffix: ","}
|
wr := wrapper{sep: ", ", suffix: ","}
|
||||||
|
|
||||||
fieldOpts := o | skipTypes
|
fieldOpts := o | skipTypes
|
||||||
@ -262,6 +262,7 @@ func reflectStruct(o opts, r reflect.Value) node {
|
|||||||
": ",
|
": ",
|
||||||
reflectValue(
|
reflectValue(
|
||||||
fieldOpts,
|
fieldOpts,
|
||||||
|
p,
|
||||||
r.FieldByName(name),
|
r.FieldByName(name),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -287,17 +288,64 @@ func reflectUnsafePointer(o opts, r reflect.Value) node {
|
|||||||
return nodeOf(reflectType(r.Type()), "(pointer)")
|
return nodeOf(reflectType(r.Type()), "(pointer)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectValue(o opts, r reflect.Value) node {
|
func checkPending(p *pending, r reflect.Value) (applyRef func(node) node, ref node, isPending bool) {
|
||||||
|
applyRef = func(n node) node { return n }
|
||||||
|
switch r.Kind() {
|
||||||
|
case reflect.Slice, reflect.Map:
|
||||||
|
case reflect.Ptr:
|
||||||
|
if r.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var nr nodeRef
|
||||||
|
key := valueKey{typ: r.Type(), ptr: r.Pointer()}
|
||||||
|
nr, isPending = p.values[key]
|
||||||
|
if isPending {
|
||||||
|
nr.refCount++
|
||||||
|
p.values[key] = nr
|
||||||
|
ref = nodeOf("r", nr.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nr = nodeRef{id: p.idCounter}
|
||||||
|
p.idCounter++
|
||||||
|
p.values[key] = nr
|
||||||
|
applyRef = func(n node) node {
|
||||||
|
nr = p.values[key]
|
||||||
|
if nr.refCount > 0 {
|
||||||
|
n.parts = append(
|
||||||
|
[]interface{}{"r", nr.id, "="},
|
||||||
|
n.parts...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(p.values, key)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func reflectValue(o opts, p *pending, r reflect.Value) node {
|
||||||
|
applyRef, ref, isPending := checkPending(p, r)
|
||||||
|
if isPending {
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
|
||||||
|
var n node
|
||||||
switch r.Kind() {
|
switch r.Kind() {
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return reflectPrimitive(o, r, r.Bool(), "bool")
|
n = reflectPrimitive(o, r, r.Bool(), "bool")
|
||||||
case
|
case
|
||||||
reflect.Int,
|
reflect.Int,
|
||||||
reflect.Int8,
|
reflect.Int8,
|
||||||
reflect.Int16,
|
reflect.Int16,
|
||||||
reflect.Int32,
|
reflect.Int32,
|
||||||
reflect.Int64:
|
reflect.Int64:
|
||||||
return reflectPrimitive(o, r, r.Int(), "int")
|
n = reflectPrimitive(o, r, r.Int(), "int")
|
||||||
case
|
case
|
||||||
reflect.Uint,
|
reflect.Uint,
|
||||||
reflect.Uint8,
|
reflect.Uint8,
|
||||||
@ -305,30 +353,32 @@ func reflectValue(o opts, r reflect.Value) node {
|
|||||||
reflect.Uint32,
|
reflect.Uint32,
|
||||||
reflect.Uint64,
|
reflect.Uint64,
|
||||||
reflect.Uintptr:
|
reflect.Uintptr:
|
||||||
return reflectPrimitive(o, r, r.Uint())
|
n = reflectPrimitive(o, r, r.Uint())
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return reflectPrimitive(o, r, r.Float())
|
n = reflectPrimitive(o, r, r.Float())
|
||||||
case reflect.Complex64, reflect.Complex128:
|
case reflect.Complex64, reflect.Complex128:
|
||||||
return reflectPrimitive(o, r, r.Complex())
|
n = reflectPrimitive(o, r, r.Complex())
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
return reflectArray(o, r)
|
n = reflectArray(o, p, r)
|
||||||
case reflect.Chan:
|
case reflect.Chan:
|
||||||
return reflectChan(o, r)
|
n = reflectChan(o, r)
|
||||||
case reflect.Func:
|
case reflect.Func:
|
||||||
return reflectFunc(o, r)
|
n = reflectFunc(o, r)
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
return reflectInterface(o, r)
|
n = reflectInterface(o, p, r)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return reflectMap(o, r)
|
n = reflectMap(o, p, r)
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
return reflectPointer(o, r)
|
n = reflectPointer(o, p, r)
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
return reflectList(o, r)
|
n = reflectList(o, p, r)
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return reflectString(o, r)
|
n = reflectString(o, r)
|
||||||
case reflect.UnsafePointer:
|
case reflect.UnsafePointer:
|
||||||
return reflectUnsafePointer(o, r)
|
n = reflectUnsafePointer(o, r)
|
||||||
default:
|
default:
|
||||||
return reflectStruct(o, r)
|
n = reflectStruct(o, p, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return applyRef(n)
|
||||||
}
|
}
|
||||||
|
@ -1001,3 +1001,78 @@ func TestSingleLongString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCyclicReferences(t *testing.T) {
|
||||||
|
t.Run("slice", func(t *testing.T) {
|
||||||
|
const expect = `r0=[]{r0}`
|
||||||
|
l := []interface{}{"foo"}
|
||||||
|
l[0] = l
|
||||||
|
s := Sprint(l)
|
||||||
|
if s != expect {
|
||||||
|
t.Fatalf("expected: %s, got: %s", expect, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("map", func(t *testing.T) {
|
||||||
|
const expect = `r0=map{"foo": r0}`
|
||||||
|
m := map[string]interface{}{"foo": "bar"}
|
||||||
|
m["foo"] = m
|
||||||
|
s := Sprint(m)
|
||||||
|
if s != expect {
|
||||||
|
t.Fatalf("expected: %s, got: %s", expect, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer", func(t *testing.T) {
|
||||||
|
const expect = `r0=r0`
|
||||||
|
p := new(interface{})
|
||||||
|
*p = p
|
||||||
|
s := Sprint(p)
|
||||||
|
if s != expect {
|
||||||
|
t.Fatalf("expected: %s, got: %s", expect, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple refs", func(t *testing.T) {
|
||||||
|
const expect = `{f0: r1={f0: r2={f0: nil, f1: r1, f2: r2}, f1: nil, f2: nil}, f1: nil, f2: nil}`
|
||||||
|
type typ struct{ f0, f1, f2 *typ }
|
||||||
|
v0 := new(typ)
|
||||||
|
v1 := new(typ)
|
||||||
|
v2 := new(typ)
|
||||||
|
v0.f0 = v1
|
||||||
|
v1.f0 = v2
|
||||||
|
v2.f1 = v1
|
||||||
|
v2.f2 = v2
|
||||||
|
s := Sprintw(v0)
|
||||||
|
if s != expect {
|
||||||
|
t.Fatalf("expected: %s, got: %s", expect, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple refs, different subtrees", func(t *testing.T) {
|
||||||
|
const expect = `{
|
||||||
|
f0: r1={f0: r2={f0: nil, f1: r1, f2: r2}, f1: nil, f2: nil},
|
||||||
|
f1: r3={f0: r4={f0: nil, f1: r3, f2: r4}, f1: nil, f2: nil},
|
||||||
|
f2: nil,
|
||||||
|
}`
|
||||||
|
|
||||||
|
type typ struct{ f0, f1, f2 *typ }
|
||||||
|
v0 := new(typ)
|
||||||
|
v11 := new(typ)
|
||||||
|
v12 := new(typ)
|
||||||
|
v21 := new(typ)
|
||||||
|
v22 := new(typ)
|
||||||
|
v0.f0 = v11
|
||||||
|
v11.f0 = v12
|
||||||
|
v12.f1 = v11
|
||||||
|
v12.f2 = v12
|
||||||
|
v0.f1 = v21
|
||||||
|
v21.f0 = v22
|
||||||
|
v22.f1 = v21
|
||||||
|
v22.f2 = v22
|
||||||
|
s := Sprintw(v0)
|
||||||
|
if s != expect {
|
||||||
|
t.Fatalf("expected: %s, got: %s", expect, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user