@@ -18,6 +18,7 @@ package testing
1818
1919import (
2020 "reflect"
21+ "slices"
2122 "testing"
2223 "time"
2324)
@@ -35,7 +36,7 @@ func NearZeros(t *testing.T, sample any) []any {
3536 if typ .Kind () != reflect .Struct {
3637 t .Fatalf ("NearZeros: sample must be a struct, got %s" , typ .Kind ())
3738 }
38- paths := CollectPaths (typ , [] int {} )
39+ paths := collectPaths (typ , nil , nil )
3940 var results []any
4041 for _ , path := range paths {
4142 inst := makeInstanceWithNonZeroField (typ , path )
@@ -45,14 +46,22 @@ func NearZeros(t *testing.T, sample any) []any {
4546}
4647
4748// CollectPaths walks over the struct type (recursively) and returns a slice of
48- // index paths. Each path points to exactly one (exported) sub-field.
49+ // index paths. Each path points to exactly one (exported) sub-field. If the
50+ // type supplied is recursive, the path terminates at the recursion point.
4951func CollectPaths (typ reflect.Type , prefix []int ) [][]int {
52+ return collectPaths (typ , prefix , []reflect.Type {})
53+ }
54+
55+ // collectPaths walks over the struct type (recursively) and returns a slice of
56+ // index paths. Each path points to exactly one (exported) sub-field.
57+ // It tracks types in the current path to avoid infinite loops on recursive types.
58+ func collectPaths (typ reflect.Type , prefix []int , pathStack []reflect.Type ) [][]int {
5059 var paths [][]int
5160
5261 switch typ .Kind () {
5362 case reflect .Ptr , reflect .Slice , reflect .Array :
5463 // Look through container to the element
55- return CollectPaths (typ .Elem (), prefix )
64+ return collectPaths (typ .Elem (), prefix , pathStack )
5665
5766 case reflect .Map :
5867 // Record as a leaf because we will just make a single entry in the map
@@ -64,13 +73,23 @@ func CollectPaths(typ reflect.Type, prefix []int) [][]int {
6473 return [][]int {prefix }
6574 }
6675
76+ // Check if this type is already in the path stack (cycle detection)
77+ if slices .Contains (pathStack , typ ) {
78+ // We've encountered a cycle, treat this as a leaf
79+ return [][]int {prefix }
80+ }
81+
82+ // Add this type to the path stack
83+ // Clone to avoid sharing the underlying array across branches
84+ newStack := append (slices .Clone (pathStack ), typ )
85+
6786 for i := 0 ; i < typ .NumField (); i ++ {
6887 field := typ .Field (i )
6988 if ! field .IsExported () {
7089 continue
7190 }
7291 newPath := append (append ([]int (nil ), prefix ... ), i )
73- subPaths := CollectPaths (field .Type , newPath )
92+ subPaths := collectPaths (field .Type , newPath , newStack )
7493
7594 // If recursion yielded deeper paths, use them
7695 if len (subPaths ) > 0 {
0 commit comments