#![feature(proc_macro_diagnostic)]
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::fold::Fold;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
parse_macro_input, Attribute, Expr, ExprCall, ExprMethodCall, ExprPath, FnArg, Ident, ItemFn,
Meta, NestedMeta, Pat, Path, PathArguments, PathSegment, Signature, Token, Type,
};
struct Args {
decl: FnArg,
var: Ident,
ty: Type,
external_calls: Vec<Ident>,
external_methods: Vec<Ident>,
}
impl Args {
fn decl(&self) -> FnArg {
self.decl.clone()
}
fn var(&self) -> Ident {
self.var.clone()
}
fn var_as_expr(&self) -> Expr {
let var = self.var();
let segment = PathSegment {
ident: var,
arguments: PathArguments::None,
};
let path = Path::from(segment);
let expr_path = ExprPath {
attrs: Vec::new(),
qself: None,
path,
};
let expr = Expr::from(expr_path);
expr
}
fn ty(&self) -> Type {
self.ty.clone()
}
}
fn pat_to_ident(pat: Pat) -> Result<Ident> {
if let Pat::Ident(pat_ident) = pat {
return Ok(pat_ident.ident);
}
Err(syn::Error::new(
pat.span(),
"Argument needs to be bound to an identifier",
))
}
fn path_to_last_ident(path: Path) -> Result<Ident> {
let err = Err(syn::Error::new(
path.span(),
"Expr needs to be an ident to call expr_to_ident",
));
let final_segment = path.segments.last();
return final_segment.map_or(err, |i| Ok(i.ident.clone()));
}
fn expr_to_ident(expr: Expr) -> Result<Ident> {
if let Expr::Path(expr_path) = expr {
return path_to_last_ident(expr_path.path);
}
Err(syn::Error::new(
expr.span(),
"Expr needs to be an ident to call expr_to_ident",
))
}
impl Parse for Args {
fn parse(input: ParseStream) -> Result<Self> {
let decl = syn::FnArg::parse(input)?;
if let FnArg::Typed(ref pat_ty) = decl {
let var = pat_to_ident(*pat_ty.pat.clone())?;
let ty = *pat_ty.ty.clone();
return Ok(Args {
decl,
var,
ty,
external_calls: vec![
Ident::new("Ok", Span::call_site()),
Ident::new("Err", Span::call_site()),
Ident::new("new", Span::call_site()),
Ident::new("start_timer", Span::call_site()),
Ident::new("stop_timer", Span::call_site()),
Ident::new("push_syscall_result", Span::call_site()),
Ident::new("push_hostcall_result", Span::call_site()),
],
external_methods: vec![
Ident::new("into", Span::call_site()),
Ident::new("len", Span::call_site()),
Ident::new("ok_or", Span::call_site()),
Ident::new("fits_in_lin_mem", Span::call_site()),
Ident::new("fits_in_lin_mem_usize", Span::call_site()),
Ident::new("in_lin_mem", Span::call_site()),
Ident::new("in_lin_mem_usize", Span::call_site()),
],
});
}
Err(syn::Error::new(
decl.span(),
"You can't use self as an extra arg.",
))
}
}
impl Fold for Args {
fn fold_signature(&mut self, sig: Signature) -> Signature {
let arg_decl = self.decl(); let mut new_sig = sig;
new_sig.inputs.push(arg_decl);
new_sig
}
fn fold_expr_call(&mut self, call: ExprCall) -> ExprCall {
for external_call in self.external_calls.iter() {
let func_name: Result<Ident> = expr_to_ident(*call.func.clone());
match func_name {
Ok(name) => {
if name.to_string() == external_call.to_string() {
return call;
}
}
Err(_) => {}
}
}
let arg = self.var_as_expr();
let mut new_call = call;
new_call.args.push(arg);
new_call
}
fn fold_expr_method_call(&mut self, method_call: ExprMethodCall) -> ExprMethodCall {
let mut new_method_call = method_call;
*new_method_call.receiver = self.fold_expr(*new_method_call.receiver);
for external_method in self.external_methods.iter() {
if new_method_call.method.to_string() == external_method.to_string() {
return new_method_call;
}
}
let arg = self.var_as_expr();
new_method_call.args.push(arg);
new_method_call
}
fn fold_attribute(&mut self, attr: Attribute) -> Attribute {
if attr.path.is_ident("external_call") {
let func_name: Result<Ident> = attr.parse_args();
match func_name {
Ok(name) => self.external_calls.push(name),
Err(_) => return attr,
};
}
if attr.path.is_ident("external_method") {
let method_name: Result<Ident> = attr.parse_args();
match method_name {
Ok(name) => self.external_methods.push(name),
Err(_) => return attr,
};
}
if attr.path.is_ident("external_calls") {
let func_names: Result<Meta> = attr.parse_meta();
if let Ok(Meta::List(l)) = func_names {
for meta in l.nested.iter() {
if let NestedMeta::Meta(m2) = meta {
let name = path_to_last_ident(m2.path().clone()).unwrap();
self.external_calls.push(name.clone());
}
}
}
}
if attr.path.is_ident("external_methods") {
let func_names: Result<Meta> = attr.parse_meta();
if let Ok(Meta::List(l)) = func_names {
for meta in l.nested.iter() {
if let NestedMeta::Meta(m2) = meta {
let name = path_to_last_ident(m2.path().clone()).unwrap();
self.external_methods.push(name.clone());
}
}
}
}
attr
}
}
#[cfg(feature = "enable")]
#[proc_macro_attribute]
pub fn with_ghost_var(args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
let mut args = parse_macro_input!(args as Args);
let output = args.fold_item_fn(input);
TokenStream::from(quote!(#output))
}
#[cfg(not(feature = "enable"))]
#[proc_macro_attribute]
pub fn with_ghost_var(args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn external_call(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn external_method(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn external_calls(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn external_methods(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}