summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--util/deephash/deephash.go49
-rw-r--r--util/deephash/deephash_test.go50
2 files changed, 97 insertions, 2 deletions
diff --git a/util/deephash/deephash.go b/util/deephash/deephash.go
index 93ab1abc5..a5036028d 100644
--- a/util/deephash/deephash.go
+++ b/util/deephash/deephash.go
@@ -424,11 +424,56 @@ func genHashPtrToMemoryRange(eleType reflect.Type) typeHasherFunc {
}
}
-const debug = false
+const debug = true
+
+// canMaybeFastPath reports whether t is a type that we might be able to
+// hash quickly at runtime with a typeHasherFunc.
+//
+// If it returns false, the slow path should be used directly.
+func canMaybeFastPath(t reflect.Type) (ret bool) {
+ defer func() {
+ if !ret {
+ log.Printf("Can't on %v", t)
+ }
+ }()
+ ti := getTypeInfo(t)
+ if ti.isRecursive {
+ return false
+ }
+ switch t.Kind() {
+ case reflect.Map, reflect.Interface, reflect.Func, reflect.UnsafePointer, reflect.Chan:
+ return false
+ case reflect.Array:
+ return t.Size() == 0 || canMaybeFastPath(t.Elem())
+ case reflect.Slice, reflect.Pointer:
+ return canMaybeFastPath(t.Elem())
+ case reflect.Struct:
+ if t == timeTimeType || t.Implements(appenderToType) {
+ return true
+ }
+ for i, n := 0, t.NumField(); i < n; i++ {
+ sf := t.Field(i)
+ if sf.Type.Size() == 0 {
+ continue
+ }
+ if !canMaybeFastPath(sf.Type) {
+ return false
+ }
+ }
+ return true
+ }
+ return true
+}
func genTypeHasher(t reflect.Type) typeHasherFunc {
+ fastable := canMaybeFastPath(t)
if debug {
- log.Printf("generating func for %v", t)
+ log.Printf("generating func for %v; fastable=%v", t, fastable)
+ }
+ if !fastable {
+ return func(h *hasher, v reflect.Value) bool {
+ return false
+ }
}
switch t.Kind() {
diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go
index 3f20b8e61..0b8211997 100644
--- a/util/deephash/deephash_test.go
+++ b/util/deephash/deephash_test.go
@@ -317,6 +317,45 @@ func TestTypeIsRecursive(t *testing.T) {
}
}
+func TestCanMaybeFastPath(t *testing.T) {
+ type RecursiveStruct struct {
+ v *RecursiveStruct
+ }
+ type RecursiveChan chan *RecursiveChan
+ type FastStruct struct{ _, _ string }
+
+ tests := []struct {
+ val any
+ want bool
+ }{
+ {val: 42, want: true},
+ {val: "string", want: true},
+ {val: 1 + 2i, want: true},
+ {val: struct{}{}, want: true},
+ {val: (*RecursiveStruct)(nil), want: false},
+ {val: RecursiveStruct{}, want: false},
+ {val: FastStruct{}, want: true},
+ {val: time.Unix(0, 0), want: true},
+ {val: structs.Incomparable{}, want: true}, // ignore its [0]func()
+ {val: tailcfg.NetPortRange{}, want: true}, // uses structs.Incomparable
+ {val: (*tailcfg.Node)(nil), want: true},
+ {val: map[string]bool{}, want: false},
+ {val: func() {}, want: false},
+ {val: make(chan int), want: false},
+ {val: unsafe.Pointer(nil), want: false},
+ {val: make(RecursiveChan), want: false},
+ {val: make(chan int), want: false},
+ {val: tailcfg.SSHRule{}, want: false}, // contains a map
+ {val: (*tailcfg.SSHRule)(nil), want: false},
+ }
+ for _, tt := range tests {
+ got := canMaybeFastPath(reflect.TypeOf(tt.val))
+ if got != tt.want {
+ t.Errorf("for type %T: got %v, want %v", tt.val, got, tt.want)
+ }
+ }
+}
+
type IntThenByte struct {
i int
b byte
@@ -611,6 +650,17 @@ func TestGetTypeHasher(t *testing.T) {
val: &tailcfg.Node{},
out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x140001-01-01T00:00:00Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x140001-01-01T00:00:00Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
},
+ {
+ name: "no_hashing_for_mixed_fast_slow_type",
+ val: struct {
+ A, B string
+ M map[string]string
+ }{
+ "foo", "bar", map[string]string{"alice": "bob"},
+ },
+ want: false,
+ out: "", // no "foo" or "bar" included
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {