Skip to content

Commit cfb8a64

Browse files
committed
Switch to using a #[binja(...)] attribute instead of naked decorators
The attribute supports three properties: - Specifying pointer width is done for the whole struct/union now instead of each field using a `#[binja(pointer_width = <int>)]` property. - Specifying named fields is done either with `#[binja(name = "...")]`, or `#[binja(named)]` which will use the name of the Rust type as a default value.
1 parent 9d0226f commit cfb8a64

File tree

1 file changed

+153
-75
lines changed
  • rust/binaryninja-derive/src

1 file changed

+153
-75
lines changed

rust/binaryninja-derive/src/lib.rs

Lines changed: 153 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,155 @@
1-
use proc_macro2::TokenStream;
1+
use proc_macro2::{Span, TokenStream};
22
use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt};
33
use quote::{format_ident, quote};
4+
use std::cell::OnceCell;
45
use syn::spanned::Spanned;
56
use syn::{
67
parenthesized, parse_macro_input, token, Attribute, Data, DeriveInput, Expr, Field, Fields,
7-
FieldsNamed, Ident, Lit, LitInt, Path, Type, Variant,
8+
FieldsNamed, Ident, Lit, LitInt, Meta, Path, Type, Variant,
89
};
910

1011
type Result<T> = std::result::Result<T, Diagnostic>;
1112

13+
enum FieldKind {
14+
Ptr(Type, usize),
15+
Ty(Type),
16+
}
17+
18+
impl FieldKind {
19+
fn ty(&self) -> &Type {
20+
match self {
21+
FieldKind::Ptr(ty, _) | FieldKind::Ty(ty) => &ty,
22+
}
23+
}
24+
}
25+
1226
struct AbstractField {
13-
ty: Type,
14-
width: Option<Type>,
27+
kind: FieldKind,
1528
ident: Ident,
16-
named: bool,
29+
name: Option<String>,
1730
}
1831

