Skip to content

Commit cc70ffc

Browse files
committed
implement exclusive non-overlapping field support
1 parent b074b7f commit cc70ffc

15 files changed

+250
-23
lines changed

bitbybit-tests/src/bitfield_tests.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,66 @@ fn test_defmt_impl_bitfields() {
18611861
defmt_impl_check(&test);
18621862
}
18631863

1864+
#[test]
1865+
fn test_forbidden_overlaps_okay_u8() {
1866+
#[bitfield(u8, forbid_overlaps)]
1867+
struct Test {
1868+
#[bits(4..=7, rw)]
1869+
bit_upper: u4,
1870+
#[bit(3, rw)]
1871+
bit_3: bool,
1872+
#[bit(2, rw)]
1873+
bit_2: bool,
1874+
#[bit(1, rw)]
1875+
bit_1: bool,
1876+
#[bit(0, rw)]
1877+
bit_0: bool,
1878+
}
1879+
Test::new_with_raw_value(0xFF);
1880+
}
1881+
1882+
#[test]
1883+
fn test_forbidden_overlaps_okay_u16() {
1884+
#[bitfield(u16, forbid_overlaps)]
1885+
struct Test {
1886+
#[bits(12..=15, rw)]
1887+
bit_upper: u4,
1888+
#[bit(11, rw)]
1889+
bit_11: bool,
1890+
#[bit(10, rw)]
1891+
bit_10: bool,
1892+
#[bit(9, rw)]
1893+
bit_9: bool,
1894+
#[bit(8, rw)]
1895+
bit_8: bool,
1896+
#[bits(0..=7, rw)]
1897+
lower_bits: u8,
1898+
}
1899+
Test::new_with_raw_value(0x1F1F);
1900+
}
1901+
1902+
#[test]
1903+
fn test_forbidden_overlaps_okay_u32() {
1904+
#[bitfield(u32, forbid_overlaps)]
1905+
struct Test {
1906+
#[bits(16..=31, rw)]
1907+
bits_upper_2: u16,
1908+
#[bits(12..=15, rw)]
1909+
bits_upper_1: u4,
1910+
#[bit(11, rw)]
1911+
bit_11: bool,
1912+
#[bit(10, rw)]
1913+
bit_10: bool,
1914+
#[bit(9, rw)]
1915+
bit_9: bool,
1916+
#[bit(8, rw)]
1917+
bit_8: bool,
1918+
#[bits(0..=7, rw)]
1919+
lower_bits: u8,
1920+
}
1921+
Test::new_with_raw_value(0x1F1FF1F1);
1922+
}
1923+
18641924
#[test]
18651925
fn test_defmt_impl_bitfields_on_arbitrary_int_fields_u8_primitive_val() {
18661926
#[bitfield(u6, defmt_bitfields)]

bitbybit/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This version expects arbitrary-int 2.x.
77
### Added
88

99
- Support for signed arbitrary-int integers as field types, e.g. `i3`, `i24`, etc.
10+
- Added `forbid_overlaps` attribute argument which checks and denies overlaps of `bitfield` fields.
1011

1112
## bitbybit 1.4.0
1213

bitbybit/README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct GICD_TYPER {
3737

3838
How this works:
3939

40-
- #[bitfield(u32)] specifies that this is a bitfield in which u32 is the underlying data type. This means that all the
40+
- `#[bitfield(u32)]` specifies that this is a bitfield in which u32 is the underlying data type. This means that all the
4141
bits inside of the bitfield
4242
have to fit within 32 bits. Built-in Rust types (u8, u16, u32, u64, u128) as well as arbitrary-ints (u17, u48 etc) are
4343
supported.
@@ -187,6 +187,23 @@ immediates in a way that they have to be reassembled. This can be achieved like
187187
}
188188
```
189189

190+
## Field overlap checking
191+
192+
In some situations, it can be useful to check for and deny overlapping bits.
193+
The macro code generator can be instructed to do this using the `forbid_overlaps` specifier.
194+
195+
For example, the following code will not compile:
196+
197+
```rs
198+
#[bitfield(u32, forbid_overlaps)]
199+
struct NoOverlaps {
200+
#[bits(4..=11, r)]
201+
overlapping_portion: u8,
202+
#[bits(0..=7, r)]
203+
lower_8_bits: u8,
204+
}
205+
```
206+
190207
## Debug
191208

192209
The `bitfield` macro can generate a `Debug` implementation for you which prints

bitbybit/src/bitfield/codegen.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::bitfield::{
2-
const_name, mask_name, setter_name, with_name, BaseDataSize, BitfieldAttributes, CustomType,
3-
DefmtVariant, FieldDefinition, BITCOUNT_BOOL,
2+
const_name, mask_name, setter_name, with_name, ArrayInfo, BaseDataSize, BitfieldAttributes,
3+
CustomType, DefmtVariant, FieldDefinition, BITCOUNT_BOOL,
44
};
55
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream, TokenTree};
66
use quote::{quote, TokenStreamExt as _};
@@ -52,7 +52,7 @@ pub fn generate(
5252
let mask_name = mask_name(field_name);
5353
let mask = setter_mask(&one, field_definition);
5454

55-
if let Some((count, stride)) = field_definition.array {
55+
if let Some(ArrayInfo { count, indexed_stride: stride }) = field_definition.array {
5656
let count_name = const_name(field_name, "COUNT");
5757
let stride_name = const_name(field_name, "STRIDE");
5858
quote! {
@@ -99,7 +99,7 @@ pub fn generate(
9999
};
100100

101101
if let Some(array) = field_definition.array {
102-
let indexed_count = array.0;
102+
let indexed_count = array.count;
103103
quote! {
104104
#(#doc_comment)*
105105
#[inline]
@@ -160,7 +160,7 @@ pub fn generate(
160160
let with_name = with_name(field_name);
161161

162162
if let Some(array) = field_definition.array {
163-
let indexed_count = array.0;
163+
let indexed_count = array.count;
164164
quote! {
165165
#(#doc_comment)*
166166
#[inline]
@@ -235,7 +235,7 @@ fn extracted_bits(
235235
base_data_size: BaseDataSize,
236236
total_number_bits: usize,
237237
) -> TokenStream {
238-
let indexed_stride = field_definition.array.map(|(_, stride)| stride);
238+
let indexed_stride = field_definition.array.map(|info| info.indexed_stride);
239239
let array_shift = indexed_stride.map_or_else(
240240
|| quote! {},
241241
|indexed_stride| quote! { + index * #indexed_stride },
@@ -296,8 +296,8 @@ fn setter_new_raw_value(
296296
base_data_size: BaseDataSize,
297297
internal_base_data_type: &Type,
298298
) -> TokenStream {
299-
if let Some(array) = field_definition.array {
300-
let indexed_stride = array.1;
299+
if let Some(array) = &field_definition.array {
300+
let indexed_stride = array.indexed_stride;
301301
// bool?
302302
if field_definition.field_type_size_from_data_type == Some(BITCOUNT_BOOL) {
303303
assert_eq!(field_definition.ranges.len(), 1);
@@ -449,8 +449,8 @@ pub fn make_builder(
449449
// .with_a(1, value[1])
450450
// .with_a(2, value[2])
451451

452-
let array_count = array.0;
453-
let array_stride = array.1;
452+
let array_count = array.count;
453+
let array_stride = array.indexed_stride;
454454
if ranges_have_self_overlap(&field_definition.ranges, array_stride, array_count) {
455455
return (quote! {}, Vec::new());
456456
}

bitbybit/src/bitfield/mod.rs

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,18 @@ fn try_parse_arbitrary_int_type(s: &str, allow_signed: bool) -> Option<(usize, b
5252
}
5353
}
5454

55+
#[derive(Debug, Clone, Copy)]
56+
pub struct ArrayInfo {
57+
count: usize,
58+
indexed_stride: usize,
59+
}
60+
5561
#[cfg_attr(feature = "extra-traits", derive(Debug))]
5662
struct FieldDefinition {
5763
field_name: Ident,
5864
ranges: Vec<Range<usize>>,
5965
unsigned_field_type: Option<Type>,
60-
array: Option<(usize, usize)>,
66+
array: Option<ArrayInfo>,
6167
field_type_size: usize,
6268
getter_type: Option<Type>,
6369
setter_type: Option<Type>,
@@ -141,6 +147,7 @@ pub enum DefmtVariant {
141147
struct BitfieldAttributes {
142148
pub base_type: Option<Ident>,
143149
pub default_val: Option<DefaultVal>,
150+
pub forbid_overlaps: bool,
144151
pub debug_trait: bool,
145152
pub introspect: bool,
146153
pub defmt_trait: Option<DefmtTrait>,
@@ -174,6 +181,10 @@ impl BitfieldAttributes {
174181
}
175182
return Ok(());
176183
}
184+
if meta.path.is_ident("forbid_overlaps") {
185+
self.forbid_overlaps = true;
186+
return Ok(());
187+
}
177188
if meta.path.is_ident("debug") {
178189
self.debug_trait = true;
179190
return Ok(());
@@ -265,19 +276,25 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream {
265276
.unwrap_or_else(|_| panic!("bitfield!: Error parsing internal base data type"));
266277

267278
let input = syn::parse_macro_input!(input as DeriveInput);
268-
let struct_name = input.ident;
269-
let struct_vis = input.vis;
270-
let struct_attrs = input.attrs;
279+
let struct_name = &input.ident;
280+
let struct_vis = &input.vis;
281+
let struct_attrs = &input.attrs;
271282

272-
let fields = match input.data {
273-
Data::Struct(struct_data) => struct_data.fields,
283+
let fields = match &input.data {
284+
Data::Struct(struct_data) => &struct_data.fields,
274285
_ => panic!("bitfield!: Must be used on struct"),
275286
};
276287

277-
let field_definitions = match parsing::parse(&fields, base_data_size) {
288+
let field_definitions = match parsing::parse(fields, base_data_size) {
278289
Ok(definitions) => definitions,
279290
Err(token_stream) => return token_stream.into_compile_error().into(),
280291
};
292+
293+
if bitfield_attrs.forbid_overlaps {
294+
if let Err(e) = check_for_overlaps(&field_definitions, &input) {
295+
return e.into_compile_error().into();
296+
}
297+
}
281298
let accessors = codegen::generate(
282299
&field_definitions,
283300
base_data_size,
@@ -351,16 +368,16 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream {
351368
}
352369

353370
let defmt_trait = codegen::generate_defmt_trait_impl(
354-
&struct_name,
371+
struct_name,
355372
&bitfield_attrs,
356373
&field_definitions,
357374
base_data_size,
358375
);
359376

360377
let (new_with_constructor, new_with_builder_chain) = codegen::make_builder(
361-
&struct_name,
378+
struct_name,
362379
bitfield_attrs.default_val.is_some(),
363-
&struct_vis,
380+
struct_vis,
364381
&internal_base_data_type,
365382
base_data_type,
366383
base_data_size,
@@ -435,6 +452,47 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream {
435452
TokenStream::from(expanded)
436453
}
437454

455+
fn check_for_overlaps(
456+
field_definitions: &[FieldDefinition],
457+
input: &DeriveInput,
458+
) -> syn::Result<()> {
459+
let mut current_bitmask: u128 = 0;
460+
461+
for field in field_definitions {
462+
let mut check_and_update_bitmask = |offset: usize, width: usize| {
463+
// Create an all-ones bitmask for the given range, e.g. 0b1110 for range (1..=3).
464+
let mask = ((1_u128 << width) - 1) << offset;
465+
if (current_bitmask & mask) != 0 {
466+
return Err(syn::Error::new_spanned(
467+
input,
468+
format!(
469+
"bitfield!: Detected overlap of field `{}` with other field",
470+
field.field_name
471+
),
472+
));
473+
}
474+
current_bitmask |= mask;
475+
Ok(())
476+
};
477+
for range in &field.ranges {
478+
let width = range.end - range.start;
479+
match field.array {
480+
Some(info) => {
481+
let mut current_offset = range.start;
482+
for _ in 0..info.count {
483+
check_and_update_bitmask(current_offset, width)?;
484+
current_offset += info.indexed_stride;
485+
}
486+
}
487+
None => {
488+
check_and_update_bitmask(range.start, width)?;
489+
}
490+
}
491+
}
492+
}
493+
Ok(())
494+
}
495+
438496
fn with_name(field_name: &Ident) -> Ident {
439497
// The field might have started with r#. If so, it was likely used for a keyword. This can be dropped here
440498
let field_name_without_prefix = {

bitbybit/src/bitfield/parsing.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::bitfield::{
2-
is_int_size_regular_type, try_parse_arbitrary_int_type, BaseDataSize, CustomType,
2+
is_int_size_regular_type, try_parse_arbitrary_int_type, ArrayInfo, BaseDataSize, CustomType,
33
FieldDefinition, BITCOUNT_BOOL,
44
};
55
use proc_macro2::{Ident, Literal, Punct, TokenStream as TokenStream2, TokenTree};
@@ -428,7 +428,10 @@ fn parse_field(base_data_size: usize, field: &Field) -> Result<FieldDefinition>
428428
primitive_type,
429429
custom_type,
430430
doc_comment,
431-
array: indexed_count.map(|count| (count, indexed_stride.unwrap())),
431+
array: indexed_count.map(|count| ArrayInfo {
432+
count,
433+
indexed_stride: indexed_stride.unwrap(),
434+
}),
432435
field_type_size_from_data_type: field_type_size_from_data_type.map(|v| v.0),
433436
is_signed: field_type_size_from_data_type.map_or(false, |v| v.1),
434437
unsigned_field_type,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use bitbybit::bitfield;
2+
3+
#[bitfield(u32, default = 0, forbid_overlaps)]
4+
struct Test {
5+
#[bits(8..=15, rw)]
6+
overlapping_bits: u8,
7+
#[bits(0..=7, rw)]
8+
u8_array: [u8; 2],
9+
}
10+
11+
fn main() {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: bitfield!: Detected overlap of field `u8_array` with other field
2+
--> tests/no_compile/overlapping_bitfield_arrays.rs:4:1
3+
|
4+
4 | / struct Test {
5+
5 | | #[bits(8..=15, rw)]
6+
6 | | overlapping_bits: u8,
7+
7 | | #[bits(0..=7, rw)]
8+
8 | | u8_array: [u8; 2],
9+
9 | | }
10+
| |_^
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use bitbybit::bitfield;
2+
3+
#[bitfield(u32, default = 0, forbid_overlaps)]
4+
struct Test {
5+
#[bits(16..=23, rw)]
6+
overlapping_bits: u8,
7+
#[bits(0..=7, rw, stride = 16)]
8+
u8_array: [u8; 2],
9+
}
10+
11+
fn main() {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: bitfield!: Detected overlap of field `u8_array` with other field
2+
--> tests/no_compile/overlapping_bitfield_arrays_with_stride.rs:4:1
3+
|
4+
4 | / struct Test {
5+
5 | | #[bits(16..=23, rw)]
6+
6 | | overlapping_bits: u8,
7+
7 | | #[bits(0..=7, rw, stride = 16)]
8+
8 | | u8_array: [u8; 2],
9+
9 | | }
10+
| |_^

0 commit comments

Comments
 (0)