Skip to content

Commit 3681be9

Browse files
authored
Fix the verus_spec(with ...) when a function is const. (#1842)
1 parent 369c5a6 commit 3681be9

2 files changed

Lines changed: 80 additions & 21 deletions

File tree

source/builtin_macros/src/attr_rewrite.rs

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -359,40 +359,38 @@ pub(crate) fn rewrite_verus_spec_on_fun_or_loop(
359359
AnyFnOrLoop::Fn(mut fun) => {
360360
// Note: trait default methods appear in this case,
361361
// since they look syntactically like non-trait functions
362-
replace_block(erase, fun.block_mut().unwrap());
363362
let spec_attr =
364363
syn_verus::parse_macro_input!(outer_attr_tokens as syn_verus::SignatureSpecAttr);
365364

366365
fun.attrs.push(mk_verus_attr_syn(fun.span(), quote! { verus_macro }));
367366

367+
let mut new_stream = TokenStream::new();
368+
368369
// Create a copy of unverified function.
369370
// To avoid misuse of the unverified function,
370371
// we add `requires false` and thus prevent verified function to use it.
371372
// Allow unverified code to use the function without changing in/output.
372-
let mut new_stream = TokenStream::new();
373373
if let Some(with) = &spec_attr.spec.with {
374-
let unverified_fun = rewrite_unverified_func(&mut fun, with.with.span());
375-
unverified_fun.to_tokens(&mut new_stream);
374+
let extra_funs = rewrite_unverified_func(&mut fun, with.with.span());
375+
extra_funs.iter().for_each(|f| f.to_tokens(&mut new_stream));
376376
}
377+
378+
// Update function signature based on verus_spec.
379+
let spec_stmts = syntax::sig_specs_attr(erase, spec_attr, &mut fun.sig);
380+
381+
// Create const proxy function if it is a const function.
377382
if fun.sig.constness.is_some() {
378-
let mut const_fun = fun.clone();
379-
let span = fun.sig.constness.unwrap().span();
380-
// It seems that we do not need to erase anything.
381-
// But just do it to be safe and consistent with verus macro.
382-
replace_block(EraseGhost::Erase, const_fun.block_mut().unwrap());
383-
const_fun.attrs.push(mk_verifier_attr_syn(span, quote! { external }));
384-
const_fun.attrs.push(mk_verus_attr_syn(span, quote! { uses_unerased_proxy }));
385-
const_fun.attrs.push(mk_verus_attr_syn(span, quote! { encoded_const }));
386-
const_fun.to_tokens(&mut new_stream);
387-
fun.sig.ident = syn::Ident::new(
388-
&format!("{VERUS_UNERASED_PROXY}{}", fun.sig.ident),
389-
fun.sig.ident.span(),
390-
);
391-
fun.attrs.push(mk_verus_attr_syn(span, quote! { unerased_proxy }));
383+
let proxy = rewrite_const_ret_proxy(&mut fun);
384+
fun.to_tokens(&mut new_stream);
385+
fun = proxy; // Add proof and spec on proxy func.
392386
}
393-
let spec_stmts = syntax::sig_specs_attr(erase, spec_attr, &mut fun.sig);
387+
388+
// Add the spec/proof (requires/ensures) to the function body.
394389
let new_stmts = spec_stmts.into_iter().map(|s| parse2(quote! { #s }).unwrap());
395390
let _ = fun.block_mut().unwrap().stmts.splice(0..0, new_stmts);
391+
392+
// Parse and replace proof_xxx!() inside function and replace panic.
393+
replace_block(erase, fun.block_mut().unwrap());
396394
fun.to_tokens(&mut new_stream);
397395
proc_macro::TokenStream::from(new_stream)
398396
}
@@ -644,11 +642,40 @@ fn rewrite_with_expr(
644642
x_declares
645643
}
646644

645+
/// Rewrite the const function and return a proxy function.
646+
fn rewrite_const_ret_proxy(const_fun: &mut syn::ItemFn) -> syn::ItemFn {
647+
// This function is used to rewrite a const function to link it to a proxy function
648+
// that can be used to verify code.
649+
// It seems that we do not need to erase anything.
650+
// But just do it to be safe and consistent with verus macro.
651+
let span = const_fun.sig.constness.unwrap().span();
652+
let mut proxy_fun = const_fun.clone();
653+
replace_block(EraseGhost::Erase, const_fun.block_mut().unwrap());
654+
const_fun.attrs.push(mk_verifier_attr_syn(span, quote! { external }));
655+
const_fun.attrs.push(mk_verus_attr_syn(span, quote! { uses_unerased_proxy }));
656+
const_fun.attrs.push(mk_verus_attr_syn(span, quote! { encoded_const }));
657+
658+
proxy_fun.sig.ident = syn::Ident::new(
659+
&format!("{VERUS_UNERASED_PROXY}{}", const_fun.sig.ident),
660+
const_fun.sig.ident.span(),
661+
);
662+
proxy_fun.attrs.push(mk_verus_attr_syn(span, quote! { unerased_proxy }));
663+
proxy_fun
664+
}
665+
647666
// Create a copy of function with unverified function signature without a
648667
// function body, to enable seamless use of unverified call to the function in
649668
// verification.
650-
fn rewrite_unverified_func(fun: &mut syn::ItemFn, span: proc_macro2::Span) -> syn::ItemFn {
669+
// If the function is const, it will be rewritten to a proxy function and a verified function.
670+
fn rewrite_unverified_func(fun: &mut syn::ItemFn, span: proc_macro2::Span) -> Vec<syn::ItemFn> {
671+
let mut ret = vec![];
651672
let mut unverified_fun = fun.clone();
673+
if fun.sig.constness.is_some() {
674+
// Create a proxy function to include requires/ensures.
675+
let proxy = rewrite_const_ret_proxy(&mut unverified_fun);
676+
ret.push(unverified_fun);
677+
unverified_fun = proxy;
678+
}
652679
let stmts = vec![
653680
syn::Stmt::Expr(
654681
syn::Expr::Verbatim(
@@ -669,5 +696,6 @@ fn rewrite_unverified_func(fun: &mut syn::ItemFn, span: proc_macro2::Span) -> sy
669696
// change name to verified_{fname}
670697
let x = &fun.sig.ident;
671698
fun.sig.ident = syn::Ident::new(&format!("{VERIFIED}_{x}"), x.span());
672-
unverified_fun
699+
ret.push(unverified_fun);
700+
ret
673701
}

source/rust_verify_test/tests/syntax_attr.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,3 +812,34 @@ test_verify_one_file! {
812812
pub const X: u64 = const_fn(1);
813813
} => Ok(())
814814
}
815+
816+
test_verify_one_file! {
817+
#[test] test_const_fn_with_ghost code!{
818+
use vstd::prelude::*;
819+
#[verus_spec(ret =>
820+
with Ghost(g): Ghost<u64>
821+
)]
822+
#[allow(unused_variables)]
823+
pub const fn const_fn(x: u64) -> u64 {
824+
proof!{
825+
assert(true);
826+
}
827+
{
828+
proof!{assert(true);}
829+
}
830+
x
831+
}
832+
833+
#[verus_spec(
834+
with Ghost(g): Ghost<u64>
835+
)]
836+
pub const fn call_const_fn(x: u64) -> u64 {
837+
proof_with!{Ghost(g)}
838+
const_fn(x)
839+
}
840+
841+
842+
// external call to const_fn does not need ghost var.
843+
pub const X: u64 = const_fn(1);
844+
} => Ok(())
845+
}

0 commit comments

Comments
 (0)