265 lines
7.6 KiB
Go
265 lines
7.6 KiB
Go
// Copyright 2020, The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE.md file.
|
|
|
|
package cmp
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/google/go-cmp/cmp/internal/flags"
|
|
"github.com/google/go-cmp/cmp/internal/value"
|
|
)
|
|
|
|
const (
|
|
pointerDelimPrefix = "⟪"
|
|
pointerDelimSuffix = "⟫"
|
|
)
|
|
|
|
// formatPointer prints the address of the pointer.
|
|
func formatPointer(p value.Pointer, withDelims bool) string {
|
|
v := p.Uintptr()
|
|
if flags.Deterministic {
|
|
v = 0xdeadf00f // Only used for stable testing purposes
|
|
}
|
|
if withDelims {
|
|
return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix
|
|
}
|
|
return formatHex(uint64(v))
|
|
}
|
|
|
|
// pointerReferences is a stack of pointers visited so far.
|
|
type pointerReferences [][2]value.Pointer
|
|
|
|
func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {
|
|
if deref && vx.IsValid() {
|
|
vx = vx.Addr()
|
|
}
|
|
if deref && vy.IsValid() {
|
|
vy = vy.Addr()
|
|
}
|
|
switch d {
|
|
case diffUnknown, diffIdentical:
|
|
pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}
|
|
case diffRemoved:
|
|
pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}
|
|
case diffInserted:
|
|
pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}
|
|
}
|
|
*ps = append(*ps, pp)
|
|
return pp
|
|
}
|
|
|
|
func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {
|
|
p = value.PointerOf(v)
|
|
for _, pp := range *ps {
|
|
if p == pp[0] || p == pp[1] {
|
|
return p, true
|
|
}
|
|
}
|
|
*ps = append(*ps, [2]value.Pointer{p, p})
|
|
return p, false
|
|
}
|
|
|
|
func (ps *pointerReferences) Pop() {
|
|
*ps = (*ps)[:len(*ps)-1]
|
|
}
|
|
|
|
// trunkReferences is metadata for a textNode indicating that the sub-tree
|
|
// represents the value for either pointer in a pair of references.
|
|
type trunkReferences struct{ pp [2]value.Pointer }
|
|
|
|
// trunkReference is metadata for a textNode indicating that the sub-tree
|
|
// represents the value for the given pointer reference.
|
|
type trunkReference struct{ p value.Pointer }
|
|
|
|
// leafReference is metadata for a textNode indicating that the value is
|
|
// truncated as it refers to another part of the tree (i.e., a trunk).
|
|
type leafReference struct{ p value.Pointer }
|
|
|
|
func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {
|
|
switch {
|
|
case pp[0].IsNil():
|
|
return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}
|
|
case pp[1].IsNil():
|
|
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
|
case pp[0] == pp[1]:
|
|
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
|
default:
|
|
return &textWrap{Value: s, Metadata: trunkReferences{pp}}
|
|
}
|
|
}
|
|
func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {
|
|
var prefix string
|
|
if printAddress {
|
|
prefix = formatPointer(p, true)
|
|
}
|
|
return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}
|
|
}
|
|
func makeLeafReference(p value.Pointer, printAddress bool) textNode {
|
|
out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}
|
|
var prefix string
|
|
if printAddress {
|
|
prefix = formatPointer(p, true)
|
|
}
|
|
return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}
|
|
}
|
|
|
|
// resolveReferences walks the textNode tree searching for any leaf reference
|
|
// metadata and resolves each against the corresponding trunk references.
|
|
// Since pointer addresses in memory are not particularly readable to the user,
|
|
// it replaces each pointer value with an arbitrary and unique reference ID.
|
|
func resolveReferences(s textNode) {
|
|
var walkNodes func(textNode, func(textNode))
|
|
walkNodes = func(s textNode, f func(textNode)) {
|
|
f(s)
|
|
switch s := s.(type) {
|
|
case *textWrap:
|
|
walkNodes(s.Value, f)
|
|
case textList:
|
|
for _, r := range s {
|
|
walkNodes(r.Value, f)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect all trunks and leaves with reference metadata.
|
|
var trunks, leaves []*textWrap
|
|
walkNodes(s, func(s textNode) {
|
|
if s, ok := s.(*textWrap); ok {
|
|
switch s.Metadata.(type) {
|
|
case leafReference:
|
|
leaves = append(leaves, s)
|
|
case trunkReference, trunkReferences:
|
|
trunks = append(trunks, s)
|
|
}
|
|
}
|
|
})
|
|
|
|
// No leaf references to resolve.
|
|
if len(leaves) == 0 {
|
|
return
|
|
}
|
|
|
|
// Collect the set of all leaf references to resolve.
|
|
leafPtrs := make(map[value.Pointer]bool)
|
|
for _, leaf := range leaves {
|
|
leafPtrs[leaf.Metadata.(leafReference).p] = true
|
|
}
|
|
|
|
// Collect the set of trunk pointers that are always paired together.
|
|
// This allows us to assign a single ID to both pointers for brevity.
|
|
// If a pointer in a pair ever occurs by itself or as a different pair,
|
|
// then the pair is broken.
|
|
pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)
|
|
unpair := func(p value.Pointer) {
|
|
if !pairedTrunkPtrs[p].IsNil() {
|
|
pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half
|
|
}
|
|
pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half
|
|
}
|
|
for _, trunk := range trunks {
|
|
switch p := trunk.Metadata.(type) {
|
|
case trunkReference:
|
|
unpair(p.p) // standalone pointer cannot be part of a pair
|
|
case trunkReferences:
|
|
p0, ok0 := pairedTrunkPtrs[p.pp[0]]
|
|
p1, ok1 := pairedTrunkPtrs[p.pp[1]]
|
|
switch {
|
|
case !ok0 && !ok1:
|
|
// Register the newly seen pair.
|
|
pairedTrunkPtrs[p.pp[0]] = p.pp[1]
|
|
pairedTrunkPtrs[p.pp[1]] = p.pp[0]
|
|
case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:
|
|
// Exact pair already seen; do nothing.
|
|
default:
|
|
// Pair conflicts with some other pair; break all pairs.
|
|
unpair(p.pp[0])
|
|
unpair(p.pp[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Correlate each pointer referenced by leaves to a unique identifier,
|
|
// and print the IDs for each trunk that matches those pointers.
|
|
var nextID uint
|
|
ptrIDs := make(map[value.Pointer]uint)
|
|
newID := func() uint {
|
|
id := nextID
|
|
nextID++
|
|
return id
|
|
}
|
|
for _, trunk := range trunks {
|
|
switch p := trunk.Metadata.(type) {
|
|
case trunkReference:
|
|
if print := leafPtrs[p.p]; print {
|
|
id, ok := ptrIDs[p.p]
|
|
if !ok {
|
|
id = newID()
|
|
ptrIDs[p.p] = id
|
|
}
|
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
|
}
|
|
case trunkReferences:
|
|
print0 := leafPtrs[p.pp[0]]
|
|
print1 := leafPtrs[p.pp[1]]
|
|
if print0 || print1 {
|
|
id0, ok0 := ptrIDs[p.pp[0]]
|
|
id1, ok1 := ptrIDs[p.pp[1]]
|
|
isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]
|
|
if isPair {
|
|
var id uint
|
|
assert(ok0 == ok1) // must be seen together or not at all
|
|
if ok0 {
|
|
assert(id0 == id1) // must have the same ID
|
|
id = id0
|
|
} else {
|
|
id = newID()
|
|
ptrIDs[p.pp[0]] = id
|
|
ptrIDs[p.pp[1]] = id
|
|
}
|
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
|
} else {
|
|
if print0 && !ok0 {
|
|
id0 = newID()
|
|
ptrIDs[p.pp[0]] = id0
|
|
}
|
|
if print1 && !ok1 {
|
|
id1 = newID()
|
|
ptrIDs[p.pp[1]] = id1
|
|
}
|
|
switch {
|
|
case print0 && print1:
|
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))
|
|
case print0:
|
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))
|
|
case print1:
|
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update all leaf references with the unique identifier.
|
|
for _, leaf := range leaves {
|
|
if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {
|
|
leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))
|
|
}
|
|
}
|
|
}
|
|
|
|
func formatReference(id uint) string {
|
|
return fmt.Sprintf("ref#%d", id)
|
|
}
|
|
|
|
func updateReferencePrefix(prefix, ref string) string {
|
|
if prefix == "" {
|
|
return pointerDelimPrefix + ref + pointerDelimSuffix
|
|
}
|
|
suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)
|
|
return pointerDelimPrefix + ref + ": " + suffix
|
|
}
|