mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 14:05:39 +00:00
util/deephash: fix map hashing when key & element have the same type
Regression from 09afb8e35b
, in which the
same reflect.Value scratch value was being used as the map iterator
copy destination.
Also: make nil and empty maps hash differently, add test.
Fixes #4871
Co-authored-by: Josh Bleecher Snyder <josharian@gmail.com>
Change-Id: I67f42524bc81f694c1b7259d6682200125ea4a66
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
22c544bca7
commit
757ecf7e80
@ -98,10 +98,14 @@ func (s Sum) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
once sync.Once
|
seedOnce sync.Once
|
||||||
seed uint64
|
seed uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func initSeed() {
|
||||||
|
seed = uint64(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
func (h *hasher) sum() (s Sum) {
|
func (h *hasher) sum() (s Sum) {
|
||||||
h.bw.Flush()
|
h.bw.Flush()
|
||||||
// Sum into scratch & copy out, as hash.Hash is an interface
|
// Sum into scratch & copy out, as hash.Hash is an interface
|
||||||
@ -121,9 +125,7 @@ func Hash(v any) (s Sum) {
|
|||||||
h := hasherPool.Get().(*hasher)
|
h := hasherPool.Get().(*hasher)
|
||||||
defer hasherPool.Put(h)
|
defer hasherPool.Put(h)
|
||||||
h.reset()
|
h.reset()
|
||||||
once.Do(func() {
|
seedOnce.Do(initSeed)
|
||||||
seed = uint64(time.Now().UnixNano())
|
|
||||||
})
|
|
||||||
h.hashUint64(seed)
|
h.hashUint64(seed)
|
||||||
h.hashValue(reflect.ValueOf(v), false)
|
h.hashValue(reflect.ValueOf(v), false)
|
||||||
return h.sum()
|
return h.sum()
|
||||||
@ -425,9 +427,9 @@ func (h *hasher) hashValueWithType(v reflect.Value, ti *typeInfo, forceCycleChec
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mapHasher struct {
|
type mapHasher struct {
|
||||||
h hasher
|
h hasher
|
||||||
val valueCache // re-usable values for map iteration
|
valKey, valElem valueCache // re-usable values for map iteration
|
||||||
iter reflect.MapIter // re-usable map iterator
|
iter reflect.MapIter // re-usable map iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapHasherPool = &sync.Pool{
|
var mapHasherPool = &sync.Pool{
|
||||||
@ -461,8 +463,12 @@ func (h *hasher) hashMap(v reflect.Value, ti *typeInfo, checkCycles bool) {
|
|||||||
defer iter.Reset(reflect.Value{}) // avoid pinning v from mh.iter when we return
|
defer iter.Reset(reflect.Value{}) // avoid pinning v from mh.iter when we return
|
||||||
|
|
||||||
var sum Sum
|
var sum Sum
|
||||||
k := mh.val.get(v.Type().Key())
|
if v.IsNil() {
|
||||||
e := mh.val.get(v.Type().Elem())
|
sum.sum[0] = 1 // something non-zero
|
||||||
|
}
|
||||||
|
|
||||||
|
k := mh.valKey.get(v.Type().Key())
|
||||||
|
e := mh.valElem.get(v.Type().Elem())
|
||||||
mh.h.visitStack = h.visitStack // always use the parent's visit stack to avoid cycles
|
mh.h.visitStack = h.visitStack // always use the parent's visit stack to avoid cycles
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
k.SetIterKey(iter)
|
k.SetIterKey(iter)
|
||||||
|
@ -11,9 +11,11 @@
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -144,6 +146,40 @@ func TestIssue4868(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue4871(t *testing.T) {
|
||||||
|
m1 := map[string]string{"": "", "x": "foo"}
|
||||||
|
m2 := map[string]string{}
|
||||||
|
if h1, h2 := Hash(m1), Hash(m2); h1 == h2 {
|
||||||
|
t.Errorf("bogus: h1=%x, h2=%x", h1, h2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNilVsEmptymap(t *testing.T) {
|
||||||
|
m1 := map[string]string(nil)
|
||||||
|
m2 := map[string]string{}
|
||||||
|
if h1, h2 := Hash(m1), Hash(m2); h1 == h2 {
|
||||||
|
t.Errorf("bogus: h1=%x, h2=%x", h1, h2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapFraming(t *testing.T) {
|
||||||
|
m1 := map[string]string{"foo": "", "fo": "o"}
|
||||||
|
m2 := map[string]string{}
|
||||||
|
if h1, h2 := Hash(m1), Hash(m2); h1 == h2 {
|
||||||
|
t.Errorf("bogus: h1=%x, h2=%x", h1, h2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuick(t *testing.T) {
|
||||||
|
initSeed()
|
||||||
|
err := quick.Check(func(v, w map[string]string) bool {
|
||||||
|
return (Hash(v) == Hash(w)) == reflect.DeepEqual(v, w)
|
||||||
|
}, &quick.Config{MaxCount: 1000, Rand: rand.New(rand.NewSource(int64(seed)))})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("seed=%v, err=%v", seed, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getVal() []any {
|
func getVal() []any {
|
||||||
return []any{
|
return []any{
|
||||||
&wgcfg.Config{
|
&wgcfg.Config{
|
||||||
|
Loading…
Reference in New Issue
Block a user