-
Notifications
You must be signed in to change notification settings - Fork 84
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
List Subcommand (Implementation) #3523
base: main
Are you sure you want to change the base?
Changes from 17 commits
f54c0f3
67ed073
4ace776
b903473
b412d06
bc802a1
bfd2684
8e4efc0
2e79fbc
b1c5a9e
ced1dab
c98b0b0
b57e47d
6a659d0
d88a4aa
87328bc
0e14cd1
e41c2b6
5d0edb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -477,7 +477,7 @@ impl<'tcx> KaniAttributes<'tcx> { | |
/// | ||
/// We only extract attributes for harnesses that are local to the current crate. | ||
/// Note that all attributes should be valid by now. | ||
pub fn harness_attributes(&self) -> HarnessAttributes { | ||
pub fn harness_attributes(&self, is_list_enabled: bool) -> HarnessAttributes { | ||
// Abort if not local. | ||
if !self.item.is_local() { | ||
panic!("Expected a local item, but got: {:?}", self.item); | ||
|
@@ -505,7 +505,7 @@ impl<'tcx> KaniAttributes<'tcx> { | |
harness.unwind_value = parse_unwind(self.tcx, attributes[0]) | ||
} | ||
KaniAttributeKind::Proof => { /* no-op */ } | ||
KaniAttributeKind::ProofForContract => self.handle_proof_for_contract(&mut harness), | ||
KaniAttributeKind::ProofForContract => self.handle_proof_for_contract(&mut harness, is_list_enabled), | ||
KaniAttributeKind::StubVerified => self.handle_stub_verified(&mut harness), | ||
KaniAttributeKind::Unstable => { | ||
// Internal attribute which shouldn't exist here. | ||
|
@@ -531,7 +531,7 @@ impl<'tcx> KaniAttributes<'tcx> { | |
}) | ||
} | ||
|
||
fn handle_proof_for_contract(&self, harness: &mut HarnessAttributes) { | ||
fn handle_proof_for_contract(&self, harness: &mut HarnessAttributes, is_list_enabled: bool) { | ||
let dcx = self.tcx.dcx(); | ||
let (name, id, span) = match self.interpret_for_contract_attribute() { | ||
None => return, // This error was already emitted | ||
|
@@ -540,6 +540,13 @@ impl<'tcx> KaniAttributes<'tcx> { | |
assert!(matches!( | ||
&harness.kind, HarnessKind::ProofForContract { target_fn } | ||
if *target_fn == name.to_string())); | ||
|
||
// Only emit an error if we are trying to actually verify the contract. | ||
// (If we are running the list subcommand, we just report later that there are no contracts for this harness). | ||
if is_list_enabled { | ||
return; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? I think this should emit a compilation error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. People may want to write harnesses before they write the contracts. For instance, if I'm planning to write a contract for a function, I could write: fn foo(x: usize) -> usize { ... }
#[kani::proof_for_contract(foo)]
fn harness() {
let x: usize = kani::any();
foo(x);
} before I write the contracts for It makes sense to error during verification, since we can't verify a contract that doesn't exist. But I didn't think it made sense to punish people for being halfway done; they may want to use the list command to see what work they have left to do. That being said, I'm not even sure I could make this distinction if we're making the compiler unaware of the list command, so we may have to make it a compiler error. |
||
|
||
if KaniAttributes::for_item(self.tcx, id).contract_attributes().is_none() { | ||
dcx.struct_span_err( | ||
span, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
||
// Collects contract and contract harness metadata for the list subcommand. | ||
|
||
use std::collections::HashMap; | ||
|
||
use crate::kani_middle::attributes::{matches_diagnostic as matches_function, KaniAttributes}; | ||
use crate::kani_middle::codegen_units::CodegenUnits; | ||
use crate::kani_middle::{find_closure_in_body, InternalDefId, SourceLocation}; | ||
use kani_metadata::ContractedFunction; | ||
use rustc_middle::ty::TyCtxt; | ||
use rustc_smir::rustc_internal; | ||
use stable_mir::mir::mono::Instance; | ||
use stable_mir::mir::{Body, TerminatorKind}; | ||
use stable_mir::ty::{RigidTy, TyKind}; | ||
use stable_mir::{CrateDef, CrateItems}; | ||
|
||
/// Map each function to its contract harnesses | ||
/// `fns` includes all functions with contracts and all functions that are targets of a contract harness. | ||
fn fns_to_harnesses(tcx: TyCtxt) -> HashMap<InternalDefId, Vec<String>> { | ||
// We work with stable_mir::CrateItem instead of stable_mir::Instance to include generic items | ||
let crate_items: CrateItems = stable_mir::all_local_items(); | ||
|
||
let mut fns_to_harnesses: HashMap<InternalDefId, Vec<String>> = HashMap::new(); | ||
|
||
for item in crate_items { | ||
let def_id = rustc_internal::internal(tcx, item.def_id()); | ||
let fn_name = tcx.def_path_str(def_id); | ||
let attributes = KaniAttributes::for_item(tcx, def_id); | ||
|
||
if attributes.has_contract() { | ||
fns_to_harnesses.insert(def_id, vec![]); | ||
} else if let Some((_, target_def_id, _)) = attributes.interpret_for_contract_attribute() { | ||
if let Some(harnesses) = fns_to_harnesses.get_mut(&target_def_id) { | ||
harnesses.push(fn_name); | ||
} else { | ||
fns_to_harnesses.insert(target_def_id, vec![fn_name]); | ||
} | ||
} | ||
} | ||
|
||
fns_to_harnesses | ||
} | ||
|
||
/// Count the number of contracts in `check_body`, where `check_body` is the body of the | ||
/// kanitool::checked_with closure (c.f. kani_macros::sysroot::contracts). | ||
/// In this closure, preconditions are denoted by kani::assume() calls and postconditions by kani::assert() calls. | ||
/// The number of contracts is the number of times these functions are called inside the closure | ||
fn count_contracts(tcx: TyCtxt, check_body: &Body) -> usize { | ||
let mut count = 0; | ||
|
||
for bb in &check_body.blocks { | ||
if let TerminatorKind::Call { ref func, .. } = bb.terminator.kind { | ||
let fn_ty = func.ty(check_body.locals()).unwrap(); | ||
if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = fn_ty.kind() { | ||
let instance = Instance::resolve(fn_def, &args).unwrap(); | ||
// For each precondition or postcondition, increment the count | ||
if matches_function(tcx, instance.def, "KaniAssume") | ||
|| matches_function(tcx, instance.def, "KaniAssert") | ||
{ | ||
count += 1; | ||
} | ||
} | ||
} | ||
} | ||
count | ||
} | ||
|
||
/// For each function with contracts (or that is a target of a contract harness), | ||
/// construct a ContractedFunction object for it and store it in `units`. | ||
pub fn collect_contracted_fns(tcx: TyCtxt, units: &mut CodegenUnits) { | ||
for (fn_def_id, harnesses) in fns_to_harnesses(tcx) { | ||
let attrs = KaniAttributes::for_item(tcx, fn_def_id); | ||
|
||
// It's possible that a function is a target of a proof for contract but does not actually have contracts. | ||
// If the function does have contracts, count them. | ||
let total_contracts = if attrs.has_contract() { | ||
let contract_attrs = | ||
KaniAttributes::for_item(tcx, fn_def_id).contract_attributes().unwrap(); | ||
let body: Body = rustc_internal::stable(tcx.optimized_mir(fn_def_id)); | ||
let check_body: Body = | ||
find_closure_in_body(&body, contract_attrs.checked_with.as_str()) | ||
.unwrap() | ||
.body() | ||
.unwrap(); | ||
|
||
count_contracts(tcx, &check_body) | ||
} else { | ||
0 | ||
}; | ||
|
||
units.contracted_functions.push(ContractedFunction { | ||
function: tcx.def_path_str(fn_def_id), | ||
file: SourceLocation::new(rustc_internal::stable(tcx.def_span(fn_def_id))).filename, | ||
harnesses, | ||
total_contracts, | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does the compiler need to be aware of the list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per slack, no -- will fix. Going to move to draft until then, since this will take me a bit of time to do and I don't want anyone wasting time reviewing in the meantime.