Skip to content

Commit aa5a0e1

Browse files
rebasedmingstuhood
andauthored
fix: overflow for large bit counts and large document counts in b… (#99)
Co-authored-by: Stu Hood <stuhood@paradedb.com>
1 parent ebb55ff commit aa5a0e1

1 file changed

Lines changed: 30 additions & 11 deletions

File tree

bitpacker/src/bitpacker.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ impl BitUnpacker {
103103
#[inline]
104104
pub fn block_num(&self, idx: u32) -> BlockNumber {
105105
// Find the address in bits of the index.
106-
let addr_in_bits = (idx * self.num_bits) as usize;
106+
let addr_in_bits = (idx as usize) * (self.num_bits as usize);
107107

108108
// Then round down to the nearest byte.
109109
let addr_in_bytes = addr_in_bits >> 3;
@@ -138,10 +138,11 @@ impl BitUnpacker {
138138
/// boundaries.
139139
#[inline]
140140
pub fn block_oblivious_range(&self, id_range: Range<u32>, data_len: usize) -> Range<usize> {
141-
let start_in_bits = id_range.start * self.num_bits;
142-
let start = (start_in_bits >> 3) as usize;
143-
let end_in_bits = id_range.end * self.num_bits;
144-
let end = (end_in_bits >> 3) as usize;
141+
let start_in_bits = (id_range.start as usize) * (self.num_bits as usize);
142+
let start = start_in_bits >> 3;
143+
assert!(start <= data_len);
144+
let end_in_bits = (id_range.end as usize) * (self.num_bits as usize);
145+
let end = end_in_bits >> 3;
145146
// TODO: We fetch more than we need and then truncate.
146147
start..(std::cmp::min(end + 8, data_len))
147148
}
@@ -154,16 +155,16 @@ impl BitUnpacker {
154155
/// Get the value at the given idx, which must exist within the given subset of the data.
155156
#[inline]
156157
pub fn get_from_subset(&self, idx: u32, data_offset: usize, data: &[u8]) -> u64 {
157-
let addr_in_bits = idx * self.num_bits;
158-
let addr = (addr_in_bits >> 3) as usize - data_offset;
158+
let addr_in_bits = (idx as usize) * (self.num_bits as usize);
159+
let addr = (addr_in_bits >> 3) - data_offset;
159160
if addr + 8 > data.len() {
160161
if self.num_bits == 0 {
161162
return 0;
162163
}
163-
let bit_shift = addr_in_bits & 7;
164+
let bit_shift = (addr_in_bits & 7) as u32;
164165
return self.get_slow_path(addr, bit_shift, data);
165166
}
166-
let bit_shift = addr_in_bits & 7;
167+
let bit_shift = (addr_in_bits & 7) as u32;
167168
let bytes: [u8; 8] = (&data[addr..addr + 8]).try_into().unwrap();
168169
let val_unshifted_unmasked: u64 = u64::from_le_bytes(bytes);
169170
let val_shifted = val_unshifted_unmasked >> bit_shift;
@@ -197,7 +198,7 @@ impl BitUnpacker {
197198

198199
let end_idx = start_idx + output.len() as u32;
199200

200-
let end_bit_read = end_idx * self.num_bits;
201+
let end_bit_read = (end_idx as usize) * (self.num_bits as usize);
201202
let end_byte_read = (end_bit_read + 7) / 8;
202203
assert!(
203204
end_byte_read as usize <= data_offset + data.len(),
@@ -237,7 +238,7 @@ impl BitUnpacker {
237238
get_batch_ramp(start_idx, &mut output[..entrance_ramp_len as usize]);
238239

239240
// Highway
240-
let mut offset = ((highway_start * self.num_bits) as usize / 8) - data_offset;
241+
let mut offset = (((highway_start as usize) * (self.num_bits as usize)) / 8) - data_offset;
241242
let mut output_cursor = (highway_start - start_idx) as usize;
242243
for _ in 0..num_blocks {
243244
offset += BitPacker1x.decompress(
@@ -450,4 +451,22 @@ mod test {
450451
}
451452
}
452453
}
454+
455+
#[test]
456+
fn test_block_oblivious_range_overflow() {
457+
let bitunpacker = BitUnpacker::new(64);
458+
// Using IDs that would cause a 32-bit overflow:
459+
// start_doc * 64 bits = 4,294,967,232 (< 2^32)
460+
// end_doc * 64 bits = 4,294,967,360 (> 2^32)
461+
let start_doc = 67_108_863;
462+
let end_doc = 67_108_865;
463+
let data_len = 1_000_000_000;
464+
let range = bitunpacker.block_oblivious_range(start_doc..end_doc, data_len);
465+
assert!(
466+
range.start <= range.end,
467+
"Range start ({}) must be <= end ({})",
468+
range.start,
469+
range.end
470+
);
471+
}
453472
}

0 commit comments

Comments
 (0)