1
- use proc_macro2:: TokenStream ;
1
+ use proc_macro2:: { Span , TokenStream } ;
2
2
use proc_macro2_diagnostics:: { Diagnostic , SpanDiagnosticExt } ;
3
3
use quote:: { format_ident, quote} ;
4
+ use std:: cell:: OnceCell ;
4
5
use syn:: spanned:: Spanned ;
5
6
use syn:: {
6
7
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 ,
8
9
} ;
9
10
10
11
type Result < T > = std:: result:: Result < T , Diagnostic > ;
11
12
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
+
12
26
struct AbstractField {
13
- ty : Type ,
14
- width : Option < Type > ,
27
+ kind : FieldKind ,
15
28
ident : Ident ,
16
- named : bool ,
29
+ name : Option < String > ,
17
30
}
18
31
19
32
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 > {
21
34
let Some ( ident) = field. ident else {
22
35
return Err ( field. span ( ) . error ( "field must be named" ) ) ;
23
36
} ;
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)
41
47
}
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 } )
57
65
}
58
66
59
67
fn resolved_ty ( & self ) -> TokenStream {
60
- let ty = & self . ty ;
68
+ let ty = self . kind . ty ( ) ;
61
69
let mut resolved = quote ! { <#ty as :: binaryninja:: types:: AbstractType >:: resolve_type( ) } ;
62
- if self . named {
70
+ if let Some ( name ) = & self . name {
63
71
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
+ }
69
74
}
70
- if let Some ( width) = & self . width {
75
+ if let FieldKind :: Ptr ( _ , width) = self . kind {
71
76
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 )
79
78
}
80
79
}
81
80
resolved
82
81
}
83
82
}
84
83
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
+
85
153
struct Repr {
86
154
c : bool ,
87
155
packed : Option < Option < LitInt > > ,
@@ -90,7 +158,7 @@ struct Repr {
90
158
}
91
159
92
160
impl Repr {
93
- fn from_attrs ( attrs : Vec < Attribute > ) -> Result < Self > {
161
+ fn from_attrs ( attrs : & [ Attribute ] ) -> Result < Self > {
94
162
let mut c = false ;
95
163
let mut packed = None ;
96
164
let mut align = None ;
@@ -99,15 +167,7 @@ impl Repr {
99
167
let Some ( ident) = attr. path ( ) . get_ident ( ) else {
100
168
continue ;
101
169
} ;
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" {
111
171
attr. parse_nested_meta ( |meta| {
112
172
if let Some ( ident) = meta. path . get_ident ( ) {
113
173
if ident == "C" {
@@ -153,7 +213,7 @@ fn ident_in_list<const N: usize>(ident: &Ident, list: [&'static str; N]) -> bool
153
213
list. iter ( ) . any ( |id| ident == id)
154
214
}
155
215
156
- #[ proc_macro_derive( AbstractType , attributes( named , width ) ) ]
216
+ #[ proc_macro_derive( AbstractType , attributes( binja ) ) ]
157
217
pub fn abstract_type_derive ( input : proc_macro:: TokenStream ) -> proc_macro:: TokenStream {
158
218
let input = parse_macro_input ! ( input as DeriveInput ) ;
159
219
match impl_abstract_type ( input) {
@@ -163,7 +223,18 @@ pub fn abstract_type_derive(input: proc_macro::TokenStream) -> proc_macro::Token
163
223
}
164
224
165
225
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 ( ) ?;
167
238
168
239
if !ast. generics . params . is_empty ( ) {
169
240
return Err ( ast. generics . span ( ) . error ( "type must not be generic" ) ) ;
@@ -173,7 +244,7 @@ fn impl_abstract_type(ast: DeriveInput) -> Result<TokenStream> {
173
244
match ast. data {
174
245
Data :: Struct ( s) => match s. fields {
175
246
Fields :: Named ( fields) => {
176
- impl_abstract_structure_type ( ident, fields, repr, StructureKind :: Struct )
247
+ impl_abstract_structure_type ( ident, fields, repr, width , StructureKind :: Struct )
177
248
}
178
249
Fields :: Unnamed ( _) => Err ( s
179
250
. fields
@@ -184,7 +255,9 @@ fn impl_abstract_type(ast: DeriveInput) -> Result<TokenStream> {
184
255
. error ( "unit structs are unsupported; provide at least one named field" ) ) ,
185
256
} ,
186
257
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
+ }
188
261
}
189
262
}
190
263
@@ -197,6 +270,7 @@ fn impl_abstract_structure_type(
197
270
name : Ident ,
198
271
fields : FieldsNamed ,
199
272
repr : Repr ,
273
+ pointer_width : Option < usize > ,
200
274
kind : StructureKind ,
201
275
) -> Result < TokenStream > {
202
276
if !repr. c {
@@ -210,21 +284,25 @@ fn impl_abstract_structure_type(
210
284
let abstract_fields = fields
211
285
. named
212
286
. into_iter ( )
213
- . map ( AbstractField :: from_field)
287
+ . map ( |field| AbstractField :: from_field ( field , & name , pointer_width ) )
214
288
. collect :: < Result < Vec < _ > > > ( ) ?;
215
289
let layout_name = format_ident ! ( "__{name}_layout" ) ;
216
290
let field_wrapper = format_ident ! ( "__{name}_field_wrapper" ) ;
217
291
let layout_fields = abstract_fields
218
292
. iter ( )
219
293
. map ( |field| {
220
294
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> }
228
306
} )
229
307
. collect :: < Vec < _ > > ( ) ;
230
308
let args = abstract_fields
0 commit comments