Path: blob/main/vendor/github.com/google/go-cmp/cmp/report_references.go
2880 views
// Copyright 2020, The Go Authors. All rights reserved.1// Use of this source code is governed by a BSD-style2// license that can be found in the LICENSE file.34package cmp56import (7"fmt"8"reflect"9"strings"1011"github.com/google/go-cmp/cmp/internal/flags"12"github.com/google/go-cmp/cmp/internal/value"13)1415const (16pointerDelimPrefix = "⟪"17pointerDelimSuffix = "⟫"18)1920// formatPointer prints the address of the pointer.21func formatPointer(p value.Pointer, withDelims bool) string {22v := p.Uintptr()23if flags.Deterministic {24v = 0xdeadf00f // Only used for stable testing purposes25}26if withDelims {27return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix28}29return formatHex(uint64(v))30}3132// pointerReferences is a stack of pointers visited so far.33type pointerReferences [][2]value.Pointer3435func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {36if deref && vx.IsValid() {37vx = vx.Addr()38}39if deref && vy.IsValid() {40vy = vy.Addr()41}42switch d {43case diffUnknown, diffIdentical:44pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}45case diffRemoved:46pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}47case diffInserted:48pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}49}50*ps = append(*ps, pp)51return pp52}5354func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {55p = value.PointerOf(v)56for _, pp := range *ps {57if p == pp[0] || p == pp[1] {58return p, true59}60}61*ps = append(*ps, [2]value.Pointer{p, p})62return p, false63}6465func (ps *pointerReferences) Pop() {66*ps = (*ps)[:len(*ps)-1]67}6869// trunkReferences is metadata for a textNode indicating that the sub-tree70// represents the value for either pointer in a pair of references.71type trunkReferences struct{ pp [2]value.Pointer }7273// trunkReference is metadata for a textNode indicating that the sub-tree74// represents the value for the given pointer reference.75type trunkReference struct{ p value.Pointer }7677// leafReference is metadata for a textNode indicating that the value is78// truncated as it refers to another part of the tree (i.e., a trunk).79type leafReference struct{ p value.Pointer }8081func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {82switch {83case pp[0].IsNil():84return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}85case pp[1].IsNil():86return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}87case pp[0] == pp[1]:88return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}89default:90return &textWrap{Value: s, Metadata: trunkReferences{pp}}91}92}93func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {94var prefix string95if printAddress {96prefix = formatPointer(p, true)97}98return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}99}100func makeLeafReference(p value.Pointer, printAddress bool) textNode {101out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}102var prefix string103if printAddress {104prefix = formatPointer(p, true)105}106return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}107}108109// resolveReferences walks the textNode tree searching for any leaf reference110// metadata and resolves each against the corresponding trunk references.111// Since pointer addresses in memory are not particularly readable to the user,112// it replaces each pointer value with an arbitrary and unique reference ID.113func resolveReferences(s textNode) {114var walkNodes func(textNode, func(textNode))115walkNodes = func(s textNode, f func(textNode)) {116f(s)117switch s := s.(type) {118case *textWrap:119walkNodes(s.Value, f)120case textList:121for _, r := range s {122walkNodes(r.Value, f)123}124}125}126127// Collect all trunks and leaves with reference metadata.128var trunks, leaves []*textWrap129walkNodes(s, func(s textNode) {130if s, ok := s.(*textWrap); ok {131switch s.Metadata.(type) {132case leafReference:133leaves = append(leaves, s)134case trunkReference, trunkReferences:135trunks = append(trunks, s)136}137}138})139140// No leaf references to resolve.141if len(leaves) == 0 {142return143}144145// Collect the set of all leaf references to resolve.146leafPtrs := make(map[value.Pointer]bool)147for _, leaf := range leaves {148leafPtrs[leaf.Metadata.(leafReference).p] = true149}150151// Collect the set of trunk pointers that are always paired together.152// This allows us to assign a single ID to both pointers for brevity.153// If a pointer in a pair ever occurs by itself or as a different pair,154// then the pair is broken.155pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)156unpair := func(p value.Pointer) {157if !pairedTrunkPtrs[p].IsNil() {158pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half159}160pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half161}162for _, trunk := range trunks {163switch p := trunk.Metadata.(type) {164case trunkReference:165unpair(p.p) // standalone pointer cannot be part of a pair166case trunkReferences:167p0, ok0 := pairedTrunkPtrs[p.pp[0]]168p1, ok1 := pairedTrunkPtrs[p.pp[1]]169switch {170case !ok0 && !ok1:171// Register the newly seen pair.172pairedTrunkPtrs[p.pp[0]] = p.pp[1]173pairedTrunkPtrs[p.pp[1]] = p.pp[0]174case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:175// Exact pair already seen; do nothing.176default:177// Pair conflicts with some other pair; break all pairs.178unpair(p.pp[0])179unpair(p.pp[1])180}181}182}183184// Correlate each pointer referenced by leaves to a unique identifier,185// and print the IDs for each trunk that matches those pointers.186var nextID uint187ptrIDs := make(map[value.Pointer]uint)188newID := func() uint {189id := nextID190nextID++191return id192}193for _, trunk := range trunks {194switch p := trunk.Metadata.(type) {195case trunkReference:196if print := leafPtrs[p.p]; print {197id, ok := ptrIDs[p.p]198if !ok {199id = newID()200ptrIDs[p.p] = id201}202trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))203}204case trunkReferences:205print0 := leafPtrs[p.pp[0]]206print1 := leafPtrs[p.pp[1]]207if print0 || print1 {208id0, ok0 := ptrIDs[p.pp[0]]209id1, ok1 := ptrIDs[p.pp[1]]210isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]211if isPair {212var id uint213assert(ok0 == ok1) // must be seen together or not at all214if ok0 {215assert(id0 == id1) // must have the same ID216id = id0217} else {218id = newID()219ptrIDs[p.pp[0]] = id220ptrIDs[p.pp[1]] = id221}222trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))223} else {224if print0 && !ok0 {225id0 = newID()226ptrIDs[p.pp[0]] = id0227}228if print1 && !ok1 {229id1 = newID()230ptrIDs[p.pp[1]] = id1231}232switch {233case print0 && print1:234trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))235case print0:236trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))237case print1:238trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))239}240}241}242}243}244245// Update all leaf references with the unique identifier.246for _, leaf := range leaves {247if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {248leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))249}250}251}252253func formatReference(id uint) string {254return fmt.Sprintf("ref#%d", id)255}256257func updateReferencePrefix(prefix, ref string) string {258if prefix == "" {259return pointerDelimPrefix + ref + pointerDelimSuffix260}261suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)262return pointerDelimPrefix + ref + ": " + suffix263}264265266