Skip to content

assert: detect cycles in EqualExportedValues - #1946

Closed
22elix3r wants to merge 1 commit into
stretchr:masterfrom
22elix3r:fix/1915-equal-exported-cycles
Closed

assert: detect cycles in EqualExportedValues#1946
22elix3r wants to merge 1 commit into
stretchr:masterfrom
22elix3r:fix/1915-equal-exported-cycles

Conversation

@22elix3r

Copy link
Copy Markdown

Summary

EqualExportedValues copies exported fields via copyExportedFields, which recursed forever on pointer, slice, and map cycles and aborted the test binary with fatal error: stack overflow. This change detects those cycles (same identity keys as encoding/json) and reuses the in-progress copy so cyclic equal structures compare equal, matching reflect.DeepEqual.

This is a bug fix. Example (the report from #1915):

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) // now succeeds instead of overflowing
}

Cyclic structures that differ in exported fields still compare unequal. Cyclic vs non-cyclic values (self-pointer vs nil) also remain unequal, because the copy preserves the cycle instead of zeroing it.

Related Issue

Fixes #1915

Changes Made

  • Track visited pointer, slice, and map identities in copyExportedFields using a visit key in the same shape as encoding/json (type + pointer, plus length for slices).
  • Check that map once at the start of each recursive call; store the new copy before walking children so a cycle reuses the in-progress value.
  • Format EqualExportedValues failure messages with spew (which already handles cycles and is used for diffs) instead of fmt %#v, so unequal cyclic values do not overflow while printing.
  • Add regression tests for self-referential structs, mutual pointers, map cycles, slice cycles, arrays of cyclic pointers, finite chains, unequal exported fields, and cyclic vs non-cyclic.

Testing

  • gofmt clean
  • go vet ./assert
  • go test ./assert -count=1 -timeout=30s -run 'TestCopyExportedFields|TestEqualExportedValues|TestObjectsExportedFieldsAreEqual'
  • go test ./assert -count=1
  • go test -race ./assert -count=1
  • go test ./... -count=1

Notes

  • Zeroing cyclic pointers would make a self-referential value compare equal to one with a nil pointer, which is wrong. Reusing the in-progress copy preserves the cycle so reflect.DeepEqual can tell those cases apart.
  • Arrays are values and cannot be cyclic by themselves; cycles through arrays are detected via the pointers they hold.
  • Fix EqualExportedValues on recursive values #1916 is an earlier cycle-tracking patch. A maintainer asked there for map/slice/array tests and a single visit check; this PR includes those and also covers the failure-message overflow (fmt %#v does not handle cycles). Happy to close this if Fix EqualExportedValues on recursive values #1916 is updated instead.

copyExportedFields followed pointer, slice, and map cycles until the
stack overflowed. Track visited values the same way encoding/json does
and reuse in-progress copies so cyclic equal structures compare equal.

Failure messages use spew, which already handles cycles, instead of
fmt %#v so unequal cyclic values do not overflow when formatting.

Fixes stretchr#1915

Signed-off-by: elix3r <157088510+22elix3r@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 27, 2026 02:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@22elix3r

Copy link
Copy Markdown
Author

Ready for review whenever a maintainer is available. I cannot request a reviewer from this account.

@22elix3r

Copy link
Copy Markdown
Author

Withdrawing: I missed that this issue already had earlier open PRs. Sorry for the noise — leaving the field to those patches.

@22elix3r

Copy link
Copy Markdown
Author

Withdrawing this PR so the earlier open work on the issue can proceed.

@22elix3r 22elix3r closed this Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EqualExportedValues overflow the stack on recursive data structures

2 participants