1 // Copyright 2020, The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE.md file.
12 "github.com/google/go-cmp/cmp/internal/flags"
13 "github.com/google/go-cmp/cmp/internal/value"
17 pointerDelimPrefix = "⟪"
18 pointerDelimSuffix = "⟫"
21 // formatPointer prints the address of the pointer.
22 func formatPointer(p value.Pointer, withDelims bool) string {
24 if flags.Deterministic {
25 v = 0xdeadf00f // Only used for stable testing purposes
28 return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix
30 return formatHex(uint64(v))
33 // pointerReferences is a stack of pointers visited so far.
34 type pointerReferences [][2]value.Pointer
36 func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {
37 if deref && vx.IsValid() {
40 if deref && vy.IsValid() {
44 case diffUnknown, diffIdentical:
45 pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}
47 pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}
49 pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}
55 func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {
56 p = value.PointerOf(v)
57 for _, pp := range *ps {
58 if p == pp[0] || p == pp[1] {
62 *ps = append(*ps, [2]value.Pointer{p, p})
66 func (ps *pointerReferences) Pop() {
67 *ps = (*ps)[:len(*ps)-1]
70 // trunkReferences is metadata for a textNode indicating that the sub-tree
71 // represents the value for either pointer in a pair of references.
72 type trunkReferences struct{ pp [2]value.Pointer }
74 // trunkReference is metadata for a textNode indicating that the sub-tree
75 // represents the value for the given pointer reference.
76 type trunkReference struct{ p value.Pointer }
78 // leafReference is metadata for a textNode indicating that the value is
79 // truncated as it refers to another part of the tree (i.e., a trunk).
80 type leafReference struct{ p value.Pointer }
82 func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {
85 return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}
87 return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
89 return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
91 return &textWrap{Value: s, Metadata: trunkReferences{pp}}
94 func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {
97 prefix = formatPointer(p, true)
99 return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}
101 func makeLeafReference(p value.Pointer, printAddress bool) textNode {
102 out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}
105 prefix = formatPointer(p, true)
107 return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}
110 // resolveReferences walks the textNode tree searching for any leaf reference
111 // metadata and resolves each against the corresponding trunk references.
112 // Since pointer addresses in memory are not particularly readable to the user,
113 // it replaces each pointer value with an arbitrary and unique reference ID.
114 func resolveReferences(s textNode) {
115 var walkNodes func(textNode, func(textNode))
116 walkNodes = func(s textNode, f func(textNode)) {
118 switch s := s.(type) {
120 walkNodes(s.Value, f)
122 for _, r := range s {
123 walkNodes(r.Value, f)
128 // Collect all trunks and leaves with reference metadata.
129 var trunks, leaves []*textWrap
130 walkNodes(s, func(s textNode) {
131 if s, ok := s.(*textWrap); ok {
132 switch s.Metadata.(type) {
134 leaves = append(leaves, s)
135 case trunkReference, trunkReferences:
136 trunks = append(trunks, s)
141 // No leaf references to resolve.
142 if len(leaves) == 0 {
146 // Collect the set of all leaf references to resolve.
147 leafPtrs := make(map[value.Pointer]bool)
148 for _, leaf := range leaves {
149 leafPtrs[leaf.Metadata.(leafReference).p] = true
152 // Collect the set of trunk pointers that are always paired together.
153 // This allows us to assign a single ID to both pointers for brevity.
154 // If a pointer in a pair ever occurs by itself or as a different pair,
155 // then the pair is broken.
156 pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)
157 unpair := func(p value.Pointer) {
158 if !pairedTrunkPtrs[p].IsNil() {
159 pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half
161 pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half
163 for _, trunk := range trunks {
164 switch p := trunk.Metadata.(type) {
166 unpair(p.p) // standalone pointer cannot be part of a pair
167 case trunkReferences:
168 p0, ok0 := pairedTrunkPtrs[p.pp[0]]
169 p1, ok1 := pairedTrunkPtrs[p.pp[1]]
172 // Register the newly seen pair.
173 pairedTrunkPtrs[p.pp[0]] = p.pp[1]
174 pairedTrunkPtrs[p.pp[1]] = p.pp[0]
175 case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:
176 // Exact pair already seen; do nothing.
178 // Pair conflicts with some other pair; break all pairs.
185 // Correlate each pointer referenced by leaves to a unique identifier,
186 // and print the IDs for each trunk that matches those pointers.
188 ptrIDs := make(map[value.Pointer]uint)
189 newID := func() uint {
194 for _, trunk := range trunks {
195 switch p := trunk.Metadata.(type) {
197 if print := leafPtrs[p.p]; print {
198 id, ok := ptrIDs[p.p]
203 trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
205 case trunkReferences:
206 print0 := leafPtrs[p.pp[0]]
207 print1 := leafPtrs[p.pp[1]]
208 if print0 || print1 {
209 id0, ok0 := ptrIDs[p.pp[0]]
210 id1, ok1 := ptrIDs[p.pp[1]]
211 isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]
214 assert(ok0 == ok1) // must be seen together or not at all
216 assert(id0 == id1) // must have the same ID
223 trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
227 ptrIDs[p.pp[0]] = id0
231 ptrIDs[p.pp[1]] = id1
234 case print0 && print1:
235 trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))
237 trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))
239 trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))
246 // Update all leaf references with the unique identifier.
247 for _, leaf := range leaves {
248 if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {
249 leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))
254 func formatReference(id uint) string {
255 return fmt.Sprintf("ref#%d", id)
258 func updateReferencePrefix(prefix, ref string) string {
260 return pointerDelimPrefix + ref + pointerDelimSuffix
262 suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)
263 return pointerDelimPrefix + ref + ": " + suffix