Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
8f9deb9
Collect specs for async fns and their poll stubs in spec-collector
erdmannc May 29, 2024
01ba811
Abort if SpecAttributeKind::AsyncEnsures encountered too early
erdmannc May 29, 2024
dd7ab41
Encode necessary types for async code
erdmannc Jun 5, 2024
5db9e83
Encode future-generator bodies like method bodies
erdmannc Jun 5, 2024
6ded9f7
Encode necessary rvalues for async code
erdmannc Jun 5, 2024
a0ef84b
Temporarily remove self-arg for mutable reference constructor to avoi…
erdmannc Jun 5, 2024
99dfd08
Add type parameters to pure make_generic method for consistency
erdmannc Jun 5, 2024
2bbbbfa
Wrap fold result for GotoIf in Seqn
erdmannc Jun 26, 2024
821e61a
Fix name mangling
erdmannc Jun 5, 2024
0dff5a7
Replace calls to async trait methods
erdmannc Jun 5, 2024
ef043eb
Encode future-generator ghost fields as individual fields instead of …
erdmannc Jun 12, 2024
bee9f19
Fix post_args available to async postconditions
erdmannc Jun 12, 2024
8eee343
Only invoke MirPolyImpureEnc for async generators
erdmannc Jun 12, 2024
b1dae9e
Create poll stubs for async functions
erdmannc Jun 20, 2024
357a5fd
Add fields for upvars in addition to ghost fields for generator domains
erdmannc Jun 20, 2024
f7b1736
cleanup
erdmannc Jul 4, 2024
10577b9
Add postconditions about typing of pinned generator reference as well…
erdmannc Jul 4, 2024
661ab23
Merge branch 'generic-cast-args' into async-testing
erdmannc Jul 9, 2024
9e8b74c
Merge branch 'gotoif-seqn' into async-testing
erdmannc Jul 9, 2024
0e15b96
Add postconditions about future generator fields on future constructors
erdmannc Jul 9, 2024
1153243
Use TyCtxt to detect future generators
erdmannc Jul 10, 2024
52da198
Store procedure kind (method, async constructor, and async poll for n…
erdmannc Jul 10, 2024
3820711
Add suspension-point macro to desugar specified suspension point to m…
erdmannc Jul 16, 2024
6fc9b45
Merge remote-tracking branch 'aurel/rewrite-2023' into async-testing
erdmannc Jul 16, 2024
a34c807
Encode dummy type for closures (necessary to pass closures to suspens…
erdmannc Jul 17, 2024
80f1d4f
Make sure suspension-point marker closures are tupled even if there i…
erdmannc Jul 17, 2024
4f8b5f6
evaluate future first when desugaring suspension-point
erdmannc Jul 22, 2024
fd74e42
fix edge case to make sure closures inside marker functions are alway…
erdmannc Jul 26, 2024
618a54a
Encode on_exit/on_entry conditions by replacing their marker function…
erdmannc Jul 26, 2024
cb0aa5b
Support decorating methods with async invariants
erdmannc Jul 30, 2024
116dfb5
Collect async invariant specs
erdmannc Jul 30, 2024
b819faf
Return async invariant spec-ids in spec-encoder
erdmannc Jul 30, 2024
0c4e84b
Export async-invariant macro
erdmannc Jul 30, 2024
a5539f7
Encode async-invariants in spec-encoder
erdmannc Jul 30, 2024
ac02676
Bugfix
erdmannc Jul 30, 2024
e49d1dd
Bugfix
erdmannc Jul 30, 2024
fa86439
Use procedure-kind set in spec-collector to detect async poll / const…
erdmannc Jul 30, 2024
0ef5755
Make sure async_invariant is attached to async fn, augment errors wit…
erdmannc Jul 31, 2024
73cf01b
Fix arguments available to async invariants and add them as precondit…
erdmannc Jul 31, 2024
c87e3ac
Use upvar- instead of ghost-fields for async invariants
erdmannc Jul 31, 2024
1dc13b1
Support yield-terminators
erdmannc Jul 31, 2024
cf0b6cc
Exhale async-invariants at yield and return in generator body (work-i…
erdmannc Jul 31, 2024
ef5b018
Add async-invariants as pre- and postconditions of poll stub
erdmannc Jul 31, 2024
d9beb02
Rename async poll stub encoder
erdmannc Aug 9, 2024
7b16a87
Make sure suspension-point labels are positive u32's
erdmannc Aug 12, 2024
97758b4
Add mod file
erdmannc Aug 12, 2024
9e3bf5d
Add suspension-point detection encoder
erdmannc Aug 12, 2024
dc8bce9
Add invariants about `ResumeTy` arg's typing and polled future to bus…
erdmannc Aug 12, 2024
07f0864
Add state field to generators and initialize to 0 in constructors
erdmannc Aug 13, 2024
0d42703
Add pre- and postcondition about possible state values
erdmannc Aug 14, 2024
db33800
Add pre- and postconditions corresponding to on_entry/on_exit conditi…
erdmannc Aug 14, 2024
f2e85a4
Encode and reify on_exit/on_entry conditions in poll-stub encoder to …
erdmannc Aug 14, 2024
97d3407
Take snapshot of future constructor arguments
erdmannc Aug 15, 2024
1d301db
Track futures from constructor call to suspension-point and add invar…
erdmannc Aug 16, 2024
ed930ce
Remove comment
erdmannc Aug 16, 2024
55621fd
Fix naming of future argument snapshot variable now that SuspensionPo…
erdmannc Sep 6, 2024
8be65b2
Store BB with yield in SuspensionPointAnalysis
erdmannc Sep 6, 2024
05ece30
Add a few comments
erdmannc Sep 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions prusti-contracts/prusti-contracts-proc-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ pub fn ensures(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
tokens
}

#[cfg(not(feature = "prusti"))]
#[proc_macro_attribute]
pub fn async_invariant(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
tokens
}

#[cfg(not(feature = "prusti"))]
#[proc_macro_attribute]
pub fn after_expiry(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -130,6 +136,12 @@ pub fn body_variant(_tokens: TokenStream) -> TokenStream {
TokenStream::new()
}

#[cfg(not(feature = "prusti"))]
#[proc_macro]
pub fn suspension_point(tokens: TokenStream) -> TokenStream {
tokens
}

// ----------------------
// --- PRUSTI ENABLED ---

Expand All @@ -148,6 +160,12 @@ pub fn ensures(attr: TokenStream, tokens: TokenStream) -> TokenStream {
rewrite_prusti_attributes(SpecAttributeKind::Ensures, attr.into(), tokens.into()).into()
}

#[cfg(feature = "prusti")]
#[proc_macro_attribute]
pub fn async_invariant(attr: TokenStream, tokens: TokenStream) -> TokenStream {
rewrite_prusti_attributes(SpecAttributeKind::AsyncInvariant, attr.into(), tokens.into()).into()
}

#[cfg(feature = "prusti")]
#[proc_macro_attribute]
pub fn after_expiry(attr: TokenStream, tokens: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -273,5 +291,11 @@ pub fn body_variant(tokens: TokenStream) -> TokenStream {
prusti_specs::body_variant(tokens.into()).into()
}

#[cfg(feature = "prusti")]
#[proc_macro]
pub fn suspension_point(tokens: TokenStream) -> TokenStream {
prusti_specs::suspension_point(tokens.into()).into()
}

// Ensure that you've also crated a transparent `#[cfg(not(feature = "prusti"))]`
// version of your new macro above!
10 changes: 10 additions & 0 deletions prusti-contracts/prusti-contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ pub use prusti_contracts_proc_macros::terminates;
/// A macro to annotate body variant of a loop to prove termination
pub use prusti_contracts_proc_macros::body_variant;

/// A macro to annotate suspension points inside async constructs
pub use prusti_contracts_proc_macros::suspension_point;

/// A macro for writing async invariants on an async function
pub use prusti_contracts_proc_macros::async_invariant;

#[cfg(not(feature = "prusti"))]
mod private {
use core::marker::PhantomData;
Expand Down Expand Up @@ -339,6 +345,10 @@ pub fn old<T>(arg: T) -> T {
arg
}

pub fn suspension_point_on_exit_marker<T>(_label: u32, _closures: T) {}

pub fn suspension_point_on_entry_marker<T>(_label: u32, _closures: T) {}

/// Universal quantifier.
///
/// This is a Prusti-internal representation of the `forall` syntax.
Expand Down
233 changes: 233 additions & 0 deletions prusti-contracts/prusti-specs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ fn extract_prusti_attributes(
let tokens = match attr_kind {
SpecAttributeKind::Requires
| SpecAttributeKind::Ensures
| SpecAttributeKind::AsyncInvariant
| SpecAttributeKind::AfterExpiry
| SpecAttributeKind::AssertOnExpiry
| SpecAttributeKind::RefineSpec => {
Expand All @@ -81,6 +82,10 @@ fn extract_prusti_attributes(
assert!(iter.next().is_none(), "Unexpected shape of an attribute.");
group.stream()
}
// these cannot appear here, as postconditions are only marked as async
// at a later stage
SpecAttributeKind::AsyncEnsures =>
unreachable!("SpecAttributeKind::AsyncEnsures should not appear at this stage"),
// Nothing to do for attributes without arguments.
SpecAttributeKind::Pure
| SpecAttributeKind::Terminates
Expand Down Expand Up @@ -125,6 +130,39 @@ pub fn rewrite_prusti_attributes(
// Collect the remaining Prusti attributes, removing them from `item`.
prusti_attributes.extend(extract_prusti_attributes(&mut item));

// in the case of an async fn, mark the postconditions as such,
// since they are handled differently
if let untyped::AnyFnItem::Fn(ref fn_item) = &item {
if fn_item.sig.asyncness.is_some() {
prusti_attributes = prusti_attributes
.into_iter()
.map(|(attr, tt)| match attr {
SpecAttributeKind::Ensures => (SpecAttributeKind::AsyncEnsures, tt),
_ => (attr, tt)
}
)
.collect();
}
}

// make sure async invariants can only be attached to async fn's
if matches!(outer_attr_kind, SpecAttributeKind::AsyncInvariant) {
// TODO: should the error be reported to the function's or the attribute's span?
let untyped::AnyFnItem::Fn(ref fn_item) = &item else {
return syn::Error::new(
item.span(),
"async_invariant attached to non-function item"
).to_compile_error();
};
if fn_item.sig.asyncness.is_none() {
return syn::Error::new(
item.span(),
"async_invariant attached to non-async function"
)
.to_compile_error();
}
}

// make sure to also update the check in the predicate! handling method
if prusti_attributes
.iter()
Expand Down Expand Up @@ -162,6 +200,8 @@ fn generate_spec_and_assertions(
let rewriting_result = match attr_kind {
SpecAttributeKind::Requires => generate_for_requires(attr_tokens, item),
SpecAttributeKind::Ensures => generate_for_ensures(attr_tokens, item),
SpecAttributeKind::AsyncEnsures => generate_for_async_ensures(attr_tokens, item),
SpecAttributeKind::AsyncInvariant => generate_for_async_invariant(attr_tokens, item),
SpecAttributeKind::AfterExpiry => generate_for_after_expiry(attr_tokens, item),
SpecAttributeKind::AssertOnExpiry => generate_for_assert_on_expiry(attr_tokens, item),
SpecAttributeKind::Pure => generate_for_pure(attr_tokens, item),
Expand Down Expand Up @@ -215,6 +255,55 @@ fn generate_for_ensures(attr: TokenStream, item: &untyped::AnyFnItem) -> Generat
))
}

/// Generate spec items and attributes to typecheck and later retrieve "ensures" annotations,
/// but for async fn, as their postconditions will need to be moved to their generator method
/// instead of the future-constructor.
fn generate_for_async_ensures(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
let mut rewriter = rewriter::AstRewriter::new();
// parse the postcondition
let expr = parse_prusti(attr)?;
// generate a spec item for the postcondition itself,
// which will be attached to the future's implementation
let spec_id = rewriter.generate_spec_id();
let spec_id_str = spec_id.to_string();
let spec_item =
rewriter.generate_spec_item_fn(rewriter::SpecItemType::Postcondition, spec_id, expr.clone(), item)?;

// and generate one wrapped in a `Poll` for the stub
let stub_spec_id = rewriter.generate_spec_id();
let stub_spec_id_str = stub_spec_id.to_string();
let stub_spec_item = rewriter.generate_async_ensures_item_fn(stub_spec_id, expr, item)?;

Ok((
vec![spec_item, stub_spec_item],
vec![
parse_quote_spanned! {item.span()=>
#[prusti::async_post_spec_id_ref = #spec_id_str]
},
parse_quote_spanned! {item.span()=>
#[prusti::async_stub_post_spec_id_ref = #stub_spec_id_str]
},
]
))
}


/// Generate spec items and attributes to typecheck and later retrieve invariant annotations
/// on async functions.
fn generate_for_async_invariant(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
let mut rewriter = rewriter::AstRewriter::new();
let spec_id = rewriter.generate_spec_id();
let spec_id_str = spec_id.to_string();
let spec_item =
rewriter.process_assertion(rewriter::SpecItemType::Precondition, spec_id, attr, item)?;
Ok((
vec![spec_item],
vec![parse_quote_spanned! {item.span()=>
#[prusti::async_inv_spec_id_ref = #spec_id_str]
}]
))
}

/// Generate spec items and attributes to typecheck and later retrieve "after_expiry" annotations.
fn generate_for_after_expiry(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
let mut rewriter = rewriter::AstRewriter::new();
Expand Down Expand Up @@ -433,6 +522,146 @@ pub fn prusti_refutation(tokens: TokenStream) -> TokenStream {
generate_expression_closure(&AstRewriter::process_prusti_refutation, tokens)
}


pub fn suspension_point(tokens: TokenStream) -> TokenStream {
// parse the expression inside the suspension point,
// making sure it is just an await (possibly with attributes)
let expr: syn::Expr = handle_result!(syn::parse2(tokens));
let expr_span = expr.span();
let syn::Expr::Await(mut await_expr) = expr else {
return syn::Error::new(
expr.span(),
"suspension-point must contain a single await-expression"
).to_compile_error();
};
let future = &await_expr.base;

let mut label: Option<u32> = None;
let mut on_exit: Vec<TokenStream> = Vec::new();
let mut on_entry: Vec<TokenStream> = Vec::new();

// extract label, on-exit, and on-entry conditions from the attributes
for attr in await_expr.attrs {
let attr_span = attr.span();
if !matches!(attr.style, syn::AttrStyle::Outer) {
return syn::Error::new(
attr_span,
"only outer attributes allowed in suspension-point",
).to_compile_error();
}
if !(attr.path.segments.len() == 1
|| (attr.path.segments.len() == 2 && attr.path.segments[0].ident == "prusti_contracts"))
{
return syn::Error::new(
attr_span,
"invalid attribute in suspension-point",
).to_compile_error();
}
let name = attr.path.segments[attr.path.segments.len() - 1]
.ident
.to_string();
// labels
if name == "label" {
let [proc_macro2::TokenTree::Group(group)] =
&attr.tokens.into_iter().collect::<Vec<_>>()[..]
else {
return syn::Error::new(
attr_span,
"expected group with a single positive integer as label",
).to_compile_error();
};
let [proc_macro2::TokenTree::Literal(lit)] =
&group.stream().into_iter().collect::<Vec<_>>()[..]
else {
return syn::Error::new(
attr_span,
"expected single positive integer as label",
).to_compile_error();
};
let lbl_num: u32 = lit
.to_string()
.parse()
.expect("expected single positive integer as label");
if lbl_num == 0 {
return syn::Error::new(
attr_span,
"suspension-point label must be a positive integer",
).to_compile_error();
}
if label.replace(lbl_num).is_some() {
return syn::Error::new(
attr_span,
"multiple labels provided for suspension-point",
).to_compile_error();
}
// on-exit conditions
} else if name == "on_exit" {
on_exit.push(attr.tokens);
// on-entry conditions
} else if name == "on_entry" {
on_entry.push(attr.tokens);
// all other attributes are not permitted
} else {
// TODO: more precise error message with span?
panic!("invalid attribute in suspension-point");
}
}

let label = label.unwrap_or_else(|| panic!("suspension-point must have a label"));

// generate spec-items for each condition
let create_spec_item = |tokens: TokenStream| -> syn::Result<syn::ExprClosure> {
let expr = parse_prusti(tokens)?;
Ok(parse_quote_spanned! {expr_span=>
|| -> bool {
let val: bool = #expr;
val
}
})
};

let on_exit: syn::Result<Vec<syn::ExprClosure>> = on_exit
.into_iter()
.map(create_spec_item)
.collect();
let on_exit = handle_result!(on_exit);

let on_entry: syn::Result<Vec<syn::ExprClosure>> = on_entry
.into_iter()
.map(create_spec_item)
.collect();
let on_entry = handle_result!(on_entry);

// return just the await-expression
let on_exit_closures = match on_exit.len() {
1 => {
let on_exit = &on_exit[0];
quote_spanned! { expr_span => (#on_exit,) }
}
_ => quote_spanned! { expr_span => (#(#on_exit),*) },
};
let on_entry_closures = match on_entry.len() {
1 => {
let on_entry = &on_entry[0];
quote_spanned! { expr_span => (#on_entry,) }
}
_ => quote_spanned! { expr_span => (#(#on_entry),*) },
};

await_expr.attrs = Vec::new();
quote_spanned! { expr_span =>
{
let fut = #future;
#[allow(unused_parens)]
::prusti_contracts::suspension_point_on_exit_marker(#label, #on_exit_closures);
let res = fut.await;
#[allow(unused_parens)]
::prusti_contracts::suspension_point_on_entry_marker(#label, #on_entry_closures);
res
}
}
}

/// Generates the TokenStream encoding an expression using prusti syntax
/// Used for body invariants, assertions, and assumptions
fn generate_expression_closure(
Expand Down Expand Up @@ -855,6 +1084,8 @@ fn extract_prusti_attributes_for_types(
let tokens = match attr_kind {
SpecAttributeKind::Requires => unreachable!("requires on type"),
SpecAttributeKind::Ensures => unreachable!("ensures on type"),
SpecAttributeKind::AsyncEnsures => unreachable!("ensures on type"),
SpecAttributeKind::AsyncInvariant => unreachable!("async-invariant on type"),
SpecAttributeKind::AfterExpiry => unreachable!("after_expiry on type"),
SpecAttributeKind::AssertOnExpiry => unreachable!("assert_on_expiry on type"),
SpecAttributeKind::RefineSpec => unreachable!("refine_spec on type"),
Expand Down Expand Up @@ -900,6 +1131,8 @@ fn generate_spec_and_assertions_for_types(
let rewriting_result = match attr_kind {
SpecAttributeKind::Requires => unreachable!(),
SpecAttributeKind::Ensures => unreachable!(),
SpecAttributeKind::AsyncEnsures => unreachable!(),
SpecAttributeKind::AsyncInvariant => unreachable!(),
SpecAttributeKind::AfterExpiry => unreachable!(),
SpecAttributeKind::AssertOnExpiry => unreachable!(),
SpecAttributeKind::Pure => unreachable!(),
Expand Down
Loading