From edeeff9fd021768f2c52b11110de7683b1c052a6 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 8 Sep 2026 20:57:39 +0200 Subject: [PATCH] Fix power-of-two integer hash collisions Don't use integer values verbatim but hash them first by multiplicating them with a magic value, like we do elsewhere. Most of the randomness ends up in the high bits so use those to index into the hash table, i.e., right-shift the hashed value. Fixes: https://github.com/quickjs-ng/quickjs/issues/1717 --- cutils.h | 23 +++++++++++++++++++++++ quickjs.c | 19 ++++++++++++------- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/cutils.h b/cutils.h index 8b8842d85..5d3c75edf 100644 --- a/cutils.h +++ b/cutils.h @@ -206,6 +206,20 @@ static inline uint32_t hash32(uint32_t a) return a * 0x61c88647; } +static inline uint64_t hash64(uint64_t a) +{ + return a * 0x61c8864680b583ebULL; +} + +static inline uintptr_t hash_uintptr(uintptr_t a) +{ + if (sizeof(uintptr_t) == sizeof(uint32_t)) { + return hash32(a); + } else { + return hash64(a); + } +} + /* WARNING: undefined if a = 0 */ static inline int clz32(unsigned int a) { @@ -237,6 +251,15 @@ static inline int clz64(uint64_t a) #endif } +static inline uintptr_t clz_uintptr(uintptr_t a) +{ + if (sizeof(uintptr_t) == sizeof(uint32_t)) { + return clz32(a); + } else { + return clz64(a); + } +} + /* WARNING: undefined if a = 0 */ static inline int ctz32(unsigned int a) { diff --git a/quickjs.c b/quickjs.c index 27dc68c31..d1f302a56 100644 --- a/quickjs.c +++ b/quickjs.c @@ -5601,7 +5601,12 @@ static int init_shape_hash(JSRuntime *rt) return 0; } -/* same magic hash multiplier as the Linux kernel */ +static uintptr_t hash_atom(JSAtom atom, uintptr_t mask) +{ + int shift = 1 + clz_uintptr(mask+1); + return hash_uintptr(atom) >> shift; +} + static uint32_t shape_hash(uint32_t h, uint32_t val) { return hash32(h + val); @@ -5881,7 +5886,7 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, sizeof(prop_hash_end(sh)[0]) * new_hash_size); for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { if (pr->atom != JS_ATOM_NULL) { - h = ((uintptr_t)pr->atom & new_hash_mask); + h = hash_atom(pr->atom, new_hash_mask); pr->hash_next = prop_hash_end(sh)[-h - 1]; prop_hash_end(sh)[-h - 1] = i + 1; } @@ -5956,7 +5961,7 @@ static int compact_properties(JSContext *ctx, JSObject *p) if (old_pr->atom != JS_ATOM_NULL) { pr->atom = old_pr->atom; pr->flags = old_pr->flags; - h = ((uintptr_t)old_pr->atom & new_hash_mask); + h = hash_atom(old_pr->atom, new_hash_mask); pr->hash_next = prop_hash_end(sh)[-h - 1]; prop_hash_end(sh)[-h - 1] = j + 1; prop[j] = prop[i]; @@ -6018,7 +6023,7 @@ static int add_shape_property(JSContext *ctx, JSShape **psh, pr->flags = prop_flags; /* add in hash table */ hash_mask = sh->prop_hash_mask; - h = atom & hash_mask; + h = hash_atom(atom, hash_mask); pr->hash_next = prop_hash_end(sh)[-h - 1]; prop_hash_end(sh)[-h - 1] = sh->prop_count; return 0; @@ -6810,7 +6815,7 @@ static inline JSShapeProperty *find_own_property1(JSObject *p, JSAtom atom) JSShapeProperty *pr, *prop; intptr_t h; sh = p->shape; - h = (uintptr_t)atom & sh->prop_hash_mask; + h = hash_atom(atom, sh->prop_hash_mask); h = prop_hash_end(sh)[-h - 1]; prop = get_shape_prop(sh); while (h) { @@ -6831,7 +6836,7 @@ static inline JSShapeProperty *find_own_property(JSProperty **ppr, JSShapeProperty *pr, *prop; intptr_t h; sh = p->shape; - h = (uintptr_t)atom & sh->prop_hash_mask; + h = hash_atom(atom, sh->prop_hash_mask); h = prop_hash_end(sh)[-h - 1]; prop = get_shape_prop(sh); while (h) { @@ -10237,7 +10242,7 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) redo: sh = p->shape; - h1 = atom & sh->prop_hash_mask; + h1 = hash_atom(atom, sh->prop_hash_mask); h = prop_hash_end(sh)[-h1 - 1]; prop = get_shape_prop(sh); lpr = NULL;