Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple Stubbing with Contracts #2746

Merged
merged 29 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e6db205
Replacement basics
JustusAdam Sep 7, 2023
ff344b6
Fixing errors and formatting
JustusAdam Sep 7, 2023
16d6e50
Documentation and general fixes
JustusAdam Sep 7, 2023
6ab4834
More docs, more fixes
JustusAdam Sep 7, 2023
3347515
Mostly expanding the test case conditions
JustusAdam Sep 7, 2023
a1f12be
Stupid whitespace
JustusAdam Sep 7, 2023
2c6abd6
Turns out I did need the copies.
JustusAdam Sep 8, 2023
4f3c633
Fix test cases
JustusAdam Sep 8, 2023
a847af2
Added nicer errors + tests for missing contracts
JustusAdam Sep 10, 2023
29b128a
Incorporating suggestions
JustusAdam Sep 15, 2023
5651146
Apply suggestions from code review
JustusAdam Sep 15, 2023
cf9e4a5
Format
JustusAdam Sep 18, 2023
9429f47
Merge branch 'main' into simple-contract-replacement
JustusAdam Sep 18, 2023
a6dc282
Switch the notes
JustusAdam Sep 19, 2023
05abe4d
Added type annotation
JustusAdam Sep 19, 2023
05071f1
Sketch for module-level contracts documentation.
JustusAdam Sep 19, 2023
0777d0d
Formatting
JustusAdam Sep 19, 2023
272326a
Change code structure
JustusAdam Sep 21, 2023
686b626
Adding code review suggestions
JustusAdam Sep 21, 2023
5dc470b
Make the macro emit an `Arbitrary` bound for return types.
JustusAdam Sep 21, 2023
9ae06e4
Hehe
JustusAdam Sep 21, 2023
82121c5
Remove unused shadowing statement.
JustusAdam Sep 26, 2023
54bdfee
Expand the doc even more
JustusAdam Sep 26, 2023
415a0f5
Apply suggestions from code review
JustusAdam Sep 26, 2023
f2a93de
Formatting
JustusAdam Sep 26, 2023
d987640
Merge branch 'simple-contract-replacement' of github.com:JustusAdam/k…
JustusAdam Sep 26, 2023
85f59c7
Committed wrong file
JustusAdam Sep 26, 2023
d32e651
Update library/kani_macros/src/sysroot/contracts.rs
JustusAdam Sep 26, 2023
a9b36a3
Merge branch 'main' into simple-contract-replacement
JustusAdam Sep 26, 2023
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
118 changes: 98 additions & 20 deletions kani-compiler/src/kani_middle/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use strum_macros::{AsRefStr, EnumString};

use tracing::{debug, trace};

use super::resolve::{self, resolve_fn};
use super::resolve::{self, resolve_fn, ResolveError};

#[derive(Debug, Clone, Copy, AsRefStr, EnumString, PartialEq, Eq, PartialOrd, Ord)]
#[strum(serialize_all = "snake_case")]
Expand All @@ -31,13 +31,20 @@ enum KaniAttributeKind {
/// Attribute used to mark unstable APIs.
Unstable,
Unwind,
/// A sound [`Self::Stub`] that replaces a function by a stub generated from
/// its contract.
StubVerified,
/// A harness, similar to [`Self::Proof`], but for checking a function
/// contract, e.g. the contract check is substituted for the target function
/// before the the verification runs.
ProofForContract,
/// Attribute on a function with a contract that identifies the code
/// implementing the check for this contract.
CheckedWith,
/// Internal attribute of the contracts implementation that identifies the
/// name of the function which was generated as the sound stub from the
/// contract of this function.
ReplacedWith,
/// Attribute on a function that was auto-generated from expanding a
/// function contract.
IsContractGenerated,
Expand All @@ -52,8 +59,10 @@ impl KaniAttributeKind {
| KaniAttributeKind::Solver
| KaniAttributeKind::Stub
| KaniAttributeKind::ProofForContract
| KaniAttributeKind::StubVerified
| KaniAttributeKind::Unwind => true,
KaniAttributeKind::Unstable
| KaniAttributeKind::ReplacedWith
| KaniAttributeKind::CheckedWith
| KaniAttributeKind::IsContractGenerated => false,
}
Expand Down Expand Up @@ -134,6 +143,33 @@ impl<'tcx> KaniAttributes<'tcx> {
}
}

