Skip to content

Commit dba1674

Browse files
JustusAdamcelinvalfeliperodri
authored
Simple Stubbing with Contracts (rust-lang#2746)
### Description of changes: Expands the contract features introduced in rust-lang#2655 by completing the checking with stubbing/replacement capabilities. ### Resolved issues: Resolves rust-lang#2621 ### Related RFC: [0009 Function Contracts](https://model-checking.github.io/kani/rfc/rfcs/0009-function-contracts.html) By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Celina G. Val <[email protected]> Co-authored-by: Felipe R. Monteiro <[email protected]>
1 parent 8480dc6 commit dba1674

21 files changed

+1225
-257
lines changed

kani-compiler/src/kani_middle/attributes.rs

Lines changed: 145 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use strum_macros::{AsRefStr, EnumString};
1919

2020
use tracing::{debug, trace};
2121

22-
use super::resolve::{self, resolve_fn};
22+
use super::resolve::{self, resolve_fn, ResolveError};
2323

2424
#[derive(Debug, Clone, Copy, AsRefStr, EnumString, PartialEq, Eq, PartialOrd, Ord)]
2525
#[strum(serialize_all = "snake_case")]
@@ -31,13 +31,20 @@ enum KaniAttributeKind {
3131
/// Attribute used to mark unstable APIs.
3232
Unstable,
3333
Unwind,
34+
/// A sound [`Self::Stub`] that replaces a function by a stub generated from
35+
/// its contract.
36+
StubVerified,
3437
/// A harness, similar to [`Self::Proof`], but for checking a function
3538
/// contract, e.g. the contract check is substituted for the target function
3639
/// before the the verification runs.
3740
ProofForContract,
3841
/// Attribute on a function with a contract that identifies the code
3942
/// implementing the check for this contract.
4043
CheckedWith,
44+
/// Internal attribute of the contracts implementation that identifies the
45+
/// name of the function which was generated as the sound stub from the
46+
/// contract of this function.
47+
ReplacedWith,
4148
/// Attribute on a function that was auto-generated from expanding a
4249
/// function contract.
4350
IsContractGenerated,
@@ -52,8 +59,10 @@ impl KaniAttributeKind {
5259
| KaniAttributeKind::Solver
5360
| KaniAttributeKind::Stub
5461
| KaniAttributeKind::ProofForContract
62+
| KaniAttributeKind::StubVerified
5563
| KaniAttributeKind::Unwind => true,
5664
KaniAttributeKind::Unstable
65+
| KaniAttributeKind::ReplacedWith
5766
| KaniAttributeKind::CheckedWith
5867
| KaniAttributeKind::IsContractGenerated => false,
5968
}
@@ -134,6 +143,30 @@ impl<'tcx> KaniAttributes<'tcx> {
134143
}
135144
}
136145

146+
/// Parse, extract and resolve the target of `stub_verified(TARGET)`. The
147+
/// returned `Symbol` and `DefId` are respectively the name and id of
148+
/// `TARGET`. The `Span` is that of the contents of the attribute and used
149+
/// for error reporting.
150+
fn interpret_stub_verified_attribute(
151+
&self,
152+
) -> Vec<Result<(Symbol, DefId, Span), ErrorGuaranteed>> {
153+
self.map
154+
.get(&KaniAttributeKind::StubVerified)
155+
.map_or([].as_slice(), Vec::as_slice)
156+
.iter()
157+
.map(|attr| {
158+
let name = expect_key_string_value(self.tcx.sess, attr)?;
159+
let ok = self.resolve_sibling(name.as_str()).map_err(|e| {
160+
self.tcx.sess.span_err(
161+
attr.span,
162+
format!("Failed to resolve replacement function {}: {e}", name.as_str()),
163+
)
164+
})?;
165+
Ok((name, ok, attr.span))
166+
})
167+
.collect()
168+
}
169+
137170
/// Parse and extract the `proof_for_contract(TARGET)` attribute. The
138171
/// returned symbol and DefId are respectively the name and id of `TARGET`,
139172
/// the span in the span for the attribute (contents).
@@ -142,30 +175,50 @@ impl<'tcx> KaniAttributes<'tcx> {
142175
) -> Option<Result<(Symbol, DefId, Span), ErrorGuaranteed>> {
143176
self.expect_maybe_one(KaniAttributeKind::ProofForContract).map(|target| {
144177
let name = expect_key_string_value(self.tcx.sess, target)?;
145-
let resolved = resolve_fn(
146-
self.tcx,
147-
self.tcx.parent_module_from_def_id(self.item.expect_local()).to_local_def_id(),
148-
name.as_str(),
149-
);
150-
resolved.map(|ok| (name, ok, target.span)).map_err(|resolve_err| {
151-
self.tcx.sess.span_err(
152-
target.span,
153-
format!(
154-
"Failed to resolve replacement function {} because {resolve_err}",
155-
name.as_str()
156-
),
157-
)
158-
})
178+
self.resolve_sibling(name.as_str()).map(|ok| (name, ok, target.span)).map_err(
179+
|resolve_err| {
180+
self.tcx.sess.span_err(
181+
target.span,
182+
format!(
183+
"Failed to resolve checking function {} because {resolve_err}",
184+
name.as_str()
185+
),
186+
)
187+
},
188+
)
159189
})
160190
}
161191

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

202+
/// Extract the name of the sibling function this function's contract is
203+
/// stubbed as (if any).
204+
///
205+
/// `None` indicates this function does not use a contract, `Some(Err(_))`
206+
/// indicates a contract does exist but an error occurred during resolution.
207+
pub fn replaced_with(&self) -> Option<Result<Symbol, ErrorGuaranteed>> {
208+
self.expect_maybe_one(KaniAttributeKind::ReplacedWith)
209+
.map(|target| expect_key_string_value(self.tcx.sess, target))
210+
}
211+
212+
/// Resolve a function that is known to reside in the same module as the one
213+
/// these attributes belong to (`self.item`).
214+
fn resolve_sibling(&self, path_str: &str) -> Result<DefId, ResolveError<'tcx>> {
215+
resolve_fn(
216+
self.tcx,
217+
self.tcx.parent_module_from_def_id(self.item.expect_local()).to_local_def_id(),
218+
path_str,
219+
)
220+
}
221+
169222
/// Check that all attributes assigned to an item is valid.
170223
/// Errors will be added to the session. Invoke self.tcx.sess.abort_if_errors() to terminate
171224
/// the session and emit all errors found.
@@ -223,7 +276,10 @@ impl<'tcx> KaniAttributes<'tcx> {
223276
}
224277
expect_single(self.tcx, kind, &attrs);
225278
}
226-
KaniAttributeKind::CheckedWith => {
279+
KaniAttributeKind::StubVerified => {
280+
expect_single(self.tcx, kind, &attrs);
281+
}
282+
KaniAttributeKind::CheckedWith | KaniAttributeKind::ReplacedWith => {
227283
self.expect_maybe_one(kind)
228284
.map(|attr| expect_key_string_value(&self.tcx.sess, attr));
229285
}
@@ -325,38 +381,87 @@ impl<'tcx> KaniAttributes<'tcx> {
325381
harness.unwind_value = parse_unwind(self.tcx, attributes[0])
326382
}
327383
KaniAttributeKind::Proof => harness.proof = true,
328-
KaniAttributeKind::ProofForContract => {
329-
harness.proof = true;
330-
let Some(Ok((name, id, span))) = self.interpret_the_for_contract_attribute()
331-
else {
332-
self.tcx.sess.span_err(
333-
self.tcx.def_span(self.item),
334-
format!("Invalid `{}` attribute format", kind.as_ref()),
335-
);
336-
return harness;
337-
};
338-
let Some(Ok(replacement_name)) =
339-
KaniAttributes::for_item(self.tcx, id).checked_with()
340-
else {
341-
self.tcx
342-
.sess
343-
.span_err(span, "Target function for this check has no contract");
344-
return harness;
345-
};
346-
harness.stubs.push(self.stub_for_relative_item(name, replacement_name));
347-
}
384+
KaniAttributeKind::ProofForContract => self.handle_proof_for_contract(&mut harness),
385+
KaniAttributeKind::StubVerified => self.handle_stub_verified(&mut harness),
348386
KaniAttributeKind::Unstable => {
349387
// Internal attribute which shouldn't exist here.
350388
unreachable!()
351389
}
352-
KaniAttributeKind::CheckedWith | KaniAttributeKind::IsContractGenerated => {
353-
todo!("Contract attributes are not supported on proofs")
390+
KaniAttributeKind::CheckedWith
391+
| KaniAttributeKind::IsContractGenerated
392+
| KaniAttributeKind::ReplacedWith => {
393+
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()));
354394
}
355395
};
356396
harness
357397
})
358398
}
359399

