| // 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 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 | |
| } |