diff --git a/diff.go b/diff.go index 3c2ba17..4543f2c 100644 --- a/diff.go +++ b/diff.go @@ -86,6 +86,7 @@ type Differ struct { FlattenEmbeddedStructs bool ConvertCompatibleTypes bool Filter FilterFunc + pointersSeen map[uintptr]map[uintptr]struct{} } // Changelog stores a list of changed items @@ -127,6 +128,7 @@ func NewDiffer(opts ...func(d *Differ) error) (*Differ, error) { d := Differ{ TagName: "diff", DiscardParent: false, + pointersSeen: make(map[uintptr]map[uintptr]struct{}), } for _, opt := range opts { @@ -222,7 +224,6 @@ func (d *Differ) Diff(a, b interface{}) (Changelog, error) { } func (d *Differ) diff(path []string, a, b reflect.Value, parent interface{}) error { - //look and see if we need to discard the parent if parent != nil { if d.DiscardParent || reflect.TypeOf(parent).Kind() != reflect.Struct { diff --git a/diff_pointer.go b/diff_pointer.go index 7c9d875..a4019d2 100644 --- a/diff_pointer.go +++ b/diff_pointer.go @@ -48,6 +48,26 @@ func (d *Differ) diffPtr(path []string, a, b reflect.Value, parent interface{}) return nil } + // If two pointers have already been compared, assume they have no changes + // This mirrors how reflect.DeepEqual works, and guarantees termination + aSeen, ok := d.pointersSeen[a.Pointer()] + if !ok { + aSeen = make(map[uintptr]struct{}) + d.pointersSeen[a.Pointer()] = aSeen + } + bSeen, ok := d.pointersSeen[b.Pointer()] + if !ok { + bSeen = make(map[uintptr]struct{}) + d.pointersSeen[b.Pointer()] = bSeen + } + _, aok := aSeen[b.Pointer()] + _, bok := aSeen[b.Pointer()] + if aok || bok { + return nil + } + aSeen[b.Pointer()] = struct{}{} + bSeen[a.Pointer()] = struct{}{} + return d.diff(path, reflect.Indirect(a), reflect.Indirect(b), parent) } diff --git a/diff_slice.go b/diff_slice.go index 3fd281b..d5b57f3 100644 --- a/diff_slice.go +++ b/diff_slice.go @@ -104,6 +104,7 @@ func (st *sliceTracker) has(s, v reflect.Value, d *Differ) bool { var nd Differ nd.Filter = d.Filter nd.customValueDiffers = d.customValueDiffers + nd.pointersSeen = d.pointersSeen err := nd.diff([]string{}, x, v, nil) if err != nil { diff --git a/diff_struct.go b/diff_struct.go index fb14c57..236bfe4 100644 --- a/diff_struct.go +++ b/diff_struct.go @@ -72,6 +72,7 @@ func (d *Differ) structValues(t string, path []string, a reflect.Value) error { var nd Differ nd.Filter = d.Filter nd.customValueDiffers = d.customValueDiffers + nd.pointersSeen = d.pointersSeen if t != CREATE && t != DELETE { return ErrInvalidChangeType