yara_x_macros/lib.rs
1use darling::ast::NestedMeta;
2use proc_macro::TokenStream;
3use syn::{DeriveInput, Error, ItemFn, parse_macro_input};
4
5mod error;
6mod module_export;
7mod wasm_export;
8
9/// The `ErrorStruct` derive macro generates boilerplate code for structs that
10/// define YARA errors and warnings.
11///
12/// Let's see an example:
13///
14/// ```text
15/// #[derive(ErrorStruct)]
16/// #[associated_enum(CompileError)]
17/// #[error(code = "E021", title = "duplicate tag `{tag}`")]
18/// #[label("duplicate tag", loc)]
19/// pub struct DuplicateTag {
20/// report: Report,
21/// tag: String,
22/// loc: SourceRef,
23/// }
24///
25/// #[derive(ErrorEnum)]
26/// pub enum CompileError {
27/// DuplicateTag(DuplicateTag),
28/// ... more variants
29/// }
30/// ```
31///
32/// Now let's dissect the example line by line:
33///
34/// ```text
35/// #[derive(ErrorStruct)]
36/// ```
37///
38/// 1. The first line is the derive attribute itself.
39///
40/// ```text
41/// #[associated_enum(CompileError)]
42/// ```
43///
44/// 2. The `associated_enum` attribute indicates the name of an enum type that
45/// contains a variant for each error/warning type, including the one being
46/// defined here. In this case the struct is `DuplicateTag`, so the enum must
47/// contain the variant `DuplicateTag(DuplicateTag)`. This attribute is
48/// required.
49///
50/// ```text
51/// #[error(code = "E021", title = "duplicate tag `{tag}`")]
52/// ```
53///
54/// 3. The `error` attribute indicates that this is an error with code "E021"
55/// and title "duplicate tag `{tag}`". Notice the use of format arguments
56/// in the title for specifying the tag. For each format argument there must
57/// be a field in the structure with that name. The value of that field is
58/// used when rendering the title. When defining a warning you use
59/// `#[warning(...)]` instead of `#[error(...)]`, but one of the two
60/// attributes must be present.
61///
62/// ```text
63/// #[label("duplicate tag", loc, Level::Error)]
64/// ```
65///
66/// 4. Then comes one or more `label` attributes, where each label is composed
67/// of a text, the name of some field of type `SourceRef` in the structure,
68/// and optionally, the label's error level. Valid error levels are:
69///
70/// - `Level::ERROR`
71/// - `Level::WARNING`
72/// - `Level::INFO`
73/// - `Level::NOTE`
74/// - `Level::HELP`
75///
76/// If the level is omitted it will be either `Level::ERROR` or
77/// `Level::WARNING`, depending on whether we are defining an error with
78/// `#[error(...)]`, or a warning with `#[warning(...)]`.
79///
80/// ```text
81/// pub struct DuplicateTag {
82/// report: Report,
83/// tag: String,
84/// loc: SourceRef,
85/// }
86/// ```
87///
88/// 4. Finally, we have the struct. The first field in the structure must be
89/// `report: Report`. The rest of the fields vary from error to error
90///
91///
92/// This is how the error looks when printed:
93///
94/// ```text
95/// error[E021]: duplicate tag `tag1`
96/// --> test.yar:1:18
97/// |
98/// 1 | rule test : tag1 tag1 { condition: true }
99/// | ^^^^ duplicate tag
100/// |
101/// ```
102///
103#[proc_macro_derive(
104 ErrorStruct,
105 attributes(error, warning, label, footer, associated_enum)
106)]
107pub fn error_struct_macro_derive(input: TokenStream) -> TokenStream {
108 let input = parse_macro_input!(input as DeriveInput);
109 error::impl_error_struct_macro(input)
110 .unwrap_or_else(Error::into_compile_error)
111 .into()
112}
113
114/// The `ErrorEnum` macro is used with enums that define YARA errors and
115/// warnings.
116///
117/// This macro is used in combination with `ErrorStruct`, see the documentation
118/// of `ErrorStruct` for details.
119#[proc_macro_derive(ErrorEnum)]
120pub fn error_enum_macro_derive(input: TokenStream) -> TokenStream {
121 let input = parse_macro_input!(input as DeriveInput);
122 error::impl_error_enum_macro(input)
123 .unwrap_or_else(Error::into_compile_error)
124 .into()
125}
126
127/// The `wasm_export` macro is used for declaring a Rust function that will be
128/// called from WASM.
129///
130/// The function's first argument must be of type `wasmtime::Caller`, which
131/// contains information about the context in which the function is called,
132/// including a reference to the `yara_x::scanner::ScanContext` corresponding
133/// to the current scan.
134///
135/// The rest of the arguments, if any, must be of any of the following types:
136///
137/// - `i32`
138/// - `i64`
139/// - `f32`
140/// - `f64`
141/// - `bool`
142/// - `RuntimeString`
143/// - `RuleId`
144/// - `PatternId`
145/// - `Rc<Struct>`
146/// - `Rc<Map>`
147/// - `Rc<Array>`
148///
149/// # Example
150///
151/// ```text
152/// #[wasm_export]
153/// fn add(caller: Caller<'_, ScanContext>, a: i64, b: i64) -> i64 {
154/// a + b
155/// }
156/// ```
157///
158/// Optionally, the `wasm_export` macro can receive the name used for exporting
159/// the function. If not specified, the function will be exported with the name
160/// it has in the Rust code, but you can specify a different name. This allow
161/// having multiple functions with the same name, as long as their signatures
162/// are different.
163///
164/// # Example
165///
166/// ```text
167/// use wasmtime::Caller;
168///
169/// #[wasm_export(name = "add")]
170/// fn add_i64(caller: Caller<'_, ScanContext>, a: i64, b: i64) -> i64 {
171/// a + b
172/// }
173///
174/// #[wasm_export(name = "add")]
175/// fn add_f64(caller: Caller<'_, ScanContext>, a: f64, b: f64) -> f64 {
176/// a + b
177/// }
178/// ```
179///
180/// The macro can also receive a `public` argument, which specifies that the
181/// function will be visible from YARA rules.
182///
183/// # Example
184///
185/// ```text
186/// #[wasm_export(public = true)]
187/// fn uint8(caller: Caller<'_, ScanContext>, offset: i64) -> i64 {
188/// ...
189/// }
190/// ```
191#[proc_macro_attribute]
192pub fn wasm_export(args: TokenStream, input: TokenStream) -> TokenStream {
193 let args = match NestedMeta::parse_meta_list(args.into()) {
194 Ok(args) => args,
195 Err(e) => return darling::Error::from(e).write_errors().into(),
196 };
197 wasm_export::impl_wasm_export_macro(
198 args,
199 parse_macro_input!(input as ItemFn),
200 )
201 .unwrap_or_else(Error::into_compile_error)
202 .into()
203}
204
205/// Indicates that a function is exported from a YARA module and therefore
206/// it's callable from YARA rules.
207///
208/// The function's first argument must be of a mutable or immutable reference
209/// to `ScanContext`. The rest of the arguments, if any, can be of any of
210/// the following types:
211///
212/// - `i32`
213/// - `i64`
214/// - `f32`
215/// - `f64`
216/// - `bool`
217/// - `RuntimeString`
218///
219/// Optionally, `module_export` can receive the path of the function within
220/// the module's structure, like in `#[module_export(foo)]` and
221/// `#[module_export(foo.bar)]`.
222///
223/// # Examples
224///
225/// Using `#[module_export]` without arguments. The function will be exported
226/// with name `add` at the module's top-level structure (i.e: if the module
227/// is named `my_module`, the function is invoked as `my_module.add`):
228///
229/// ```text
230/// #[module_export]
231/// fn add(ctx: &ScanContext, a: i64, b: i64) -> i64 {
232/// a + b
233/// }
234/// ```
235///
236/// Passing the function name to `#[module_export]` and using the same name
237/// with two functions that have different signatures. Both functions will
238/// be called as `my_module.add`, YARA chooses which one to call based on
239/// the type of the arguments:
240///
241/// ```text
242/// #[module_export(add)]
243/// fn add_i64(ctx: &ScanContext, a: i64, b: i64) -> i64 {
244/// a + b
245/// }
246///
247/// #[module_export(add)]
248/// fn add_f64(ctx: &ScanContext, a: f64, b: f64) -> f64 {
249/// a + b
250/// }
251/// ```
252///
253/// Passing a path to `#[module_export]`. The function will be called as
254/// `my_module.my_struct.add`. The module must have a field `my_struct`
255/// of struct type.
256///
257/// ```text
258/// #[module_export(my_struct.add)]
259/// fn add(ctx: &ScanContext, a: i64, b: i64) -> i64 {
260/// a + b
261/// }
262/// ```
263#[proc_macro_attribute]
264pub fn module_export(args: TokenStream, input: TokenStream) -> TokenStream {
265 let args = match NestedMeta::parse_meta_list(args.into()) {
266 Ok(args) => args,
267 Err(e) => return darling::Error::from(e).write_errors().into(),
268 };
269 module_export::impl_module_export_macro(
270 args,
271 parse_macro_input!(input as ItemFn),
272 )
273 .unwrap_or_else(Error::into_compile_error)
274 .into()
275}