Skip to content

EqualExportedValues overflow the stack on recursive data structures #1915

Description

@iwahbe

Description

assert.EqualExportedValues recurses through a value's exported fields via copyExportedFields. When the value contains a pointer cycle (a struct that, directly or indirectly, points back to itself), copyExportedFields follows the cycle forever and never terminates. The result is a fatal error: stack overflow that aborts the whole test binary. Tested with testify v1.11.1 and Go 1.26.4.

Step To Reproduce

Run this test file:

package repro

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

type Node struct {
	Self *Node
}

func TestRecursive(t *testing.T) {
	a := &Node{}
	a.Self = a
	b := &Node{}
	b.Self = b
	assert.EqualExportedValues(t, a, b)
}

Expected behavior

The assertion should detect the cycle and report the two values as equal.

assert.EqualExportedValues already compares by structural equality, recursive structures are no different:

Details
func TestChain(t *testing.T) {
	a := &Node{Self: &Node{Self: &Node{}}}
	b := &Node{Self: &Node{Self: &Node{}}}
	assert.EqualExportedValues(t, a, b)
}

Actual behavior

copyExportedFields recurses on the self-referential pointer until the goroutine stack is exhausted, killing the entire test run:

runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0x... stack=[...]
fatal error: stack overflow

goroutine 5 [running]:
internal/abi.Name.Name(...)
	.../internal/abi/type.go:659 +0xc0 ...
reflect.(*structType).Field(...)
	.../reflect/type.go:1189 +0x90 ...
reflect.(*rtype).Field(...)
	.../reflect/type.go:777 +0x50 ...
github.com/stretchr/testify/assert.copyExportedFields(...)
	.../testify@v1.11.1/assert/assertions.go:99 +0x42c ...
github.com/stretchr/testify/assert.copyExportedFields(...)
	.../testify@v1.11.1/assert/assertions.go:114 +0x310 ...
github.com/stretchr/testify/assert.copyExportedFields(...)
	.../testify@v1.11.1/assert/assertions.go:106 +0x538 ...
[copyExportedFields repeats until the stack is exhausted]

I'd be happy to take a stab at addressing this if your willing to review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions