Skip to content

Commit 35782f8

Browse files
committed
util/deephash: add canMemHash func + typeInfo property
Currently unused. (breaking up a bigger change) Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
1 parent 7b9a901 commit 35782f8

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

util/deephash/deephash.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ var uint8Type = reflect.TypeOf(byte(0))
180180
// typeInfo describes properties of a type.
181181
type typeInfo struct {
182182
rtype reflect.Type
183+
canMemHash bool
183184
isRecursive bool
184185

185186
// elemTypeInfo is the element type's typeInfo.
@@ -218,6 +219,7 @@ func getTypeInfoLocked(t reflect.Type, incomplete map[reflect.Type]*typeInfo) *t
218219
ti := &typeInfo{
219220
rtype: t,
220221
isRecursive: typeIsRecursive(t),
222+
canMemHash: canMemHash(t),
221223
}
222224
incomplete[t] = ti
223225

@@ -311,6 +313,34 @@ func typeIsRecursive(t reflect.Type) bool {
311313
return visitType(t)
312314
}
313315

316+
// canMemHash reports whether a slice of t can be hashed by looking at its
317+
// contiguous bytes in memory alone. (e.g. structs with gaps aren't memhashable)
318+
func canMemHash(t reflect.Type) bool {
319+
switch t.Kind() {
320+
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
321+
reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
322+
reflect.Float64, reflect.Float32, reflect.Complex128, reflect.Complex64:
323+
return true
324+
case reflect.Array:
325+
return canMemHash(t.Elem())
326+
case reflect.Struct:
327+
var sumFieldSize uintptr
328+
for i, numField := 0, t.NumField(); i < numField; i++ {
329+
sf := t.Field(i)
330+
if !canMemHash(sf.Type) {
331+
// Special case for 0-width fields that aren't at the end.
332+
if sf.Type.Size() == 0 && i < numField-1 {
333+
continue
334+
}
335+
return false
336+
}
337+
sumFieldSize += sf.Type.Size()
338+
}
339+
return sumFieldSize == t.Size() // else there are gaps
340+
}
341+
return false
342+
}
343+
314344
func (h *hasher) hashValue(v reflect.Value, forceCycleChecking bool) {
315345
if !v.IsValid() {
316346
return

util/deephash/deephash_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"bytes"
1111
"crypto/sha256"
1212
"fmt"
13+
"io"
1314
"math"
1415
"math/rand"
1516
"reflect"
@@ -314,6 +315,83 @@ func TestTypeIsRecursive(t *testing.T) {
314315
}
315316
}
316317

318+
type IntThenByte struct {
319+
i int
320+
b byte
321+
}
322+
323+
type TwoInts struct{ a, b int }
324+
325+
type IntIntByteInt struct {
326+
i1, i2 int32
327+
b byte // padding after
328+
i3 int32
329+
}
330+
331+
func TestCanMemHash(t *testing.T) {
332+
tests := []struct {
333+
val any
334+
want bool
335+
}{
336+
{true, true},
337+
{uint(1), true},
338+
{uint8(1), true},
339+
{uint16(1), true},
340+
{uint32(1), true},
341+
{uint64(1), true},
342+
{uintptr(1), true},
343+
{int(1), true},
344+
{int8(1), true},
345+
{int16(1), true},
346+
{int32(1), true},
347+
{int64(1), true},
348+
{float32(1), true},
349+
{float64(1), true},
350+
{complex64(1), true},
351+
{complex128(1), true},
352+
{[32]byte{}, true},
353+
{func() {}, false},
354+
{make(chan int), false},
355+
{struct{ io.Writer }{nil}, false},
356+
{unsafe.Pointer(nil), false},
357+
{new(int), false},
358+
{TwoInts{}, true},
359+
{[4]TwoInts{}, true},
360+
{IntThenByte{}, false},
361+
{[4]IntThenByte{}, false},
362+
{tailcfg.PortRange{}, true},
363+
{int16(0), true},
364+
{struct {
365+
_ int
366+
_ int
367+
}{}, true},
368+
{struct {
369+
_ int
370+
_ uint8
371+
_ int
372+
}{}, false}, // gap
373+
{
374+
struct {
375+
_ structs.Incomparable // if not last, zero-width
376+
x int
377+
}{},
378+
true,
379+
},
380+
{
381+
struct {
382+
x int
383+
_ structs.Incomparable // zero-width last: has space, can't memhash
384+
}{},
385+
false,
386+
}}
387+
for _, tt := range tests {
388+
got := canMemHash(reflect.TypeOf(tt.val))
389+
if got != tt.want {
390+
t.Errorf("for type %T: got %v, want %v", tt.val, got, tt.want)
391+
}
392+
}
393+
}
394+
317395
var sink = Hash("foo")
318396

319397
func BenchmarkHash(b *testing.B) {

0 commit comments

Comments
 (0)