From f54c0f3f00b315c326fa1cab36c46a2bfd5cc143 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 12 Sep 2024 11:03:42 -0400 Subject: [PATCH 01/34] standalone list command --- kani-compiler/src/args.rs | 3 + .../codegen_cprover_gotoc/codegen/contract.rs | 68 ++++++--- .../compiler_interface.rs | 62 +++++++- .../src/kani_middle/codegen_units.rs | 41 ++--- kani-driver/Cargo.toml | 2 + kani-driver/src/args/list_args.rs | 82 ++++++++++ kani-driver/src/args/mod.rs | 12 ++ kani-driver/src/assess/mod.rs | 4 +- kani-driver/src/call_single_file.rs | 7 +- kani-driver/src/list.rs | 141 ++++++++++++++++++ kani-driver/src/main.rs | 8 +- kani-driver/src/metadata.rs | 2 + kani-driver/src/session.rs | 11 +- kani_metadata/Cargo.toml | 1 + kani_metadata/src/lib.rs | 29 +++- kani_metadata/src/unstable.rs | 2 + 16 files changed, 418 insertions(+), 57 deletions(-) create mode 100644 kani-driver/src/args/list_args.rs create mode 100644 kani-driver/src/list.rs diff --git a/kani-compiler/src/args.rs b/kani-compiler/src/args.rs index 3fa74b0e5aba..b0cec78cc09f 100644 --- a/kani-compiler/src/args.rs +++ b/kani-compiler/src/args.rs @@ -47,6 +47,9 @@ pub struct Arguments { pub reachability_analysis: ReachabilityType, #[clap(long = "enable-stubbing")] pub stubbing_enabled: bool, + /// Option name used to tell the compiler to execute the list subcommand + #[clap(long = "list")] + pub list_enabled: bool, /// Option name used to define unstable features. #[clap(short = 'Z', long = "unstable")] pub unstable_features: Vec, diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index a871cd58f94f..d1985bc86f1f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -1,14 +1,14 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::codegen_cprover_gotoc::{codegen::ty_stable::pointee_type_stable, GotocCtx}; -use crate::kani_middle::attributes::KaniAttributes; +use crate::kani_middle::attributes::{ContractAttributes, KaniAttributes, matches_diagnostic as matches_function}; use cbmc::goto_program::FunctionContract; use cbmc::goto_program::{Expr, Lambda, Location, Type}; use kani_metadata::AssignsContract; use rustc_hir::def_id::DefId as InternalDefId; use rustc_smir::rustc_internal; use stable_mir::mir::mono::{Instance, MonoItem}; -use stable_mir::mir::{Local, VarDebugInfoContents}; +use stable_mir::mir::{Local, TerminatorKind, VarDebugInfoContents}; use stable_mir::ty::{FnDef, RigidTy, TyKind}; use stable_mir::CrateDef; @@ -87,33 +87,34 @@ impl<'tcx> GotocCtx<'tcx> { recursion_tracker } + fn find_closure(&mut self, inside: Instance, name: &str) -> Option { + let body = self.transformer.body(self.tcx, inside); + body.var_debug_info.iter().find_map(|var_info| { + if var_info.name.as_str() == name { + let ty = match &var_info.value { + VarDebugInfoContents::Place(place) => place.ty(body.locals()).unwrap(), + VarDebugInfoContents::Const(const_op) => const_op.ty(), + }; + if let TyKind::RigidTy(RigidTy::Closure(def, args)) = ty.kind() { + return Some(Instance::resolve(FnDef(def.def_id()), &args).unwrap()); + } + } + None + }) + } + /// Find the modifies recursively since we may have a recursion wrapper. /// I.e.: [recursion_wrapper ->]? check -> modifies. fn find_modifies(&mut self, instance: Instance) -> Option { let contract_attrs = KaniAttributes::for_instance(self.tcx, instance).contract_attributes()?; - let mut find_closure = |inside: Instance, name: &str| { - let body = self.transformer.body(self.tcx, inside); - body.var_debug_info.iter().find_map(|var_info| { - if var_info.name.as_str() == name { - let ty = match &var_info.value { - VarDebugInfoContents::Place(place) => place.ty(body.locals()).unwrap(), - VarDebugInfoContents::Const(const_op) => const_op.ty(), - }; - if let TyKind::RigidTy(RigidTy::Closure(def, args)) = ty.kind() { - return Some(Instance::resolve(FnDef(def.def_id()), &args).unwrap()); - } - } - None - }) - }; let check_instance = if contract_attrs.has_recursion { - let recursion_check = find_closure(instance, contract_attrs.recursion_check.as_str())?; - find_closure(recursion_check, contract_attrs.checked_with.as_str())? + let recursion_check = self.find_closure(instance, contract_attrs.recursion_check.as_str())?; + self.find_closure(recursion_check, contract_attrs.checked_with.as_str())? } else { - find_closure(instance, contract_attrs.checked_with.as_str())? + self.find_closure(instance, contract_attrs.checked_with.as_str())? }; - find_closure(check_instance, contract_attrs.modifies_wrapper.as_str()) + self.find_closure(check_instance, contract_attrs.modifies_wrapper.as_str()) } /// Convert the Kani level contract into a CBMC level contract by creating a @@ -242,4 +243,29 @@ impl<'tcx> GotocCtx<'tcx> { self.symbol_table.attach_contract(&mangled_name, goto_contract); self.reset_current_fn(); } + + /// Count the number of contracts applied to `contracted_function` + pub fn count_contracts(&mut self, contracted_function: &Instance, contract_attrs: ContractAttributes) -> usize { + // Extract the body of the check_closure, which will contain kani::assume() calls for each precondition + // and kani::assert() calls for each postcondition. + let check_closure = self.find_closure(*contracted_function, contract_attrs.checked_with.as_str()).unwrap(); + let body = check_closure.body().unwrap(); + + let mut count = 0; + + for bb in &body.blocks { + if let TerminatorKind::Call { ref func, .. } = bb.terminator.kind { + let fn_ty = func.ty(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(self.tcx, instance.def, "KaniAssume") + || matches_function(self.tcx, instance.def, "KaniAssert") { + count += 1; + } + } + } + } + count + } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 9f700192f2f2..0de3b80adac9 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -5,7 +5,7 @@ use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; -use crate::kani_middle::analysis; +use crate::kani_middle::{analysis, SourceLocation}; use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; use crate::kani_middle::check_reachable_items; use crate::kani_middle::codegen_units::{CodegenUnit, CodegenUnits}; @@ -21,7 +21,7 @@ use cbmc::irep::goto_binary_serde::write_goto_binary_file; use cbmc::RoundingMode; use cbmc::{InternedString, MachineModel}; use kani_metadata::artifact::convert_type; -use kani_metadata::UnsupportedFeature; +use kani_metadata::{ContractedFunction, UnsupportedFeature}; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use kani_metadata::{AssignsContract, CompilerArtifactStub}; use rustc_codegen_ssa::back::archive::{ArArchiveBuilder, ArchiveBuilder, DEFAULT_OBJECT_READER}; @@ -47,7 +47,7 @@ use rustc_target::spec::PanicStrategy; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::{CrateDef, DefId}; use std::any::Any; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::ffi::OsString; use std::fmt::Write; use std::fs::File; @@ -342,7 +342,57 @@ impl CodegenBackend for GotocCodegenBackend { results.harnesses.push(metadata); } } - ReachabilityType::None => {} + ReachabilityType::None => { + // If the list subcommand is enabled, still don't generate any goto, + // but write the necessary KaniMetadata to a file + if queries.args().list_enabled { + let contract_harnesses: Vec = filter_crate_items(tcx, |_, instance| { + let attr = KaniAttributes::for_instance(tcx, instance); + attr.proof_for_contract().is_some_and(|res| res.is_ok()) + }); + + let mut function_to_harnesses: HashMap> = HashMap::new(); + + // Map each function under contract to a vector of its proof harnesses. + // Note that this logic will only find contracted functions *with* harnesses. + // We find functions without harnesses by iterating through `crate_fns` later. + for harness in contract_harnesses { + let attr = KaniAttributes::for_instance(tcx, harness); + let target_def_id = attr.interpret_for_contract_attribute().unwrap().1; + if let Some(harnesses) = function_to_harnesses.get_mut(&target_def_id) { + harnesses.push(harness.name()); + } else { + function_to_harnesses.insert(target_def_id, vec![harness.name()]); + } + } + + let mut units: CodegenUnits = CodegenUnits::new(&queries, tcx); + let transformer = BodyTransformation::new(&queries, tcx, &CodegenUnit::default()); + let mut gcx = GotocCtx::new(tcx, (*self.queries.lock().unwrap()).clone(), &results.machine_model, transformer); + + // Get all functions in the crate. + let crate_fns = filter_crate_items(tcx, |_, _| true); + + // For each function annotated with contracts, construct a ContractedFunction object for it and store it in `units`. + for instance in crate_fns { + let attributes = KaniAttributes::for_instance(tcx, instance); + if attributes.has_contract() { + let fn_def_id = rustc_internal::internal(tcx, instance.def.def_id()); + let harnesses = function_to_harnesses.get(&fn_def_id).map_or(vec![], |v| v.to_owned()); + let contracts_count = gcx.count_contracts(&instance, attributes.contract_attributes().unwrap()); + + units.contracted_functions.push(ContractedFunction { + pretty_name: instance.name(), + original_file: SourceLocation::new(instance.body().unwrap().span).filename, + harnesses, + contracts_count + }); + } + } + + units.write_metadata(&queries, tcx); + } + } ReachabilityType::PubFns => { let unit = CodegenUnit::default(); let transformer = BodyTransformation::new(&queries, tcx, &unit); @@ -652,6 +702,10 @@ impl GotoCodegenResults { proof_harnesses: proofs, unsupported_features, test_harnesses: tests, + // Just leave contracted_functions empty, since we don't use this field unless we're running the + // list subcommand and that uses CodegenUnits::generate_metadata instead. + // TODO: should we consolidate these generate_metadata functions? + contracted_functions: vec![] } } diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index d16e4d2b93d1..414db70f5deb 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -14,7 +14,7 @@ use crate::kani_middle::reachability::filter_crate_items; use crate::kani_middle::resolve::expect_resolve_fn; use crate::kani_middle::stubbing::{check_compatibility, harness_stub_map}; use crate::kani_queries::QueryDb; -use kani_metadata::{ArtifactType, AssignsContract, HarnessKind, HarnessMetadata, KaniMetadata}; +use kani_metadata::{ArtifactType, AssignsContract, ContractedFunction, HarnessKind, HarnessMetadata, KaniMetadata}; use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; use rustc_session::config::OutputType; @@ -46,6 +46,7 @@ pub struct CodegenUnits { units: Vec, harness_info: HashMap, crate_info: CrateInfo, + pub contracted_functions: Vec, } #[derive(Clone, Default, Debug)] @@ -57,27 +58,32 @@ pub struct CodegenUnit { impl CodegenUnits { pub fn new(queries: &QueryDb, tcx: TyCtxt) -> Self { let crate_info = CrateInfo { name: stable_mir::local_crate().name.as_str().into() }; - if queries.args().reachability_analysis == ReachabilityType::Harnesses { - let base_filepath = tcx.output_filenames(()).path(OutputType::Object); - let base_filename = base_filepath.as_path(); - let harnesses = filter_crate_items(tcx, |_, instance| is_proof_harness(tcx, instance)); - let all_harnesses = harnesses - .into_iter() - .map(|harness| { - let metadata = gen_proof_metadata(tcx, harness, &base_filename); - (harness, metadata) - }) - .collect::>(); + if queries.args().reachability_analysis != ReachabilityType::Harnesses && !queries.args().list_enabled { + // Leave other reachability type handling as is for now. + return CodegenUnits { units: vec![], harness_info: HashMap::default(), crate_info, contracted_functions: vec![] }; + } + + let base_filepath = tcx.output_filenames(()).path(OutputType::Object); + let base_filename = base_filepath.as_path(); + let harnesses = filter_crate_items(tcx, |_, instance| is_proof_harness(tcx, instance)); + let all_harnesses = harnesses + .into_iter() + .map(|harness| { + let metadata = gen_proof_metadata(tcx, harness, &base_filename); + (harness, metadata) + }) + .collect::>(); + let mut units = vec![]; + + if queries.args().reachability_analysis == ReachabilityType::Harnesses { // Even if no_stubs is empty we still need to store rustc metadata. - let units = group_by_stubs(tcx, &all_harnesses); + units = group_by_stubs(tcx, &all_harnesses); validate_units(tcx, &units); debug!(?units, "CodegenUnits::new"); - CodegenUnits { units, harness_info: all_harnesses, crate_info } - } else { - // Leave other reachability type handling as is for now. - CodegenUnits { units: vec![], harness_info: HashMap::default(), crate_info } } + + CodegenUnits { units, harness_info: all_harnesses, crate_info, contracted_functions: vec![] } } pub fn iter(&self) -> impl Iterator { @@ -111,6 +117,7 @@ impl CodegenUnits { proof_harnesses, unsupported_features: vec![], test_harnesses, + contracted_functions: self.contracted_functions.clone() } } } diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index 7485d2279ad6..8564c3f1ef78 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -20,6 +20,8 @@ once_cell = "1.19.0" serde = { version = "1", features = ["derive"] } serde_json = "1" clap = { version = "4.4.11", features = ["derive"] } +cli-table = "0.4.9" +colour = "2.1.0" glob = "0.3" toml = "0.8" regex = "1.6" diff --git a/kani-driver/src/args/list_args.rs b/kani-driver/src/args/list_args.rs new file mode 100644 index 000000000000..759c363ce3d7 --- /dev/null +++ b/kani-driver/src/args/list_args.rs @@ -0,0 +1,82 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Implements the subcommand handling of the list subcommand + +use std::path::PathBuf; + +use crate::args::ValidateArgs; +use clap::{error::ErrorKind, Error, Parser, ValueEnum}; +use kani_metadata::UnstableFeature; + +use super::VerificationArgs; + +/// List information relevant to verification +#[derive(Debug, Parser)] +pub struct CargoListArgs { + /// Output format + #[clap(default_value = "pretty")] + pub format: Format +} + +/// List information relevant to verification +#[derive(Debug, Parser)] +pub struct StandaloneListArgs { + /// Rust file to verify + #[arg(required = true)] + pub input: PathBuf, + + #[arg(long, hide = true)] + pub crate_name: Option, + + #[command(flatten)] + pub verify_opts: VerificationArgs, + + /// Output format + #[clap(default_value = "pretty")] + pub format: Format +} + +/// Message formats available for the subcommand. +#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, strum_macros::Display)] +#[strum(serialize_all = "kebab-case")] +pub enum Format { + /// Print diagnostic messages in a user friendly format. + Pretty, + /// Print diagnostic messages in JSON format. + Json, +} + +impl ValidateArgs for CargoListArgs { + fn validate(&self) -> Result<(), Error> { + todo!() + } +} + +impl ValidateArgs for StandaloneListArgs { + fn validate(&self) -> Result<(), Error> { + self.verify_opts.validate()?; + if !self + .verify_opts + .common_args + .unstable_features + .contains(UnstableFeature::List) + { + return Err(Error::raw( + ErrorKind::MissingRequiredArgument, + "The `list` subcommand is unstable and requires -Z list", + )); + } + + if !self.input.is_file() { + return Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: Input invalid. `{}` is not a regular file.", + self.input.display() + ), + )); + } + + Ok(()) + } +} diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index c3cfc113af64..9932fb47b4ed 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -5,6 +5,7 @@ pub mod assess_args; pub mod cargo; pub mod common; +pub mod list_args; pub mod playback_args; pub mod std_args; @@ -93,6 +94,8 @@ pub enum StandaloneSubcommand { Playback(Box), /// Verify the rust standard library. VerifyStd(Box), + /// Execute the list subcommand + List(Box), } #[derive(Debug, clap::Parser)] @@ -118,6 +121,9 @@ pub enum CargoKaniSubcommand { /// Execute concrete playback testcases of a local package. Playback(Box), + + /// List package/crate metadata relevant to verification + List(Box), } // Common arguments for invoking Kani for verification purpose. This gets put into KaniContext, @@ -288,6 +294,10 @@ pub struct VerificationArgs { #[command(flatten)] pub common_args: CommonArgs, + + /// Enable the list subcommand + #[arg(long = "list")] + pub list_enabled: bool } impl VerificationArgs { @@ -424,6 +434,7 @@ impl ValidateArgs for StandaloneArgs { match &self.command { Some(StandaloneSubcommand::VerifyStd(args)) => args.validate()?, + Some(StandaloneSubcommand::List(args)) => args.validate()?, // TODO: Invoke PlaybackArgs::validate() None | Some(StandaloneSubcommand::Playback(..)) => {} }; @@ -470,6 +481,7 @@ impl ValidateArgs for CargoKaniSubcommand { // Assess doesn't implement validation yet. CargoKaniSubcommand::Assess(_) => Ok(()), CargoKaniSubcommand::Playback(playback) => playback.validate(), + CargoKaniSubcommand::List(list) => list.validate(), } } } diff --git a/kani-driver/src/assess/mod.rs b/kani-driver/src/assess/mod.rs index f5b2ccb63035..cf991554f9b0 100644 --- a/kani-driver/src/assess/mod.rs +++ b/kani-driver/src/assess/mod.rs @@ -5,7 +5,7 @@ use self::metadata::{write_metadata, AssessMetadata}; use anyhow::{bail, Result}; use kani_metadata::KaniMetadata; -use crate::assess::table_builder::TableBuilder; +use crate::{assess::table_builder::TableBuilder, session::ReachabilityMode}; use crate::metadata::merge_kani_metadata; use crate::project; use crate::session::KaniSession; @@ -46,7 +46,7 @@ fn assess_project(mut session: KaniSession) -> Result { session.args.unwind = Some(session.args.default_unwind.unwrap_or(1)); session.args.tests = true; session.args.output_format = crate::args::OutputFormat::Terse; - session.codegen_tests = true; + session.reachability_mode = ReachabilityMode::Tests; if session.args.jobs.is_none() { // assess will default to fully parallel instead of single-threaded. // can be overridden with e.g. `cargo kani --enable-unstable -j 8 assess` diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index 868d1adce636..cdc68dc50236 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -52,7 +52,10 @@ impl KaniSession { outdir: &Path, ) -> Result<()> { let mut kani_args = self.kani_compiler_flags(); - kani_args.push(format!("--reachability={}", self.reachability_mode())); + kani_args.push(format!("--reachability={}", self.reachability_mode)); + if self.args.list_enabled { + kani_args.push("--list".to_string()); + } let lib_path = lib_folder().unwrap(); let mut rustc_args = self.kani_rustc_flags(LibConfig::new(lib_path)); @@ -92,7 +95,7 @@ impl KaniSession { /// Create a compiler option that represents the reachability mod. pub fn reachability_arg(&self) -> String { - to_rustc_arg(vec![format!("--reachability={}", self.reachability_mode())]) + to_rustc_arg(vec![format!("--reachability={}", self.reachability_mode)]) } /// These arguments are arguments passed to kani-compiler that are `kani` compiler specific. diff --git a/kani-driver/src/list.rs b/kani-driver/src/list.rs new file mode 100644 index 000000000000..f053ce986a15 --- /dev/null +++ b/kani-driver/src/list.rs @@ -0,0 +1,141 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Implements the list subcommand logic + +use std::collections::BTreeMap; + +use crate::{args::list_args::{CargoListArgs, Format, StandaloneListArgs}, metadata::from_json, project::{self, Artifact}, session::{KaniSession, ReachabilityMode}, util::crate_name, version::print_kani_version, InvocationType}; +use anyhow::Result; +use cli_table::{print_stdout, Cell, Style, Table}; +use colour::print_ln_bold; +use kani_metadata::{ArtifactType, ContractedFunction, HarnessKind, KaniMetadata}; + +fn set_session_args(session: &mut KaniSession) { + session.reachability_mode = ReachabilityMode::None; + session.args.list_enabled = true; +} + +fn process_metadata(metadata: Vec, format: Format) -> Result<()> { + let mut standard_harnesses: Vec = vec![]; + let mut contract_harnesses: Vec = vec![]; + let mut contracted_functions: Vec = vec![]; + let mut total_contracts = 0; + + for kani_meta in metadata { + for harness_meta in kani_meta.proof_harnesses { + match harness_meta.attributes.kind { + HarnessKind::Proof => standard_harnesses.push(harness_meta.pretty_name), + HarnessKind::ProofForContract { .. } => contract_harnesses.push(harness_meta.pretty_name), + HarnessKind::Test => {} + } + } + + for cf in &kani_meta.contracted_functions { + total_contracts += cf.contracts_count; + } + + contracted_functions.extend(kani_meta.contracted_functions.into_iter()); + } + + let totals = BTreeMap::from([ + ("Standard Harnesses", standard_harnesses.len()), + ("Contract Harnesses", contract_harnesses.len()), + ("Functions with Contracts", contracted_functions.len()), + ("Contracts", total_contracts) + ]); + + match format { + Format::Pretty => pretty_print(standard_harnesses, contract_harnesses, contracted_functions, totals), + Format::Json => json_print(standard_harnesses, contract_harnesses, contracted_functions, totals), + } +} + +pub fn list_cargo(mut session: KaniSession, args: CargoListArgs) -> Result<()> { + set_session_args(&mut session); + let project = project::cargo_project(&session, false)?; + // process_project(project, args.format) + todo!() +} + +pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { + let mut session = KaniSession::new(args.verify_opts)?; + if !session.args.common_args.quiet { + print_kani_version(InvocationType::Standalone); + } + set_session_args(&mut session); + + let crate_name = if let Some(name) = args.crate_name { name } else { crate_name(&args.input) }; + + // Ensure the directory exist and it's in its canonical form. + let outdir = if let Some(target_dir) = &session.args.target_dir { + std::fs::create_dir_all(target_dir)?; // This is a no-op if directory exists. + target_dir.canonicalize()? + } else { + args.input.canonicalize().unwrap().parent().unwrap().to_path_buf() + }; + + session.compile_single_rust_file(&args.input, &crate_name, &outdir)?; + + let mut path = outdir.join(crate_name.clone()); + let _ = path.set_extension(ArtifactType::Metadata); + let m = Artifact::try_new(&path, ArtifactType::Metadata)?; + + let metadata: KaniMetadata = from_json(&m)?; + + process_metadata(vec![metadata], args.format) +} + +pub fn list_std(session: KaniSession, args: CargoListArgs) -> Result<()> { + todo!() +} + +fn pretty_print( + mut standard_harnesses: Vec, + mut contract_harnesses: Vec, + mut contracted_functions: Vec, + totals: BTreeMap<&str, usize> +) -> Result<()> { + + standard_harnesses.sort(); + contract_harnesses.sort(); + contracted_functions.sort_by_key(|cf| cf.pretty_name.clone()); + + print_ln_bold!("\nContracts:"); + + print_stdout( + contracted_functions + .table() + .title(vec![ + "Function".cell().bold(true), + "# of Contracts".cell().bold(true), + "Contract Harnesses".cell().bold(true), + ]) + )?; + + + print_ln_bold!("\nContract Harnesses:"); + for harness in &contract_harnesses { + println!("- {}", harness); + } + + print_ln_bold!("\nStandard Harnesses:"); + for harness in &standard_harnesses { + println!("- {}", harness); + } + + print_ln_bold!("\nTotals:"); + for (key, total) in totals { + println!("{key}: {total}"); + } + + Ok(()) +} + +fn json_print( + standard_harnesses: Vec, + contract_harnesses: Vec, + contracted_functions: Vec, + totals: BTreeMap<&str, usize> +) -> Result<()> { + todo!() +} \ No newline at end of file diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index d3bd697d2ea4..9da872459621 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -12,6 +12,7 @@ use args_toml::join_args; use crate::args::StandaloneSubcommand; use crate::concrete_playback::playback::{playback_cargo, playback_standalone}; +use crate::list::list_standalone; use crate::project::Project; use crate::session::KaniSession; use crate::version::print_kani_version; @@ -32,6 +33,7 @@ mod cbmc_output_parser; mod cbmc_property_renderer; mod concrete_playback; mod coverage; +mod list; mod harness_runner; mod metadata; mod project; @@ -80,6 +82,9 @@ fn cargokani_main(input_args: Vec) -> Result<()> { Some(CargoKaniSubcommand::Playback(args)) => { return playback_cargo(*args); } + Some(CargoKaniSubcommand::List(args)) => { + return list::list_cargo(session, *args); + } None => {} } @@ -98,6 +103,7 @@ fn standalone_main() -> Result<()> { let (session, project) = match args.command { Some(StandaloneSubcommand::Playback(args)) => return playback_standalone(*args), + Some(StandaloneSubcommand::List(args)) => return list_standalone(*args), Some(StandaloneSubcommand::VerifyStd(args)) => { let session = KaniSession::new(args.verify_opts)?; if !session.args.common_args.quiet { @@ -106,7 +112,7 @@ fn standalone_main() -> Result<()> { let project = project::std_project(&args.std_path, &session)?; (session, project) - } + }, None => { let session = KaniSession::new(args.verify_opts)?; if !session.args.common_args.quiet { diff --git a/kani-driver/src/metadata.rs b/kani-driver/src/metadata.rs index 3f9cd8f2bf84..8170f0ab291b 100644 --- a/kani-driver/src/metadata.rs +++ b/kani-driver/src/metadata.rs @@ -96,6 +96,7 @@ pub fn merge_kani_metadata(files: Vec) -> KaniMetadata { proof_harnesses: vec![], unsupported_features: vec![], test_harnesses: vec![], + contracted_functions: vec![], }; for md in files { // Note that we're taking ownership of the original vec, and so we can move the data into the new data structure. @@ -104,6 +105,7 @@ pub fn merge_kani_metadata(files: Vec) -> KaniMetadata { // https://github.com/model-checking/kani/issues/1758 result.unsupported_features.extend(md.unsupported_features); result.test_harnesses.extend(md.test_harnesses); + result.contracted_functions.extend(md.contracted_functions); } result } diff --git a/kani-driver/src/session.rs b/kani-driver/src/session.rs index 03cfb108e324..9c75d8a79aa9 100644 --- a/kani-driver/src/session.rs +++ b/kani-driver/src/session.rs @@ -29,7 +29,7 @@ pub struct KaniSession { /// Include all publicly-visible symbols in the generated goto binary, not just those reachable from /// a proof harness. Useful when attempting to verify things that were not annotated with kani /// proof attributes. - pub codegen_tests: bool, + pub reachability_mode: ReachabilityMode, /// The location we found the 'kani_rustc' command pub kani_compiler: PathBuf, @@ -57,7 +57,7 @@ impl KaniSession { Ok(KaniSession { args, - codegen_tests: false, + reachability_mode: ReachabilityMode::ProofHarnesses, kani_compiler: install.kani_compiler()?, kani_lib_c: install.kani_lib_c()?, temporaries: Mutex::new(vec![]), @@ -78,12 +78,6 @@ impl KaniSession { let mut t = self.temporaries.lock().unwrap(); t.extend(temps.iter().map(|p| p.as_ref().to_owned())); } - - /// Determine which symbols Kani should codegen (i.e. by slicing away symbols - /// that are considered unreachable.) - pub fn reachability_mode(&self) -> ReachabilityMode { - if self.codegen_tests { ReachabilityMode::Tests } else { ReachabilityMode::ProofHarnesses } - } } #[derive(Debug, Copy, Clone, Display)] @@ -92,6 +86,7 @@ pub enum ReachabilityMode { #[strum(to_string = "harnesses")] ProofHarnesses, Tests, + None, } impl Drop for KaniSession { diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index 18eadc4095ed..638fb0b7ae09 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -16,6 +16,7 @@ cbmc = { path = "../cprover_bindings", package = "cprover_bindings" } strum = "0.26" strum_macros = "0.26" clap = { version = "4.4.11", features = ["derive"] } +cli-table = "0.4.9" [lints] workspace = true diff --git a/kani_metadata/src/lib.rs b/kani_metadata/src/lib.rs index fa5a8828b6be..35d3970cbb8e 100644 --- a/kani_metadata/src/lib.rs +++ b/kani_metadata/src/lib.rs @@ -3,8 +3,8 @@ extern crate clap; -use std::{collections::HashSet, path::PathBuf}; - +use cli_table::Table; +use std::{collections::HashSet, fmt::Display, path::PathBuf}; use serde::{Deserialize, Serialize}; pub use artifact::ArtifactType; @@ -32,6 +32,22 @@ pub struct KaniMetadata { pub unsupported_features: Vec, /// If crates are built in test-mode, then test harnesses will be recorded here. pub test_harnesses: Vec, + /// The functions with contracts in this crate + pub contracted_functions: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Table)] +pub struct ContractedFunction { + /// The fully qualified name the user gave to the function (i.e. includes the module path). + pub pretty_name: String, + /// The (currently full-) path to the file this function was declared within. + #[table(skip)] + pub original_file: String, + /// The number of contracts applied to this function + pub contracts_count: usize, + /// The pretty names of the proof harnesses (`#[kani::proof_for_contract]`) for this function + #[table(display_fn = "print_contract_harnesses")] + pub harnesses: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -55,3 +71,12 @@ pub struct Location { pub struct CompilerArtifactStub { pub metadata_path: PathBuf, } + +fn print_contract_harnesses(harnesses: &Vec) -> impl Display { + let joined = harnesses.join("\n"); + if joined.is_empty() { + "NONE".to_string() + } else { + joined + } +} diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index 11df998c820f..a5444b9da62b 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -94,6 +94,8 @@ pub enum UnstableFeature { UninitChecks, /// Enable an unstable option or subcommand. UnstableOptions, + /// The list subcommand [RFC 13](https://model-checking.github.io/kani/rfc/rfcs/0013-list.html) + List } impl UnstableFeature { From 67ed0735b16178166f4f6048c38c0bb8e644c6a7 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 12 Sep 2024 11:52:04 -0400 Subject: [PATCH 02/34] more concise pretty output --- kani-driver/src/list.rs | 80 +++++++++++++++++++--------------------- kani_metadata/src/lib.rs | 16 +------- 2 files changed, 40 insertions(+), 56 deletions(-) diff --git a/kani-driver/src/list.rs b/kani-driver/src/list.rs index f053ce986a15..983df6a658b8 100644 --- a/kani-driver/src/list.rs +++ b/kani-driver/src/list.rs @@ -2,11 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // Implements the list subcommand logic -use std::collections::BTreeMap; - use crate::{args::list_args::{CargoListArgs, Format, StandaloneListArgs}, metadata::from_json, project::{self, Artifact}, session::{KaniSession, ReachabilityMode}, util::crate_name, version::print_kani_version, InvocationType}; use anyhow::Result; -use cli_table::{print_stdout, Cell, Style, Table}; +use cli_table::{print_stdout, Cell, CellStruct, Style, Table}; use colour::print_ln_bold; use kani_metadata::{ArtifactType, ContractedFunction, HarnessKind, KaniMetadata}; @@ -17,15 +15,15 @@ fn set_session_args(session: &mut KaniSession) { fn process_metadata(metadata: Vec, format: Format) -> Result<()> { let mut standard_harnesses: Vec = vec![]; - let mut contract_harnesses: Vec = vec![]; let mut contracted_functions: Vec = vec![]; let mut total_contracts = 0; + let mut total_contract_harnesses = 0; for kani_meta in metadata { for harness_meta in kani_meta.proof_harnesses { match harness_meta.attributes.kind { HarnessKind::Proof => standard_harnesses.push(harness_meta.pretty_name), - HarnessKind::ProofForContract { .. } => contract_harnesses.push(harness_meta.pretty_name), + HarnessKind::ProofForContract { .. } => total_contract_harnesses += 1, HarnessKind::Test => {} } } @@ -37,16 +35,9 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { contracted_functions.extend(kani_meta.contracted_functions.into_iter()); } - let totals = BTreeMap::from([ - ("Standard Harnesses", standard_harnesses.len()), - ("Contract Harnesses", contract_harnesses.len()), - ("Functions with Contracts", contracted_functions.len()), - ("Contracts", total_contracts) - ]); - match format { - Format::Pretty => pretty_print(standard_harnesses, contract_harnesses, contracted_functions, totals), - Format::Json => json_print(standard_harnesses, contract_harnesses, contracted_functions, totals), + Format::Pretty => pretty_print(standard_harnesses, contracted_functions, total_contract_harnesses, total_contracts), + Format::Json => json_print(standard_harnesses, contracted_functions, total_contract_harnesses, total_contracts), } } @@ -91,51 +82,56 @@ pub fn list_std(session: KaniSession, args: CargoListArgs) -> Result<()> { fn pretty_print( mut standard_harnesses: Vec, - mut contract_harnesses: Vec, mut contracted_functions: Vec, - totals: BTreeMap<&str, usize> + total_contract_harnesses: usize, + total_contracts: usize, ) -> Result<()> { + let total_contracted_functions = contracted_functions.len(); + // Print in alphabetical order standard_harnesses.sort(); - contract_harnesses.sort(); contracted_functions.sort_by_key(|cf| cf.pretty_name.clone()); - print_ln_bold!("\nContracts:"); - - print_stdout( - contracted_functions - .table() - .title(vec![ - "Function".cell().bold(true), - "# of Contracts".cell().bold(true), - "Contract Harnesses".cell().bold(true), - ]) - )?; + fn format_contract_harnesses(harnesses: &mut Vec) -> String { + harnesses.sort(); + let joined = harnesses.join("\n"); + if joined.is_empty() { + "NONE".to_string() + } else { + joined + } + } + let mut contracts_table: Vec> = vec![]; - print_ln_bold!("\nContract Harnesses:"); - for harness in &contract_harnesses { - println!("- {}", harness); + for mut cf in contracted_functions { + contracts_table.push(vec!["".cell(), cf.pretty_name.cell(), cf.contracts_count.cell(), format_contract_harnesses(&mut cf.harnesses).cell()]); } - print_ln_bold!("\nStandard Harnesses:"); - for harness in &standard_harnesses { - println!("- {}", harness); - } + contracts_table.push(vec!["Total".cell().bold(true), total_contracted_functions.cell(), total_contracts.cell(), total_contract_harnesses.cell()]); + + print_ln_bold!("\nContracts:"); + print_stdout(contracts_table.table().title(vec![ + "".cell(), + "Function Under Contract".cell().bold(true), + "# of Contracts".cell().bold(true), + "Contract Harnesses".cell().bold(true), + ]) + )?; - print_ln_bold!("\nTotals:"); - for (key, total) in totals { - println!("{key}: {total}"); + print_ln_bold!("\nStandard Harnesses:"); + for (i, harness) in standard_harnesses.iter().enumerate() { + println!("{}. {harness}", i+1); } Ok(()) } fn json_print( - standard_harnesses: Vec, - contract_harnesses: Vec, - contracted_functions: Vec, - totals: BTreeMap<&str, usize> + mut standard_harnesses: Vec, + mut contracted_functions: Vec, + total_contract_harnesses: usize, + total_contracts: usize, ) -> Result<()> { todo!() } \ No newline at end of file diff --git a/kani_metadata/src/lib.rs b/kani_metadata/src/lib.rs index 35d3970cbb8e..1ebeca5d843e 100644 --- a/kani_metadata/src/lib.rs +++ b/kani_metadata/src/lib.rs @@ -3,8 +3,7 @@ extern crate clap; -use cli_table::Table; -use std::{collections::HashSet, fmt::Display, path::PathBuf}; +use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; pub use artifact::ArtifactType; @@ -36,17 +35,15 @@ pub struct KaniMetadata { pub contracted_functions: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, Table)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractedFunction { /// The fully qualified name the user gave to the function (i.e. includes the module path). pub pretty_name: String, /// The (currently full-) path to the file this function was declared within. - #[table(skip)] pub original_file: String, /// The number of contracts applied to this function pub contracts_count: usize, /// The pretty names of the proof harnesses (`#[kani::proof_for_contract]`) for this function - #[table(display_fn = "print_contract_harnesses")] pub harnesses: Vec, } @@ -71,12 +68,3 @@ pub struct Location { pub struct CompilerArtifactStub { pub metadata_path: PathBuf, } - -fn print_contract_harnesses(harnesses: &Vec) -> impl Display { - let joined = harnesses.join("\n"); - if joined.is_empty() { - "NONE".to_string() - } else { - joined - } -} From 4ace776df3b5dd71e8796103296404f450879cea Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 12 Sep 2024 13:08:51 -0400 Subject: [PATCH 03/34] json output --- .../compiler_interface.rs | 6 +- kani-driver/Cargo.toml | 2 +- kani-driver/src/args/list_args.rs | 2 +- kani-driver/src/list.rs | 68 ++++++++++++++----- kani-driver/src/version.rs | 2 +- kani_metadata/src/lib.rs | 6 +- 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 0de3b80adac9..bbec9bc4f50a 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -382,10 +382,10 @@ impl CodegenBackend for GotocCodegenBackend { let contracts_count = gcx.count_contracts(&instance, attributes.contract_attributes().unwrap()); units.contracted_functions.push(ContractedFunction { - pretty_name: instance.name(), - original_file: SourceLocation::new(instance.body().unwrap().span).filename, + function: instance.name(), + file: SourceLocation::new(instance.body().unwrap().span).filename, harnesses, - contracts_count + total_contracts: contracts_count }); } } diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index 8564c3f1ef78..85a93aff5940 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -18,7 +18,7 @@ anyhow = "1" console = "0.15.1" once_cell = "1.19.0" serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde_json = { version = "1", features = ["preserve_order"] } clap = { version = "4.4.11", features = ["derive"] } cli-table = "0.4.9" colour = "2.1.0" diff --git a/kani-driver/src/args/list_args.rs b/kani-driver/src/args/list_args.rs index 759c363ce3d7..6cef9124dc48 100644 --- a/kani-driver/src/args/list_args.rs +++ b/kani-driver/src/args/list_args.rs @@ -32,7 +32,7 @@ pub struct StandaloneListArgs { pub verify_opts: VerificationArgs, /// Output format - #[clap(default_value = "pretty")] + #[clap(long, default_value = "pretty")] pub format: Format } diff --git a/kani-driver/src/list.rs b/kani-driver/src/list.rs index 983df6a658b8..45144ce5589b 100644 --- a/kani-driver/src/list.rs +++ b/kani-driver/src/list.rs @@ -2,11 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // Implements the list subcommand logic -use crate::{args::list_args::{CargoListArgs, Format, StandaloneListArgs}, metadata::from_json, project::{self, Artifact}, session::{KaniSession, ReachabilityMode}, util::crate_name, version::print_kani_version, InvocationType}; +use std::{fs::File, io::BufWriter}; + +use crate::{args::list_args::{CargoListArgs, Format, StandaloneListArgs}, metadata::from_json, project::{self, Artifact}, session::{KaniSession, ReachabilityMode}, util::crate_name, version::{print_kani_version, KANI_VERSION}, InvocationType}; use anyhow::Result; use cli_table::{print_stdout, Cell, CellStruct, Style, Table}; use colour::print_ln_bold; use kani_metadata::{ArtifactType, ContractedFunction, HarnessKind, KaniMetadata}; +use serde_json::json; + +// Represents the version of our JSON file format. +// Increment this version (according to semantic versioning rules) whenever the JSON output format changes. +const FILE_VERSION: &str = "0.1"; fn set_session_args(session: &mut KaniSession) { session.reachability_mode = ReachabilityMode::None; @@ -15,29 +22,34 @@ fn set_session_args(session: &mut KaniSession) { fn process_metadata(metadata: Vec, format: Format) -> Result<()> { let mut standard_harnesses: Vec = vec![]; + let mut contract_harnesses: Vec = vec![]; let mut contracted_functions: Vec = vec![]; let mut total_contracts = 0; - let mut total_contract_harnesses = 0; for kani_meta in metadata { for harness_meta in kani_meta.proof_harnesses { match harness_meta.attributes.kind { HarnessKind::Proof => standard_harnesses.push(harness_meta.pretty_name), - HarnessKind::ProofForContract { .. } => total_contract_harnesses += 1, + HarnessKind::ProofForContract { .. } => contract_harnesses.push(harness_meta.pretty_name), HarnessKind::Test => {} } } for cf in &kani_meta.contracted_functions { - total_contracts += cf.contracts_count; + total_contracts += cf.total_contracts; } contracted_functions.extend(kani_meta.contracted_functions.into_iter()); } + // Print in alphabetical order + standard_harnesses.sort(); + contract_harnesses.sort(); + contracted_functions.sort_by_key(|cf| cf.function.clone()); + match format { - Format::Pretty => pretty_print(standard_harnesses, contracted_functions, total_contract_harnesses, total_contracts), - Format::Json => json_print(standard_harnesses, contracted_functions, total_contract_harnesses, total_contracts), + Format::Pretty => pretty_print(standard_harnesses, contracted_functions, contract_harnesses.len(), total_contracts), + Format::Json => json(standard_harnesses, contract_harnesses, contracted_functions, total_contracts), } } @@ -67,6 +79,8 @@ pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { session.compile_single_rust_file(&args.input, &crate_name, &outdir)?; + // TODO delete intermediate files + let mut path = outdir.join(crate_name.clone()); let _ = path.set_extension(ArtifactType::Metadata); let m = Artifact::try_new(&path, ArtifactType::Metadata)?; @@ -81,17 +95,13 @@ pub fn list_std(session: KaniSession, args: CargoListArgs) -> Result<()> { } fn pretty_print( - mut standard_harnesses: Vec, - mut contracted_functions: Vec, + standard_harnesses: Vec, + contracted_functions: Vec, total_contract_harnesses: usize, total_contracts: usize, ) -> Result<()> { let total_contracted_functions = contracted_functions.len(); - // Print in alphabetical order - standard_harnesses.sort(); - contracted_functions.sort_by_key(|cf| cf.pretty_name.clone()); - fn format_contract_harnesses(harnesses: &mut Vec) -> String { harnesses.sort(); let joined = harnesses.join("\n"); @@ -105,7 +115,7 @@ fn pretty_print( let mut contracts_table: Vec> = vec![]; for mut cf in contracted_functions { - contracts_table.push(vec!["".cell(), cf.pretty_name.cell(), cf.contracts_count.cell(), format_contract_harnesses(&mut cf.harnesses).cell()]); + contracts_table.push(vec!["".cell(), cf.function.cell(), cf.total_contracts.cell(), format_contract_harnesses(&mut cf.harnesses).cell()]); } contracts_table.push(vec!["Total".cell().bold(true), total_contracted_functions.cell(), total_contracts.cell(), total_contract_harnesses.cell()]); @@ -127,11 +137,33 @@ fn pretty_print( Ok(()) } -fn json_print( - mut standard_harnesses: Vec, - mut contracted_functions: Vec, - total_contract_harnesses: usize, +fn json( + standard_harnesses: Vec, + contract_harnesses: Vec, + contracted_functions: Vec, total_contracts: usize, ) -> Result<()> { - todo!() + // FIXME + let filename = "list.json"; + + let out_file = File::create(filename).unwrap(); + let writer = BufWriter::new(out_file); + + let json_obj = json!({ + "kani-version": KANI_VERSION, + "file-version": FILE_VERSION, + "standard-harnesses": &standard_harnesses, + "contract-harnesses": &contract_harnesses, + "contracts": &contracted_functions, + "totals": { + "standard-harnesses": standard_harnesses.len(), + "contract-harnesses": contract_harnesses.len(), + "functions-under-contract": contracted_functions.len(), + "contracts": total_contracts, + } + }); + + serde_json::to_writer_pretty(writer, &json_obj)?; + + Ok(()) } \ No newline at end of file diff --git a/kani-driver/src/version.rs b/kani-driver/src/version.rs index 95d98b0d6d3e..e1b995c3cd53 100644 --- a/kani-driver/src/version.rs +++ b/kani-driver/src/version.rs @@ -7,7 +7,7 @@ const KANI_RUST_VERIFIER: &str = "Kani Rust Verifier"; /// We assume this is the same as the `kani-verifier` version, but we should /// make sure it's enforced through CI: /// -const KANI_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub(crate) const KANI_VERSION: &str = env!("CARGO_PKG_VERSION"); /// Print Kani version. At present, this is only release version information. pub(crate) fn print_kani_version(invocation_type: InvocationType) { diff --git a/kani_metadata/src/lib.rs b/kani_metadata/src/lib.rs index 1ebeca5d843e..f377bec13901 100644 --- a/kani_metadata/src/lib.rs +++ b/kani_metadata/src/lib.rs @@ -38,11 +38,11 @@ pub struct KaniMetadata { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractedFunction { /// The fully qualified name the user gave to the function (i.e. includes the module path). - pub pretty_name: String, + pub function: String, /// The (currently full-) path to the file this function was declared within. - pub original_file: String, + pub file: String, /// The number of contracts applied to this function - pub contracts_count: usize, + pub total_contracts: usize, /// The pretty names of the proof harnesses (`#[kani::proof_for_contract]`) for this function pub harnesses: Vec, } From b903473abf1f36353b3deaff9582c66bc4c830b7 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 12 Sep 2024 13:37:04 -0400 Subject: [PATCH 04/34] use standalone project instead --- kani-driver/src/list.rs | 28 ++++------------------------ kani-driver/src/project.rs | 6 ++++++ 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/kani-driver/src/list.rs b/kani-driver/src/list.rs index 45144ce5589b..4cff8ff8b204 100644 --- a/kani-driver/src/list.rs +++ b/kani-driver/src/list.rs @@ -4,11 +4,11 @@ use std::{fs::File, io::BufWriter}; -use crate::{args::list_args::{CargoListArgs, Format, StandaloneListArgs}, metadata::from_json, project::{self, Artifact}, session::{KaniSession, ReachabilityMode}, util::crate_name, version::{print_kani_version, KANI_VERSION}, InvocationType}; +use crate::{args::list_args::{CargoListArgs, Format, StandaloneListArgs}, project::{self, standalone_project}, session::{KaniSession, ReachabilityMode}, version::{print_kani_version, KANI_VERSION}, InvocationType}; use anyhow::Result; use cli_table::{print_stdout, Cell, CellStruct, Style, Table}; use colour::print_ln_bold; -use kani_metadata::{ArtifactType, ContractedFunction, HarnessKind, KaniMetadata}; +use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; use serde_json::json; // Represents the version of our JSON file format. @@ -66,28 +66,9 @@ pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { print_kani_version(InvocationType::Standalone); } set_session_args(&mut session); - - let crate_name = if let Some(name) = args.crate_name { name } else { crate_name(&args.input) }; - - // Ensure the directory exist and it's in its canonical form. - let outdir = if let Some(target_dir) = &session.args.target_dir { - std::fs::create_dir_all(target_dir)?; // This is a no-op if directory exists. - target_dir.canonicalize()? - } else { - args.input.canonicalize().unwrap().parent().unwrap().to_path_buf() - }; - - session.compile_single_rust_file(&args.input, &crate_name, &outdir)?; - - // TODO delete intermediate files - - let mut path = outdir.join(crate_name.clone()); - let _ = path.set_extension(ArtifactType::Metadata); - let m = Artifact::try_new(&path, ArtifactType::Metadata)?; - - let metadata: KaniMetadata = from_json(&m)?; + let project = standalone_project(&args.input, args.crate_name, &session)?; - process_metadata(vec![metadata], args.format) + process_metadata(project.metadata, args.format) } pub fn list_std(session: KaniSession, args: CargoListArgs) -> Result<()> { @@ -143,7 +124,6 @@ fn json( contracted_functions: Vec, total_contracts: usize, ) -> Result<()> { - // FIXME let filename = "list.json"; let out_file = File::create(filename).unwrap(); diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index e0d2d2edd139..c69cc2006358 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -90,6 +90,12 @@ impl Project { cargo_metadata: Option, failed_targets: Option>, ) -> Result { + + // For the list subcommand, we do not generate any goto, so skip extending the artifacts + if session.args.list_enabled { + return Ok(Project { outdir, input, metadata, artifacts: vec![], cargo_metadata, failed_targets }); + } + // For each harness (test or proof) from each metadata, read the path for the goto // SymTabGoto file. Use that path to find all the other artifacts. let mut artifacts = vec![]; From b412d0613bb7c86dde8cead38dd565585a8c9d0d Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 12 Sep 2024 14:06:37 -0400 Subject: [PATCH 05/34] clippy --- .../codegen_cprover_gotoc/codegen/contract.rs | 21 +++++-- .../compiler_interface.rs | 59 +++++++++++------- .../src/kani_middle/codegen_units.rs | 26 ++++++-- kani-driver/src/args/list_args.rs | 11 +--- kani-driver/src/args/mod.rs | 2 +- kani-driver/src/assess/mod.rs | 2 +- kani-driver/src/list.rs | 62 ++++++++++++------- kani-driver/src/main.rs | 4 +- kani-driver/src/project.rs | 10 ++- kani_metadata/src/lib.rs | 2 +- kani_metadata/src/unstable.rs | 2 +- 11 files changed, 130 insertions(+), 71 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index d1985bc86f1f..5981ed2d55ca 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -1,7 +1,9 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::codegen_cprover_gotoc::{codegen::ty_stable::pointee_type_stable, GotocCtx}; -use crate::kani_middle::attributes::{ContractAttributes, KaniAttributes, matches_diagnostic as matches_function}; +use crate::kani_middle::attributes::{ + matches_diagnostic as matches_function, ContractAttributes, KaniAttributes, +}; use cbmc::goto_program::FunctionContract; use cbmc::goto_program::{Expr, Lambda, Location, Type}; use kani_metadata::AssignsContract; @@ -109,7 +111,8 @@ impl<'tcx> GotocCtx<'tcx> { let contract_attrs = KaniAttributes::for_instance(self.tcx, instance).contract_attributes()?; let check_instance = if contract_attrs.has_recursion { - let recursion_check = self.find_closure(instance, contract_attrs.recursion_check.as_str())?; + let recursion_check = + self.find_closure(instance, contract_attrs.recursion_check.as_str())?; self.find_closure(recursion_check, contract_attrs.checked_with.as_str())? } else { self.find_closure(instance, contract_attrs.checked_with.as_str())? @@ -245,10 +248,15 @@ impl<'tcx> GotocCtx<'tcx> { } /// Count the number of contracts applied to `contracted_function` - pub fn count_contracts(&mut self, contracted_function: &Instance, contract_attrs: ContractAttributes) -> usize { + pub fn count_contracts( + &mut self, + contracted_function: &Instance, + contract_attrs: ContractAttributes, + ) -> usize { // Extract the body of the check_closure, which will contain kani::assume() calls for each precondition // and kani::assert() calls for each postcondition. - let check_closure = self.find_closure(*contracted_function, contract_attrs.checked_with.as_str()).unwrap(); + let check_closure = + self.find_closure(*contracted_function, contract_attrs.checked_with.as_str()).unwrap(); let body = check_closure.body().unwrap(); let mut count = 0; @@ -259,8 +267,9 @@ impl<'tcx> GotocCtx<'tcx> { 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(self.tcx, instance.def, "KaniAssume") - || matches_function(self.tcx, instance.def, "KaniAssert") { + if matches_function(self.tcx, instance.def, "KaniAssume") + || matches_function(self.tcx, instance.def, "KaniAssert") + { count += 1; } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index bbec9bc4f50a..94f8b755fd2e 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -5,7 +5,6 @@ use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; -use crate::kani_middle::{analysis, SourceLocation}; use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; use crate::kani_middle::check_reachable_items; use crate::kani_middle::codegen_units::{CodegenUnit, CodegenUnits}; @@ -15,15 +14,16 @@ use crate::kani_middle::reachability::{ collect_reachable_items, filter_const_crate_items, filter_crate_items, }; use crate::kani_middle::transform::{BodyTransformation, GlobalPasses}; +use crate::kani_middle::{analysis, SourceLocation}; use crate::kani_queries::QueryDb; use cbmc::goto_program::Location; use cbmc::irep::goto_binary_serde::write_goto_binary_file; use cbmc::RoundingMode; use cbmc::{InternedString, MachineModel}; use kani_metadata::artifact::convert_type; -use kani_metadata::{ContractedFunction, UnsupportedFeature}; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use kani_metadata::{AssignsContract, CompilerArtifactStub}; +use kani_metadata::{ContractedFunction, UnsupportedFeature}; use rustc_codegen_ssa::back::archive::{ArArchiveBuilder, ArchiveBuilder, DEFAULT_OBJECT_READER}; use rustc_codegen_ssa::back::metadata::create_wrapper_file; use rustc_codegen_ssa::traits::CodegenBackend; @@ -346,13 +346,15 @@ impl CodegenBackend for GotocCodegenBackend { // If the list subcommand is enabled, still don't generate any goto, // but write the necessary KaniMetadata to a file if queries.args().list_enabled { - let contract_harnesses: Vec = filter_crate_items(tcx, |_, instance| { - let attr = KaniAttributes::for_instance(tcx, instance); - attr.proof_for_contract().is_some_and(|res| res.is_ok()) - }); - - let mut function_to_harnesses: HashMap> = HashMap::new(); - + let contract_harnesses: Vec = + filter_crate_items(tcx, |_, instance| { + let attr = KaniAttributes::for_instance(tcx, instance); + attr.proof_for_contract().is_some_and(|res| res.is_ok()) + }); + + let mut function_to_harnesses: HashMap> = + HashMap::new(); + // Map each function under contract to a vector of its proof harnesses. // Note that this logic will only find contracted functions *with* harnesses. // We find functions without harnesses by iterating through `crate_fns` later. @@ -365,31 +367,44 @@ impl CodegenBackend for GotocCodegenBackend { function_to_harnesses.insert(target_def_id, vec![harness.name()]); } } - + let mut units: CodegenUnits = CodegenUnits::new(&queries, tcx); - let transformer = BodyTransformation::new(&queries, tcx, &CodegenUnit::default()); - let mut gcx = GotocCtx::new(tcx, (*self.queries.lock().unwrap()).clone(), &results.machine_model, transformer); - + let transformer = + BodyTransformation::new(&queries, tcx, &CodegenUnit::default()); + let mut gcx = GotocCtx::new( + tcx, + (*self.queries.lock().unwrap()).clone(), + &results.machine_model, + transformer, + ); + // Get all functions in the crate. let crate_fns = filter_crate_items(tcx, |_, _| true); - + // For each function annotated with contracts, construct a ContractedFunction object for it and store it in `units`. for instance in crate_fns { let attributes = KaniAttributes::for_instance(tcx, instance); if attributes.has_contract() { - let fn_def_id = rustc_internal::internal(tcx, instance.def.def_id()); - let harnesses = function_to_harnesses.get(&fn_def_id).map_or(vec![], |v| v.to_owned()); - let contracts_count = gcx.count_contracts(&instance, attributes.contract_attributes().unwrap()); - + let fn_def_id = + rustc_internal::internal(tcx, instance.def.def_id()); + let harnesses = function_to_harnesses + .get(&fn_def_id) + .map_or(vec![], |v| v.to_owned()); + let contracts_count = gcx.count_contracts( + &instance, + attributes.contract_attributes().unwrap(), + ); + units.contracted_functions.push(ContractedFunction { function: instance.name(), - file: SourceLocation::new(instance.body().unwrap().span).filename, + file: SourceLocation::new(instance.body().unwrap().span) + .filename, harnesses, - total_contracts: contracts_count + total_contracts: contracts_count, }); } } - + units.write_metadata(&queries, tcx); } } @@ -705,7 +720,7 @@ impl GotoCodegenResults { // Just leave contracted_functions empty, since we don't use this field unless we're running the // list subcommand and that uses CodegenUnits::generate_metadata instead. // TODO: should we consolidate these generate_metadata functions? - contracted_functions: vec![] + contracted_functions: vec![], } } diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index 414db70f5deb..10646c21e421 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -14,7 +14,9 @@ use crate::kani_middle::reachability::filter_crate_items; use crate::kani_middle::resolve::expect_resolve_fn; use crate::kani_middle::stubbing::{check_compatibility, harness_stub_map}; use crate::kani_queries::QueryDb; -use kani_metadata::{ArtifactType, AssignsContract, ContractedFunction, HarnessKind, HarnessMetadata, KaniMetadata}; +use kani_metadata::{ + ArtifactType, AssignsContract, ContractedFunction, HarnessKind, HarnessMetadata, KaniMetadata, +}; use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; use rustc_session::config::OutputType; @@ -59,9 +61,16 @@ impl CodegenUnits { pub fn new(queries: &QueryDb, tcx: TyCtxt) -> Self { let crate_info = CrateInfo { name: stable_mir::local_crate().name.as_str().into() }; - if queries.args().reachability_analysis != ReachabilityType::Harnesses && !queries.args().list_enabled { + if queries.args().reachability_analysis != ReachabilityType::Harnesses + && !queries.args().list_enabled + { // Leave other reachability type handling as is for now. - return CodegenUnits { units: vec![], harness_info: HashMap::default(), crate_info, contracted_functions: vec![] }; + return CodegenUnits { + units: vec![], + harness_info: HashMap::default(), + crate_info, + contracted_functions: vec![], + }; } let base_filepath = tcx.output_filenames(()).path(OutputType::Object); @@ -82,8 +91,13 @@ impl CodegenUnits { validate_units(tcx, &units); debug!(?units, "CodegenUnits::new"); } - - CodegenUnits { units, harness_info: all_harnesses, crate_info, contracted_functions: vec![] } + + CodegenUnits { + units, + harness_info: all_harnesses, + crate_info, + contracted_functions: vec![], + } } pub fn iter(&self) -> impl Iterator { @@ -117,7 +131,7 @@ impl CodegenUnits { proof_harnesses, unsupported_features: vec![], test_harnesses, - contracted_functions: self.contracted_functions.clone() + contracted_functions: self.contracted_functions.clone(), } } } diff --git a/kani-driver/src/args/list_args.rs b/kani-driver/src/args/list_args.rs index 6cef9124dc48..e24c232d3dde 100644 --- a/kani-driver/src/args/list_args.rs +++ b/kani-driver/src/args/list_args.rs @@ -15,7 +15,7 @@ use super::VerificationArgs; pub struct CargoListArgs { /// Output format #[clap(default_value = "pretty")] - pub format: Format + pub format: Format, } /// List information relevant to verification @@ -33,7 +33,7 @@ pub struct StandaloneListArgs { /// Output format #[clap(long, default_value = "pretty")] - pub format: Format + pub format: Format, } /// Message formats available for the subcommand. @@ -55,12 +55,7 @@ impl ValidateArgs for CargoListArgs { impl ValidateArgs for StandaloneListArgs { fn validate(&self) -> Result<(), Error> { self.verify_opts.validate()?; - if !self - .verify_opts - .common_args - .unstable_features - .contains(UnstableFeature::List) - { + if !self.verify_opts.common_args.unstable_features.contains(UnstableFeature::List) { return Err(Error::raw( ErrorKind::MissingRequiredArgument, "The `list` subcommand is unstable and requires -Z list", diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 9932fb47b4ed..9f883ed6b6cf 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -297,7 +297,7 @@ pub struct VerificationArgs { /// Enable the list subcommand #[arg(long = "list")] - pub list_enabled: bool + pub list_enabled: bool, } impl VerificationArgs { diff --git a/kani-driver/src/assess/mod.rs b/kani-driver/src/assess/mod.rs index cf991554f9b0..daa347246d09 100644 --- a/kani-driver/src/assess/mod.rs +++ b/kani-driver/src/assess/mod.rs @@ -5,10 +5,10 @@ use self::metadata::{write_metadata, AssessMetadata}; use anyhow::{bail, Result}; use kani_metadata::KaniMetadata; -use crate::{assess::table_builder::TableBuilder, session::ReachabilityMode}; use crate::metadata::merge_kani_metadata; use crate::project; use crate::session::KaniSession; +use crate::{assess::table_builder::TableBuilder, session::ReachabilityMode}; pub use crate::args::{AssessArgs, AssessSubcommand}; diff --git a/kani-driver/src/list.rs b/kani-driver/src/list.rs index 4cff8ff8b204..13e42c42106a 100644 --- a/kani-driver/src/list.rs +++ b/kani-driver/src/list.rs @@ -4,7 +4,13 @@ use std::{fs::File, io::BufWriter}; -use crate::{args::list_args::{CargoListArgs, Format, StandaloneListArgs}, project::{self, standalone_project}, session::{KaniSession, ReachabilityMode}, version::{print_kani_version, KANI_VERSION}, InvocationType}; +use crate::{ + args::list_args::{CargoListArgs, Format, StandaloneListArgs}, + project::{self, standalone_project}, + session::{KaniSession, ReachabilityMode}, + version::{print_kani_version, KANI_VERSION}, + InvocationType, +}; use anyhow::Result; use cli_table::{print_stdout, Cell, CellStruct, Style, Table}; use colour::print_ln_bold; @@ -25,12 +31,14 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { let mut contract_harnesses: Vec = vec![]; let mut contracted_functions: Vec = vec![]; let mut total_contracts = 0; - + for kani_meta in metadata { for harness_meta in kani_meta.proof_harnesses { match harness_meta.attributes.kind { HarnessKind::Proof => standard_harnesses.push(harness_meta.pretty_name), - HarnessKind::ProofForContract { .. } => contract_harnesses.push(harness_meta.pretty_name), + HarnessKind::ProofForContract { .. } => { + contract_harnesses.push(harness_meta.pretty_name) + } HarnessKind::Test => {} } } @@ -48,14 +56,21 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { contracted_functions.sort_by_key(|cf| cf.function.clone()); match format { - Format::Pretty => pretty_print(standard_harnesses, contracted_functions, contract_harnesses.len(), total_contracts), - Format::Json => json(standard_harnesses, contract_harnesses, contracted_functions, total_contracts), + Format::Pretty => pretty_print( + standard_harnesses, + contracted_functions, + contract_harnesses.len(), + total_contracts, + ), + Format::Json => { + json(standard_harnesses, contract_harnesses, contracted_functions, total_contracts) + } } } -pub fn list_cargo(mut session: KaniSession, args: CargoListArgs) -> Result<()> { +pub fn list_cargo(mut session: KaniSession, _args: CargoListArgs) -> Result<()> { set_session_args(&mut session); - let project = project::cargo_project(&session, false)?; + let _project = project::cargo_project(&session, false)?; // process_project(project, args.format) todo!() } @@ -71,7 +86,7 @@ pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { process_metadata(project.metadata, args.format) } -pub fn list_std(session: KaniSession, args: CargoListArgs) -> Result<()> { +pub fn _list_std(_session: KaniSession, _args: CargoListArgs) -> Result<()> { todo!() } @@ -83,23 +98,29 @@ fn pretty_print( ) -> Result<()> { let total_contracted_functions = contracted_functions.len(); - fn format_contract_harnesses(harnesses: &mut Vec) -> String { + fn format_contract_harnesses(harnesses: &mut [String]) -> String { harnesses.sort(); let joined = harnesses.join("\n"); - if joined.is_empty() { - "NONE".to_string() - } else { - joined - } + if joined.is_empty() { "NONE".to_string() } else { joined } } let mut contracts_table: Vec> = vec![]; for mut cf in contracted_functions { - contracts_table.push(vec!["".cell(), cf.function.cell(), cf.total_contracts.cell(), format_contract_harnesses(&mut cf.harnesses).cell()]); + contracts_table.push(vec![ + "".cell(), + cf.function.cell(), + cf.total_contracts.cell(), + format_contract_harnesses(&mut cf.harnesses).cell(), + ]); } - contracts_table.push(vec!["Total".cell().bold(true), total_contracted_functions.cell(), total_contracts.cell(), total_contract_harnesses.cell()]); + contracts_table.push(vec![ + "Total".cell().bold(true), + total_contracted_functions.cell(), + total_contracts.cell(), + total_contract_harnesses.cell(), + ]); print_ln_bold!("\nContracts:"); print_stdout(contracts_table.table().title(vec![ @@ -107,12 +128,11 @@ fn pretty_print( "Function Under Contract".cell().bold(true), "# of Contracts".cell().bold(true), "Contract Harnesses".cell().bold(true), - ]) - )?; + ]))?; print_ln_bold!("\nStandard Harnesses:"); for (i, harness) in standard_harnesses.iter().enumerate() { - println!("{}. {harness}", i+1); + println!("{}. {harness}", i + 1); } Ok(()) @@ -142,8 +162,8 @@ fn json( "contracts": total_contracts, } }); - + serde_json::to_writer_pretty(writer, &json_obj)?; Ok(()) -} \ No newline at end of file +} diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index 9da872459621..421ad99fc707 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -33,8 +33,8 @@ mod cbmc_output_parser; mod cbmc_property_renderer; mod concrete_playback; mod coverage; -mod list; mod harness_runner; +mod list; mod metadata; mod project; mod session; @@ -112,7 +112,7 @@ fn standalone_main() -> Result<()> { let project = project::std_project(&args.std_path, &session)?; (session, project) - }, + } None => { let session = KaniSession::new(args.verify_opts)?; if !session.args.common_args.quiet { diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index c69cc2006358..6c7700c371d3 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -90,10 +90,16 @@ impl Project { cargo_metadata: Option, failed_targets: Option>, ) -> Result { - // For the list subcommand, we do not generate any goto, so skip extending the artifacts if session.args.list_enabled { - return Ok(Project { outdir, input, metadata, artifacts: vec![], cargo_metadata, failed_targets }); + return Ok(Project { + outdir, + input, + metadata, + artifacts: vec![], + cargo_metadata, + failed_targets, + }); } // For each harness (test or proof) from each metadata, read the path for the goto diff --git a/kani_metadata/src/lib.rs b/kani_metadata/src/lib.rs index f377bec13901..0a7229d18635 100644 --- a/kani_metadata/src/lib.rs +++ b/kani_metadata/src/lib.rs @@ -3,8 +3,8 @@ extern crate clap; -use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; +use std::{collections::HashSet, path::PathBuf}; pub use artifact::ArtifactType; pub use cbmc_solver::CbmcSolver; diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index a5444b9da62b..db302498ef58 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -95,7 +95,7 @@ pub enum UnstableFeature { /// Enable an unstable option or subcommand. UnstableOptions, /// The list subcommand [RFC 13](https://model-checking.github.io/kani/rfc/rfcs/0013-list.html) - List + List, } impl UnstableFeature { From bc802a1f83d6ba2c46b01f60982bdb857f03c497 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 12 Sep 2024 16:28:01 -0400 Subject: [PATCH 06/34] refactor --- kani-driver/src/list.rs | 169 ----------------------- kani-driver/src/list/collect_metadata.rs | 80 +++++++++++ kani-driver/src/list/mod.rs | 6 + kani-driver/src/list/output.rs | 90 ++++++++++++ kani-driver/src/main.rs | 4 +- 5 files changed, 178 insertions(+), 171 deletions(-) delete mode 100644 kani-driver/src/list.rs create mode 100644 kani-driver/src/list/collect_metadata.rs create mode 100644 kani-driver/src/list/mod.rs create mode 100644 kani-driver/src/list/output.rs diff --git a/kani-driver/src/list.rs b/kani-driver/src/list.rs deleted file mode 100644 index 13e42c42106a..000000000000 --- a/kani-driver/src/list.rs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// Implements the list subcommand logic - -use std::{fs::File, io::BufWriter}; - -use crate::{ - args::list_args::{CargoListArgs, Format, StandaloneListArgs}, - project::{self, standalone_project}, - session::{KaniSession, ReachabilityMode}, - version::{print_kani_version, KANI_VERSION}, - InvocationType, -}; -use anyhow::Result; -use cli_table::{print_stdout, Cell, CellStruct, Style, Table}; -use colour::print_ln_bold; -use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; -use serde_json::json; - -// Represents the version of our JSON file format. -// Increment this version (according to semantic versioning rules) whenever the JSON output format changes. -const FILE_VERSION: &str = "0.1"; - -fn set_session_args(session: &mut KaniSession) { - session.reachability_mode = ReachabilityMode::None; - session.args.list_enabled = true; -} - -fn process_metadata(metadata: Vec, format: Format) -> Result<()> { - let mut standard_harnesses: Vec = vec![]; - let mut contract_harnesses: Vec = vec![]; - let mut contracted_functions: Vec = vec![]; - let mut total_contracts = 0; - - for kani_meta in metadata { - for harness_meta in kani_meta.proof_harnesses { - match harness_meta.attributes.kind { - HarnessKind::Proof => standard_harnesses.push(harness_meta.pretty_name), - HarnessKind::ProofForContract { .. } => { - contract_harnesses.push(harness_meta.pretty_name) - } - HarnessKind::Test => {} - } - } - - for cf in &kani_meta.contracted_functions { - total_contracts += cf.total_contracts; - } - - contracted_functions.extend(kani_meta.contracted_functions.into_iter()); - } - - // Print in alphabetical order - standard_harnesses.sort(); - contract_harnesses.sort(); - contracted_functions.sort_by_key(|cf| cf.function.clone()); - - match format { - Format::Pretty => pretty_print( - standard_harnesses, - contracted_functions, - contract_harnesses.len(), - total_contracts, - ), - Format::Json => { - json(standard_harnesses, contract_harnesses, contracted_functions, total_contracts) - } - } -} - -pub fn list_cargo(mut session: KaniSession, _args: CargoListArgs) -> Result<()> { - set_session_args(&mut session); - let _project = project::cargo_project(&session, false)?; - // process_project(project, args.format) - todo!() -} - -pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { - let mut session = KaniSession::new(args.verify_opts)?; - if !session.args.common_args.quiet { - print_kani_version(InvocationType::Standalone); - } - set_session_args(&mut session); - let project = standalone_project(&args.input, args.crate_name, &session)?; - - process_metadata(project.metadata, args.format) -} - -pub fn _list_std(_session: KaniSession, _args: CargoListArgs) -> Result<()> { - todo!() -} - -fn pretty_print( - standard_harnesses: Vec, - contracted_functions: Vec, - total_contract_harnesses: usize, - total_contracts: usize, -) -> Result<()> { - let total_contracted_functions = contracted_functions.len(); - - fn format_contract_harnesses(harnesses: &mut [String]) -> String { - harnesses.sort(); - let joined = harnesses.join("\n"); - if joined.is_empty() { "NONE".to_string() } else { joined } - } - - let mut contracts_table: Vec> = vec![]; - - for mut cf in contracted_functions { - contracts_table.push(vec![ - "".cell(), - cf.function.cell(), - cf.total_contracts.cell(), - format_contract_harnesses(&mut cf.harnesses).cell(), - ]); - } - - contracts_table.push(vec![ - "Total".cell().bold(true), - total_contracted_functions.cell(), - total_contracts.cell(), - total_contract_harnesses.cell(), - ]); - - print_ln_bold!("\nContracts:"); - print_stdout(contracts_table.table().title(vec![ - "".cell(), - "Function Under Contract".cell().bold(true), - "# of Contracts".cell().bold(true), - "Contract Harnesses".cell().bold(true), - ]))?; - - print_ln_bold!("\nStandard Harnesses:"); - for (i, harness) in standard_harnesses.iter().enumerate() { - println!("{}. {harness}", i + 1); - } - - Ok(()) -} - -fn json( - standard_harnesses: Vec, - contract_harnesses: Vec, - contracted_functions: Vec, - total_contracts: usize, -) -> Result<()> { - let filename = "list.json"; - - let out_file = File::create(filename).unwrap(); - let writer = BufWriter::new(out_file); - - let json_obj = json!({ - "kani-version": KANI_VERSION, - "file-version": FILE_VERSION, - "standard-harnesses": &standard_harnesses, - "contract-harnesses": &contract_harnesses, - "contracts": &contracted_functions, - "totals": { - "standard-harnesses": standard_harnesses.len(), - "contract-harnesses": contract_harnesses.len(), - "functions-under-contract": contracted_functions.len(), - "contracts": total_contracts, - } - }); - - serde_json::to_writer_pretty(writer, &json_obj)?; - - Ok(()) -} diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs new file mode 100644 index 000000000000..870b9a223a08 --- /dev/null +++ b/kani-driver/src/list/collect_metadata.rs @@ -0,0 +1,80 @@ +use crate::{ + args::list_args::{CargoListArgs, Format, StandaloneListArgs}, + project::{self, standalone_project}, + session::{KaniSession, ReachabilityMode}, + version::print_kani_version, + InvocationType, +}; +use anyhow::Result; +use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; + +use super::output::{json, pretty}; + +fn set_session_args(session: &mut KaniSession) { + session.reachability_mode = ReachabilityMode::None; + session.args.list_enabled = true; +} + +fn process_metadata(metadata: Vec, format: Format) -> Result<()> { + let mut standard_harnesses: Vec = vec![]; + let mut contract_harnesses: Vec = vec![]; + let mut contracted_functions: Vec = vec![]; + let mut total_contracts = 0; + + for kani_meta in metadata { + for harness_meta in kani_meta.proof_harnesses { + match harness_meta.attributes.kind { + HarnessKind::Proof => standard_harnesses.push(harness_meta.pretty_name), + HarnessKind::ProofForContract { .. } => { + contract_harnesses.push(harness_meta.pretty_name) + } + HarnessKind::Test => {} + } + } + + for cf in &kani_meta.contracted_functions { + total_contracts += cf.total_contracts; + } + + contracted_functions.extend(kani_meta.contracted_functions.into_iter()); + } + + // Print in alphabetical order + standard_harnesses.sort(); + contract_harnesses.sort(); + contracted_functions.sort_by_key(|cf| cf.function.clone()); + + match format { + Format::Pretty => pretty( + standard_harnesses, + contracted_functions, + contract_harnesses.len(), + total_contracts, + ), + Format::Json => { + json(standard_harnesses, contract_harnesses, contracted_functions, total_contracts) + } + } +} + +pub fn list_cargo(mut session: KaniSession, _args: CargoListArgs) -> Result<()> { + set_session_args(&mut session); + let _project = project::cargo_project(&session, false)?; + // process_project(project.metadata, args.format); + todo!() +} + +pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { + let mut session = KaniSession::new(args.verify_opts)?; + if !session.args.common_args.quiet { + print_kani_version(InvocationType::Standalone); + } + set_session_args(&mut session); + let project = standalone_project(&args.input, args.crate_name, &session)?; + + process_metadata(project.metadata, args.format) +} + +pub fn _list_std(_session: KaniSession, _args: CargoListArgs) -> Result<()> { + todo!() +} diff --git a/kani-driver/src/list/mod.rs b/kani-driver/src/list/mod.rs new file mode 100644 index 000000000000..193789bca6e7 --- /dev/null +++ b/kani-driver/src/list/mod.rs @@ -0,0 +1,6 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Implements the list subcommand logic + +pub mod collect_metadata; +mod output; diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs new file mode 100644 index 000000000000..360d39663653 --- /dev/null +++ b/kani-driver/src/list/output.rs @@ -0,0 +1,90 @@ +use std::{fs::File, io::BufWriter}; + +use crate::version::KANI_VERSION; +use anyhow::Result; +use cli_table::{print_stdout, Cell, CellStruct, Style, Table}; +use colour::print_ln_bold; +use kani_metadata::ContractedFunction; +use serde_json::json; + +// Represents the version of our JSON file format. +// Increment this version (according to semantic versioning rules) whenever the JSON output format changes. +const FILE_VERSION: &str = "0.1"; + +pub fn pretty( + standard_harnesses: Vec, + contracted_functions: Vec, + total_contract_harnesses: usize, + total_contracts: usize, +) -> Result<()> { + let total_contracted_functions = contracted_functions.len(); + + fn format_contract_harnesses(harnesses: &mut [String]) -> String { + harnesses.sort(); + let joined = harnesses.join("\n"); + if joined.is_empty() { "NONE".to_string() } else { joined } + } + + let mut contracts_table: Vec> = vec![]; + + for mut cf in contracted_functions { + contracts_table.push(vec![ + "".cell(), + cf.function.cell(), + cf.total_contracts.cell(), + format_contract_harnesses(&mut cf.harnesses).cell(), + ]); + } + + contracts_table.push(vec![ + "Total".cell().bold(true), + total_contracted_functions.cell(), + total_contracts.cell(), + total_contract_harnesses.cell(), + ]); + + print_ln_bold!("\nContracts:"); + print_stdout(contracts_table.table().title(vec![ + "".cell(), + "Function Under Contract".cell().bold(true), + "# of Contracts".cell().bold(true), + "Contract Harnesses".cell().bold(true), + ]))?; + + print_ln_bold!("\nStandard Harnesses:"); + for (i, harness) in standard_harnesses.iter().enumerate() { + println!("{}. {harness}", i + 1); + } + + Ok(()) +} + +pub fn json( + standard_harnesses: Vec, + contract_harnesses: Vec, + contracted_functions: Vec, + total_contracts: usize, +) -> Result<()> { + let filename = "list.json"; + + let out_file = File::create(filename).unwrap(); + let writer = BufWriter::new(out_file); + + let json_obj = json!({ + "kani-version": KANI_VERSION, + "file-version": FILE_VERSION, + "standard-harnesses": &standard_harnesses, + "contract-harnesses": &contract_harnesses, + "contracts": &contracted_functions, + "totals": { + "standard-harnesses": standard_harnesses.len(), + "contract-harnesses": contract_harnesses.len(), + "functions-under-contract": contracted_functions.len(), + "contracts": total_contracts, + } + }); + + serde_json::to_writer_pretty(writer, &json_obj)?; + + Ok(()) +} diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index 421ad99fc707..a666d4ccbd29 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -12,7 +12,7 @@ use args_toml::join_args; use crate::args::StandaloneSubcommand; use crate::concrete_playback::playback::{playback_cargo, playback_standalone}; -use crate::list::list_standalone; +use crate::list::collect_metadata::{list_cargo, list_standalone}; use crate::project::Project; use crate::session::KaniSession; use crate::version::print_kani_version; @@ -83,7 +83,7 @@ fn cargokani_main(input_args: Vec) -> Result<()> { return playback_cargo(*args); } Some(CargoKaniSubcommand::List(args)) => { - return list::list_cargo(session, *args); + return list_cargo(session, *args); } None => {} } From bfd268428120f240ccb4cce8e03445def25aea30 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 12 Sep 2024 19:50:58 -0400 Subject: [PATCH 07/34] cargo list --- kani-driver/src/args/list_args.rs | 15 +++++++++++++-- kani-driver/src/args/mod.rs | 4 ---- kani-driver/src/call_single_file.rs | 7 ++++--- kani-driver/src/list/collect_metadata.rs | 24 ++++++++++++------------ kani-driver/src/list/output.rs | 2 +- kani-driver/src/main.rs | 8 +++++--- kani-driver/src/project.rs | 3 ++- 7 files changed, 37 insertions(+), 26 deletions(-) diff --git a/kani-driver/src/args/list_args.rs b/kani-driver/src/args/list_args.rs index e24c232d3dde..573b7a02a52d 100644 --- a/kani-driver/src/args/list_args.rs +++ b/kani-driver/src/args/list_args.rs @@ -13,8 +13,11 @@ use super::VerificationArgs; /// List information relevant to verification #[derive(Debug, Parser)] pub struct CargoListArgs { + #[command(flatten)] + pub verify_opts: VerificationArgs, + /// Output format - #[clap(default_value = "pretty")] + #[clap(long, default_value = "pretty")] pub format: Format, } @@ -48,7 +51,15 @@ pub enum Format { impl ValidateArgs for CargoListArgs { fn validate(&self) -> Result<(), Error> { - todo!() + self.verify_opts.validate()?; + if !self.verify_opts.common_args.unstable_features.contains(UnstableFeature::List) { + return Err(Error::raw( + ErrorKind::MissingRequiredArgument, + "The `list` subcommand is unstable and requires -Z list", + )); + } + + Ok(()) } } diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 9f883ed6b6cf..706c057a899a 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -294,10 +294,6 @@ pub struct VerificationArgs { #[command(flatten)] pub common_args: CommonArgs, - - /// Enable the list subcommand - #[arg(long = "list")] - pub list_enabled: bool, } impl VerificationArgs { diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index cdc68dc50236..4f40506cf900 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -53,9 +53,6 @@ impl KaniSession { ) -> Result<()> { let mut kani_args = self.kani_compiler_flags(); kani_args.push(format!("--reachability={}", self.reachability_mode)); - if self.args.list_enabled { - kani_args.push("--list".to_string()); - } let lib_path = lib_folder().unwrap(); let mut rustc_args = self.kani_rustc_flags(LibConfig::new(lib_path)); @@ -138,6 +135,10 @@ impl KaniSession { flags.push("--ub-check=validity".into()) } + if self.args.common_args.unstable_features.contains(UnstableFeature::List) { + flags.push("--list".into()) + } + if self.args.common_args.unstable_features.contains(UnstableFeature::UninitChecks) { // Automatically enable shadow memory, since the version of uninitialized memory checks // without non-determinism depends on it. diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 870b9a223a08..1ad849495770 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -1,6 +1,6 @@ use crate::{ args::list_args::{CargoListArgs, Format, StandaloneListArgs}, - project::{self, standalone_project}, + project::{cargo_project, standalone_project}, session::{KaniSession, ReachabilityMode}, version::print_kani_version, InvocationType, @@ -10,11 +10,6 @@ use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; use super::output::{json, pretty}; -fn set_session_args(session: &mut KaniSession) { - session.reachability_mode = ReachabilityMode::None; - session.args.list_enabled = true; -} - fn process_metadata(metadata: Vec, format: Format) -> Result<()> { let mut standard_harnesses: Vec = vec![]; let mut contract_harnesses: Vec = vec![]; @@ -57,11 +52,16 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { } } -pub fn list_cargo(mut session: KaniSession, _args: CargoListArgs) -> Result<()> { - set_session_args(&mut session); - let _project = project::cargo_project(&session, false)?; - // process_project(project.metadata, args.format); - todo!() +pub fn list_cargo(args: CargoListArgs) -> Result<()> { + let mut session = KaniSession::new(args.verify_opts)?; + + if !session.args.common_args.quiet { + print_kani_version(InvocationType::CargoKani(vec![])); + } + + session.reachability_mode = ReachabilityMode::None; + let project = cargo_project(&session, false)?; + process_metadata(project.metadata, args.format) } pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { @@ -69,7 +69,7 @@ pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { if !session.args.common_args.quiet { print_kani_version(InvocationType::Standalone); } - set_session_args(&mut session); + session.reachability_mode = ReachabilityMode::None; let project = standalone_project(&args.input, args.crate_name, &session)?; process_metadata(project.metadata, args.format) diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index 360d39663653..d871be3d4005 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -65,7 +65,7 @@ pub fn json( contracted_functions: Vec, total_contracts: usize, ) -> Result<()> { - let filename = "list.json"; + let filename = "kani-list.json"; let out_file = File::create(filename).unwrap(); let writer = BufWriter::new(out_file); diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index a666d4ccbd29..c40a014ba861 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -69,6 +69,10 @@ fn cargokani_main(input_args: Vec) -> Result<()> { let args = args::CargoKaniArgs::parse_from(&input_args); check_is_valid(&args); + if let Some(CargoKaniSubcommand::List(args)) = args.command { + return list_cargo(*args); + } + let session = session::KaniSession::new(args.verify_opts)?; if !session.args.common_args.quiet { @@ -82,9 +86,7 @@ fn cargokani_main(input_args: Vec) -> Result<()> { Some(CargoKaniSubcommand::Playback(args)) => { return playback_cargo(*args); } - Some(CargoKaniSubcommand::List(args)) => { - return list_cargo(session, *args); - } + Some(CargoKaniSubcommand::List(_)) => unreachable!(), None => {} } diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index 6c7700c371d3..694c448aa413 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -8,6 +8,7 @@ use crate::metadata::from_json; use crate::session::KaniSession; use crate::util::crate_name; use anyhow::{Context, Result}; +use kani_metadata::UnstableFeature; use kani_metadata::{ artifact::convert_type, ArtifactType, ArtifactType::*, HarnessMetadata, KaniMetadata, }; @@ -91,7 +92,7 @@ impl Project { failed_targets: Option>, ) -> Result { // For the list subcommand, we do not generate any goto, so skip extending the artifacts - if session.args.list_enabled { + if session.args.common_args.unstable_features.contains(UnstableFeature::List) { return Ok(Project { outdir, input, From 8e4efc084f968c3b03940fad15d2df72f4660855 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 16 Sep 2024 13:52:03 -0400 Subject: [PATCH 08/34] use CrateItems instead; refactor into kani-middle --- .../codegen_cprover_gotoc/codegen/contract.rs | 55 ++-------- .../compiler_interface.rs | 67 +----------- kani-compiler/src/kani_middle/attributes.rs | 13 ++- .../src/kani_middle/codegen_units.rs | 6 +- kani-compiler/src/kani_middle/list/mod.rs | 100 ++++++++++++++++++ kani-compiler/src/kani_middle/metadata.rs | 9 +- kani-compiler/src/kani_middle/mod.rs | 20 +++- kani-driver/src/args/list_args.rs | 46 ++++++-- kani-driver/src/list/collect_metadata.rs | 5 +- kani-driver/src/list/output.rs | 2 + 10 files changed, 196 insertions(+), 127 deletions(-) create mode 100644 kani-compiler/src/kani_middle/list/mod.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index 5981ed2d55ca..10906c13e2d7 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -1,17 +1,16 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::codegen_cprover_gotoc::{codegen::ty_stable::pointee_type_stable, GotocCtx}; -use crate::kani_middle::attributes::{ - matches_diagnostic as matches_function, ContractAttributes, KaniAttributes, -}; +use crate::kani_middle::attributes::KaniAttributes; +use crate::kani_middle::find_closure_in_body; use cbmc::goto_program::FunctionContract; use cbmc::goto_program::{Expr, Lambda, Location, Type}; use kani_metadata::AssignsContract; use rustc_hir::def_id::DefId as InternalDefId; use rustc_smir::rustc_internal; use stable_mir::mir::mono::{Instance, MonoItem}; -use stable_mir::mir::{Local, TerminatorKind, VarDebugInfoContents}; -use stable_mir::ty::{FnDef, RigidTy, TyKind}; +use stable_mir::mir::Local; +use stable_mir::ty::{RigidTy, TyKind}; use stable_mir::CrateDef; impl<'tcx> GotocCtx<'tcx> { @@ -91,18 +90,7 @@ impl<'tcx> GotocCtx<'tcx> { fn find_closure(&mut self, inside: Instance, name: &str) -> Option { let body = self.transformer.body(self.tcx, inside); - body.var_debug_info.iter().find_map(|var_info| { - if var_info.name.as_str() == name { - let ty = match &var_info.value { - VarDebugInfoContents::Place(place) => place.ty(body.locals()).unwrap(), - VarDebugInfoContents::Const(const_op) => const_op.ty(), - }; - if let TyKind::RigidTy(RigidTy::Closure(def, args)) = ty.kind() { - return Some(Instance::resolve(FnDef(def.def_id()), &args).unwrap()); - } - } - None - }) + find_closure_in_body(&body, name) } /// Find the modifies recursively since we may have a recursion wrapper. @@ -238,7 +226,7 @@ impl<'tcx> GotocCtx<'tcx> { let body = self.transformer.body(self.tcx, instance); self.set_current_fn(instance, &body); let mangled_name = instance.mangled_name(); - let goto_contract = self.codegen_modifies_contract( + let goto_contract: FunctionContract = self.codegen_modifies_contract( &mangled_name, instance, self.codegen_span_stable(instance.def.span()), @@ -246,35 +234,4 @@ impl<'tcx> GotocCtx<'tcx> { self.symbol_table.attach_contract(&mangled_name, goto_contract); self.reset_current_fn(); } - - /// Count the number of contracts applied to `contracted_function` - pub fn count_contracts( - &mut self, - contracted_function: &Instance, - contract_attrs: ContractAttributes, - ) -> usize { - // Extract the body of the check_closure, which will contain kani::assume() calls for each precondition - // and kani::assert() calls for each postcondition. - let check_closure = - self.find_closure(*contracted_function, contract_attrs.checked_with.as_str()).unwrap(); - let body = check_closure.body().unwrap(); - - let mut count = 0; - - for bb in &body.blocks { - if let TerminatorKind::Call { ref func, .. } = bb.terminator.kind { - let fn_ty = func.ty(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(self.tcx, instance.def, "KaniAssume") - || matches_function(self.tcx, instance.def, "KaniAssert") - { - count += 1; - } - } - } - } - count - } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 94f8b755fd2e..c179491c74a6 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -5,25 +5,25 @@ use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; +use crate::kani_middle::analysis; use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; use crate::kani_middle::check_reachable_items; use crate::kani_middle::codegen_units::{CodegenUnit, CodegenUnits}; +use crate::kani_middle::list::collect_contracted_fns; use crate::kani_middle::metadata::gen_test_metadata; use crate::kani_middle::provide; use crate::kani_middle::reachability::{ collect_reachable_items, filter_const_crate_items, filter_crate_items, }; use crate::kani_middle::transform::{BodyTransformation, GlobalPasses}; -use crate::kani_middle::{analysis, SourceLocation}; use crate::kani_queries::QueryDb; use cbmc::goto_program::Location; use cbmc::irep::goto_binary_serde::write_goto_binary_file; use cbmc::RoundingMode; use cbmc::{InternedString, MachineModel}; use kani_metadata::artifact::convert_type; -use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; +use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata, UnsupportedFeature}; use kani_metadata::{AssignsContract, CompilerArtifactStub}; -use kani_metadata::{ContractedFunction, UnsupportedFeature}; use rustc_codegen_ssa::back::archive::{ArArchiveBuilder, ArchiveBuilder, DEFAULT_OBJECT_READER}; use rustc_codegen_ssa::back::metadata::create_wrapper_file; use rustc_codegen_ssa::traits::CodegenBackend; @@ -47,7 +47,7 @@ use rustc_target::spec::PanicStrategy; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::{CrateDef, DefId}; use std::any::Any; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use std::ffi::OsString; use std::fmt::Write; use std::fs::File; @@ -346,65 +346,8 @@ impl CodegenBackend for GotocCodegenBackend { // If the list subcommand is enabled, still don't generate any goto, // but write the necessary KaniMetadata to a file if queries.args().list_enabled { - let contract_harnesses: Vec = - filter_crate_items(tcx, |_, instance| { - let attr = KaniAttributes::for_instance(tcx, instance); - attr.proof_for_contract().is_some_and(|res| res.is_ok()) - }); - - let mut function_to_harnesses: HashMap> = - HashMap::new(); - - // Map each function under contract to a vector of its proof harnesses. - // Note that this logic will only find contracted functions *with* harnesses. - // We find functions without harnesses by iterating through `crate_fns` later. - for harness in contract_harnesses { - let attr = KaniAttributes::for_instance(tcx, harness); - let target_def_id = attr.interpret_for_contract_attribute().unwrap().1; - if let Some(harnesses) = function_to_harnesses.get_mut(&target_def_id) { - harnesses.push(harness.name()); - } else { - function_to_harnesses.insert(target_def_id, vec![harness.name()]); - } - } - let mut units: CodegenUnits = CodegenUnits::new(&queries, tcx); - let transformer = - BodyTransformation::new(&queries, tcx, &CodegenUnit::default()); - let mut gcx = GotocCtx::new( - tcx, - (*self.queries.lock().unwrap()).clone(), - &results.machine_model, - transformer, - ); - - // Get all functions in the crate. - let crate_fns = filter_crate_items(tcx, |_, _| true); - - // For each function annotated with contracts, construct a ContractedFunction object for it and store it in `units`. - for instance in crate_fns { - let attributes = KaniAttributes::for_instance(tcx, instance); - if attributes.has_contract() { - let fn_def_id = - rustc_internal::internal(tcx, instance.def.def_id()); - let harnesses = function_to_harnesses - .get(&fn_def_id) - .map_or(vec![], |v| v.to_owned()); - let contracts_count = gcx.count_contracts( - &instance, - attributes.contract_attributes().unwrap(), - ); - - units.contracted_functions.push(ContractedFunction { - function: instance.name(), - file: SourceLocation::new(instance.body().unwrap().span) - .filename, - harnesses, - total_contracts: contracts_count, - }); - } - } - + collect_contracted_fns(tcx, &mut units); units.write_metadata(&queries, tcx); } } diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 9a3ff7c1d6a6..3642dcc7a5b1 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -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; + } + if KaniAttributes::for_item(self.tcx, id).contract_attributes().is_none() { dcx.struct_span_err( span, diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index 10646c21e421..52ce0c070c27 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -79,7 +79,8 @@ impl CodegenUnits { let all_harnesses = harnesses .into_iter() .map(|harness| { - let metadata = gen_proof_metadata(tcx, harness, &base_filename); + let metadata = + gen_proof_metadata(tcx, harness, &base_filename, queries.args().list_enabled); (harness, metadata) }) .collect::>(); @@ -92,6 +93,8 @@ impl CodegenUnits { debug!(?units, "CodegenUnits::new"); } + tcx.dcx().abort_if_errors(); + CodegenUnits { units, harness_info: all_harnesses, @@ -234,7 +237,6 @@ fn validate_units(tcx: TyCtxt, units: &[CodegenUnit]) { tcx.dcx().span_err(rustc_internal::internal(tcx, span), msg); } } - tcx.dcx().abort_if_errors(); } /// Apply stub transitivity operations. diff --git a/kani-compiler/src/kani_middle/list/mod.rs b/kani-compiler/src/kani_middle/list/mod.rs new file mode 100644 index 000000000000..da344891ceb4 --- /dev/null +++ b/kani-compiler/src/kani_middle/list/mod.rs @@ -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> { + // 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> = 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, + }); + } +} diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 2f0f22d49e1c..00fc8c4cf99b 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -14,7 +14,12 @@ use stable_mir::CrateDef; use super::{attributes::KaniAttributes, SourceLocation}; /// Create the harness metadata for a proof harness for a given function. -pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> HarnessMetadata { +pub fn gen_proof_metadata( + tcx: TyCtxt, + instance: Instance, + base_name: &Path, + is_list_enabled: bool, +) -> HarnessMetadata { let def = instance.def; let kani_attributes = KaniAttributes::for_instance(tcx, instance); let pretty_name = instance.name(); @@ -33,7 +38,7 @@ pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> original_file: loc.filename, original_start_line: loc.start_line, original_end_line: loc.end_line, - attributes: kani_attributes.harness_attributes(), + attributes: kani_attributes.harness_attributes(is_list_enabled), // TODO: This no longer needs to be an Option. goto_file: Some(model_file), contract: Default::default(), diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index f281e5de5e88..0d99ba08ce85 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -18,7 +18,8 @@ use rustc_span::source_map::respan; use rustc_span::Span; use rustc_target::abi::call::FnAbi; use rustc_target::abi::{HasDataLayout, TargetDataLayout}; -use stable_mir::mir::mono::MonoItem; +use stable_mir::mir::mono::{Instance, MonoItem}; +use stable_mir::mir::{Body, VarDebugInfoContents}; use stable_mir::ty::{FnDef, RigidTy, Span as SpanStable, Ty, TyKind}; use stable_mir::visitor::{Visitable, Visitor as TyVisitor}; use stable_mir::CrateDef; @@ -31,6 +32,7 @@ pub mod attributes; pub mod codegen_units; pub mod coercion; mod intrinsics; +pub mod list; pub mod metadata; pub mod points_to; pub mod provide; @@ -238,3 +240,19 @@ pub fn stable_fn_def(tcx: TyCtxt, def_id: InternalDefId) -> Option { None } } + +/// Find the user-declared closure by the name `name` in `body`. +pub fn find_closure_in_body(body: &Body, name: &str) -> Option { + body.var_debug_info.iter().find_map(|var_info| { + if var_info.name.as_str() == name { + let ty = match &var_info.value { + VarDebugInfoContents::Place(place) => place.ty(body.locals()).unwrap(), + VarDebugInfoContents::Const(const_op) => const_op.ty(), + }; + if let TyKind::RigidTy(RigidTy::Closure(def, args)) = ty.kind() { + return Some(Instance::resolve(FnDef(def.def_id()), &args).unwrap()); + } + } + None + }) +} diff --git a/kani-driver/src/args/list_args.rs b/kani-driver/src/args/list_args.rs index 573b7a02a52d..33390400f765 100644 --- a/kani-driver/src/args/list_args.rs +++ b/kani-driver/src/args/list_args.rs @@ -73,16 +73,50 @@ impl ValidateArgs for StandaloneListArgs { )); } - if !self.input.is_file() { - return Err(Error::raw( + // if !self.input.is_file() { + // return Err(Error::raw( + // ErrorKind::InvalidValue, + // format!( + // "Invalid argument: Input invalid. `{}` is not a regular file.", + // self.input.display() + // ), + // )); + // } + + // Ok(()) + + if !self.input.exists() { + Err(Error::raw( ErrorKind::InvalidValue, format!( - "Invalid argument: Input invalid. `{}` is not a regular file.", + "Invalid argument: `` argument `{}` does not exist", self.input.display() ), - )); + )) + } else if !self.input.is_dir() { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: `` argument `{}` is not a directory", + self.input.display() + ), + )) + } else { + let full_path = self.input.canonicalize()?; + let dir = full_path.file_stem().unwrap(); + if dir != "library" { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: Expected `` to point to the `library` folder \ + containing the standard library crates.\n\ + Found `{}` folder instead", + dir.to_string_lossy() + ), + )) + } else { + Ok(()) + } } - - Ok(()) } } diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 1ad849495770..0b44f63d6b3e 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -1,6 +1,6 @@ use crate::{ args::list_args::{CargoListArgs, Format, StandaloneListArgs}, - project::{cargo_project, standalone_project}, + project::{cargo_project, std_project}, session::{KaniSession, ReachabilityMode}, version::print_kani_version, InvocationType, @@ -70,7 +70,8 @@ pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { print_kani_version(InvocationType::Standalone); } session.reachability_mode = ReachabilityMode::None; - let project = standalone_project(&args.input, args.crate_name, &session)?; + //let project = standalone_project(&args.input, args.crate_name, &session)?; + let project = std_project(&args.input, &session)?; process_metadata(project.metadata, args.format) } diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index d871be3d4005..a5d581aa758b 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -56,6 +56,8 @@ pub fn pretty( println!("{}. {harness}", i + 1); } + println!(); + Ok(()) } From 2e79fbc5a9d6db5bddc50915a740904ed8174157 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 16 Sep 2024 14:41:06 -0400 Subject: [PATCH 09/34] add std flag --- .../compiler_interface.rs | 3 +- .../src/kani_middle/{list/mod.rs => list.rs} | 0 kani-driver/src/args/list_args.rs | 79 ++++++++++--------- kani-driver/src/args/mod.rs | 2 +- kani-driver/src/list/collect_metadata.rs | 17 ++-- 5 files changed, 52 insertions(+), 49 deletions(-) rename kani-compiler/src/kani_middle/{list/mod.rs => list.rs} (100%) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index c179491c74a6..1022d9a97b68 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -343,8 +343,7 @@ impl CodegenBackend for GotocCodegenBackend { } } ReachabilityType::None => { - // If the list subcommand is enabled, still don't generate any goto, - // but write the necessary KaniMetadata to a file + // If the list subcommand is enabled, record the necessary KaniMetadata. if queries.args().list_enabled { let mut units: CodegenUnits = CodegenUnits::new(&queries, tcx); collect_contracted_fns(tcx, &mut units); diff --git a/kani-compiler/src/kani_middle/list/mod.rs b/kani-compiler/src/kani_middle/list.rs similarity index 100% rename from kani-compiler/src/kani_middle/list/mod.rs rename to kani-compiler/src/kani_middle/list.rs diff --git a/kani-driver/src/args/list_args.rs b/kani-driver/src/args/list_args.rs index 33390400f765..0b7a80875f3a 100644 --- a/kani-driver/src/args/list_args.rs +++ b/kani-driver/src/args/list_args.rs @@ -37,6 +37,11 @@ pub struct StandaloneListArgs { /// Output format #[clap(long, default_value = "pretty")] pub format: Format, + + /// Pass this flag to run the `list` command on the standard library. + /// Ensure that the provided `path` is the `library` folder. + #[arg(long)] + pub std: bool, } /// Message formats available for the subcommand. @@ -73,50 +78,50 @@ impl ValidateArgs for StandaloneListArgs { )); } - // if !self.input.is_file() { - // return Err(Error::raw( - // ErrorKind::InvalidValue, - // format!( - // "Invalid argument: Input invalid. `{}` is not a regular file.", - // self.input.display() - // ), - // )); - // } - - // Ok(()) - - if !self.input.exists() { - Err(Error::raw( - ErrorKind::InvalidValue, - format!( - "Invalid argument: `` argument `{}` does not exist", - self.input.display() - ), - )) - } else if !self.input.is_dir() { - Err(Error::raw( - ErrorKind::InvalidValue, - format!( - "Invalid argument: `` argument `{}` is not a directory", - self.input.display() - ), - )) - } else { - let full_path = self.input.canonicalize()?; - let dir = full_path.file_stem().unwrap(); - if dir != "library" { + if self.std { + if !self.input.exists() { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: `` argument `{}` does not exist", + self.input.display() + ), + )) + } else if !self.input.is_dir() { Err(Error::raw( ErrorKind::InvalidValue, format!( - "Invalid argument: Expected `` to point to the `library` folder \ - containing the standard library crates.\n\ - Found `{}` folder instead", - dir.to_string_lossy() + "Invalid argument: `` argument `{}` is not a directory", + self.input.display() ), )) } else { - Ok(()) + let full_path = self.input.canonicalize()?; + let dir = full_path.file_stem().unwrap(); + if dir != "library" { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: Expected `` to point to the `library` folder \ + containing the standard library crates.\n\ + Found `{}` folder instead", + dir.to_string_lossy() + ), + )) + } else { + Ok(()) + } } + } else if self.input.is_file() { + Ok(()) + } else { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: Input invalid. `{}` is not a regular file.", + self.input.display() + ), + )) } } } diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 706c057a899a..5f16fa2b5f02 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -122,7 +122,7 @@ pub enum CargoKaniSubcommand { /// Execute concrete playback testcases of a local package. Playback(Box), - /// List package/crate metadata relevant to verification + /// List metadata relevant to verification, e.g., harnesses. List(Box), } diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 0b44f63d6b3e..85d1f193e28e 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -1,6 +1,6 @@ use crate::{ args::list_args::{CargoListArgs, Format, StandaloneListArgs}, - project::{cargo_project, std_project}, + project::{cargo_project, standalone_project, std_project, Project}, session::{KaniSession, ReachabilityMode}, version::print_kani_version, InvocationType, @@ -54,12 +54,11 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { pub fn list_cargo(args: CargoListArgs) -> Result<()> { let mut session = KaniSession::new(args.verify_opts)?; - if !session.args.common_args.quiet { print_kani_version(InvocationType::CargoKani(vec![])); } - session.reachability_mode = ReachabilityMode::None; + let project = cargo_project(&session, false)?; process_metadata(project.metadata, args.format) } @@ -70,12 +69,12 @@ pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { print_kani_version(InvocationType::Standalone); } session.reachability_mode = ReachabilityMode::None; - //let project = standalone_project(&args.input, args.crate_name, &session)?; - let project = std_project(&args.input, &session)?; - process_metadata(project.metadata, args.format) -} + let project: Project = if args.std { + std_project(&args.input, &session)? + } else { + standalone_project(&args.input, args.crate_name, &session)? + }; -pub fn _list_std(_session: KaniSession, _args: CargoListArgs) -> Result<()> { - todo!() + process_metadata(project.metadata, args.format) } From b1c5a9ec5e64cd7ae7961553ad3dbd5789e68078 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 16 Sep 2024 19:32:49 -0400 Subject: [PATCH 10/34] output updates --- kani-driver/src/list/collect_metadata.rs | 31 ++++++++--- kani-driver/src/list/output.rs | 71 +++++++++++++++--------- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 85d1f193e28e..2c5b91268c6e 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use crate::{ args::list_args::{CargoListArgs, Format, StandaloneListArgs}, project::{cargo_project, standalone_project, std_project, Project}, @@ -11,23 +13,40 @@ use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; use super::output::{json, pretty}; fn process_metadata(metadata: Vec, format: Format) -> Result<()> { - let mut standard_harnesses: Vec = vec![]; - let mut contract_harnesses: Vec = vec![]; + // Map each file to a vector of its harnesses + let mut standard_harnesses: BTreeMap> = BTreeMap::new(); + let mut contract_harnesses: BTreeMap> = BTreeMap::new(); let mut contracted_functions: Vec = vec![]; let mut total_contracts = 0; + let mut total_contract_harnesses = 0; for kani_meta in metadata { for harness_meta in kani_meta.proof_harnesses { match harness_meta.attributes.kind { - HarnessKind::Proof => standard_harnesses.push(harness_meta.pretty_name), + HarnessKind::Proof => { + if let Some(harnesses) = standard_harnesses.get_mut(&harness_meta.original_file) + { + harnesses.push(harness_meta.pretty_name); + } else { + standard_harnesses + .insert(harness_meta.original_file, vec![harness_meta.pretty_name]); + } + } HarnessKind::ProofForContract { .. } => { - contract_harnesses.push(harness_meta.pretty_name) + if let Some(harnesses) = contract_harnesses.get_mut(&harness_meta.original_file) + { + harnesses.push(harness_meta.pretty_name); + } else { + contract_harnesses + .insert(harness_meta.original_file, vec![harness_meta.pretty_name]); + } } HarnessKind::Test => {} } } for cf in &kani_meta.contracted_functions { + total_contract_harnesses += cf.harnesses.len(); total_contracts += cf.total_contracts; } @@ -35,15 +54,13 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { } // Print in alphabetical order - standard_harnesses.sort(); - contract_harnesses.sort(); contracted_functions.sort_by_key(|cf| cf.function.clone()); match format { Format::Pretty => pretty( standard_harnesses, contracted_functions, - contract_harnesses.len(), + total_contract_harnesses, total_contracts, ), Format::Json => { diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index a5d581aa758b..dd61cb89aa93 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -1,4 +1,4 @@ -use std::{fs::File, io::BufWriter}; +use std::{collections::BTreeMap, fs::File, io::BufWriter}; use crate::version::KANI_VERSION; use anyhow::Result; @@ -12,7 +12,7 @@ use serde_json::json; const FILE_VERSION: &str = "0.1"; pub fn pretty( - standard_harnesses: Vec, + standard_harnesses: BTreeMap>, contracted_functions: Vec, total_contract_harnesses: usize, total_contracts: usize, @@ -25,35 +25,52 @@ pub fn pretty( if joined.is_empty() { "NONE".to_string() } else { joined } } - let mut contracts_table: Vec> = vec![]; + print_ln_bold!("\nContracts:"); + println!( + "Each function in the table below either has contracts or is the target of a contract harness (#[kani::proof_for_contract])." + ); + + if contracted_functions.is_empty() { + println!("No contracts or contract harnesses found.") + } else { + let mut contracts_table: Vec> = vec![]; + + for mut cf in contracted_functions { + contracts_table.push(vec![ + "".cell(), + cf.function.cell(), + cf.total_contracts.cell(), + format_contract_harnesses(&mut cf.harnesses).cell(), + ]); + } - for mut cf in contracted_functions { contracts_table.push(vec![ - "".cell(), - cf.function.cell(), - cf.total_contracts.cell(), - format_contract_harnesses(&mut cf.harnesses).cell(), + "Total".cell().bold(true), + total_contracted_functions.cell(), + total_contracts.cell(), + total_contract_harnesses.cell(), ]); + + print_stdout(contracts_table.table().title(vec![ + "".cell(), + "Function".cell().bold(true), + "# of Contracts".cell().bold(true), + "Contract Harnesses".cell().bold(true), + ]))?; } - contracts_table.push(vec![ - "Total".cell().bold(true), - total_contracted_functions.cell(), - total_contracts.cell(), - total_contract_harnesses.cell(), - ]); + print_ln_bold!("\nStandard Harnesses (#[kani::proof]):"); + if standard_harnesses.is_empty() { + println!("No standard harnesses found."); + } - print_ln_bold!("\nContracts:"); - print_stdout(contracts_table.table().title(vec![ - "".cell(), - "Function Under Contract".cell().bold(true), - "# of Contracts".cell().bold(true), - "Contract Harnesses".cell().bold(true), - ]))?; - - print_ln_bold!("\nStandard Harnesses:"); - for (i, harness) in standard_harnesses.iter().enumerate() { - println!("{}. {harness}", i + 1); + let mut std_harness_index = 0; + + for (_, harnesses) in standard_harnesses { + for harness in harnesses { + println!("{}. {harness}", std_harness_index + 1); + std_harness_index += 1; + } } println!(); @@ -62,8 +79,8 @@ pub fn pretty( } pub fn json( - standard_harnesses: Vec, - contract_harnesses: Vec, + standard_harnesses: BTreeMap>, + contract_harnesses: BTreeMap>, contracted_functions: Vec, total_contracts: usize, ) -> Result<()> { From ced1dab7e8d783e70b44e93736777855026ad73a Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 16 Sep 2024 19:33:56 -0400 Subject: [PATCH 11/34] update RFC with implementation --- rfc/src/rfcs/0013-list.md | 190 +++++++++++++++++++++++++++++--------- 1 file changed, 145 insertions(+), 45 deletions(-) diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index bdbf4681f430..5d8661655c72 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -2,7 +2,7 @@ - **Feature Request Issue:** [#2573](https://github.com/model-checking/kani/issues/2573), [#1612](https://github.com/model-checking/kani/issues/1612) - **RFC PR:** #3463 - **Status:** Under Review -- **Version:** 1 +- **Version:** 2 ------------------- @@ -20,53 +20,53 @@ This feature will not cause any regressions for exisiting users. ## User Experience -Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand will take the option `--message-format=[pretty|json]`, which changes the output format. The default is `pretty`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. +Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand takes two options: +- `--message-format=[pretty|json]`: change the output format. The default is `pretty`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. +- `--std`: Include if we are running on the standard library. This option is only available for `kani list` (not `cargo kani list`), which mirrors the verification workflow for the standard library. -This subcommand will not fail. In the case that it does not find any harnesses or contracts, it will print a message informing the user of that fact. +This subcommand does not fail. In the case that it does not find any harnesses or contracts, it prints a message informing the user of that fact. ### Pretty Format -The default format, `pretty`, will print the harnesses and contracts in a project, along with the total counts of each. +The default format, `pretty`, prints a "Contracts" table and a "Standard Harnesses" list. +Each row of the "Contracts" table consists of a function, the number of contracts it has, and its contract harnesses. +A function is listed if it has contracts or it is the target of contract harness(es). For example: ``` Kani Rust Verifier 0.54.0 (standalone) -Standard Harnesses: -- example::verify::check_new -- example::verify::check_modify - -Contract Harnesses: -- example::verify::check_foo_u32 -- example::verify::check_foo_u64 -- example::verify::check_func -- example::verify::check_bar - Contracts: -|--------------------------|-----------------------------------------------| -| Function | Contract Harnesses | -|--------------------------|-----------------------------------------------| -| example::impl::func | example::verify::check_func | -|--------------------------|-----------------------------------------------| -| example::impl::bar | example::verify::check_bar | -|--------------------------|-----------------------------------------------| -| example::impl::foo | example::verify::check_foo_u32, | -| | example::verify::check_foo_u64 | -|--------------------------|-----------------------------------------------| -| example::prep::parse | NONE | -|--------------------------|-----------------------------------------------| - -Totals: -- Standard Harnesses: 2 -- Contract Harnesses: 4 -- Functions with Contracts: 4 -- Contracts: 10 +Each function in the table below either has contracts +or is the target of a contract harness (#[kani::proof_for_contract]). + +|-------|-------------------------|----------------|--------------------| +| | Function | # of Contracts | Contract Harnesses | +|-------|-------------------------|----------------|--------------------| +| | example::impl::bar | 4 | check_bar | +|-------|-------------------------|----------------|--------------------| +| | example::impl::baz | 0 | check_baz | +|-------|-------------------------|----------------|--------------------| +| | example::impl::foo | 2 | check_foo_u32 | +| | | | check_foo_u64 | +|-------|-------------------------|----------------|--------------------| +| | example::impl::func | 1 | check_func | +|-------|-------------------------|----------------|--------------------| +| | example::prep::parse | 1 | NONE | +|-------|-------------------------|----------------|--------------------| +| Total | 5 | 8 | 5 | +|-------|-------------------------|----------------|--------------------| + +Standard Harnesses (#[kani::proof]): +1. example::verify::check_new +2. example::verify::check_modify ``` -A "Standard Harness" is a `#[proof]` harness, while a "Contract Harness" is a `#[proof_for_contract]` harness. - -All sections will be present in the output, regardless of the result. If a list is empty, Kani will output a `NONE` string. +All sections will be present in the output, regardless of the result. +If there are no harnesses for a function under contract, Kani inserts `NONE` in the "Contract Harnesses" row. +If the "Contracts" section is empty, Kani prints a message that "No contracts or contract harnesses were found." +If the "Standard Harnesses" section is empty, Kani prints a message that "No standard harnesses were found." ### JSON Format @@ -96,6 +96,7 @@ For example: file: /Users/johnsmith/example/kani_contract_proofs.rs harnesses: [ example::verify::check_bar, + example::verify::check_baz, example::verify::check_foo_u32, example::verify::check_foo_u64, example::verify::check_func @@ -104,34 +105,44 @@ For example: ], contracts: [ { - function: example::impl::func + function: example::impl::bar + total_contracts: 4 file: /Users/johnsmith/example/impl.rs - harnesses: [example::verify::check_func] + harnesses: [example::verify::check_bar] }, { - function: example::impl::bar + function: example::impl::baz + total_contracts: 0 file: /Users/johnsmith/example/impl.rs - harnesses: [example::verify::check_bar] + harnesses: [example::verify::check_baz] }, { function: example::impl::foo + total_contracts: 2 file: /Users/johnsmith/example/impl.rs harnesses: [ example::verify::check_foo_u32, example::verify::check_foo_u64 ] }, + { + function: example::impl::func + total_contracts: 1 + file: /Users/johnsmith/example/impl.rs + harnesses: [example::verify::check_func] + }, { function: example::prep::parse + total_contracts: 1 file: /Users/johnsmith/example/prep.rs harnesses: [] } ], totals: { standard-harnesses: 2, - contract-harnesses: 4, - functions-with-contracts: 4, - contracts: 10, + contract-harnesses: 5, + functions-with-contracts: 5, + contracts: 8, } } ``` @@ -141,9 +152,98 @@ If there is no result for a given field (e.g., there are no contracts), Kani wil ## Software Design -We will add a new subcommand to `kani-driver`. +### Metdata Changes +We introduce a new `ContractedFunction` struct to `kani_metadata`: +```rust +pub struct ContractedFunction { + /// The fully qualified name the user gave to the function (i.e. includes the module path). + pub function: String, + /// The (currently full-) path to the file this function was declared within. + pub file: String, + /// The number of contracts applied to this function + pub total_contracts: usize, + /// The pretty names of the proof harnesses (`#[kani::proof_for_contract]`) for this function + pub harnesses: Vec, +} +``` +We extend `KaniMetadata` and `CodegenUnits` with new `contracted_functions: Vec` fields. + +### Compiler Changes + +This subcommand is concerned with two fields of `KaniMetadata`: `proof_harnesses` and `contracted_functions`. +In `codegen_crate()`, `kani-compiler` will check if we are running the list subcommand, and, if so, it constructs a new `CodegenUnits` object. +The `CodegenUnits` constructor handles initializing the `proof_harnesses` field for us. +We add new functionality to populate the `contracted_functions` field, which we explain in two parts: + +#### Part 1: Map Functions to Harnesses + +First, we iterate through each local item in the crate and construct a map of functions to contract harnesses. +The keys in this map are functions that either 1) have contracts or 2) are targets of contract harnesses. +These are not necessarily identical sets; functions under contract may not have harnesses, and targets of contract harnesses may not have contracts. + +Observe that we iterate through each local item in the crate (`stable_mir::CrateItem`), not each local *instance* (`stable_mir::Instance`). +This is so that we can include generic functions with contracts in the output. +`Instances` are monomorphized. +When we're verifying a contract harness, this monomorphization assumption is fine; if the harness calls the function under contract, a monomorphized version of the function must exist. +However, if we are just trying to list metadata, we cannot rely on this assumption that the function under contract gets called, and therefore cannot assume that a monomorphized version of the generic function exists. +For example, imagine running `kani list` on a file with only these contents: +```rust +#[kani::requires(true)] +fn foo(x: T) -> T { x } +``` +Kani should be able to find `foo` and report it has a contract, +but we cannot construct a `stable_mir::Instance` from `foo` because it requires monomorphization. + +#### Part 2: Count Contracts +For each function in the map from Part 1, we count its contracts, then construct a `ContractedFunction` object for it. + +Since we are counting the contracts at the MIR level, we work with the expanded version of the contract attribute macros. +We locate the body of the `kanitool::checked_with` closure, then count the number of `kani::assume()` and `kani::assert()` calls. +For example, given the following code (example taken from `kani_macros` contracts documentation): + +```rust +#[kani::requires(divisor != 0)] +#[kani::ensures(|result : &u32| *result <= dividend)] +fn div(dividend: u32, divisor: u32) -> u32 { + dividend / divisor +} +``` + +The generated `check` closure is: + +```rust +let mut __kani_check_div = + || -> u32 + { + kani::assume(divisor != 0); + let _wrapper_arg = (); + #[kanitool::is_contract_generated(wrapper)] + #[allow(dead_code, unused_variables, unused_mut)] + let mut __kani_modifies_div = + |_wrapper_arg| -> u32 { dividend / divisor }; + let result_kani_internal: u32 = + __kani_modifies_div(_wrapper_arg); + kani::assert(kani::internal::apply_closure(|result: &u32| + *result <= dividend, &result_kani_internal), + "|result : &u32| *result <= dividend"); + result_kani_internal + }; +; +``` + +Observe that there is one `kani::assume()` call for the `requires` contract and one `kani::assert()` +call for the `ensures` contract, so we can obtain the total number of contracts by counting these calls. + +Once these parts are complete, `CodegenUnits` contains all of the required metadata. +We use an existing method (`CodegenUnits::write_metadata`) to write this metadata to a file. -*We will update this section once the UX is finalized*. +### Driver Changes +We add a new subcommand to `kani-driver`. +This subcommand exists for both `cargo kani` and `kani` invocations. +The driver constructs a `Project` representing the input. +(For `kani` invocations, the driver either constructs a `standalone_project` or a `std_project` depending on whether the use passed the `--std` flag). +The `Project` is populated with `metadata: Vec` from `kani-compiler`. +We iterate through this metadata to print a table with the results or output a JSON file, depending on the user-specified `format` argument. ## Rationale and alternatives @@ -164,7 +264,7 @@ If we do not implement this feature, users will have to obtain this metadata thr 1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts, the number of `requires`, `ensures`, or `modifies` contracts, or the locations of the contracts. 2. More generally, we could introduce additional options that collect information about other Kani attributes (e.g., stubs). The default would be to leave them out, but this way a user could get more verbose output if they so choose. -3. Do we want to add a filtering option? For instance, `--harnesses ` and `--contracts `, where `pattern` corresponds to a Rust-style path. For example, `kani list --harnesses "my_crate::my_module::*"` would include all harnesses with that path prefix, while `kani list --contracts "my_crate::my_module::*"` would include all functions under contract with that path prefix. +3. Do we want to add a filtering option? For instance, `--harnesses ` and `--contracts `, where `pattern` corresponds to a Rust-style path. For example, `kani list --harnesses "my_crate::my_module::*"` would include all harnesses with that path prefix, while `kani list --contracts "my_crate::my_module::*"` would include all functions under contract with that path prefix. (If we do this work, we could use it to improve our `--harness` [pattern handling for verification](https://github.com/model-checking/kani/blob/main/kani-driver/src/metadata.rs#L187-L189)). ## Out of scope / Future Improvements From c98b0b0f27612f83a95fb24d040c1d1e3561fa71 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 17 Sep 2024 10:37:08 -0400 Subject: [PATCH 12/34] nits --- kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs | 2 +- kani-driver/src/session.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index 10906c13e2d7..0d0f9dc21d67 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -226,7 +226,7 @@ impl<'tcx> GotocCtx<'tcx> { let body = self.transformer.body(self.tcx, instance); self.set_current_fn(instance, &body); let mangled_name = instance.mangled_name(); - let goto_contract: FunctionContract = self.codegen_modifies_contract( + let goto_contract = self.codegen_modifies_contract( &mangled_name, instance, self.codegen_span_stable(instance.def.span()), diff --git a/kani-driver/src/session.rs b/kani-driver/src/session.rs index 9c75d8a79aa9..dda5d2603e2f 100644 --- a/kani-driver/src/session.rs +++ b/kani-driver/src/session.rs @@ -26,9 +26,7 @@ pub struct KaniSession { /// The common command-line arguments pub args: VerificationArgs, - /// Include all publicly-visible symbols in the generated goto binary, not just those reachable from - /// a proof harness. Useful when attempting to verify things that were not annotated with kani - /// proof attributes. + /// The reachability mode to use for code generation. pub reachability_mode: ReachabilityMode, /// The location we found the 'kani_rustc' command From b57e47d5abd087741c0260e824a0ea99ed7f6246 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 17 Sep 2024 13:32:22 -0400 Subject: [PATCH 13/34] add tests --- rfc/src/rfcs/0013-list.md | 34 ++++---- tests/script-based-pre/cargo_list/Cargo.toml | 10 +++ tests/script-based-pre/cargo_list/config.yml | 4 + .../cargo_list/list-pretty.expected | 36 +++++++++ tests/script-based-pre/cargo_list/list.sh | 8 ++ tests/script-based-pre/cargo_list/src/lib.rs | 78 ++++++++++++++++++ .../cargo_list/src/standard_harnesses.rs | 15 ++++ tests/script-based-pre/kani_list/config.yml | 4 + .../kani_list/list-pretty.expected | 36 +++++++++ tests/script-based-pre/kani_list/list.sh | 8 ++ tests/script-based-pre/kani_list/src/lib.rs | 81 +++++++++++++++++++ 11 files changed, 297 insertions(+), 17 deletions(-) create mode 100644 tests/script-based-pre/cargo_list/Cargo.toml create mode 100644 tests/script-based-pre/cargo_list/config.yml create mode 100644 tests/script-based-pre/cargo_list/list-pretty.expected create mode 100755 tests/script-based-pre/cargo_list/list.sh create mode 100644 tests/script-based-pre/cargo_list/src/lib.rs create mode 100644 tests/script-based-pre/cargo_list/src/standard_harnesses.rs create mode 100644 tests/script-based-pre/kani_list/config.yml create mode 100644 tests/script-based-pre/kani_list/list-pretty.expected create mode 100755 tests/script-based-pre/kani_list/list.sh create mode 100644 tests/script-based-pre/kani_list/src/lib.rs diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index 5d8661655c72..a5c95348d34d 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -41,22 +41,22 @@ Contracts: Each function in the table below either has contracts or is the target of a contract harness (#[kani::proof_for_contract]). -|-------|-------------------------|----------------|--------------------| -| | Function | # of Contracts | Contract Harnesses | -|-------|-------------------------|----------------|--------------------| -| | example::impl::bar | 4 | check_bar | -|-------|-------------------------|----------------|--------------------| -| | example::impl::baz | 0 | check_baz | -|-------|-------------------------|----------------|--------------------| -| | example::impl::foo | 2 | check_foo_u32 | -| | | | check_foo_u64 | -|-------|-------------------------|----------------|--------------------| -| | example::impl::func | 1 | check_func | -|-------|-------------------------|----------------|--------------------| -| | example::prep::parse | 1 | NONE | -|-------|-------------------------|----------------|--------------------| -| Total | 5 | 8 | 5 | -|-------|-------------------------|----------------|--------------------| +|-------|-------------------------|----------------|-------------------------------------| +| | Function | # of Contracts | Contract Harnesses | +|-------|-------------------------|----------------|-------------------------------------| +| | example::impl::bar | 4 | example::verify::check_bar | +|-------|-------------------------|----------------|-------------------------------------| +| | example::impl::baz | 0 | example::verify::check_baz | +|-------|-------------------------|----------------|-------------------------------------| +| | example::impl::foo | 2 | example::verify::check_foo_u32 | +| | | | example::verify::check_foo_u64 | +|-------|-------------------------|----------------|-------------------------------------| +| | example::impl::func | 1 | example::verify::check_func | +|-------|-------------------------|----------------|-------------------------------------| +| | example::prep::parse | 1 | NONE | +|-------|-------------------------|----------------|-------------------------------------| +| Total | 5 | 8 | 5 | +|-------|-------------------------|----------------|-------------------------------------| Standard Harnesses (#[kani::proof]): 1. example::verify::check_new @@ -243,7 +243,7 @@ This subcommand exists for both `cargo kani` and `kani` invocations. The driver constructs a `Project` representing the input. (For `kani` invocations, the driver either constructs a `standalone_project` or a `std_project` depending on whether the use passed the `--std` flag). The `Project` is populated with `metadata: Vec` from `kani-compiler`. -We iterate through this metadata to print a table with the results or output a JSON file, depending on the user-specified `format` argument. +We iterate through this metadata to print a table with the results or output a `kani-list.json` file, depending on the user-specified `format` argument. ## Rationale and alternatives diff --git a/tests/script-based-pre/cargo_list/Cargo.toml b/tests/script-based-pre/cargo_list/Cargo.toml new file mode 100644 index 000000000000..00f6ef48965e --- /dev/null +++ b/tests/script-based-pre/cargo_list/Cargo.toml @@ -0,0 +1,10 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "sample_crate" +version = "0.1.0" +edition = "2021" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] } \ No newline at end of file diff --git a/tests/script-based-pre/cargo_list/config.yml b/tests/script-based-pre/cargo_list/config.yml new file mode 100644 index 000000000000..816e3413c65f --- /dev/null +++ b/tests/script-based-pre/cargo_list/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: list.sh +expected: list-pretty.expected diff --git a/tests/script-based-pre/cargo_list/list-pretty.expected b/tests/script-based-pre/cargo_list/list-pretty.expected new file mode 100644 index 000000000000..e86fd0d595b2 --- /dev/null +++ b/tests/script-based-pre/cargo_list/list-pretty.expected @@ -0,0 +1,36 @@ +Contracts: +Each function in the table below either has contracts or is the target of a contract harness (#[kani::proof_for_contract]). + +Function +# of Contracts +Contract Harnesses + +example::implementation::bar +4 +example::verify::check_bar + +example::implementation::baz +0 +example::verify::check_baz + +example::implementation::foo +2 +example::verify::check_foo_u32 + +example::verify::check_foo_u64 +example::implementation::func +1 +example::verify::check_func + +example::prep::parse +1 +NONE + +Total +5 +8 +5 + +Standard Harnesses (#[kani::proof]): +1. standard_harnesses::example::verify::check_modify +2. standard_harnesses::example::verify::check_new \ No newline at end of file diff --git a/tests/script-based-pre/cargo_list/list.sh b/tests/script-based-pre/cargo_list/list.sh new file mode 100755 index 000000000000..c394e8dff8de --- /dev/null +++ b/tests/script-based-pre/cargo_list/list.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# The table in the expected omits the table borders because the runtest script +# does not evaluate the table borders in the captured output as equal to the table borders in the expected file. + +cargo kani list -Z list \ No newline at end of file diff --git a/tests/script-based-pre/cargo_list/src/lib.rs b/tests/script-based-pre/cargo_list/src/lib.rs new file mode 100644 index 000000000000..82044b6d7ea5 --- /dev/null +++ b/tests/script-based-pre/cargo_list/src/lib.rs @@ -0,0 +1,78 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! This test replicates the module structure from the running example in the list RFC. +//! It ensures that the list command works across modules, and with modifies clauses, history expressions, and generic functions. + +mod standard_harnesses; + +#[cfg(kani)] +mod example { + pub mod implementation { + #[kani::requires(*x < 4)] + #[kani::requires(*x > 2)] + #[kani::ensures(|_| old(*x - 1) == *x)] + #[kani::ensures(|_| *x == 4)] + #[kani::modifies(x)] + pub fn bar(x: &mut u32) { + *x += 1; + } + + #[kani::requires(true)] + #[kani::ensures(|_| old(*x) == *x)] + pub fn foo(x: &mut T) -> T { + *x + } + + #[kani::requires(*x < 100)] + #[kani::modifies(x)] + pub fn func(x: &mut i32) { + *x *= 1; + } + + pub fn baz(x: &mut i32) { + *x /= 1; + } + } + + mod prep { + #[kani::requires(s.len() < 10)] + fn parse(s: &str) -> u32 { + s.parse().unwrap() + } + } + + mod verify { + use crate::example::implementation; + + #[kani::proof_for_contract(implementation::bar)] + fn check_bar() { + let mut x = 7; + implementation::bar(&mut x); + } + + #[kani::proof_for_contract(implementation::foo)] + fn check_foo_u32() { + let mut x: u32 = 7; + implementation::foo(&mut x); + } + + #[kani::proof_for_contract(implementation::foo)] + fn check_foo_u64() { + let mut x: u64 = 7; + implementation::foo(&mut x); + } + + #[kani::proof_for_contract(implementation::func)] + fn check_func() { + let mut x = 7; + implementation::func(&mut x); + } + + #[kani::proof_for_contract(implementation::baz)] + fn check_baz() { + let mut x = 7; + implementation::baz(&mut x); + } + } +} diff --git a/tests/script-based-pre/cargo_list/src/standard_harnesses.rs b/tests/script-based-pre/cargo_list/src/standard_harnesses.rs new file mode 100644 index 000000000000..5df490461d0c --- /dev/null +++ b/tests/script-based-pre/cargo_list/src/standard_harnesses.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Test that the cargo list command can find Kani attributes across multiple files. + +#[cfg(kani)] +mod example { + mod verify { + #[kani::proof] + fn check_modify() {} + + #[kani::proof] + fn check_new() {} + } +} diff --git a/tests/script-based-pre/kani_list/config.yml b/tests/script-based-pre/kani_list/config.yml new file mode 100644 index 000000000000..816e3413c65f --- /dev/null +++ b/tests/script-based-pre/kani_list/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: list.sh +expected: list-pretty.expected diff --git a/tests/script-based-pre/kani_list/list-pretty.expected b/tests/script-based-pre/kani_list/list-pretty.expected new file mode 100644 index 000000000000..5d22cd0e7eb9 --- /dev/null +++ b/tests/script-based-pre/kani_list/list-pretty.expected @@ -0,0 +1,36 @@ +Contracts: +Each function in the table below either has contracts or is the target of a contract harness (#[kani::proof_for_contract]). + +Function +# of Contracts +Contract Harnesses + +example::implementation::bar +4 +example::verify::check_bar + +example::implementation::baz +0 +example::verify::check_baz + +example::implementation::foo +2 +example::verify::check_foo_u32 + +example::verify::check_foo_u64 +example::implementation::func +1 +example::verify::check_func + +example::prep::parse +1 +NONE + +Total +5 +8 +5 + +Standard Harnesses (#[kani::proof]): +1. example::verify::check_modify +2. example::verify::check_new \ No newline at end of file diff --git a/tests/script-based-pre/kani_list/list.sh b/tests/script-based-pre/kani_list/list.sh new file mode 100755 index 000000000000..bbd852a8b054 --- /dev/null +++ b/tests/script-based-pre/kani_list/list.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# The table in the expected omits the table borders because the runtest script +# does not evaluate the table borders in the captured output as equal to the table borders in the expected file. + +kani list -Z list src/lib.rs \ No newline at end of file diff --git a/tests/script-based-pre/kani_list/src/lib.rs b/tests/script-based-pre/kani_list/src/lib.rs new file mode 100644 index 000000000000..16bab7aaaebc --- /dev/null +++ b/tests/script-based-pre/kani_list/src/lib.rs @@ -0,0 +1,81 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! This test replicates the module structure from the running example in the list RFC. +//! It ensures that the list command across modules, and with modifies clauses, history expressions, and generic functions. + +mod example { + pub mod implementation { + #[kani::requires(*x < 4)] + #[kani::requires(*x > 2)] + #[kani::ensures(|_| old(*x - 1) == *x)] + #[kani::ensures(|_| *x == 4)] + #[kani::modifies(x)] + pub fn bar(x: &mut u32) { + *x += 1; + } + + #[kani::requires(true)] + #[kani::ensures(|_| old(*x) == *x)] + pub fn foo(x: &mut T) -> T { + *x + } + + #[kani::requires(*x < 100)] + #[kani::modifies(x)] + pub fn func(x: &mut i32) { + *x *= 1; + } + + pub fn baz(x: &mut i32) { + *x /= 1; + } + } + + mod prep { + #[kani::requires(s.len() < 10)] + fn parse(s: &str) -> u32 { + s.parse().unwrap() + } + } + + mod verify { + use crate::example::implementation; + + #[kani::proof_for_contract(implementation::bar)] + fn check_bar() { + let mut x = 7; + implementation::bar(&mut x); + } + + #[kani::proof_for_contract(implementation::foo)] + fn check_foo_u32() { + let mut x: u32 = 7; + implementation::foo(&mut x); + } + + #[kani::proof_for_contract(implementation::foo)] + fn check_foo_u64() { + let mut x: u64 = 7; + implementation::foo(&mut x); + } + + #[kani::proof_for_contract(implementation::func)] + fn check_func() { + let mut x = 7; + implementation::func(&mut x); + } + + #[kani::proof_for_contract(implementation::baz)] + fn check_baz() { + let mut x = 7; + implementation::baz(&mut x); + } + + #[kani::proof] + fn check_modify() {} + + #[kani::proof] + fn check_new() {} + } +} From 6a659d06318b660ff3aa821174d75671e9e22b0e Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 17 Sep 2024 16:48:15 -0400 Subject: [PATCH 14/34] add modifies rationale to rfc --- rfc/src/rfcs/0013-list.md | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index a5c95348d34d..48c626d8551b 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -260,6 +260,47 @@ We could have a more verbose or granular output, e.g., printing the metadata on If we do not implement this feature, users will have to obtain this metadata through manual searching, or by writing a script to do it themselves. This feature will improve our internal productivity by automating the process. +### Modifies Clauses +As discussed in [Software Design](#software-design), the contracts count includes `requires` and `ensures` contracts only. +We do not include `modifies` clauses for two reasons: +1. `modifies` clauses are not really "contracts" in the same way that `requires` and `ensures` are--they are important for our contracts instrumentation (c.f. [#2594](https://github.com/model-checking/kani/issues/2594)), but it's not as if Kani goes and verifies that the function actually modifies the pointer. +For example, if I write this: + +```rust +#[kani::modifies(x)] +fn foo(x: &mut u32) {} + +#[kani::proof_for_contract(foo)] +fn check_foo() { + let mut x = 7; + foo(&mut x); +} +``` +verification succeeds even though `foo` does not modify the location to which `x` points. + +2. `kani_macros` folds the arguments to each `modifies` into a single tuple, e.g.: +```rust +#[modifies(x)] +#[modifies(y)] +fn foo(x: &mut u32, y: &mut u32) {} +``` + +```rust +#[modifies(x, y)] +fn foo(x: &mut u32, y: &mut u32) {} +``` + +are indistinguishable once the macro expansion finishes. +We could of course count the modifies clauses before macro expansion, but even if we do, it's not clear whether counting the number of *attributes* or *arguments* is more intuitive. +In the latter example above, do we have two modifies clauses (one each for `x` and `y`), or just one, since we only wrote one `#[modifies]`? + +I decided it was better to not include modifies clauses to avoid confusing the user. +Thus, for functions that only have modifies clauses, the subcommand will report that the user has zero contracts. +However, users could still be confused by this choice because Kani allows users to run `proof_for_contract` harnesses on functions that only have `modifies` clauses. +(If Kani detects no contract-related attributes at all, it errors.) +A user may be confused as to why the list subcommand reports zero contracts when they are running a `proof_for_contract` harness without errors. + + ## Open questions 1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts, the number of `requires`, `ensures`, or `modifies` contracts, or the locations of the contracts. From 87328bc98a6697b55b5674a1acfdfb3c6f0d6af0 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 17 Sep 2024 16:57:57 -0400 Subject: [PATCH 15/34] copyrights --- kani-driver/src/list/collect_metadata.rs | 4 ++++ kani-driver/src/list/output.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 2c5b91268c6e..2be7e0f6d2db 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -1,3 +1,7 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// This module invokes the compiler to gather the metadata for the list subcommand, then post-processes the output. + use std::collections::BTreeMap; use crate::{ diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index dd61cb89aa93..dc1a7abf931f 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -1,3 +1,7 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// This module handles outputting the result for the list subcommand + use std::{collections::BTreeMap, fs::File, io::BufWriter}; use crate::version::KANI_VERSION; From e41c2b66f66aabded85969ac48e152f0774ee2df Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Fri, 20 Sep 2024 17:09:05 -0400 Subject: [PATCH 16/34] kani-compiler is list-agnostic --- Cargo.lock | 73 ++++++++++++++++--- kani-compiler/src/args.rs | 3 - .../compiler_interface.rs | 17 ++--- kani-compiler/src/kani_middle/attributes.rs | 13 +--- .../src/kani_middle/codegen_units.rs | 48 ++++-------- kani-compiler/src/kani_middle/list.rs | 8 +- kani-compiler/src/kani_middle/metadata.rs | 10 +-- kani-driver/src/assess/mod.rs | 4 +- kani-driver/src/call_single_file.rs | 8 +- kani-driver/src/list/collect_metadata.rs | 37 +++++----- kani-driver/src/list/mod.rs | 7 ++ kani-driver/src/list/output.rs | 23 +++--- kani-driver/src/session.rs | 15 +++- kani_metadata/Cargo.toml | 1 - 14 files changed, 145 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63f86490e284..11838e1fb6f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -185,12 +185,44 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "cli-table" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53f9241f288a7b12c56565f04aaeaeeab6b8923d42d99255d4ca428b4d97f89" +dependencies = [ + "cli-table-derive", + "csv", + "termcolor", + "unicode-width", +] + +[[package]] +name = "cli-table-derive" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e83a93253aaae7c74eb7428ce4faa6e219ba94886908048888701819f82fb94" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "colour" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b536eebcabe54980476d120a182f7da2268fe02d22575cca99cee5fdda178280" +dependencies = [ + "winapi", +] + [[package]] name = "comfy-table" version = "7.1.1" @@ -483,7 +515,7 @@ dependencies = [ "shell-words", "strum", "strum_macros", - "syn", + "syn 2.0.77", "tracing", "tracing-subscriber", "tracing-tree", @@ -496,6 +528,8 @@ dependencies = [ "anyhow", "cargo_metadata", "clap", + "cli-table", + "colour", "comfy-table", "console", "glob", @@ -541,7 +575,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -819,7 +853,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1027,7 +1061,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1036,6 +1070,7 @@ version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ + "indexmap", "itoa", "memchr", "ryu", @@ -1134,7 +1169,18 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.77", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -1161,6 +1207,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.63" @@ -1178,7 +1233,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1275,7 +1330,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1562,5 +1617,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] diff --git a/kani-compiler/src/args.rs b/kani-compiler/src/args.rs index b0cec78cc09f..3fa74b0e5aba 100644 --- a/kani-compiler/src/args.rs +++ b/kani-compiler/src/args.rs @@ -47,9 +47,6 @@ pub struct Arguments { pub reachability_analysis: ReachabilityType, #[clap(long = "enable-stubbing")] pub stubbing_enabled: bool, - /// Option name used to tell the compiler to execute the list subcommand - #[clap(long = "list")] - pub list_enabled: bool, /// Option name used to define unstable features. #[clap(short = 'Z', long = "unstable")] pub unstable_features: Vec, diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 1022d9a97b68..e57baa35f860 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -343,12 +343,8 @@ impl CodegenBackend for GotocCodegenBackend { } } ReachabilityType::None => { - // If the list subcommand is enabled, record the necessary KaniMetadata. - if queries.args().list_enabled { - let mut units: CodegenUnits = CodegenUnits::new(&queries, tcx); - collect_contracted_fns(tcx, &mut units); - units.write_metadata(&queries, tcx); - } + let units = CodegenUnits::new(&queries, tcx); + units.write_metadata(&queries, tcx); } ReachabilityType::PubFns => { let unit = CodegenUnit::default(); @@ -388,7 +384,7 @@ impl CodegenBackend for GotocCodegenBackend { write_file( &base_filename, ArtifactType::Metadata, - &results.generate_metadata(), + &results.generate_metadata(tcx), queries.args().output_pretty_json, ); } @@ -627,7 +623,7 @@ impl GotoCodegenResults { } } /// Method that generates `KaniMetadata` from the given compilation results. - pub fn generate_metadata(&self) -> KaniMetadata { + pub fn generate_metadata(&self, tcx: TyCtxt) -> KaniMetadata { // Maps the goto-context "unsupported features" data into the KaniMetadata "unsupported features" format. // TODO: Do we really need different formats?? let unsupported_features = self @@ -659,10 +655,7 @@ impl GotoCodegenResults { proof_harnesses: proofs, unsupported_features, test_harnesses: tests, - // Just leave contracted_functions empty, since we don't use this field unless we're running the - // list subcommand and that uses CodegenUnits::generate_metadata instead. - // TODO: should we consolidate these generate_metadata functions? - contracted_functions: vec![], + contracted_functions: collect_contracted_fns(tcx), } } diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 3642dcc7a5b1..9a3ff7c1d6a6 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -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, is_list_enabled: bool) -> HarnessAttributes { + pub fn harness_attributes(&self) -> 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, is_list_enabled), + KaniAttributeKind::ProofForContract => self.handle_proof_for_contract(&mut harness), 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, is_list_enabled: bool) { + fn handle_proof_for_contract(&self, harness: &mut HarnessAttributes) { let dcx = self.tcx.dcx(); let (name, id, span) = match self.interpret_for_contract_attribute() { None => return, // This error was already emitted @@ -540,13 +540,6 @@ 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; - } - if KaniAttributes::for_item(self.tcx, id).contract_attributes().is_none() { dcx.struct_span_err( span, diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index 52ce0c070c27..36c3fbd28e3d 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -9,14 +9,13 @@ use crate::args::ReachabilityType; use crate::kani_middle::attributes::is_proof_harness; +use crate::kani_middle::list::collect_contracted_fns; use crate::kani_middle::metadata::gen_proof_metadata; use crate::kani_middle::reachability::filter_crate_items; use crate::kani_middle::resolve::expect_resolve_fn; use crate::kani_middle::stubbing::{check_compatibility, harness_stub_map}; use crate::kani_queries::QueryDb; -use kani_metadata::{ - ArtifactType, AssignsContract, ContractedFunction, HarnessKind, HarnessMetadata, KaniMetadata, -}; +use kani_metadata::{ArtifactType, AssignsContract, HarnessKind, HarnessMetadata, KaniMetadata}; use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; use rustc_session::config::OutputType; @@ -48,7 +47,6 @@ pub struct CodegenUnits { units: Vec, harness_info: HashMap, crate_info: CrateInfo, - pub contracted_functions: Vec, } #[derive(Clone, Default, Debug)] @@ -60,46 +58,29 @@ pub struct CodegenUnit { impl CodegenUnits { pub fn new(queries: &QueryDb, tcx: TyCtxt) -> Self { let crate_info = CrateInfo { name: stable_mir::local_crate().name.as_str().into() }; - - if queries.args().reachability_analysis != ReachabilityType::Harnesses - && !queries.args().list_enabled - { - // Leave other reachability type handling as is for now. - return CodegenUnits { - units: vec![], - harness_info: HashMap::default(), - crate_info, - contracted_functions: vec![], - }; - } - let base_filepath = tcx.output_filenames(()).path(OutputType::Object); let base_filename = base_filepath.as_path(); let harnesses = filter_crate_items(tcx, |_, instance| is_proof_harness(tcx, instance)); let all_harnesses = harnesses .into_iter() .map(|harness| { - let metadata = - gen_proof_metadata(tcx, harness, &base_filename, queries.args().list_enabled); + let metadata = gen_proof_metadata(tcx, harness, &base_filename); (harness, metadata) }) .collect::>(); - let mut units = vec![]; if queries.args().reachability_analysis == ReachabilityType::Harnesses { // Even if no_stubs is empty we still need to store rustc metadata. - units = group_by_stubs(tcx, &all_harnesses); + let units = group_by_stubs(tcx, &all_harnesses); validate_units(tcx, &units); debug!(?units, "CodegenUnits::new"); - } - - tcx.dcx().abort_if_errors(); - - CodegenUnits { - units, - harness_info: all_harnesses, - crate_info, - contracted_functions: vec![], + CodegenUnits { units, harness_info: all_harnesses, crate_info } + } else { + // Only ReachabilityType::Harnesses uses harness_info directly, + // but we collect it for the other ReachabilityTypes so that we can write the metadata to a file. + // Then, if the user invokes the list subcommand after verification, the metadata will already be cached + // and we do not need to recompile. + CodegenUnits { units: vec![], harness_info: all_harnesses, crate_info } } } @@ -116,7 +97,7 @@ impl CodegenUnits { /// Write compilation metadata into a file. pub fn write_metadata(&self, queries: &QueryDb, tcx: TyCtxt) { - let metadata = self.generate_metadata(); + let metadata = self.generate_metadata(tcx); let outpath = metadata_output_path(tcx); store_metadata(queries, &metadata, &outpath); } @@ -126,7 +107,7 @@ impl CodegenUnits { } /// Generate [KaniMetadata] for the target crate. - fn generate_metadata(&self) -> KaniMetadata { + fn generate_metadata(&self, tcx: TyCtxt) -> KaniMetadata { let (proof_harnesses, test_harnesses) = self.harness_info.values().cloned().partition(|md| md.attributes.is_proof_harness()); KaniMetadata { @@ -134,7 +115,7 @@ impl CodegenUnits { proof_harnesses, unsupported_features: vec![], test_harnesses, - contracted_functions: self.contracted_functions.clone(), + contracted_functions: collect_contracted_fns(tcx), } } } @@ -237,6 +218,7 @@ fn validate_units(tcx: TyCtxt, units: &[CodegenUnit]) { tcx.dcx().span_err(rustc_internal::internal(tcx, span), msg); } } + tcx.dcx().abort_if_errors(); } /// Apply stub transitivity operations. diff --git a/kani-compiler/src/kani_middle/list.rs b/kani-compiler/src/kani_middle/list.rs index da344891ceb4..9d61aeb4a554 100644 --- a/kani-compiler/src/kani_middle/list.rs +++ b/kani-compiler/src/kani_middle/list.rs @@ -6,7 +6,6 @@ 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; @@ -69,7 +68,8 @@ fn count_contracts(tcx: TyCtxt, check_body: &Body) -> usize { /// 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) { +pub fn collect_contracted_fns(tcx: TyCtxt) -> Vec { + let mut contracted_fns = vec![]; for (fn_def_id, harnesses) in fns_to_harnesses(tcx) { let attrs = KaniAttributes::for_item(tcx, fn_def_id); @@ -90,11 +90,13 @@ pub fn collect_contracted_fns(tcx: TyCtxt, units: &mut CodegenUnits) { 0 }; - units.contracted_functions.push(ContractedFunction { + contracted_fns.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, }); } + + contracted_fns } diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 00fc8c4cf99b..40686362cbca 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -13,13 +13,7 @@ use stable_mir::CrateDef; use super::{attributes::KaniAttributes, SourceLocation}; -/// Create the harness metadata for a proof harness for a given function. -pub fn gen_proof_metadata( - tcx: TyCtxt, - instance: Instance, - base_name: &Path, - is_list_enabled: bool, -) -> HarnessMetadata { +pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> HarnessMetadata { let def = instance.def; let kani_attributes = KaniAttributes::for_instance(tcx, instance); let pretty_name = instance.name(); @@ -38,7 +32,7 @@ pub fn gen_proof_metadata( original_file: loc.filename, original_start_line: loc.start_line, original_end_line: loc.end_line, - attributes: kani_attributes.harness_attributes(is_list_enabled), + attributes: kani_attributes.harness_attributes(), // TODO: This no longer needs to be an Option. goto_file: Some(model_file), contract: Default::default(), diff --git a/kani-driver/src/assess/mod.rs b/kani-driver/src/assess/mod.rs index daa347246d09..f5b2ccb63035 100644 --- a/kani-driver/src/assess/mod.rs +++ b/kani-driver/src/assess/mod.rs @@ -5,10 +5,10 @@ use self::metadata::{write_metadata, AssessMetadata}; use anyhow::{bail, Result}; use kani_metadata::KaniMetadata; +use crate::assess::table_builder::TableBuilder; use crate::metadata::merge_kani_metadata; use crate::project; use crate::session::KaniSession; -use crate::{assess::table_builder::TableBuilder, session::ReachabilityMode}; pub use crate::args::{AssessArgs, AssessSubcommand}; @@ -46,7 +46,7 @@ fn assess_project(mut session: KaniSession) -> Result { session.args.unwind = Some(session.args.default_unwind.unwrap_or(1)); session.args.tests = true; session.args.output_format = crate::args::OutputFormat::Terse; - session.reachability_mode = ReachabilityMode::Tests; + session.codegen_tests = true; if session.args.jobs.is_none() { // assess will default to fully parallel instead of single-threaded. // can be overridden with e.g. `cargo kani --enable-unstable -j 8 assess` diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index 4f40506cf900..868d1adce636 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -52,7 +52,7 @@ impl KaniSession { outdir: &Path, ) -> Result<()> { let mut kani_args = self.kani_compiler_flags(); - kani_args.push(format!("--reachability={}", self.reachability_mode)); + kani_args.push(format!("--reachability={}", self.reachability_mode())); let lib_path = lib_folder().unwrap(); let mut rustc_args = self.kani_rustc_flags(LibConfig::new(lib_path)); @@ -92,7 +92,7 @@ impl KaniSession { /// Create a compiler option that represents the reachability mod. pub fn reachability_arg(&self) -> String { - to_rustc_arg(vec![format!("--reachability={}", self.reachability_mode)]) + to_rustc_arg(vec![format!("--reachability={}", self.reachability_mode())]) } /// These arguments are arguments passed to kani-compiler that are `kani` compiler specific. @@ -135,10 +135,6 @@ impl KaniSession { flags.push("--ub-check=validity".into()) } - if self.args.common_args.unstable_features.contains(UnstableFeature::List) { - flags.push("--list".into()) - } - if self.args.common_args.unstable_features.contains(UnstableFeature::UninitChecks) { // Automatically enable shadow memory, since the version of uninitialized memory checks // without non-determinism depends on it. diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 2be7e0f6d2db..028adec68444 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -6,28 +6,31 @@ use std::collections::BTreeMap; use crate::{ args::list_args::{CargoListArgs, Format, StandaloneListArgs}, + list::output::{json, pretty}, + list::Totals, project::{cargo_project, standalone_project, std_project, Project}, - session::{KaniSession, ReachabilityMode}, + session::KaniSession, version::print_kani_version, InvocationType, }; use anyhow::Result; use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; -use super::output::{json, pretty}; - fn process_metadata(metadata: Vec, format: Format) -> Result<()> { // Map each file to a vector of its harnesses let mut standard_harnesses: BTreeMap> = BTreeMap::new(); let mut contract_harnesses: BTreeMap> = BTreeMap::new(); let mut contracted_functions: Vec = vec![]; - let mut total_contracts = 0; + + let mut total_standard_harnesses = 0; let mut total_contract_harnesses = 0; + let mut total_contracts = 0; for kani_meta in metadata { for harness_meta in kani_meta.proof_harnesses { match harness_meta.attributes.kind { HarnessKind::Proof => { + total_standard_harnesses += 1; if let Some(harnesses) = standard_harnesses.get_mut(&harness_meta.original_file) { harnesses.push(harness_meta.pretty_name); @@ -37,6 +40,7 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { } } HarnessKind::ProofForContract { .. } => { + total_contract_harnesses += 1; if let Some(harnesses) = contract_harnesses.get_mut(&harness_meta.original_file) { harnesses.push(harness_meta.pretty_name); @@ -50,7 +54,6 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { } for cf in &kani_meta.contracted_functions { - total_contract_harnesses += cf.harnesses.len(); total_contracts += cf.total_contracts; } @@ -60,36 +63,34 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { // Print in alphabetical order contracted_functions.sort_by_key(|cf| cf.function.clone()); + let totals = Totals { + standard_harnesses: total_standard_harnesses, + contract_harnesses: total_contract_harnesses, + contracted_functions: contracted_functions.len(), + contracts: total_contracts, + }; + match format { - Format::Pretty => pretty( - standard_harnesses, - contracted_functions, - total_contract_harnesses, - total_contracts, - ), - Format::Json => { - json(standard_harnesses, contract_harnesses, contracted_functions, total_contracts) - } + Format::Pretty => pretty(standard_harnesses, contracted_functions, totals), + Format::Json => json(standard_harnesses, contract_harnesses, contracted_functions, totals), } } pub fn list_cargo(args: CargoListArgs) -> Result<()> { - let mut session = KaniSession::new(args.verify_opts)?; + let session = KaniSession::new(args.verify_opts)?; if !session.args.common_args.quiet { print_kani_version(InvocationType::CargoKani(vec![])); } - session.reachability_mode = ReachabilityMode::None; let project = cargo_project(&session, false)?; process_metadata(project.metadata, args.format) } pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { - let mut session = KaniSession::new(args.verify_opts)?; + let session = KaniSession::new(args.verify_opts)?; if !session.args.common_args.quiet { print_kani_version(InvocationType::Standalone); } - session.reachability_mode = ReachabilityMode::None; let project: Project = if args.std { std_project(&args.input, &session)? diff --git a/kani-driver/src/list/mod.rs b/kani-driver/src/list/mod.rs index 193789bca6e7..74108c93fceb 100644 --- a/kani-driver/src/list/mod.rs +++ b/kani-driver/src/list/mod.rs @@ -4,3 +4,10 @@ pub mod collect_metadata; mod output; + +struct Totals { + standard_harnesses: usize, + contract_harnesses: usize, + contracted_functions: usize, + contracts: usize, +} diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index dc1a7abf931f..80f134ee5dbf 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -4,7 +4,7 @@ use std::{collections::BTreeMap, fs::File, io::BufWriter}; -use crate::version::KANI_VERSION; +use crate::{list::Totals, version::KANI_VERSION}; use anyhow::Result; use cli_table::{print_stdout, Cell, CellStruct, Style, Table}; use colour::print_ln_bold; @@ -18,11 +18,8 @@ const FILE_VERSION: &str = "0.1"; pub fn pretty( standard_harnesses: BTreeMap>, contracted_functions: Vec, - total_contract_harnesses: usize, - total_contracts: usize, + totals: Totals, ) -> Result<()> { - let total_contracted_functions = contracted_functions.len(); - fn format_contract_harnesses(harnesses: &mut [String]) -> String { harnesses.sort(); let joined = harnesses.join("\n"); @@ -50,9 +47,9 @@ pub fn pretty( contracts_table.push(vec![ "Total".cell().bold(true), - total_contracted_functions.cell(), - total_contracts.cell(), - total_contract_harnesses.cell(), + totals.contracted_functions.cell(), + totals.contracts.cell(), + totals.contract_harnesses.cell(), ]); print_stdout(contracts_table.table().title(vec![ @@ -86,7 +83,7 @@ pub fn json( standard_harnesses: BTreeMap>, contract_harnesses: BTreeMap>, contracted_functions: Vec, - total_contracts: usize, + totals: Totals, ) -> Result<()> { let filename = "kani-list.json"; @@ -100,10 +97,10 @@ pub fn json( "contract-harnesses": &contract_harnesses, "contracts": &contracted_functions, "totals": { - "standard-harnesses": standard_harnesses.len(), - "contract-harnesses": contract_harnesses.len(), - "functions-under-contract": contracted_functions.len(), - "contracts": total_contracts, + "standard-harnesses": totals.standard_harnesses, + "contract-harnesses": totals.contract_harnesses, + "functions-under-contract": totals.contracted_functions, + "contracts": totals.contracts, } }); diff --git a/kani-driver/src/session.rs b/kani-driver/src/session.rs index dda5d2603e2f..03cfb108e324 100644 --- a/kani-driver/src/session.rs +++ b/kani-driver/src/session.rs @@ -26,8 +26,10 @@ pub struct KaniSession { /// The common command-line arguments pub args: VerificationArgs, - /// The reachability mode to use for code generation. - pub reachability_mode: ReachabilityMode, + /// Include all publicly-visible symbols in the generated goto binary, not just those reachable from + /// a proof harness. Useful when attempting to verify things that were not annotated with kani + /// proof attributes. + pub codegen_tests: bool, /// The location we found the 'kani_rustc' command pub kani_compiler: PathBuf, @@ -55,7 +57,7 @@ impl KaniSession { Ok(KaniSession { args, - reachability_mode: ReachabilityMode::ProofHarnesses, + codegen_tests: false, kani_compiler: install.kani_compiler()?, kani_lib_c: install.kani_lib_c()?, temporaries: Mutex::new(vec![]), @@ -76,6 +78,12 @@ impl KaniSession { let mut t = self.temporaries.lock().unwrap(); t.extend(temps.iter().map(|p| p.as_ref().to_owned())); } + + /// Determine which symbols Kani should codegen (i.e. by slicing away symbols + /// that are considered unreachable.) + pub fn reachability_mode(&self) -> ReachabilityMode { + if self.codegen_tests { ReachabilityMode::Tests } else { ReachabilityMode::ProofHarnesses } + } } #[derive(Debug, Copy, Clone, Display)] @@ -84,7 +92,6 @@ pub enum ReachabilityMode { #[strum(to_string = "harnesses")] ProofHarnesses, Tests, - None, } impl Drop for KaniSession { diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index 638fb0b7ae09..18eadc4095ed 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -16,7 +16,6 @@ cbmc = { path = "../cprover_bindings", package = "cprover_bindings" } strum = "0.26" strum_macros = "0.26" clap = { version = "4.4.11", features = ["derive"] } -cli-table = "0.4.9" [lints] workspace = true From 5d0edb8e696c91be80a62f403d41d7e9fac3d5f5 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Fri, 20 Sep 2024 19:29:22 -0400 Subject: [PATCH 17/34] check explicitly that fn resolves --- kani-compiler/src/kani_middle/list.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/kani-compiler/src/kani_middle/list.rs b/kani-compiler/src/kani_middle/list.rs index 9d61aeb4a554..5d03bb86cb20 100644 --- a/kani-compiler/src/kani_middle/list.rs +++ b/kani-compiler/src/kani_middle/list.rs @@ -53,12 +53,13 @@ fn count_contracts(tcx: TyCtxt, check_body: &Body) -> usize { 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; + if let Ok(instance) = Instance::resolve(fn_def, &args) { + // For each precondition or postcondition, increment the count + if matches_function(tcx, instance.def, "KaniAssume") + || matches_function(tcx, instance.def, "KaniAssert") + { + count += 1; + } } } } From e5de12cde4eba8f47755166d24e0f2c8067d8c67 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 26 Sep 2024 17:34:24 -0400 Subject: [PATCH 18/34] change tests to json; sort output --- .../compiler_interface.rs | 15 ++- .../src/kani_middle/codegen_units.rs | 5 +- kani-compiler/src/kani_middle/list.rs | 103 ------------------ kani-compiler/src/kani_middle/metadata.rs | 90 ++++++++++++++- kani-compiler/src/kani_middle/mod.rs | 1 - kani-driver/src/list/collect_metadata.rs | 19 ++-- kani-driver/src/list/output.rs | 17 ++- kani_metadata/src/lib.rs | 2 +- tests/script-based-pre/cargo_list/Cargo.toml | 2 +- tests/script-based-pre/cargo_list/config.yml | 2 +- .../cargo_list/list-pretty.expected | 36 ------ .../script-based-pre/cargo_list/list.expected | 57 ++++++++++ tests/script-based-pre/cargo_list/list.sh | 8 +- tests/script-based-pre/cargo_list/src/lib.rs | 32 ++---- tests/script-based-pre/kani_list/config.yml | 2 +- .../kani_list/list-pretty.expected | 36 ------ .../script-based-pre/kani_list/list.expected | 57 ++++++++++ tests/script-based-pre/kani_list/list.sh | 8 +- tests/script-based-pre/kani_list/src/lib.rs | 24 ++-- 19 files changed, 258 insertions(+), 258 deletions(-) delete mode 100644 kani-compiler/src/kani_middle/list.rs delete mode 100644 tests/script-based-pre/cargo_list/list-pretty.expected create mode 100644 tests/script-based-pre/cargo_list/list.expected delete mode 100644 tests/script-based-pre/kani_list/list-pretty.expected create mode 100644 tests/script-based-pre/kani_list/list.expected diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index e57baa35f860..bb82d6403627 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -9,7 +9,6 @@ use crate::kani_middle::analysis; use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; use crate::kani_middle::check_reachable_items; use crate::kani_middle::codegen_units::{CodegenUnit, CodegenUnits}; -use crate::kani_middle::list::collect_contracted_fns; use crate::kani_middle::metadata::gen_test_metadata; use crate::kani_middle::provide; use crate::kani_middle::reachability::{ @@ -342,10 +341,7 @@ impl CodegenBackend for GotocCodegenBackend { results.harnesses.push(metadata); } } - ReachabilityType::None => { - let units = CodegenUnits::new(&queries, tcx); - units.write_metadata(&queries, tcx); - } + ReachabilityType::None => {} ReachabilityType::PubFns => { let unit = CodegenUnit::default(); let transformer = BodyTransformation::new(&queries, tcx, &unit); @@ -384,7 +380,7 @@ impl CodegenBackend for GotocCodegenBackend { write_file( &base_filename, ArtifactType::Metadata, - &results.generate_metadata(tcx), + &results.generate_metadata(), queries.args().output_pretty_json, ); } @@ -623,7 +619,7 @@ impl GotoCodegenResults { } } /// Method that generates `KaniMetadata` from the given compilation results. - pub fn generate_metadata(&self, tcx: TyCtxt) -> KaniMetadata { + pub fn generate_metadata(&self) -> KaniMetadata { // Maps the goto-context "unsupported features" data into the KaniMetadata "unsupported features" format. // TODO: Do we really need different formats?? let unsupported_features = self @@ -655,7 +651,10 @@ impl GotoCodegenResults { proof_harnesses: proofs, unsupported_features, test_harnesses: tests, - contracted_functions: collect_contracted_fns(tcx), + // We don't collect the functions under contract because the logic assumes that, if contract attributes are present, + // they are well-formed, which Kani only checks if we run verification or the list subcommand (i.e., for ReachabilityType::Harnesses). + // In those cases, we use the CodegenUnits object to generate the metadata instead of this function. + contracted_functions: vec![], } } diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index 36c3fbd28e3d..ce9fc7e0ed09 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -9,8 +9,7 @@ use crate::args::ReachabilityType; use crate::kani_middle::attributes::is_proof_harness; -use crate::kani_middle::list::collect_contracted_fns; -use crate::kani_middle::metadata::gen_proof_metadata; +use crate::kani_middle::metadata::{gen_proof_metadata, gen_contracts_metadata}; use crate::kani_middle::reachability::filter_crate_items; use crate::kani_middle::resolve::expect_resolve_fn; use crate::kani_middle::stubbing::{check_compatibility, harness_stub_map}; @@ -115,7 +114,7 @@ impl CodegenUnits { proof_harnesses, unsupported_features: vec![], test_harnesses, - contracted_functions: collect_contracted_fns(tcx), + contracted_functions: gen_contracts_metadata(tcx), } } } diff --git a/kani-compiler/src/kani_middle/list.rs b/kani-compiler/src/kani_middle/list.rs deleted file mode 100644 index 5d03bb86cb20..000000000000 --- a/kani-compiler/src/kani_middle/list.rs +++ /dev/null @@ -1,103 +0,0 @@ -// 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::{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> { - // 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> = 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() { - if let Ok(instance) = Instance::resolve(fn_def, &args) { - // 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) -> Vec { - let mut contracted_fns = vec![]; - 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 - }; - - contracted_fns.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, - }); - } - - contracted_fns -} diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 40686362cbca..30b9851ec1af 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -3,15 +3,22 @@ //! This module handles Kani metadata generation. For example, generating HarnessMetadata for a //! given function. +use std::collections::HashMap; use std::path::Path; use crate::kani_middle::attributes::test_harness_name; +use crate::kani_middle::attributes::{ + matches_diagnostic as matches_function, ContractAttributes, KaniAttributes, +}; +use crate::kani_middle::{find_closure_in_body, InternalDefId, SourceLocation}; +use kani_metadata::ContractedFunction; use kani_metadata::{ArtifactType, HarnessAttributes, HarnessKind, HarnessMetadata}; use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; -use stable_mir::CrateDef; - -use super::{attributes::KaniAttributes, SourceLocation}; +use stable_mir::mir::{Body, TerminatorKind}; +use stable_mir::ty::{RigidTy, TyKind}; +use stable_mir::{CrateDef, CrateItems}; pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> HarnessMetadata { let def = instance.def; @@ -39,6 +46,83 @@ pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> } } +/// Map each function under contract to its contract harnesses +fn fns_to_contract_harnesses(tcx: TyCtxt) -> HashMap> { + // 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> = 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() { + if let Ok(instance) = Instance::resolve(fn_def, &args) { + // For each precondition or postcondition, increment the count + if matches_function(tcx, instance.def, "KaniAssume") + || matches_function(tcx, instance.def, "KaniAssert") + { + count += 1; + } + } + } + } + } + count +} + +/// Collects contract and contract harness metadata. +/// +/// For each function with contracts (or that is a target of a contract harness), +/// construct a ContractedFunction object for it. +pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec { + let mut contracted_fns = vec![]; + for (fn_def_id, harnesses) in fns_to_contract_harnesses(tcx) { + let attrs: ContractAttributes = + 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, attrs.checked_with.as_str()).unwrap().body().unwrap(); + + let total_contracts = count_contracts(tcx, &check_body); + + contracted_fns.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, + }); + } + + contracted_fns +} + /// Create the harness metadata for a test description. #[allow(dead_code)] pub fn gen_test_metadata( diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index 0d99ba08ce85..ec16b4cd9449 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -32,7 +32,6 @@ pub mod attributes; pub mod codegen_units; pub mod coercion; mod intrinsics; -pub mod list; pub mod metadata; pub mod points_to; pub mod provide; diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 028adec68444..d74cf60d71e0 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // This module invokes the compiler to gather the metadata for the list subcommand, then post-processes the output. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use crate::{ args::list_args::{CargoListArgs, Format, StandaloneListArgs}, @@ -18,9 +18,9 @@ use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; fn process_metadata(metadata: Vec, format: Format) -> Result<()> { // Map each file to a vector of its harnesses - let mut standard_harnesses: BTreeMap> = BTreeMap::new(); - let mut contract_harnesses: BTreeMap> = BTreeMap::new(); - let mut contracted_functions: Vec = vec![]; + let mut standard_harnesses: BTreeMap> = BTreeMap::new(); + let mut contract_harnesses: BTreeMap> = BTreeMap::new(); + let mut contracted_functions: BTreeSet = BTreeSet::new(); let mut total_standard_harnesses = 0; let mut total_contract_harnesses = 0; @@ -33,20 +33,20 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { total_standard_harnesses += 1; if let Some(harnesses) = standard_harnesses.get_mut(&harness_meta.original_file) { - harnesses.push(harness_meta.pretty_name); + harnesses.insert(harness_meta.pretty_name); } else { standard_harnesses - .insert(harness_meta.original_file, vec![harness_meta.pretty_name]); + .insert(harness_meta.original_file, BTreeSet::from([harness_meta.pretty_name])); } } HarnessKind::ProofForContract { .. } => { total_contract_harnesses += 1; if let Some(harnesses) = contract_harnesses.get_mut(&harness_meta.original_file) { - harnesses.push(harness_meta.pretty_name); + harnesses.insert(harness_meta.pretty_name); } else { contract_harnesses - .insert(harness_meta.original_file, vec![harness_meta.pretty_name]); + .insert(harness_meta.original_file, BTreeSet::from([harness_meta.pretty_name])); } } HarnessKind::Test => {} @@ -60,9 +60,6 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { contracted_functions.extend(kani_meta.contracted_functions.into_iter()); } - // Print in alphabetical order - contracted_functions.sort_by_key(|cf| cf.function.clone()); - let totals = Totals { standard_harnesses: total_standard_harnesses, contract_harnesses: total_contract_harnesses, diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index 80f134ee5dbf..d71b69bf7422 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // This module handles outputting the result for the list subcommand -use std::{collections::BTreeMap, fs::File, io::BufWriter}; +use std::{collections::{BTreeMap, BTreeSet}, fs::File, io::BufWriter}; use crate::{list::Totals, version::KANI_VERSION}; use anyhow::Result; @@ -14,10 +14,11 @@ use serde_json::json; // Represents the version of our JSON file format. // Increment this version (according to semantic versioning rules) whenever the JSON output format changes. const FILE_VERSION: &str = "0.1"; +const JSON_FILENAME: &str = "kani-list.json"; pub fn pretty( - standard_harnesses: BTreeMap>, - contracted_functions: Vec, + standard_harnesses: BTreeMap>, + contracted_functions: BTreeSet, totals: Totals, ) -> Result<()> { fn format_contract_harnesses(harnesses: &mut [String]) -> String { @@ -80,14 +81,12 @@ pub fn pretty( } pub fn json( - standard_harnesses: BTreeMap>, - contract_harnesses: BTreeMap>, - contracted_functions: Vec, + standard_harnesses: BTreeMap>, + contract_harnesses: BTreeMap>, + contracted_functions: BTreeSet, totals: Totals, ) -> Result<()> { - let filename = "kani-list.json"; - - let out_file = File::create(filename).unwrap(); + let out_file = File::create(JSON_FILENAME).unwrap(); let writer = BufWriter::new(out_file); let json_obj = json!({ diff --git a/kani_metadata/src/lib.rs b/kani_metadata/src/lib.rs index 0a7229d18635..3906b41ff89e 100644 --- a/kani_metadata/src/lib.rs +++ b/kani_metadata/src/lib.rs @@ -35,7 +35,7 @@ pub struct KaniMetadata { pub contracted_functions: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] pub struct ContractedFunction { /// The fully qualified name the user gave to the function (i.e. includes the module path). pub function: String, diff --git a/tests/script-based-pre/cargo_list/Cargo.toml b/tests/script-based-pre/cargo_list/Cargo.toml index 00f6ef48965e..487d2356f381 100644 --- a/tests/script-based-pre/cargo_list/Cargo.toml +++ b/tests/script-based-pre/cargo_list/Cargo.toml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [package] -name = "sample_crate" +name = "cargo_list" version = "0.1.0" edition = "2021" diff --git a/tests/script-based-pre/cargo_list/config.yml b/tests/script-based-pre/cargo_list/config.yml index 816e3413c65f..ce681685e7c2 100644 --- a/tests/script-based-pre/cargo_list/config.yml +++ b/tests/script-based-pre/cargo_list/config.yml @@ -1,4 +1,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT script: list.sh -expected: list-pretty.expected +expected: list.expected \ No newline at end of file diff --git a/tests/script-based-pre/cargo_list/list-pretty.expected b/tests/script-based-pre/cargo_list/list-pretty.expected deleted file mode 100644 index e86fd0d595b2..000000000000 --- a/tests/script-based-pre/cargo_list/list-pretty.expected +++ /dev/null @@ -1,36 +0,0 @@ -Contracts: -Each function in the table below either has contracts or is the target of a contract harness (#[kani::proof_for_contract]). - -Function -# of Contracts -Contract Harnesses - -example::implementation::bar -4 -example::verify::check_bar - -example::implementation::baz -0 -example::verify::check_baz - -example::implementation::foo -2 -example::verify::check_foo_u32 - -example::verify::check_foo_u64 -example::implementation::func -1 -example::verify::check_func - -example::prep::parse -1 -NONE - -Total -5 -8 -5 - -Standard Harnesses (#[kani::proof]): -1. standard_harnesses::example::verify::check_modify -2. standard_harnesses::example::verify::check_new \ No newline at end of file diff --git a/tests/script-based-pre/cargo_list/list.expected b/tests/script-based-pre/cargo_list/list.expected new file mode 100644 index 000000000000..3e57203438dc --- /dev/null +++ b/tests/script-based-pre/cargo_list/list.expected @@ -0,0 +1,57 @@ +{ + "kani-version": + "file-version": "0.1", + "standard-harnesses": { + "src/standard_harnesses.rs": [ + "standard_harnesses::example::verify::check_modify", + "standard_harnesses::example::verify::check_new" + ] + }, + "contract-harnesses": { + "src/lib.rs": [ + "example::verify::check_bar", + "example::verify::check_foo_u32", + "example::verify::check_foo_u64", + "example::verify::check_func" + ] + }, + "contracts": [ + { + "function": "example::implementation::bar", + "file": "src/lib.rs", + "total_contracts": 4, + "harnesses": [ + "example::verify::check_bar" + ] + }, + { + "function": "example::implementation::foo", + "file": "src/lib.rs", + "total_contracts": 2, + "harnesses": [ + "example::verify::check_foo_u32", + "example::verify::check_foo_u64" + ] + }, + { + "function": "example::implementation::func", + "file": "src/lib.rs", + "total_contracts": 1, + "harnesses": [ + "example::verify::check_func" + ] + }, + { + "function": "example::prep::parse", + "file": "src/lib.rs", + "total_contracts": 1, + "harnesses": [] + } + ], + "totals": { + "standard-harnesses": 2, + "contract-harnesses": 4, + "functions-under-contract": 4, + "contracts": 8 + } +} \ No newline at end of file diff --git a/tests/script-based-pre/cargo_list/list.sh b/tests/script-based-pre/cargo_list/list.sh index c394e8dff8de..6b1ab80b0f5f 100755 --- a/tests/script-based-pre/cargo_list/list.sh +++ b/tests/script-based-pre/cargo_list/list.sh @@ -2,7 +2,9 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -# The table in the expected omits the table borders because the runtest script -# does not evaluate the table borders in the captured output as equal to the table borders in the expected file. +# Check that the JSON file produced by `cargo kani list` is correct. +# Note that the list.expected file omits the value for "kani-version" +# to avoid having to update the test every time we bump versions. -cargo kani list -Z list \ No newline at end of file +cargo kani list -Z list -Z function-contracts --format json +cat "kani-list.json" diff --git a/tests/script-based-pre/cargo_list/src/lib.rs b/tests/script-based-pre/cargo_list/src/lib.rs index 82044b6d7ea5..e7382a9124a3 100644 --- a/tests/script-based-pre/cargo_list/src/lib.rs +++ b/tests/script-based-pre/cargo_list/src/lib.rs @@ -8,6 +8,13 @@ mod standard_harnesses; #[cfg(kani)] mod example { + mod prep { + #[kani::requires(s.len() < 10)] + fn parse(s: &str) -> u32 { + s.parse().unwrap() + } + } + pub mod implementation { #[kani::requires(*x < 4)] #[kani::requires(*x > 2)] @@ -18,27 +25,16 @@ mod example { *x += 1; } - #[kani::requires(true)] - #[kani::ensures(|_| old(*x) == *x)] - pub fn foo(x: &mut T) -> T { - *x - } - #[kani::requires(*x < 100)] #[kani::modifies(x)] pub fn func(x: &mut i32) { *x *= 1; } - pub fn baz(x: &mut i32) { - *x /= 1; - } - } - - mod prep { - #[kani::requires(s.len() < 10)] - fn parse(s: &str) -> u32 { - s.parse().unwrap() + #[kani::requires(true)] + #[kani::ensures(|_| old(*x) == *x)] + pub fn foo(x: &mut T) -> T { + *x } } @@ -68,11 +64,5 @@ mod example { let mut x = 7; implementation::func(&mut x); } - - #[kani::proof_for_contract(implementation::baz)] - fn check_baz() { - let mut x = 7; - implementation::baz(&mut x); - } } } diff --git a/tests/script-based-pre/kani_list/config.yml b/tests/script-based-pre/kani_list/config.yml index 816e3413c65f..4eac6f79588c 100644 --- a/tests/script-based-pre/kani_list/config.yml +++ b/tests/script-based-pre/kani_list/config.yml @@ -1,4 +1,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT script: list.sh -expected: list-pretty.expected +expected: list.expected diff --git a/tests/script-based-pre/kani_list/list-pretty.expected b/tests/script-based-pre/kani_list/list-pretty.expected deleted file mode 100644 index 5d22cd0e7eb9..000000000000 --- a/tests/script-based-pre/kani_list/list-pretty.expected +++ /dev/null @@ -1,36 +0,0 @@ -Contracts: -Each function in the table below either has contracts or is the target of a contract harness (#[kani::proof_for_contract]). - -Function -# of Contracts -Contract Harnesses - -example::implementation::bar -4 -example::verify::check_bar - -example::implementation::baz -0 -example::verify::check_baz - -example::implementation::foo -2 -example::verify::check_foo_u32 - -example::verify::check_foo_u64 -example::implementation::func -1 -example::verify::check_func - -example::prep::parse -1 -NONE - -Total -5 -8 -5 - -Standard Harnesses (#[kani::proof]): -1. example::verify::check_modify -2. example::verify::check_new \ No newline at end of file diff --git a/tests/script-based-pre/kani_list/list.expected b/tests/script-based-pre/kani_list/list.expected new file mode 100644 index 000000000000..806a2d0e7550 --- /dev/null +++ b/tests/script-based-pre/kani_list/list.expected @@ -0,0 +1,57 @@ +{ + "kani-version": + "file-version": "0.1", + "standard-harnesses": { + "src/lib.rs": [ + "example::verify::check_modify", + "example::verify::check_new" + ] + }, + "contract-harnesses": { + "src/lib.rs": [ + "example::verify::check_bar", + "example::verify::check_foo_u32", + "example::verify::check_foo_u64", + "example::verify::check_func" + ] + }, + "contracts": [ + { + "function": "example::implementation::bar", + "file": "src/lib.rs", + "total_contracts": 4, + "harnesses": [ + "example::verify::check_bar" + ] + }, + { + "function": "example::implementation::foo", + "file": "src/lib.rs", + "total_contracts": 2, + "harnesses": [ + "example::verify::check_foo_u32", + "example::verify::check_foo_u64" + ] + }, + { + "function": "example::implementation::func", + "file": "src/lib.rs", + "total_contracts": 1, + "harnesses": [ + "example::verify::check_func" + ] + }, + { + "function": "example::prep::parse", + "file": "src/lib.rs", + "total_contracts": 1, + "harnesses": [] + } + ], + "totals": { + "standard-harnesses": 2, + "contract-harnesses": 4, + "functions-under-contract": 4, + "contracts": 8 + } +} \ No newline at end of file diff --git a/tests/script-based-pre/kani_list/list.sh b/tests/script-based-pre/kani_list/list.sh index bbd852a8b054..f969709e081d 100755 --- a/tests/script-based-pre/kani_list/list.sh +++ b/tests/script-based-pre/kani_list/list.sh @@ -2,7 +2,9 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT -# The table in the expected omits the table borders because the runtest script -# does not evaluate the table borders in the captured output as equal to the table borders in the expected file. +# Check that the JSON file produced by `kani list` is correct. +# Note that the list.expected file omits the value for "kani-version" +# to avoid having to update the test every time we bump versions. -kani list -Z list src/lib.rs \ No newline at end of file +kani list -Z list -Z function-contracts src/lib.rs --format json +cat "kani-list.json" \ No newline at end of file diff --git a/tests/script-based-pre/kani_list/src/lib.rs b/tests/script-based-pre/kani_list/src/lib.rs index 16bab7aaaebc..69dbba5a9e0f 100644 --- a/tests/script-based-pre/kani_list/src/lib.rs +++ b/tests/script-based-pre/kani_list/src/lib.rs @@ -5,6 +5,13 @@ //! It ensures that the list command across modules, and with modifies clauses, history expressions, and generic functions. mod example { + mod prep { + #[kani::requires(s.len() < 10)] + fn parse(s: &str) -> u32 { + s.parse().unwrap() + } + } + pub mod implementation { #[kani::requires(*x < 4)] #[kani::requires(*x > 2)] @@ -26,17 +33,6 @@ mod example { pub fn func(x: &mut i32) { *x *= 1; } - - pub fn baz(x: &mut i32) { - *x /= 1; - } - } - - mod prep { - #[kani::requires(s.len() < 10)] - fn parse(s: &str) -> u32 { - s.parse().unwrap() - } } mod verify { @@ -66,12 +62,6 @@ mod example { implementation::func(&mut x); } - #[kani::proof_for_contract(implementation::baz)] - fn check_baz() { - let mut x = 7; - implementation::baz(&mut x); - } - #[kani::proof] fn check_modify() {} From 7a525034ec4e3b264f81cdf5e0b7a68d23cd82d7 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 26 Sep 2024 17:35:15 -0400 Subject: [PATCH 19/34] clippy --- kani-compiler/src/kani_middle/codegen_units.rs | 2 +- kani-driver/src/list/collect_metadata.rs | 12 ++++++++---- kani-driver/src/list/output.rs | 6 +++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index ce9fc7e0ed09..709f6009deea 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -9,7 +9,7 @@ use crate::args::ReachabilityType; use crate::kani_middle::attributes::is_proof_harness; -use crate::kani_middle::metadata::{gen_proof_metadata, gen_contracts_metadata}; +use crate::kani_middle::metadata::{gen_contracts_metadata, gen_proof_metadata}; use crate::kani_middle::reachability::filter_crate_items; use crate::kani_middle::resolve::expect_resolve_fn; use crate::kani_middle::stubbing::{check_compatibility, harness_stub_map}; diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index d74cf60d71e0..437ae9ab11ef 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -35,8 +35,10 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { { harnesses.insert(harness_meta.pretty_name); } else { - standard_harnesses - .insert(harness_meta.original_file, BTreeSet::from([harness_meta.pretty_name])); + standard_harnesses.insert( + harness_meta.original_file, + BTreeSet::from([harness_meta.pretty_name]), + ); } } HarnessKind::ProofForContract { .. } => { @@ -45,8 +47,10 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { { harnesses.insert(harness_meta.pretty_name); } else { - contract_harnesses - .insert(harness_meta.original_file, BTreeSet::from([harness_meta.pretty_name])); + contract_harnesses.insert( + harness_meta.original_file, + BTreeSet::from([harness_meta.pretty_name]), + ); } } HarnessKind::Test => {} diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index d71b69bf7422..d0882949430a 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -2,7 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // This module handles outputting the result for the list subcommand -use std::{collections::{BTreeMap, BTreeSet}, fs::File, io::BufWriter}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fs::File, + io::BufWriter, +}; use crate::{list::Totals, version::KANI_VERSION}; use anyhow::Result; From 73834458fb0fd4d8e957e4fd06fba30b225c10c6 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 26 Sep 2024 21:29:03 -0400 Subject: [PATCH 20/34] clippy --- kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs | 4 ++-- kani-compiler/src/kani_middle/metadata.rs | 4 ++-- kani-compiler/src/kani_middle/mod.rs | 2 +- kani-driver/src/args/list_args.rs | 2 +- kani-driver/src/list/collect_metadata.rs | 6 +++--- kani-driver/src/list/output.rs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index f46887a67ef6..01313487cc82 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -9,9 +9,9 @@ use kani_metadata::AssignsContract; use rustc_hir::def_id::DefId as InternalDefId; use rustc_smir::rustc_internal; use stable_mir::CrateDef; +use stable_mir::mir::Local; use stable_mir::mir::mono::{Instance, MonoItem}; -use stable_mir::mir::{Local, VarDebugInfoContents}; -use stable_mir::ty::{FnDef, RigidTy, TyKind}; +use stable_mir::ty::{RigidTy, TyKind}; impl<'tcx> GotocCtx<'tcx> { /// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`, diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 06985e750411..14cd3459cd06 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -8,9 +8,9 @@ use std::path::Path; use crate::kani_middle::attributes::test_harness_name; use crate::kani_middle::attributes::{ - matches_diagnostic as matches_function, ContractAttributes, KaniAttributes, + ContractAttributes, KaniAttributes, matches_diagnostic as matches_function, }; -use crate::kani_middle::{find_closure_in_body, InternalDefId, SourceLocation}; +use crate::kani_middle::{InternalDefId, SourceLocation, find_closure_in_body}; use kani_metadata::ContractedFunction; use kani_metadata::{ArtifactType, HarnessAttributes, HarnessKind, HarnessMetadata}; use rustc_middle::ty::TyCtxt; diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index f5f6288fc701..2190207ee5de 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -18,9 +18,9 @@ use rustc_span::Span; use rustc_span::source_map::respan; use rustc_target::abi::call::FnAbi; use rustc_target::abi::{HasDataLayout, TargetDataLayout}; +use stable_mir::CrateDef; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::mir::{Body, VarDebugInfoContents}; -use stable_mir::CrateDef; use stable_mir::ty::{FnDef, RigidTy, Span as SpanStable, Ty, TyKind}; use stable_mir::visitor::{Visitable, Visitor as TyVisitor}; use std::ops::ControlFlow; diff --git a/kani-driver/src/args/list_args.rs b/kani-driver/src/args/list_args.rs index 0b7a80875f3a..7129bd0a85c4 100644 --- a/kani-driver/src/args/list_args.rs +++ b/kani-driver/src/args/list_args.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use crate::args::ValidateArgs; -use clap::{error::ErrorKind, Error, Parser, ValueEnum}; +use clap::{Error, Parser, ValueEnum, error::ErrorKind}; use kani_metadata::UnstableFeature; use super::VerificationArgs; diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 437ae9ab11ef..708ffbb35db4 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -5,13 +5,13 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::{ + InvocationType, args::list_args::{CargoListArgs, Format, StandaloneListArgs}, - list::output::{json, pretty}, list::Totals, - project::{cargo_project, standalone_project, std_project, Project}, + list::output::{json, pretty}, + project::{Project, cargo_project, standalone_project, std_project}, session::KaniSession, version::print_kani_version, - InvocationType, }; use anyhow::Result; use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index d0882949430a..e8a937596533 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -10,7 +10,7 @@ use std::{ use crate::{list::Totals, version::KANI_VERSION}; use anyhow::Result; -use cli_table::{print_stdout, Cell, CellStruct, Style, Table}; +use cli_table::{Cell, CellStruct, Style, Table, print_stdout}; use colour::print_ln_bold; use kani_metadata::ContractedFunction; use serde_json::json; From 55c9937562c85bc4dded1aa3d68a75d7be39591c Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 26 Sep 2024 22:10:50 -0400 Subject: [PATCH 21/34] revert unnecessary changes --- .../src/kani_middle/codegen_units.rs | 29 +++++++++---------- kani-driver/src/project.rs | 13 --------- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index c6c35c407716..ebb12f7656b6 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -57,29 +57,26 @@ pub struct CodegenUnit { impl CodegenUnits { pub fn new(queries: &QueryDb, tcx: TyCtxt) -> Self { let crate_info = CrateInfo { name: stable_mir::local_crate().name.as_str().into() }; - let base_filepath = tcx.output_filenames(()).path(OutputType::Object); - let base_filename = base_filepath.as_path(); - let harnesses = filter_crate_items(tcx, |_, instance| is_proof_harness(tcx, instance)); - let all_harnesses = harnesses - .into_iter() - .map(|harness| { - let metadata = gen_proof_metadata(tcx, harness, &base_filename); - (harness, metadata) - }) - .collect::>(); - if queries.args().reachability_analysis == ReachabilityType::Harnesses { + let base_filepath = tcx.output_filenames(()).path(OutputType::Object); + let base_filename = base_filepath.as_path(); + let harnesses = filter_crate_items(tcx, |_, instance| is_proof_harness(tcx, instance)); + let all_harnesses = harnesses + .into_iter() + .map(|harness| { + let metadata = gen_proof_metadata(tcx, harness, &base_filename); + (harness, metadata) + }) + .collect::>(); + // Even if no_stubs is empty we still need to store rustc metadata. let units = group_by_stubs(tcx, &all_harnesses); validate_units(tcx, &units); debug!(?units, "CodegenUnits::new"); CodegenUnits { units, harness_info: all_harnesses, crate_info } } else { - // Only ReachabilityType::Harnesses uses harness_info directly, - // but we collect it for the other ReachabilityTypes so that we can write the metadata to a file. - // Then, if the user invokes the list subcommand after verification, the metadata will already be cached - // and we do not need to recompile. - CodegenUnits { units: vec![], harness_info: all_harnesses, crate_info } + // Leave other reachability type handling as is for now. + CodegenUnits { units: vec![], harness_info: HashMap::default(), crate_info } } } diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index 9aed31683bd6..363050958d61 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -8,7 +8,6 @@ use crate::metadata::from_json; use crate::session::KaniSession; use crate::util::crate_name; use anyhow::{Context, Result}; -use kani_metadata::UnstableFeature; use kani_metadata::{ ArtifactType, ArtifactType::*, HarnessMetadata, KaniMetadata, artifact::convert_type, }; @@ -91,18 +90,6 @@ impl Project { cargo_metadata: Option, failed_targets: Option>, ) -> Result { - // For the list subcommand, we do not generate any goto, so skip extending the artifacts - if session.args.common_args.unstable_features.contains(UnstableFeature::List) { - return Ok(Project { - outdir, - input, - metadata, - artifacts: vec![], - cargo_metadata, - failed_targets, - }); - } - // For each harness (test or proof) from each metadata, read the path for the goto // SymTabGoto file. Use that path to find all the other artifacts. let mut artifacts = vec![]; From a85ec2c6fd65242d70184c98831829b6cb79333b Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Fri, 27 Sep 2024 12:14:10 -0400 Subject: [PATCH 22/34] update rfc --- .../compiler_interface.rs | 6 +-- rfc/src/rfcs/0013-list.md | 51 +++++++++---------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 9d016e148f43..250a7b79d8bc 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -643,9 +643,9 @@ impl GotoCodegenResults { proof_harnesses: proofs, unsupported_features, test_harnesses: tests, - // We don't collect the functions under contract because the logic assumes that, if contract attributes are present, - // they are well-formed, which Kani only checks if we run verification or the list subcommand (i.e., for ReachabilityType::Harnesses). - // In those cases, we use the CodegenUnits object to generate the metadata instead of this function. + // We don't collect the contracts metadata because the FunctionWithContractPass + // removes any contracts logic for ReachabilityType::Test or ReachabilityType::PubFns, + // which are the two ReachabilityTypes under which the compiler calls this function. contracted_functions: vec![], } } diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index 48c626d8551b..f663b76b2532 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -31,6 +31,7 @@ This subcommand does not fail. In the case that it does not find any harnesses o The default format, `pretty`, prints a "Contracts" table and a "Standard Harnesses" list. Each row of the "Contracts" table consists of a function, the number of contracts it has, and its contract harnesses. A function is listed if it has contracts or it is the target of contract harness(es). +The results are printed in lexographic order. For example: @@ -59,8 +60,8 @@ or is the target of a contract harness (#[kani::proof_for_contract]). |-------|-------------------------|----------------|-------------------------------------| Standard Harnesses (#[kani::proof]): -1. example::verify::check_new -2. example::verify::check_modify +1. example::verify::check_modify +2. example::verify::check_new ``` All sections will be present in the output, regardless of the result. @@ -166,25 +167,28 @@ pub struct ContractedFunction { pub harnesses: Vec, } ``` -We extend `KaniMetadata` and `CodegenUnits` with new `contracted_functions: Vec` fields. +We extend `KaniMetadata` with a new `contracted_functions: Vec` field. + +### Driver Changes +We add a new subcommand to `kani-driver`. +This subcommand exists for both `cargo kani` and `kani` invocations. +The driver constructs a `Project` representing the input. +(For `kani` invocations, the driver either constructs a `standalone_project` or a `std_project` depending on whether the use passed the `--std` flag). +The `Project` is populated with `metadata: Vec` from `kani-compiler`. +We iterate through this metadata to print a table with the results or output a `kani-list.json` file, depending on the user-specified `format` argument. ### Compiler Changes -This subcommand is concerned with two fields of `KaniMetadata`: `proof_harnesses` and `contracted_functions`. -In `codegen_crate()`, `kani-compiler` will check if we are running the list subcommand, and, if so, it constructs a new `CodegenUnits` object. -The `CodegenUnits` constructor handles initializing the `proof_harnesses` field for us. -We add new functionality to populate the `contracted_functions` field, which we explain in two parts: +In `codegen_crate`, we update the generation of `KaniMetadata` to include the new `contracted_functions` field. +We populate this field in two parts: #### Part 1: Map Functions to Harnesses -First, we iterate through each local item in the crate and construct a map of functions to contract harnesses. -The keys in this map are functions that either 1) have contracts or 2) are targets of contract harnesses. -These are not necessarily identical sets; functions under contract may not have harnesses, and targets of contract harnesses may not have contracts. - -Observe that we iterate through each local item in the crate (`stable_mir::CrateItem`), not each local *instance* (`stable_mir::Instance`). +First, we iterate through each local item in the crate and construct a map of functions under contract to their contract harnesses. +We iterate through each local item in the crate (`stable_mir::CrateItem`), not each local *instance* (`stable_mir::Instance`). This is so that we can include generic functions with contracts in the output. `Instances` are monomorphized. -When we're verifying a contract harness, this monomorphization assumption is fine; if the harness calls the function under contract, a monomorphized version of the function must exist. +When we're verifying a contract harness, this monomorphization assumption is fine; if the harness calls the function under contract, a monomorphized version of the function must exist. However, if we are just trying to list metadata, we cannot rely on this assumption that the function under contract gets called, and therefore cannot assume that a monomorphized version of the generic function exists. For example, imagine running `kani list` on a file with only these contents: ```rust @@ -195,8 +199,6 @@ Kani should be able to find `foo` and report it has a contract, but we cannot construct a `stable_mir::Instance` from `foo` because it requires monomorphization. #### Part 2: Count Contracts -For each function in the map from Part 1, we count its contracts, then construct a `ContractedFunction` object for it. - Since we are counting the contracts at the MIR level, we work with the expanded version of the contract attribute macros. We locate the body of the `kanitool::checked_with` closure, then count the number of `kani::assume()` and `kani::assert()` calls. For example, given the following code (example taken from `kani_macros` contracts documentation): @@ -234,16 +236,7 @@ let mut __kani_check_div = Observe that there is one `kani::assume()` call for the `requires` contract and one `kani::assert()` call for the `ensures` contract, so we can obtain the total number of contracts by counting these calls. -Once these parts are complete, `CodegenUnits` contains all of the required metadata. -We use an existing method (`CodegenUnits::write_metadata`) to write this metadata to a file. - -### Driver Changes -We add a new subcommand to `kani-driver`. -This subcommand exists for both `cargo kani` and `kani` invocations. -The driver constructs a `Project` representing the input. -(For `kani` invocations, the driver either constructs a `standalone_project` or a `std_project` depending on whether the use passed the `--std` flag). -The `Project` is populated with `metadata: Vec` from `kani-compiler`. -We iterate through this metadata to print a table with the results or output a `kani-list.json` file, depending on the user-specified `format` argument. +Once these parts are complete, we generate a `ContractedFunction` object for each function under contract and populate it with the data gathered above. ## Rationale and alternatives @@ -300,10 +293,16 @@ However, users could still be confused by this choice because Kani allows users (If Kani detects no contract-related attributes at all, it errors.) A user may be confused as to why the list subcommand reports zero contracts when they are running a `proof_for_contract` harness without errors. +### Compiler - Build Cache +The compiler is unaware of the list subcommand; i.e., there are no special arguments to indicate to the compiler that the list subcommand is running. +If the user only wants to invoke the list subcommand, this design wastes work, since Kani generates unnecessary GOTO files. +However, it also allows the compiler to take advantage of the build cache. +If a user verifies their code and then invokes `list` (or vice versa), +the compiler will be invoked with the same arguments both times, which means that it can just use the cached targets from the first compiler invocation. ## Open questions -1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts, the number of `requires`, `ensures`, or `modifies` contracts, or the locations of the contracts. +1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts or the number of `requires` and `ensures` contracts. 2. More generally, we could introduce additional options that collect information about other Kani attributes (e.g., stubs). The default would be to leave them out, but this way a user could get more verbose output if they so choose. 3. Do we want to add a filtering option? For instance, `--harnesses ` and `--contracts `, where `pattern` corresponds to a Rust-style path. For example, `kani list --harnesses "my_crate::my_module::*"` would include all harnesses with that path prefix, while `kani list --contracts "my_crate::my_module::*"` would include all functions under contract with that path prefix. (If we do this work, we could use it to improve our `--harness` [pattern handling for verification](https://github.com/model-checking/kani/blob/main/kani-driver/src/metadata.rs#L187-L189)). From b5f48a1cc7e54a7da4ff81985055b49f7be87d86 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 1 Oct 2024 14:26:21 -0400 Subject: [PATCH 23/34] update RFC status --- rfc/src/rfcs/0013-list.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index f663b76b2532..07692deede60 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -1,7 +1,7 @@ - **Feature Name:** List Subcommand - **Feature Request Issue:** [#2573](https://github.com/model-checking/kani/issues/2573), [#1612](https://github.com/model-checking/kani/issues/1612) - **RFC PR:** #3463 -- **Status:** Under Review +- **Status:** Unstable - **Version:** 2 ------------------- @@ -319,4 +319,4 @@ fn check() { See [this blog post](https://model-checking.github.io/kani-verifier-blog/2022/10/27/using-kani-with-the-bolero-property-testing-framework.html) for more information. -There's no easy way for us to know whether a harness comes from Bolero, since Bolero takes care of rewriting the test to use Kani syntax and invoking the Kani engine. By the time the harness gets to Kani, there's no way for us to tell it apart from a regular harness. Fixing this would require some changes to our Bolero integration. \ No newline at end of file +There's no easy way for us to know whether a harness comes from Bolero, since Bolero takes care of rewriting the test to use Kani syntax and invoking the Kani engine. By the time the harness gets to Kani, there's no way for us to tell it apart from a regular harness. Fixing this would require some changes to our Bolero integration. From 0854cb3427ec0614018bbc5b2684ad1cff1d4167 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 1 Oct 2024 16:15:35 -0400 Subject: [PATCH 24/34] update Cargo.lock --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f062b7bc0ee9..e570bdc9cadc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -515,7 +515,7 @@ dependencies = [ "shell-words", "strum", "strum_macros", - "syn 2.0.77", + "syn 2.0.79", "tracing", "tracing-subscriber", "tracing-tree", @@ -575,7 +575,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -862,7 +862,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1070,7 +1070,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1178,7 +1178,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1242,7 +1242,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1339,7 +1339,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1626,5 +1626,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] From 7faeb2a5ee53e884609e91076b69300e62922517 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 1 Oct 2024 18:45:30 -0400 Subject: [PATCH 25/34] remove cli-table dependency; print manually --- Cargo.lock | 62 ++----------- kani-driver/Cargo.toml | 1 - kani-driver/src/list/output.rs | 158 +++++++++++++++++++++++++-------- rfc/src/rfcs/0013-list.md | 35 ++++---- 4 files changed, 145 insertions(+), 111 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e570bdc9cadc..42abe00cb6dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn", ] [[package]] @@ -185,29 +185,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" -[[package]] -name = "cli-table" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53f9241f288a7b12c56565f04aaeaeeab6b8923d42d99255d4ca428b4d97f89" -dependencies = [ - "cli-table-derive", - "csv", - "termcolor", - "unicode-width", -] - -[[package]] -name = "cli-table-derive" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e83a93253aaae7c74eb7428ce4faa6e219ba94886908048888701819f82fb94" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "colorchoice" version = "1.0.2" @@ -515,7 +492,7 @@ dependencies = [ "shell-words", "strum", "strum_macros", - "syn 2.0.79", + "syn", "tracing", "tracing-subscriber", "tracing-tree", @@ -528,7 +505,6 @@ dependencies = [ "anyhow", "cargo_metadata", "clap", - "cli-table", "colour", "comfy-table", "console", @@ -575,7 +551,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.79", + "syn", ] [[package]] @@ -862,7 +838,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.79", + "syn", ] [[package]] @@ -1070,7 +1046,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn", ] [[package]] @@ -1178,18 +1154,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "syn", ] [[package]] @@ -1216,15 +1181,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.64" @@ -1242,7 +1198,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn", ] [[package]] @@ -1339,7 +1295,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn", ] [[package]] @@ -1626,5 +1582,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn", ] diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index 85a93aff5940..a67dd1cea50f 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -20,7 +20,6 @@ once_cell = "1.19.0" serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["preserve_order"] } clap = { version = "4.4.11", features = ["derive"] } -cli-table = "0.4.9" colour = "2.1.0" glob = "0.3" toml = "0.8" diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index e8a937596533..2c3d218db2e9 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -3,6 +3,7 @@ // This module handles outputting the result for the list subcommand use std::{ + cmp::max, collections::{BTreeMap, BTreeSet}, fs::File, io::BufWriter, @@ -10,7 +11,6 @@ use std::{ use crate::{list::Totals, version::KANI_VERSION}; use anyhow::Result; -use cli_table::{Cell, CellStruct, Style, Table, print_stdout}; use colour::print_ln_bold; use kani_metadata::ContractedFunction; use serde_json::json; @@ -20,54 +20,135 @@ use serde_json::json; const FILE_VERSION: &str = "0.1"; const JSON_FILENAME: &str = "kani-list.json"; -pub fn pretty( - standard_harnesses: BTreeMap>, +/// Construct the table of contracts information. +fn construct_contracts_table( contracted_functions: BTreeSet, totals: Totals, -) -> Result<()> { - fn format_contract_harnesses(harnesses: &mut [String]) -> String { - harnesses.sort(); - let joined = harnesses.join("\n"); - if joined.is_empty() { "NONE".to_string() } else { joined } +) -> Vec { + const NO_HARNESSES_MSG: &str = "NONE"; + + // Since the harnesses will be separated by newlines, the harness length is equal to the length of the longest harness + fn harnesses_len(harnesses: &[String]) -> usize { + harnesses.iter().map(|s| s.len()).max().unwrap_or(NO_HARNESSES_MSG.len()) } - print_ln_bold!("\nContracts:"); - println!( - "Each function in the table below either has contracts or is the target of a contract harness (#[kani::proof_for_contract])." + // Contracts table headers + const FUNCTION_HEADER: &str = "Function"; + const CONTRACTS_COUNT_HEADER: &str = "# of Contracts"; + const CONTRACT_HARNESSES_HEADER: &str = "Contract Harnesses (#[kani::proof_for_contract])"; + + // Contracts table totals row + const TOTALS_HEADER: &str = "Total"; + let functions_total = totals.contracted_functions.to_string(); + let contracts_total = totals.contracts.to_string(); + let harnesses_total = totals.contract_harnesses.to_string(); + + let mut table_rows: Vec = vec![]; + let mut max_function_fmt_width = max(FUNCTION_HEADER.len(), functions_total.len()); + let mut max_contracts_count_fmt_width = + max(CONTRACTS_COUNT_HEADER.len(), contracts_total.len()); + let mut max_contract_harnesses_fmt_width = + max(CONTRACT_HARNESSES_HEADER.len(), harnesses_total.len()); + + let mut data_rows: Vec<(String, String, Vec)> = vec![]; + + for cf in contracted_functions { + max_function_fmt_width = max(max_function_fmt_width, cf.function.len()); + max_contracts_count_fmt_width = max(max_contracts_count_fmt_width, cf.total_contracts); + max_contract_harnesses_fmt_width = + max(max_contract_harnesses_fmt_width, harnesses_len(&cf.harnesses)); + + data_rows.push((cf.function, cf.total_contracts.to_string(), cf.harnesses)); + } + + let function_sep = "-".repeat(max_function_fmt_width); + let contracts_count_sep = "-".repeat(max_contracts_count_fmt_width); + let contract_harnesses_sep = "-".repeat(max_contract_harnesses_fmt_width); + let totals_sep = "-".repeat(TOTALS_HEADER.len()); + + let sep_row = format!( + "| {totals_sep} | {function_sep} | {contracts_count_sep} | {contract_harnesses_sep} |" ); + table_rows.push(sep_row.clone()); - if contracted_functions.is_empty() { - println!("No contracts or contract harnesses found.") - } else { - let mut contracts_table: Vec> = vec![]; - - for mut cf in contracted_functions { - contracts_table.push(vec![ - "".cell(), - cf.function.cell(), - cf.total_contracts.cell(), - format_contract_harnesses(&mut cf.harnesses).cell(), - ]); + let function_space = " ".repeat(max_function_fmt_width - FUNCTION_HEADER.len()); + let contracts_count_space = + " ".repeat(max_contracts_count_fmt_width - CONTRACTS_COUNT_HEADER.len()); + let contract_harnesses_space = + " ".repeat(max_contract_harnesses_fmt_width - CONTRACT_HARNESSES_HEADER.len()); + let totals_space = " ".repeat(TOTALS_HEADER.len()); + + let header_row = format!( + "| {totals_space} | {FUNCTION_HEADER}{function_space} | {CONTRACTS_COUNT_HEADER}{contracts_count_space} | {CONTRACT_HARNESSES_HEADER}{contract_harnesses_space} |" + ); + table_rows.push(header_row); + table_rows.push(sep_row.clone()); + + for (function, total_contracts, harnesses) in data_rows { + let function_space = " ".repeat(max_function_fmt_width - function.len()); + let contracts_count_space = + " ".repeat(max_contracts_count_fmt_width - total_contracts.len()); + let first_harness = harnesses.first().map_or(NO_HARNESSES_MSG, |v| v); + let contract_harnesses_space = + " ".repeat(max_contract_harnesses_fmt_width - first_harness.len()); + + let first_row = format!( + "| {totals_space} | {function}{function_space} | {total_contracts}{contracts_count_space} | {first_harness}{contract_harnesses_space} |" + ); + table_rows.push(first_row); + + for subsequent_harness in harnesses.iter().skip(1) { + let function_space = " ".repeat(max_function_fmt_width); + let contracts_count_space = " ".repeat(max_contracts_count_fmt_width); + let contract_harnesses_space = + " ".repeat(max_contract_harnesses_fmt_width - subsequent_harness.len()); + let row = format!( + "| {totals_space} | {function_space} | {contracts_count_space} | {subsequent_harness}{contract_harnesses_space} |" + ); + table_rows.push(row); } - contracts_table.push(vec![ - "Total".cell().bold(true), - totals.contracted_functions.cell(), - totals.contracts.cell(), - totals.contract_harnesses.cell(), - ]); - - print_stdout(contracts_table.table().title(vec![ - "".cell(), - "Function".cell().bold(true), - "# of Contracts".cell().bold(true), - "Contract Harnesses".cell().bold(true), - ]))?; + table_rows.push(sep_row.clone()) } - print_ln_bold!("\nStandard Harnesses (#[kani::proof]):"); + let total_function_space = " ".repeat(max_function_fmt_width - functions_total.len()); + let total_contracts_space = " ".repeat(max_contracts_count_fmt_width - contracts_total.len()); + let total_harnesses_space = + " ".repeat(max_contract_harnesses_fmt_width - harnesses_total.len()); + + let totals_row = format!( + "| {TOTALS_HEADER} | {functions_total}{total_function_space} | {contracts_total}{total_contracts_space} | {harnesses_total}{total_harnesses_space} |" + ); + + table_rows.push(totals_row); + table_rows.push(sep_row.clone()); + + table_rows +} + +/// Output results as a table printed to the terminal. +pub fn pretty( + standard_harnesses: BTreeMap>, + contracted_functions: BTreeSet, + totals: Totals, +) -> Result<()> { + const CONTRACTS_SECTION: &str = "Contracts:"; + const HARNESSES_SECTION: &str = "Standard Harnesses (#[kani::proof]):"; + const NO_CONTRACTS_MSG: &str = "No contracts or contract harnesses found."; + const NO_HARNESSES_MSG: &str = "No standard harnesses found."; + + print_ln_bold!("\n{CONTRACTS_SECTION}"); + + if contracted_functions.is_empty() { + println!("{NO_CONTRACTS_MSG}"); + } else { + let table_rows = construct_contracts_table(contracted_functions, totals); + println!("{}", table_rows.join("\n")); + }; + + print_ln_bold!("\n{HARNESSES_SECTION}"); if standard_harnesses.is_empty() { - println!("No standard harnesses found."); + println!("{NO_HARNESSES_MSG}"); } let mut std_harness_index = 0; @@ -84,6 +165,7 @@ pub fn pretty( Ok(()) } +/// Output results as a JSON file. pub fn json( standard_harnesses: BTreeMap>, contract_harnesses: BTreeMap>, diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index 07692deede60..61e5bf55b39e 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -39,25 +39,22 @@ For example: Kani Rust Verifier 0.54.0 (standalone) Contracts: -Each function in the table below either has contracts -or is the target of a contract harness (#[kani::proof_for_contract]). - -|-------|-------------------------|----------------|-------------------------------------| -| | Function | # of Contracts | Contract Harnesses | -|-------|-------------------------|----------------|-------------------------------------| -| | example::impl::bar | 4 | example::verify::check_bar | -|-------|-------------------------|----------------|-------------------------------------| -| | example::impl::baz | 0 | example::verify::check_baz | -|-------|-------------------------|----------------|-------------------------------------| -| | example::impl::foo | 2 | example::verify::check_foo_u32 | -| | | | example::verify::check_foo_u64 | -|-------|-------------------------|----------------|-------------------------------------| -| | example::impl::func | 1 | example::verify::check_func | -|-------|-------------------------|----------------|-------------------------------------| -| | example::prep::parse | 1 | NONE | -|-------|-------------------------|----------------|-------------------------------------| -| Total | 5 | 8 | 5 | -|-------|-------------------------|----------------|-------------------------------------| +|-------|-------------------------|----------------|--------------------------------------------------| +| | Function | # of Contracts | Contract Harnesses (#[kani::proof_for_contract]) | +|-------|-------------------------|----------------|--------------------------------------------------| +| | example::impl::bar | 4 | example::verify::check_bar | +|-------|-------------------------|----------------|--------------------------------------------------| +| | example::impl::baz | 0 | example::verify::check_baz | +|-------|-------------------------|----------------|--------------------------------------------------| +| | example::impl::foo | 2 | example::verify::check_foo_u32 | +| | | | example::verify::check_foo_u64 | +|-------|-------------------------|----------------|--------------------------------------------------| +| | example::impl::func | 1 | example::verify::check_func | +|-------|-------------------------|----------------|--------------------------------------------------| +| | example::prep::parse | 1 | NONE | +|-------|-------------------------|----------------|--------------------------------------------------| +| Total | 5 | 8 | 5 | +|-------|-------------------------|----------------|--------------------------------------------------| Standard Harnesses (#[kani::proof]): 1. example::verify::check_modify From 15dd78a852bf7eb43393a159233cbb04852d840a Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 2 Oct 2024 14:08:34 -0400 Subject: [PATCH 26/34] add Markdown explanation to RFc --- rfc/src/rfcs/0013-list.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index 61e5bf55b39e..8a91d3fcd934 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -297,6 +297,12 @@ However, it also allows the compiler to take advantage of the build cache. If a user verifies their code and then invokes `list` (or vice versa), the compiler will be invoked with the same arguments both times, which means that it can just use the cached targets from the first compiler invocation. +### Pretty Format +The Contracts table is close to Markdown, but not quite Markdown--it includes line separators between each row, +when Markdown would only have a separator for the header. +We include the separator because without it, it can be difficult to tell from reading the terminal output which entries are in the same row. +The user can transform the table to Markdown by deleting these separators, and we can trivially add a Markdown option in the future if there is demand. + ## Open questions 1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts or the number of `requires` and `ensures` contracts. From 3f583d35e22cb6b4583b23c41c86cb9b2809ebb9 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 2 Oct 2024 16:58:01 -0400 Subject: [PATCH 27/34] remove contracts count; reduce rfc detail --- .../codegen_cprover_gotoc/codegen/contract.rs | 34 ++-- kani-compiler/src/kani_middle/metadata.rs | 99 +++------- kani-compiler/src/kani_middle/mod.rs | 19 +- kani-driver/src/list/collect_metadata.rs | 12 +- kani-driver/src/list/mod.rs | 1 - kani-driver/src/list/output.rs | 37 ++-- kani_metadata/src/lib.rs | 2 - rfc/src/rfcs/0013-list.md | 182 +++--------------- .../script-based-pre/cargo_list/list.expected | 7 +- .../script-based-pre/kani_list/list.expected | 7 +- 10 files changed, 95 insertions(+), 305 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index 01313487cc82..26caf29bea1d 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -2,16 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::codegen_cprover_gotoc::{GotocCtx, codegen::ty_stable::pointee_type_stable}; use crate::kani_middle::attributes::KaniAttributes; -use crate::kani_middle::find_closure_in_body; use cbmc::goto_program::FunctionContract; use cbmc::goto_program::{Expr, Lambda, Location, Type}; use kani_metadata::AssignsContract; use rustc_hir::def_id::DefId as InternalDefId; use rustc_smir::rustc_internal; use stable_mir::CrateDef; -use stable_mir::mir::Local; use stable_mir::mir::mono::{Instance, MonoItem}; -use stable_mir::ty::{RigidTy, TyKind}; +use stable_mir::mir::{Local, VarDebugInfoContents}; +use stable_mir::ty::{FnDef, RigidTy, TyKind}; impl<'tcx> GotocCtx<'tcx> { /// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`, @@ -88,24 +87,33 @@ impl<'tcx> GotocCtx<'tcx> { recursion_tracker } - fn find_closure(&mut self, inside: Instance, name: &str) -> Option { - let body = self.transformer.body(self.tcx, inside); - find_closure_in_body(&body, name) - } - /// Find the modifies recursively since we may have a recursion wrapper. /// I.e.: [recursion_wrapper ->]? check -> modifies. fn find_modifies(&mut self, instance: Instance) -> Option { let contract_attrs = KaniAttributes::for_instance(self.tcx, instance).contract_attributes()?; + let mut find_closure = |inside: Instance, name: &str| { + let body = self.transformer.body(self.tcx, inside); + body.var_debug_info.iter().find_map(|var_info| { + if var_info.name.as_str() == name { + let ty = match &var_info.value { + VarDebugInfoContents::Place(place) => place.ty(body.locals()).unwrap(), + VarDebugInfoContents::Const(const_op) => const_op.ty(), + }; + if let TyKind::RigidTy(RigidTy::Closure(def, args)) = ty.kind() { + return Some(Instance::resolve(FnDef(def.def_id()), &args).unwrap()); + } + } + None + }) + }; let check_instance = if contract_attrs.has_recursion { - let recursion_check = - self.find_closure(instance, contract_attrs.recursion_check.as_str())?; - self.find_closure(recursion_check, contract_attrs.checked_with.as_str())? + let recursion_check = find_closure(instance, contract_attrs.recursion_check.as_str())?; + find_closure(recursion_check, contract_attrs.checked_with.as_str())? } else { - self.find_closure(instance, contract_attrs.checked_with.as_str())? + find_closure(instance, contract_attrs.checked_with.as_str())? }; - self.find_closure(check_instance, contract_attrs.modifies_wrapper.as_str()) + find_closure(check_instance, contract_attrs.modifies_wrapper.as_str()) } /// Convert the Kani level contract into a CBMC level contract by creating a diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 14cd3459cd06..1273af5e801a 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -6,18 +6,13 @@ use std::collections::HashMap; use std::path::Path; -use crate::kani_middle::attributes::test_harness_name; -use crate::kani_middle::attributes::{ - ContractAttributes, KaniAttributes, matches_diagnostic as matches_function, -}; -use crate::kani_middle::{InternalDefId, SourceLocation, find_closure_in_body}; +use crate::kani_middle::attributes::{KaniAttributes, test_harness_name}; +use crate::kani_middle::{InternalDefId, SourceLocation}; use kani_metadata::ContractedFunction; use kani_metadata::{ArtifactType, HarnessAttributes, HarnessKind, HarnessMetadata}; 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}; /// Create the harness metadata for a proof harness for a given function. @@ -47,81 +42,45 @@ pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> } } -/// Map each function under contract to its contract harnesses -fn fns_to_contract_harnesses(tcx: TyCtxt) -> HashMap> { +/// Collects contract and contract harness metadata. +/// +/// For each function with contracts (or that is a target of a contract harness), +/// construct a ContractedFunction object for it. +pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec { // 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> = HashMap::new(); + let mut fn_to_data: HashMap = 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); + let internal_def_id = rustc_internal::internal(tcx, item.def_id()); + + let function = item.name(); + let file = SourceLocation::new(item.span()).filename; + let attributes = KaniAttributes::for_def_id(tcx, item.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); + fn_to_data.insert(internal_def_id, ContractedFunction { + function, + file, + harnesses: vec![], + }); + } else if let Some((target_name, target_def_id, _)) = + attributes.interpret_for_contract_attribute() + { + if let Some(cf) = fn_to_data.get_mut(&target_def_id) { + cf.harnesses.push(function); } else { - fns_to_harnesses.insert(target_def_id, vec![fn_name]); + fn_to_data.insert(target_def_id, ContractedFunction { + function: target_name.to_string(), + file, + harnesses: vec![function], + }); } } } - 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() { - if let Ok(instance) = Instance::resolve(fn_def, &args) { - // For each precondition or postcondition, increment the count - if matches_function(tcx, instance.def, "KaniAssume") - || matches_function(tcx, instance.def, "KaniAssert") - { - count += 1; - } - } - } - } - } - count -} - -/// Collects contract and contract harness metadata. -/// -/// For each function with contracts (or that is a target of a contract harness), -/// construct a ContractedFunction object for it. -pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec { - let mut contracted_fns = vec![]; - for (fn_def_id, harnesses) in fns_to_contract_harnesses(tcx) { - let attrs: ContractAttributes = - 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, attrs.checked_with.as_str()).unwrap().body().unwrap(); - - let total_contracts = count_contracts(tcx, &check_body); - - contracted_fns.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, - }); - } - - contracted_fns + fn_to_data.into_values().collect() } /// Create the harness metadata for a test description. diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index 2190207ee5de..5f7f4c28068f 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -19,8 +19,7 @@ use rustc_span::source_map::respan; use rustc_target::abi::call::FnAbi; use rustc_target::abi::{HasDataLayout, TargetDataLayout}; use stable_mir::CrateDef; -use stable_mir::mir::mono::{Instance, MonoItem}; -use stable_mir::mir::{Body, VarDebugInfoContents}; +use stable_mir::mir::mono::MonoItem; use stable_mir::ty::{FnDef, RigidTy, Span as SpanStable, Ty, TyKind}; use stable_mir::visitor::{Visitable, Visitor as TyVisitor}; use std::ops::ControlFlow; @@ -239,19 +238,3 @@ pub fn stable_fn_def(tcx: TyCtxt, def_id: InternalDefId) -> Option { None } } - -/// Find the user-declared closure by the name `name` in `body`. -pub fn find_closure_in_body(body: &Body, name: &str) -> Option { - body.var_debug_info.iter().find_map(|var_info| { - if var_info.name.as_str() == name { - let ty = match &var_info.value { - VarDebugInfoContents::Place(place) => place.ty(body.locals()).unwrap(), - VarDebugInfoContents::Const(const_op) => const_op.ty(), - }; - if let TyKind::RigidTy(RigidTy::Closure(def, args)) = ty.kind() { - return Some(Instance::resolve(FnDef(def.def_id()), &args).unwrap()); - } - } - None - }) -} diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 708ffbb35db4..5769e374d546 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -16,15 +16,18 @@ use crate::{ use anyhow::Result; use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; +/// Process the KaniMetadata output from kani-compiler and output the list subcommand results fn process_metadata(metadata: Vec, format: Format) -> Result<()> { - // Map each file to a vector of its harnesses + // We use ordered maps and sets so that the output is in lexographic order (and consistent across invocations). + + // Map each file to a vector of its harnesses. let mut standard_harnesses: BTreeMap> = BTreeMap::new(); let mut contract_harnesses: BTreeMap> = BTreeMap::new(); + let mut contracted_functions: BTreeSet = BTreeSet::new(); let mut total_standard_harnesses = 0; let mut total_contract_harnesses = 0; - let mut total_contracts = 0; for kani_meta in metadata { for harness_meta in kani_meta.proof_harnesses { @@ -57,10 +60,6 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { } } - for cf in &kani_meta.contracted_functions { - total_contracts += cf.total_contracts; - } - contracted_functions.extend(kani_meta.contracted_functions.into_iter()); } @@ -68,7 +67,6 @@ fn process_metadata(metadata: Vec, format: Format) -> Result<()> { standard_harnesses: total_standard_harnesses, contract_harnesses: total_contract_harnesses, contracted_functions: contracted_functions.len(), - contracts: total_contracts, }; match format { diff --git a/kani-driver/src/list/mod.rs b/kani-driver/src/list/mod.rs index 74108c93fceb..37ac1485d618 100644 --- a/kani-driver/src/list/mod.rs +++ b/kani-driver/src/list/mod.rs @@ -9,5 +9,4 @@ struct Totals { standard_harnesses: usize, contract_harnesses: usize, contracted_functions: usize, - contracts: usize, } diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index 2c3d218db2e9..70c6f3e23ced 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -27,83 +27,70 @@ fn construct_contracts_table( ) -> Vec { const NO_HARNESSES_MSG: &str = "NONE"; - // Since the harnesses will be separated by newlines, the harness length is equal to the length of the longest harness - fn harnesses_len(harnesses: &[String]) -> usize { + // Since the harnesses will be separated by newlines, the column length is equal to the length of the longest harness + fn column_len(harnesses: &[String]) -> usize { harnesses.iter().map(|s| s.len()).max().unwrap_or(NO_HARNESSES_MSG.len()) } // Contracts table headers const FUNCTION_HEADER: &str = "Function"; - const CONTRACTS_COUNT_HEADER: &str = "# of Contracts"; const CONTRACT_HARNESSES_HEADER: &str = "Contract Harnesses (#[kani::proof_for_contract])"; // Contracts table totals row const TOTALS_HEADER: &str = "Total"; let functions_total = totals.contracted_functions.to_string(); - let contracts_total = totals.contracts.to_string(); let harnesses_total = totals.contract_harnesses.to_string(); let mut table_rows: Vec = vec![]; let mut max_function_fmt_width = max(FUNCTION_HEADER.len(), functions_total.len()); - let mut max_contracts_count_fmt_width = - max(CONTRACTS_COUNT_HEADER.len(), contracts_total.len()); let mut max_contract_harnesses_fmt_width = max(CONTRACT_HARNESSES_HEADER.len(), harnesses_total.len()); - let mut data_rows: Vec<(String, String, Vec)> = vec![]; + let mut data_rows: Vec<(String, Vec)> = vec![]; for cf in contracted_functions { max_function_fmt_width = max(max_function_fmt_width, cf.function.len()); - max_contracts_count_fmt_width = max(max_contracts_count_fmt_width, cf.total_contracts); max_contract_harnesses_fmt_width = - max(max_contract_harnesses_fmt_width, harnesses_len(&cf.harnesses)); + max(max_contract_harnesses_fmt_width, column_len(&cf.harnesses)); - data_rows.push((cf.function, cf.total_contracts.to_string(), cf.harnesses)); + data_rows.push((cf.function, cf.harnesses)); } let function_sep = "-".repeat(max_function_fmt_width); - let contracts_count_sep = "-".repeat(max_contracts_count_fmt_width); let contract_harnesses_sep = "-".repeat(max_contract_harnesses_fmt_width); let totals_sep = "-".repeat(TOTALS_HEADER.len()); - let sep_row = format!( - "| {totals_sep} | {function_sep} | {contracts_count_sep} | {contract_harnesses_sep} |" - ); + let sep_row = format!("| {totals_sep} | {function_sep} | {contract_harnesses_sep} |"); table_rows.push(sep_row.clone()); let function_space = " ".repeat(max_function_fmt_width - FUNCTION_HEADER.len()); - let contracts_count_space = - " ".repeat(max_contracts_count_fmt_width - CONTRACTS_COUNT_HEADER.len()); let contract_harnesses_space = " ".repeat(max_contract_harnesses_fmt_width - CONTRACT_HARNESSES_HEADER.len()); let totals_space = " ".repeat(TOTALS_HEADER.len()); let header_row = format!( - "| {totals_space} | {FUNCTION_HEADER}{function_space} | {CONTRACTS_COUNT_HEADER}{contracts_count_space} | {CONTRACT_HARNESSES_HEADER}{contract_harnesses_space} |" + "| {totals_space} | {FUNCTION_HEADER}{function_space} | {CONTRACT_HARNESSES_HEADER}{contract_harnesses_space} |" ); table_rows.push(header_row); table_rows.push(sep_row.clone()); - for (function, total_contracts, harnesses) in data_rows { + for (function, harnesses) in data_rows { let function_space = " ".repeat(max_function_fmt_width - function.len()); - let contracts_count_space = - " ".repeat(max_contracts_count_fmt_width - total_contracts.len()); let first_harness = harnesses.first().map_or(NO_HARNESSES_MSG, |v| v); let contract_harnesses_space = " ".repeat(max_contract_harnesses_fmt_width - first_harness.len()); let first_row = format!( - "| {totals_space} | {function}{function_space} | {total_contracts}{contracts_count_space} | {first_harness}{contract_harnesses_space} |" + "| {totals_space} | {function}{function_space} | {first_harness}{contract_harnesses_space} |" ); table_rows.push(first_row); for subsequent_harness in harnesses.iter().skip(1) { let function_space = " ".repeat(max_function_fmt_width); - let contracts_count_space = " ".repeat(max_contracts_count_fmt_width); let contract_harnesses_space = " ".repeat(max_contract_harnesses_fmt_width - subsequent_harness.len()); let row = format!( - "| {totals_space} | {function_space} | {contracts_count_space} | {subsequent_harness}{contract_harnesses_space} |" + "| {totals_space} | {function_space} | {subsequent_harness}{contract_harnesses_space} |" ); table_rows.push(row); } @@ -112,12 +99,11 @@ fn construct_contracts_table( } let total_function_space = " ".repeat(max_function_fmt_width - functions_total.len()); - let total_contracts_space = " ".repeat(max_contracts_count_fmt_width - contracts_total.len()); let total_harnesses_space = " ".repeat(max_contract_harnesses_fmt_width - harnesses_total.len()); let totals_row = format!( - "| {TOTALS_HEADER} | {functions_total}{total_function_space} | {contracts_total}{total_contracts_space} | {harnesses_total}{total_harnesses_space} |" + "| {TOTALS_HEADER} | {functions_total}{total_function_space} | {harnesses_total}{total_harnesses_space} |" ); table_rows.push(totals_row); @@ -185,7 +171,6 @@ pub fn json( "standard-harnesses": totals.standard_harnesses, "contract-harnesses": totals.contract_harnesses, "functions-under-contract": totals.contracted_functions, - "contracts": totals.contracts, } }); diff --git a/kani_metadata/src/lib.rs b/kani_metadata/src/lib.rs index 3906b41ff89e..96caf92133e9 100644 --- a/kani_metadata/src/lib.rs +++ b/kani_metadata/src/lib.rs @@ -41,8 +41,6 @@ pub struct ContractedFunction { pub function: String, /// The (currently full-) path to the file this function was declared within. pub file: String, - /// The number of contracts applied to this function - pub total_contracts: usize, /// The pretty names of the proof harnesses (`#[kani::proof_for_contract]`) for this function pub harnesses: Vec, } diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index 8a91d3fcd934..6fa61f7f56f8 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -39,22 +39,22 @@ For example: Kani Rust Verifier 0.54.0 (standalone) Contracts: -|-------|-------------------------|----------------|--------------------------------------------------| -| | Function | # of Contracts | Contract Harnesses (#[kani::proof_for_contract]) | -|-------|-------------------------|----------------|--------------------------------------------------| -| | example::impl::bar | 4 | example::verify::check_bar | -|-------|-------------------------|----------------|--------------------------------------------------| -| | example::impl::baz | 0 | example::verify::check_baz | -|-------|-------------------------|----------------|--------------------------------------------------| -| | example::impl::foo | 2 | example::verify::check_foo_u32 | -| | | | example::verify::check_foo_u64 | -|-------|-------------------------|----------------|--------------------------------------------------| -| | example::impl::func | 1 | example::verify::check_func | -|-------|-------------------------|----------------|--------------------------------------------------| -| | example::prep::parse | 1 | NONE | -|-------|-------------------------|----------------|--------------------------------------------------| -| Total | 5 | 8 | 5 | -|-------|-------------------------|----------------|--------------------------------------------------| +|-------|-------------------------|--------------------------------------------------| +| | Function | Contract Harnesses (#[kani::proof_for_contract]) | +|-------|-------------------------|--------------------------------------------------| +| | example::impl::bar | example::verify::check_bar | +|-------|-------------------------|--------------------------------------------------| +| | example::impl::baz | example::verify::check_baz | +|-------|-------------------------|--------------------------------------------------| +| | example::impl::foo | example::verify::check_foo_u32 | +| | | example::verify::check_foo_u64 | +|-------|-------------------------|--------------------------------------------------| +| | example::impl::func | example::verify::check_func | +|-------|-------------------------|--------------------------------------------------| +| | example::prep::parse | NONE | +|-------|-------------------------|--------------------------------------------------| +| Total | 5 | 5 | +|-------|-------------------------|--------------------------------------------------| Standard Harnesses (#[kani::proof]): 1. example::verify::check_modify @@ -104,19 +104,16 @@ For example: contracts: [ { function: example::impl::bar - total_contracts: 4 file: /Users/johnsmith/example/impl.rs harnesses: [example::verify::check_bar] }, { function: example::impl::baz - total_contracts: 0 file: /Users/johnsmith/example/impl.rs harnesses: [example::verify::check_baz] }, { function: example::impl::foo - total_contracts: 2 file: /Users/johnsmith/example/impl.rs harnesses: [ example::verify::check_foo_u32, @@ -125,13 +122,11 @@ For example: }, { function: example::impl::func - total_contracts: 1 file: /Users/johnsmith/example/impl.rs harnesses: [example::verify::check_func] }, { function: example::prep::parse - total_contracts: 1 file: /Users/johnsmith/example/prep.rs harnesses: [] } @@ -140,7 +135,6 @@ For example: standard-harnesses: 2, contract-harnesses: 5, functions-with-contracts: 5, - contracts: 8, } } ``` @@ -150,90 +144,16 @@ If there is no result for a given field (e.g., there are no contracts), Kani wil ## Software Design -### Metdata Changes -We introduce a new `ContractedFunction` struct to `kani_metadata`: -```rust -pub struct ContractedFunction { - /// The fully qualified name the user gave to the function (i.e. includes the module path). - pub function: String, - /// The (currently full-) path to the file this function was declared within. - pub file: String, - /// The number of contracts applied to this function - pub total_contracts: usize, - /// The pretty names of the proof harnesses (`#[kani::proof_for_contract]`) for this function - pub harnesses: Vec, -} -``` -We extend `KaniMetadata` with a new `contracted_functions: Vec` field. +### Driver/Metdata Changes -### Driver Changes -We add a new subcommand to `kani-driver`. -This subcommand exists for both `cargo kani` and `kani` invocations. -The driver constructs a `Project` representing the input. -(For `kani` invocations, the driver either constructs a `standalone_project` or a `std_project` depending on whether the use passed the `--std` flag). -The `Project` is populated with `metadata: Vec` from `kani-compiler`. -We iterate through this metadata to print a table with the results or output a `kani-list.json` file, depending on the user-specified `format` argument. +We add a new `list` subcommand to `kani-driver`, which invokes the compiler to collect metadata, then post-processes that metadata and outputs the result. +We extend `KaniMetadata` to include a new field containing each function under contract and its contract harnesses. ### Compiler Changes -In `codegen_crate`, we update the generation of `KaniMetadata` to include the new `contracted_functions` field. -We populate this field in two parts: - -#### Part 1: Map Functions to Harnesses - -First, we iterate through each local item in the crate and construct a map of functions under contract to their contract harnesses. -We iterate through each local item in the crate (`stable_mir::CrateItem`), not each local *instance* (`stable_mir::Instance`). -This is so that we can include generic functions with contracts in the output. -`Instances` are monomorphized. -When we're verifying a contract harness, this monomorphization assumption is fine; if the harness calls the function under contract, a monomorphized version of the function must exist. -However, if we are just trying to list metadata, we cannot rely on this assumption that the function under contract gets called, and therefore cannot assume that a monomorphized version of the generic function exists. -For example, imagine running `kani list` on a file with only these contents: -```rust -#[kani::requires(true)] -fn foo(x: T) -> T { x } -``` -Kani should be able to find `foo` and report it has a contract, -but we cannot construct a `stable_mir::Instance` from `foo` because it requires monomorphization. - -#### Part 2: Count Contracts -Since we are counting the contracts at the MIR level, we work with the expanded version of the contract attribute macros. -We locate the body of the `kanitool::checked_with` closure, then count the number of `kani::assume()` and `kani::assert()` calls. -For example, given the following code (example taken from `kani_macros` contracts documentation): - -```rust -#[kani::requires(divisor != 0)] -#[kani::ensures(|result : &u32| *result <= dividend)] -fn div(dividend: u32, divisor: u32) -> u32 { - dividend / divisor -} -``` - -The generated `check` closure is: - -```rust -let mut __kani_check_div = - || -> u32 - { - kani::assume(divisor != 0); - let _wrapper_arg = (); - #[kanitool::is_contract_generated(wrapper)] - #[allow(dead_code, unused_variables, unused_mut)] - let mut __kani_modifies_div = - |_wrapper_arg| -> u32 { dividend / divisor }; - let result_kani_internal: u32 = - __kani_modifies_div(_wrapper_arg); - kani::assert(kani::internal::apply_closure(|result: &u32| - *result <= dividend, &result_kani_internal), - "|result : &u32| *result <= dividend"); - result_kani_internal - }; -; -``` - -Observe that there is one `kani::assume()` call for the `requires` contract and one `kani::assert()` -call for the `ensures` contract, so we can obtain the total number of contracts by counting these calls. - -Once these parts are complete, we generate a `ContractedFunction` object for each function under contract and populate it with the data gathered above. +In `codegen_crate`, we update the generation of `KaniMetadata` to include the new contracts information. +We iterate through each local item in the crate. +Each time we find a function under contract or a contract harness, we include it in the metadata. ## Rationale and alternatives @@ -242,70 +162,20 @@ Users of Kani may have many questions about their project--not only where their 1. Where are the harnesses? 2. Where are the contracts? 3. Which contracts are verified, and by which harnesses? -4. How many harnesses and contracts are there? +4. How many harnesses and functions under contract are there? We believe these questions are the most important for our use cases of tracking verification progress for customers and the standard library. The UX is designed to answer these questions clearly and concisely. We could have a more verbose or granular output, e.g., printing the metadata on a per-crate or per-module level, or including stubs or other attributes. Such a design would have the benefit of providing more information, with the disadvantage of being more complex to implement and more information for the user to process. - If we do not implement this feature, users will have to obtain this metadata through manual searching, or by writing a script to do it themselves. This feature will improve our internal productivity by automating the process. -### Modifies Clauses -As discussed in [Software Design](#software-design), the contracts count includes `requires` and `ensures` contracts only. -We do not include `modifies` clauses for two reasons: -1. `modifies` clauses are not really "contracts" in the same way that `requires` and `ensures` are--they are important for our contracts instrumentation (c.f. [#2594](https://github.com/model-checking/kani/issues/2594)), but it's not as if Kani goes and verifies that the function actually modifies the pointer. -For example, if I write this: - -```rust -#[kani::modifies(x)] -fn foo(x: &mut u32) {} - -#[kani::proof_for_contract(foo)] -fn check_foo() { - let mut x = 7; - foo(&mut x); -} -``` -verification succeeds even though `foo` does not modify the location to which `x` points. - -2. `kani_macros` folds the arguments to each `modifies` into a single tuple, e.g.: -```rust -#[modifies(x)] -#[modifies(y)] -fn foo(x: &mut u32, y: &mut u32) {} -``` - -```rust -#[modifies(x, y)] -fn foo(x: &mut u32, y: &mut u32) {} -``` - -are indistinguishable once the macro expansion finishes. -We could of course count the modifies clauses before macro expansion, but even if we do, it's not clear whether counting the number of *attributes* or *arguments* is more intuitive. -In the latter example above, do we have two modifies clauses (one each for `x` and `y`), or just one, since we only wrote one `#[modifies]`? - -I decided it was better to not include modifies clauses to avoid confusing the user. -Thus, for functions that only have modifies clauses, the subcommand will report that the user has zero contracts. -However, users could still be confused by this choice because Kani allows users to run `proof_for_contract` harnesses on functions that only have `modifies` clauses. -(If Kani detects no contract-related attributes at all, it errors.) -A user may be confused as to why the list subcommand reports zero contracts when they are running a `proof_for_contract` harness without errors. - -### Compiler - Build Cache -The compiler is unaware of the list subcommand; i.e., there are no special arguments to indicate to the compiler that the list subcommand is running. -If the user only wants to invoke the list subcommand, this design wastes work, since Kani generates unnecessary GOTO files. -However, it also allows the compiler to take advantage of the build cache. -If a user verifies their code and then invokes `list` (or vice versa), -the compiler will be invoked with the same arguments both times, which means that it can just use the cached targets from the first compiler invocation. - -### Pretty Format -The Contracts table is close to Markdown, but not quite Markdown--it includes line separators between each row, -when Markdown would only have a separator for the header. +The Contracts table is close to Markdown, but not quite Markdown--it includes line separators between each row, when Markdown would only have a separator for the header. We include the separator because without it, it can be difficult to tell from reading the terminal output which entries are in the same row. -The user can transform the table to Markdown by deleting these separators, and we can trivially add a Markdown option in the future if there is demand. +The user can transform the table to Markdown by deleting these separators, and we can trivially add a Markdown option in the future if there is demand for it. ## Open questions -1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts or the number of `requires` and `ensures` contracts. +1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts or the number of contracts. 2. More generally, we could introduce additional options that collect information about other Kani attributes (e.g., stubs). The default would be to leave them out, but this way a user could get more verbose output if they so choose. 3. Do we want to add a filtering option? For instance, `--harnesses ` and `--contracts `, where `pattern` corresponds to a Rust-style path. For example, `kani list --harnesses "my_crate::my_module::*"` would include all harnesses with that path prefix, while `kani list --contracts "my_crate::my_module::*"` would include all functions under contract with that path prefix. (If we do this work, we could use it to improve our `--harness` [pattern handling for verification](https://github.com/model-checking/kani/blob/main/kani-driver/src/metadata.rs#L187-L189)). diff --git a/tests/script-based-pre/cargo_list/list.expected b/tests/script-based-pre/cargo_list/list.expected index 3e57203438dc..0627cca6cada 100644 --- a/tests/script-based-pre/cargo_list/list.expected +++ b/tests/script-based-pre/cargo_list/list.expected @@ -19,7 +19,6 @@ { "function": "example::implementation::bar", "file": "src/lib.rs", - "total_contracts": 4, "harnesses": [ "example::verify::check_bar" ] @@ -27,7 +26,6 @@ { "function": "example::implementation::foo", "file": "src/lib.rs", - "total_contracts": 2, "harnesses": [ "example::verify::check_foo_u32", "example::verify::check_foo_u64" @@ -36,7 +34,6 @@ { "function": "example::implementation::func", "file": "src/lib.rs", - "total_contracts": 1, "harnesses": [ "example::verify::check_func" ] @@ -44,14 +41,12 @@ { "function": "example::prep::parse", "file": "src/lib.rs", - "total_contracts": 1, "harnesses": [] } ], "totals": { "standard-harnesses": 2, "contract-harnesses": 4, - "functions-under-contract": 4, - "contracts": 8 + "functions-under-contract": 4 } } \ No newline at end of file diff --git a/tests/script-based-pre/kani_list/list.expected b/tests/script-based-pre/kani_list/list.expected index 806a2d0e7550..abcd99c01641 100644 --- a/tests/script-based-pre/kani_list/list.expected +++ b/tests/script-based-pre/kani_list/list.expected @@ -19,7 +19,6 @@ { "function": "example::implementation::bar", "file": "src/lib.rs", - "total_contracts": 4, "harnesses": [ "example::verify::check_bar" ] @@ -27,7 +26,6 @@ { "function": "example::implementation::foo", "file": "src/lib.rs", - "total_contracts": 2, "harnesses": [ "example::verify::check_foo_u32", "example::verify::check_foo_u64" @@ -36,7 +34,6 @@ { "function": "example::implementation::func", "file": "src/lib.rs", - "total_contracts": 1, "harnesses": [ "example::verify::check_func" ] @@ -44,14 +41,12 @@ { "function": "example::prep::parse", "file": "src/lib.rs", - "total_contracts": 1, "harnesses": [] } ], "totals": { "standard-harnesses": 2, "contract-harnesses": 4, - "functions-under-contract": 4, - "contracts": 8 + "functions-under-contract": 4 } } \ No newline at end of file From 9daa8ba0d0505ff97086470ff8c1b316458a2096 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 2 Oct 2024 21:16:53 -0400 Subject: [PATCH 28/34] use StableDefId --- kani-compiler/src/kani_middle/metadata.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 1273af5e801a..16793b23cbae 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -7,13 +7,12 @@ use std::collections::HashMap; use std::path::Path; use crate::kani_middle::attributes::{KaniAttributes, test_harness_name}; -use crate::kani_middle::{InternalDefId, SourceLocation}; +use crate::kani_middle::{SourceLocation, stable_fn_def}; use kani_metadata::ContractedFunction; use kani_metadata::{ArtifactType, HarnessAttributes, HarnessKind, HarnessMetadata}; use rustc_middle::ty::TyCtxt; -use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; -use stable_mir::{CrateDef, CrateItems}; +use stable_mir::{CrateDef, CrateItems, DefId}; /// Create the harness metadata for a proof harness for a given function. pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> HarnessMetadata { @@ -50,24 +49,23 @@ pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec { // 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 fn_to_data: HashMap = HashMap::new(); + let mut fn_to_data: HashMap = HashMap::new(); for item in crate_items { - let internal_def_id = rustc_internal::internal(tcx, item.def_id()); - let function = item.name(); let file = SourceLocation::new(item.span()).filename; let attributes = KaniAttributes::for_def_id(tcx, item.def_id()); if attributes.has_contract() { - fn_to_data.insert(internal_def_id, ContractedFunction { + fn_to_data.insert(item.def_id(), ContractedFunction { function, file, harnesses: vec![], }); - } else if let Some((target_name, target_def_id, _)) = + } else if let Some((target_name, internal_def_id, _)) = attributes.interpret_for_contract_attribute() { + let target_def_id = stable_fn_def(tcx, internal_def_id).unwrap().def_id(); if let Some(cf) = fn_to_data.get_mut(&target_def_id) { cf.harnesses.push(function); } else { From cfc1db323005b370fb38b3d71cc0ec9a32d7fc47 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Fri, 4 Oct 2024 10:35:01 -0400 Subject: [PATCH 29/34] Doc comments formatting Co-authored-by: Felipe R. Monteiro --- kani-compiler/src/kani_middle/metadata.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 16793b23cbae..9f5bc5f14a15 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -44,9 +44,9 @@ pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> /// Collects contract and contract harness metadata. /// /// For each function with contracts (or that is a target of a contract harness), -/// construct a ContractedFunction object for it. +/// construct a `ContractedFunction` object for it. pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec { - // We work with stable_mir::CrateItem instead of stable_mir::Instance to include generic items + // 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 fn_to_data: HashMap = HashMap::new(); From 3380cb81aefbb25e2c1dae0b577a2ed248535aed Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Fri, 4 Oct 2024 10:36:38 -0400 Subject: [PATCH 30/34] PR feedback --- kani-compiler/src/kani_middle/metadata.rs | 4 +++- kani-driver/src/list/mod.rs | 2 ++ tests/script-based-pre/cargo_list/Cargo.toml | 2 +- tests/script-based-pre/cargo_list/config.yml | 2 +- tests/script-based-pre/cargo_list/list.expected | 2 +- tests/script-based-pre/kani_list/list.expected | 2 +- tests/script-based-pre/kani_list/list.sh | 2 +- 7 files changed, 10 insertions(+), 6 deletions(-) diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 9f5bc5f14a15..c92b20cf49d6 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -65,7 +65,9 @@ pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec { } else if let Some((target_name, internal_def_id, _)) = attributes.interpret_for_contract_attribute() { - let target_def_id = stable_fn_def(tcx, internal_def_id).unwrap().def_id(); + let target_def_id = stable_fn_def(tcx, internal_def_id) + .expect("The target of a proof for contract should be a function definition") + .def_id(); if let Some(cf) = fn_to_data.get_mut(&target_def_id) { cf.harnesses.push(function); } else { diff --git a/kani-driver/src/list/mod.rs b/kani-driver/src/list/mod.rs index 37ac1485d618..0987a0e9c927 100644 --- a/kani-driver/src/list/mod.rs +++ b/kani-driver/src/list/mod.rs @@ -5,6 +5,8 @@ pub mod collect_metadata; mod output; +/// Stores the total count of standard harnesses, contract harnesses, +/// and functions under contract across all `KaniMetadata` objects. struct Totals { standard_harnesses: usize, contract_harnesses: usize, diff --git a/tests/script-based-pre/cargo_list/Cargo.toml b/tests/script-based-pre/cargo_list/Cargo.toml index 487d2356f381..2f213d2fccd7 100644 --- a/tests/script-based-pre/cargo_list/Cargo.toml +++ b/tests/script-based-pre/cargo_list/Cargo.toml @@ -7,4 +7,4 @@ version = "0.1.0" edition = "2021" [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] } \ No newline at end of file +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] } diff --git a/tests/script-based-pre/cargo_list/config.yml b/tests/script-based-pre/cargo_list/config.yml index ce681685e7c2..4eac6f79588c 100644 --- a/tests/script-based-pre/cargo_list/config.yml +++ b/tests/script-based-pre/cargo_list/config.yml @@ -1,4 +1,4 @@ # Copyright Kani Contributors # SPDX-License-Identifier: Apache-2.0 OR MIT script: list.sh -expected: list.expected \ No newline at end of file +expected: list.expected diff --git a/tests/script-based-pre/cargo_list/list.expected b/tests/script-based-pre/cargo_list/list.expected index 0627cca6cada..fb168c5a3bd2 100644 --- a/tests/script-based-pre/cargo_list/list.expected +++ b/tests/script-based-pre/cargo_list/list.expected @@ -49,4 +49,4 @@ "contract-harnesses": 4, "functions-under-contract": 4 } -} \ No newline at end of file +} diff --git a/tests/script-based-pre/kani_list/list.expected b/tests/script-based-pre/kani_list/list.expected index abcd99c01641..e2ed4506eb4e 100644 --- a/tests/script-based-pre/kani_list/list.expected +++ b/tests/script-based-pre/kani_list/list.expected @@ -49,4 +49,4 @@ "contract-harnesses": 4, "functions-under-contract": 4 } -} \ No newline at end of file +} diff --git a/tests/script-based-pre/kani_list/list.sh b/tests/script-based-pre/kani_list/list.sh index f969709e081d..e7bb6f081044 100755 --- a/tests/script-based-pre/kani_list/list.sh +++ b/tests/script-based-pre/kani_list/list.sh @@ -7,4 +7,4 @@ # to avoid having to update the test every time we bump versions. kani list -Z list -Z function-contracts src/lib.rs --format json -cat "kani-list.json" \ No newline at end of file +cat "kani-list.json" From e3455c07c4f04033c5e9d838c9ab666459e83b63 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 7 Oct 2024 18:30:38 -0400 Subject: [PATCH 31/34] Apply RFC suggestions from code review Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> --- rfc/src/rfcs/0013-list.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index 6fa61f7f56f8..3ba8b7ca638f 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -21,8 +21,8 @@ This feature will not cause any regressions for exisiting users. ## User Experience Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand takes two options: -- `--message-format=[pretty|json]`: change the output format. The default is `pretty`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. -- `--std`: Include if we are running on the standard library. This option is only available for `kani list` (not `cargo kani list`), which mirrors the verification workflow for the standard library. +- `--message-format=[pretty|json]`: choose the output format. The default is `pretty`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. +- `--std`: this option should be specified when listing the harnesses and contracts in the standard library. This option is only available for `kani list` (not `cargo kani list`), which mirrors the verification workflow for the standard library. This subcommand does not fail. In the case that it does not find any harnesses or contracts, it prints a message informing the user of that fact. @@ -31,7 +31,7 @@ This subcommand does not fail. In the case that it does not find any harnesses o The default format, `pretty`, prints a "Contracts" table and a "Standard Harnesses" list. Each row of the "Contracts" table consists of a function, the number of contracts it has, and its contract harnesses. A function is listed if it has contracts or it is the target of contract harness(es). -The results are printed in lexographic order. +The results are printed in lexicographic order. For example: @@ -62,7 +62,7 @@ Standard Harnesses (#[kani::proof]): ``` All sections will be present in the output, regardless of the result. -If there are no harnesses for a function under contract, Kani inserts `NONE` in the "Contract Harnesses" row. +If there are no harnesses for a function under contract, Kani inserts `NONE` in the "Contract Harnesses" column. If the "Contracts" section is empty, Kani prints a message that "No contracts or contract harnesses were found." If the "Standard Harnesses" section is empty, Kani prints a message that "No standard harnesses were found." From 2ad13605c2ff3e082fab5fcf8059890f7cfc2286 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 7 Oct 2024 18:31:14 -0400 Subject: [PATCH 32/34] Apply suggestions from code review Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> --- kani-driver/src/list/collect_metadata.rs | 2 +- kani-driver/src/list/output.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs index 5769e374d546..99da0477314d 100644 --- a/kani-driver/src/list/collect_metadata.rs +++ b/kani-driver/src/list/collect_metadata.rs @@ -18,7 +18,7 @@ use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; /// Process the KaniMetadata output from kani-compiler and output the list subcommand results fn process_metadata(metadata: Vec, format: Format) -> Result<()> { - // We use ordered maps and sets so that the output is in lexographic order (and consistent across invocations). + // We use ordered maps and sets so that the output is in lexicographic order (and consistent across invocations). // Map each file to a vector of its harnesses. let mut standard_harnesses: BTreeMap> = BTreeMap::new(); diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs index 70c6f3e23ced..79a5fcf6fe5e 100644 --- a/kani-driver/src/list/output.rs +++ b/kani-driver/src/list/output.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// This module handles outputting the result for the list subcommand +//! This module handles outputting the result for the list subcommand use std::{ cmp::max, From 1cc50a081c6e51bbb0656b298fb54e1e6df24542 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 7 Oct 2024 18:32:53 -0400 Subject: [PATCH 33/34] remove contracts count mention from RFC --- rfc/src/rfcs/0013-list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index 3ba8b7ca638f..771e7e3d7114 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -29,7 +29,7 @@ This subcommand does not fail. In the case that it does not find any harnesses o ### Pretty Format The default format, `pretty`, prints a "Contracts" table and a "Standard Harnesses" list. -Each row of the "Contracts" table consists of a function, the number of contracts it has, and its contract harnesses. +Each row of the "Contracts" table consists of a function under contract and its contract harnesses. A function is listed if it has contracts or it is the target of contract harness(es). The results are printed in lexicographic order. From c2474e38073eb93433e8a896146b823e026f0c11 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 7 Oct 2024 18:36:06 -0400 Subject: [PATCH 34/34] rfc nit --- rfc/src/rfcs/0013-list.md | 1 - 1 file changed, 1 deletion(-) diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index 771e7e3d7114..0d2baee2b594 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -30,7 +30,6 @@ This subcommand does not fail. In the case that it does not find any harnesses o The default format, `pretty`, prints a "Contracts" table and a "Standard Harnesses" list. Each row of the "Contracts" table consists of a function under contract and its contract harnesses. -A function is listed if it has contracts or it is the target of contract harness(es). The results are printed in lexicographic order. For example: