Browse Source

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

Updates: corp#16409
Signed-off-by: Tom DNetto <[email protected]>
Tom DNetto 2 years ago
parent
commit
2aeef4e610
4 changed files with 62 additions and 2 deletions
  1. 15 0
      util/deephash/deephash.go
  2. 15 0
      util/deephash/deephash_test.go
  3. 21 2
      util/deephash/types.go
  4. 11 0
      util/deephash/types_test.go

+ 15 - 0
util/deephash/deephash.go

@@ -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)))

+ 15 - 0
util/deephash/deephash_test.go

@@ -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 {

+ 21 - 2
util/deephash/types.go

@@ -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
 	}
 }

+ 11 - 0
util/deephash/types_test.go

@@ -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))