Conversation
Address review feedback on stretchr#1916: look up already-copied pointers, slices and maps in a single place before the kind switch, instead of repeating the lookup in each case. Extend the regression tests to cover direct, two-node and three-node pointer cycles, acyclic chains, shared (non-cyclic) references, structs and arrays holding cyclic pointers, slice and map cycles, and unequal cyclic values (different exported fields, cycle vs nil, cycle vs chain). Also check that the copy of a self-referential value points to itself.
HaraldNordgren
approved these changes
Sep 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix the stack overflow in
EqualExportedValueswhen values contain pointer, slice or map cycles. This picks up #1916 (by @cyphercodes, whose commit is kept as-is with its original authorship) and addresses the open review feedback on it.Changes
copyExportedFieldsrecords the copy it creates for each pointer, slice and map (keyed by type, address and, for slices, length — the same identityencoding/jsonuses for cycle detection). Meeting the same value again reuses that in-progress copy instead of recursing, so the copy keeps the original's shape, including its cycles.Tests in
TestEqualExportedValuesRecursiveStruct(table-driven):A → AA → B → AA → B → C → AA → B → C → nilLeftandRight→ same node)TestCopyExportedFieldsRecursiveStructalso checks that the copy of a self-referential value is a new value, points back to itself, and has its unexported field cleared.Motivation
copyExportedFieldsbuilds a copy with only exported fields by recursing through the value. It had no record of what it had already visited, so fora.Self = athePtrcase would copy*a, whoseSelffield led back to thePtrcase with the same pointer, forever. The resultingfatal error: stack overflowkills the whole test binary, not just the failing test.Reusing the in-progress copy (rather than returning nil for a pointer that was already seen) matters for correctness:
The final comparison is still done by
ObjectsAreEqualValues/reflect.DeepEqual, which already handles cyclic values.Example that previously crashed and now passes:
Out of scope: when two values are unequal and the cycle goes only through slices or maps (for example, a
[]interface{}that contains itself), building the failure message with%#vcan still recurse.assert.Equalhas the same behaviour today, and pointer cycles are unaffected because%#vprints nested pointers as addresses.Testing
assertions.gofrommaster, and pass with this change.go test -run 'RecursiveStruct|TestEqualExportedValues|TestCopyExportedFields|TestObjectsExportedFieldsAreEqual' ./assert: passgo test ./...: all EqualExportedValues-related tests pass. On my Windows machine a fewassertfile/dir-existence tests and somesuitetests fail, and they fail the same way on unmodifiedmaster, so they are unrelated to this change.gofmt -l .,go vet ./...,go generate ./...(no diff),go run ./_readme-gofmt/main.go,git diff --check: clean-racewas not run locally (cgo is unavailable on my machine); CI runs it.Related issues
Closes #1915
Supersedes #1916 (keeps its commit). Happy to close this if @cyphercodes would rather update #1916 directly.