1932
impl AbstractField {
20-
fn from_field(field: Field) -> Result<Self> {
33+
fn from_field(field: Field, parent_name: &Ident, pointer_width: Option<usize>) -> Result<Self> {
2134
let Some(ident) = field.ident else {
2235
return Err(field.span().error("field must be named"));
2336
};
24-
let named = field.attrs.iter().any(|attr| attr.path().is_ident("named"));
25-
let width = field
26-
.attrs
27-
.iter()
28-
.find(|attr| attr.path().is_ident("width"));
29-
if let Type::Ptr(ty) = field.ty {
30-
if let Some(attr) = width {
31-
if let Expr::Lit(expr) = &attr.meta.require_name_value()?.value {
32-
if let Lit::Str(lit_str) = &expr.lit {
33-
return Ok(Self {
34-
ty: *ty.elem,
35-
width: Some(lit_str.parse()?),
36-
ident,
37-
named,
38-
});
39-
}
40-
}
37+
let kind = match field.ty {
38+
Type::Ptr(ty) => {
39+
let Some(width) = pointer_width else {
40+
return Err(parent_name.span().error(
41+
// broken up to make rustfmt happy
42+
"types containing pointer fields must be \
43+
decorated with `#[binja(pointer_width = <int>)]`",
44+
));
45+
};
46+
FieldKind::Ptr(*ty.elem, width)
4147
}
42-
Err(ident.span()
43-
.error("pointer field must have explicit `#[width = \"<type>\"]` attribute, for example: `u64`"))
44-
} else {
45-
match width {
46-
Some(attr) => Err(attr
47-
.span()
48-
.error("`#[width]` attribute can only be applied to pointer fields")),
49-
None => Ok(Self {
50-
ty: field.ty,
51-
width: None,
52-
ident,
53-
named,
54-
}),
55-
}
56-
}
48+
_ => FieldKind::Ty(field.ty),
49+
};
50+
let name = find_binja_attr(&field.attrs)?
51+
.map(|attr| match attr.kind {
52+
BinjaAttrKind::PointerWidth(_) => Err(attr.span.error(
53+
// broken up to make rustfmt happy
54+
"invalid attribute, expected either \
55+
`#[binja(named)]` or `#[binja(name = \"...\")]`",
56+
)),
57+
BinjaAttrKind::Named(Some(name)) => Ok(name),
58+
BinjaAttrKind::Named(None) => {
59+
let ty = kind.ty();
60+
Ok(quote!(#ty).to_string())
61+
}
62+
})
63+
.transpose()?;
64+
Ok(Self { kind, ident, name })
5765
}
5866

5967
fn resolved_ty(&self) -> TokenStream {
60-
let ty = &self.ty;
68+
let ty = self.kind.ty();
6169
let mut resolved = quote! { <#ty as ::binaryninja::types::AbstractType>::resolve_type() };
62-
if self.named {
70+
if let Some(name) = &self.name {
6371
resolved = quote! {
64-
::binaryninja::types::Type::named_type_from_type(
65-
stringify!(#ty),
66-
&#resolved
67-
)
68-
};
72+
::binaryninja::types::Type::named_type_from_type(#name, &#resolved)
73+
}
6974
}
70-
if let Some(width) = &self.width {
75+
if let FieldKind::Ptr(_, width) = self.kind {
7176
resolved = quote! {
72-
::binaryninja::types::Type::pointer_of_width(
73-
&#resolved,
74-
::std::mem::size_of::<#width>(),
75-
false,
76-
false,
77-
None
78-
)
77+
::binaryninja::types::Type::pointer_of_width(&#resolved, #width, false, false, None)
7978
}
8079
}
8180
resolved
8281
}
8382
}
8483

84+
#[derive(Debug)]
85+
struct BinjaAttr {
86+
kind: BinjaAttrKind,
87+
span: Span,
88+
}
89+
90+
#[derive(Debug)]
91+
enum BinjaAttrKind {
92+
PointerWidth(usize),
93+
Named(Option<String>),
94+
}
95+
96+
fn find_binja_attr(attrs: &[Attribute]) -> Result<Option<BinjaAttr>> {
97+
let binja_attr = OnceCell::new();
98+
99+
let set_attr = |attr: BinjaAttr| {
100+
let span = attr.span;
101+
binja_attr
102+
.set(attr)
103+
.map_err(|_| span.error("conflicting `#[binja(...)]` attributes"))
104+
};
105+
106+
for attr in attrs {
107+
let Some(ident) = attr.path().get_ident() else {
108+
continue;
109+
};
110+
if ident == "binja" {
111+
let meta = attr.parse_args::<Meta>()?;
112+
let meta_ident = meta.path().require_ident()?;
113+
if meta_ident == "pointer_width" {
114+
let value = &meta.require_name_value()?.value;
115+
if let Expr::Lit(expr) = &value {
116+
if let Lit::Int(val) = &expr.lit {
117+
set_attr(BinjaAttr {
118+
kind: BinjaAttrKind::PointerWidth(val.base10_parse()?),
119+
span: attr.span(),
120+
})?;
121+
continue;
122+
}
123+
}
124+
return Err(value.span().error("expected integer literal"));
125+
} else if meta_ident == "name" {
126+
let value = &meta.require_name_value()?.value;
127+
if let Expr::Lit(expr) = &value {
128+
if let Lit::Str(lit) = &expr.lit {
129+
set_attr(BinjaAttr {
130+
kind: BinjaAttrKind::Named(Some(lit.value())),
131+
span: attr.span(),
132+
})?;
133+
continue;
134+
}
135+
}
136+
return Err(value.span().error(r#"expected string literal"#));
137+
} else if meta_ident == "named" {
138+
meta.require_path_only()?;
139+
set_attr(BinjaAttr {
140+
kind: BinjaAttrKind::Named(None),
141+
span: attr.span(),
142+
})?;
143+
} else {
144+
return Err(meta
145+
.span()
146+
.error(format!("unrecognized property `{meta_ident}`")));
147+
}
148+
}
149+
}
150+
Ok(binja_attr.into_inner())
151+
}
152+
85153
struct Repr {
86154
c: bool,
87155
packed: Option<Option<LitInt>>,
@@ -90,7 +158,7 @@ struct Repr {
90158
}
91159

92160
impl Repr {
93-
fn from_attrs(attrs: Vec<Attribute>) -> Result<Self> {
161+
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
94162
let mut c = false;
95163
let mut packed = None;
96164
let mut align = None;
@@ -99,15 +167,7 @@ impl Repr {
99167
let Some(ident) = attr.path().get_ident() else {
100168
continue;
101169
};
102-
if ident == "named" {
103-
return Err(attr
104-
.span()
105-
.error("`#[named]` attribute can only be applied to fields"));
106-
} else if ident == "width" {
107-
return Err(attr
108-
.span()
109-
.error("`#[width]` attribute can only be applied to pointer fields"));
110-
} else if ident == "repr" {
170+
if ident == "repr" {
111171
attr.parse_nested_meta(|meta| {
112172
if let Some(ident) = meta.path.get_ident() {
113173
if ident == "C" {
@@ -153,7 +213,7 @@ fn ident_in_list<const N: usize>(ident: &Ident, list: [&'static str; N]) -> bool
153213
list.iter().any(|id| ident == id)
154214
}
155215

156-
#[proc_macro_derive(AbstractType, attributes(named, width))]
216+
#[proc_macro_derive(AbstractType, attributes(binja))]
157217
pub fn abstract_type_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
158218
let input = parse_macro_input!(input as DeriveInput);
159219
match impl_abstract_type(input) {
@@ -163,7 +223,18 @@ pub fn abstract_type_derive(input: proc_macro::TokenStream) -> proc_macro::Token
163223
}
164224

165225
fn impl_abstract_type(ast: DeriveInput) -> Result<TokenStream> {
166-
let repr = Repr::from_attrs(ast.attrs)?;
226+
let repr = Repr::from_attrs(&ast.attrs)?;
227+
let width = find_binja_attr(&ast.attrs)?
228+
.map(|attr| match attr.kind {
229+
BinjaAttrKind::PointerWidth(width) => Ok(width),
230+
BinjaAttrKind::Named(Some(_)) => Err(attr
231+
.span
232+
.error(r#"`#[binja(name = "...")] is only supported on fields"#)),
233+
BinjaAttrKind::Named(None) => Err(attr
234+
.span
235+
.error("`#[binja(named)]` is only supported on fields")),
236+
})
237+
.transpose()?;
167238

168239
if !ast.generics.params.is_empty() {
169240
return Err(ast.generics.span().error("type must not be generic"));
@@ -173,7 +244,7 @@ fn impl_abstract_type(ast: DeriveInput) -> Result<TokenStream> {
173244
match ast.data {
174245
Data::Struct(s) => match s.fields {
175246
Fields::Named(fields) => {
176-
impl_abstract_structure_type(ident, fields, repr, StructureKind::Struct)
247+
impl_abstract_structure_type(ident, fields, repr, width, StructureKind::Struct)
177248
}
178249
Fields::Unnamed(_) => Err(s
179250
.fields
@@ -184,7 +255,9 @@ fn impl_abstract_type(ast: DeriveInput) -> Result<TokenStream> {
184255
.error("unit structs are unsupported; provide at least one named field")),
185256
},
186257
Data::Enum(e) => impl_abstract_enum_type(ident, e.variants, repr),
187-
Data::Union(u) => impl_abstract_structure_type(ident, u.fields, repr, StructureKind::Union),
258+
Data::Union(u) => {
259+
impl_abstract_structure_type(ident, u.fields, repr, width, StructureKind::Union)
260+
}
188261
}
189262
}
190263

@@ -197,6 +270,7 @@ fn impl_abstract_structure_type(
197270
name: Ident,
198271
fields: FieldsNamed,
199272
repr: Repr,
273+
pointer_width: Option<usize>,
200274
kind: StructureKind,
201275
) -> Result<TokenStream> {
202276
if !repr.c {
@@ -210,21 +284,25 @@ fn impl_abstract_structure_type(
210284
let abstract_fields = fields
211285
.named
212286
.into_iter()
213-
.map(AbstractField::from_field)
287+
.map(|field| AbstractField::from_field(field, &name, pointer_width))
214288
.collect::<Result<Vec<_>>>()?;
215289
let layout_name = format_ident!("__{name}_layout");
216290
let field_wrapper = format_ident!("__{name}_field_wrapper");
217291
let layout_fields = abstract_fields
218292
.iter()
219293
.map(|field| {
220294
let ident = &field.ident;
221-
let layout_ty = field.width.as_ref().unwrap_or(&field.ty);
222-
quote! {
223-
#ident: #field_wrapper<
224-
[u8; <#layout_ty as ::binaryninja::types::AbstractType>::SIZE],
225-
{ <#layout_ty as ::binaryninja::types::AbstractType>::ALIGN },
226-
>
227-
}
295+
let (size, align) = match &field.kind {
296+
FieldKind::Ptr(_, width) => {
297+
let align = width.next_power_of_two();
298+
(quote! { #width }, quote! { #align })
299+
}
300+
FieldKind::Ty(ty) => (
301+
quote! { <#ty as ::binaryninja::types::AbstractType>::SIZE },
302+
quote! { { <#ty as ::binaryninja::types::AbstractType>::ALIGN } },
303+
),
304+
};
305+
quote! { #ident: #field_wrapper<[u8; #size], #align> }
228306
})
229307
.collect::<Vec<_>>();
230308
let args = abstract_fields

0 commit comments

Comments
 (0)