util/deephash: prevent infinite loop on map cycle

Fixes #2340

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2021-07-06 21:41:18 -07:00 committed by Brad Fitzpatrick
parent aceaa70b16
commit 3962744450
2 changed files with 34 additions and 0 deletions

View File

@ -169,6 +169,28 @@ func (h *hasher) print(v reflect.Value) (acyclic bool) {
case reflect.Interface:
return h.print(v.Elem())
case reflect.Map:
// TODO(bradfitz): ideally we'd avoid these map
// operations to detect cycles if we knew from the map
// element type that there no way to form a cycle,
// which is the common case. Notably, we don't care
// about hashing the same map+contents twice in
// different parts of the tree. In fact, we should
// ideally. (And this prevents it) We should only stop
// hashing when there's a cycle. What we should
// probably do is make sure we enumerate the data
// structure tree is a fixed order and then give each
// pointer an increasing number, and when we hit a
// dup, rather than emitting nothing, we should emit a
// "value #12" reference. Which implies that all things
// emit to the bufio.Writer should be type-tagged so
// we can distinguish loop references without risk of
// collisions.
ptr := v.Pointer()
if visited[ptr] {
return false
}
visited[ptr] = true
if h.hashMapAcyclic(v) {
return true
}

View File

@ -226,3 +226,15 @@ func TestSHA256EqualHex(t *testing.T) {
}
}
}
// verify this doesn't loop forever, as it used to (Issue 2340)
func TestMapCyclicFallback(t *testing.T) {
type T struct {
M map[string]interface{}
}
v := &T{
M: map[string]interface{}{},
}
v.M["m"] = v.M
Hash(v)
}