use crate::ast::{
Annotation, AnnotationParams, BitmaskDecl, BitsetDecl, ConstExpr, ConstrTypeDecl, Definition,
LiteralKind, Specification, TypeDecl,
};
use crate::errors::Span;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BitfieldValidationError {
PositionOutOfRange {
bitmask: String,
position: u32,
bit_bound: u32,
span: Span,
},
DuplicatePosition {
bitmask: String,
position: u32,
span: Span,
},
BitBoundTooLarge {
name: String,
value: u32,
span: Span,
},
BitsetTotalTooLarge {
name: String,
total: u32,
span: Span,
},
BitfieldWidthTooLarge {
bitset: String,
width: u32,
span: Span,
},
BitfieldExceedsStorageCap {
bitset: String,
width: u32,
cap: u32,
dest_type: &'static str,
span: Span,
},
}
#[must_use]
pub fn validate_bitfields(spec: &Specification) -> Vec<BitfieldValidationError> {
let mut errs = Vec::new();
for d in &spec.definitions {
walk(d, &mut errs);
}
errs
}
fn walk(d: &Definition, errs: &mut Vec<BitfieldValidationError>) {
match d {
Definition::Module(m) => {
for inner in &m.definitions {
walk(inner, errs);
}
}
Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Bitmask(b))) => {
validate_bitmask(b, errs);
}
Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Bitset(b))) => {
validate_bitset(b, errs);
}
_ => {}
}
}
fn extract_int_arg(p: &AnnotationParams) -> Option<u32> {
if let AnnotationParams::Single(ConstExpr::Literal(l)) = p {
if matches!(l.kind, LiteralKind::Integer) {
return l.raw.parse::<u32>().ok();
}
}
None
}
fn extract_annotation<'a>(anns: &'a [Annotation], name: &str) -> Option<&'a Annotation> {
anns.iter()
.find(|a| a.name.parts.last().map(|p| p.text.as_str()) == Some(name))
}
pub fn validate_bitmask(b: &BitmaskDecl, errs: &mut Vec<BitfieldValidationError>) {
let bit_bound = extract_annotation(&b.annotations, "bit_bound")
.and_then(|a| extract_int_arg(&a.params))
.unwrap_or(32);
if bit_bound > 64 {
errs.push(BitfieldValidationError::BitBoundTooLarge {
name: b.name.text.clone(),
value: bit_bound,
span: b.span,
});
}
let mut seen: Vec<u32> = Vec::new();
let mut next_implicit: u32 = 0;
for v in &b.values {
let pos = extract_annotation(&v.annotations, "position")
.and_then(|a| extract_int_arg(&a.params))
.unwrap_or(next_implicit);
next_implicit = pos + 1;
if pos >= bit_bound {
errs.push(BitfieldValidationError::PositionOutOfRange {
bitmask: b.name.text.clone(),
position: pos,
bit_bound,
span: v.span,
});
}
if seen.contains(&pos) {
errs.push(BitfieldValidationError::DuplicatePosition {
bitmask: b.name.text.clone(),
position: pos,
span: v.span,
});
} else {
seen.push(pos);
}
}
}
pub fn validate_bitset(b: &BitsetDecl, errs: &mut Vec<BitfieldValidationError>) {
let mut total: u32 = 0;
for bf in &b.bitfields {
let width = if let ConstExpr::Literal(l) = &bf.spec.width {
l.raw.parse::<u32>().unwrap_or(0)
} else {
0
};
if width > 64 {
errs.push(BitfieldValidationError::BitfieldWidthTooLarge {
bitset: b.name.text.clone(),
width,
span: bf.span,
});
}
if let Some((cap, name)) = bf.spec.dest_type.and_then(dest_type_cap) {
if width > cap {
errs.push(BitfieldValidationError::BitfieldExceedsStorageCap {
bitset: b.name.text.clone(),
width,
cap,
dest_type: name,
span: bf.span,
});
}
}
total = total.saturating_add(width);
}
if total > 64 {
errs.push(BitfieldValidationError::BitsetTotalTooLarge {
name: b.name.text.clone(),
total,
span: b.span,
});
}
}
fn dest_type_cap(dt: crate::ast::PrimitiveType) -> Option<(u32, &'static str)> {
use crate::ast::{IntegerType, PrimitiveType};
Some(match dt {
PrimitiveType::Boolean => (1, "boolean"),
PrimitiveType::Octet => (8, "octet"),
PrimitiveType::Char | PrimitiveType::WideChar => return None,
PrimitiveType::Integer(it) => match it {
IntegerType::Int8 | IntegerType::UInt8 => (8, "int8/uint8"),
IntegerType::Short | IntegerType::UShort | IntegerType::Int16 | IntegerType::UInt16 => {
(16, "short/uint16")
}
IntegerType::Long | IntegerType::ULong | IntegerType::Int32 | IntegerType::UInt32 => {
(32, "long/uint32")
}
IntegerType::LongLong
| IntegerType::ULongLong
| IntegerType::Int64
| IntegerType::UInt64 => (64, "long long/uint64"),
},
PrimitiveType::Floating(_) => return None,
})
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use crate::config::ParserConfig;
use crate::parser::parse;
fn parse_to_ast(src: &str) -> Specification {
parse(src, &ParserConfig::default()).expect("parse ok")
}
#[test]
fn position_within_bit_bound_ok() {
let ast = parse_to_ast("@bit_bound(8) bitmask Flags { @position(0) F0, @position(1) F1 };");
let errs = validate_bitfields(&ast);
assert!(errs.is_empty(), "got {errs:?}");
}
#[test]
fn position_out_of_range_errors() {
let ast = parse_to_ast("@bit_bound(4) bitmask Flags { @position(0) F0, @position(8) F1 };");
let errs = validate_bitfields(&ast);
assert!(
errs.iter()
.any(|e| matches!(e, BitfieldValidationError::PositionOutOfRange { .. }))
);
}
#[test]
fn duplicate_position_errors() {
let ast = parse_to_ast("bitmask Flags { @position(2) F0, @position(2) F1 };");
let errs = validate_bitfields(&ast);
assert!(
errs.iter()
.any(|e| matches!(e, BitfieldValidationError::DuplicatePosition { .. }))
);
}
#[test]
fn implicit_positions_increment() {
let ast = parse_to_ast("@bit_bound(4) bitmask Flags { F0, F1, F2, F3 };");
let errs = validate_bitfields(&ast);
assert!(errs.is_empty(), "got {errs:?}");
}
#[test]
fn implicit_positions_overflow_bound() {
let ast = parse_to_ast("@bit_bound(2) bitmask Flags { F0, F1, F2 };");
let errs = validate_bitfields(&ast);
assert!(
errs.iter()
.any(|e| matches!(e, BitfieldValidationError::PositionOutOfRange { .. }))
);
}
#[test]
fn bit_bound_above_64_errors() {
let ast = parse_to_ast("@bit_bound(128) bitmask Flags { F0 };");
let errs = validate_bitfields(&ast);
assert!(
errs.iter()
.any(|e| matches!(e, BitfieldValidationError::BitBoundTooLarge { .. }))
);
}
#[test]
fn bitfield_width_within_storage_cap_ok() {
let ast = parse_to_ast(
"bitset BS {\n\
bitfield<1, boolean> b;\n\
bitfield<8, octet> o;\n\
bitfield<16, short> s;\n\
};",
);
let errs = validate_bitfields(&ast);
assert!(errs.is_empty(), "got {errs:?}");
}
#[test]
fn bitfield_size_exceeds_octet_destination_is_error() {
let ast = parse_to_ast("bitset BS { bitfield<9, octet> b; };");
let errs = validate_bitfields(&ast);
assert!(
errs.iter().any(|e| matches!(
e,
BitfieldValidationError::BitfieldExceedsStorageCap {
cap: 8,
width: 9,
..
}
)),
"got {errs:?}"
);
}
#[test]
fn bitfield_size_exceeds_short_destination_is_error() {
let ast = parse_to_ast("bitset BS { bitfield<17, short> b; };");
let errs = validate_bitfields(&ast);
assert!(
errs.iter().any(|e| matches!(
e,
BitfieldValidationError::BitfieldExceedsStorageCap {
cap: 16,
width: 17,
..
}
)),
"got {errs:?}"
);
}
}