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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions