1*88d15eacSSasha Smundak// Copyright 2020, The Go Authors. All rights reserved. 2*88d15eacSSasha Smundak// Use of this source code is governed by a BSD-style 3*88d15eacSSasha Smundak// license that can be found in the LICENSE file. 4*88d15eacSSasha Smundak 5*88d15eacSSasha Smundakpackage cmp 6*88d15eacSSasha Smundak 7*88d15eacSSasha Smundakimport ( 8*88d15eacSSasha Smundak "fmt" 9*88d15eacSSasha Smundak "reflect" 10*88d15eacSSasha Smundak "strings" 11*88d15eacSSasha Smundak 12*88d15eacSSasha Smundak "github.com/google/go-cmp/cmp/internal/flags" 13*88d15eacSSasha Smundak "github.com/google/go-cmp/cmp/internal/value" 14*88d15eacSSasha Smundak) 15*88d15eacSSasha Smundak 16*88d15eacSSasha Smundakconst ( 17*88d15eacSSasha Smundak pointerDelimPrefix = "⟪" 18*88d15eacSSasha Smundak pointerDelimSuffix = "⟫" 19*88d15eacSSasha Smundak) 20*88d15eacSSasha Smundak 21*88d15eacSSasha Smundak// formatPointer prints the address of the pointer. 22*88d15eacSSasha Smundakfunc formatPointer(p value.Pointer, withDelims bool) string { 23*88d15eacSSasha Smundak v := p.Uintptr() 24*88d15eacSSasha Smundak if flags.Deterministic { 25*88d15eacSSasha Smundak v = 0xdeadf00f // Only used for stable testing purposes 26*88d15eacSSasha Smundak } 27*88d15eacSSasha Smundak if withDelims { 28*88d15eacSSasha Smundak return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix 29*88d15eacSSasha Smundak } 30*88d15eacSSasha Smundak return formatHex(uint64(v)) 31*88d15eacSSasha Smundak} 32*88d15eacSSasha Smundak 33*88d15eacSSasha Smundak// pointerReferences is a stack of pointers visited so far. 34*88d15eacSSasha Smundaktype pointerReferences [][2]value.Pointer 35*88d15eacSSasha Smundak 36*88d15eacSSasha Smundakfunc (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) { 37*88d15eacSSasha Smundak if deref && vx.IsValid() { 38*88d15eacSSasha Smundak vx = vx.Addr() 39*88d15eacSSasha Smundak } 40*88d15eacSSasha Smundak if deref && vy.IsValid() { 41*88d15eacSSasha Smundak vy = vy.Addr() 42*88d15eacSSasha Smundak } 43*88d15eacSSasha Smundak switch d { 44*88d15eacSSasha Smundak case diffUnknown, diffIdentical: 45*88d15eacSSasha Smundak pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)} 46*88d15eacSSasha Smundak case diffRemoved: 47*88d15eacSSasha Smundak pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}} 48*88d15eacSSasha Smundak case diffInserted: 49*88d15eacSSasha Smundak pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)} 50*88d15eacSSasha Smundak } 51*88d15eacSSasha Smundak *ps = append(*ps, pp) 52*88d15eacSSasha Smundak return pp 53*88d15eacSSasha Smundak} 54*88d15eacSSasha Smundak 55*88d15eacSSasha Smundakfunc (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) { 56*88d15eacSSasha Smundak p = value.PointerOf(v) 57*88d15eacSSasha Smundak for _, pp := range *ps { 58*88d15eacSSasha Smundak if p == pp[0] || p == pp[1] { 59*88d15eacSSasha Smundak return p, true 60*88d15eacSSasha Smundak } 61*88d15eacSSasha Smundak } 62*88d15eacSSasha Smundak *ps = append(*ps, [2]value.Pointer{p, p}) 63*88d15eacSSasha Smundak return p, false 64*88d15eacSSasha Smundak} 65*88d15eacSSasha Smundak 66*88d15eacSSasha Smundakfunc (ps *pointerReferences) Pop() { 67*88d15eacSSasha Smundak *ps = (*ps)[:len(*ps)-1] 68*88d15eacSSasha Smundak} 69*88d15eacSSasha Smundak 70*88d15eacSSasha Smundak// trunkReferences is metadata for a textNode indicating that the sub-tree 71*88d15eacSSasha Smundak// represents the value for either pointer in a pair of references. 72*88d15eacSSasha Smundaktype trunkReferences struct{ pp [2]value.Pointer } 73*88d15eacSSasha Smundak 74*88d15eacSSasha Smundak// trunkReference is metadata for a textNode indicating that the sub-tree 75*88d15eacSSasha Smundak// represents the value for the given pointer reference. 76*88d15eacSSasha Smundaktype trunkReference struct{ p value.Pointer } 77*88d15eacSSasha Smundak 78*88d15eacSSasha Smundak// leafReference is metadata for a textNode indicating that the value is 79*88d15eacSSasha Smundak// truncated as it refers to another part of the tree (i.e., a trunk). 80*88d15eacSSasha Smundaktype leafReference struct{ p value.Pointer } 81*88d15eacSSasha Smundak 82*88d15eacSSasha Smundakfunc wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode { 83*88d15eacSSasha Smundak switch { 84*88d15eacSSasha Smundak case pp[0].IsNil(): 85*88d15eacSSasha Smundak return &textWrap{Value: s, Metadata: trunkReference{pp[1]}} 86*88d15eacSSasha Smundak case pp[1].IsNil(): 87*88d15eacSSasha Smundak return &textWrap{Value: s, Metadata: trunkReference{pp[0]}} 88*88d15eacSSasha Smundak case pp[0] == pp[1]: 89*88d15eacSSasha Smundak return &textWrap{Value: s, Metadata: trunkReference{pp[0]}} 90*88d15eacSSasha Smundak default: 91*88d15eacSSasha Smundak return &textWrap{Value: s, Metadata: trunkReferences{pp}} 92*88d15eacSSasha Smundak } 93*88d15eacSSasha Smundak} 94*88d15eacSSasha Smundakfunc wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode { 95*88d15eacSSasha Smundak var prefix string 96*88d15eacSSasha Smundak if printAddress { 97*88d15eacSSasha Smundak prefix = formatPointer(p, true) 98*88d15eacSSasha Smundak } 99*88d15eacSSasha Smundak return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}} 100*88d15eacSSasha Smundak} 101*88d15eacSSasha Smundakfunc makeLeafReference(p value.Pointer, printAddress bool) textNode { 102*88d15eacSSasha Smundak out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"} 103*88d15eacSSasha Smundak var prefix string 104*88d15eacSSasha Smundak if printAddress { 105*88d15eacSSasha Smundak prefix = formatPointer(p, true) 106*88d15eacSSasha Smundak } 107*88d15eacSSasha Smundak return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}} 108*88d15eacSSasha Smundak} 109*88d15eacSSasha Smundak 110*88d15eacSSasha Smundak// resolveReferences walks the textNode tree searching for any leaf reference 111*88d15eacSSasha Smundak// metadata and resolves each against the corresponding trunk references. 112*88d15eacSSasha Smundak// Since pointer addresses in memory are not particularly readable to the user, 113*88d15eacSSasha Smundak// it replaces each pointer value with an arbitrary and unique reference ID. 114*88d15eacSSasha Smundakfunc resolveReferences(s textNode) { 115*88d15eacSSasha Smundak var walkNodes func(textNode, func(textNode)) 116*88d15eacSSasha Smundak walkNodes = func(s textNode, f func(textNode)) { 117*88d15eacSSasha Smundak f(s) 118*88d15eacSSasha Smundak switch s := s.(type) { 119*88d15eacSSasha Smundak case *textWrap: 120*88d15eacSSasha Smundak walkNodes(s.Value, f) 121*88d15eacSSasha Smundak case textList: 122*88d15eacSSasha Smundak for _, r := range s { 123*88d15eacSSasha Smundak walkNodes(r.Value, f) 124*88d15eacSSasha Smundak } 125*88d15eacSSasha Smundak } 126*88d15eacSSasha Smundak } 127*88d15eacSSasha Smundak 128*88d15eacSSasha Smundak // Collect all trunks and leaves with reference metadata. 129*88d15eacSSasha Smundak var trunks, leaves []*textWrap 130*88d15eacSSasha Smundak walkNodes(s, func(s textNode) { 131*88d15eacSSasha Smundak if s, ok := s.(*textWrap); ok { 132*88d15eacSSasha Smundak switch s.Metadata.(type) { 133*88d15eacSSasha Smundak case leafReference: 134*88d15eacSSasha Smundak leaves = append(leaves, s) 135*88d15eacSSasha Smundak case trunkReference, trunkReferences: 136*88d15eacSSasha Smundak trunks = append(trunks, s) 137*88d15eacSSasha Smundak } 138*88d15eacSSasha Smundak } 139*88d15eacSSasha Smundak }) 140*88d15eacSSasha Smundak 141*88d15eacSSasha Smundak // No leaf references to resolve. 142*88d15eacSSasha Smundak if len(leaves) == 0 { 143*88d15eacSSasha Smundak return 144*88d15eacSSasha Smundak } 145*88d15eacSSasha Smundak 146*88d15eacSSasha Smundak // Collect the set of all leaf references to resolve. 147*88d15eacSSasha Smundak leafPtrs := make(map[value.Pointer]bool) 148*88d15eacSSasha Smundak for _, leaf := range leaves { 149*88d15eacSSasha Smundak leafPtrs[leaf.Metadata.(leafReference).p] = true 150*88d15eacSSasha Smundak } 151*88d15eacSSasha Smundak 152*88d15eacSSasha Smundak // Collect the set of trunk pointers that are always paired together. 153*88d15eacSSasha Smundak // This allows us to assign a single ID to both pointers for brevity. 154*88d15eacSSasha Smundak // If a pointer in a pair ever occurs by itself or as a different pair, 155*88d15eacSSasha Smundak // then the pair is broken. 156*88d15eacSSasha Smundak pairedTrunkPtrs := make(map[value.Pointer]value.Pointer) 157*88d15eacSSasha Smundak unpair := func(p value.Pointer) { 158*88d15eacSSasha Smundak if !pairedTrunkPtrs[p].IsNil() { 159*88d15eacSSasha Smundak pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half 160*88d15eacSSasha Smundak } 161*88d15eacSSasha Smundak pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half 162*88d15eacSSasha Smundak } 163*88d15eacSSasha Smundak for _, trunk := range trunks { 164*88d15eacSSasha Smundak switch p := trunk.Metadata.(type) { 165*88d15eacSSasha Smundak case trunkReference: 166*88d15eacSSasha Smundak unpair(p.p) // standalone pointer cannot be part of a pair 167*88d15eacSSasha Smundak case trunkReferences: 168*88d15eacSSasha Smundak p0, ok0 := pairedTrunkPtrs[p.pp[0]] 169*88d15eacSSasha Smundak p1, ok1 := pairedTrunkPtrs[p.pp[1]] 170*88d15eacSSasha Smundak switch { 171*88d15eacSSasha Smundak case !ok0 && !ok1: 172*88d15eacSSasha Smundak // Register the newly seen pair. 173*88d15eacSSasha Smundak pairedTrunkPtrs[p.pp[0]] = p.pp[1] 174*88d15eacSSasha Smundak pairedTrunkPtrs[p.pp[1]] = p.pp[0] 175*88d15eacSSasha Smundak case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]: 176*88d15eacSSasha Smundak // Exact pair already seen; do nothing. 177*88d15eacSSasha Smundak default: 178*88d15eacSSasha Smundak // Pair conflicts with some other pair; break all pairs. 179*88d15eacSSasha Smundak unpair(p.pp[0]) 180*88d15eacSSasha Smundak unpair(p.pp[1]) 181*88d15eacSSasha Smundak } 182*88d15eacSSasha Smundak } 183*88d15eacSSasha Smundak } 184*88d15eacSSasha Smundak 185*88d15eacSSasha Smundak // Correlate each pointer referenced by leaves to a unique identifier, 186*88d15eacSSasha Smundak // and print the IDs for each trunk that matches those pointers. 187*88d15eacSSasha Smundak var nextID uint 188*88d15eacSSasha Smundak ptrIDs := make(map[value.Pointer]uint) 189*88d15eacSSasha Smundak newID := func() uint { 190*88d15eacSSasha Smundak id := nextID 191*88d15eacSSasha Smundak nextID++ 192*88d15eacSSasha Smundak return id 193*88d15eacSSasha Smundak } 194*88d15eacSSasha Smundak for _, trunk := range trunks { 195*88d15eacSSasha Smundak switch p := trunk.Metadata.(type) { 196*88d15eacSSasha Smundak case trunkReference: 197*88d15eacSSasha Smundak if print := leafPtrs[p.p]; print { 198*88d15eacSSasha Smundak id, ok := ptrIDs[p.p] 199*88d15eacSSasha Smundak if !ok { 200*88d15eacSSasha Smundak id = newID() 201*88d15eacSSasha Smundak ptrIDs[p.p] = id 202*88d15eacSSasha Smundak } 203*88d15eacSSasha Smundak trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id)) 204*88d15eacSSasha Smundak } 205*88d15eacSSasha Smundak case trunkReferences: 206*88d15eacSSasha Smundak print0 := leafPtrs[p.pp[0]] 207*88d15eacSSasha Smundak print1 := leafPtrs[p.pp[1]] 208*88d15eacSSasha Smundak if print0 || print1 { 209*88d15eacSSasha Smundak id0, ok0 := ptrIDs[p.pp[0]] 210*88d15eacSSasha Smundak id1, ok1 := ptrIDs[p.pp[1]] 211*88d15eacSSasha Smundak isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0] 212*88d15eacSSasha Smundak if isPair { 213*88d15eacSSasha Smundak var id uint 214*88d15eacSSasha Smundak assert(ok0 == ok1) // must be seen together or not at all 215*88d15eacSSasha Smundak if ok0 { 216*88d15eacSSasha Smundak assert(id0 == id1) // must have the same ID 217*88d15eacSSasha Smundak id = id0 218*88d15eacSSasha Smundak } else { 219*88d15eacSSasha Smundak id = newID() 220*88d15eacSSasha Smundak ptrIDs[p.pp[0]] = id 221*88d15eacSSasha Smundak ptrIDs[p.pp[1]] = id 222*88d15eacSSasha Smundak } 223*88d15eacSSasha Smundak trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id)) 224*88d15eacSSasha Smundak } else { 225*88d15eacSSasha Smundak if print0 && !ok0 { 226*88d15eacSSasha Smundak id0 = newID() 227*88d15eacSSasha Smundak ptrIDs[p.pp[0]] = id0 228*88d15eacSSasha Smundak } 229*88d15eacSSasha Smundak if print1 && !ok1 { 230*88d15eacSSasha Smundak id1 = newID() 231*88d15eacSSasha Smundak ptrIDs[p.pp[1]] = id1 232*88d15eacSSasha Smundak } 233*88d15eacSSasha Smundak switch { 234*88d15eacSSasha Smundak case print0 && print1: 235*88d15eacSSasha Smundak trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1)) 236*88d15eacSSasha Smundak case print0: 237*88d15eacSSasha Smundak trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)) 238*88d15eacSSasha Smundak case print1: 239*88d15eacSSasha Smundak trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1)) 240*88d15eacSSasha Smundak } 241*88d15eacSSasha Smundak } 242*88d15eacSSasha Smundak } 243*88d15eacSSasha Smundak } 244*88d15eacSSasha Smundak } 245*88d15eacSSasha Smundak 246*88d15eacSSasha Smundak // Update all leaf references with the unique identifier. 247*88d15eacSSasha Smundak for _, leaf := range leaves { 248*88d15eacSSasha Smundak if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok { 249*88d15eacSSasha Smundak leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id)) 250*88d15eacSSasha Smundak } 251*88d15eacSSasha Smundak } 252*88d15eacSSasha Smundak} 253*88d15eacSSasha Smundak 254*88d15eacSSasha Smundakfunc formatReference(id uint) string { 255*88d15eacSSasha Smundak return fmt.Sprintf("ref#%d", id) 256*88d15eacSSasha Smundak} 257*88d15eacSSasha Smundak 258*88d15eacSSasha Smundakfunc updateReferencePrefix(prefix, ref string) string { 259*88d15eacSSasha Smundak if prefix == "" { 260*88d15eacSSasha Smundak return pointerDelimPrefix + ref + pointerDelimSuffix 261*88d15eacSSasha Smundak } 262*88d15eacSSasha Smundak suffix := strings.TrimPrefix(prefix, pointerDelimPrefix) 263*88d15eacSSasha Smundak return pointerDelimPrefix + ref + ": " + suffix 264*88d15eacSSasha Smundak} 265