/// Parse, extract and resolve the target of `stub_verified(TARGET)`. The
/// returned `Symbol` and `DefId` are respectively the name and id of
/// `TARGET`. The `Span` is that of the contents of the attribute and used
/// for error reporting.
fn interpret_stub_verified_attribute(
&self,
) -> Vec<Result<(Symbol, DefId, Span), ErrorGuaranteed>> {
self.map
.get(&KaniAttributeKind::StubVerified)
.map_or([].as_slice(), Vec::as_slice)
.iter()
.map(|attr| {
let name = expect_key_string_value(self.tcx.sess, attr)?;
let ok = self.resolve_sibling(name.as_str()).map_err(|e| {
self.tcx.sess.span_err(
attr.span,
format!(
"Sould not resolve replacement function {} because {e}",
JustusAdam marked this conversation as resolved.
Show resolved Hide resolved
JustusAdam marked this conversation as resolved.
Show resolved Hide resolved
name.as_str()
),
)
})?;
Ok((name, ok, attr.span))
})
.collect()
}

/// Parse and extract the `proof_for_contract(TARGET)` attribute. The
/// returned symbol and DefId are respectively the name and id of `TARGET`,
/// the span in the span for the attribute (contents).
Expand All @@ -142,30 +178,50 @@ impl<'tcx> KaniAttributes<'tcx> {
) -> Option<Result<(Symbol, DefId, Span), ErrorGuaranteed>> {
self.expect_maybe_one(KaniAttributeKind::ProofForContract).map(|target| {
let name = expect_key_string_value(self.tcx.sess, target)?;
let resolved = resolve_fn(
self.tcx,
self.tcx.parent_module_from_def_id(self.item.expect_local()).to_local_def_id(),
name.as_str(),
);
resolved.map(|ok| (name, ok, target.span)).map_err(|resolve_err| {
self.tcx.sess.span_err(
target.span,
format!(
"Failed to resolve replacement function {} because {resolve_err}",
name.as_str()
),
)
})
self.resolve_sibling(name.as_str()).map(|ok| (name, ok, target.span)).map_err(
|resolve_err| {
self.tcx.sess.span_err(
target.span,
format!(
"Failed to resolve checking function {} because {resolve_err}",
name.as_str()
),
)
},
)
})
}

/// Extract the name of the sibling function this contract is checked with
/// (if any).
/// Extract the name of the sibling function this function's contract is
/// checked with (if any).
///
/// `None` indicates this function does not use a contract, `Some(Err(_))`
/// indicates a contract does exist but an error occurred during resolution.
pub fn checked_with(&self) -> Option<Result<Symbol, ErrorGuaranteed>> {
self.expect_maybe_one(KaniAttributeKind::CheckedWith)
.map(|target| expect_key_string_value(self.tcx.sess, target))
}

/// Extract the name of the sibling function this function's contract is
/// stubbed as (if any).
///
/// `None` indicates this function does not use a contract, `Some(Err(_))`
/// indicates a contract does exist but an error occurred during resolution.
pub fn replaced_with(&self) -> Option<Result<Symbol, ErrorGuaranteed>> {
self.expect_maybe_one(KaniAttributeKind::ReplacedWith)
.map(|target| expect_key_string_value(self.tcx.sess, target))
}

/// Resolve a function that is known to reside in the same module as the one
/// these attributes belong to (`self.item`).
fn resolve_sibling(&self, path_str: &str) -> Result<DefId, ResolveError<'tcx>> {
resolve_fn(
self.tcx,
self.tcx.parent_module_from_def_id(self.item.expect_local()).to_local_def_id(),
path_str,
)
}

