Skip to content

Commit e5e9bf4

Browse files
authored
chore: increase coverage, handle more decoding crashes (#71)
this handles a few more decoding edge cases, minor code cleanup
1 parent 76a2080 commit e5e9bf4

9 files changed

Lines changed: 814 additions & 131 deletions

File tree

fuzz/fuzz_targets/rust_decompress_arbitrary.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,7 @@ fuzz_target!(|data: FuzzInput| {
7575
};
7676

7777
// The decoder must either succeed or return an error. A panic is a bug.
78+
// We are ok if not all data is consumed because it tries to parse
79+
// garbage - as long as we don't panic, we are good
7880
let _ = codec.decompress_to_slice(&compressed, &mut output);
7981
});

fuzz/justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ rust-compress *args: (run 'rust_compress_oracle' args)
2828
# Run rust_decompress_oracle (uses C++ as oracle)
2929
rust-decompress *args: (run 'rust_decompress_oracle' args)
3030

31-
# Feed arbitrary bytes directly to the Rust decompressr; runs=0 means run indefinitely (Ctrl-C to stop)
31+
# Feed arbitrary bytes directly to the Rust decompressor; runs=0 means run indefinitely (Ctrl-C to stop)
3232
rust-decompress-arbitrary *args: (run 'rust_decompress_arbitrary' args)
3333

3434
# Run cpp_roundtrip (C++ roundtrip)

justfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ clippy *args:
5656
cargo clippy --workspace --all-targets --features _all_compatible {{args}}
5757

5858
# Generate code coverage report. Will install `cargo llvm-cov` if missing.
59-
coverage *args='--no-clean --open': (cargo-install 'cargo-llvm-cov')
59+
coverage *args='--open': (cargo-install 'cargo-llvm-cov')
60+
cargo llvm-cov clean --workspace
6061
cargo llvm-cov --workspace --all-targets --features _all_compatible --include-build-script {{args}}
6162

6263
# Build and open code documentation

src/rust/integer_compression/fastpfor.rs

