util/deephash: implement SelfHasher to allow types to hash themselves

Updates: corp#16409
Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
Tom DNetto 2024-01-31 15:33:59 -08:00 committed by Tom
parent b4b2ec7801
commit 2aeef4e610
4 changed files with 62 additions and 2 deletions

View File

@ -292,6 +292,14 @@ func makeTypeHasher(t reflect.Type) typeHasherFunc {
return hashAddr
}
// Types that implement their own hashing.
if t.Kind() != reflect.Pointer && t.Kind() != reflect.Interface {
// A method can be implemented on either the value receiver or pointer receiver.
if t.Implements(selfHasherType) || reflect.PointerTo(t).Implements(selfHasherType) {
return makeSelfHasherImpl(t)
}
}
// Types that can have their memory representation directly hashed.
if typeIsMemHashable(t) {
return makeMemHasher(t.Size())
@ -350,6 +358,13 @@ func hashAddr(h *hasher, p pointer) {
}
}
func makeSelfHasherImpl(t reflect.Type) typeHasherFunc {
return func(h *hasher, p pointer) {
e := p.asValue(t)
e.Interface().(SelfHasher).Hash(&h.Block512)
}
}
func hashString(h *hasher, p pointer) {
s := *p.asString()
h.HashUint64(uint64(len(s)))

View File

@ -29,6 +29,7 @@ import (
"tailscale.com/types/ptr"
"tailscale.com/util/deephash/testtype"
"tailscale.com/util/dnsname"
"tailscale.com/util/hashx"
"tailscale.com/version"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/router"
@ -41,6 +42,14 @@ func (p appendBytes) AppendTo(b []byte) []byte {
return append(b, p...)
}
type implsSelfHasherValueRecv struct {
emit uint64
}
func (s implsSelfHasherValueRecv) Hash(h *hashx.Block512) {
h.HashUint64(s.emit)
}
func TestHash(t *testing.T) {
type tuple [2]any
type iface struct{ X any }
@ -169,6 +178,12 @@ func TestHash(t *testing.T) {
b[0] = 1
return b
}()))}, wantEq: false},
{in: tuple{&implsSelfHasher{}, &implsSelfHasher{}}, wantEq: true},
{in: tuple{(*implsSelfHasher)(nil), (*implsSelfHasher)(nil)}, wantEq: true},
{in: tuple{(*implsSelfHasher)(nil), &implsSelfHasher{}}, wantEq: false},
{in: tuple{&implsSelfHasher{emit: 1}, &implsSelfHasher{emit: 2}}, wantEq: false},
{in: tuple{implsSelfHasherValueRecv{emit: 1}, implsSelfHasherValueRecv{emit: 2}}, wantEq: false},
{in: tuple{implsSelfHasherValueRecv{emit: 2}, implsSelfHasherValueRecv{emit: 2}}, wantEq: true},
}
for _, tt := range tests {

View File

@ -7,11 +7,25 @@ import (
"net/netip"
"reflect"
"time"
"tailscale.com/util/hashx"
)
// SelfHasher is the interface implemented by types that can compute their own hash
// by writing values through the given parameter.
//
// Implementations of Hash MUST NOT call `Reset` or `Sum` on the provided argument.
//
// This interface should not be considered stable and is likely to change in the
// future.
type SelfHasher interface {
Hash(*hashx.Block512)
}
var (
timeTimeType = reflect.TypeOf((*time.Time)(nil)).Elem()
netipAddrType = reflect.TypeOf((*netip.Addr)(nil)).Elem()
timeTimeType = reflect.TypeOf((*time.Time)(nil)).Elem()
netipAddrType = reflect.TypeOf((*netip.Addr)(nil)).Elem()
selfHasherType = reflect.TypeOf((*SelfHasher)(nil)).Elem()
)
// typeIsSpecialized reports whether this type has specialized hashing.
@ -21,6 +35,11 @@ func typeIsSpecialized(t reflect.Type) bool {
case timeTimeType, netipAddrType:
return true
default:
if t.Kind() != reflect.Pointer && t.Kind() != reflect.Interface {
if t.Implements(selfHasherType) || reflect.PointerTo(t).Implements(selfHasherType) {
return true
}
}
return false
}
}

View File

@ -12,8 +12,17 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/types/structs"
"tailscale.com/util/hashx"
)
type implsSelfHasher struct {
emit uint64
}
func (s *implsSelfHasher) Hash(h *hashx.Block512) {
h.HashUint64(s.emit)
}
func TestTypeIsMemHashable(t *testing.T) {
tests := []struct {
val any
@ -67,6 +76,7 @@ func TestTypeIsMemHashable(t *testing.T) {
false},
{[0]chan bool{}, true},
{struct{ f [0]func() }{}, true},
{&implsSelfHasher{}, false},
}
for _, tt := range tests {
got := typeIsMemHashable(reflect.TypeOf(tt.val))
@ -102,6 +112,7 @@ func TestTypeIsRecursive(t *testing.T) {
{val: unsafe.Pointer(nil), want: false},
{val: make(RecursiveChan), want: true},
{val: make(chan int), want: false},
{val: (*implsSelfHasher)(nil), want: false},
}
for _, tt := range tests {
got := typeIsRecursive(reflect.TypeOf(tt.val))