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 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. // Types that can have their memory representation directly hashed.
if typeIsMemHashable(t) { if typeIsMemHashable(t) {
return makeMemHasher(t.Size()) 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) { func hashString(h *hasher, p pointer) {
s := *p.asString() s := *p.asString()
h.HashUint64(uint64(len(s))) h.HashUint64(uint64(len(s)))

View File

@ -29,6 +29,7 @@
"tailscale.com/types/ptr" "tailscale.com/types/ptr"
"tailscale.com/util/deephash/testtype" "tailscale.com/util/deephash/testtype"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/util/hashx"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
"tailscale.com/wgengine/router" "tailscale.com/wgengine/router"
@ -41,6 +42,14 @@ func (p appendBytes) AppendTo(b []byte) []byte {
return append(b, p...) 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) { func TestHash(t *testing.T) {
type tuple [2]any type tuple [2]any
type iface struct{ X any } type iface struct{ X any }
@ -169,6 +178,12 @@ type scalars struct {
b[0] = 1 b[0] = 1
return b return b
}()))}, wantEq: false}, }()))}, 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 { for _, tt := range tests {

View File

@ -7,11 +7,25 @@
"net/netip" "net/netip"
"reflect" "reflect"
"time" "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 ( var (
timeTimeType = reflect.TypeOf((*time.Time)(nil)).Elem() timeTimeType = reflect.TypeOf((*time.Time)(nil)).Elem()
netipAddrType = reflect.TypeOf((*netip.Addr)(nil)).Elem() netipAddrType = reflect.TypeOf((*netip.Addr)(nil)).Elem()
selfHasherType = reflect.TypeOf((*SelfHasher)(nil)).Elem()
) )
// typeIsSpecialized reports whether this type has specialized hashing. // typeIsSpecialized reports whether this type has specialized hashing.
@ -21,6 +35,11 @@ func typeIsSpecialized(t reflect.Type) bool {
case timeTimeType, netipAddrType: case timeTimeType, netipAddrType:
return true return true
default: default:
if t.Kind() != reflect.Pointer && t.Kind() != reflect.Interface {
if t.Implements(selfHasherType) || reflect.PointerTo(t).Implements(selfHasherType) {
return true
}
}
return false return false
} }
} }

View File

@ -12,8 +12,17 @@
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/structs" "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) { func TestTypeIsMemHashable(t *testing.T) {
tests := []struct { tests := []struct {
val any val any
@ -67,6 +76,7 @@ func TestTypeIsMemHashable(t *testing.T) {
false}, false},
{[0]chan bool{}, true}, {[0]chan bool{}, true},
{struct{ f [0]func() }{}, true}, {struct{ f [0]func() }{}, true},
{&implsSelfHasher{}, false},
} }
for _, tt := range tests { for _, tt := range tests {
got := typeIsMemHashable(reflect.TypeOf(tt.val)) got := typeIsMemHashable(reflect.TypeOf(tt.val))
@ -102,6 +112,7 @@ type RecursiveStruct struct {
{val: unsafe.Pointer(nil), want: false}, {val: unsafe.Pointer(nil), want: false},
{val: make(RecursiveChan), want: true}, {val: make(RecursiveChan), want: true},
{val: make(chan int), want: false}, {val: make(chan int), want: false},
{val: (*implsSelfHasher)(nil), want: false},
} }
for _, tt := range tests { for _, tt := range tests {
got := typeIsRecursive(reflect.TypeOf(tt.val)) got := typeIsRecursive(reflect.TypeOf(tt.val))