From 86640d904a0117aa67dbd0ae5ce1a72c30e70dcb Mon Sep 17 00:00:00 2001 From: Tamish Mhatre Date: Sun, 12 Jul 2026 18:39:50 +0530 Subject: [PATCH 1/3] fix: detect pointer cycles in copyExportedFields --- assert/assertions.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 1419e4776..68a5cbb39 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -83,6 +83,10 @@ func ObjectsAreEqual(expected, actual interface{}) bool { // copyExportedFields iterates downward through nested data structures and creates a copy // that only contains the exported struct fields. func copyExportedFields(expected interface{}) interface{} { + return copyExportedFieldsWithVisited(expected, make(map[uintptr]bool)) +} + +func copyExportedFieldsWithVisited(expected interface{}, visited map[uintptr]bool) interface{} { if isNil(expected) { return expected } @@ -102,15 +106,20 @@ func copyExportedFields(expected interface{}) interface{} { if isNil(fieldValue) || isNil(fieldValue.Interface()) { continue } - newValue := copyExportedFields(fieldValue.Interface()) + newValue := copyExportedFieldsWithVisited(fieldValue.Interface(), visited) result.Field(i).Set(reflect.ValueOf(newValue)) } } return result.Interface() case reflect.Ptr: + ptr := expectedValue.Pointer() + if visited[ptr] { + return reflect.Zero(expectedType).Interface() + } + visited[ptr] = true result := reflect.New(expectedType.Elem()) - unexportedRemoved := copyExportedFields(expectedValue.Elem().Interface()) + unexportedRemoved := copyExportedFieldsWithVisited(expectedValue.Elem().Interface(), visited) result.Elem().Set(reflect.ValueOf(unexportedRemoved)) return result.Interface() @@ -126,7 +135,7 @@ func copyExportedFields(expected interface{}) interface{} { if isNil(index) { continue } - unexportedRemoved := copyExportedFields(index.Interface()) + unexportedRemoved := copyExportedFieldsWithVisited(index.Interface(), visited) result.Index(i).Set(reflect.ValueOf(unexportedRemoved)) } return result.Interface() @@ -135,7 +144,7 @@ func copyExportedFields(expected interface{}) interface{} { result := reflect.MakeMap(expectedType) for _, k := range expectedValue.MapKeys() { index := expectedValue.MapIndex(k) - unexportedRemoved := copyExportedFields(index.Interface()) + unexportedRemoved := copyExportedFieldsWithVisited(index.Interface(), visited) result.SetMapIndex(k, reflect.ValueOf(unexportedRemoved)) } return result.Interface() From 8af3ccf030c27e4650c93bf896ff0a96c693c301 Mon Sep 17 00:00:00 2001 From: Tamish Mhatre Date: Sun, 12 Jul 2026 18:40:12 +0530 Subject: [PATCH 2/3] test: add recursive struct cycle detection test --- assert/assertions_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 11642e096..411ae4963 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -1,3 +1,20 @@ + + +func TestEqualExportedValuesRecursiveStruct(t *testing.T) { + type Node struct { + Self *Node + } + a := &Node{} + a.Self = a + b := &Node{} + b.Self = b + + mockT := new(testing.T) + if !EqualExportedValues(mockT, a, b) { + t.Error("EqualExportedValues should handle recursive structures without stack overflow") + } +} + package assert import ( From 9114e233aaa8c57af445acbc60512aa11cc7e7fa Mon Sep 17 00:00:00 2001 From: Tamish Mhatre Date: Sun, 12 Jul 2026 18:40:42 +0530 Subject: [PATCH 3/3] test: add recursive struct cycle detection test --- assert/assertions_test.go | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 411ae4963..cf6f248cd 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -1,20 +1,3 @@ - - -func TestEqualExportedValuesRecursiveStruct(t *testing.T) { - type Node struct { - Self *Node - } - a := &Node{} - a.Self = a - b := &Node{} - b.Self = b - - mockT := new(testing.T) - if !EqualExportedValues(mockT, a, b) { - t.Error("EqualExportedValues should handle recursive structures without stack overflow") - } -} - package assert import ( @@ -3029,6 +3012,22 @@ Diff: - B: (int) 10 + B: (int) 15 } + +func TestEqualExportedValuesRecursiveStruct(t *testing.T) { + type Node struct { + Self *Node + } + a := &Node{} + a.Self = a + b := &Node{} + b.Self = b + + mockT := new(testing.T) + if !EqualExportedValues(mockT, a, b) { + t.Error("EqualExportedValues should handle recursive structures without stack overflow") + } +} + ` actual = diff(