400+
fn handle_proof_for_contract(&self, harness: &mut HarnessAttributes) {
401+
let sess = self.tcx.sess;
402+
let (name, id, span) = match self.interpret_the_for_contract_attribute() {
403+
None => unreachable!(
404+
"impossible, was asked to handle `proof_for_contract` but didn't find such an attribute."
405+
),
406+
Some(Err(_)) => return, // This error was already emitted
407+
Some(Ok(values)) => values,
408+
};
409+
let Some(Ok(replacement_name)) = KaniAttributes::for_item(self.tcx, id).checked_with()
410+
else {
411+
sess.struct_span_err(
412+
span,
413+
format!(
414+
"Failed to check contract: Function `{}` has no contract.",
415+
self.item_name(),
416+
),
417+
)
418+
.span_note(self.tcx.def_span(id), "Try adding a contract to this function.")
419+
.emit();
420+
return;
421+
};
422+
harness.stubs.push(self.stub_for_relative_item(name, replacement_name));
423+
}
424+
425+
fn handle_stub_verified(&self, harness: &mut HarnessAttributes) {
426+
let sess = self.tcx.sess;
427+
for contract in self.interpret_stub_verified_attribute() {
428+
let Ok((name, def_id, span)) = contract else {
429+
// This error has already been emitted so we can ignore it now.
430+
// Later the session will fail anyway so we can just
431+
// optimistically forge on and try to find more errors.
432+
continue;
433+
};
434+
let replacement_name = match KaniAttributes::for_item(self.tcx, def_id).replaced_with()
435+
{
436+
None => {
437+
sess.struct_span_err(
438+
span,
439+
format!(
440+
"Failed to generate verified stub: Function `{}` has no contract.",
441+
self.item_name(),
442+
),
443+
)
444+
.span_note(
445+
self.tcx.def_span(def_id),
446+
format!(
447+
"Try adding a contract to this function or use the unsound `{}` attribute instead.",
448+
KaniAttributeKind::Stub.as_ref(),
449+
)
450+
)
451+
.emit();
452+
continue;
453+
}
454+
Some(Ok(replacement_name)) => replacement_name,
455+
Some(Err(_)) => continue,
456+
};
457+
harness.stubs.push(self.stub_for_relative_item(name, replacement_name))
458+
}
459+
}
460+
461+
fn item_name(&self) -> Symbol {
462+
self.tcx.item_name(self.item)
463+
}
464+
360465
/// Check that if this item is tagged with a proof_attribute, it is a valid harness.
361466
fn check_proof_attribute(&self, proof_attribute: &Attribute) {
362467
let span = proof_attribute.span;

0 commit comments

Comments
 (0)