report_references.go 7.6 KB

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