Skip to content

Commit 3a2c8cd

Browse files
committed
increase mask robustness, add tests
1 parent cc70ffc commit 3a2c8cd

File tree

1 file changed

+51
-1
lines changed

1 file changed

+51
-1
lines changed

bitbybit/src/bitfield/mod.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,18 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream {
452452
TokenStream::from(expanded)
453453
}
454454

455+
const fn mask_for_width_and_offset(width: usize, offset: usize) -> u128 {
456+
if width == 128 {
457+
return u128::MAX;
458+
}
459+
if width + offset > 128 {
460+
panic!("bitfield!: Internal error: width + offset > 128");
461+
}
462+
// The wrapping subtraction is important for the case where width is 128.
463+
// A shift with 128 would yield a value of 0, and a regular subtraction of 1 could panic.
464+
((1_u128 << width) - 1) << offset
465+
}
466+
455467
fn check_for_overlaps(
456468
field_definitions: &[FieldDefinition],
457469
input: &DeriveInput,
@@ -461,7 +473,7 @@ fn check_for_overlaps(
461473
for field in field_definitions {
462474
let mut check_and_update_bitmask = |offset: usize, width: usize| {
463475
// Create an all-ones bitmask for the given range, e.g. 0b1110 for range (1..=3).
464-
let mask = ((1_u128 << width) - 1) << offset;
476+
let mask = mask_for_width_and_offset(width, offset);
465477
if (current_bitmask & mask) != 0 {
466478
return Err(syn::Error::new_spanned(
467479
input,
@@ -556,3 +568,41 @@ fn const_name(field_name: &Ident, suffix: &str) -> Ident {
556568
syn::parse_str::<Ident>(&name)
557569
.unwrap_or_else(|_| panic!("bitfield!: Error creating {name} name"))
558570
}
571+
572+
#[cfg(test)]
573+
mod tests {
574+
use super::*;
575+
576+
#[test]
577+
fn test_mask_underflow() {
578+
assert_eq!(mask_for_width_and_offset(128, 0), u128::MAX);
579+
}
580+
581+
#[test]
582+
#[should_panic]
583+
fn test_mask_error() {
584+
assert_eq!(mask_for_width_and_offset(127, 1), u128::MAX - 1);
585+
// This panics.
586+
assert_eq!(mask_for_width_and_offset(127, 2), u128::MAX);
587+
}
588+
589+
#[test]
590+
fn test_mask_0() {
591+
assert_eq!(mask_for_width_and_offset(3, 1), 0b1110);
592+
}
593+
594+
#[test]
595+
fn test_mask_1() {
596+
assert_eq!(mask_for_width_and_offset(0, 0), 0);
597+
}
598+
599+
#[test]
600+
fn test_mask_2() {
601+
assert_eq!(mask_for_width_and_offset(1, 0), 1);
602+
}
603+
604+
#[test]
605+
fn test_mask_3() {
606+
assert_eq!(mask_for_width_and_offset(2, 0), 0b11);
607+
}
608+
}

0 commit comments

Comments
 (0)