/// Check that all attributes assigned to an item is valid.
/// Errors will be added to the session. Invoke self.tcx.sess.abort_if_errors() to terminate
/// the session and emit all errors found.
Expand Down Expand Up @@ -223,7 +279,10 @@ impl<'tcx> KaniAttributes<'tcx> {
}
expect_single(self.tcx, kind, &attrs);
}
KaniAttributeKind::CheckedWith => {
KaniAttributeKind::StubVerified => {
expect_single(self.tcx, kind, &attrs);
}
KaniAttributeKind::CheckedWith | KaniAttributeKind::ReplacedWith => {
self.expect_maybe_one(kind)
.map(|attr| expect_key_string_value(&self.tcx.sess, attr));
}
Expand Down Expand Up @@ -345,12 +404,31 @@ impl<'tcx> KaniAttributes<'tcx> {
};
harness.stubs.push(self.stub_for_relative_item(name, replacement_name));
}
KaniAttributeKind::StubVerified => {
for contract in self.interpret_stub_verified_attribute() {
let Ok((name, def_id, _span)) = contract else {
continue;
JustusAdam marked this conversation as resolved.
Show resolved Hide resolved
};
let Some(Ok(replacement_name)) =
KaniAttributes::for_item(self.tcx, def_id).replaced_with()
else {
self.tcx.sess.span_err(
JustusAdam marked this conversation as resolved.
Show resolved Hide resolved
self.tcx.def_span(self.item),
format!("Invalid `{}` attribute format", kind.as_ref()),
);
continue;
};
harness.stubs.push(self.stub_for_relative_item(name, replacement_name))
}
}
KaniAttributeKind::Unstable => {
// Internal attribute which shouldn't exist here.
unreachable!()
}
KaniAttributeKind::CheckedWith | KaniAttributeKind::IsContractGenerated => {
todo!("Contract attributes are not supported on proofs")
KaniAttributeKind::CheckedWith
| KaniAttributeKind::IsContractGenerated
| KaniAttributeKind::ReplacedWith => {
self.tcx.sess.span_err(self.tcx.def_span(self.item), format!("Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)", kind.as_ref()));
}
};
harness
Expand Down
23 changes: 22 additions & 1 deletion library/kani_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,26 @@ pub fn proof_for_contract(attr: TokenStream, item: TokenStream) -> TokenStream {
attr_impl::proof_for_contract(attr, item)
}

/// `stub_verified(TARGET)` is a harness attribute (to be used on [`proof`] or
/// [`proof_for_contract`] function) that replaces all occurrences of `TARGET`
/// reachable from this harness with a stub generated from the contract on
/// `TARGET`.
///
/// To create a contract for `TARGET` you must decorate it with at least one
JustusAdam marked this conversation as resolved.
Show resolved Hide resolved
/// [`requires`] or [`ensures`] attribute.
///
/// You may use multiple `stub_verified` attributes on a single harness.
///
/// For more information see the [function contract
/// RFC](https://model-checking.github.io/kani/rfc/rfcs/0009-function-contracts.html).
///
/// This attribute is part of the unstable contracts API and requires
/// `-Zfunction-contracts` flag to be used.
#[proc_macro_attribute]
pub fn stub_verified(attr: TokenStream, item: TokenStream) -> TokenStream {
attr_impl::stub_verified(attr, item)
}

/// This module implements Kani attributes in a way that only Kani's compiler can understand.
/// This code should only be activated when pre-building Kani's sysroot.
#[cfg(kani_sysroot)]
Expand All @@ -170,7 +190,7 @@ mod sysroot {

mod contracts;

pub use contracts::{ensures, proof_for_contract, requires};
pub use contracts::{ensures, proof_for_contract, requires, stub_verified};

use super::*;

Expand Down Expand Up @@ -344,4 +364,5 @@ mod regular {
no_op!(requires);
no_op!(ensures);
no_op!(proof_for_contract);
no_op!(stub_verified);
}
Loading
Loading