Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions crates/common/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ impl HashSecret {
if value.is_empty() {
0
} else {
self.hash_value(value)
let mut hasher = self.build_hasher();
// Keep the slice length prefix consistent with the 64-bit hash
// representation, including on 32-bit targets.
hasher.write_u64(value.len() as u64);
hasher.write(value);
fix_sentinel(mod_int(hasher.finish() as PyHash))
}
}

Expand Down Expand Up @@ -249,15 +254,17 @@ pub fn hash_tuple<E>(
/// each element hash fallibly). The fold is commutative, so element order does
/// not affect the result.
pub struct FrozenSetHash {
hash: u64,
// collections.abc.Set._hash uses sys.maxsize to mask this algorithm,
// even when PyHash is wider than the native pointer size.
hash: usize,
}

impl FrozenSetHash {
#[must_use]
pub fn new(len: usize) -> Self {
// Factor in the number of active entries
Self {
hash: (len as u64 + 1).wrapping_mul(1927868237),
hash: len.wrapping_add(1).wrapping_mul(1927868237),
}
}

Expand All @@ -266,12 +273,12 @@ impl FrozenSetHash {
// This is important because some use cases have many combinations of a
// small number of elements with nearby hashes so that many distinct
// combinations collapse to only a handful of distinct hash values.
const fn shuffle_bits(h: u64) -> u64 {
const fn shuffle_bits(h: usize) -> usize {
((h ^ 89869747) ^ (h.wrapping_shl(16))).wrapping_mul(3644798167)
}
// Xor-in shuffled bits from every entry's hash field because xor is
// commutative and a frozenset hash should be independent of order.
self.hash ^= shuffle_bits(element_hash as u64);
self.hash ^= shuffle_bits(element_hash as usize);
}

#[must_use]
Expand All @@ -281,17 +288,43 @@ impl FrozenSetHash {
hash ^= (hash >> 11) ^ (hash >> 25);
hash = hash.wrapping_mul(69069).wrapping_add(907133923);
// -1 is reserved as an error code
if hash == u64::MAX {
if hash == usize::MAX {
hash = 590923713;
}
hash as PyHash
hash as isize as PyHash
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn frozenset_hash_matches_set_mixin_width() {
for elements in [
&[][..],
&[0][..],
&[-1, 1][..],
&[1, 2, 3][..],
&[i64::MIN, i64::MAX][..],
] {
let mut hasher = FrozenSetHash::new(elements.len());
let mask = usize::MAX as u128;
let mut expected = (1927868237 * (elements.len() as u128 + 1)) & mask;
for &element in elements {
hasher.add(element);
let h = element as u64 as u128;
expected ^= (h ^ (h << 16) ^ 89869747) * 3644798167;
expected &= mask;
}
expected ^= (expected >> 11) ^ (expected >> 25);
expected = (expected * 69069 + 907133923) & mask;
let expected = expected as usize as isize as PyHash;
let expected = if expected == -1 { 590923713 } else { expected };
assert_eq!(hasher.finish(), expected);
}
}

#[test]
fn tuple_hash_uses_the_declared_hash_width() {
for (elements, expected) in [
Expand Down
51 changes: 42 additions & 9 deletions crates/stdlib/src/random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,27 +76,28 @@ mod _random {
if k_bigint.is_negative() {
return Err(vm.new_value_error("number of bits must be non-negative"));
}
let k: isize = k_int
let k: u64 = k_int
.try_to_primitive(vm)
.map_err(|_| vm.new_overflow_error("getrandbits: number of bits too large"))?;
match k {
0 => Ok(BigInt::zero()),
1..=32 => Ok(BigInt::from(zelf.rng.lock().next_u32() >> (32 - k))),
mut k => {
let words = word_count(k).ok_or_else(|| vm.no_memory_error())?;
let mut word_array = Vec::new();
word_array
.try_reserve_exact(words)
.map_err(|_| vm.no_memory_error())?;
let mut rng = zelf.rng.lock();
let mut gen_u32 = |k| {
let r = rng.next_u32();
if k < 32 { r >> (32 - k) } else { r }
};

let words = (k - 1) / 32 + 1;
let word_array = (0..words)
.map(|_| {
let word = gen_u32(k);
k = k.wrapping_sub(32);
word
})
.collect::<Vec<_>>();
for _ in 0..words {
word_array.push(gen_u32(k));
k = k.wrapping_sub(32);
}

let uint = BigUint::new(word_array);
// very unlikely but might as well check
Expand Down Expand Up @@ -145,4 +146,36 @@ mod _random {
Ok(())
}
}

fn word_count(bits: u64) -> Option<usize> {
let words = usize::try_from(bits.div_ceil(32)).ok()?;
(words <= isize::MAX as usize / size_of::<u32>()).then_some(words)
}

#[cfg(test)]
mod tests {
use super::word_count;

#[test]
fn getrandbits_word_counts() {
for (bits, words) in [
(0, 0),
(1, 1),
(32, 1),
(33, 2),
(1 << 31, 1 << 26),
(1 << 32, 1 << 27),
] {
assert_eq!(word_count(bits), Some(words));
}
if usize::BITS == 32 {
let max_words = isize::MAX as usize / size_of::<u32>();
assert_eq!(word_count(max_words as u64 * 32), Some(max_words));
assert_eq!(word_count(max_words as u64 * 32 + 1), None);
assert_eq!(word_count(u64::MAX), None);
} else {
assert_eq!(word_count(u64::MAX), usize::try_from(1_u64 << 59).ok());
}
}
}
}
Loading