Skip to content

Commit 202b291

Browse files
committed
Support deriving AbstractType on enums
1 parent e0c07eb commit 202b291

File tree

1 file changed

+103
-24
lines changed
  • rust/binaryninja-derive/src

1 file changed

+103
-24
lines changed

rust/binaryninja-derive/src/lib.rs

Lines changed: 103 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,51 @@ use proc_macro2::TokenStream;
22
use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt};
33
use quote::quote;
44
use syn::spanned::Spanned;
5-
use syn::{parse_macro_input, Data, DeriveInput, Fields, FieldsNamed, Ident};
5+
use syn::{
6+
parse_macro_input, Attribute, Data, DeriveInput, Fields, FieldsNamed, Ident, Path, Variant,
7+
};
68

79
type Result<T> = std::result::Result<T, Diagnostic>;
810

11+
struct Repr {
12+
c: bool,
13+
primitive: Option<(Path, bool)>,
14+
}
15+
16+
impl Repr {
17+
fn from_attrs(attrs: Vec<Attribute>) -> Result<Self> {
18+
let mut c = false;
19+
let mut primitive = None;
20+
for attr in attrs {
21+
if attr.path().is_ident("repr") {
22+
attr.parse_nested_meta(|meta| {
23+
if let Some(ident) = meta.path.get_ident() {
24+
if ident == "C" {
25+
c = true;
26+
} else if ident_in_list(ident, ["u8", "u16", "u32", "u64"]) {
27+
primitive = Some((meta.path.clone(), false));
28+
} else if ident_in_list(ident, ["i8", "i16", "i32", "i64"]) {
29+
primitive = Some((meta.path.clone(), true));
30+
} else if ident_in_list(ident, ["usize", "isize", "u128", "i128"]) {
31+
return Err(ident
32+
.span()
33+
.error(format!("`repr({ident})` types are not supported"))
34+
.into());
35+
}
36+
}
37+
Ok(())
38+
})?;
39+
}
40+
}
41+
42+
Ok(Self { c, primitive })
43+
}
44+
}
45+
46+
fn ident_in_list<const N: usize>(ident: &Ident, list: [&'static str; N]) -> bool {
47+
list.iter().any(|id| ident == id)
48+
}
49+
950
#[proc_macro_derive(AbstractType)]
1051
pub fn abstract_type_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1152
let input = parse_macro_input!(input as DeriveInput);
@@ -16,21 +57,7 @@ pub fn abstract_type_derive(input: proc_macro::TokenStream) -> proc_macro::Token
1657
}
1758