Lines changed: 93 additions & 98 deletions
Large diffs are not rendered by default.
Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::rust::{FastPForError, FastPForResult};
2+
13
/// Finds the greatest multiple of `factor` that is less than or equal to `value`.
24
pub fn greatest_multiple(value: u32, factor: u32) -> u32 {
35
value - value % factor
@@ -9,29 +11,51 @@ pub fn bits(i: u32) -> usize {
911
32 - i.leading_zeros() as usize
1012
}
1113

12-
/// Extracts a byte from an i32 array treated as packed bytes in big-endian order.
13-
#[expect(dead_code)]
14-
pub fn grab_byte(input: &[i32], index: u32) -> u8 {
15-
(input[(index / 4) as usize] >> (24 - (index % 4) * 8)) as u8
14+
pub trait AsUsize: Eq + Copy {
15+
fn as_usize(self) -> usize;
1616
}
1717

18-
/// Returns the position of the most significant bit in `x` (1-indexed).
19-
/// Returns 0 for input 0.
20-
#[expect(dead_code)]
21-
pub fn leading_bit_position(x: u32) -> i32 {
22-
bitlen(u64::from(x))
18+
impl AsUsize for usize {
19+
#[inline]
20+
fn as_usize(self) -> usize {
21+
self
22+
}
2323
}
2424

25-
/// Counts the number of leading zeros in `x`.
26-
fn clz(x: u64) -> u64 {
27-
u64::from(x.leading_zeros())
25+
impl AsUsize for u32 {
26+
#[inline]
27+
fn as_usize(self) -> usize {
28+
const _: () = {
29+
// Some day Rust may support usize smaller than u32?
30+
assert!(
31+
size_of::<u32>() <= size_of::<usize>(),
32+
"usize must be able to hold all u32 values"
33+
);
34+
};
35+
36+
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
37+
{
38+
self as usize
39+
}
40+
}
2841
}
2942

30-
/// Returns the bit length of `x` (number of bits needed to represent it).
31-
/// Returns 0 for input 0.
32-
fn bitlen(x: u64) -> i32 {
33-
if x == 0 {
34-
return 0;
43+
pub trait GetWithErr<T> {
44+
fn get_val(&self, pos: impl AsUsize) -> FastPForResult<T>;
45+
}
46+
47+
impl<T: Copy> GetWithErr<T> for &[T] {
48+
#[inline]
49+
fn get_val(&self, pos: impl AsUsize) -> FastPForResult<T> {
50+
self.get(pos.as_usize())
51+
.copied()
52+
.ok_or(FastPForError::NotEnoughData)
53+
}
54+
}
55+
56+
impl<T: Copy> GetWithErr<T> for Vec<T> {
57+
#[inline]
58+
fn get_val(&self, pos: impl AsUsize) -> FastPForResult<T> {
59+
self.as_slice().get_val(pos)
3560
}
36-
64 - clz(x) as i32
3761
}

src/rust/integer_compression/variable_byte.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::io::Cursor;
33
use bytemuck::{cast_slice, cast_slice_mut};
44

55
use crate::rust::cursor::IncrementCursor;
6+
use crate::rust::integer_compression::helpers::AsUsize;
67
use crate::rust::{FastPForError, FastPForResult, Integer, Skippable};
78

89
/// Variable-byte encoding codec for integer compression.
@@ -138,7 +139,7 @@ impl Integer<u32> for VariableByte {
138139
}
139140

140141
// Convert u32 array to byte view
141-
let byte_length = (input_length as usize) * 4;
142+
let byte_length = (input_length.as_usize()) * 4;
142143
let input_start = input_offset.position() as usize;
143144

144145
// Create a byte slice view of the input
@@ -232,7 +233,7 @@ impl Integer<i8> for VariableByte {
232233
}
233234
let mut out_pos_tmp = output_offset.position();
234235
for k in input_offset.position() as u32..(input_offset.position() as u32 + input_length) {
235-
let val = input[k as usize];
236+
let val = input[k.as_usize()];
236237
if val < (1 << 7) {
237238
output[out_pos_tmp as usize] = Self::extract_7bits::<0>(val) as i8;
238239
out_pos_tmp += 1;
@@ -287,43 +288,43 @@ impl Integer<i8> for VariableByte {
287288
let mut tmp_outpos = output_offset.position();
288289

289290
while p < final_p {
290-
let mut v = i32::from(input[p as usize] & 0x7F);
291-
if input[p as usize] >= 0 {
291+
let mut v = i32::from(input[p.as_usize()] & 0x7F);
292+
if input[p.as_usize()] >= 0 {
292293
// High bit is NOT set, this is the last byte
293294
p += 1;
294295
output[tmp_outpos as usize] = v as u32;
295296
tmp_outpos += 1;
296297
continue;
297298
}
298299

299-
v |= i32::from(input[p as usize + 1] & 0x7F) << 7;
300-
if input[p as usize + 1] >= 0 {
300+
v |= i32::from(input[p.as_usize() + 1] & 0x7F) << 7;
301+
if input[p.as_usize() + 1] >= 0 {
301302
// High bit is NOT set, this is the last byte
302303
p += 2;
303304
output[tmp_outpos as usize] = v as u32;
304305
tmp_outpos += 1;
305306
continue;
306307
}
307308

308-
v |= i32::from(input[p as usize + 2] & 0x7F) << 14;
309-
if input[p as usize + 2] >= 0 {
309+
v |= i32::from(input[p.as_usize() + 2] & 0x7F) << 14;
310+
if input[p.as_usize() + 2] >= 0 {
310311
// High bit is NOT set, this is the last byte
311312
p += 3;
312313
output[tmp_outpos as usize] = v as u32;
313314
tmp_outpos += 1;
314315
continue;
315316
}
316317

317-
v |= i32::from(input[p as usize + 3] & 0x7F) << 21;
318-
if input[p as usize + 3] >= 0 {
318+
v |= i32::from(input[p.as_usize() + 3] & 0x7F) << 21;
319+
if input[p.as_usize() + 3] >= 0 {
319320
// High bit is NOT set, this is the last byte
320321
p += 4;
321322
output[tmp_outpos as usize] = v as u32;
322323
tmp_outpos += 1;
323324
continue;
324325
}
325326

326-
v |= i32::from(input[p as usize + 4] & 0x0F) << 28;
327+
v |= i32::from(input[p.as_usize() + 4] & 0x0F) << 28;
327328
p += 5;
328329
output[tmp_outpos as usize] = v as u32;
329330
tmp_outpos += 1;

tests/common.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
//! Common test utilities for codec compatibility testing.
22
33
#![cfg(all(feature = "rust", feature = "cpp"))]
4-
// This file is shared by several test modules
5-
#![allow(dead_code)]
4+
#![allow(dead_code, reason = "This file is shared by several test modules")]
65

76
use std::io::Cursor;
87

0 commit comments

Comments
 (0)