x86test_macro/
lib.rs

1//! This implements the x86test macro to run and also customize the execution
2//! of KVM based unit-tests.
3#![feature(proc_macro_diagnostic)]
4extern crate proc_macro;
5
6use proc_macro::TokenStream;
7use proc_macro2::Span;
8
9use syn::punctuated::Punctuated;
10use syn::spanned::Spanned;
11use syn::token::Comma;
12use syn::{AttributeArgs, Ident, ItemFn, Lit, Meta, MetaList, NestedMeta};
13
14use quote::quote;
15
16/// Parse two integer literals from args (e.g., like (1, 2)).
17fn parse_two_ints(args: Punctuated<NestedMeta, Comma>) -> (u64, u64) {
18    if args.len() != 2 {
19        args.span()
20            .unstable()
21            .error("needs two numbers as parameters")
22            .emit();
23    }
24
25    let a = if let NestedMeta::Literal(Lit::Int(first)) = &args[0] {
26        first.value()
27    } else {
28        args[0]
29            .span()
30            .unstable()
31            .error("first parameter not an int literal")
32            .emit();
33        0
34    };
35
36    let b = if let NestedMeta::Literal(Lit::Int(second)) = &args[1] {
37        second.value()
38    } else {
39        args[1]
40            .span()
41            .unstable()
42            .error("second parameter not an int literal")
43            .emit();
44        0
45    };
46
47    (a, b)
48}
49
50/// Checks is ItemFn is annotated with #[should_panic]
51fn should_panic(fun: &ItemFn) -> bool {
52    fun.attrs
53        .iter()
54        .find(|&attr| {
55            attr.path
56                .segments
57                .iter()
58                .find(|&path_segment| path_segment.ident == "should_panic")
59                .is_some()
60        })
61        .is_some()
62}
63
64/// The `x86test` macro adds and initializes a `X86TestFn` struct for
65/// every test function. That `X86TestFn` in turn is annotated with
66/// `#[test_case]` therefore all these structs are aggregated with
67/// by the custom test framework runner which is declared in `runner.rs`.
68///
69/// # Example
70/// As an example, if we add x86test to a function, we do the following:
71///
72/// ```no-run
73/// #[x86test(ram(0x10000, 0x11000), ioport(0x1, 0xfe), should_panic)]
74/// fn use_the_port() {
75///     unsafe {
76///         kassert!(x86::io::inw(0x1) == 0xff, "`inw` instruction didn't read correct value");
77///     }
78/// }
79/// ```
80///
81/// Will expand to:
82///
83/// ```no-run
84/// fn use_the_port() {
85///     unsafe {
86///         kassert!(x86::io::inw(0x1) == 0xff, "`inw` instruction didn't read correct value");
87///     }
88/// }
89///
90/// #[allow(non_upper_case_globals)]
91/// #[test_case]
92/// static use_the_port_genkvmtest: X86TestFn = X86TestFn {
93///     name: "use_the_port",
94///     ignore: false,
95///     identity_map: true,
96///     physical_memory: (0x10000, 0x11000),
97///     ioport_enable: (0x1, 0xfe),
98///     should_panic: true,
99///     testfn: x86test::StaticTestFn(|| {
100///         use_the_port()
101///         // Tell our "hypervisor" that we finished the test
102///         unsafe { x86::io::outw(0xf4, 0x00); }
103///     })
104/// };
105/// ```
106#[proc_macro_attribute]
107pub fn x86test(args: TokenStream, input: TokenStream) -> TokenStream {
108    let args: Vec<NestedMeta> = syn::parse_macro_input!(args as AttributeArgs);
109    let input_fn = syn::parse_macro_input!(input as ItemFn);
110
111    let mut physical_memory: (u64, u64) = (0, 0);
112    let mut ioport_enable: (u64, u64) = (0, 0);
113    let should_panic = should_panic(&input_fn);
114    let mut should_halt = false;
115
116    // Parse the arguments of x86test:
117    // #[x86test(ram(0xdead, 12), ioport(0x1, 0xfe))]
118    // will push (0xdead, 12) to physical_memory and (0x1, 0xfe) to ioport_enable:
119    for arg in args {
120        //println!("arg {:#?}", arg);
121        if let NestedMeta::Meta(Meta::List(MetaList {
122            ident,
123            paren_token: _,
124            nested,
125        })) = arg
126        {
127            match ident.to_string().as_str() {
128                "ram" => {
129                    physical_memory = parse_two_ints(nested);
130                }
131                "ioport" => {
132                    ioport_enable = parse_two_ints(nested);
133                }
134                x => unreachable!("unsupported attribute: {}", x),
135            }
136        } else if let NestedMeta::Meta(Meta::Word(ident)) = arg {
137            match ident.to_string().as_str() {
138                "should_halt" => should_halt = true,
139                x => unreachable!("unsupported attribute: {}", x),
140            }
141        }
142    }
143
144    let physical_memory_tuple = {
145        let (a, b) = physical_memory;
146        quote! { (#a, #b) }
147    };
148
149    let ioport_enable_tuple = {
150        let (a, b) = ioport_enable;
151        quote! { (#a as u16, #b as u32) }
152    };
153
154    let struct_name = format!("{}_genkvmtest", input_fn.ident);
155    let struct_ident = Ident::new(struct_name.as_str(), Span::call_site());
156    let test_name = format!("{}", input_fn.ident);
157    let fn_ident = input_fn.ident.clone();
158
159    let ast = quote! {
160            #[allow(non_upper_case_globals, unused_attributes)]
161            #[test_case]
162            static #struct_ident: X86TestFn = X86TestFn {
163                name: #test_name,
164                ignore: false,
165                identity_map: true,
166                physical_memory: #physical_memory_tuple,
167                ioport_enable: #ioport_enable_tuple,
168                should_panic: #should_panic,
169                should_halt: #should_halt,
170                testfn: x86test::StaticTestFn(|| {
171                    #fn_ident();
172                    // Tell our "hypervisor" that we finished the test
173                    unsafe { x86test::outw(0xf4, 0x00); }
174                })
175            };
176
177            // Suppress unused attribute #[should_panic] warning (XXX: there is probably a better way to do this)
178            #[allow(unused_attributes)]
179            #input_fn
180    };
181
182    ast.into()
183}