xref: /aosp_15_r20/external/go-cmp/cmp/report_references.go (revision 88d15eac089d7f20c739ff1001d56b91872b21a1)
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