1859
fn impl_abstract_type(ast: DeriveInput) -> Result<TokenStream> {
19-
let mut repr_c = false;
20-
for attr in ast.attrs {
21-
if attr.path().is_ident("repr") {
22-
let _ = attr.parse_nested_meta(|meta| {
23-
if meta.path.is_ident("c") {
24-
repr_c = true;
25-
}
26-
Ok(())
27-
});
28-
}
29-
}
30-
31-
if !repr_c {
32-
return Err(ast.ident.span().error("type must be `repr(C)`"));
33-
}
60+
let repr = Repr::from_attrs(ast.attrs)?;
3461

3562
if !ast.generics.params.is_empty() {
3663
return Err(ast.generics.span().error("type must not be generic"));
@@ -39,7 +66,7 @@ fn impl_abstract_type(ast: DeriveInput) -> Result<TokenStream> {
3966
let ident = ast.ident;
4067
match ast.data {
4168
Data::Struct(s) => match s.fields {
42-
Fields::Named(fields) => Ok(impl_abstract_struct_type(ident, fields)),
69+
Fields::Named(fields) => impl_abstract_struct_type(ident, fields, repr),
4370
Fields::Unnamed(_) => Err(s
4471
.fields
4572
.span()
@@ -48,8 +75,8 @@ fn impl_abstract_type(ast: DeriveInput) -> Result<TokenStream> {
4875
.span()
4976
.error("unit structs are unsupported; provide at least one named field")),
5077
},
51-
Data::Enum(_) => todo!(),
52-
Data::Union(u) => Ok(impl_abstract_union_type(ident, u.fields)),
78+
Data::Enum(e) => impl_abstract_enum_type(ident, e.variants, repr),
79+
Data::Union(u) => impl_abstract_union_type(ident, u.fields, repr),
5380
}
5481
}
5582

@@ -72,9 +99,13 @@ fn field_arguments(name: &Ident, fields: FieldsNamed) -> Vec<TokenStream> {
7299
.collect()
73100
}
74101

75-
fn impl_abstract_struct_type(name: Ident, fields: FieldsNamed) -> TokenStream {
102+
fn impl_abstract_struct_type(name: Ident, fields: FieldsNamed, repr: Repr) -> Result<TokenStream> {
103+
if !repr.c {
104+
return Err(name.span().error("struct must be `repr(C)`"));
105+
}
106+
76107
let args = field_arguments(&name, fields);
77-
quote! {
108+
Ok(quote! {
78109
impl ::binaryninja::types::AbstractType for #name {
79110
fn resolve_type() -> ::binaryninja::rc::Ref<::binaryninja::types::Type> {
80111
::binaryninja::types::Type::structure(
@@ -84,12 +115,16 @@ fn impl_abstract_struct_type(name: Ident, fields: FieldsNamed) -> TokenStream {
84115
)
85116
}
86117
}
87-
}
118+
})
88119
}
89120

90-
fn impl_abstract_union_type(name: Ident, fields: FieldsNamed) -> TokenStream {
121+
fn impl_abstract_union_type(name: Ident, fields: FieldsNamed, repr: Repr) -> Result<TokenStream> {
122+
if !repr.c {
123+
return Err(name.span().error("union must be `repr(C)`"));
124+
}
125+
91126
let args = field_arguments(&name, fields);
92-
quote! {
127+
Ok(quote! {
93128
impl ::binaryninja::types::AbstractType for #name {
94129
fn resolve_type() -> ::binaryninja::rc::Ref<::binaryninja::types::Type> {
95130
::binaryninja::types::Type::structure(
@@ -102,5 +137,49 @@ fn impl_abstract_union_type(name: Ident, fields: FieldsNamed) -> TokenStream {
102137
)
103138
}
104139
}
140+
})
141+
}
142+
143+
fn impl_abstract_enum_type(
144+
name: Ident,
145+
variants: impl IntoIterator<Item = Variant>,
146+
repr: Repr,
147+
) -> Result<TokenStream> {
148+
if repr.c {
149+
return Err(name.span().error("`repr(C)` enums are not supported"));
105150
}
151+
152+
let Some((primitive, signed)) = repr.primitive else {
153+
return Err(name
154+
.span()
155+
.error("must provide a primitive `repr` type, e.g. `u32`"));
156+
};
157+
let variants = variants
158+
.into_iter()
159+
.map(|variant| {
160+
if !variant.fields.is_empty() {
161+
return Err(variant.span().error("variant must not have any fields"));
162+
}
163+
let Some((_, discriminant)) = variant.discriminant else {
164+
return Err(variant
165+
.span()
166+
.error("variant must have an explicit discriminant"));
167+
};
168+
let ident = variant.ident;
169+
Ok(quote! { stringify!(#ident), #discriminant as u64 })
170+
})
171+
.collect::<Result<Vec<_>>>()?;
172+
Ok(quote! {
173+
impl ::binaryninja::types::AbstractType for #name {
174+
fn resolve_type() -> ::binaryninja::rc::Ref<::binaryninja::types::Type> {
175+
::binaryninja::types::Type::enumeration(
176+
&::binaryninja::types::Enumeration::builder()
177+
#(.insert(#variants))*
178+
.finalize(),
179+
::std::mem::size_of::<#primitive>(),
180+
#signed
181+
)
182+
}
183+
}
184+
})
106185
}

0 commit comments

Comments
 (0)