Skip to content

Commit

Permalink
Check for overflow in computed flags
Browse files Browse the repository at this point in the history
  • Loading branch information
meithecatte committed Feb 24, 2021
1 parent d0eb893 commit c2d64de
Show file tree
Hide file tree
Showing 11 changed files with 54 additions and 15 deletions.
24 changes: 17 additions & 7 deletions enumflags_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ use syn::{
Ident, Item, ItemEnum, Token,
};

#[derive(Debug)]
struct Flag {
name: Ident,
span: Span,
value: FlagValue,
}

#[derive(Debug)]
enum FlagValue {
Literal(u128),
Deferred,
Expand Down Expand Up @@ -88,6 +90,9 @@ fn fold_expr(expr: &syn::Expr) -> Option<u128> {
_ => None,
}
}
Expr::Paren(syn::ExprParen { expr, .. }) | Expr::Group(syn::ExprGroup { expr, .. }) => {
fold_expr(&expr)
}
_ => None,
}
}
Expand Down Expand Up @@ -187,7 +192,7 @@ fn type_bits(ty: &Ident) -> Result<u8, syn::Error> {
}

/// Returns deferred checks
fn check_flag(type_name: &Ident, flag: &Flag) -> Result<Option<TokenStream>, syn::Error> {
fn check_flag(type_name: &Ident, flag: &Flag, bits: u8) -> Result<Option<TokenStream>, syn::Error> {
use FlagValue::*;
match flag.value {
Literal(n) => {
Expand All @@ -196,6 +201,11 @@ fn check_flag(type_name: &Ident, flag: &Flag) -> Result<Option<TokenStream>, syn
flag.span,
"Flags must have exactly one set bit",
))
} else if bits < 128 && n >= 1 << bits {
Err(syn::Error::new(
flag.span,
format!("Flag value out of range for u{}", bits),
))
} else {
Ok(None)
}
Expand Down Expand Up @@ -235,17 +245,17 @@ fn gen_enumflags(ast: &ItemEnum, default: Vec<Ident>) -> Result<TokenStream, syn
let variant_names = ast.variants.iter().map(|v| &v.ident).collect::<Vec<_>>();
let repeated_name = vec![&ident; ast.variants.len()];

let variants = collect_flags(ast.variants.iter())?;
let deferred = variants
.iter()
.flat_map(|variant| check_flag(ident, variant).transpose())
.collect::<Result<Vec<_>, _>>()?;

let ty = extract_repr(&ast.attrs)?
.ok_or_else(|| syn::Error::new_spanned(&ident,
"repr attribute missing. Add #[repr(u64)] or a similar attribute to specify the size of the bitfield."))?;
let bits = type_bits(&ty)?;

let variants = collect_flags(ast.variants.iter())?;
let deferred = variants
.iter()
.flat_map(|variant| check_flag(ident, variant, bits).transpose())
.collect::<Result<Vec<_>, _>>()?;

if (bits as usize) < variants.len() {
return Err(syn::Error::new_spanned(
&ty,
Expand Down
1 change: 1 addition & 0 deletions test_suite/ui/missing_disciminant.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#[enumflags2::bitflags]
#[repr(u8)]
#[derive(Copy, Clone)]
enum Foo {
OhNoTheresNoDiscriminant,
Expand Down
4 changes: 2 additions & 2 deletions test_suite/ui/missing_disciminant.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: Please add an explicit discriminant
--> $DIR/missing_disciminant.rs:4:5
--> $DIR/missing_disciminant.rs:5:5
|
4 | OhNoTheresNoDiscriminant,
5 | OhNoTheresNoDiscriminant,
| ^^^^^^^^^^^^^^^^^^^^^^^^
1 change: 1 addition & 0 deletions test_suite/ui/multiple_bits.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#[enumflags2::bitflags]
#[repr(u8)]
#[derive(Copy, Clone)]
enum Foo {
SingleBit = 1,
Expand Down
4 changes: 2 additions & 2 deletions test_suite/ui/multiple_bits.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: Flags must have exactly one set bit
--> $DIR/multiple_bits.rs:5:5
--> $DIR/multiple_bits.rs:6:5
|
5 | MultipleBits = 6,
6 | MultipleBits = 6,
| ^^^^^^^^^^^^^^^^
7 changes: 7 additions & 0 deletions test_suite/ui/shift_out_of_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ enum Bar {
BigNumber = 1 << 20,
}

#[enumflags2::bitflags]
#[repr(u16)]
#[derive(Copy, Clone)]
enum Baz {
BigNumber = (1 << 10) << 10,
}

fn main() {}
18 changes: 18 additions & 0 deletions test_suite/ui/shift_out_of_range.stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
error: Flag value out of range for u64
--> $DIR/shift_out_of_range.rs:5:5
|
5 | BigNumber = 1 << 69,
| ^^^^^^^^^^^^^^^^^^^

error: Flag value out of range for u16
--> $DIR/shift_out_of_range.rs:12:5
|
12 | BigNumber = 1 << 20,
| ^^^^^^^^^^^^^^^^^^^

error: Flag value out of range for u16
--> $DIR/shift_out_of_range.rs:19:5
|
19 | BigNumber = (1 << 10) << 10,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0080]: evaluation of constant value failed
--> $DIR/shift_out_of_range.rs:5:17
|
Expand Down
1 change: 1 addition & 0 deletions test_suite/ui/with_fields.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#[enumflags2::bitflags]
#[repr(u8)]
#[derive(Copy, Clone)]
enum Foo {
Bar(u32),
Expand Down
4 changes: 2 additions & 2 deletions test_suite/ui/with_fields.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: Bitflag variants cannot contain additional data
--> $DIR/with_fields.rs:4:8
--> $DIR/with_fields.rs:5:8
|
4 | Bar(u32),
5 | Bar(u32),
| ^^^^^
1 change: 1 addition & 0 deletions test_suite/ui/zero_disciminant.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#[enumflags2::bitflags]
#[repr(u8)]
#[derive(Copy, Clone)]
enum Foo {
Zero = 0,
Expand Down
4 changes: 2 additions & 2 deletions test_suite/ui/zero_disciminant.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: Flags must have exactly one set bit
--> $DIR/zero_disciminant.rs:4:5
--> $DIR/zero_disciminant.rs:5:5
|
4 | Zero = 0,
5 | Zero = 0,
| ^^^^^^^^

0 comments on commit c2d64de

Please sign in to comment.