From 3bc4f0c76f8a64e62241daf61e81de18b66f0cd2 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 31 Jul 2024 11:50:08 -0700 Subject: [PATCH 01/28] Enable concrete playback for contract and stubs (#3389) This PR enables concrete playback for contract and stubs. Since these two cases may not actually behave as users expect (it can even have an internal failure), I am adding documentation to the generated test calling that out. As I was testing this issue, I realized that concrete playback didn't quite work for arrays. So I introduced a new private function `any_raw_array`, which doesn't change the behavior during verification, but allow us to special case it in the concrete playback flow. Resolves #3383 --------- Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> --- kani-compiler/src/kani_middle/attributes.rs | 45 +++++----- .../src/kani_middle/codegen_units.rs | 2 +- kani-compiler/src/kani_middle/metadata.rs | 4 +- kani-driver/src/args/mod.rs | 13 +-- .../src/concrete_playback/test_generator.rs | 85 +++++++++++++++---- kani-driver/src/metadata.rs | 16 ++-- kani_metadata/src/harness.rs | 32 ++++++- library/kani/src/arbitrary.rs | 2 +- library/kani/src/concrete_playback.rs | 5 ++ library/kani/src/lib.rs | 16 ++-- library/kani_core/src/arbitrary.rs | 2 +- library/kani_core/src/lib.rs | 16 ++-- .../kani_macros/src/sysroot/contracts/mod.rs | 11 +-- .../playback_expected/config.yml | 4 + .../playback_expected/expected | 8 ++ .../playback_expected/playback.sh | 42 +++++++++ .../src/playback_contract.rs | 19 +++++ .../playback_expected/src/playback_stubs.rs | 34 ++++++++ .../verify_std_cmd/verify_std.sh | 2 +- 19 files changed, 279 insertions(+), 79 deletions(-) create mode 100644 tests/script-based-pre/playback_expected/config.yml create mode 100644 tests/script-based-pre/playback_expected/expected create mode 100755 tests/script-based-pre/playback_expected/playback.sh create mode 100644 tests/script-based-pre/playback_expected/src/playback_contract.rs create mode 100644 tests/script-based-pre/playback_expected/src/playback_stubs.rs diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 9eb6d3d6ee4e..8c729bbdec9f 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; -use kani_metadata::{CbmcSolver, HarnessAttributes, Stub}; +use kani_metadata::{CbmcSolver, HarnessAttributes, HarnessKind, Stub}; use rustc_ast::{ attr, token::Token, @@ -310,7 +310,7 @@ impl<'tcx> KaniAttributes<'tcx> { /// the session and emit all errors found. pub(super) fn check_attributes(&self) { // Check that all attributes are correctly used and well formed. - let is_harness = self.is_harness(); + let is_harness = self.is_proof_harness(); for (&kind, attrs) in self.map.iter() { let local_error = |msg| self.tcx.dcx().span_err(attrs[0].span, msg); @@ -454,7 +454,7 @@ impl<'tcx> KaniAttributes<'tcx> { /// Is this item a harness? (either `proof` or `proof_for_contract` /// attribute are present) - fn is_harness(&self) -> bool { + fn is_proof_harness(&self) -> bool { self.map.contains_key(&KaniAttributeKind::Proof) || self.map.contains_key(&KaniAttributeKind::ProofForContract) } @@ -469,13 +469,18 @@ impl<'tcx> KaniAttributes<'tcx> { panic!("Expected a local item, but got: {:?}", self.item); }; trace!(?self, "extract_harness_attributes"); - assert!(self.is_harness()); - self.map.iter().fold(HarnessAttributes::default(), |mut harness, (kind, attributes)| { + assert!(self.is_proof_harness()); + let harness_attrs = if let Some(Ok(harness)) = self.proof_for_contract() { + HarnessAttributes::new(HarnessKind::ProofForContract { target_fn: harness.to_string() }) + } else { + HarnessAttributes::new(HarnessKind::Proof) + }; + self.map.iter().fold(harness_attrs, |mut harness, (kind, attributes)| { match kind { KaniAttributeKind::ShouldPanic => harness.should_panic = true, KaniAttributeKind::Recursion => { self.tcx.dcx().span_err(self.tcx.def_span(self.item), "The attribute `kani::recursion` should only be used in combination with function contracts."); - }, + } KaniAttributeKind::Solver => { harness.solver = parse_solver(self.tcx, attributes[0]); } @@ -485,7 +490,7 @@ impl<'tcx> KaniAttributes<'tcx> { KaniAttributeKind::Unwind => { harness.unwind_value = parse_unwind(self.tcx, attributes[0]) } - KaniAttributeKind::Proof => harness.proof = true, + KaniAttributeKind::Proof => { /* no-op */ } KaniAttributeKind::ProofForContract => self.handle_proof_for_contract(&mut harness), KaniAttributeKind::StubVerified => self.handle_stub_verified(&mut harness), KaniAttributeKind::Unstable => { @@ -498,7 +503,7 @@ impl<'tcx> KaniAttributes<'tcx> { | KaniAttributeKind::InnerCheck | KaniAttributeKind::ReplacedWith => { self.tcx.dcx().span_err(self.tcx.def_span(self.item), format!("Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)", kind.as_ref())); - }, + } KaniAttributeKind::DisableChecks => { // Internal attribute which shouldn't exist here. unreachable!() @@ -552,14 +557,14 @@ impl<'tcx> KaniAttributes<'tcx> { self.item_name(), ), ) - .with_span_note( - self.tcx.def_span(def_id), - format!( - "Try adding a contract to this function or use the unsound `{}` attribute instead.", - KaniAttributeKind::Stub.as_ref(), + .with_span_note( + self.tcx.def_span(def_id), + format!( + "Try adding a contract to this function or use the unsound `{}` attribute instead.", + KaniAttributeKind::Stub.as_ref(), + ), ) - ) - .emit(); + .emit(); continue; } Some(Ok(replacement_name)) => replacement_name, @@ -689,7 +694,7 @@ fn has_kani_attribute bool>( tcx.get_attrs_unchecked(def_id).iter().filter_map(|a| attr_kind(tcx, a)).any(predicate) } -/// Same as [`KaniAttributes::is_harness`] but more efficient because less +/// Same as [`KaniAttributes::is_proof_harness`] but more efficient because less /// attribute parsing is performed. pub fn is_proof_harness(tcx: TyCtxt, instance: InstanceStable) -> bool { let def_id = rustc_internal::internal(tcx, instance.def.def_id()); @@ -896,7 +901,7 @@ fn parse_stubs(tcx: TyCtxt, harness: DefId, attributes: &[&Attribute]) -> Vec { tcx.dcx().span_err( error_span, - "attribute `kani::stub` takes two path arguments; found argument that is not a path", + "attribute `kani::stub` takes two path arguments; found argument that is not a path", ); None } @@ -910,9 +915,9 @@ fn parse_solver(tcx: TyCtxt, attr: &Attribute) -> Option { const ATTRIBUTE: &str = "#[kani::solver]"; let invalid_arg_err = |attr: &Attribute| { tcx.dcx().span_err( - attr.span, - format!("invalid argument for `{ATTRIBUTE}` attribute, expected one of the supported solvers (e.g. `kissat`) or a SAT solver binary (e.g. `bin=\"\"`)") - ) + attr.span, + format!("invalid argument for `{ATTRIBUTE}` attribute, expected one of the supported solvers (e.g. `kissat`) or a SAT solver binary (e.g. `bin=\"\"`)"), + ) }; let attr_args = attr.meta_item_list().unwrap(); diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index 260b363a868a..b4ea06c8d5db 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -104,7 +104,7 @@ impl CodegenUnits { /// Generate [KaniMetadata] for the target crate. fn generate_metadata(&self) -> KaniMetadata { let (proof_harnesses, test_harnesses) = - self.harness_info.values().cloned().partition(|md| md.attributes.proof); + self.harness_info.values().cloned().partition(|md| md.attributes.is_proof_harness()); KaniMetadata { crate_name: self.crate_info.name.clone(), proof_harnesses, diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index c00f38be4cb0..2f0f22d49e1c 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -6,7 +6,7 @@ use std::path::Path; use crate::kani_middle::attributes::test_harness_name; -use kani_metadata::{ArtifactType, HarnessAttributes, HarnessMetadata}; +use kani_metadata::{ArtifactType, HarnessAttributes, HarnessKind, HarnessMetadata}; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::CrateDef; @@ -61,7 +61,7 @@ pub fn gen_test_metadata( original_file: loc.filename, original_start_line: loc.start_line, original_end_line: loc.end_line, - attributes: HarnessAttributes::default(), + attributes: HarnessAttributes::new(HarnessKind::Test), // TODO: This no longer needs to be an Option. goto_file: Some(model_file), contract: Default::default(), diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 158f1c001b5b..2d7593e8050a 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -559,13 +559,6 @@ impl ValidateArgs for VerificationArgs { --output-format=old.", )); } - if self.concrete_playback.is_some() && self.is_stubbing_enabled() { - // Concrete playback currently does not work with contracts or stubbing. - return Err(Error::raw( - ErrorKind::ArgumentConflict, - "Conflicting options: --concrete-playback isn't compatible with stubbing.", - )); - } if self.concrete_playback.is_some() && self.jobs() != Some(1) { // Concrete playback currently embeds a lot of assumptions about the order in which harnesses get called. return Err(Error::raw( @@ -869,13 +862,13 @@ mod tests { let res = parse_unstable_disabled("--harness foo -Z stubbing").unwrap(); assert!(res.verify_opts.is_stubbing_enabled()); - // `-Z stubbing` cannot be called with `--concrete-playback` + // `-Z stubbing` can now be called with concrete playback. let res = parse_unstable_disabled( "--harness foo --concrete-playback=print -Z concrete-playback -Z stubbing", ) .unwrap(); - let err = res.validate().unwrap_err(); - assert_eq!(err.kind(), ErrorKind::ArgumentConflict); + // Note that `res.validate()` fails because input file does not exist. + assert!(matches!(res.verify_opts.validate(), Ok(()))); } #[test] diff --git a/kani-driver/src/concrete_playback/test_generator.rs b/kani-driver/src/concrete_playback/test_generator.rs index dbd2fb9e03d2..5faa6299a5d3 100644 --- a/kani-driver/src/concrete_playback/test_generator.rs +++ b/kani-driver/src/concrete_playback/test_generator.rs @@ -6,10 +6,11 @@ use crate::args::ConcretePlaybackMode; use crate::call_cbmc::VerificationResult; +use crate::cbmc_output_parser::Property; use crate::session::KaniSession; use anyhow::{Context, Result}; use concrete_vals_extractor::{extract_harness_values, ConcreteVal}; -use kani_metadata::HarnessMetadata; +use kani_metadata::{HarnessKind, HarnessMetadata}; use std::collections::hash_map::DefaultHasher; use std::ffi::OsString; use std::fs::{read_to_string, File}; @@ -32,7 +33,7 @@ impl KaniSession { }; if let Ok(result_items) = &verification_result.results { - let harness_values: Vec> = extract_harness_values(result_items); + let harness_values = extract_harness_values(result_items); if harness_values.is_empty() { println!( @@ -43,9 +44,9 @@ impl KaniSession { } else { let mut unit_tests: Vec = harness_values .iter() - .map(|concrete_vals| { + .map(|(prop, concrete_vals)| { let pretty_name = harness.get_harness_name_unqualified(); - format_unit_test(&pretty_name, &concrete_vals) + format_unit_test(&pretty_name, &concrete_vals, gen_test_doc(harness, prop)) }) .collect(); unit_tests.dedup_by(|a, b| a.name == b.name); @@ -168,6 +169,9 @@ impl KaniSession { writeln!(temp_file, "{line}")?; if curr_line_num == proof_harness_end_line { for unit_test in unit_tests.iter() { + // Write an empty line before the unit test. + writeln!(temp_file)?; + for unit_test_line in unit_test.code.iter() { curr_line_num += 1; writeln!(temp_file, "{unit_test_line}")?; @@ -176,7 +180,7 @@ impl KaniSession { } } - // Renames are usually automic, so we won't corrupt the user's source file during a + // Renames are usually atomic, so we won't corrupt the user's source file during a // crash; but first flush all updates to disk, which persist wouldn't take care of. temp_file.as_file().sync_all()?; temp_file.persist(source_path).expect("Could not rename file"); @@ -231,8 +235,52 @@ impl KaniSession { } } +fn gen_test_doc(harness: &HarnessMetadata, property: &Property) -> String { + let mut doc_str = match &harness.attributes.kind { + HarnessKind::Proof => { + format!("/// Test generated for harness `{}` \n", harness.pretty_name) + } + HarnessKind::ProofForContract { target_fn } => { + format!( + "/// Test generated for harness `{}` that checks contract for `{target_fn}`\n", + harness.pretty_name + ) + } + HarnessKind::Test => { + unreachable!("Concrete playback for tests is not supported") + } + }; + doc_str.push_str("///\n"); + doc_str.push_str(&format!( + "/// Check for `{}`: \"{}\"\n", + property.property_class(), + property.description + )); + if !harness.attributes.stubs.is_empty() { + doc_str.push_str( + r#"/// +/// # Warning +/// +/// Concrete playback tests combined with stubs or contracts is highly +/// experimental, and subject to change. +/// +/// The original harness has stubs which are not applied to this test. +/// This may cause a mismatch of non-deterministic values if the stub +/// creates any non-deterministic value. +/// The execution path may also differ, which can be used to refine the stub +/// logic. +"#, + ); + } + doc_str +} + /// Generate a formatted unit test from a list of concrete values. -fn format_unit_test(harness_name: &str, concrete_vals: &[ConcreteVal]) -> UnitTest { +fn format_unit_test( + harness_name: &str, + concrete_vals: &[ConcreteVal], + doc_str: String, +) -> UnitTest { // Hash the concrete values along with the proof harness name. let mut hasher = DefaultHasher::new(); harness_name.hash(&mut hasher); @@ -241,6 +289,7 @@ fn format_unit_test(harness_name: &str, concrete_vals: &[ConcreteVal]) -> UnitTe let func_name = format!("kani_concrete_playback_{harness_name}_{hash}"); let func_before_concrete_vals = [ + doc_str, "#[test]".to_string(), format!("fn {func_name}() {{"), format!("{:<4}let concrete_vals: Vec> = vec![", " "), @@ -324,7 +373,7 @@ mod concrete_vals_extractor { /// Extract a set of concrete values that trigger one assertion /// failure. Each element of the outer vector corresponds to /// inputs triggering one assertion failure or cover statement. - pub fn extract_harness_values(result_items: &[Property]) -> Vec> { + pub fn extract_harness_values(result_items: &[Property]) -> Vec<(&Property, Vec)> { result_items .iter() .filter(|prop| { @@ -340,7 +389,7 @@ mod concrete_vals_extractor { let concrete_vals: Vec = trace.iter().filter_map(&extract_from_trace_item).collect(); - concrete_vals + (property, concrete_vals) }) .collect() } @@ -359,7 +408,7 @@ mod concrete_vals_extractor { { if trace_item.step_type == "assignment" && lhs.starts_with("goto_symex$$return_value") - && func.starts_with("kani::any_raw_internal") + && func.starts_with("kani::any_raw_") { let declared_width = width_u64 as usize; let actual_width = bit_concrete_val.len(); @@ -484,9 +533,10 @@ mod tests { /// Since hashes can not be relied on in tests, this compares all parts of a unit test except the hash. #[test] fn format_unit_test_full_func() { + let doc_str = "/// Test documentation"; let harness_name = "test_proof_harness"; let concrete_vals = [ConcreteVal { byte_arr: vec![0, 0], interp_val: "0".to_string() }]; - let unit_test = format_unit_test(harness_name, &concrete_vals); + let unit_test = format_unit_test(harness_name, &concrete_vals, doc_str.to_string()); let full_func = unit_test.code; let split_unit_test_name = split_unit_test_name(&unit_test.name); let expected_after_func_name = vec![ @@ -498,18 +548,23 @@ mod tests { "}".to_string(), ]; - assert_eq!(full_func[0], "#[test]"); + assert_eq!(full_func[0], doc_str); + assert_eq!(full_func[1], "#[test]"); assert_eq!( split_unit_test_name.before_hash, format!("kani_concrete_playback_{harness_name}") ); - assert_eq!(full_func[1], format!("fn {}() {{", unit_test.name)); - assert_eq!(full_func[2..], expected_after_func_name); + assert_eq!(full_func[2], format!("fn {}() {{", unit_test.name)); + assert_eq!(full_func[3..], expected_after_func_name); } /// Generates a unit test and returns its hash. fn extract_hash_from_unit_test(harness_name: &str, concrete_vals: &[ConcreteVal]) -> String { - let unit_test = format_unit_test(harness_name, concrete_vals); + let unit_test = format_unit_test( + harness_name, + concrete_vals, + "/// Harness created for unit test".to_string(), + ); split_unit_test_name(&unit_test.name).hash } @@ -603,7 +658,7 @@ mod tests { }), }]), }]; - let concrete_vals = extract_harness_values(&processed_items).pop().unwrap(); + let (_, concrete_vals) = extract_harness_values(&processed_items).pop().unwrap(); let concrete_val = &concrete_vals[0]; assert_eq!(concrete_val.byte_arr, vec![1, 3]); diff --git a/kani-driver/src/metadata.rs b/kani-driver/src/metadata.rs index cd916358d92e..3f9cd8f2bf84 100644 --- a/kani-driver/src/metadata.rs +++ b/kani-driver/src/metadata.rs @@ -200,7 +200,7 @@ fn find_proof_harnesses<'a>( #[cfg(test)] pub mod tests { use super::*; - use kani_metadata::HarnessAttributes; + use kani_metadata::{HarnessAttributes, HarnessKind}; use std::path::PathBuf; pub fn mock_proof_harness( @@ -209,6 +209,8 @@ pub mod tests { krate: Option<&str>, model_file: Option, ) -> HarnessMetadata { + let mut attributes = HarnessAttributes::new(HarnessKind::Proof); + attributes.unwind_value = unwind_value; HarnessMetadata { pretty_name: name.into(), mangled_name: name.into(), @@ -216,7 +218,7 @@ pub mod tests { original_file: "".into(), original_start_line: 0, original_end_line: 0, - attributes: HarnessAttributes { unwind_value, proof: true, ..Default::default() }, + attributes, goto_file: model_file, contract: Default::default(), } @@ -236,7 +238,7 @@ pub mod tests { find_proof_harnesses( &BTreeSet::from([&"check_three".to_string()]), &ref_harnesses, - false + false, ) .len(), 1 @@ -245,7 +247,7 @@ pub mod tests { find_proof_harnesses( &BTreeSet::from([&"check_two".to_string()]), &ref_harnesses, - false + false, ) .first() .unwrap() @@ -256,7 +258,7 @@ pub mod tests { find_proof_harnesses( &BTreeSet::from([&"check_one".to_string()]), &ref_harnesses, - false + false, ) .first() .unwrap() @@ -280,7 +282,7 @@ pub mod tests { find_proof_harnesses( &BTreeSet::from([&"check_three".to_string()]), &ref_harnesses, - true + true, ) .is_empty() ); @@ -299,7 +301,7 @@ pub mod tests { find_proof_harnesses( &BTreeSet::from([&"module::not_check_three".to_string()]), &ref_harnesses, - true + true, ) .first() .unwrap() diff --git a/kani_metadata/src/harness.rs b/kani_metadata/src/harness.rs index 3dd6c82ebd39..41eb4eb20919 100644 --- a/kani_metadata/src/harness.rs +++ b/kani_metadata/src/harness.rs @@ -38,10 +38,10 @@ pub struct HarnessMetadata { } /// The attributes added by the user to control how a harness is executed. -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct HarnessAttributes { /// Whether the harness has been annotated with proof. - pub proof: bool, + pub kind: HarnessKind, /// Whether the harness is expected to panic or not. pub should_panic: bool, /// Optional data to store solver. @@ -52,6 +52,34 @@ pub struct HarnessAttributes { pub stubs: Vec, } +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub enum HarnessKind { + /// Function was annotated with `#[kani::proof]`. + Proof, + /// Function was annotated with `#[kani::proof_for_contract(target_fn)]`. + ProofForContract { target_fn: String }, + /// This is a test harness annotated with `#[test]`. + Test, +} + +impl HarnessAttributes { + /// Create a new harness of the provided kind. + pub fn new(kind: HarnessKind) -> HarnessAttributes { + HarnessAttributes { + kind, + should_panic: false, + solver: None, + unwind_value: None, + stubs: vec![], + } + } + + /// Return whether this is a proof harness. + pub fn is_proof_harness(&self) -> bool { + matches!(self.kind, HarnessKind::Proof | HarnessKind::ProofForContract { .. }) + } +} + /// The stubbing type. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Stub { diff --git a/library/kani/src/arbitrary.rs b/library/kani/src/arbitrary.rs index ef6e2ef23dd4..83b113d64927 100644 --- a/library/kani/src/arbitrary.rs +++ b/library/kani/src/arbitrary.rs @@ -31,7 +31,7 @@ macro_rules! trivial_arbitrary { unsafe { crate::any_raw_internal::() } } fn any_array() -> [Self; MAX_ARRAY_LENGTH] { - unsafe { crate::any_raw_internal::<[Self; MAX_ARRAY_LENGTH]>() } + unsafe { crate::any_raw_array::() } } } }; diff --git a/library/kani/src/concrete_playback.rs b/library/kani/src/concrete_playback.rs index aa6cd7e4018d..0de51862b7d8 100644 --- a/library/kani/src/concrete_playback.rs +++ b/library/kani/src/concrete_playback.rs @@ -40,6 +40,11 @@ pub fn concrete_playback_run(mut local_concrete_vals: Vec>, pro }); } +/// Iterate over `any_raw_internal` since CBMC produces assignment per element. +pub(crate) unsafe fn any_raw_array() -> [T; N] { + [(); N].map(|_| crate::any_raw_internal::()) +} + /// Concrete playback implementation of /// kani::any_raw_internal. Because CBMC does not bother putting in /// Zero-Sized Types, those are defaulted to an empty vector. diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 3cf46bd7af07..046c6e7a0667 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -249,21 +249,25 @@ pub fn any_where bool>(f: F) -> T { /// Note that SIZE_T must be equal the size of type T in bytes. #[inline(never)] #[cfg(not(feature = "concrete_playback"))] -pub(crate) unsafe fn any_raw_internal() -> T { - any_raw_inner::() +unsafe fn any_raw_internal() -> T { + any_raw::() } +/// This is the same as [any_raw_internal] for verification flow, but not for concrete playback. #[inline(never)] -#[cfg(feature = "concrete_playback")] -pub(crate) unsafe fn any_raw_internal() -> T { - concrete_playback::any_raw_internal::() +#[cfg(not(feature = "concrete_playback"))] +unsafe fn any_raw_array() -> [T; N] { + any_raw::<[T; N]>() } +#[cfg(feature = "concrete_playback")] +use concrete_playback::{any_raw_array, any_raw_internal}; + /// This low-level function returns nondet bytes of size T. #[rustc_diagnostic_item = "KaniAnyRaw"] #[inline(never)] #[allow(dead_code)] -fn any_raw_inner() -> T { +fn any_raw() -> T { kani_intrinsic() } diff --git a/library/kani_core/src/arbitrary.rs b/library/kani_core/src/arbitrary.rs index 7cfb649b11a0..8c6cfd335104 100644 --- a/library/kani_core/src/arbitrary.rs +++ b/library/kani_core/src/arbitrary.rs @@ -34,7 +34,7 @@ macro_rules! generate_arbitrary { unsafe { crate::kani::any_raw_internal::() } } fn any_array() -> [Self; MAX_ARRAY_LENGTH] { - unsafe { crate::kani::any_raw_internal::<[Self; MAX_ARRAY_LENGTH]>() } + unsafe { crate::kani::any_raw_array::() } } } }; diff --git a/library/kani_core/src/lib.rs b/library/kani_core/src/lib.rs index 016c805e8f8e..9baba1abe886 100644 --- a/library/kani_core/src/lib.rs +++ b/library/kani_core/src/lib.rs @@ -248,21 +248,25 @@ macro_rules! kani_intrinsics { /// Note that SIZE_T must be equal the size of type T in bytes. #[inline(never)] #[cfg(not(feature = "concrete_playback"))] - pub(crate) unsafe fn any_raw_internal() -> T { - any_raw_inner::() + unsafe fn any_raw_internal() -> T { + any_raw::() } + /// This is the same as [any_raw_internal] for verification flow, but not for concrete playback. #[inline(never)] - #[cfg(feature = "concrete_playback")] - pub(crate) unsafe fn any_raw_internal() -> T { - concrete_playback::any_raw_internal::() + #[cfg(not(feature = "concrete_playback"))] + unsafe fn any_raw_array() -> [T; N] { + any_raw::<[T; N]>() } + #[cfg(feature = "concrete_playback")] + use concrete_playback::{any_raw_array, any_raw_internal}; + /// This low-level function returns nondet bytes of size T. #[rustc_diagnostic_item = "KaniAnyRaw"] #[inline(never)] #[allow(dead_code)] - pub fn any_raw_inner() -> T { + fn any_raw() -> T { kani_intrinsic() } diff --git a/library/kani_macros/src/sysroot/contracts/mod.rs b/library/kani_macros/src/sysroot/contracts/mod.rs index defcd9dae1b4..12a1215de2c7 100644 --- a/library/kani_macros/src/sysroot/contracts/mod.rs +++ b/library/kani_macros/src/sysroot/contracts/mod.rs @@ -336,7 +336,7 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, Expr, ExprClosure, ItemFn}; +use syn::{parse_macro_input, parse_quote, Expr, ExprClosure, ItemFn}; mod bootstrap; mod check; @@ -387,15 +387,12 @@ passthrough!(stub_verified, false); pub fn proof_for_contract(attr: TokenStream, item: TokenStream) -> TokenStream { let args = proc_macro2::TokenStream::from(attr); - let ItemFn { attrs, vis, sig, block } = parse_macro_input!(item as ItemFn); + let mut fn_item = parse_macro_input!(item as ItemFn); + fn_item.block.stmts.insert(0, parse_quote!(kani::internal::init_contracts();)); quote!( #[allow(dead_code)] #[kanitool::proof_for_contract = stringify!(#args)] - #(#attrs)* - #vis #sig { - kani::internal::init_contracts(); - #block - } + #fn_item ) .into() } diff --git a/tests/script-based-pre/playback_expected/config.yml b/tests/script-based-pre/playback_expected/config.yml new file mode 100644 index 000000000000..d15b5d277ed6 --- /dev/null +++ b/tests/script-based-pre/playback_expected/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: playback.sh +expected: expected diff --git a/tests/script-based-pre/playback_expected/expected b/tests/script-based-pre/playback_expected/expected new file mode 100644 index 000000000000..bedb39581b7f --- /dev/null +++ b/tests/script-based-pre/playback_expected/expected @@ -0,0 +1,8 @@ +[TEST] Generate test for playback_contract.rs... +Verification failed for - check_modify_slice +Result for playback_contract.rs: test result: FAILED. 1 passed; 1 failed + +[TEST] Generate test for playback_stubs.rs... +Verification failed for - check_lt_0 +Verification failed for - check_bad_stub +Result for playback_stubs.rs: test result: FAILED. 1 passed; 1 failed diff --git a/tests/script-based-pre/playback_expected/playback.sh b/tests/script-based-pre/playback_expected/playback.sh new file mode 100755 index 000000000000..3b358db257a2 --- /dev/null +++ b/tests/script-based-pre/playback_expected/playback.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +# This will run Kani verification in every `src/*.rs` file followed by playback command. +# Expected output is generated from individual expected files. +set -o nounset + +run() { + local input_rs=${1:?"Missing input file"} + + echo "[TEST] Generate test for ${input_rs}..." + kani "${input_rs}" \ + -Z concrete-playback --concrete-playback=inplace \ + -Z function-contracts -Z stubbing --output-format terse + + # Note that today one of the tests will succeed since the contract pre-conditions are not inserted by Kani. + # Hopefully this will change with https://github.com/model-checking/kani/issues/3326 + echo "[TEST] Run test for ${input_rs}..." + summary=$(kani playback -Z concrete-playback "${input_rs}" -- kani_concrete_playback | grep "test result") + echo "Result for ${input_rs}: ${summary}" +} + +ROOT_DIR=$(git rev-parse --show-toplevel) +MODIFIED_DIR=modified +rm -rf "${MODIFIED_DIR}" +mkdir "${MODIFIED_DIR}" + +for rs in src/*.rs; do + if [[ -e "${rs}" ]]; then + echo "Running ${rs}" + cp "${rs}" "${MODIFIED_DIR}" + pushd "${MODIFIED_DIR}" > /dev/null + run "$(basename "${rs}")" + popd > /dev/null + else + echo "No .rs files found in src directory" + exit 1 + fi +done + + # Cleanup +rm -rf "${MODIFIED_DIR}" \ No newline at end of file diff --git a/tests/script-based-pre/playback_expected/src/playback_contract.rs b/tests/script-based-pre/playback_expected/src/playback_contract.rs new file mode 100644 index 000000000000..8271e9a3d03f --- /dev/null +++ b/tests/script-based-pre/playback_expected/src/playback_contract.rs @@ -0,0 +1,19 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Check that Kani correctly adds tests to when the harness is a proof for contract. +extern crate kani; + +#[kani::requires(idx < slice.len())] +#[kani::modifies(slice)] +#[kani::ensures(| _ | slice[idx] == new_val)] +fn modify_slice(slice: &mut [u32], idx: usize, new_val: u32) { + // Inject bug by incrementing index first. + let new_idx = idx + 1; + *slice.get_mut(new_idx).expect("Expected valid index, but contract is wrong") = new_val; +} + +#[kani::proof_for_contract(modify_slice)] +fn check_modify_slice() { + let mut data: [u32; 4] = kani::any(); + modify_slice(&mut data, kani::any(), kani::any()) +} diff --git a/tests/script-based-pre/playback_expected/src/playback_stubs.rs b/tests/script-based-pre/playback_expected/src/playback_stubs.rs new file mode 100644 index 000000000000..ce5107949f04 --- /dev/null +++ b/tests/script-based-pre/playback_expected/src/playback_stubs.rs @@ -0,0 +1,34 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Check that Kani playback works with stubs. +#![allow(dead_code)] + +fn is_zero(val: u8) -> bool { + val == 0 +} + +fn not_zero(val: u8) -> bool { + val != 0 +} + +/// Add a harness that will fail due to incorrect stub but the test will succeed. +#[kani::proof] +#[kani::stub(is_zero, not_zero)] +fn check_bad_stub() { + assert!(is_zero(kani::any())) +} + +fn lt_zero(val: i8) -> bool { + val < 0 +} + +fn lt_ten(val: i8) -> bool { + val < 10 +} + +/// Add a harness that will fail in an equivalent way. +#[kani::proof] +#[kani::stub(lt_zero, lt_ten)] +fn check_lt_0() { + assert!(lt_zero(kani::any())) +} diff --git a/tests/script-based-pre/verify_std_cmd/verify_std.sh b/tests/script-based-pre/verify_std_cmd/verify_std.sh index 91454c4b65e7..3a24bf15241e 100755 --- a/tests/script-based-pre/verify_std_cmd/verify_std.sh +++ b/tests/script-based-pre/verify_std_cmd/verify_std.sh @@ -29,7 +29,7 @@ mod verify { use core::kani; #[kani::proof] fn check_non_zero() { - let orig: u32 = kani::any_raw_inner(); + let orig: u32 = kani::any(); if let Some(val) = core::num::NonZeroU32::new(orig) { assert!(orig == val.into()); } else { From 695e6f76832bafd379a9fea2e6434032cebb6166 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 31 Jul 2024 16:15:45 -0400 Subject: [PATCH 02/28] Tutorial Sections 2.1 and 2.2 Updates (#3387) Update the tutorial, namely: - Update the [bounds checking and pointers example](https://model-checking.github.io/kani/tutorial-kinds-of-failure.html#bounds-checking-and-pointers). `cargo test` catches the UB in the current example, so this PR modifies the code slightly so that `cargo test` still misses the UB, as desired. - Rather than including larger sections on experimental features throughout the tutorial, create a separate experimental features section and include (briefer) references to them in the tutorial. - The old text recommended debugging by generating a trace with `--visualize`, with a briefer mention of `--concrete-playback`. Since `--visualize` is deprecated, revise the debugging exercises to recommend `--concrete-playback` instead. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Jaisurya Nanduri <91620234+jaisnan@users.noreply.github.com> --- docs/src/SUMMARY.md | 7 +- docs/src/reference/attributes.md | 2 +- .../experimental/concrete-playback.md} | 19 ++--- docs/src/reference/experimental/coverage.md | 32 ++++++++ .../experimental/experimental-features.md | 5 ++ .../reference/{ => experimental}/stubbing.md | 2 +- docs/src/tutorial-first-steps.md | 60 ++------------- docs/src/tutorial-kinds-of-failure.md | 74 ++++++------------- docs/src/tutorial-real-code.md | 2 +- .../kinds-of-failure/src/bounds_check.rs | 2 +- docs/src/usage.md | 2 +- docs/src/verification-results.md | 2 +- 12 files changed, 81 insertions(+), 128 deletions(-) rename docs/src/{debugging-verification-failures.md => reference/experimental/concrete-playback.md} (87%) create mode 100644 docs/src/reference/experimental/coverage.md create mode 100644 docs/src/reference/experimental/experimental-features.md rename docs/src/reference/{ => experimental}/stubbing.md (99%) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index ff7914c1a07a..784ec075d183 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -12,12 +12,13 @@ - [Failures that Kani can spot](./tutorial-kinds-of-failure.md) - [Loop unwinding](./tutorial-loop-unwinding.md) - [Nondeterministic variables](./tutorial-nondeterministic-variables.md) - - [Debugging verification failures](./debugging-verification-failures.md) - [Reference](./reference.md) - [Attributes](./reference/attributes.md) - - [Stubbing](./reference/stubbing.md) - + - [Experimental features](./reference/experimental/experimental-features.md) + - [Coverage](./reference/experimental/coverage.md) + - [Stubbing](./reference/experimental/stubbing.md) + - [Concrete Playback](./reference/experimental/concrete-playback.md) - [Application](./application.md) - [Comparison with other tools](./tool-comparison.md) - [Where to start on real code](./tutorial-real-code.md) diff --git a/docs/src/reference/attributes.md b/docs/src/reference/attributes.md index bd8b80cbbdaa..4556077911fc 100644 --- a/docs/src/reference/attributes.md +++ b/docs/src/reference/attributes.md @@ -60,7 +60,7 @@ For example, the class in `Check 1: my_harness.assertion.1` is `assertion`, so t > **NOTE**: The `#[kani::should_panic]` is only recommended for writing > harnesses which complement existing harnesses that don't use the same -> attribute. In order words, it's only recommended to write *negative harnesses* +> attribute. In other words, it's only recommended to write *negative harnesses* > after having written *positive* harnesses that successfully verify interesting > properties about the function under verification. diff --git a/docs/src/debugging-verification-failures.md b/docs/src/reference/experimental/concrete-playback.md similarity index 87% rename from docs/src/debugging-verification-failures.md rename to docs/src/reference/experimental/concrete-playback.md index c5dae6e74308..237f5673caf0 100644 --- a/docs/src/debugging-verification-failures.md +++ b/docs/src/reference/experimental/concrete-playback.md @@ -1,20 +1,13 @@ -# Debugging verification failures +# Concrete Playback -When the result of a certain check comes back as a `FAILURE`, -Kani offers different options to help debug: -* `--concrete-playback`. This _experimental_ feature generates a Rust unit test case that plays back a failing -proof harness using a concrete counterexample. -* `--visualize`. This feature generates an HTML text-based trace that -enumerates the execution steps leading to the check failure. - -## Concrete playback +When the result of a certain check comes back as a `FAILURE`, Kani offers the `concrete-playback` option to help debug. This feature generates a Rust unit test case that plays back a failing proof harness using a concrete counterexample. When concrete playback is enabled, Kani will generate unit tests for assertions that failed during verification, as well as cover statements that are reachable. These tests can then be executed using Kani's playback subcommand. -### Usage +## Usage In order to enable this feature, run Kani with the `-Z concrete-playback --concrete-playback=[print|inplace]` flag. After getting a verification failure, Kani will generate a Rust unit test case that plays back a failing @@ -46,7 +39,7 @@ The output will have a line in the beginning like You can further debug the binary with tools like `rust-gdb` or `lldb`. -### Example +## Example Running `kani -Z concrete-playback --concrete-playback=print` on the following source file: ```rust @@ -75,7 +68,7 @@ Here, `133` and `35207` are the concrete values that, when substituted for `a` a cause an assertion failure. `vec![135, 137]` is the byte array representation of `35207`. -### Request for comments +## Request for comments This feature is experimental and is therefore subject to change. If you have ideas for improving the user experience of this feature, @@ -83,7 +76,7 @@ please add them to [this GitHub issue](https://github.com/model-checking/kani/is We are tracking the existing feature requests in [this GitHub milestone](https://github.com/model-checking/kani/milestone/10). -### Limitations +## Limitations * This feature does not generate unit tests for failing non-panic checks (e.g., UB checks). This is because checks would not trigger runtime errors during concrete playback. diff --git a/docs/src/reference/experimental/coverage.md b/docs/src/reference/experimental/coverage.md new file mode 100644 index 000000000000..fb73d5f7c05b --- /dev/null +++ b/docs/src/reference/experimental/coverage.md @@ -0,0 +1,32 @@ +## Coverage + +Recall our `estimate_size` example from [First steps](../../tutorial-first-steps.md), +where we wrote a proof harness constraining the range of inputs to integers less than 4096: + +```rust +{{#include ../../tutorial/first-steps-v2/src/lib.rs:kani}} +``` + +We must wonder if we've really fully tested our function. +What if we revise the function, but forget to update the assumption in our proof harness to cover the new range of inputs? + +Fortunately, Kani is able to report a coverage metric for each proof harness. +In the `first-steps-v2` directory, try running: + +``` +cargo kani --coverage -Z line-coverage --harness verify_success +``` + +which verifies the harness, then prints coverage information for each line. +In this case, we see that each line of `estimate_size` is followed by `FULL`, indicating that our proof harness provides full coverage. + +Try changing the assumption in the proof harness to `x < 2048`. +Now the harness won't be testing all possible cases. +Rerun the command. +You'll see this line: + +``` +src/lib.rs, 24, NONE +``` + +which indicates that the proof no longer covers line 24, which addresses the case where `x >= 2048`. diff --git a/docs/src/reference/experimental/experimental-features.md b/docs/src/reference/experimental/experimental-features.md new file mode 100644 index 000000000000..bd9fb6cd8572 --- /dev/null +++ b/docs/src/reference/experimental/experimental-features.md @@ -0,0 +1,5 @@ +# Experimental Features + +We elaborate on some of the more commonly used experimental features in Kani. +This is not an exhaustive list; to see all of Kani's experimental features, run `cargo kani --help`. +To use an experimental feature, invoke Kani with the `--unstable` or `-Z` flag followed by the name of the feature. \ No newline at end of file diff --git a/docs/src/reference/stubbing.md b/docs/src/reference/experimental/stubbing.md similarity index 99% rename from docs/src/reference/stubbing.md rename to docs/src/reference/experimental/stubbing.md index b4eeb4fe6b98..0ffbba5f0b0b 100644 --- a/docs/src/reference/stubbing.md +++ b/docs/src/reference/experimental/stubbing.md @@ -113,7 +113,7 @@ In the following, we describe all the limitations of the stubbing feature. The usage of stubbing is limited to the verification of a single harness. Therefore, users are **required to pass the `--harness` option** when using the stubbing feature. -In addition, this feature **isn't compatible with [concrete playback](../debugging-verification-failures.md#concrete-playback)**. +In addition, this feature **isn't compatible with [concrete playback](./concrete-playback.md)**. ### Support diff --git a/docs/src/tutorial-first-steps.md b/docs/src/tutorial-first-steps.md index 57e70edb96dd..bbfd236c6b58 100644 --- a/docs/src/tutorial-first-steps.md +++ b/docs/src/tutorial-first-steps.md @@ -53,40 +53,11 @@ Kani has immediately found a failure. Notably, we haven't had to write explicit assertions in our proof harness: by default, Kani will find a host of erroneous conditions which include a reachable call to `panic` or a failing `assert`. If Kani had run successfully on this harness, this amounts to a mathematical proof that there is no input that could cause a panic in `estimate_size`. -### Getting a trace +> By default, Kani only reports failures, not how the failure happened. +> In this example, it would be nice to get a concrete example of a value of `x` that triggers the failure. +> Kani offers an (experimental) [concrete playback](reference/experimental/concrete-playback.md) feature that serves this purpose. +> As an exercise, try applying concrete playback to this example and see what Kani outputs. -By default, Kani only reports failures, not how the failure happened. -In this running example, it seems obvious what we're interested in (the value of `x` that caused the failure) because we just have one unknown input at the start (similar to the property test), but that's kind of a special case. -In general, understanding how a failure happened requires exploring a full (potentially large) _execution trace_. - -An execution trace is a record of exactly how a failure can occur. -Nondeterminism (like a call to `kani::any()`, which could return any value) can appear in the middle of its execution. -A trace is a record of exactly how execution proceeded, including concrete choices (like `1023`) for all of these nondeterministic values. - -To get a trace for a failing check in Kani, run: - -``` -cargo kani --visualize --enable-unstable -``` - -This command runs Kani and generates an HTML report that includes a trace. -Open the report with your preferred browser. -Under the "Errors" heading, click on the "trace" link to find the trace for this failure. - -From this trace report, we can filter through it to find relevant lines. -A good rule of thumb is to search for either `kani::any()` or assignments to variables you're interested in. -At present time, an unfortunate amount of generated code is present in the trace. -This code isn't a part of the Rust code you wrote, but is an internal implementation detail of how Kani runs proof harnesses. -Still, searching for `kani::any()` quickly finds us these lines: - -``` -let x: u32 = kani::any(); -x = 1023u -``` - -Here we're seeing the line of code and the value assigned in this particular trace. -Like property testing, this is just one **example** of a failure. -To proceed, we recommend fixing the code to avoid this particular issue and then re-running Kani to see if you find more issues. ### Exercise: Try other failures @@ -193,25 +164,6 @@ Here's a revised example of the proof harness, one that now succeeds: {{#include tutorial/first-steps-v2/src/lib.rs:kani}} ``` -But now we must wonder if we've really fully tested our function. -What if we revise the function, but forget to update the assumption in our proof harness to cover the new range of inputs? - -Fortunately, Kani is able to report a coverage metric for each proof harness. -Try running: - -``` -cargo kani --visualize --harness verify_success -``` - -The beginning of the report includes coverage information. -Clicking through to the file will show fully-covered lines in green. -Lines not covered by our proof harness will show in red. - -Try changing the assumption in the proof harness to `x < 2048`. -Now the harness won't be testing all possible cases. -Rerun `cargo kani --visualize`. -Look at the report: you'll see we no longer have 100% coverage of the function. - ## Summary In this section: @@ -219,6 +171,4 @@ In this section: 1. We saw Kani find panics, assertion failures, and even some other failures like unsafe dereferencing of null pointers. 2. We saw Kani find failures that testing could not easily find. 3. We saw how to write a proof harness and use `kani::any()`. -4. We saw how to get a failing **trace** using `kani --visualize` -5. We saw how proof harnesses are used to set up preconditions with `kani::assume()`. -6. We saw how to obtain **coverage** metrics and use them to ensure our proofs are covering as much as they should be. +4. We saw how proof harnesses are used to set up preconditions with `kani::assume()`. diff --git a/docs/src/tutorial-kinds-of-failure.md b/docs/src/tutorial-kinds-of-failure.md index 29236b95db0c..00f9408ca709 100644 --- a/docs/src/tutorial-kinds-of-failure.md +++ b/docs/src/tutorial-kinds-of-failure.md @@ -25,7 +25,7 @@ This property test will immediately find a failing case, thanks to Rust's built- But what if we change this function to use unsafe Rust? ```rust -return unsafe { *a.get_unchecked(i % a.len() + 1) }; +return unsafe { *a.as_ptr().add(i % a.len() + 1) }; ``` Now the error becomes invisible to this test: @@ -55,7 +55,7 @@ cargo kani --harness bound_check We still see a failure from Kani, even without Rust's runtime bounds checking. > Also, notice there were many checks in the verification output. -> (At time of writing, 351.) +> (At time of writing, 345.) > This is a result of using the standard library `Vec` implementation, which means our harness actually used quite a bit of code, short as it looks. > Kani is inserting a lot more checks than appear as asserts in our code, so the output can be large. @@ -63,7 +63,7 @@ We get the following summary at the end: ``` SUMMARY: - ** 1 of 351 failed + ** 1 of 345 failed (8 unreachable) Failed Checks: dereference failure: pointer outside object bounds File: "./src/bounds_check.rs", line 11, in bounds_check::get_wrapped @@ -79,71 +79,44 @@ Consider trying a few more small exercises with this example: 1. Exercise: Switch back to the normal/safe indexing operation and re-try Kani. How does Kani's output change, compared to the unsafe operation? (Try predicting the answer, then seeing if you got it right.) -2. Exercise: [Remember how to get a trace from Kani?](./tutorial-first-steps.md#getting-a-trace) Find out what inputs it failed on. +2. Exercise: Try Kani's experimental [concrete playback](reference/experimental/concrete-playback.md) feature on this example. 3. Exercise: Fix the error, run Kani, and see a successful verification. 4. Exercise: Try switching back to the unsafe code (now with the error fixed) and re-run Kani. Does it still verify successfully?
Click to see explanation for exercise 1 -Having switched back to the safe indexing operation, Kani reports two failures: +Having switched back to the safe indexing operation, Kani reports a bounds check failure: ``` -SUMMARY: - ** 2 of 350 failed +SUMMARY: + ** 1 of 343 failed (8 unreachable) Failed Checks: index out of bounds: the length is less than or equal to the given index - File: "./src/bounds_check.rs", line 11, in bounds_check::get_wrapped -Failed Checks: dereference failure: pointer outside object bounds - File: "./src/bounds_check.rs", line 11, in bounds_check::get_wrapped + File: "src/bounds_check.rs", line 11, in bounds_check::get_wrapped VERIFICATION:- FAILED ``` -The first is Rust's runtime bounds checking for the safe indexing operation. -The second is Kani's check to ensure the pointer operation is actually safe. -This pattern (two checks for similar issues in safe Rust code) is common to see, and we'll see it again in the next section. - -> **NOTE**: While Kani will always be checking for both properties, [in the future the output here may change](https://github.com/model-checking/kani/issues/1349). -> You might have noticed that the bad pointer dereference can't happen, since the bounds check would panic first. -> In the future, Kani's output may report only the bounds checking failure in this example. -
Click to see explanation for exercise 2 -Having run `cargo kani --harness bound_check --visualize` and clicked on one of the failures to see a trace, there are three things to immediately notice: - -1. This trace is huge. Because the standard library `Vec` is involved, there's a lot going on. -2. The top of the trace file contains some "trace navigation tips" that might be helpful in navigating the trace. -3. There's a lot of generated code and it's really hard to just read the trace itself. - -To navigate this trace to find the information you need, we again recommend searching for things you expect to be somewhere in the trace: - -1. Search the page for `kani::any` or ` =` such as `size =` or `let size`. -We can use this to find out what example values lead to a problem. -In this case, where we just have a couple of `kani::any` values in our proof harness, we can learn a lot just by seeing what these are. -In this trace we find (and the values you get may be different): - -``` -Step 36: Function bound_check, File src/bounds_check.rs, Line 37 -let size: usize = kani::any(); -size = 2464ul - -Step 39: Function bound_check, File src/bounds_check.rs, Line 39 -let index: usize = kani::any(); -index = 2463ul +`cargo kani -Z concrete-playback --concrete-playback=inplace --harness bound_check` produces the following test: +``` +rust +#[test] +fn kani_concrete_playback_bound_check_4752536404478138800() { + let concrete_vals: Vec> = vec![ + // 1ul + vec![1, 0, 0, 0, 0, 0, 0, 0], + // 18446744073709551615ul + vec![255, 255, 255, 255, 255, 255, 255, 255], + ]; + kani::concrete_playback_run(concrete_vals, bound_check); +} ``` - -You may see different values here, as it depends on the solver's behavior. - -2. Try searching for `failure:`. This will be near the end of the page. -You can now search upwards from a failure to see what values certain variables had. -Sometimes it can be helpful to change the source code to add intermediate variables, so their value is visible in the trace. -For instance, you might want to compute the index before indexing into the array. -That way you'd see in the trace exactly what value is being used. - -These two techniques should help you find both the nondeterministic inputs, and the values that were involved in the failing assertion. +which indicates that substituting the concrete values `size = 1` and `index = 2^64` in our proof harness will produce the out of bounds access.
@@ -247,6 +220,5 @@ In this section: 1. We saw Kani spot out-of-bounds accesses. 2. We saw Kani spot actually-unsafe dereferencing of a raw pointer to invalid memory. -3. We got more experience reading the traces that Kani generates, to debug a failing proof harness. 3. We saw Kani spot a division by zero error and an overflowing addition. -5. As an exercise, we tried proving an assertion (finding the midpoint) that was not completely trivial. +4. As an exercise, we tried proving an assertion (finding the midpoint) that was not completely trivial. diff --git a/docs/src/tutorial-real-code.md b/docs/src/tutorial-real-code.md index c4bad5a2d82e..3dd216cd6258 100644 --- a/docs/src/tutorial-real-code.md +++ b/docs/src/tutorial-real-code.md @@ -74,7 +74,7 @@ A first proof will likely start in the following form: Running Kani on this simple starting point will help figure out: 1. What unexpected constraints might be needed on your inputs (using `kani::assume`) to avoid "expected" failures. -2. Whether you're over-constrained. Check the coverage report using `--visualize`. Ideally you'd see 100% coverage, and if not, it's usually because you've assumed too much (thus over-constraining the inputs). +2. Whether you're over-constrained. Check the coverage report using `--coverage -Z line-coverage`. Ideally you'd see 100% coverage, and if not, it's usually because you've assumed too much (thus over-constraining the inputs). 3. Whether Kani will support all the Rust features involved. 4. Whether you've started with a tractable problem. (Remember to try setting `#[kani::unwind(1)]` to force early termination and work up from there.) diff --git a/docs/src/tutorial/kinds-of-failure/src/bounds_check.rs b/docs/src/tutorial/kinds-of-failure/src/bounds_check.rs index 9204d8f0f17e..c4a30982c95d 100644 --- a/docs/src/tutorial/kinds-of-failure/src/bounds_check.rs +++ b/docs/src/tutorial/kinds-of-failure/src/bounds_check.rs @@ -13,7 +13,7 @@ fn get_wrapped(i: usize, a: &[u32]) -> u32 { // ANCHOR_END: code // Alternative unsafe return for the above function: -// return unsafe { *a.get_unchecked(i % a.len() + 1) }; +// return unsafe { *a.as_ptr().add(i % a.len() + 1) }; #[cfg(test)] mod tests { diff --git a/docs/src/usage.md b/docs/src/usage.md index 459916c87222..aaa5d3fa234c 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -26,7 +26,7 @@ Common to both `kani` and `cargo kani` are many command-line flags: * `--concrete-playback=[print|inplace]`: _Experimental_, `--enable-unstable` feature that generates a Rust unit test case that plays back a failing proof harness using a concrete counterexample. If used with `print`, Kani will only print the unit test to stdout. - If used with `inplace`, Kani will automatically add the unit test to the user's source code, next to the proof harness. For more detailed instructions, see the [debugging verification failures](./debugging-verification-failures.md) section. + If used with `inplace`, Kani will automatically add the unit test to the user's source code, next to the proof harness. For more detailed instructions, see the [concrete playback](./experimental/concrete-playback.md) section. * `--visualize`: _Experimental_, `--enable-unstable` feature that generates an HTML report providing traces (i.e., counterexamples) for each failure found by Kani. diff --git a/docs/src/verification-results.md b/docs/src/verification-results.md index a8187163d41b..100c9ed554be 100644 --- a/docs/src/verification-results.md +++ b/docs/src/verification-results.md @@ -38,7 +38,7 @@ Check 4: success_example.assertion.4 ``` 2. `FAILURE`: This indicates that the check failed (i.e., the property doesn't -hold). In this case, please see the [debugging verification failures](./debugging-verification-failures.md) +hold). In this case, please see the [concrete playback](./experimental/concrete-playback.md) section for more help. Example: From 9d1cc0ee89c8510c7208867cd4becd9d3dc6b9a5 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 31 Jul 2024 17:01:16 -0700 Subject: [PATCH 03/28] Add code scanner tool (#3120) This is just a utility tool that allow us to scan a crate and extract some metrics out of it. I also added a script to scan the standard library. Currently, the tool will produce a few CSV files with raw data. --------- Co-authored-by: Qinheping Hu --- Cargo.lock | 31 + Cargo.toml | 1 + scripts/std-analysis.sh | 115 ++++ tests/perf/s2n-quic | 2 +- .../script-based-pre/tool-scanner/config.yml | 4 + .../tool-scanner/scanner-test.expected | 6 + .../tool-scanner/scanner-test.sh | 20 + tests/script-based-pre/tool-scanner/test.rs | 77 +++ tools/scanner/Cargo.toml | 23 + tools/scanner/build.rs | 26 + tools/scanner/src/analysis.rs | 629 ++++++++++++++++++ tools/scanner/src/bin/scan.rs | 31 + tools/scanner/src/lib.rs | 103 +++ 13 files changed, 1067 insertions(+), 1 deletion(-) create mode 100755 scripts/std-analysis.sh create mode 100644 tests/script-based-pre/tool-scanner/config.yml create mode 100644 tests/script-based-pre/tool-scanner/scanner-test.expected create mode 100755 tests/script-based-pre/tool-scanner/scanner-test.sh create mode 100644 tests/script-based-pre/tool-scanner/test.rs create mode 100644 tools/scanner/Cargo.toml create mode 100644 tools/scanner/build.rs create mode 100644 tools/scanner/src/analysis.rs create mode 100644 tools/scanner/src/bin/scan.rs create mode 100644 tools/scanner/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 01dbccdd546a..d68b8db21918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "either" version = "1.13.0" @@ -892,6 +913,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scanner" +version = "0.0.0" +dependencies = [ + "csv", + "serde", + "strum", + "strum_macros", +] + [[package]] name = "scopeguard" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 1b4933c5bdcf..68b5bcc20ff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ members = [ "library/std", "tools/compiletest", "tools/build-kani", + "tools/scanner", "kani-driver", "kani-compiler", "kani_metadata", diff --git a/scripts/std-analysis.sh b/scripts/std-analysis.sh new file mode 100755 index 000000000000..87ac991cb00d --- /dev/null +++ b/scripts/std-analysis.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# Collect some metrics related to the crates that compose the standard library. +# +# Files generates so far: +# +# - ${crate}_scan_overall.csv: Summary of function metrics, such as safe vs unsafe. +# - ${crate}_scan_input_tys.csv: Detailed information about the inputs' type of each +# function found in this crate. +# +# How we collect metrics: +# +# - Compile the standard library using the `scan` tool to collect some metrics. +# - After compilation, move all CSV files that were generated by the scanner, +# to the results folder. +set -eu + +# Test for platform +PLATFORM=$(uname -sp) +if [[ $PLATFORM == "Linux x86_64" ]] +then + TARGET="x86_64-unknown-linux-gnu" + # 'env' necessary to avoid bash built-in 'time' + WRAPPER="env time -v" +elif [[ $PLATFORM == "Darwin i386" ]] +then + TARGET="x86_64-apple-darwin" + # mac 'time' doesn't have -v + WRAPPER="time" +elif [[ $PLATFORM == "Darwin arm" ]] +then + TARGET="aarch64-apple-darwin" + # mac 'time' doesn't have -v + WRAPPER="time" +else + echo + echo "Std-Lib codegen regression only works on Linux or OSX x86 platforms, skipping..." + echo + exit 0 +fi + +# Get Kani root +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +KANI_DIR=$(dirname "$SCRIPT_DIR") + +echo "-------------------------------------------------------" +echo "-- Starting analysis of the Rust standard library... --" +echo "-------------------------------------------------------" + +echo "-- Build scanner" +cd $KANI_DIR +cargo build -p scanner + +echo "-- Build std" +cd /tmp +if [ -d std_lib_analysis ] +then + rm -rf std_lib_analysis +fi +cargo new std_lib_analysis --lib +cd std_lib_analysis + +echo ' +pub fn dummy() { +} +' > src/lib.rs + +# Use same nightly toolchain used to build Kani +cp ${KANI_DIR}/rust-toolchain.toml . + +export RUST_BACKTRACE=1 +export RUSTC_LOG=error + +RUST_FLAGS=( + "-Cpanic=abort" + "-Zalways-encode-mir" +) +export RUSTFLAGS="${RUST_FLAGS[@]}" +export RUSTC="$KANI_DIR/target/debug/scan" +# Compile rust with our extension +$WRAPPER cargo build --verbose -Z build-std --lib --target $TARGET + +echo "-- Process results" + +# Move files to results folder +results=/tmp/std_lib_analysis/results +mkdir $results +find /tmp/std_lib_analysis/target -name "*.csv" -exec mv {} $results \; + +# Create a summary table +summary=$results/summary.csv + +# write header +echo -n "crate," > $summary +tr -d "[:digit:],;" < $results/alloc_scan_overall.csv \ + | tr -s '\n' ',' >> $summary +echo "" >> $summary + +# write body +for f in $results/*overall.csv; do + # Join all crate summaries into one table + fname=$(basename $f) + crate=${fname%_scan_overall.csv} + echo -n "$crate," >> $summary + tr -d [:alpha:]_,; < $f | tr -s '\n' ',' \ + >> $summary + echo "" >> $summary +done + +echo "-------------------------------------------------------" +echo "Finished analysis successfully..." +echo "- See results at ${results}" +echo "-------------------------------------------------------" diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index 71f8d9f5aafb..2d5e891f3fdc 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 71f8d9f5aafbf59f31ad85eeb7b4b67a7564a685 +Subproject commit 2d5e891f3fdc8a88b2d457baceedea5751efaa0d diff --git a/tests/script-based-pre/tool-scanner/config.yml b/tests/script-based-pre/tool-scanner/config.yml new file mode 100644 index 000000000000..6fd2895971a4 --- /dev/null +++ b/tests/script-based-pre/tool-scanner/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: scanner-test.sh +expected: scanner-test.expected diff --git a/tests/script-based-pre/tool-scanner/scanner-test.expected b/tests/script-based-pre/tool-scanner/scanner-test.expected new file mode 100644 index 000000000000..c8f9af0ef1b7 --- /dev/null +++ b/tests/script-based-pre/tool-scanner/scanner-test.expected @@ -0,0 +1,6 @@ +2 test_scan_fn_loops.csv +16 test_scan_functions.csv +5 test_scan_input_tys.csv +14 test_scan_overall.csv +3 test_scan_recursion.csv +5 test_scan_unsafe_ops.csv diff --git a/tests/script-based-pre/tool-scanner/scanner-test.sh b/tests/script-based-pre/tool-scanner/scanner-test.sh new file mode 100755 index 000000000000..2cd5a33a3f8e --- /dev/null +++ b/tests/script-based-pre/tool-scanner/scanner-test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +set -e + +# Run this inside a tmp folder in the current directory +OUT_DIR=output_dir +# Ensure output folder is clean +rm -rf ${OUT_DIR} +mkdir output_dir +# Move the original source to the output folder since it will be modified +cp test.rs ${OUT_DIR} +pushd $OUT_DIR + +cargo run -p scanner test.rs --crate-type lib +wc -l *csv + +popd +rm -rf ${OUT_DIR} diff --git a/tests/script-based-pre/tool-scanner/test.rs b/tests/script-based-pre/tool-scanner/test.rs new file mode 100644 index 000000000000..24b346e535b5 --- /dev/null +++ b/tests/script-based-pre/tool-scanner/test.rs @@ -0,0 +1,77 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Sanity check for the utility tool `scanner`. + +pub fn check_outer_coercion() { + assert!(false); +} + +unsafe fn do_nothing() {} + +pub fn generic() -> T { + unsafe { do_nothing() }; + T::default() +} + +pub struct RecursiveType { + pub inner: Option<*const RecursiveType>, +} + +pub enum RecursiveEnum { + Base, + Recursion(Box), + RefCell(std::cell::RefCell), +} + +pub fn recursive_type(input1: RecursiveType, input2: RecursiveEnum) { + let _ = (input1, input2); +} + +pub fn with_iterator(input: &[usize]) -> usize { + input + .iter() + .copied() + .find(|e| *e == 0) + .unwrap_or_else(|| input.iter().fold(0, |acc, i| acc + 1)) +} + +static mut COUNTER: Option = Some(0); +static OK: bool = true; + +pub unsafe fn next_id() -> usize { + let sum = COUNTER.unwrap() + 1; + COUNTER = Some(sum); + sum +} + +pub unsafe fn current_id() -> usize { + COUNTER.unwrap() +} + +pub fn ok() -> bool { + OK +} + +pub unsafe fn raw_to_ref<'a, T>(raw: *const T) -> &'a T { + &*raw +} + +pub fn recursion_begin(stop: bool) { + if !stop { + recursion_tail() + } +} + +pub fn recursion_tail() { + recursion_begin(false); + not_recursive(); +} + +pub fn start_recursion() { + recursion_begin(true); +} + +pub fn not_recursive() { + let _ = ok(); +} diff --git a/tools/scanner/Cargo.toml b/tools/scanner/Cargo.toml new file mode 100644 index 000000000000..edbd330bea47 --- /dev/null +++ b/tools/scanner/Cargo.toml @@ -0,0 +1,23 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + + +[package] +name = "scanner" +description = "A rustc extension used to scan rust features in a crate" +version = "0.0.0" +edition = "2021" +license = "MIT OR Apache-2.0" +publish = false + +[dependencies] +csv = "1.3" +serde = {version = "1", features = ["derive"]} +strum = "0.26" +strum_macros = "0.26" + +[package.metadata.rust-analyzer] +# This crate uses rustc crates. +# More info: https://github.com/rust-analyzer/rust-analyzer/pull/7891 +rustc_private = true + diff --git a/tools/scanner/build.rs b/tools/scanner/build.rs new file mode 100644 index 000000000000..775a0f507a45 --- /dev/null +++ b/tools/scanner/build.rs @@ -0,0 +1,26 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use std::env; +use std::path::PathBuf; + +macro_rules! path_str { + ($input:expr) => { + String::from( + $input + .iter() + .collect::() + .to_str() + .unwrap_or_else(|| panic!("Invalid path {}", stringify!($input))), + ) + }; +} + +/// Configure the compiler to properly link the scanner binary with rustc's library. +pub fn main() { + // Add rustup to the rpath in order to properly link with the correct rustc version. + let rustup_home = env::var("RUSTUP_HOME").unwrap(); + let rustup_tc = env::var("RUSTUP_TOOLCHAIN").unwrap(); + let rustup_lib = path_str!([&rustup_home, "toolchains", &rustup_tc, "lib"]); + println!("cargo:rustc-link-arg-bin=scan=-Wl,-rpath,{rustup_lib}"); +} diff --git a/tools/scanner/src/analysis.rs b/tools/scanner/src/analysis.rs new file mode 100644 index 000000000000..c376af9662f8 --- /dev/null +++ b/tools/scanner/src/analysis.rs @@ -0,0 +1,629 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Provide different static analysis to be performed in the crate under compilation + +use crate::info; +use csv::WriterBuilder; +use serde::{ser::SerializeStruct, Serialize, Serializer}; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef}; +use stable_mir::mir::{ + Body, MirVisitor, Mutability, ProjectionElem, Safety, Terminator, TerminatorKind, +}; +use stable_mir::ty::{AdtDef, AdtKind, FnDef, GenericArgs, MirConst, RigidTy, Ty, TyKind}; +use stable_mir::visitor::{Visitable, Visitor}; +use stable_mir::{CrateDef, CrateItem}; +use std::collections::{HashMap, HashSet}; +use std::ops::ControlFlow; +use std::path::{Path, PathBuf}; + +#[derive(Clone, Debug)] +pub struct OverallStats { + /// The key and value of each counter. + counters: Vec<(&'static str, usize)>, + /// TODO: Group stats per function. + fn_stats: HashMap, +} + +#[derive(Clone, Debug, Serialize)] +struct FnStats { + name: String, + is_unsafe: Option, + has_unsafe_ops: Option, + has_unsupported_input: Option, + has_loop: Option, +} + +impl FnStats { + fn new(fn_item: CrateItem) -> FnStats { + FnStats { + name: fn_item.name(), + is_unsafe: None, + has_unsafe_ops: None, + has_unsupported_input: None, + // TODO: Implement this. + has_loop: None, + } + } +} + +impl OverallStats { + pub fn new() -> OverallStats { + let all_items = stable_mir::all_local_items(); + let fn_stats: HashMap<_, _> = all_items + .into_iter() + .filter_map(|item| item.ty().kind().is_fn().then_some((item, FnStats::new(item)))) + .collect(); + let counters = vec![("total_fns", fn_stats.len())]; + OverallStats { counters, fn_stats } + } + + pub fn store_csv(&self, base_path: PathBuf, file_stem: &str) { + let filename = format!("{}_overall", file_stem); + let mut out_path = base_path.parent().map_or(PathBuf::default(), Path::to_path_buf); + out_path.set_file_name(filename); + dump_csv(out_path, &self.counters); + + let filename = format!("{}_functions", file_stem); + let mut out_path = base_path.parent().map_or(PathBuf::default(), Path::to_path_buf); + out_path.set_file_name(filename); + dump_csv(out_path, &self.fn_stats.values().collect::>()); + } + + /// Iterate over all functions defined in this crate and log generic vs monomorphic. + pub fn generic_fns(&mut self) { + let all_items = stable_mir::all_local_items(); + let fn_items = + all_items.into_iter().filter(|item| item.ty().kind().is_fn()).collect::>(); + let (mono_fns, generics) = fn_items + .iter() + .partition::, _>(|fn_item| Instance::try_from(**fn_item).is_ok()); + self.counters + .extend_from_slice(&[("generic_fns", generics.len()), ("mono_fns", mono_fns.len())]); + } + + /// Iterate over all functions defined in this crate and log safe vs unsafe. + pub fn safe_fns(&mut self, _base_filename: PathBuf) { + let all_items = stable_mir::all_local_items(); + let (unsafe_fns, safe_fns) = all_items + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + let fn_sig = kind.fn_sig().unwrap(); + let is_unsafe = fn_sig.skip_binder().safety == Safety::Unsafe; + self.fn_stats.get_mut(&item).unwrap().is_unsafe = Some(is_unsafe); + Some((item, is_unsafe)) + }) + .partition::, _>(|(_, is_unsafe)| *is_unsafe); + self.counters + .extend_from_slice(&[("safe_fns", safe_fns.len()), ("unsafe_fns", unsafe_fns.len())]); + } + + /// Iterate over all functions defined in this crate and log the inputs. + pub fn supported_inputs(&mut self, filename: PathBuf) { + let all_items = stable_mir::all_local_items(); + let (supported, unsupported) = all_items + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + let fn_sig = kind.fn_sig().unwrap(); + let props = FnInputProps::new(item.name()).collect(fn_sig.skip_binder().inputs()); + self.fn_stats.get_mut(&item).unwrap().has_unsupported_input = + Some(!props.is_supported()); + Some(props) + }) + .partition::, _>(|props| props.is_supported()); + self.counters.extend_from_slice(&[ + ("supported_inputs", supported.len()), + ("unsupported_inputs", unsupported.len()), + ]); + dump_csv(filename, &unsupported); + } + + /// Iterate over all functions defined in this crate and log any unsafe operation. + pub fn unsafe_operations(&mut self, filename: PathBuf) { + let all_items = stable_mir::all_local_items(); + let (has_unsafe, no_unsafe) = all_items + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + let unsafe_ops = FnUnsafeOperations::new(item.name()).collect(&item.body()); + let fn_sig = kind.fn_sig().unwrap(); + let is_unsafe = fn_sig.skip_binder().safety == Safety::Unsafe; + self.fn_stats.get_mut(&item).unwrap().has_unsafe_ops = + Some(unsafe_ops.has_unsafe()); + Some((is_unsafe, unsafe_ops)) + }) + .partition::, _>(|(_, props)| props.has_unsafe()); + self.counters.extend_from_slice(&[ + ("has_unsafe_ops", has_unsafe.len()), + ("no_unsafe_ops", no_unsafe.len()), + ("safe_abstractions", has_unsafe.iter().filter(|(is_unsafe, _)| !is_unsafe).count()), + ]); + dump_csv(filename, &has_unsafe.into_iter().map(|(_, props)| props).collect::>()); + } + + /// Iterate over all functions defined in this crate and log any loop / "hidden" loop. + /// + /// A hidden loop is a call to a iterator function that has a loop inside. + pub fn loops(&mut self, filename: PathBuf) { + let all_items = stable_mir::all_local_items(); + let (has_loops, no_loops) = all_items + .into_iter() + .filter_map(|item| { + let kind = item.ty().kind(); + if !kind.is_fn() { + return None; + }; + Some(FnLoops::new(item.name()).collect(&item.body())) + }) + .partition::, _>(|props| props.has_loops()); + self.counters + .extend_from_slice(&[("has_loops", has_loops.len()), ("no_loops", no_loops.len())]); + dump_csv(filename, &has_loops); + } + + /// Create a callgraph for this crate and try to find recursive calls. + pub fn recursion(&mut self, filename: PathBuf) { + let all_items = stable_mir::all_local_items(); + let recursions = Recursion::collect(&all_items); + self.counters.extend_from_slice(&[ + ("with_recursion", recursions.with_recursion.len()), + ("recursive_fns", recursions.recursive_fns.len()), + ]); + dump_csv( + filename, + &recursions + .with_recursion + .iter() + .map(|def| { + ( + def.name(), + if recursions.recursive_fns.contains(&def) { "recursive" } else { "" }, + ) + }) + .collect::>(), + ); + } +} + +macro_rules! fn_props { + ($(#[$attr:meta])* + struct $name:ident { + $( + $(#[$prop_attr:meta])* + $prop:ident, + )+ + }) => { + #[derive(Debug)] + struct $name { + fn_name: String, + $($(#[$prop_attr])* $prop: usize,)+ + } + + impl $name { + const fn num_props() -> usize { + [$(stringify!($prop),)+].len() + } + + fn new(fn_name: String) -> Self { + Self { fn_name, $($prop: 0,)+} + } + } + + /// Need to manually implement this, since CSV serializer does not support map (i.e.: flatten). + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("FnInputProps", Self::num_props())?; + state.serialize_field("fn_name", &self.fn_name)?; + $(state.serialize_field(stringify!($prop), &self.$prop)?;)+ + state.end() + } + } + }; +} + +fn_props! { + struct FnInputProps { + boxes, + closures, + coroutines, + floats, + fn_defs, + fn_ptrs, + generics, + interior_muts, + raw_ptrs, + recursive_types, + mut_refs, + simd, + unions, + } +} + +impl FnInputProps { + pub fn collect(mut self, inputs: &[Ty]) -> FnInputProps { + for input in inputs { + let mut visitor = TypeVisitor { metrics: &mut self, visited: HashSet::new() }; + let _ = visitor.visit_ty(input); + } + self + } + + pub fn is_supported(&self) -> bool { + (self.closures + + self.coroutines + + self.floats + + self.fn_defs + + self.fn_ptrs + + self.interior_muts + + self.raw_ptrs + + self.recursive_types + + self.mut_refs) + == 0 + } +} + +struct TypeVisitor<'a> { + metrics: &'a mut FnInputProps, + visited: HashSet, +} + +impl<'a> TypeVisitor<'a> { + pub fn visit_variants(&mut self, def: AdtDef, _args: &GenericArgs) -> ControlFlow<()> { + for variant in def.variants_iter() { + for field in variant.fields() { + self.visit_ty(&field.ty())? + } + } + ControlFlow::Continue(()) + } +} + +impl<'a> Visitor for TypeVisitor<'a> { + type Break = (); + + fn visit_ty(&mut self, ty: &Ty) -> ControlFlow { + if self.visited.contains(ty) { + self.metrics.recursive_types += 1; + ControlFlow::Continue(()) + } else { + self.visited.insert(*ty); + let kind = ty.kind(); + match kind { + TyKind::Alias(..) => {} + TyKind::Param(_) => self.metrics.generics += 1, + TyKind::RigidTy(rigid) => match rigid { + RigidTy::Coroutine(..) => self.metrics.coroutines += 1, + RigidTy::Closure(..) => self.metrics.closures += 1, + RigidTy::FnDef(..) => self.metrics.fn_defs += 1, + RigidTy::FnPtr(..) => self.metrics.fn_ptrs += 1, + RigidTy::Float(..) => self.metrics.floats += 1, + RigidTy::RawPtr(..) => self.metrics.raw_ptrs += 1, + RigidTy::Ref(_, _, Mutability::Mut) => self.metrics.mut_refs += 1, + RigidTy::Adt(def, args) => match def.kind() { + AdtKind::Union => self.metrics.unions += 1, + _ => { + let name = def.name(); + if def.is_box() { + self.metrics.boxes += 1; + } else if name.ends_with("UnsafeCell") { + self.metrics.interior_muts += 1; + } else { + self.visit_variants(def, &args)?; + } + } + }, + _ => {} + }, + kind => unreachable!("Expected rigid type, but found: {kind:?}"), + } + ty.super_visit(self) + } + } +} + +fn dump_csv(mut out_path: PathBuf, data: &[T]) { + out_path.set_extension("csv"); + info(format!("Write file: {out_path:?}")); + let mut writer = WriterBuilder::new().delimiter(b';').from_path(&out_path).unwrap(); + for d in data { + writer.serialize(d).unwrap(); + } +} + +fn_props! { + struct FnUnsafeOperations { + inline_assembly, + /// Dereference a raw pointer. + /// This is also counted when we access a static variable since it gets translated to a raw pointer. + unsafe_dereference, + /// Call an unsafe function or method. + unsafe_call, + /// Access or modify a mutable static variable. + unsafe_static_access, + /// Access fields of unions. + unsafe_union_access, + } +} + +impl FnUnsafeOperations { + pub fn collect(self, body: &Body) -> FnUnsafeOperations { + let mut visitor = BodyVisitor { props: self, body }; + visitor.visit_body(body); + visitor.props + } + + pub fn has_unsafe(&self) -> bool { + (self.inline_assembly + + self.unsafe_static_access + + self.unsafe_dereference + + self.unsafe_union_access + + self.unsafe_call) + > 0 + } +} + +struct BodyVisitor<'a> { + props: FnUnsafeOperations, + body: &'a Body, +} + +impl<'a> MirVisitor for BodyVisitor<'a> { + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + match &term.kind { + TerminatorKind::Call { func, .. } => { + let fn_sig = func.ty(self.body.locals()).unwrap().kind().fn_sig().unwrap(); + if fn_sig.value.safety == Safety::Unsafe { + self.props.unsafe_call += 1; + } + } + TerminatorKind::InlineAsm { .. } => self.props.inline_assembly += 1, + _ => { /* safe */ } + } + self.super_terminator(term, location) + } + + fn visit_projection_elem( + &mut self, + place: PlaceRef, + elem: &ProjectionElem, + ptx: PlaceContext, + location: Location, + ) { + match elem { + ProjectionElem::Deref => { + if place.ty(self.body.locals()).unwrap().kind().is_raw_ptr() { + self.props.unsafe_dereference += 1; + } + } + ProjectionElem::Field(_, ty) => { + if ty.kind().is_union() { + self.props.unsafe_union_access += 1; + } + } + ProjectionElem::Downcast(_) => {} + ProjectionElem::OpaqueCast(_) => {} + ProjectionElem::Subtype(_) => {} + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => { /* safe */ } + } + self.super_projection_elem(elem, ptx, location) + } + + fn visit_mir_const(&mut self, constant: &MirConst, location: Location) { + if constant.ty().kind().is_raw_ptr() { + self.props.unsafe_static_access += 1; + } + self.super_mir_const(constant, location) + } +} + +fn_props! { + struct FnLoops { + iterators, + nested_loops, + /// TODO: Collect loops. + loops, + } +} + +impl FnLoops { + pub fn collect(self, body: &Body) -> FnLoops { + let mut visitor = IteratorVisitor { props: self, body }; + visitor.visit_body(body); + visitor.props + } + + pub fn has_loops(&self) -> bool { + (self.iterators + self.loops + self.nested_loops) > 0 + } +} + +/// Try to find hidden loops by looking for calls to Iterator functions that has a loop in them. +/// +/// Note that this will not find a loop, if the iterator is called inside a closure. +/// Run with -C opt-level 2 to help with this issue (i.e.: inline). +struct IteratorVisitor<'a> { + props: FnLoops, + body: &'a Body, +} + +impl<'a> MirVisitor for IteratorVisitor<'a> { + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + if let TerminatorKind::Call { func, .. } = &term.kind { + let kind = func.ty(self.body.locals()).unwrap().kind(); + if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = kind { + let fullname = def.name(); + let names = fullname.split("::").collect::>(); + if let [.., s_last, last] = names.as_slice() { + if *s_last == "Iterator" + && [ + "for_each", + "collect", + "advance_by", + "all", + "any", + "partition", + "partition_in_place", + "fold", + "try_fold", + "spec_fold", + "spec_try_fold", + "try_for_each", + "for_each", + "try_reduce", + "reduce", + "find", + "find_map", + "try_find", + "position", + "rposition", + "nth", + "count", + "last", + "find", + ] + .contains(last) + { + self.props.iterators += 1; + } + } + } + } + self.super_terminator(term, location) + } +} + +#[derive(Debug, Default)] +struct Recursion { + /// Collect the functions that may lead to a recursion loop. + /// I.e., for the following control flow graph: + /// ```dot + /// A -> B + /// B -> C + /// C -> [B, D] + /// ``` + /// this field value would contain A, B, and C since they can all lead to a recursion. + with_recursion: HashSet, + /// Collect the functions that are part of a recursion loop. + /// For the following control flow graph: + /// ```dot + /// A -> [B, C] + /// B -> B + /// C -> D + /// D -> [C, E] + /// ``` + /// The recursive functions would be B, C, and D. + recursive_fns: HashSet, +} + +impl Recursion { + pub fn collect<'a>(items: impl IntoIterator) -> Recursion { + let call_graph = items + .into_iter() + .filter_map(|item| { + if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = item.ty().kind() { + let body = item.body(); + let mut visitor = FnCallVisitor { body: &body, fns: vec![] }; + visitor.visit_body(&body); + Some((def, visitor.fns)) + } else { + None + } + }) + .collect::>(); + let mut recursions = Recursion::default(); + recursions.analyze(call_graph); + recursions + } + + /// DFS post-order traversal to collect all loops in our control flow graph. + /// We only include direct call recursions which can only happen within a crate. + /// + /// # How it works + /// + /// Given a call graph, [(fn_def, [fn_def]*)]*, enqueue all existing nodes together with the + /// graph distance. + /// Keep track of the current path and the visiting status of each node. + /// For those that we have visited once, store whether a loop is reachable from them. + fn analyze(&mut self, call_graph: HashMap>) { + #[derive(Copy, Clone, PartialEq, Eq)] + enum Status { + ToVisit, + Visiting, + Visited, + } + let mut visit_status = HashMap::::new(); + let mut queue: Vec<_> = call_graph.keys().map(|node| (*node, 0)).collect(); + let mut path: Vec = vec![]; + while let Some((next, level)) = queue.last().copied() { + match visit_status.get(&next).unwrap_or(&Status::ToVisit) { + Status::ToVisit => { + assert_eq!(path.len(), level); + path.push(next); + visit_status.insert(next, Status::Visiting); + let next_level = level + 1; + if let Some(callees) = call_graph.get(&next) { + queue.extend(callees.iter().map(|callee| (*callee, next_level))); + } + } + Status::Visiting => { + if level < path.len() { + // We have visited all callees in this node. + visit_status.insert(next, Status::Visited); + path.pop(); + } else { + // Found a loop. + let mut in_loop = false; + for def in &path { + in_loop |= *def == next; + if in_loop { + self.recursive_fns.insert(*def); + } + self.with_recursion.insert(*def); + } + } + queue.pop(); + } + Status::Visited => { + queue.pop(); + if self.with_recursion.contains(&next) { + self.with_recursion.extend(&path); + } + } + } + } + } +} + +struct FnCallVisitor<'a> { + body: &'a Body, + fns: Vec, +} + +impl<'a> MirVisitor for FnCallVisitor<'a> { + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + if let TerminatorKind::Call { func, .. } = &term.kind { + let kind = func.ty(self.body.locals()).unwrap().kind(); + if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = kind { + self.fns.push(def); + } + } + self.super_terminator(term, location) + } +} diff --git a/tools/scanner/src/bin/scan.rs b/tools/scanner/src/bin/scan.rs new file mode 100644 index 000000000000..92b5319ec780 --- /dev/null +++ b/tools/scanner/src/bin/scan.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Modifications Copyright Kani Contributors +// See GitHub history for details. + +// This is a modified version of project-stable-mir `test-drive` +// + +//! Provide a binary that can be used as a replacement to rustc. +//! +//! Besides executing the regular compilation, this binary will run a few static analyses. +//! +//! The result for each analysis will be stored in a file with the same prefix as an object file, +//! together with the name of the analysis. +//! +//! Look at each analysis documentation to see which files an analysis produces. + +use scanner::run_all; +use std::process::ExitCode; + +// ---- Arguments that should be parsed by the test-driver (w/ "scan" prefix) +/// Enable verbose mode. +const VERBOSE_ARG: &str = "--scan-verbose"; + +/// This is a wrapper that can be used to replace rustc. +fn main() -> ExitCode { + let args = std::env::args(); + let (scan_args, rustc_args): (Vec, _) = args.partition(|arg| arg.starts_with("--scan")); + let verbose = scan_args.contains(&VERBOSE_ARG.to_string()); + run_all(rustc_args, verbose) +} diff --git a/tools/scanner/src/lib.rs b/tools/scanner/src/lib.rs new file mode 100644 index 000000000000..7f9555781ccf --- /dev/null +++ b/tools/scanner/src/lib.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Modifications Copyright Kani Contributors +// See GitHub history for details. + +// This is a modified version of project-stable-mir `test-drive` +// + +//! This library provide different ways of scanning a crate. + +#![feature(rustc_private)] + +mod analysis; + +extern crate rustc_driver; +extern crate rustc_interface; +extern crate rustc_middle; +extern crate rustc_session; +#[macro_use] +extern crate rustc_smir; +extern crate stable_mir; + +use crate::analysis::OverallStats; +use rustc_middle::ty::TyCtxt; +use rustc_session::config::OutputType; +use rustc_smir::{run_with_tcx, rustc_internal}; +use stable_mir::CompilerError; +use std::ops::ControlFlow; +use std::path::{Path, PathBuf}; +use std::process::ExitCode; +use std::sync::atomic::{AtomicBool, Ordering}; +use strum::IntoEnumIterator; +use strum_macros::{AsRefStr, EnumIter}; + +// Use a static variable for simplicity. +static VERBOSE: AtomicBool = AtomicBool::new(false); + +pub fn run_all(rustc_args: Vec, verbose: bool) -> ExitCode { + run_analyses(rustc_args, &Analysis::iter().collect::>(), verbose) +} + +/// Executes a compilation and run the analysis that were requested. +pub fn run_analyses(rustc_args: Vec, analyses: &[Analysis], verbose: bool) -> ExitCode { + VERBOSE.store(verbose, Ordering::Relaxed); + let result = run_with_tcx!(rustc_args, |tcx| analyze_crate(tcx, analyses)); + if result.is_ok() || matches!(result, Err(CompilerError::Skipped)) { + ExitCode::SUCCESS + } else { + ExitCode::FAILURE + } +} + +#[derive(AsRefStr, EnumIter, Debug, PartialEq)] +#[strum(serialize_all = "snake_case")] +pub enum Analysis { + /// Collect information about generic functions. + MonoFns, + /// Collect information about function safety. + SafeFns, + /// Collect information about function inputs. + InputTys, + /// Collect information about unsafe operations. + UnsafeOps, + /// Collect information about loops inside a function. + FnLoops, + /// Collect information about recursion via direct calls. + Recursion, +} + +fn info(msg: String) { + if VERBOSE.load(Ordering::Relaxed) { + eprintln!("[INFO] {}", msg); + } +} + +/// This function invoke the required analyses in the given order. +fn analyze_crate(tcx: TyCtxt, analyses: &[Analysis]) -> ControlFlow<()> { + let object_file = tcx.output_filenames(()).path(OutputType::Object); + let base_path = object_file.as_path().to_path_buf(); + // Use name for now to make it more friendly. Change to base_path.file_stem() to avoid conflict. + // let file_stem = base_path.file_stem().unwrap(); + let file_stem = format!("{}_scan", stable_mir::local_crate().name); + let mut crate_stats = OverallStats::new(); + for analysis in analyses { + let filename = format!("{}_{}", file_stem, analysis.as_ref()); + let mut out_path = base_path.parent().map_or(PathBuf::default(), Path::to_path_buf); + out_path.set_file_name(filename); + match analysis { + Analysis::MonoFns => { + crate_stats.generic_fns(); + } + Analysis::SafeFns => { + crate_stats.safe_fns(out_path); + } + Analysis::InputTys => crate_stats.supported_inputs(out_path), + Analysis::UnsafeOps => crate_stats.unsafe_operations(out_path), + Analysis::FnLoops => crate_stats.loops(out_path), + Analysis::Recursion => crate_stats.recursion(out_path), + } + } + crate_stats.store_csv(base_path, &file_stem); + ControlFlow::<()>::Continue(()) +} From 370b2159ce179b82c4781fe7db1e93de68c900aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 1 Aug 2024 09:38:47 +0200 Subject: [PATCH 04/28] Automatic toolchain upgrade to nightly-2024-08-01 (#3402) Update Rust toolchain from nightly-2024-07-31 to nightly-2024-08-01 without any other source changes. This is an automatically generated pull request. If any of the CI checks fail, manual intervention is required. In such a case, review the changes at https://github.com/rust-lang/rust from https://github.com/rust-lang/rust/commit/f8060d282d42770fadd73905e3eefb85660d3278 up to https://github.com/rust-lang/rust/commit/28a58f2fa7f0c46b8fab8237c02471a915924fe5. --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f7324e45d06d..8753157827b5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-07-31" +channel = "nightly-2024-08-01" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From e980aa2f233b37fed53f446a720a45ca2af19956 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 1 Aug 2024 13:00:16 -0700 Subject: [PATCH 05/28] Enable contracts in associated functions (#3363) Contracts could not be used with associated function unless they used Self. This is because at a proc-macro level, we cannot determine if we are inside a associated function or not, except in cases where `Self` is used. In cases where the contract generation could not identify an associated function, it would generate incorrect call, triggering a compilation error. Another problem with our encoding is that users could not annotate trait implementations with contract attributes. This would try to create new functions inside the trait implementation context, which is not allowed. In order to solve these issues, we decided to wrap contract logic using closures instead of functions. See the discussion in the original issue (#3206) for more details. The new solution is still split in two: 1. The proc-macro will now expand the code inside the original function to encode all possible scenarios (check, replace, recursive check, and original body). 2. Instead of using stub, we introduced a new transformation pass that chooses which scenario to pick according to the target harness configuration, and cleanup unused logic. The expanded function will have the following structure: ```rust #[kanitool::recursion_check = "__kani_recursion_check_modify"] #[kanitool::checked_with = "__kani_check_modify"] #[kanitool::replaced_with = "__kani_replace_modify"] #[kanitool::inner_check = "__kani_modifies_modify"] fn name_fn(ptr: &mut u32) { #[kanitool::fn_marker = "kani_register_contract"] pub const fn kani_register_contract T>(f: F) -> T { kani::panic("internal error: entered unreachable code: ") } let kani_contract_mode = kani::internal::mode(); match kani_contract_mode { kani::internal::RECURSION_CHECK => { #[kanitool::is_contract_generated(recursion_check)] let mut __kani_recursion_check_name_fn = || { /* recursion check body */ }; kani_register_contract(__kani_recursion_check_modify) } kani::internal::REPLACE => { #[kanitool::is_contract_generated(replace)] let mut __kani_replace_name_fn = || { /* replace body */ }; kani_register_contract(__kani_replace_name_fn) } kani::internal::SIMPLE_CHECK => { #[kanitool::is_contract_generated(check)] let mut __kani_check_name_fn = || { /* check body */ }; kani_register_contract(__kani_check_name_fn) } _ => { /* original body */ } } } ``` In runtime, `kani::internal::mode()` will return kani::internal::ORIGINAL, which runs the original body. The transformation will replace this call by a different assignment in case the function needs to be replaced. The body of the unused closures will be replaced by a `UNREACHABLE` statement to avoid unnecessary code to be analyzed. This is still fairly hacky, but hopefully we can cleanup this logic once Rust adds contract support. :crossed_fingers: Resolves #3206 --- .../codegen_cprover_gotoc/codegen/contract.rs | 227 ++++---- .../compiler_interface.rs | 9 +- kani-compiler/src/kani_middle/attributes.rs | 363 +++++------- .../src/kani_middle/codegen_units.rs | 53 +- kani-compiler/src/kani_middle/resolve.rs | 31 + .../src/kani_middle/transform/body.rs | 38 +- .../src/kani_middle/transform/contracts.rs | 379 +++++++++++-- .../kani_middle/transform/kani_intrinsics.rs | 7 +- .../src/kani_middle/transform/mod.rs | 6 +- kani-driver/src/call_goto_instrument.rs | 10 +- kani_metadata/src/harness.rs | 6 +- library/kani/src/internal.rs | 74 ++- library/kani_core/src/lib.rs | 65 ++- .../src/sysroot/contracts/bootstrap.rs | 188 +++--- .../src/sysroot/contracts/check.rs | 367 +++++------- .../src/sysroot/contracts/helpers.rs | 260 +++------ .../src/sysroot/contracts/initialize.rs | 41 +- .../kani_macros/src/sysroot/contracts/mod.rs | 533 +++++++++--------- .../src/sysroot/contracts/replace.rs | 104 ++-- .../src/sysroot/contracts/shared.rs | 104 +--- .../gcd_rec_replacement_pass.expected | 2 +- .../gcd_replacement_pass.expected | 2 +- .../modifies/global_fail.expected | 8 +- .../modifies/havoc_pass.expected | 29 +- .../modifies/havoc_pass_reordered.expected | 25 - .../modifies/simple_fail.expected | 4 +- .../modifies/vec_pass.expected | 10 +- .../expected/function-contract/pattern_use.rs | 3 +- .../simple_replace_pass.expected | 2 +- .../trait_impls/associated_fn.expected | 7 + .../trait_impls/associated_fn.rs | 36 ++ .../trait_impls/methods.expected | 17 + .../function-contract/trait_impls/methods.rs | 67 +++ tests/kani/FunctionContracts/fn_params.rs | 73 +++ .../FunctionContracts/receiver_contracts.rs | 155 +++++ tests/perf/s2n-quic | 2 +- 36 files changed, 1849 insertions(+), 1458 deletions(-) create mode 100644 tests/expected/function-contract/trait_impls/associated_fn.expected create mode 100644 tests/expected/function-contract/trait_impls/associated_fn.rs create mode 100644 tests/expected/function-contract/trait_impls/methods.expected create mode 100644 tests/expected/function-contract/trait_impls/methods.rs create mode 100644 tests/kani/FunctionContracts/fn_params.rs create mode 100644 tests/kani/FunctionContracts/receiver_contracts.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index 7f19e5814ce0..b210b2c9333e 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -8,27 +8,23 @@ 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; +use stable_mir::mir::{Local, VarDebugInfoContents}; +use stable_mir::ty::{FnDef, RigidTy, TyKind}; use stable_mir::CrateDef; -use tracing::debug; - -use stable_mir::ty::{RigidTy, TyKind}; impl<'tcx> GotocCtx<'tcx> { /// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`, /// find or create the `AssignsContract` that needs to be enforced and attach it to the symbol /// for which it needs to be enforced. /// - /// 1. Gets the `#[kanitool::inner_check = "..."]` target, then resolves exactly one instance - /// of it. Panics if there are more or less than one instance. - /// 2. Expects that a `#[kanitool::modifies(...)]` is placed on the `inner_check` function, - /// turns it into a CBMC contract and attaches it to the symbol for the previously resolved - /// instance. + /// 1. Gets the `#[kanitool::modifies_wrapper = "..."]` target, then resolves exactly one + /// instance of it. Panics if there are more or less than one instance. + /// 2. The additional arguments for the inner checks are locations that may be modified. + /// Add them to the list of CBMC's assigns. /// 3. Returns the mangled name of the symbol it attached the contract to. - /// 4. Resolves the `#[kanitool::checked_with = "..."]` target from `function_under_contract` - /// which has `static mut REENTRY : bool` declared inside. - /// 5. Returns the full path to this constant that `--nondet-static-exclude` expects which is - /// comprised of the file path that `checked_with` is located in, the name of the + /// 4. Returns the full path to the static marked with `#[kanitool::recursion_tracker]` which + /// is passed to the `--nondet-static-exclude` argument. + /// This flag expects the file path that `checked_with` is located in, the name of the /// `checked_with` function and the name of the constant (`REENTRY`). pub fn handle_check_contract( &mut self, @@ -36,86 +32,101 @@ impl<'tcx> GotocCtx<'tcx> { items: &[MonoItem], ) -> AssignsContract { let tcx = self.tcx; - let function_under_contract_attrs = KaniAttributes::for_item(tcx, function_under_contract); + let modify = items + .iter() + .find_map(|item| { + // Find the instance under contract + let MonoItem::Fn(instance) = *item else { return None }; + if rustc_internal::internal(tcx, instance.def.def_id()) == function_under_contract { + self.find_modifies(instance) + } else { + None + } + }) + .unwrap(); + self.attach_modifies_contract(modify); + let recursion_tracker = self.find_recursion_tracker(items); + AssignsContract { recursion_tracker, contracted_function_name: modify.mangled_name() } + } - let recursion_wrapper_id = - function_under_contract_attrs.checked_with_id().unwrap().unwrap(); - let expected_name = format!("{}::REENTRY", tcx.item_name(recursion_wrapper_id)); - let mut recursion_tracker = items.iter().filter_map(|i| match i { - MonoItem::Static(recursion_tracker) - if (*recursion_tracker).name().contains(expected_name.as_str()) => + /// The name and location for the recursion tracker should match the exact information added + /// to the symbol table, otherwise our contract instrumentation will silently failed. + /// This happens because Kani relies on `--nondet-static-exclude` from CBMC to properly + /// handle this tracker. CBMC silently fails if there is no match in the symbol table + /// that correspond to the argument of this flag. + /// More details at https://github.com/model-checking/kani/pull/3045. + /// + /// We must use the pretty name of the tracker instead of the mangled name. + /// This restriction comes from `--nondet-static-exclude` in CBMC. + /// Mode details at https://github.com/diffblue/cbmc/issues/8225. + fn find_recursion_tracker(&mut self, items: &[MonoItem]) -> Option { + // Return item tagged with `#[kanitool::recursion_tracker]` + let mut recursion_trackers = items.iter().filter_map(|item| { + let MonoItem::Static(static_item) = item else { return None }; + if !static_item + .attrs_by_path(&["kanitool".into(), "recursion_tracker".into()]) + .is_empty() { - Some(*recursion_tracker) + let span = static_item.span(); + let loc = self.codegen_span_stable(span); + Some(format!( + "{}:{}", + loc.filename().expect("recursion location wrapper should have a file name"), + static_item.name(), + )) + } else { + None } - _ => None, }); - let recursion_tracker_def = recursion_tracker - .next() - .expect("There should be at least one recursion tracker (REENTRY) in scope"); + let recursion_tracker = recursion_trackers.next(); assert!( - recursion_tracker.next().is_none(), - "Only one recursion tracker (REENTRY) may be in scope" + recursion_trackers.next().is_none(), + "Expected up to one recursion tracker (`REENTRY`) in scope" ); + recursion_tracker + } - let span_of_recursion_wrapper = tcx.def_span(recursion_wrapper_id); - let location_of_recursion_wrapper = self.codegen_span(&span_of_recursion_wrapper); - // The name and location for the recursion tracker should match the exact information added - // to the symbol table, otherwise our contract instrumentation will silently failed. - // This happens because Kani relies on `--nondet-static-exclude` from CBMC to properly - // handle this tracker. CBMC silently fails if there is no match in the symbol table - // that correspond to the argument of this flag. - // More details at https://github.com/model-checking/kani/pull/3045. - let full_recursion_tracker_name = format!( - "{}:{}", - location_of_recursion_wrapper - .filename() - .expect("recursion location wrapper should have a file name"), - // We must use the pretty name of the tracker instead of the mangled name. - // This restrictions comes from `--nondet-static-exclude` in CBMC. - // Mode details at https://github.com/diffblue/cbmc/issues/8225. - recursion_tracker_def.name(), - ); - - let wrapped_fn = function_under_contract_attrs.inner_check().unwrap().unwrap(); - let mut instance_under_contract = items.iter().filter_map(|i| match i { - MonoItem::Fn(instance @ Instance { def, .. }) - if wrapped_fn == rustc_internal::internal(tcx, def.def_id()) => - { - Some(*instance) - } - _ => None, - }); - let instance_of_check = instance_under_contract.next().unwrap(); - assert!( - instance_under_contract.next().is_none(), - "Only one instance of a checked function may be in scope" - ); - let attrs_of_wrapped_fn = KaniAttributes::for_item(tcx, wrapped_fn); - let assigns_contract = attrs_of_wrapped_fn.modifies_contract().unwrap_or_else(|| { - debug!(?instance_of_check, "had no assigns contract specified"); - vec![] - }); - self.attach_modifies_contract(instance_of_check, assigns_contract); - let wrapper_name = instance_of_check.mangled_name(); - - AssignsContract { - recursion_tracker: full_recursion_tracker_name, - contracted_function_name: wrapper_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 = find_closure(instance, contract_attrs.recursion_check.as_str())?; + find_closure(recursion_check, contract_attrs.checked_with.as_str())? + } else { + find_closure(instance, contract_attrs.checked_with.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 /// CBMC lambda. fn codegen_modifies_contract( &mut self, - modified_places: Vec, + goto_annotated_fn_name: &str, + modifies: Instance, loc: Location, ) -> FunctionContract { - let goto_annotated_fn_name = self.current_fn().name(); let goto_annotated_fn_typ = self .symbol_table - .lookup(&goto_annotated_fn_name) + .lookup(goto_annotated_fn_name) .unwrap_or_else(|| panic!("Function '{goto_annotated_fn_name}' is not declared")) .typ .clone(); @@ -141,13 +152,24 @@ impl<'tcx> GotocCtx<'tcx> { }) .unwrap_or_default(); - let assigns = modified_places + // The last argument is a tuple with addresses that can be modified. + let modifies_local = Local::from(modifies.fn_abi().unwrap().args.len()); + let modifies_ty = self.local_ty_stable(modifies_local); + let modifies_args = + self.codegen_place_stable(&modifies_local.into(), loc).unwrap().goto_expr; + let TyKind::RigidTy(RigidTy::Tuple(modifies_tys)) = modifies_ty.kind() else { + unreachable!("found {:?}", modifies_ty.kind()) + }; + let assigns: Vec<_> = modifies_tys .into_iter() - .map(|local| { - if self.is_fat_pointer_stable(self.local_ty_stable(local)) { - let unref = match self.local_ty_stable(local).kind() { - TyKind::RigidTy(RigidTy::Ref(_, ty, _)) => ty, - kind => unreachable!("{:?} is not a reference", kind), + .enumerate() + .map(|(idx, ty)| { + assert!(ty.kind().is_any_ptr(), "Expected pointer, but found {}", ty); + let ptr = modifies_args.clone().member(idx.to_string(), &self.symbol_table); + if self.is_fat_pointer_stable(ty) { + let unref = match ty.kind() { + TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, + kind => unreachable!("Expected a raw pointer, but found {:?}", kind), }; let size = match unref.kind() { TyKind::RigidTy(RigidTy::Slice(elt_type)) => { @@ -176,30 +198,17 @@ impl<'tcx> GotocCtx<'tcx> { ), ) .call(vec![ - self.codegen_place_stable(&local.into(), loc) - .unwrap() - .goto_expr + ptr.clone() .member("data", &self.symbol_table) .cast_to(Type::empty().to_pointer()), - self.codegen_place_stable(&local.into(), loc) - .unwrap() - .goto_expr - .member("len", &self.symbol_table) - .mul(Expr::size_constant( - size.try_into().unwrap(), - &self.symbol_table, - )), + ptr.member("len", &self.symbol_table).mul(Expr::size_constant( + size.try_into().unwrap(), + &self.symbol_table, + )), ]), ) } else { - Lambda::as_contract_for( - &goto_annotated_fn_typ, - None, - self.codegen_place_stable(&local.into(), loc) - .unwrap() - .goto_expr - .dereference(), - ) + Lambda::as_contract_for(&goto_annotated_fn_typ, None, ptr.dereference()) } }) .chain(shadow_memory_assign) @@ -212,17 +221,19 @@ impl<'tcx> GotocCtx<'tcx> { /// `instance` must have previously been declared. /// /// This merges with any previously attached contracts. - pub fn attach_modifies_contract(&mut self, instance: Instance, modified_places: Vec) { + pub fn attach_modifies_contract(&mut self, instance: Instance) { // This should be safe, since the contract is pretty much evaluated as // though it was the first (or last) assertion in the function. assert!(self.current_fn.is_none()); - let body = instance.body().unwrap(); + let body = self.transformer.body(self.tcx, instance); self.set_current_fn(instance, &body); - let goto_contract = - self.codegen_modifies_contract(modified_places, self.codegen_span_stable(body.span)); - let name = self.current_fn().name(); - - self.symbol_table.attach_contract(name, goto_contract); - self.reset_current_fn() + let mangled_name = instance.mangled_name(); + let goto_contract = self.codegen_modifies_contract( + &mangled_name, + instance, + self.codegen_span_stable(instance.def.span()), + ); + self.symbol_table.attach_contract(&mangled_name, goto_contract); + self.reset_current_fn(); } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 986cd00e32a5..0a92e07f4ab4 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -264,7 +264,7 @@ impl CodegenBackend for GotocCodegenBackend { for harness in &unit.harnesses { let model_path = units.harness_model_path(*harness).unwrap(); let contract_metadata = - contract_metadata_for_harness(tcx, harness.def.def_id()).unwrap(); + contract_metadata_for_harness(tcx, harness.def.def_id()); let (gcx, items, contract_info) = self.codegen_items( tcx, &[MonoItem::Fn(*harness)], @@ -448,12 +448,9 @@ impl CodegenBackend for GotocCodegenBackend { } } -fn contract_metadata_for_harness( - tcx: TyCtxt, - def_id: DefId, -) -> Result, ErrorGuaranteed> { +fn contract_metadata_for_harness(tcx: TyCtxt, def_id: DefId) -> Option { let attrs = KaniAttributes::for_def_id(tcx, def_id); - Ok(attrs.interpret_the_for_contract_attribute().transpose()?.map(|(_, id, _)| id)) + attrs.interpret_for_contract_attribute().map(|(_, id, _)| id) } fn check_target(session: &Session) { diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 8c729bbdec9f..f75c0607e1bc 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -6,24 +6,16 @@ use std::collections::BTreeMap; use kani_metadata::{CbmcSolver, HarnessAttributes, HarnessKind, Stub}; use rustc_ast::{ - attr, - token::Token, - token::TokenKind, - tokenstream::{TokenStream, TokenTree}, - AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, + attr, AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, NestedMetaItem, }; use rustc_errors::ErrorGuaranteed; -use rustc_hir::{ - def::DefKind, - def_id::{DefId, LocalDefId}, -}; +use rustc_hir::{def::DefKind, def_id::DefId}; use rustc_middle::ty::{Instance, TyCtxt, TyKind}; use rustc_session::Session; use rustc_smir::rustc_internal; use rustc_span::{Span, Symbol}; use stable_mir::mir::mono::Instance as InstanceStable; -use stable_mir::mir::Local; use stable_mir::{CrateDef, DefId as StableDefId}; use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; @@ -56,24 +48,27 @@ enum KaniAttributeKind { /// name of the function which was generated as the sound stub from the /// contract of this function. ReplacedWith, + /// Attribute on a function with a contract that identifies the code + /// implementing the recursive check for the harness. + RecursionCheck, /// Attribute on a function that was auto-generated from expanding a /// function contract. IsContractGenerated, - /// Identifies a set of pointer arguments that should be added to the write - /// set when checking a function contract. Placed on the inner check function. - /// - /// Emitted by the expansion of a `modifies` function contract clause. - Modifies, - /// A function used as the inner code of a contract check. + /// A function with contract expanded to include the write set as arguments. /// /// Contains the original body of the contracted function. The signature is /// expanded with additional pointer arguments that are not used in the function /// but referenced by the `modifies` annotation. - InnerCheck, + ModifiesWrapper, /// Attribute used to mark contracts for functions with recursion. /// We use this attribute to properly instantiate `kani::any_modifies` in /// cases when recursion is present given our contracts instrumentation. Recursion, + /// Attribute used to mark the static variable used for tracking recursion check. + RecursionTracker, + /// Generic marker that can be used to mark functions so this list doesn't have to keep growing. + /// This takes a key which is the marker. + FnMarker, /// Used to mark functions where generating automatic pointer checks should be disabled. This is /// used later to automatically attach pragma statements to locations. DisableChecks, @@ -91,11 +86,13 @@ impl KaniAttributeKind { | KaniAttributeKind::StubVerified | KaniAttributeKind::Unwind => true, KaniAttributeKind::Unstable + | KaniAttributeKind::FnMarker | KaniAttributeKind::Recursion + | KaniAttributeKind::RecursionTracker | KaniAttributeKind::ReplacedWith + | KaniAttributeKind::RecursionCheck | KaniAttributeKind::CheckedWith - | KaniAttributeKind::Modifies - | KaniAttributeKind::InnerCheck + | KaniAttributeKind::ModifiesWrapper | KaniAttributeKind::IsContractGenerated | KaniAttributeKind::DisableChecks => false, } @@ -125,6 +122,21 @@ pub struct KaniAttributes<'tcx> { map: BTreeMap>, } +#[derive(Clone, Debug)] +/// Bundle contract attributes for a function annotated with contracts. +pub struct ContractAttributes { + /// Whether the contract was marked with #[recursion] attribute. + pub has_recursion: bool, + /// The name of the contract recursion check. + pub recursion_check: Symbol, + /// The name of the contract check. + pub checked_with: Symbol, + /// The name of the contract replacement. + pub replaced_with: Symbol, + /// The name of the inner check used to modify clauses. + pub modifies_wrapper: Symbol, +} + impl<'tcx> std::fmt::Debug for KaniAttributes<'tcx> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("KaniAttributes") @@ -182,22 +194,28 @@ impl<'tcx> KaniAttributes<'tcx> { /// returned `Symbol` and `DefId` are respectively the name and id of /// `TARGET`. The `Span` is that of the contents of the attribute and used /// for error reporting. - fn interpret_stub_verified_attribute( - &self, - ) -> Vec> { + /// + /// Any error is emitted and the attribute is filtered out. + pub fn interpret_stub_verified_attribute(&self) -> Vec<(Symbol, DefId, Span)> { self.map .get(&KaniAttributeKind::StubVerified) .map_or([].as_slice(), Vec::as_slice) .iter() - .map(|attr| { - let name = expect_key_string_value(self.tcx.sess, attr)?; - let ok = self.resolve_sibling(name.as_str()).map_err(|e| { - self.tcx.dcx().span_err( - attr.span, - format!("Failed to resolve replacement function {}: {e}", name.as_str()), - ) - })?; - Ok((name, ok, attr.span)) + .filter_map(|attr| { + let name = expect_key_string_value(self.tcx.sess, attr).ok()?; + let def = self + .resolve_from_mod(name.as_str()) + .map_err(|e| { + self.tcx.dcx().span_err( + attr.span, + format!( + "Failed to resolve replacement function {}: {e}", + name.as_str() + ), + ) + }) + .ok()?; + Some((name, def, attr.span)) }) .collect() } @@ -209,13 +227,14 @@ impl<'tcx> KaniAttributes<'tcx> { /// Parse and extract the `proof_for_contract(TARGET)` attribute. The /// returned symbol and DefId are respectively the name and id of `TARGET`, /// the span in the span for the attribute (contents). - pub(crate) fn interpret_the_for_contract_attribute( - &self, - ) -> Option> { - self.expect_maybe_one(KaniAttributeKind::ProofForContract).map(|target| { - let name = expect_key_string_value(self.tcx.sess, target)?; - self.resolve_sibling(name.as_str()).map(|ok| (name, ok, target.span)).map_err( - |resolve_err| { + /// + /// In the case of an error, this function will emit the error and return `None`. + pub(crate) fn interpret_for_contract_attribute(&self) -> Option<(Symbol, DefId, Span)> { + self.expect_maybe_one(KaniAttributeKind::ProofForContract).and_then(|target| { + let name = expect_key_string_value(self.tcx.sess, target).ok()?; + self.resolve_from_mod(name.as_str()) + .map(|ok| (name, ok, target.span)) + .map_err(|resolve_err| { self.tcx.dcx().span_err( target.span, format!( @@ -223,81 +242,62 @@ impl<'tcx> KaniAttributes<'tcx> { name.as_str() ), ) - }, - ) + }) + .ok() }) } - /// Extract the name of the sibling function this function's contract is - /// checked with (if any). - /// - /// `None` indicates this function does not use a contract, `Some(Err(_))` - /// indicates a contract does exist but an error occurred during resolution. - pub fn checked_with(&self) -> Option> { - self.expect_maybe_one(KaniAttributeKind::CheckedWith) - .map(|target| expect_key_string_value(self.tcx.sess, target)) - } - pub fn proof_for_contract(&self) -> Option> { self.expect_maybe_one(KaniAttributeKind::ProofForContract) .map(|target| expect_key_string_value(self.tcx.sess, target)) } - pub fn inner_check(&self) -> Option> { - self.eval_sibling_attribute(KaniAttributeKind::InnerCheck) + /// Extract the name of the local that represents this function's contract is + /// checked with (if any). + /// + /// `None` indicates this function does not use a contract, or an error was found. + /// Note that the error will already be emitted, so we don't return an error. + pub fn contract_attributes(&self) -> Option { + let has_recursion = self.has_recursion(); + let recursion_check = self.attribute_value(KaniAttributeKind::RecursionCheck); + let checked_with = self.attribute_value(KaniAttributeKind::CheckedWith); + let replace_with = self.attribute_value(KaniAttributeKind::ReplacedWith); + let modifies_wrapper = self.attribute_value(KaniAttributeKind::ModifiesWrapper); + + let total = recursion_check + .iter() + .chain(&checked_with) + .chain(&replace_with) + .chain(&modifies_wrapper) + .count(); + if total != 0 && total != 4 { + self.tcx.sess.dcx().err(format!( + "Failed to parse contract instrumentation tags in function `{}`.\ + Expected `4` attributes, but was only able to process `{total}`", + self.tcx.def_path_str(self.item) + )); + } + Some(ContractAttributes { + has_recursion, + recursion_check: recursion_check?, + checked_with: checked_with?, + replaced_with: replace_with?, + modifies_wrapper: modifies_wrapper?, + }) } - pub fn replaced_with(&self) -> Option> { - self.expect_maybe_one(KaniAttributeKind::ReplacedWith) - .map(|target| expect_key_string_value(self.tcx.sess, target)) + /// Return a function marker if any. + pub fn fn_marker(&self) -> Option { + self.attribute_value(KaniAttributeKind::FnMarker) } - /// Retrieves the global, static recursion tracker variable. - pub fn checked_with_id(&self) -> Option> { - self.eval_sibling_attribute(KaniAttributeKind::CheckedWith) + /// Check if function is annotated with any contract attribute. + pub fn has_contract(&self) -> bool { + self.map.contains_key(&KaniAttributeKind::CheckedWith) } - /// Find the `mod` that `self.item` is defined in, then search in the items defined in this - /// `mod` for an item that is named after the `name` in the `#[kanitool:: = ""]` - /// annotation on `self.item`. - /// - /// This is similar to [`resolve_fn`] but more efficient since it only looks inside one `mod`. - fn eval_sibling_attribute( - &self, - kind: KaniAttributeKind, - ) -> Option> { - use rustc_hir::{Item, ItemKind, Mod, Node}; - self.expect_maybe_one(kind).map(|target| { - let name = expect_key_string_value(self.tcx.sess, target)?; - let hir_map = self.tcx.hir(); - let hir_id = self.tcx.local_def_id_to_hir_id(self.item.expect_local()); - let find_in_mod = |md: &Mod<'_>| { - md.item_ids - .iter() - .find(|it| hir_map.item(**it).ident.name == name) - .unwrap() - .hir_id() - }; - - let result = match self.tcx.parent_hir_node(hir_id) { - Node::Item(Item { kind, .. }) => match kind { - ItemKind::Mod(m) => find_in_mod(m), - ItemKind::Impl(imp) => { - imp.items.iter().find(|it| it.ident.name == name).unwrap().id.hir_id() - } - other => panic!("Odd parent item kind {other:?}"), - }, - Node::Crate(m) => find_in_mod(m), - other => panic!("Odd parent node type {other:?}"), - } - .expect_owner() - .def_id - .to_def_id(); - Ok(result) - }) - } - - fn resolve_sibling(&self, path_str: &str) -> Result> { + /// Resolve a path starting from this item's module context. + fn resolve_from_mod(&self, path_str: &str) -> Result> { resolve_fn( self.tcx, self.tcx.parent_module_from_def_id(self.item.expect_local()).to_local_def_id(), @@ -371,20 +371,20 @@ impl<'tcx> KaniAttributes<'tcx> { KaniAttributeKind::StubVerified => { expect_single(self.tcx, kind, &attrs); } - KaniAttributeKind::CheckedWith | KaniAttributeKind::ReplacedWith => { - self.expect_maybe_one(kind) - .map(|attr| expect_key_string_value(&self.tcx.sess, attr)); + KaniAttributeKind::FnMarker + | KaniAttributeKind::CheckedWith + | KaniAttributeKind::ModifiesWrapper + | KaniAttributeKind::RecursionCheck + | KaniAttributeKind::ReplacedWith => { + self.attribute_value(kind); } KaniAttributeKind::IsContractGenerated => { // Ignored here because this is only used by the proc macros // to communicate with one another. So by the time it gets // here we don't care if it's valid or not. } - KaniAttributeKind::Modifies => { - self.modifies_contract(); - } - KaniAttributeKind::InnerCheck => { - self.inner_check(); + KaniAttributeKind::RecursionTracker => { + // Nothing to do here. This is used by contract instrumentation. } KaniAttributeKind::DisableChecks => { // Ignored here, because it should be an internal attribute. Actual validation @@ -394,6 +394,17 @@ impl<'tcx> KaniAttributes<'tcx> { } } + /// Get the value of an attribute if one exists. + /// + /// This expects up to one attribute with format `#[kanitool::("")]`. + /// + /// Any format or expectation error is emitted already, and does not need to be handled + /// upstream. + fn attribute_value(&self, kind: KaniAttributeKind) -> Option { + self.expect_maybe_one(kind) + .and_then(|target| expect_key_string_value(self.tcx.sess, target).ok()) + } + /// Check that any unstable API has been enabled. Otherwise, emit an error. /// /// TODO: Improve error message by printing the span of the harness instead of the definition. @@ -499,8 +510,9 @@ impl<'tcx> KaniAttributes<'tcx> { } KaniAttributeKind::CheckedWith | KaniAttributeKind::IsContractGenerated - | KaniAttributeKind::Modifies - | KaniAttributeKind::InnerCheck + | KaniAttributeKind::ModifiesWrapper + | KaniAttributeKind::RecursionCheck + | KaniAttributeKind::RecursionTracker | KaniAttributeKind::ReplacedWith => { self.tcx.dcx().span_err(self.tcx.def_span(self.item), format!("Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)", kind.as_ref())); } @@ -508,6 +520,9 @@ impl<'tcx> KaniAttributes<'tcx> { // Internal attribute which shouldn't exist here. unreachable!() } + KaniAttributeKind::FnMarker => { + /* no-op */ + } }; harness }) @@ -515,15 +530,14 @@ impl<'tcx> KaniAttributes<'tcx> { fn handle_proof_for_contract(&self, harness: &mut HarnessAttributes) { let dcx = self.tcx.dcx(); - let (name, id, span) = match self.interpret_the_for_contract_attribute() { - None => unreachable!( - "impossible, was asked to handle `proof_for_contract` but didn't find such an attribute." - ), - Some(Err(_)) => return, // This error was already emitted - Some(Ok(values)) => values, + let (name, id, span) = match self.interpret_for_contract_attribute() { + None => return, // This error was already emitted + Some(values) => values, }; - let Some(Ok(replacement_name)) = KaniAttributes::for_item(self.tcx, id).checked_with() - else { + assert!(matches!( + &harness.kind, HarnessKind::ProofForContract { target_fn } + if *target_fn == name.to_string())); + if KaniAttributes::for_item(self.tcx, id).contract_attributes().is_none() { dcx.struct_span_err( span, format!( @@ -533,24 +547,14 @@ impl<'tcx> KaniAttributes<'tcx> { ) .with_span_note(self.tcx.def_span(id), "Try adding a contract to this function.") .emit(); - return; - }; - harness.stubs.push(self.stub_for_relative_item(name, replacement_name)); + } } fn handle_stub_verified(&self, harness: &mut HarnessAttributes) { let dcx = self.tcx.dcx(); - for contract in self.interpret_stub_verified_attribute() { - let Ok((name, def_id, span)) = contract else { - // This error has already been emitted so we can ignore it now. - // Later the session will fail anyway so we can just - // optimistically forge on and try to find more errors. - continue; - }; - let replacement_name = match KaniAttributes::for_item(self.tcx, def_id).replaced_with() - { - None => { - dcx.struct_span_err( + for (name, def_id, span) in self.interpret_stub_verified_attribute() { + if KaniAttributes::for_item(self.tcx, def_id).contract_attributes().is_none() { + dcx.struct_span_err( span, format!( "Failed to generate verified stub: Function `{}` has no contract.", @@ -564,13 +568,10 @@ impl<'tcx> KaniAttributes<'tcx> { KaniAttributeKind::Stub.as_ref(), ), ) - .emit(); - continue; - } - Some(Ok(replacement_name)) => replacement_name, - Some(Err(_)) => continue, - }; - harness.stubs.push(self.stub_for_relative_item(name, replacement_name)) + .emit(); + return; + } + harness.verified_stubs.push(name.to_string()) } } @@ -595,92 +596,6 @@ impl<'tcx> KaniAttributes<'tcx> { } } } - - fn stub_for_relative_item(&self, anchor: Symbol, replacement: Symbol) -> Stub { - let local_id = self.item.expect_local(); - let current_module = self.tcx.parent_module_from_def_id(local_id); - let replace_str = replacement.as_str(); - let original_str = anchor.as_str(); - let replacement = original_str - .rsplit_once("::") - .map_or_else(|| replace_str.to_string(), |t| t.0.to_string() + "::" + replace_str); - resolve::resolve_fn(self.tcx, current_module.to_local_def_id(), &replacement).unwrap(); - Stub { original: original_str.to_string(), replacement } - } - - /// Parse and interpret the `kanitool::modifies(var1, var2, ...)` annotation into the vector - /// `[var1, var2, ...]`. - pub fn modifies_contract(&self) -> Option> { - let local_def_id = self.item.expect_local(); - self.map.get(&KaniAttributeKind::Modifies).map(|attr| { - attr.iter() - .flat_map(|clause| match &clause.get_normal_item().args { - AttrArgs::Delimited(lvals) => { - parse_modify_values(self.tcx, local_def_id, &lvals.tokens) - } - _ => unreachable!(), - }) - .collect() - }) - } -} - -/// Pattern macro for the comma token used in attributes. -macro_rules! comma_tok { - () => { - TokenTree::Token(Token { kind: TokenKind::Comma, .. }, _) - }; -} - -/// Parse the token stream inside an attribute (like `kanitool::modifies`) as a comma separated -/// sequence of function parameter names on `local_def_id` (must refer to a function). Then -/// translates the names into [`Local`]s. -fn parse_modify_values<'a>( - tcx: TyCtxt<'a>, - local_def_id: LocalDefId, - t: &'a TokenStream, -) -> impl Iterator + 'a { - let mir = tcx.optimized_mir(local_def_id); - let mut iter = t.trees(); - std::iter::from_fn(move || { - let tree = iter.next()?; - let wrong_token_err = - || tcx.sess.dcx().span_err(tree.span(), "Unexpected token. Expected identifier."); - let result = match tree { - TokenTree::Token(token, _) => { - if let TokenKind::Ident(id, _) = &token.kind { - let hir = tcx.hir(); - let bid = hir.body_owned_by(local_def_id).id(); - Some( - hir.body_param_names(bid) - .zip(mir.args_iter()) - .find(|(name, _decl)| name.name == *id) - .unwrap() - .1 - .as_usize(), - ) - } else { - wrong_token_err(); - None - } - } - _ => { - wrong_token_err(); - None - } - }; - match iter.next() { - None | Some(comma_tok!()) => (), - Some(not_comma) => { - tcx.sess.dcx().span_err( - not_comma.span(), - "Unexpected token, expected end of attribute or comma", - ); - iter.by_ref().skip_while(|t| !matches!(t, comma_tok!())).count(); - } - } - result - }) } /// An efficient check for the existence for a particular [`KaniAttributeKind`]. diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index b4ea06c8d5db..d16e4d2b93d1 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -11,23 +11,24 @@ use crate::args::ReachabilityType; use crate::kani_middle::attributes::is_proof_harness; 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, HarnessMetadata, KaniMetadata}; -use rustc_hir::def_id::{DefId, DefPathHash}; +use kani_metadata::{ArtifactType, AssignsContract, HarnessKind, HarnessMetadata, KaniMetadata}; +use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; use rustc_session::config::OutputType; use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; -use stable_mir::ty::{FnDef, RigidTy, TyKind}; +use stable_mir::ty::{FnDef, IndexedVal, RigidTy, TyKind}; use stable_mir::CrateDef; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fs::File; use std::io::BufWriter; use std::path::{Path, PathBuf}; use tracing::debug; -/// A stable (across compilation sessions) identifier for the harness function. +/// An identifier for the harness function. type Harness = Instance; /// A set of stubs. @@ -124,20 +125,21 @@ fn stub_def(tcx: TyCtxt, def_id: DefId) -> FnDef { } } -/// Group the harnesses by their stubs. +/// Group the harnesses by their stubs and contract usage. fn group_by_stubs( tcx: TyCtxt, all_harnesses: &HashMap, ) -> Vec { - let mut per_stubs: HashMap, CodegenUnit> = - HashMap::default(); + let mut per_stubs: HashMap<_, CodegenUnit> = HashMap::default(); for (harness, metadata) in all_harnesses { let stub_ids = harness_stub_map(tcx, *harness, metadata); + let contracts = extract_contracts(tcx, *harness, metadata); let stub_map = stub_ids .iter() .map(|(k, v)| (tcx.def_path_hash(*k), tcx.def_path_hash(*v))) .collect::>(); - if let Some(unit) = per_stubs.get_mut(&stub_map) { + let key = (contracts, stub_map); + if let Some(unit) = per_stubs.get_mut(&key) { unit.harnesses.push(*harness); } else { let stubs = stub_ids @@ -145,12 +147,43 @@ fn group_by_stubs( .map(|(from, to)| (stub_def(tcx, *from), stub_def(tcx, *to))) .collect::>(); let stubs = apply_transitivity(tcx, *harness, stubs); - per_stubs.insert(stub_map, CodegenUnit { stubs, harnesses: vec![*harness] }); + per_stubs.insert(key, CodegenUnit { stubs, harnesses: vec![*harness] }); } } per_stubs.into_values().collect() } +#[derive(Copy, Clone, Debug, Ord, PartialOrd, PartialEq, Eq, Hash)] +enum ContractUsage { + Stub(usize), + Check(usize), +} + +/// Extract the contract related usages. +/// +/// Note that any error interpreting the result is emitted, but we delay aborting, so we emit as +/// many errors as possible. +fn extract_contracts( + tcx: TyCtxt, + harness: Harness, + metadata: &HarnessMetadata, +) -> BTreeSet { + let def = harness.def; + let mut result = BTreeSet::new(); + if let HarnessKind::ProofForContract { target_fn } = &metadata.attributes.kind { + if let Ok(check_def) = expect_resolve_fn(tcx, def, target_fn, "proof_for_contract") { + result.insert(ContractUsage::Check(check_def.def_id().to_index())); + } + } + + for stub in &metadata.attributes.verified_stubs { + let Ok(stub_def) = expect_resolve_fn(tcx, def, stub, "stub_verified") else { continue }; + result.insert(ContractUsage::Stub(stub_def.def_id().to_index())); + } + + result +} + /// Extract the filename for the metadata file. fn metadata_output_path(tcx: TyCtxt) -> PathBuf { let filepath = tcx.output_filenames(()).path(OutputType::Object); diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index 816f25343fe3..ca4e8e749b75 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -9,14 +9,18 @@ //! //! Note that glob use statements can form loops. The paths can also walk through the loop. +use rustc_smir::rustc_internal; use std::collections::HashSet; use std::fmt; use std::iter::Peekable; +use rustc_errors::ErrorGuaranteed; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc_hir::{ItemKind, UseKind}; use rustc_middle::ty::TyCtxt; +use stable_mir::ty::{FnDef, RigidTy, TyKind}; +use stable_mir::CrateDef; use tracing::debug; /// Attempts to resolve a simple path (in the form of a string) to a function / method `DefId`. @@ -47,6 +51,33 @@ pub fn resolve_fn<'tcx>( } } +/// Resolve the name of a function from the context of the definition provided. +/// +/// Ideally this should pass a more precise span, but we don't keep them around. +pub fn expect_resolve_fn( + tcx: TyCtxt, + res_cx: T, + name: &str, + reason: &str, +) -> Result { + let internal_def_id = rustc_internal::internal(tcx, res_cx.def_id()); + let current_module = tcx.parent_module_from_def_id(internal_def_id.as_local().unwrap()); + let maybe_resolved = resolve_fn(tcx, current_module.to_local_def_id(), name); + let resolved = maybe_resolved.map_err(|err| { + tcx.dcx().span_err( + rustc_internal::internal(tcx, res_cx.span()), + format!("Failed to resolve `{name}` for `{reason}`: {err}"), + ) + })?; + let ty_internal = tcx.type_of(resolved).instantiate_identity(); + let ty = rustc_internal::stable(ty_internal); + if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = ty.kind() { + Ok(def) + } else { + unreachable!("Expected function for `{name}`, but found: {ty}") + } +} + /// Attempts to resolve a simple path (in the form of a string) to a `DefId`. /// The current module is provided as an argument in order to resolve relative /// paths. diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index d3f2afc15d31..3d110c4e9656 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -54,6 +54,10 @@ impl MutableBody { &self.locals } + pub fn arg_count(&self) -> usize { + self.arg_count + } + /// Create a mutable body from the original MIR body. pub fn from(body: Body) -> Self { MutableBody { @@ -147,6 +151,19 @@ impl MutableBody { result } + /// Add a new assignment to an existing place. + pub fn assign_to( + &mut self, + place: Place, + rvalue: Rvalue, + source: &mut SourceInstruction, + position: InsertPosition, + ) { + let span = source.span(&self.blocks); + let stmt = Statement { kind: StatementKind::Assign(place, rvalue), span }; + self.insert_stmt(stmt, source, position); + } + /// Add a new assert to the basic block indicated by the given index. /// /// The new assertion will have the same span as the source instruction, and the basic block @@ -402,12 +419,21 @@ impl MutableBody { /// by the compiler. This function allow us to delete the dummy body before /// creating a new one. /// - /// Note: We do not prune the local variables today for simplicity. - pub fn clear_body(&mut self) { + /// Keep all the locals untouched, so they can be reused by the passes if needed. + pub fn clear_body(&mut self, kind: TerminatorKind) { self.blocks.clear(); - let terminator = Terminator { kind: TerminatorKind::Return, span: self.span }; + let terminator = Terminator { kind, span: self.span }; self.blocks.push(BasicBlock { statements: Vec::default(), terminator }) } + + /// Replace a terminator from the given basic block + pub fn replace_terminator( + &mut self, + source_instruction: &SourceInstruction, + new_term: Terminator, + ) { + self.blocks.get_mut(source_instruction.bb()).unwrap().terminator = new_term; + } } #[derive(Clone, Debug)] @@ -469,6 +495,12 @@ impl SourceInstruction { SourceInstruction::Terminator { bb } => blocks[bb].terminator.span, } } + + pub fn bb(&self) -> BasicBlockIdx { + match *self { + SourceInstruction::Statement { bb, .. } | SourceInstruction::Terminator { bb } => bb, + } + } } fn find_instance(tcx: TyCtxt, diagnostic: &str) -> Option { diff --git a/kani-compiler/src/kani_middle/transform/contracts.rs b/kani-compiler/src/kani_middle/transform/contracts.rs index 3a835c7f3cb6..71e06f5c49cd 100644 --- a/kani-compiler/src/kani_middle/transform/contracts.rs +++ b/kani-compiler/src/kani_middle/transform/contracts.rs @@ -3,16 +3,21 @@ //! This module contains code related to the MIR-to-MIR pass to enable contracts. use crate::kani_middle::attributes::KaniAttributes; use crate::kani_middle::codegen_units::CodegenUnit; -use crate::kani_middle::transform::body::MutableBody; +use crate::kani_middle::find_fn_def; +use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; use cbmc::{InternString, InternedString}; +use rustc_hir::def_id::DefId as InternalDefId; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; +use rustc_span::Symbol; use stable_mir::mir::mono::Instance; -use stable_mir::mir::{Body, ConstOperand, Operand, TerminatorKind}; -use stable_mir::ty::{FnDef, MirConst, RigidTy, TyKind, TypeAndMut}; -use stable_mir::{CrateDef, DefId}; +use stable_mir::mir::{ + Body, ConstOperand, Operand, Rvalue, Terminator, TerminatorKind, VarDebugInfoContents, +}; +use stable_mir::ty::{ClosureDef, FnDef, MirConst, RigidTy, TyKind, TypeAndMut, UintTy}; +use stable_mir::CrateDef; use std::collections::HashSet; use std::fmt::Debug; use tracing::{debug, trace}; @@ -34,7 +39,6 @@ pub struct AnyModifiesPass { kani_write_any_slim: Option, kani_write_any_slice: Option, kani_write_any_str: Option, - stubbed: HashSet, target_fn: Option, } @@ -50,7 +54,7 @@ impl TransformPass for AnyModifiesPass { where Self: Sized, { - // TODO: Check if this is the harness has proof_for_contract + // TODO: Check if the harness has proof_for_contract query_db.args().unstable_features.contains(&"function-contracts".to_string()) && self.kani_any.is_some() } @@ -62,8 +66,8 @@ impl TransformPass for AnyModifiesPass { if instance.def.def_id() == self.kani_any.unwrap().def_id() { // Ensure kani::any is valid. self.any_body(tcx, body) - } else if self.should_apply(tcx, instance) { - // Replace any modifies occurrences. + } else if instance.ty().kind().is_closure() { + // Replace any modifies occurrences. They should only happen in the contract closures. self.replace_any_modifies(body) } else { (false, body) @@ -74,38 +78,19 @@ impl TransformPass for AnyModifiesPass { impl AnyModifiesPass { /// Build the pass with non-extern function stubs. pub fn new(tcx: TyCtxt, unit: &CodegenUnit) -> AnyModifiesPass { - let item_fn_def = |item| { - let TyKind::RigidTy(RigidTy::FnDef(def, _)) = - rustc_internal::stable(tcx.type_of(item)).value.kind() - else { - unreachable!("Expected function, but found `{:?}`", tcx.def_path_str(item)) - }; - def - }; - let kani_any = - tcx.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniAny")).map(item_fn_def); - let kani_any_modifies = tcx - .get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniAnyModifies")) - .map(item_fn_def); - let kani_write_any = tcx - .get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAny")) - .map(item_fn_def); - let kani_write_any_slim = tcx - .get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnySlim")) - .map(item_fn_def); - let kani_write_any_slice = tcx - .get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnySlice")) - .map(item_fn_def); - let kani_write_any_str = tcx - .get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnyStr")) - .map(item_fn_def); - let (target_fn, stubbed) = if let Some(harness) = unit.harnesses.first() { + let kani_any = find_fn_def(tcx, "KaniAny"); + let kani_any_modifies = find_fn_def(tcx, "KaniAnyModifies"); + let kani_write_any = find_fn_def(tcx, "KaniWriteAny"); + let kani_write_any_slim = find_fn_def(tcx, "KaniWriteAnySlim"); + let kani_write_any_slice = find_fn_def(tcx, "KaniWriteAnySlice"); + let kani_write_any_str = find_fn_def(tcx, "KaniWriteAnyStr"); + let target_fn = if let Some(harness) = unit.harnesses.first() { let attributes = KaniAttributes::for_instance(tcx, *harness); let target_fn = attributes.proof_for_contract().map(|symbol| symbol.unwrap().as_str().intern()); - (target_fn, unit.stubs.keys().map(|from| from.def_id()).collect::>()) + target_fn } else { - (None, HashSet::new()) + None }; AnyModifiesPass { kani_any, @@ -115,27 +100,17 @@ impl AnyModifiesPass { kani_write_any_slice, kani_write_any_str, target_fn, - stubbed, } } - /// If we apply `transform_any_modifies` in all contract-generated items, - /// we will end up instantiating `kani::any_modifies` for the replace function - /// every time, even if we are only checking the contract, because the function - /// is always included during contract instrumentation. Thus, we must only apply - /// the transformation if we are using a verified stub or in the presence of recursion. - fn should_apply(&self, tcx: TyCtxt, instance: Instance) -> bool { - let item_attributes = - KaniAttributes::for_item(tcx, rustc_internal::internal(tcx, instance.def.def_id())); - self.stubbed.contains(&instance.def.def_id()) || item_attributes.has_recursion() - } - /// Replace calls to `any_modifies` by calls to `any`. fn replace_any_modifies(&self, mut body: Body) -> (bool, Body) { let mut changed = false; let locals = body.locals().to_vec(); for bb in body.blocks.iter_mut() { - let TerminatorKind::Call { func, args, .. } = &mut bb.terminator.kind else { continue }; + let TerminatorKind::Call { func, args, .. } = &mut bb.terminator.kind else { + continue; + }; if let TyKind::RigidTy(RigidTy::FnDef(def, instance_args)) = func.ty(&locals).unwrap().kind() && Some(def) == self.kani_any_modifies @@ -199,7 +174,9 @@ impl AnyModifiesPass { let mut valid = true; let locals = body.locals().to_vec(); for bb in body.blocks.iter_mut() { - let TerminatorKind::Call { func, .. } = &mut bb.terminator.kind else { continue }; + let TerminatorKind::Call { func, .. } = &mut bb.terminator.kind else { + continue; + }; if let TyKind::RigidTy(RigidTy::FnDef(def, args)) = func.ty(&locals).unwrap().kind() { match Instance::resolve(def, &args) { Ok(_) => {} @@ -232,8 +209,306 @@ impl AnyModifiesPass { (true, body) } else { let mut new_body = MutableBody::from(body); - new_body.clear_body(); - (false, new_body.into()) + new_body.clear_body(TerminatorKind::Unreachable); + (true, new_body.into()) } } } + +/// This pass will transform functions annotated with contracts based on the harness configuration. +/// +/// Functions with contract will always follow the same structure: +/// +/// ```ignore +/// #[kanitool::recursion_check = "__kani_recursion_check_modify"] +/// #[kanitool::checked_with = "__kani_check_modify"] +/// #[kanitool::replaced_with = "__kani_replace_modify"] +/// #[kanitool::modifies_wrapper = "__kani_modifies_modify"] +/// fn name_fn(ptr: &mut u32) { +/// #[kanitool::fn_marker = "kani_register_contract"] +/// pub const fn kani_register_contract T>(f: F) -> T { +/// kani::panic("internal error: entered unreachable code: ") +/// } +/// let kani_contract_mode = kani::internal::mode(); +/// match kani_contract_mode { +/// kani::internal::RECURSION_CHECK => { +/// #[kanitool::is_contract_generated(recursion_check)] +/// let mut __kani_recursion_check_name_fn = || { /* recursion check body */ }; +/// kani_register_contract(__kani_recursion_check_modify) +/// } +/// kani::internal::REPLACE => { +/// #[kanitool::is_contract_generated(replace)] +/// let mut __kani_replace_name_fn = || { /* replace body */ }; +/// kani_register_contract(__kani_replace_name_fn) +/// } +/// kani::internal::SIMPLE_CHECK => { +/// #[kanitool::is_contract_generated(check)] +/// let mut __kani_check_name_fn = || { /* check body */ }; +/// kani_register_contract(__kani_check_name_fn) +/// } +/// _ => { /* original body */ } +/// } +/// } +/// ``` +/// +/// This pass will perform the following operations: +/// 1. For functions with contract that are not being used for check or replacement: +/// - Set `kani_contract_mode` to the value ORIGINAL. +/// - Replace the generated closures body with unreachable. +/// 2. For functions with contract that are being used: +/// - Set `kani_contract_mode` to the value corresponding to the expected usage. +/// - Replace the non-used generated closures body with unreachable. +/// 3. Replace the body of `kani_register_contract` by `kani::internal::run_contract_fn` to +/// invoke the closure. +#[derive(Debug, Default)] +pub struct FunctionWithContractPass { + /// Function that is being checked, if any. + check_fn: Option, + /// Functions that should be stubbed by their contract. + replace_fns: HashSet, + /// Functions annotated with contract attributes will contain contract closures even if they + /// are not to be used in this harness. + /// In order to avoid bringing unnecessary logic, we clear their body. + unused_closures: HashSet, + /// Cache KaniRunContract function used to implement contracts. + run_contract_fn: Option, +} + +impl TransformPass for FunctionWithContractPass { + fn transformation_type() -> TransformationType + where + Self: Sized, + { + TransformationType::Stubbing + } + + fn is_enabled(&self, _query_db: &QueryDb) -> bool + where + Self: Sized, + { + true + } + + /// Transform the function body by replacing it with the stub body. + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + trace!(function=?instance.name(), "FunctionWithContractPass::transform"); + match instance.ty().kind().rigid().unwrap() { + RigidTy::FnDef(def, args) => { + if let Some(mode) = self.contract_mode(tcx, *def) { + self.mark_unused(tcx, *def, &body, mode); + let new_body = self.set_mode(tcx, body, mode); + (true, new_body) + } else if KaniAttributes::for_instance(tcx, instance).fn_marker() + == Some(Symbol::intern("kani_register_contract")) + { + let run = Instance::resolve(self.run_contract_fn.unwrap(), args).unwrap(); + (true, run.body().unwrap()) + } else { + // Not a contract annotated function + (false, body) + } + } + RigidTy::Closure(def, _args) => { + if self.unused_closures.contains(def) { + // Delete body and mark it as unreachable. + let mut new_body = MutableBody::from(body); + new_body.clear_body(TerminatorKind::Unreachable); + (true, new_body.into()) + } else { + // Not a contract annotated function + (false, body) + } + } + _ => { + /* static variables case */ + (false, body) + } + } + } +} + +impl FunctionWithContractPass { + /// Build the pass by collecting which functions we are stubbing and which ones we are + /// verifying. + pub fn new(tcx: TyCtxt, unit: &CodegenUnit) -> FunctionWithContractPass { + if let Some(harness) = unit.harnesses.first() { + let attrs = KaniAttributes::for_instance(tcx, *harness); + let check_fn = attrs.interpret_for_contract_attribute().map(|(_, def_id, _)| def_id); + let replace_fns: HashSet<_> = attrs + .interpret_stub_verified_attribute() + .iter() + .map(|(_, def_id, _)| *def_id) + .collect(); + let run_contract_fn = find_fn_def(tcx, "KaniRunContract"); + assert!(run_contract_fn.is_some(), "Failed to find Kani run contract function"); + FunctionWithContractPass { + check_fn, + replace_fns, + unused_closures: Default::default(), + run_contract_fn, + } + } else { + // If reachability mode is PubFns or Tests, we just remove any contract logic. + // Note that in this path there is no proof harness. + FunctionWithContractPass::default() + } + } + + /// Functions with contract have the following structure: + /// ```ignore + /// fn original([self], args*) { + /// let kani_contract_mode = kani::internal::mode(); // ** Replace this call + /// match kani_contract_mode { + /// kani::internal::RECURSION_CHECK => { + /// let closure = |/*args*/|{ /*body*/}; + /// kani_register_contract(closure) // ** Replace this call + /// } + /// kani::internal::REPLACE => { + /// // same as above + /// } + /// kani::internal::SIMPLE_CHECK => { + /// // same as above + /// } + /// _ => { /* original code */} + /// } + /// } + /// ``` + /// See function `handle_untouched` inside `kani_macros`. + /// + /// Thus, we need to: + /// 1. Initialize `kani_contract_mode` variable to the value corresponding to the mode. + /// + /// Thus replace this call: + /// ```ignore + /// let kani_contract_mode = kani::internal::mode(); // ** Replace this call + /// ``` + /// by: + /// ```ignore + /// let kani_contract_mode = mode_const; + /// goto bbX; + /// ``` + /// 2. Replace `kani_register_contract` by the call to the closure. + fn set_mode(&self, tcx: TyCtxt, body: Body, mode: ContractMode) -> Body { + debug!(?mode, "set_mode"); + let mut new_body = MutableBody::from(body); + let (mut mode_call, ret, target) = new_body + .blocks() + .iter() + .enumerate() + .find_map(|(bb_idx, bb)| { + if let TerminatorKind::Call { func, target, destination, .. } = &bb.terminator.kind + { + let (callee, _) = func.ty(new_body.locals()).unwrap().kind().fn_def()?; + let marker = KaniAttributes::for_def_id(tcx, callee.def_id()).fn_marker(); + if marker.is_some_and(|s| s.as_str() == "kani_contract_mode") { + return Some(( + SourceInstruction::Terminator { bb: bb_idx }, + destination.clone(), + target.unwrap(), + )); + } + } + None + }) + .unwrap(); + + let span = mode_call.span(new_body.blocks()); + let mode_const = new_body.new_uint_operand(mode as _, UintTy::U8, span); + new_body.assign_to( + ret.clone(), + Rvalue::Use(mode_const), + &mut mode_call, + InsertPosition::Before, + ); + new_body.replace_terminator( + &mode_call, + Terminator { kind: TerminatorKind::Goto { target }, span }, + ); + + new_body.into() + } + + /// Return which contract mode to use for this function if any. + fn contract_mode(&self, tcx: TyCtxt, fn_def: FnDef) -> Option { + let kani_attributes = KaniAttributes::for_def_id(tcx, fn_def.def_id()); + kani_attributes.has_contract().then(|| { + let fn_def_id = rustc_internal::internal(tcx, fn_def.def_id()); + if self.check_fn == Some(fn_def_id) { + if kani_attributes.has_recursion() { + ContractMode::RecursiveCheck + } else { + ContractMode::SimpleCheck + } + } else if self.replace_fns.contains(&fn_def_id) { + ContractMode::Replace + } else { + ContractMode::Original + } + }) + } + + /// Select any unused closure for body deletion. + fn mark_unused(&mut self, tcx: TyCtxt, fn_def: FnDef, body: &Body, mode: ContractMode) { + let contract = + KaniAttributes::for_def_id(tcx, fn_def.def_id()).contract_attributes().unwrap(); + let recursion_closure = find_closure(tcx, fn_def, &body, contract.recursion_check.as_str()); + let check_closure = find_closure(tcx, fn_def, &body, contract.checked_with.as_str()); + let replace_closure = find_closure(tcx, fn_def, &body, contract.replaced_with.as_str()); + match mode { + ContractMode::Original => { + // No contract instrumentation needed. Add all closures to the list of unused. + self.unused_closures.insert(recursion_closure); + self.unused_closures.insert(check_closure); + self.unused_closures.insert(replace_closure); + } + ContractMode::RecursiveCheck => { + self.unused_closures.insert(replace_closure); + self.unused_closures.insert(check_closure); + } + ContractMode::SimpleCheck => { + self.unused_closures.insert(replace_closure); + self.unused_closures.insert(recursion_closure); + } + ContractMode::Replace => { + self.unused_closures.insert(recursion_closure); + self.unused_closures.insert(check_closure); + } + } + } +} + +/// Enumeration that store the value of which implementation should be selected. +/// +/// Keep the discriminant values in sync with [kani::internal::mode]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum ContractMode { + Original = 0, + RecursiveCheck = 1, + SimpleCheck = 2, + Replace = 3, +} + +fn find_closure(tcx: TyCtxt, fn_def: FnDef, body: &Body, name: &str) -> ClosureDef { + 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(def); + } + } + None + }) + .unwrap_or_else(|| { + tcx.sess.dcx().err(format!( + "Failed to find contract closure `{name}` in function `{}`", + fn_def.name() + )); + tcx.sess.dcx().abort_if_errors(); + unreachable!() + }) +} diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index d6475465d1b1..82ff25bb2442 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -19,7 +19,8 @@ use crate::kani_queries::QueryDb; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::mir::{ - BinOp, Body, ConstOperand, Operand, Place, Rvalue, Statement, StatementKind, RETURN_LOCAL, + BinOp, Body, ConstOperand, Operand, Place, Rvalue, Statement, StatementKind, TerminatorKind, + RETURN_LOCAL, }; use stable_mir::target::MachineInfo; use stable_mir::ty::{FnDef, MirConst, RigidTy, Ty, TyKind, UintTy}; @@ -86,7 +87,7 @@ impl IntrinsicGeneratorPass { /// ``` fn valid_value_body(&self, tcx: TyCtxt, body: Body) -> Body { let mut new_body = MutableBody::from(body); - new_body.clear_body(); + new_body.clear_body(TerminatorKind::Return); // Initialize return variable with True. let ret_var = RETURN_LOCAL; @@ -163,7 +164,7 @@ impl IntrinsicGeneratorPass { /// ``` fn is_initialized_body(&mut self, tcx: TyCtxt, body: Body) -> Body { let mut new_body = MutableBody::from(body); - new_body.clear_body(); + new_body.clear_body(TerminatorKind::Return); // Initialize return variable with True. let ret_var = RETURN_LOCAL; diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs index 5b497b09619d..4f3125e59868 100644 --- a/kani-compiler/src/kani_middle/transform/mod.rs +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -21,7 +21,7 @@ use crate::kani_middle::reachability::CallGraph; use crate::kani_middle::transform::body::CheckType; use crate::kani_middle::transform::check_uninit::UninitPass; use crate::kani_middle::transform::check_values::ValidValuePass; -use crate::kani_middle::transform::contracts::AnyModifiesPass; +use crate::kani_middle::transform::contracts::{AnyModifiesPass, FunctionWithContractPass}; use crate::kani_middle::transform::kani_intrinsics::IntrinsicGeneratorPass; use crate::kani_middle::transform::stubs::{ExternFnStubPass, FnStubPass}; use crate::kani_queries::QueryDb; @@ -66,7 +66,9 @@ impl BodyTransformation { let check_type = CheckType::new_assert_assume(tcx); transformer.add_pass(queries, FnStubPass::new(&unit.stubs)); transformer.add_pass(queries, ExternFnStubPass::new(&unit.stubs)); - // This has to come after stubs since we want this to replace the stubbed body. + transformer.add_pass(queries, FunctionWithContractPass::new(tcx, &unit)); + // This has to come after the contract pass since we want this to only replace the closure + // body that is relevant for this harness. transformer.add_pass(queries, AnyModifiesPass::new(tcx, &unit)); transformer.add_pass(queries, ValidValuePass { check_type: check_type.clone() }); // Putting `UninitPass` after `ValidValuePass` makes sure that the code generated by diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index ae76be150871..d31d4dae90f6 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -167,18 +167,20 @@ impl KaniSession { pub fn instrument_contracts(&self, harness: &HarnessMetadata, file: &Path) -> Result<()> { let Some(assigns) = harness.contract.as_ref() else { return Ok(()) }; - let args: &[std::ffi::OsString] = &[ + let mut args: Vec = vec![ "--dfcc".into(), (&harness.mangled_name).into(), "--enforce-contract".into(), assigns.contracted_function_name.as_str().into(), - "--nondet-static-exclude".into(), - assigns.recursion_tracker.as_str().into(), "--no-malloc-may-fail".into(), file.into(), file.into(), ]; - self.call_goto_instrument(args) + if let Some(tracker) = &assigns.recursion_tracker { + args.push("--nondet-static-exclude".into()); + args.push(tracker.as_str().into()); + } + self.call_goto_instrument(&args) } /// Generate a .demangled.c file from the .c file using the `prettyName`s from the symbol table diff --git a/kani_metadata/src/harness.rs b/kani_metadata/src/harness.rs index 41eb4eb20919..426cf8d9e960 100644 --- a/kani_metadata/src/harness.rs +++ b/kani_metadata/src/harness.rs @@ -11,7 +11,8 @@ pub struct AssignsContract { /// The target of the contract pub contracted_function_name: String, /// A static global variable used to track recursion that must not be havocked. - pub recursion_tracker: String, + /// This is only needed if the function is tagged with `#[kani::recursive]` + pub recursion_tracker: Option, } /// We emit this structure for each annotated proof harness (`#[kani::proof]`) we find. @@ -50,6 +51,8 @@ pub struct HarnessAttributes { pub unwind_value: Option, /// The stubs used in this harness. pub stubs: Vec, + /// The name of the functions being stubbed by their contract. + pub verified_stubs: Vec, } #[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] @@ -71,6 +74,7 @@ impl HarnessAttributes { solver: None, unwind_value: None, stubs: vec![], + verified_stubs: vec![], } } diff --git a/library/kani/src/internal.rs b/library/kani/src/internal.rs index 22026065106a..68b15316b4c1 100644 --- a/library/kani/src/internal.rs +++ b/library/kani/src/internal.rs @@ -8,60 +8,39 @@ use std::ptr; /// /// We allow the user to provide us with a pointer-like object that we convert as needed. #[doc(hidden)] -pub trait Pointer<'a> { +pub trait Pointer { /// Type of the pointed-to data type Inner: ?Sized; - /// Used for checking assigns contracts where we pass immutable references to the function. - /// - /// We're using a reference to self here, because the user can use just a plain function - /// argument, for instance one of type `&mut _`, in the `modifies` clause which would move it. - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner; - + /// used for havocking on replecement of a `modifies` clause. unsafe fn assignable(self) -> *mut Self::Inner; } -impl<'a, 'b, T: ?Sized> Pointer<'a> for &'b T { +impl Pointer for &T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - std::mem::transmute(*self) - } - unsafe fn assignable(self) -> *mut Self::Inner { - std::mem::transmute(self as *const T) + self as *const T as *mut T } } -impl<'a, 'b, T: ?Sized> Pointer<'a> for &'b mut T { +impl Pointer for &mut T { type Inner = T; - #[allow(clippy::transmute_ptr_to_ref)] - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - std::mem::transmute::<_, &&'a T>(self) - } - unsafe fn assignable(self) -> *mut Self::Inner { self as *mut T } } -impl<'a, T: ?Sized> Pointer<'a> for *const T { +impl Pointer for *const T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - &**self as &'a T - } unsafe fn assignable(self) -> *mut Self::Inner { - std::mem::transmute(self) + self as *mut T } } -impl<'a, T: ?Sized> Pointer<'a> for *mut T { +impl Pointer for *mut T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - &**self as &'a T - } - unsafe fn assignable(self) -> *mut Self::Inner { self } @@ -69,6 +48,8 @@ impl<'a, T: ?Sized> Pointer<'a> for *mut T { /// A way to break the ownerhip rules. Only used by contracts where we can /// guarantee it is done safely. +/// TODO: Remove this! This is not safe. Users should be able to use `ptr::read` and `old` if +/// they really need to. See . #[inline(never)] #[doc(hidden)] #[rustc_diagnostic_item = "KaniUntrackedDeref"] @@ -92,6 +73,8 @@ pub fn init_contracts() {} /// This should only be used within contracts. The intent is to /// perform type inference on a closure's argument +/// TODO: This should be generated inside the function that has contract. This is used for +/// remembers. #[doc(hidden)] pub fn apply_closure bool>(f: U, x: &T) -> bool { f(x) @@ -142,3 +125,36 @@ pub unsafe fn write_any_str(_s: *mut str) { //TODO: String validation unimplemented!("Kani does not support creating arbitrary `str`") } + +/// Function that calls a closure used to implement contracts. +/// +/// In contracts, we cannot invoke the generated closures directly, instead, we call register +/// contract. This function is a no-op. However, in the reality, we do want to call the closure, +/// so we swap the register body by this function body. +#[doc(hidden)] +#[allow(dead_code)] +#[rustc_diagnostic_item = "KaniRunContract"] +#[crate::unstable( + feature = "function-contracts", + issue = "none", + reason = "internal function required to run contract closure" +)] +fn run_contract_fn T>(func: F) -> T { + func() +} + +/// This is used by contracts to select which version of the contract to use during codegen. +#[doc(hidden)] +pub type Mode = u8; + +/// Keep the original body. +pub const ORIGINAL: Mode = 0; + +/// Run the check with recursion support. +pub const RECURSION_CHECK: Mode = 1; + +/// Run the simple check with no recursion support. +pub const SIMPLE_CHECK: Mode = 2; + +/// Stub the body with its contract. +pub const REPLACE: Mode = 3; diff --git a/library/kani_core/src/lib.rs b/library/kani_core/src/lib.rs index 9baba1abe886..b7263816800a 100644 --- a/library/kani_core/src/lib.rs +++ b/library/kani_core/src/lib.rs @@ -306,60 +306,39 @@ macro_rules! kani_intrinsics { /// /// We allow the user to provide us with a pointer-like object that we convert as needed. #[doc(hidden)] - pub trait Pointer<'a> { + pub trait Pointer { /// Type of the pointed-to data type Inner: ?Sized; - /// Used for checking assigns contracts where we pass immutable references to the function. - /// - /// We're using a reference to self here, because the user can use just a plain function - /// argument, for instance one of type `&mut _`, in the `modifies` clause which would move it. - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner; - + /// used for havocking on replecement of a `modifies` clause. unsafe fn assignable(self) -> *mut Self::Inner; } - impl<'a, 'b, T: ?Sized> Pointer<'a> for &'b T { + impl Pointer for &T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - core::mem::transmute(*self) - } - unsafe fn assignable(self) -> *mut Self::Inner { - core::mem::transmute(self as *const T) + self as *const T as *mut T } } - impl<'a, 'b, T: ?Sized> Pointer<'a> for &'b mut T { + impl Pointer for &mut T { type Inner = T; - #[allow(clippy::transmute_ptr_to_ref)] - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - core::mem::transmute::<_, &&'a T>(self) - } - unsafe fn assignable(self) -> *mut Self::Inner { self as *mut T } } - impl<'a, T: ?Sized> Pointer<'a> for *const T { + impl Pointer for *const T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - &**self as &'a T - } unsafe fn assignable(self) -> *mut Self::Inner { - core::mem::transmute(self) + self as *mut T } } - impl<'a, T: ?Sized> Pointer<'a> for *mut T { + impl Pointer for *mut T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - &**self as &'a T - } - unsafe fn assignable(self) -> *mut Self::Inner { self } @@ -436,6 +415,34 @@ macro_rules! kani_intrinsics { //TODO: String validation unimplemented!("Kani does not support creating arbitrary `str`") } + + /// Function that calls a closure used to implement contracts. + /// + /// In contracts, we cannot invoke the generated closures directly, instead, we call register + /// contract. This function is a no-op. However, in the reality, we do want to call the closure, + /// so we swap the register body by this function body. + #[doc(hidden)] + #[allow(dead_code)] + #[rustc_diagnostic_item = "KaniRunContract"] + fn run_contract_fn T>(func: F) -> T { + func() + } + + /// This is used by contracts to select which version of the contract to use during codegen. + #[doc(hidden)] + pub type Mode = u8; + + /// Keep the original body. + pub const ORIGINAL: Mode = 0; + + /// Run the check with recursion support. + pub const RECURSION_CHECK: Mode = 1; + + /// Run the simple check with no recursion support. + pub const SIMPLE_CHECK: Mode = 2; + + /// Stub the body with its contract. + pub const REPLACE: Mode = 3; } }; } diff --git a/library/kani_macros/src/sysroot/contracts/bootstrap.rs b/library/kani_macros/src/sysroot/contracts/bootstrap.rs index dee0bca42c54..8ad21e8a9c6b 100644 --- a/library/kani_macros/src/sysroot/contracts/bootstrap.rs +++ b/library/kani_macros/src/sysroot/contracts/bootstrap.rs @@ -4,106 +4,152 @@ //! Special way we handle the first time we encounter a contract attribute on a //! function. -use proc_macro2::{Ident, Span}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::ItemFn; +use syn::{Expr, ItemFn, Stmt}; -use super::{ - helpers::*, shared::identifier_for_generated_function, ContractConditionsHandler, - INTERNAL_RESULT_IDENT, -}; +use super::{helpers::*, ContractConditionsHandler, INTERNAL_RESULT_IDENT}; impl<'a> ContractConditionsHandler<'a> { - /// The complex case. We are the first time a contract is handled on this function, so - /// we're responsible for + /// Generate initial contract. /// - /// 1. Generating a name for the check function - /// 2. Emitting the original, unchanged item and register the check - /// function on it via attribute - /// 3. Renaming our item to the new name - /// 4. And (minor point) adding #[allow(dead_code)] and - /// #[allow(unused_variables)] to the check function attributes + /// 1. Generating the body for the recursion closure used by contracts. + /// - The recursion closure body will contain the check and replace closure. pub fn handle_untouched(&mut self) { - // We'll be using this to postfix the generated names for the "check" - // and "replace" functions. - let item_hash = self.hash.unwrap(); - - let original_function_name = self.annotated_fn.sig.ident.clone(); - - let check_fn_name = - identifier_for_generated_function(&original_function_name, "check", item_hash); - let replace_fn_name = - identifier_for_generated_function(&original_function_name, "replace", item_hash); - let recursion_wrapper_name = identifier_for_generated_function( - &original_function_name, - "recursion_wrapper", - item_hash, - ); - - // Constructing string literals explicitly here, because `stringify!` - // doesn't work. Let's say we have an identifier `check_fn` and we were - // to do `quote!(stringify!(check_fn))` to try to have it expand to - // `"check_fn"` in the generated code. Then when the next macro parses - // this it will *not* see the literal `"check_fn"` as you may expect but - // instead the *expression* `stringify!(check_fn)`. - let replace_fn_name_str = syn::LitStr::new(&replace_fn_name.to_string(), Span::call_site()); - let wrapper_fn_name_str = - syn::LitStr::new(&self.make_wrapper_name().to_string(), Span::call_site()); - let recursion_wrapper_name_str = - syn::LitStr::new(&recursion_wrapper_name.to_string(), Span::call_site()); + let replace_name = &self.replace_name; + let modifies_name = &self.modify_name; + let recursion_name = &self.recursion_name; + let check_name = &self.check_name; + + let replace_closure = self.replace_closure(); + let check_closure = self.check_closure(); + let recursion_closure = self.new_recursion_closure(&replace_closure, &check_closure); + + let span = Span::call_site(); + let replace_ident = Ident::new(&self.replace_name, span); + let check_ident = Ident::new(&self.check_name, span); + let recursion_ident = Ident::new(&self.recursion_name, span); // The order of `attrs` and `kanitool::{checked_with, // is_contract_generated}` is important here, because macros are // expanded outside in. This way other contract annotations in `attrs` // sees those attributes and can use them to determine // `function_state`. - // - // The same care is taken when we emit check and replace functions. - // emit the check function. - let is_impl_fn = is_probably_impl_fn(&self.annotated_fn); let ItemFn { attrs, vis, sig, block } = &self.annotated_fn; self.output.extend(quote!( #(#attrs)* - #[kanitool::checked_with = #recursion_wrapper_name_str] - #[kanitool::replaced_with = #replace_fn_name_str] - #[kanitool::inner_check = #wrapper_fn_name_str] + #[kanitool::recursion_check = #recursion_name] + #[kanitool::checked_with = #check_name] + #[kanitool::replaced_with = #replace_name] + #[kanitool::modifies_wrapper = #modifies_name] #vis #sig { - #block + // Dummy function used to force the compiler to capture the environment. + // We cannot call closures inside constant functions. + // This function gets replaced by `kani::internal::call_closure`. + #[inline(never)] + #[kanitool::fn_marker = "kani_register_contract"] + const fn kani_register_contract T>(f: F) -> T { + unreachable!() + } + // Dummy function that we replace to pick the contract mode. + // By default, return ORIGINAL + #[inline(never)] + #[kanitool::fn_marker = "kani_contract_mode"] + const fn kani_contract_mode() -> kani::internal::Mode { + kani::internal::ORIGINAL + } + let kani_contract_mode = kani_contract_mode(); + match kani_contract_mode { + kani::internal::RECURSION_CHECK => { + #recursion_closure; + kani_register_contract(#recursion_ident) + } + kani::internal::REPLACE => { + #replace_closure; + kani_register_contract(#replace_ident) + } + kani::internal::SIMPLE_CHECK => { + #check_closure; + kani_register_contract(#check_ident) + } + _ => #block + } } )); + } + + /// Handle subsequent contract attributes. + /// + /// Find the closures added by the initial setup, parse them and expand their body according + /// to the attribute being handled. + pub fn handle_expanded(&mut self) { + let mut annotated_fn = self.annotated_fn.clone(); + let ItemFn { block, .. } = &mut annotated_fn; + let recursion_closure = expect_closure_in_match(&mut block.stmts, "recursion_check"); + self.expand_recursion(recursion_closure); - let mut wrapper_sig = sig.clone(); - wrapper_sig.ident = recursion_wrapper_name; - // We use non-constant functions, thus, the wrapper cannot be constant. - wrapper_sig.constness = None; + let replace_closure = expect_closure_in_match(&mut block.stmts, "replace"); + self.expand_replace(replace_closure); - let args = pats_to_idents(&mut wrapper_sig.inputs).collect::>(); - let also_args = args.iter(); - let (call_check, call_replace) = if is_impl_fn { - (quote!(Self::#check_fn_name), quote!(Self::#replace_fn_name)) - } else { - (quote!(#check_fn_name), quote!(#replace_fn_name)) - }; + let check_closure = expect_closure_in_match(&mut block.stmts, "check"); + self.expand_check(check_closure); - let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); - self.output.extend(quote!( + self.output.extend(quote!(#annotated_fn)); + } + + /// Generate the tokens for the recursion closure. + fn new_recursion_closure( + &self, + replace_closure: &TokenStream, + check_closure: &TokenStream, + ) -> TokenStream { + let ItemFn { ref sig, .. } = self.annotated_fn; + let output = &sig.output; + let span = Span::call_site(); + let result = Ident::new(INTERNAL_RESULT_IDENT, span); + let replace_ident = Ident::new(&self.replace_name, span); + let check_ident = Ident::new(&self.check_name, span); + let recursion_ident = Ident::new(&self.recursion_name, span); + + quote!( + #[kanitool::is_contract_generated(recursion_check)] #[allow(dead_code, unused_variables, unused_mut)] - #[kanitool::is_contract_generated(recursion_wrapper)] - #wrapper_sig { + let mut #recursion_ident = || #output + { + #[kanitool::recursion_tracker] static mut REENTRY: bool = false; if unsafe { REENTRY } { - #call_replace(#(#args),*) + #replace_closure + #replace_ident() } else { unsafe { REENTRY = true }; - let #result = #call_check(#(#also_args),*); + #check_closure + let #result = #check_ident(); unsafe { REENTRY = false }; #result } - } - )); + }; + ) + } + + /// Expand an existing recursion closure with the new condition. + fn expand_recursion(&self, closure: &mut Stmt) { + // TODO: Need to enter if / else. Make this traverse body and return list statements :( + let body = closure_body(closure); + let stmts = &mut body.block.stmts; + let if_reentry = stmts + .iter_mut() + .find_map(|stmt| { + if let Stmt::Expr(Expr::If(if_expr), ..) = stmt { Some(if_expr) } else { None } + }) + .unwrap(); + + let replace_closure = expect_closure(&mut if_reentry.then_branch.stmts, "replace"); + self.expand_replace(replace_closure); - self.emit_check_function(Some(check_fn_name)); - self.emit_replace_function(Some(replace_fn_name)); - self.emit_augmented_modifies_wrapper(); + let else_branch = if_reentry.else_branch.as_mut().unwrap(); + let Expr::Block(else_block) = else_branch.1.as_mut() else { unreachable!() }; + let check_closure = expect_closure(&mut else_block.block.stmts, "check"); + self.expand_check(check_closure); } } diff --git a/library/kani_macros/src/sysroot/contracts/check.rs b/library/kani_macros/src/sysroot/contracts/check.rs index f804fc70c5f5..69f280ad9335 100644 --- a/library/kani_macros/src/sysroot/contracts/check.rs +++ b/library/kani_macros/src/sysroot/contracts/check.rs @@ -5,33 +5,28 @@ use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::quote; -use syn::{Expr, FnArg, ItemFn, Token}; +use std::mem; +use syn::{parse_quote, Block, Expr, FnArg, Local, LocalInit, Pat, PatIdent, ReturnType, Stmt}; use super::{ - helpers::*, - shared::{build_ensures, try_as_result_assign_mut}, - ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT, + helpers::*, shared::build_ensures, ContractConditionsData, ContractConditionsHandler, + INTERNAL_RESULT_IDENT, }; -const WRAPPER_ARG_PREFIX: &str = "_wrapper_arg_"; +const WRAPPER_ARG: &str = "_wrapper_arg"; impl<'a> ContractConditionsHandler<'a> { /// Create the body of a check function. /// /// Wraps the conditions from this attribute around `self.body`. - /// - /// Mutable because a `modifies` clause may need to extend the inner call to - /// the wrapper with new arguments. - pub fn make_check_body(&mut self) -> TokenStream2 { - let mut inner = self.ensure_bootstrapped_check_body(); + pub fn make_check_body(&self, mut body_stmts: Vec) -> TokenStream2 { let Self { attr_copy, .. } = self; - match &self.condition_type { ContractConditionsData::Requires { attr } => { - quote!( + quote!({ kani::assume(#attr); - #(#inner)* - ) + #(#body_stmts)* + }) } ContractConditionsData::Ensures { attr } => { let (remembers, ensures_clause) = build_ensures(attr); @@ -42,257 +37,157 @@ impl<'a> ContractConditionsHandler<'a> { kani::assert(#ensures_clause, stringify!(#attr_copy)); ); - assert!(matches!( - inner.pop(), - Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) - if pexpr.path.get_ident().map_or(false, |id| id == INTERNAL_RESULT_IDENT) - )); - - let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); - quote!( + let return_expr = body_stmts.pop(); + quote!({ #remembers - #(#inner)* + #(#body_stmts)* #exec_postconditions - #result - ) + #return_expr + }) } ContractConditionsData::Modifies { attr } => { - let wrapper_name = self.make_wrapper_name().to_string(); - - let wrapper_args = if let Some(wrapper_call_args) = - inner.iter_mut().find_map(|stmt| try_as_wrapper_call_args(stmt, &wrapper_name)) - { - let wrapper_args = make_wrapper_idents( - wrapper_call_args.len(), - attr.len(), - WRAPPER_ARG_PREFIX, - ); - wrapper_call_args - .extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a)))); - wrapper_args + let wrapper_arg_ident = Ident::new(WRAPPER_ARG, Span::call_site()); + let wrapper_tuple = body_stmts.iter_mut().find_map(|stmt| { + if let Stmt::Local(Local { + pat: Pat::Ident(PatIdent { ident, .. }), + init: Some(LocalInit { expr, .. }), + .. + }) = stmt + { + (ident == &wrapper_arg_ident).then_some(expr.as_mut()) + } else { + None + } + }); + if let Some(Expr::Tuple(values)) = wrapper_tuple { + values.elems.extend(attr.iter().map(|attr| { + let expr: Expr = parse_quote!(#attr + as *const _); + expr + })); } else { - unreachable!( - "Invariant broken, check function did not contain a call to the wrapper function" - ) - }; - - quote!( - #(let #wrapper_args = unsafe { kani::internal::Pointer::decouple_lifetime(&#attr) };)* - #(#inner)* - ) + unreachable!("Expected tuple but found `{wrapper_tuple:?}`") + } + quote!({#(#body_stmts)*}) } } } - /// Get the sequence of statements of the previous check body or create the default one. - fn ensure_bootstrapped_check_body(&self) -> Vec { - let wrapper_name = self.make_wrapper_name(); + /// Initialize the list of statements for the check closure body. + fn initial_check_stmts(&self) -> Vec { + let modifies_ident = Ident::new(&self.modify_name, Span::call_site()); + let wrapper_arg_ident = Ident::new(WRAPPER_ARG, Span::call_site()); let return_type = return_type_to_type(&self.annotated_fn.sig.output); - if self.is_first_emit() { - let args = exprs_for_args(&self.annotated_fn.sig.inputs); - let wrapper_call = if is_probably_impl_fn(self.annotated_fn) { - quote!(Self::#wrapper_name) - } else { - quote!(#wrapper_name) - }; - let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); - syn::parse_quote!( - let #result : #return_type = #wrapper_call(#(#args),*); - #result - ) - } else { - self.annotated_fn.block.stmts.clone() - } + let mut_recv = self.has_mutable_receiver().then(|| quote!(core::ptr::addr_of!(self),)); + let redefs = self.arg_redefinitions(); + let modifies_closure = + self.modifies_closure(&self.annotated_fn.sig.output, &self.annotated_fn.block, redefs); + let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); + parse_quote!( + let #wrapper_arg_ident = (#mut_recv); + #modifies_closure + let #result : #return_type = #modifies_ident(#wrapper_arg_ident); + #result + ) } - /// Emit the check function into the output stream. + /// Generate a token stream that represents the check closure. /// /// See [`Self::make_check_body`] for the most interesting parts of this /// function. - pub fn emit_check_function(&mut self, override_function_dent: Option) { - self.emit_common_header(); - - if self.function_state.emit_tag_attr() { - // If it's the first time we also emit this marker. Again, order is - // important so this happens as the last emitted attribute. - self.output.extend(quote!(#[kanitool::is_contract_generated(check)])); - } - let body = self.make_check_body(); - let mut sig = self.annotated_fn.sig.clone(); - // We use non-constant functions, thus, the wrapper cannot be constant. - sig.constness = None; - if let Some(ident) = override_function_dent { - sig.ident = ident; - } - self.output.extend(quote!( - #sig { - #body - } - )) + pub fn check_closure(&self) -> TokenStream2 { + let check_ident = Ident::new(&self.check_name, Span::call_site()); + let sig = &self.annotated_fn.sig; + let output = &sig.output; + let body_stmts = self.initial_check_stmts(); + let body = self.make_check_body(body_stmts); + + quote!( + #[kanitool::is_contract_generated(check)] + #[allow(dead_code, unused_variables, unused_mut)] + let mut #check_ident = || #output #body; + ) } - /// Emit a modifies wrapper, possibly augmenting a prior, existing one. + /// Expand the check body. /// - /// We only augment if this clause is a `modifies` clause. Before, - /// we annotated the wrapper arguments with `impl kani::Arbitrary`, - /// so Rust would infer the proper types for each argument. - /// We want to remove the restriction that these arguments must - /// implement `kani::Arbitrary` for checking. Now, we annotate each - /// argument with a generic type parameter, so the compiler can - /// continue inferring the correct types. - pub fn emit_augmented_modifies_wrapper(&mut self) { - if let ContractConditionsData::Modifies { attr } = &self.condition_type { - let wrapper_args = make_wrapper_idents( - self.annotated_fn.sig.inputs.len(), - attr.len(), - WRAPPER_ARG_PREFIX, - ); - // Generate a unique type parameter identifier - let type_params = make_wrapper_idents( - self.annotated_fn.sig.inputs.len(), - attr.len(), - "WrapperArgType", - ); - let sig = &mut self.annotated_fn.sig; - for (arg, arg_type) in wrapper_args.clone().zip(type_params) { - // Add the type parameter to the function signature's generic parameters list - let mut bounds: syn::punctuated::Punctuated = - syn::punctuated::Punctuated::new(); - bounds.push(syn::TypeParamBound::Trait(syn::TraitBound { - paren_token: None, - modifier: syn::TraitBoundModifier::Maybe(Token![?](Span::call_site())), - lifetimes: None, - path: syn::Ident::new("Sized", Span::call_site()).into(), - })); - sig.generics.params.push(syn::GenericParam::Type(syn::TypeParam { - attrs: vec![], - ident: arg_type.clone(), - colon_token: Some(Token![:](Span::call_site())), - bounds: bounds, - eq_token: None, - default: None, - })); - let lifetime = syn::Lifetime { apostrophe: Span::call_site(), ident: arg.clone() }; - sig.inputs.push(FnArg::Typed(syn::PatType { - attrs: vec![], - colon_token: Token![:](Span::call_site()), - pat: Box::new(syn::Pat::Verbatim(quote!(#arg))), - ty: Box::new(syn::parse_quote! { &#arg_type }), - })); - sig.generics.params.push(syn::GenericParam::Lifetime(syn::LifetimeParam { - lifetime, - colon_token: None, - bounds: Default::default(), - attrs: vec![], - })); - } - - self.output.extend(quote!(#[kanitool::modifies(#(#wrapper_args),*)])) - } - self.emit_common_header(); - - if self.function_state.emit_tag_attr() { - // If it's the first time we also emit this marker. Again, order is - // important so this happens as the last emitted attribute. - self.output.extend(quote!(#[kanitool::is_contract_generated(wrapper)])); - } - - let name = self.make_wrapper_name(); - let ItemFn { vis, sig, block, .. } = self.annotated_fn; - - let mut sig = sig.clone(); - sig.ident = name; - self.output.extend(quote!( - #vis #sig #block + /// First find the modifies body and expand that. Then expand the rest of the body. + pub fn expand_check(&self, closure: &mut Stmt) { + let body = closure_body(closure); + self.expand_modifies(find_contract_closure(&mut body.block.stmts, "wrapper").expect( + &format!("Internal Failure: Expected to find `wrapper` closure, but found none"), )); + *body = syn::parse2(self.make_check_body(mem::take(&mut body.block.stmts))).unwrap(); } -} - -/// Try to interpret this statement as `let result : <...> = (args ...);` and -/// return a mutable reference to the parameter list. -fn try_as_wrapper_call_args<'a>( - stmt: &'a mut syn::Stmt, - wrapper_fn_name: &str, -) -> Option<&'a mut syn::punctuated::Punctuated> { - let syn::LocalInit { diverge: None, expr: init_expr, .. } = try_as_result_assign_mut(stmt)? - else { - return None; - }; - - match init_expr.as_mut() { - Expr::Call(syn::ExprCall { func: box_func, args, .. }) => match box_func.as_ref() { - syn::Expr::Path(syn::ExprPath { qself: None, path, .. }) - if path.get_ident().map_or(false, |id| id == wrapper_fn_name) => - { - Some(args) - } - _ => None, - }, - _ => None, - } -} -/// Make `num` [`Ident`]s with the names `prefix{i}` with `i` starting at `low` and -/// increasing by one each time. -fn make_wrapper_idents( - low: usize, - num: usize, - prefix: &'static str, -) -> impl Iterator + Clone + 'static { - (low..).map(move |i| Ident::new(&format!("{prefix}{i}"), Span::mixed_site())).take(num) -} - -#[cfg(test)] -mod test { - macro_rules! detect_impl_fn { - ($expect_pass:expr, $($tt:tt)*) => {{ - let syntax = stringify!($($tt)*); - let ast = syn::parse_str(syntax).unwrap(); - assert!($expect_pass == super::is_probably_impl_fn(&ast), - "Incorrect detection.\nExpected is_impl_fun: {}\nInput Expr; {}\nParsed: {:?}", - $expect_pass, - syntax, - ast - ); - }} + /// Emit a modifies wrapper. It's only argument is the list of addresses that may be modified. + pub fn modifies_closure( + &self, + output: &ReturnType, + body: &Block, + redefs: TokenStream2, + ) -> TokenStream2 { + // Filter receiver + let wrapper_ident = Ident::new(WRAPPER_ARG, Span::call_site()); + let modifies_ident = Ident::new(&self.modify_name, Span::call_site()); + let stmts = &body.stmts; + quote!( + #[kanitool::is_contract_generated(wrapper)] + #[allow(dead_code, unused_variables, unused_mut)] + let mut #modifies_ident = |#wrapper_ident: _| #output { + #redefs + #(#stmts)* + }; + ) } - #[test] - fn detect_impl_fn_by_receiver() { - detect_impl_fn!(true, fn self_by_ref(&self, u: usize) -> bool {}); - - detect_impl_fn!(true, fn self_by_self(self, u: usize) -> bool {}); + /// Expand the modifies closure if we are handling a modifies attribute. Otherwise, no-op. + pub fn expand_modifies(&self, closure_stmt: &mut Stmt) { + if matches!(&self.condition_type, ContractConditionsData::Modifies { .. }) { + let Stmt::Local(Local { init: Some(LocalInit { expr, .. }), .. }) = closure_stmt else { + unreachable!() + }; + let Expr::Closure(closure) = expr.as_ref() else { unreachable!() }; + let Expr::Block(body) = closure.body.as_ref() else { unreachable!() }; + let stream = self.modifies_closure(&closure.output, &body.block, TokenStream2::new()); + *closure_stmt = syn::parse2(stream).unwrap(); + } } - #[test] - fn detect_impl_fn_by_self_ty() { - detect_impl_fn!(true, fn self_by_construct(u: usize) -> Self {}); - detect_impl_fn!(true, fn self_by_wrapped_construct(u: usize) -> Arc {}); - - detect_impl_fn!(true, fn self_by_other_arg(u: usize, slf: Self) {}); - - detect_impl_fn!(true, fn self_by_other_wrapped_arg(u: usize, slf: Vec) {}) + /// Return whether the original function has a mutable receiver. + fn has_mutable_receiver(&self) -> bool { + let first_arg = self.annotated_fn.sig.inputs.first(); + first_arg + .map(|arg| { + matches!( + arg, + FnArg::Receiver(syn::Receiver { mutability: Some(_), reference: None, .. },) + ) + }) + .unwrap_or(false) } - #[test] - fn detect_impl_fn_by_qself() { - detect_impl_fn!( - true, - fn self_by_mention(u: usize) { - Self::other(u) + /// Generate argument re-definitions for mutable arguments. + /// + /// This is used so Kani doesn't think that modifying a local argument value is a side effect. + fn arg_redefinitions(&self) -> TokenStream2 { + let mut result = TokenStream2::new(); + for (mutability, ident) in self.arg_bindings() { + if mutability == MutBinding::Mut { + result.extend(quote!(let mut #ident = #ident;)) + } else { + // This would make some replace some temporary variables from error messages. + //result.extend(quote!(let #ident = #ident; )) } - ); + } + result } - #[test] - fn detect_no_impl_fn() { - detect_impl_fn!( - false, - fn self_by_mention(u: usize) { - let self_name = 18; - let self_lit = "self"; - let self_lit = "Self"; - } - ); + /// Extract all arguments bindings and their mutability. + fn arg_bindings(&self) -> impl Iterator { + self.annotated_fn.sig.inputs.iter().flat_map(|arg| match arg { + FnArg::Receiver(_) => vec![], + FnArg::Typed(typed) => pat_to_bindings(typed.pat.as_ref()), + }) } } diff --git a/library/kani_macros/src/sysroot/contracts/helpers.rs b/library/kani_macros/src/sysroot/contracts/helpers.rs index 174f0f9483d7..410c2d971c44 100644 --- a/library/kani_macros/src/sysroot/contracts/helpers.rs +++ b/library/kani_macros/src/sysroot/contracts/helpers.rs @@ -5,9 +5,9 @@ //! specific to Kani and contracts. use proc_macro2::{Ident, Span}; -use quote::{quote, ToTokens}; use std::borrow::Cow; -use syn::{spanned::Spanned, visit::Visit, Expr, FnArg, ItemFn}; +use syn::spanned::Spanned; +use syn::{parse_quote, Attribute, Expr, ExprBlock, Local, LocalInit, PatIdent, Stmt}; /// If an explicit return type was provided it is returned, otherwise `()`. pub fn return_type_to_type(return_type: &syn::ReturnType) -> Cow { @@ -20,10 +20,16 @@ pub fn return_type_to_type(return_type: &syn::ReturnType) -> Cow { } } -/// Create an expression that reconstructs a struct that was matched in a pattern. +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum MutBinding { + Mut, + NotMut, +} + +/// Extract all local bindings from a given pattern. /// -/// Does not support enums, wildcards, pattern alternatives (`|`), range patterns, or verbatim. -pub fn pat_to_expr(pat: &syn::Pat) -> Expr { +/// Does not support range patterns, or verbatim. +pub fn pat_to_bindings(pat: &syn::Pat) -> Vec<(MutBinding, &Ident)> { use syn::Pat; let mk_err = |typ| { pat.span() @@ -33,167 +39,90 @@ pub fn pat_to_expr(pat: &syn::Pat) -> Expr { unreachable!() }; match pat { - Pat::Const(c) => Expr::Const(c.clone()), - Pat::Ident(id) => Expr::Verbatim(id.ident.to_token_stream()), - Pat::Lit(lit) => Expr::Lit(lit.clone()), - Pat::Reference(rf) => Expr::Reference(syn::ExprReference { - attrs: vec![], - and_token: rf.and_token, - mutability: rf.mutability, - expr: Box::new(pat_to_expr(&rf.pat)), - }), - Pat::Tuple(tup) => Expr::Tuple(syn::ExprTuple { - attrs: vec![], - paren_token: tup.paren_token, - elems: tup.elems.iter().map(pat_to_expr).collect(), - }), - Pat::Slice(slice) => Expr::Reference(syn::ExprReference { - attrs: vec![], - and_token: syn::Token!(&)(Span::call_site()), - mutability: None, - expr: Box::new(Expr::Array(syn::ExprArray { - attrs: vec![], - bracket_token: slice.bracket_token, - elems: slice.elems.iter().map(pat_to_expr).collect(), - })), - }), - Pat::Path(pth) => Expr::Path(pth.clone()), - Pat::Or(_) => mk_err("or"), - Pat::Rest(_) => mk_err("rest"), - Pat::Wild(_) => mk_err("wildcard"), - Pat::Paren(inner) => pat_to_expr(&inner.pat), - Pat::Range(_) => mk_err("range"), + Pat::Const(_) => vec![], + Pat::Ident(PatIdent { ident, subpat: Some(subpat), mutability, .. }) => { + let mut idents = pat_to_bindings(subpat.1.as_ref()); + idents.push((mutability.map_or(MutBinding::NotMut, |_| MutBinding::Mut), ident)); + idents + } + Pat::Ident(PatIdent { ident, mutability, .. }) => { + vec![(mutability.map_or(MutBinding::NotMut, |_| MutBinding::Mut), ident)] + } + Pat::Lit(_) => vec![], + Pat::Reference(_) => vec![], + Pat::Tuple(tup) => tup.elems.iter().flat_map(pat_to_bindings).collect(), + Pat::Slice(slice) => slice.elems.iter().flat_map(pat_to_bindings).collect(), + Pat::Path(_) => { + vec![] + } + Pat::Or(pat_or) => { + // Note: Patterns are not accepted in function arguments. + // No matter what, the same bindings must exist in all the patterns. + pat_or.cases.first().map(pat_to_bindings).unwrap_or_default() + } + Pat::Rest(_) => vec![], + Pat::Wild(_) => vec![], + Pat::Paren(inner) => pat_to_bindings(&inner.pat), + Pat::Range(_) => vec![], Pat::Struct(strct) => { - if strct.rest.is_some() { - mk_err(".."); - } - Expr::Struct(syn::ExprStruct { - attrs: vec![], - path: strct.path.clone(), - brace_token: strct.brace_token, - dot2_token: None, - rest: None, - qself: strct.qself.clone(), - fields: strct - .fields - .iter() - .map(|field_pat| syn::FieldValue { - attrs: vec![], - member: field_pat.member.clone(), - colon_token: field_pat.colon_token, - expr: pat_to_expr(&field_pat.pat), - }) - .collect(), - }) + strct.fields.iter().flat_map(|field_pat| pat_to_bindings(&field_pat.pat)).collect() } Pat::Verbatim(_) => mk_err("verbatim"), - Pat::Type(pt) => pat_to_expr(pt.pat.as_ref()), - Pat::TupleStruct(_) => mk_err("tuple struct"), + Pat::Type(pt) => pat_to_bindings(pt.pat.as_ref()), + Pat::TupleStruct(tup) => tup.elems.iter().flat_map(pat_to_bindings).collect(), _ => mk_err("unknown"), } } -/// For each argument create an expression that passes this argument along unmodified. -/// -/// Reconstructs structs that may have been deconstructed with patterns. -pub fn exprs_for_args( - args: &syn::punctuated::Punctuated, -) -> impl Iterator + Clone + '_ { - args.iter().map(|arg| match arg { - FnArg::Receiver(_) => Expr::Verbatim(quote!(self)), - FnArg::Typed(typed) => pat_to_expr(&typed.pat), +/// Find a closure statement attached with `kanitool::is_contract_generated` attribute. +pub fn find_contract_closure<'a>( + stmts: &'a mut [Stmt], + name: &'static str, +) -> Option<&'a mut Stmt> { + stmts.iter_mut().find(|stmt| { + if let Stmt::Local(local) = stmt { + let ident = Ident::new(name, Span::call_site()); + let attr: Attribute = parse_quote!(#[kanitool::is_contract_generated(#ident)]); + local.attrs.contains(&attr) + } else { + false + } }) } -/// The visitor used by [`is_probably_impl_fn`]. See function documentation for -/// more information. -struct SelfDetector(bool); - -impl<'ast> Visit<'ast> for SelfDetector { - fn visit_ident(&mut self, i: &'ast syn::Ident) { - self.0 |= i == &Ident::from(syn::Token![Self](Span::mixed_site())) - } - - fn visit_receiver(&mut self, _node: &'ast syn::Receiver) { - self.0 = true; - } +/// Find a closure defined in one of the provided statements. +/// +/// Panic if no closure was found. +pub fn expect_closure<'a>(stmts: &'a mut [Stmt], name: &'static str) -> &'a mut Stmt { + find_contract_closure(stmts, name) + .expect(&format!("Internal Failure: Expected to find `{name}` closure, but found none")) } -/// Try to determine if this function is part of an `impl`. -/// -/// Detects *methods* by the presence of a receiver argument. Heuristically -/// detects *associated functions* by the use of `Self` anywhere. -/// -/// Why do we need this? It's because if we want to call this `fn`, or any other -/// `fn` we generate into the same context we need to use `foo()` or -/// `Self::foo()` respectively depending on whether this is a plain or -/// associated function or Rust will complain. For the contract machinery we -/// need to generate and then call various functions we generate as well as the -/// original contracted function and so we need to determine how to call them -/// correctly. -/// -/// We can only solve this heuristically. The fundamental problem with Rust -/// macros is that they only see the syntax that's given to them and no other -/// context. It is however that context (of an `impl` block) that definitively -/// determines whether the `fn` is a plain function or an associated function. +/// Find a closure inside a match block. /// -/// The heuristic itself is flawed, but it's the best we can do. For instance -/// this is perfectly legal -/// -/// ``` -/// struct S; -/// impl S { -/// #[i_want_to_call_you] -/// fn helper(u: usize) -> bool { -/// u < 8 -/// } -/// } -/// ``` -/// -/// This function would have to be called `S::helper()` but to the -/// `#[i_want_to_call_you]` attribute this function looks just like a bare -/// function because it never mentions `self` or `Self`. While this is a rare -/// case, the following is much more likely and suffers from the same problem, -/// because we can't know that `Vec == Self`. -/// -/// ``` -/// impl Vec { -/// fn new() -> Vec { -/// Vec { cap: 0, buf: NonNull::dangling() } -/// } -/// } -/// ``` -/// -/// **Side note:** You may be tempted to suggest that we could try and parse -/// `syn::ImplItemFn` and distinguish that from `syn::ItemFn` to distinguish -/// associated function from plain functions. However parsing in an attribute -/// placed on *any* `fn` will always succeed for *both* `syn::ImplItemFn` and -/// `syn::ItemFn`, thus not letting us distinguish between the two. -pub fn is_probably_impl_fn(fun: &ItemFn) -> bool { - let mut self_detector = SelfDetector(false); - self_detector.visit_item_fn(fun); - self_detector.0 +/// Panic if no closure was found. +pub fn expect_closure_in_match<'a>(stmts: &'a mut [Stmt], name: &'static str) -> &'a mut Stmt { + let closure = stmts.iter_mut().find_map(|stmt| { + if let Stmt::Expr(Expr::Match(match_expr), ..) = stmt { + match_expr.arms.iter_mut().find_map(|arm| { + let Expr::Block(block) = arm.body.as_mut() else { return None }; + find_contract_closure(&mut block.block.stmts, name) + }) + } else { + None + } + }); + closure.expect(&format!("Internal Failure: Expected to find `{name}` closure, but found none")) } -/// Convert every use of a pattern in this signature to a simple, fresh, binding-only -/// argument ([`syn::PatIdent`]) and return the [`Ident`] that was generated. -pub fn pats_to_idents

( - sig: &mut syn::punctuated::Punctuated, -) -> impl Iterator + '_ { - sig.iter_mut().enumerate().map(|(i, arg)| match arg { - syn::FnArg::Receiver(_) => Ident::from(syn::Token![self](Span::call_site())), - syn::FnArg::Typed(syn::PatType { pat, .. }) => { - let ident = Ident::new(&format!("arg{i}"), Span::mixed_site()); - *pat.as_mut() = syn::Pat::Ident(syn::PatIdent { - attrs: vec![], - by_ref: None, - mutability: None, - ident: ident.clone(), - subpat: None, - }); - ident - } - }) +/// Extract the body of a closure declaration. +pub fn closure_body(closure: &mut Stmt) -> &mut ExprBlock { + let Stmt::Local(Local { init: Some(LocalInit { expr, .. }), .. }) = closure else { + unreachable!() + }; + let Expr::Closure(closure) = expr.as_mut() else { unreachable!() }; + let Expr::Block(body) = closure.body.as_mut() else { unreachable!() }; + body } /// Does the provided path have the same chain of identifiers as `mtch` (match) @@ -240,35 +169,6 @@ pub fn chunks_by<'a, T, C: Default + Extend>( }) } -/// Create a unique hash for a token stream (basically a [`std::hash::Hash`] -/// impl for `proc_macro2::TokenStream`). -fn hash_of_token_stream(hasher: &mut H, stream: proc_macro2::TokenStream) { - use proc_macro2::TokenTree; - use std::hash::Hash; - for token in stream { - match token { - TokenTree::Ident(i) => i.hash(hasher), - TokenTree::Punct(p) => p.as_char().hash(hasher), - TokenTree::Group(g) => { - std::mem::discriminant(&g.delimiter()).hash(hasher); - hash_of_token_stream(hasher, g.stream()); - } - TokenTree::Literal(lit) => lit.to_string().hash(hasher), - } - } -} - -/// Hash this `TokenStream` and return an integer that is at most digits -/// long when hex formatted. -pub fn short_hash_of_token_stream(stream: &proc_macro::TokenStream) -> u64 { - const SIX_HEX_DIGITS_MASK: u64 = 0x1_000_000; - use std::hash::Hasher; - let mut hasher = std::collections::hash_map::DefaultHasher::default(); - hash_of_token_stream(&mut hasher, proc_macro2::TokenStream::from(stream.clone())); - let long_hash = hasher.finish(); - long_hash % SIX_HEX_DIGITS_MASK -} - macro_rules! assert_spanned_err { ($condition:expr, $span_source:expr, $msg:expr, $($args:expr),+) => { if !$condition { diff --git a/library/kani_macros/src/sysroot/contracts/initialize.rs b/library/kani_macros/src/sysroot/contracts/initialize.rs index 73621b2aeec5..50c63173b601 100644 --- a/library/kani_macros/src/sysroot/contracts/initialize.rs +++ b/library/kani_macros/src/sysroot/contracts/initialize.rs @@ -4,8 +4,8 @@ //! Initialization routine for the contract handler use proc_macro::{Diagnostic, TokenStream}; -use proc_macro2::{Ident, TokenStream as TokenStream2}; -use syn::{spanned::Spanned, ItemFn}; +use proc_macro2::TokenStream as TokenStream2; +use syn::ItemFn; use super::{ helpers::{chunks_by, is_token_stream_2_comma, matches_path}, @@ -19,24 +19,9 @@ impl<'a> TryFrom<&'a syn::Attribute> for ContractFunctionState { /// Find out if this attribute could be describing a "contract handling" /// state and if so return it. fn try_from(attribute: &'a syn::Attribute) -> Result { - if let syn::Meta::List(lst) = &attribute.meta { - if matches_path(&lst.path, &["kanitool", "is_contract_generated"]) { - let ident = syn::parse2::(lst.tokens.clone()) - .map_err(|e| Some(lst.span().unwrap().error(format!("{e}"))))?; - let ident_str = ident.to_string(); - return match ident_str.as_str() { - "check" => Ok(Self::Check), - "replace" => Ok(Self::Replace), - "wrapper" => Ok(Self::ModifiesWrapper), - _ => { - Err(Some(lst.span().unwrap().error("Expected `check` or `replace` ident"))) - } - }; - } - } if let syn::Meta::NameValue(nv) = &attribute.meta { if matches_path(&nv.path, &["kanitool", "checked_with"]) { - return Ok(ContractFunctionState::Original); + return Ok(ContractFunctionState::Expanded); } } Err(None) @@ -66,12 +51,10 @@ impl<'a> ContractConditionsHandler<'a> { /// Initialize the handler. Constructs the required /// [`ContractConditionsType`] depending on `is_requires`. pub fn new( - function_state: ContractFunctionState, is_requires: ContractConditionsType, attr: TokenStream, annotated_fn: &'a mut ItemFn, attr_copy: TokenStream2, - hash: Option, ) -> Result { let mut output = TokenStream2::new(); let condition_type = match is_requires { @@ -86,7 +69,23 @@ impl<'a> ContractConditionsHandler<'a> { } }; - Ok(Self { function_state, condition_type, annotated_fn, attr_copy, output, hash }) + let fn_name = &annotated_fn.sig.ident; + let generate_name = |purpose| format!("__kani_{purpose}_{fn_name}"); + let check_name = generate_name("check"); + let replace_name = generate_name("replace"); + let recursion_name = generate_name("recursion_check"); + let modifies_name = generate_name("modifies"); + + Ok(Self { + condition_type, + annotated_fn, + attr_copy, + output, + check_name, + replace_name, + recursion_name, + modify_name: modifies_name, + }) } } impl ContractConditionsData { diff --git a/library/kani_macros/src/sysroot/contracts/mod.rs b/library/kani_macros/src/sysroot/contracts/mod.rs index 12a1215de2c7..db5f30131405 100644 --- a/library/kani_macros/src/sysroot/contracts/mod.rs +++ b/library/kani_macros/src/sysroot/contracts/mod.rs @@ -10,7 +10,7 @@ //! implements a state machine in order to be able to handle multiple attributes //! on the same function correctly. //! -//! ## How the handling for `requires` and `ensures` works. +//! ## How the handling for `requires`, `modifies`, and `ensures` works. //! //! Our aim is to generate a "check" function that can be used to verify the //! validity of the contract and a "replace" function that can be used as a @@ -26,102 +26,48 @@ //! instead of throwing a hard error but this means we cannot detect if a given //! function has further contract attributes placed on it during any given //! expansion. As a result every expansion needs to leave the code in a valid -//! state that could be used for all contract functionality but it must alow +//! state that could be used for all contract functionality, but it must allow //! further contract attributes to compose with what was already generated. In -//! addition we also want to make sure to support non-contract attributes on +//! addition, we also want to make sure to support non-contract attributes on //! functions with contracts. //! -//! To this end we use a state machine. The initial state is an "untouched" -//! function with possibly multiple contract attributes, none of which have been -//! expanded. When we expand the first (outermost) `requires` or `ensures` -//! attribute on such a function we re-emit the function unchanged but we also -//! generate fresh "check" and "replace" functions that enforce the condition -//! carried by the attribute currently being expanded. -//! -//! We don't copy all attributes from the original function since they may have -//! unintended consequences for the stubs, such as `inline` or `rustc_diagnostic_item`. -//! -//! We also add new marker attributes to -//! advance the state machine. The "check" function gets a -//! `kanitool::is_contract_generated(check)` attributes and analogous for -//! replace. The re-emitted original meanwhile is decorated with -//! `kanitool::checked_with(name_of_generated_check_function)` and an analogous -//! `kanittool::replaced_with` attribute also. The next contract attribute that -//! is expanded will detect the presence of these markers in the attributes of -//! the item and be able to determine their position in the state machine this -//! way. If the state is either a "check" or "replace" then the body of the -//! function is augmented with the additional conditions carried by the macro. -//! If the state is the "original" function, no changes are performed. -//! -//! We place marker attributes at the bottom of the attribute stack (innermost), -//! otherwise they would not be visible to the future macro expansions. +//! To this end we generate attributes in a two-phase approach: initial and subsequent expansions. //! -//! Below you can see a graphical rendering where boxes are states and each -//! arrow represents the expansion of a `requires` or `ensures` macro. -//! -//! ```plain -//! │ Start -//! ▼ -//! ┌───────────┐ -//! │ Untouched │ -//! │ Function │ -//! └─────┬─────┘ -//! │ -//! Emit │ Generate + Copy Attributes -//! ┌─────────────────┴─────┬──────────┬─────────────────┐ -//! │ │ │ │ -//! │ │ │ │ -//! ▼ ▼ ▼ ▼ -//! ┌──────────┐ ┌───────────┐ ┌───────┐ ┌─────────┐ -//! │ Original │◄─┐ │ Recursion │ │ Check │◄─┐ │ Replace │◄─┐ -//! └──┬───────┘ │ │ Wrapper │ └───┬───┘ │ └────┬────┘ │ -//! │ │ Ignore └───────────┘ │ │ Augment │ │ Augment -//! └──────────┘ └──────┘ └───────┘ -//! -//! │ │ │ │ -//! └───────────────┘ └─────────────────────────────────────────────┘ -//! -//! Presence of Presence of -//! "checked_with" "is_contract_generated" -//! -//! State is detected via -//! ``` +//! The initial expansion modifies the original function to contains all necessary instrumentation +//! contracts need to be analyzed. It will do the following: +//! 1. Annotate the function with extra `kanitool` attributes +//! 2. Generate closures for each contract processing scenario (recursive check, simple check, +//! replacement, and regular execution). //! -//! All named arguments of the annotated function are unsafely shallow-copied -//! with the `kani::internal::untracked_deref` function to circumvent the borrow checker -//! for postconditions. The case where this is relevant is if you want to return -//! a mutable borrow from the function which means any immutable borrow in the -//! postcondition would be illegal. We must ensure that those copies are not -//! dropped (causing a double-free) so after the postconditions we call -//! `mem::forget` on each copy. +//! Subsequent expansions will detect the existence of the extra `kanitool` attributes, +//! and they will only expand the body of the closures generated in the initial phase. //! -//! ## Check function +//! Note: We place marker attributes at the bottom of the attribute stack (innermost), +//! otherwise they would not be visible to the future macro expansions. //! -//! Generates a `_check_` function that assumes preconditions -//! and asserts postconditions. The check function is also marked as generated +//! ## Check closure +//! +//! Generates a `__kani__check` closure that assumes preconditions +//! and asserts postconditions. The check closure is also marked as generated //! with the `#[kanitool::is_contract_generated(check)]` attribute. //! //! Decorates the original function with `#[kanitool::checked_by = -//! "_check_"]`. +//! "__kani_check_"]`. //! //! The check function is a copy of the original function with preconditions //! added before the body and postconditions after as well as injected before -//! every `return` (see [`PostconditionInjector`]). Attributes on the original -//! function are also copied to the check function. +//! every `return` (see [`PostconditionInjector`]). All arguments are captured +//! by the closure. //! //! ## Replace Function //! -//! As the mirror to that also generates a `_replace_` -//! function that asserts preconditions and assumes postconditions. The replace +//! As the mirror to that also generates a `__kani_replace_` +//! closure that asserts preconditions and assumes postconditions. The replace //! function is also marked as generated with the //! `#[kanitool::is_contract_generated(replace)]` attribute. //! //! Decorates the original function with `#[kanitool::replaced_by = -//! "_replace_"]`. -//! -//! The replace function has the same signature as the original function but its -//! body is replaced by `kani::any()`, which generates a non-deterministic -//! value. +//! "__kani_replace_"]`. //! //! ## Inductive Verification //! @@ -148,26 +94,27 @@ //! flip the tracker variable back to `false` in case the function is called //! more than once in its harness. //! -//! To facilitate all this we generate a `_recursion_wrapper_` -//! function with the following shape: +//! To facilitate all this we generate a `__kani_recursion_check_` +//! closure with the following shape: //! //! ```ignored -//! fn recursion_wrapper_...(fn args ...) { +//! let __kani_recursion_check_func = || { //! static mut REENTRY: bool = false; //! //! if unsafe { REENTRY } { -//! call_replace(fn args...) +//! let __kani_replace_func = || { /* replace body */ } +//! __kani_replace_func() //! } else { //! unsafe { reentry = true }; -//! let result_kani_internal = call_check(fn args...); +//! let __kani_check_func = || { /* check body */ } +//! let result_kani_internal = __kani_check_func(); //! unsafe { reentry = false }; //! result_kani_internal //! } -//! } +//! }; //! ``` //! -//! We register this function as `#[kanitool::checked_with = -//! "recursion_wrapper_..."]` instead of the check function. +//! We register this closure as `#[kanitool::recursion_check = "__kani_recursion_..."]`. //! //! # Complete example //! @@ -180,56 +127,106 @@ //! ``` //! //! Turns into -//! //! ``` -//! #[kanitool::checked_with = "div_recursion_wrapper_965916"] -//! #[kanitool::replaced_with = "div_replace_965916"] -//! fn div(dividend: u32, divisor: u32) -> u32 { dividend / divisor } -//! -//! #[allow(dead_code, unused_variables)] -//! #[kanitool :: is_contract_generated(check)] fn -//! div_check_b97df2(dividend : u32, divisor : u32) -> u32 -//! { -//! let dividend_renamed = kani::internal::untracked_deref(& dividend); -//! let divisor_renamed = kani::internal::untracked_deref(& divisor); -//! kani::assume(divisor != 0); -//! let result_kani_internal : u32 = div_wrapper_b97df2(dividend, divisor); -//! kani::assert( -//! (| result : & u32 | *result <= dividend_renamed)(& result_kani_internal), -//! stringify!(|result : &u32| *result <= dividend)); -//! std::mem::forget(dividend_renamed); -//! std::mem::forget(divisor_renamed); -//! result_kani_internal -//! } -//! -//! #[allow(dead_code, unused_variables)] -//! #[kanitool :: is_contract_generated(replace)] fn -//! div_replace_b97df2(dividend : u32, divisor : u32) -> u32 -//! { -//! let divisor_renamed = kani::internal::untracked_deref(& divisor); -//! let dividend_renamed = kani::internal::untracked_deref(& dividend); -//! kani::assert(divisor != 0, stringify! (divisor != 0)); -//! let result_kani_internal : u32 = kani::any_modifies(); -//! kani::assume( -//! (|result : & u32| *result <= dividend_renamed)(&result_kani_internal)); -//! std::mem::forget(divisor_renamed); -//! std::mem::forget(dividend_renamed); -//! result_kani_internal -//! } -//! -//! #[allow(dead_code)] -//! #[allow(unused_variables)] -//! #[kanitool::is_contract_generated(recursion_wrapper)] -//! fn div_recursion_wrapper_965916(dividend: u32, divisor: u32) -> u32 { -//! static mut REENTRY: bool = false; -//! -//! if unsafe { REENTRY } { -//! div_replace_b97df2(dividend, divisor) -//! } else { -//! unsafe { reentry = true }; -//! let result_kani_internal = div_check_b97df2(dividend, divisor); -//! unsafe { reentry = false }; -//! result_kani_internal +//! #[kanitool::recursion_check = "__kani_recursion_check_div"] +//! #[kanitool::checked_with = "__kani_check_div"] +//! #[kanitool::replaced_with = "__kani_replace_div"] +//! #[kanitool::modifies_wrapper = "__kani_modifies_div"] +//! fn div(dividend: u32, divisor: u32) -> u32 { +//! #[inline(never)] +//! #[kanitool::fn_marker = "kani_register_contract"] +//! pub const fn kani_register_contract T>(f: F) -> T { +//! kani::panic("internal error: entered unreachable code: ") +//! } +//! let kani_contract_mode = kani::internal::mode(); +//! match kani_contract_mode { +//! kani::internal::RECURSION_CHECK => { +//! #[kanitool::is_contract_generated(recursion_check)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_recursion_check_div = +//! || -> u32 +//! { +//! #[kanitool::recursion_tracker] +//! static mut REENTRY: bool = false; +//! if unsafe { REENTRY } { +//! #[kanitool::is_contract_generated(replace)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_replace_div = +//! || -> u32 +//! { +//! kani::assert(divisor != 0, "divisor != 0"); +//! let result_kani_internal: u32 = kani::any_modifies(); +//! kani::assume(kani::internal::apply_closure(|result: &u32| +//! *result <= dividend, &result_kani_internal)); +//! result_kani_internal +//! }; +//! __kani_replace_div() +//! } else { +//! unsafe { REENTRY = true }; +//! #[kanitool::is_contract_generated(check)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! 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 +//! }; +//! let result_kani_internal = __kani_check_div(); +//! unsafe { REENTRY = false }; +//! result_kani_internal +//! } +//! }; +//! ; +//! kani_register_contract(__kani_recursion_check_div) +//! } +//! kani::internal::REPLACE => { +//! #[kanitool::is_contract_generated(replace)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_replace_div = +//! || -> u32 +//! { +//! kani::assert(divisor != 0, "divisor != 0"); +//! let result_kani_internal: u32 = kani::any_modifies(); +//! kani::assume(kani::internal::apply_closure(|result: &u32| +//! *result <= dividend, &result_kani_internal)); +//! result_kani_internal +//! }; +//! ; +//! kani_register_contract(__kani_replace_div) +//! } +//! kani::internal::SIMPLE_CHECK => { +//! #[kanitool::is_contract_generated(check)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! 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 +//! }; +//! ; +//! kani_register_contract(__kani_check_div) +//! } +//! _ => { dividend / divisor } //! } //! } //! ``` @@ -264,78 +261,140 @@ //! This expands to //! //! ``` -//! #[kanitool::checked_with = "modify_recursion_wrapper_633496"] -//! #[kanitool::replaced_with = "modify_replace_633496"] -//! #[kanitool::inner_check = "modify_wrapper_633496"] -//! fn modify(ptr: &mut u32) { { *ptr += 1; } } -//! #[allow(dead_code, unused_variables, unused_mut)] -//! #[kanitool::is_contract_generated(recursion_wrapper)] -//! fn modify_recursion_wrapper_633496(arg0: &mut u32) { -//! static mut REENTRY: bool = false; -//! if unsafe { REENTRY } { -//! modify_replace_633496(arg0) -//! } else { -//! unsafe { REENTRY = true }; -//! let result_kani_internal = modify_check_633496(arg0); -//! unsafe { REENTRY = false }; -//! result_kani_internal -//! } -//! } -//! #[allow(dead_code, unused_variables, unused_mut)] -//! #[kanitool::is_contract_generated(check)] -//! fn modify_check_633496(ptr: &mut u32) { -//! let _wrapper_arg_1 = -//! unsafe { kani::internal::Pointer::decouple_lifetime(&ptr) }; -//! kani::assume(*ptr < 100); -//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; -//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; -//! let result_kani_internal: () = modify_wrapper_633496(ptr, _wrapper_arg_1); -//! kani::assert((|result| -//! (remember_kani_internal_92cc419d8aca576c) == -//! *ptr)(&result_kani_internal), -//! "|result| old(*ptr + 1) == *ptr"); -//! kani::assert((|result| -//! (remember_kani_internal_92cc419d8aca576c) == -//! *ptr)(&result_kani_internal), -//! "|result| old(*ptr + 1) == *ptr"); -//! result_kani_internal -//! } -//! #[allow(dead_code, unused_variables, unused_mut)] -//! #[kanitool::is_contract_generated(replace)] -//! fn modify_replace_633496(ptr: &mut u32) { -//! kani::assert(*ptr < 100, "*ptr < 100"); -//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; -//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; -//! let result_kani_internal: () = kani::any_modifies(); -//! *unsafe { -//! kani::internal::Pointer::assignable(kani::internal::untracked_deref(&(ptr))) -//! } = kani::any_modifies(); -//! kani::assume((|result| -//! (remember_kani_internal_92cc419d8aca576c) == -//! *ptr)(&result_kani_internal)); -//! kani::assume((|result| -//! (remember_kani_internal_92cc419d8aca576c) == -//! *ptr)(&result_kani_internal)); -//! result_kani_internal -//! } -//! #[kanitool::modifies(_wrapper_arg_1)] -//! #[allow(dead_code, unused_variables, unused_mut)] -//! #[kanitool::is_contract_generated(wrapper)] -//! fn modify_wrapper_633496<'_wrapper_arg_1, -//! WrapperArgType1>(ptr: &mut u32, _wrapper_arg_1: &WrapperArgType1) { -//! *ptr += 1; -//! } -//! #[allow(dead_code)] -//! #[kanitool::proof_for_contract = "modify"] -//! fn main() { -//! kani::internal::init_contracts(); -//! { let mut i = kani::any(); modify(&mut i); } +//! #[kanitool::recursion_check = "__kani_recursion_check_modify"] +//! #[kanitool::checked_with = "__kani_check_modify"] +//! #[kanitool::replaced_with = "__kani_replace_modify"] +//! #[kanitool::modifies_wrapper = "__kani_modifies_modify"] +//! fn modify(ptr: &mut u32) { +//! #[inline(never)] +//! #[kanitool::fn_marker = "kani_register_contract"] +//! pub const fn kani_register_contract T>(f: F) -> T { +//! kani::panic("internal error: entered unreachable code: ") +//! } +//! let kani_contract_mode = kani::internal::mode(); +//! match kani_contract_mode { +//! kani::internal::RECURSION_CHECK => { +//! #[kanitool::is_contract_generated(recursion_check)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_recursion_check_modify = +//! || +//! { +//! #[kanitool::recursion_tracker] +//! static mut REENTRY: bool = false; +//! if unsafe { REENTRY } { +//! #[kanitool::is_contract_generated(replace)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_replace_modify = +//! || +//! { +//! kani::assert(*ptr < 100, "*ptr < 100"); +//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; +//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; +//! let result_kani_internal: () = kani::any_modifies(); +//! *unsafe { +//! kani::internal::Pointer::assignable(kani::internal::untracked_deref(&(ptr))) +//! } = kani::any_modifies(); +//! kani::assume(kani::internal::apply_closure(|result| +//! (remember_kani_internal_92cc419d8aca576c) == *ptr, +//! &result_kani_internal)); +//! kani::assume(kani::internal::apply_closure(|result| +//! (remember_kani_internal_92cc419d8aca576c) == *ptr, +//! &result_kani_internal)); +//! result_kani_internal +//! }; +//! __kani_replace_modify() +//! } else { +//! unsafe { REENTRY = true }; +//! #[kanitool::is_contract_generated(check)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_check_modify = +//! || +//! { +//! kani::assume(*ptr < 100); +//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; +//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; +//! let _wrapper_arg = (ptr as *const _,); +//! #[kanitool::is_contract_generated(wrapper)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_modifies_modify = +//! |_wrapper_arg| { *ptr += 1; }; +//! let result_kani_internal: () = +//! __kani_modifies_modify(_wrapper_arg); +//! kani::assert(kani::internal::apply_closure(|result| +//! (remember_kani_internal_92cc419d8aca576c) == *ptr, +//! &result_kani_internal), "|result| old(*ptr + 1) == *ptr"); +//! kani::assert(kani::internal::apply_closure(|result| +//! (remember_kani_internal_92cc419d8aca576c) == *ptr, +//! &result_kani_internal), "|result| old(*ptr + 1) == *ptr"); +//! result_kani_internal +//! }; +//! let result_kani_internal = __kani_check_modify(); +//! unsafe { REENTRY = false }; +//! result_kani_internal +//! } +//! }; +//! ; +//! kani_register_contract(__kani_recursion_check_modify) +//! } +//! kani::internal::REPLACE => { +//! #[kanitool::is_contract_generated(replace)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_replace_modify = +//! || +//! { +//! kani::assert(*ptr < 100, "*ptr < 100"); +//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; +//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; +//! let result_kani_internal: () = kani::any_modifies(); +//! *unsafe { +//! kani::internal::Pointer::assignable(kani::internal::untracked_deref(&(ptr))) +//! } = kani::any_modifies(); +//! kani::assume(kani::internal::apply_closure(|result| +//! (remember_kani_internal_92cc419d8aca576c) == *ptr, +//! &result_kani_internal)); +//! kani::assume(kani::internal::apply_closure(|result| +//! (remember_kani_internal_92cc419d8aca576c) == *ptr, +//! &result_kani_internal)); +//! result_kani_internal +//! }; +//! ; +//! kani_register_contract(__kani_replace_modify) +//! } +//! kani::internal::SIMPLE_CHECK => { +//! #[kanitool::is_contract_generated(check)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_check_modify = +//! || +//! { +//! kani::assume(*ptr < 100); +//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; +//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1; +//! let _wrapper_arg = (ptr as *const _,); +//! #[kanitool::is_contract_generated(wrapper)] +//! #[allow(dead_code, unused_variables, unused_mut)] +//! let mut __kani_modifies_modify = +//! |_wrapper_arg| { *ptr += 1; }; +//! let result_kani_internal: () = +//! __kani_modifies_modify(_wrapper_arg); +//! kani::assert(kani::internal::apply_closure(|result| +//! (remember_kani_internal_92cc419d8aca576c) == *ptr, +//! &result_kani_internal), "|result| old(*ptr + 1) == *ptr"); +//! kani::assert(kani::internal::apply_closure(|result| +//! (remember_kani_internal_92cc419d8aca576c) == *ptr, +//! &result_kani_internal), "|result| old(*ptr + 1) == *ptr"); +//! result_kani_internal +//! }; +//! ; +//! kani_register_contract(__kani_check_modify) +//! } +//! _ => { *ptr += 1; } +//! } //! } //! ``` use proc_macro::TokenStream; use proc_macro2::{Ident, TokenStream as TokenStream2}; -use quote::{quote, ToTokens}; +use quote::quote; use syn::{parse_macro_input, parse_quote, Expr, ExprClosure, ItemFn}; mod bootstrap; @@ -397,36 +456,35 @@ pub fn proof_for_contract(attr: TokenStream, item: TokenStream) -> TokenStream { .into() } -/// Classifies the state a function is in in the contract handling pipeline. +/// Classifies the state a function is in the contract handling pipeline. #[derive(Clone, Copy, PartialEq, Eq)] enum ContractFunctionState { - /// This is the original code, re-emitted from a contract attribute. - Original, + /// This is the function already expanded with the closures. + Expanded, /// This is the first time a contract attribute is evaluated on this /// function. Untouched, - /// This is a check function that was generated from a previous evaluation - /// of a contract attribute. - Check, - /// This is a replace function that was generated from a previous evaluation - /// of a contract attribute. - Replace, - ModifiesWrapper, } /// The information needed to generate the bodies of check and replacement /// functions that integrate the conditions from this contract attribute. struct ContractConditionsHandler<'a> { - function_state: ContractFunctionState, /// Information specific to the type of contract attribute we're expanding. condition_type: ContractConditionsData, /// Body of the function this attribute was found on. - annotated_fn: &'a mut ItemFn, + annotated_fn: &'a ItemFn, /// An unparsed, unmodified copy of `attr`, used in the error messages. attr_copy: TokenStream2, /// The stream to which we should write the generated code. output: TokenStream2, - hash: Option, + /// The name of the check closure. + check_name: String, + /// The name of the replace closure. + replace_name: String, + /// The name of the recursion closure. + recursion_name: String, + /// The name of the modifies closure. + modify_name: String, } /// Which kind of contract attribute are we dealing with? @@ -460,23 +518,8 @@ impl<'a> ContractConditionsHandler<'a> { /// Handle the contract state and return the generated code fn dispatch_on(mut self, state: ContractFunctionState) -> TokenStream2 { match state { - ContractFunctionState::ModifiesWrapper => self.emit_augmented_modifies_wrapper(), - ContractFunctionState::Check => { - // The easy cases first: If we are on a check or replace function - // emit them again but with additional conditions layered on. - // - // Since we are already on the check function, it will have an - // appropriate, unique generated name which we are just going to - // pass on. - self.emit_check_function(None); - } - ContractFunctionState::Replace => { - // Analogous to above - self.emit_replace_function(None); - } - ContractFunctionState::Original => { - unreachable!("Impossible: This is handled via short circuiting earlier.") - } + // We are on the already expanded function. + ContractFunctionState::Expanded => self.handle_expanded(), ContractFunctionState::Untouched => self.handle_untouched(), } self.output @@ -493,35 +536,9 @@ fn contract_main( is_requires: ContractConditionsType, ) -> TokenStream { let attr_copy = TokenStream2::from(attr.clone()); - - let item_stream_clone = item.clone(); let mut item_fn = parse_macro_input!(item as ItemFn); - let function_state = ContractFunctionState::from_attributes(&item_fn.attrs); - - if matches!(function_state, ContractFunctionState::Original) { - // If we're the original function that means we're *not* the first time - // that a contract attribute is handled on this function. This means - // there must exist a generated check function somewhere onto which the - // attributes have been copied and where they will be expanded into more - // checks. So we just return ourselves unchanged. - // - // Since this is the only function state case that doesn't need a - // handler to be constructed, we do this match early, separately. - return item_fn.into_token_stream().into(); - } - - let hash = matches!(function_state, ContractFunctionState::Untouched) - .then(|| helpers::short_hash_of_token_stream(&item_stream_clone)); - - let handler = match ContractConditionsHandler::new( - function_state, - is_requires, - attr, - &mut item_fn, - attr_copy, - hash, - ) { + let handler = match ContractConditionsHandler::new(is_requires, attr, &mut item_fn, attr_copy) { Ok(handler) => handler, Err(e) => return e.into_compile_error().into(), }; diff --git a/library/kani_macros/src/sysroot/contracts/replace.rs b/library/kani_macros/src/sysroot/contracts/replace.rs index 280839dcafca..02ac4a772348 100644 --- a/library/kani_macros/src/sysroot/contracts/replace.rs +++ b/library/kani_macros/src/sysroot/contracts/replace.rs @@ -3,8 +3,10 @@ //! Logic used for generating the code that replaces a function with its contract. -use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; +use std::mem; +use syn::Stmt; use super::{ helpers::*, @@ -13,6 +15,13 @@ use super::{ }; impl<'a> ContractConditionsHandler<'a> { + /// Create initial set of replace statements which is the return havoc. + fn initial_replace_stmts(&self) -> Vec { + let return_type = return_type_to_type(&self.annotated_fn.sig.output); + let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); + vec![syn::parse_quote!(let #result : #return_type = kani::any_modifies();)] + } + /// Split an existing replace body of the form /// /// ```ignore @@ -35,26 +44,21 @@ impl<'a> ContractConditionsHandler<'a> { /// Such that the first vector contains everything up to and including the single result havoc /// and the second one the rest, excluding the return. /// - /// If this is the first time we're emitting replace we create the return havoc and nothing else. - fn ensure_bootstrapped_replace_body(&self) -> (Vec, Vec) { - if self.is_first_emit() { - let return_type = return_type_to_type(&self.annotated_fn.sig.output); - let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); - (vec![syn::parse_quote!(let #result : #return_type = kani::any_modifies();)], vec![]) - } else { - let stmts = &self.annotated_fn.block.stmts; - let idx = stmts - .iter() - .enumerate() - .find_map(|(i, elem)| is_replace_return_havoc(elem).then_some(i)) - .unwrap_or_else(|| { - panic!("ICE: Could not find result let binding in statement sequence") - }); - // We want the result assign statement to end up as the last statement in the first - // vector, hence the `+1`. - let (before, after) = stmts.split_at(idx + 1); - (before.to_vec(), after.split_last().unwrap().1.to_vec()) - } + fn split_replace(&self, mut stmts: Vec) -> (Vec, Vec) { + // Pop the return result since we always re-add it. + stmts.pop(); + + let idx = stmts + .iter() + .enumerate() + .find_map(|(i, elem)| is_replace_return_havoc(elem).then_some(i)) + .unwrap_or_else(|| { + panic!("ICE: Could not find result let binding in statement sequence") + }); + // We want the result assign statement to end up as the last statement in the first + // vector, hence the `+1`. + let (before, after) = stmts.split_at_mut(idx + 1); + (before.to_vec(), after.to_vec()) } /// Create the body of a stub for this contract. @@ -65,69 +69,65 @@ impl<'a> ContractConditionsHandler<'a> { /// /// `use_nondet_result` will only be true if this is the first time we are /// generating a replace function. - fn make_replace_body(&self) -> TokenStream2 { - let (before, after) = self.ensure_bootstrapped_replace_body(); - + fn expand_replace_body(&self, before: &[Stmt], after: &[Stmt]) -> TokenStream { match &self.condition_type { ContractConditionsData::Requires { attr } => { let Self { attr_copy, .. } = self; let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); - quote!( + quote!({ kani::assert(#attr, stringify!(#attr_copy)); #(#before)* #(#after)* #result - ) + }) } ContractConditionsData::Ensures { attr } => { let (remembers, ensures_clause) = build_ensures(attr); let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); - quote!( + quote!({ #remembers #(#before)* #(#after)* kani::assume(#ensures_clause); #result - ) + }) } ContractConditionsData::Modifies { attr } => { let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); - quote!( + quote!({ #(#before)* #(unsafe{kani::internal::write_any(kani::internal::Pointer::assignable(kani::internal::untracked_deref(&#attr)))};)* #(#after)* #result - ) + }) } } } - /// Emit the replace funtion into the output stream. + /// Emit the replace function into the output stream. /// - /// See [`Self::make_replace_body`] for the most interesting parts of this + /// See [`Self::expand_replace_body`] for the most interesting parts of this /// function. - pub fn emit_replace_function(&mut self, override_function_ident: Option) { - self.emit_common_header(); + pub fn replace_closure(&self) -> TokenStream { + let replace_ident = Ident::new(&self.replace_name, Span::call_site()); + let sig = &self.annotated_fn.sig; + let output = &sig.output; + let before = self.initial_replace_stmts(); + let body = self.expand_replace_body(&before, &vec![]); - if self.function_state.emit_tag_attr() { - // If it's the first time we also emit this marker. Again, order is - // important so this happens as the last emitted attribute. - self.output.extend(quote!(#[kanitool::is_contract_generated(replace)])); - } - let mut sig = self.annotated_fn.sig.clone(); - // We use non-constant functions, thus, the wrapper cannot be constant. - sig.constness = None; - let body = self.make_replace_body(); - if let Some(ident) = override_function_ident { - sig.ident = ident; - } + quote!( + #[kanitool::is_contract_generated(replace)] + #[allow(dead_code, unused_variables, unused_mut)] + let mut #replace_ident = || #output #body; + ) + } - // Finally emit the check function itself. - self.output.extend(quote!( - #sig { - #body - } - )); + /// Expand the `replace` body with the new attribute. + pub fn expand_replace(&self, closure: &mut Stmt) { + let body = closure_body(closure); + let (before, after) = self.split_replace(mem::take(&mut body.block.stmts)); + let stream = self.expand_replace_body(&before, &after); + *body = syn::parse2(stream).unwrap(); } } diff --git a/library/kani_macros/src/sysroot/contracts/shared.rs b/library/kani_macros/src/sysroot/contracts/shared.rs index 1ab791d9a117..f189a9f98b0c 100644 --- a/library/kani_macros/src/sysroot/contracts/shared.rs +++ b/library/kani_macros/src/sysroot/contracts/shared.rs @@ -10,96 +10,11 @@ use std::collections::HashMap; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; -use quote::{quote, ToTokens}; +use quote::quote; use std::hash::{DefaultHasher, Hash, Hasher}; -use syn::{ - spanned::Spanned, visit_mut::VisitMut, Attribute, Expr, ExprCall, ExprClosure, ExprPath, Path, -}; +use syn::{spanned::Spanned, visit_mut::VisitMut, Expr, ExprCall, ExprClosure, ExprPath, Path}; -use super::{ContractConditionsHandler, ContractFunctionState, INTERNAL_RESULT_IDENT}; - -impl ContractFunctionState { - /// Do we need to emit the `is_contract_generated` tag attribute on the - /// generated function(s)? - pub fn emit_tag_attr(self) -> bool { - matches!(self, ContractFunctionState::Untouched) - } -} - -impl<'a> ContractConditionsHandler<'a> { - pub fn is_first_emit(&self) -> bool { - matches!(self.function_state, ContractFunctionState::Untouched) - } - - /// Create a new name for the assigns wrapper function *or* get the name of - /// the wrapper we must have already generated. This is so that we can - /// recognize a call to that wrapper inside the check function. - pub fn make_wrapper_name(&self) -> Ident { - if let Some(hash) = self.hash { - identifier_for_generated_function(&self.annotated_fn.sig.ident, "wrapper", hash) - } else { - let str_name = self.annotated_fn.sig.ident.to_string(); - let splits = str_name.rsplitn(3, '_').collect::>(); - let [hash, _, base] = splits.as_slice() else { - unreachable!("Odd name for function {str_name}, splits were {}", splits.len()); - }; - - Ident::new(&format!("{base}_wrapper_{hash}"), Span::call_site()) - } - } - - /// Emit attributes common to check or replace function into the output - /// stream. - pub fn emit_common_header(&mut self) { - if self.function_state.emit_tag_attr() { - self.output.extend(quote!( - #[allow(dead_code, unused_variables, unused_mut)] - )); - } - - #[cfg(not(feature = "no_core"))] - self.output.extend(self.annotated_fn.attrs.iter().flat_map(Attribute::to_token_stream)); - - // When verifying core and standard library, we need to add an unstable attribute to - // the functions generated by Kani. - // We also need to filter `rustc_diagnostic_item` attribute. - // We should consider a better strategy than just duplicating all attributes. - #[cfg(feature = "no_core")] - { - self.output.extend(quote!( - #[unstable(feature="kani", issue="none")] - )); - self.output.extend( - self.annotated_fn - .attrs - .iter() - .filter(|attr| { - if let Some(ident) = attr.path().get_ident() { - let name = ident.to_string(); - !name.starts_with("rustc") - && !(name == "stable") - && !(name == "unstable") - } else { - true - } - }) - .flat_map(Attribute::to_token_stream), - ); - } - } -} - -/// Makes consistent names for a generated function which was created for -/// `purpose`, from an attribute that decorates `related_function` with the -/// hash `hash`. -pub fn identifier_for_generated_function( - related_function_name: &Ident, - purpose: &str, - hash: u64, -) -> Ident { - let identifier = format!("{}_{purpose}_{hash:x}", related_function_name); - Ident::new(&identifier, proc_macro2::Span::mixed_site()) -} +use super::INTERNAL_RESULT_IDENT; /// Used as the "single source of truth" for [`try_as_result_assign`] and [`try_as_result_assign_mut`] /// since we can't abstract over mutability. Input is the object to match on and the name of the @@ -147,19 +62,6 @@ pub fn try_as_result_assign(stmt: &syn::Stmt) -> Option<&syn::LocalInit> { try_as_result_assign_pat!(stmt, as_ref) } -/// Try to parse this statement as `let result : <...> = ;` and return a mutable reference to -/// `init`. -/// -/// This is the shape of statement we create in check functions (with `init` being a call to check -/// function with additional pointer arguments for the `modifies` clause) and we need to recognize -/// it to then edit this call if we find another `modifies` clause and add its additional arguments. -/// additional conditions. -/// -/// It's a thin wrapper around [`try_as_result_assign_pat!`] to create a mutable match. -pub fn try_as_result_assign_mut(stmt: &mut syn::Stmt) -> Option<&mut syn::LocalInit> { - try_as_result_assign_pat!(stmt, as_mut) -} - /// When a `#[kani::ensures(|result|expr)]` is expanded, this function is called on with `build_ensures(|result|expr)`. /// This function goes through the expr and extracts out all the `old` expressions and creates a sequence /// of statements that instantiate these expressions as `let remember_kani_internal_x = old_expr;` diff --git a/tests/expected/function-contract/gcd_rec_replacement_pass.expected b/tests/expected/function-contract/gcd_rec_replacement_pass.expected index 29c61cf9437c..b522716cc001 100644 --- a/tests/expected/function-contract/gcd_rec_replacement_pass.expected +++ b/tests/expected/function-contract/gcd_rec_replacement_pass.expected @@ -10,7 +10,7 @@ Frac::check_equals.assertion\ - Status: SUCCESS\ - Description: "assertion failed: gcd1 == gcd2" -gcd.assertion\ +.assertion\ - Status: SUCCESS\ - Description: "x != 0 && y != 0" diff --git a/tests/expected/function-contract/gcd_replacement_pass.expected b/tests/expected/function-contract/gcd_replacement_pass.expected index 48d3565ef9f1..1c6df8455267 100644 --- a/tests/expected/function-contract/gcd_replacement_pass.expected +++ b/tests/expected/function-contract/gcd_replacement_pass.expected @@ -1,4 +1,4 @@ -gcd.assertion\ +.assertion\ - Status: SUCCESS\ - Description: "x != 0 && y != 0" diff --git a/tests/expected/function-contract/modifies/global_fail.expected b/tests/expected/function-contract/modifies/global_fail.expected index 04e0359ad8a4..a9ecaf689376 100644 --- a/tests/expected/function-contract/modifies/global_fail.expected +++ b/tests/expected/function-contract/modifies/global_fail.expected @@ -1,9 +1,9 @@ assigns\ - Status: FAILURE\ -- Description: "Check that *var_1 is assignable"\ -in function modify_wrapper +- Description: "Check that *var_3 is assignable"\ +in function modify -Failed Checks: Check that *var_1 is assignable\ -in modify_wrapper +Failed Checks: Check that *var_3 is assignable\ +in modify VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/havoc_pass.expected b/tests/expected/function-contract/modifies/havoc_pass.expected index 02ce972ef370..d5679f71b74c 100644 --- a/tests/expected/function-contract/modifies/havoc_pass.expected +++ b/tests/expected/function-contract/modifies/havoc_pass.expected @@ -1,38 +1,13 @@ -copy_replace.assertion\ +.assertion\ - Status: SUCCESS\ - Description: "equality"\ in function copy_replace VERIFICATION:- SUCCESSFUL -copy.assigns\ +.assigns\ - Status: SUCCESS\ - Description: "Check that var_4 is assignable"\ in function copy -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that var_3 is assignable"\ -in function copy\ - -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that var_5 is assignable"\ -in function copy - -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that *var_5 is assignable"\ -in function copy\ - -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that var_7 is assignable"\ -in function copy - -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that *var_7 is assignable"\ -in function copy - VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/havoc_pass_reordered.expected b/tests/expected/function-contract/modifies/havoc_pass_reordered.expected index 02ce972ef370..2ea601aa26a0 100644 --- a/tests/expected/function-contract/modifies/havoc_pass_reordered.expected +++ b/tests/expected/function-contract/modifies/havoc_pass_reordered.expected @@ -5,34 +5,9 @@ in function copy_replace VERIFICATION:- SUCCESSFUL -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that var_4 is assignable"\ -in function copy - -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that var_3 is assignable"\ -in function copy\ - copy.assigns\ - Status: SUCCESS\ - Description: "Check that var_5 is assignable"\ in function copy -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that *var_5 is assignable"\ -in function copy\ - -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that var_7 is assignable"\ -in function copy - -copy.assigns\ -- Status: SUCCESS\ -- Description: "Check that *var_7 is assignable"\ -in function copy - VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/simple_fail.expected b/tests/expected/function-contract/modifies/simple_fail.expected index ffaee2293931..269ffa6cc2e2 100644 --- a/tests/expected/function-contract/modifies/simple_fail.expected +++ b/tests/expected/function-contract/modifies/simple_fail.expected @@ -1,7 +1,7 @@ assigns\ - Status: FAILURE\ -- Description: "Check that *ptr is assignable" +- Description: "Check that *var_6 is assignable" -Failed Checks: Check that *ptr is assignable +Failed Checks: Check that *var_6 is assignable VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/vec_pass.expected b/tests/expected/function-contract/modifies/vec_pass.expected index 4d5407fdd850..4ac03d025558 100644 --- a/tests/expected/function-contract/modifies/vec_pass.expected +++ b/tests/expected/function-contract/modifies/vec_pass.expected @@ -1,17 +1,17 @@ -modify.assertion\ +.assertion\ - Status: SUCCESS\ - Description: "v.len() > 0"\ in function modify -modify_replace.assertion\ +.assertion\ - Status: SUCCESS\ - Description: "element set"\ -in function modify_replace +in function modify -modify_replace.assertion\ +.assertion\ - Status: SUCCESS\ - Description: "vector tail equality"\ -in function modify_replace +in function modify assertion\ - Status: SUCCESS\ diff --git a/tests/expected/function-contract/pattern_use.rs b/tests/expected/function-contract/pattern_use.rs index ead1c1538b4d..b02cc776d299 100644 --- a/tests/expected/function-contract/pattern_use.rs +++ b/tests/expected/function-contract/pattern_use.rs @@ -1,9 +1,10 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Zfunction-contracts +//! Test that contracts supports function args with pattern use and mut declaration. #[kani::ensures(|result : &u32| *result <= dividend)] -fn div((dividend, divisor): (u32, u32)) -> u32 { +fn div((mut dividend, divisor): (u32, u32)) -> u32 { dividend / divisor } diff --git a/tests/expected/function-contract/simple_replace_pass.expected b/tests/expected/function-contract/simple_replace_pass.expected index e1fc78ca462f..7dbc9bae68bd 100644 --- a/tests/expected/function-contract/simple_replace_pass.expected +++ b/tests/expected/function-contract/simple_replace_pass.expected @@ -1,4 +1,4 @@ -div.assertion\ +.assertion\ - Status: SUCCESS\ - Description: "divisor != 0" diff --git a/tests/expected/function-contract/trait_impls/associated_fn.expected b/tests/expected/function-contract/trait_impls/associated_fn.expected new file mode 100644 index 000000000000..c6f4c6382f17 --- /dev/null +++ b/tests/expected/function-contract/trait_impls/associated_fn.expected @@ -0,0 +1,7 @@ +Checking harness check_foo_b... +VERIFICATION:- SUCCESSFUL + +Checking harness check_foo_a... +VERIFICATION:- SUCCESSFUL + +Complete - 2 successfully verified harnesses, 0 failures, 2 total diff --git a/tests/expected/function-contract/trait_impls/associated_fn.rs b/tests/expected/function-contract/trait_impls/associated_fn.rs new file mode 100644 index 000000000000..807469bcc7e3 --- /dev/null +++ b/tests/expected/function-contract/trait_impls/associated_fn.rs @@ -0,0 +1,36 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +//! Check that we can add contracts to associated functions. + +extern crate kani; + +#[derive(PartialEq, Eq)] +enum Foo { + A(u8), + B(char), +} + +impl Foo { + #[kani::ensures(|result| *result == Foo::A(inner))] + pub fn new_a(inner: u8) -> Foo { + Foo::A(inner) + } + + #[kani::requires(char::from_u32(inner).is_some())] + #[kani::ensures(|result| matches!(*result, Foo::B(c) if u32::from(c) == inner))] + pub unsafe fn new_b(inner: u32) -> Foo { + Foo::B(char::from_u32_unchecked(inner)) + } +} + +#[kani::proof_for_contract(Foo::new_a)] +fn check_foo_a() { + let _ = Foo::new_a(kani::any()); +} + +#[kani::proof_for_contract(Foo::new_b)] +fn check_foo_b() { + let _ = unsafe { Foo::new_b(kani::any()) }; +} diff --git a/tests/expected/function-contract/trait_impls/methods.expected b/tests/expected/function-contract/trait_impls/methods.expected new file mode 100644 index 000000000000..3906a6fd7a10 --- /dev/null +++ b/tests/expected/function-contract/trait_impls/methods.expected @@ -0,0 +1,17 @@ +Checking harness check_next_y... + +Status: SUCCESS\ +Description: "|result| result.y == old(self.y) + 1"\ +in function Point::next_y + +VERIFICATION:- SUCCESSFUL + +Checking harness check_add_x... + +Status: SUCCESS\ +Description: "|_| val < 0 || self.x >= old(self.x)"\ +in function Point::add_x + +VERIFICATION:- SUCCESSFUL + +Complete - 2 successfully verified harnesses, 0 failures, 2 total. diff --git a/tests/expected/function-contract/trait_impls/methods.rs b/tests/expected/function-contract/trait_impls/methods.rs new file mode 100644 index 000000000000..128710f1436d --- /dev/null +++ b/tests/expected/function-contract/trait_impls/methods.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Modifications Copyright Kani Contributors +// See GitHub history for details. +// kani-flags: -Zfunction-contracts + +//! Check that we can add contract to methods and trait implementations. +//! Original code taken from: +//! + +use std::ops::Add; + +#[derive(Debug, Copy, Clone, PartialEq, kani::Arbitrary)] +struct Point { + x: i32, + y: i32, +} + +impl Add for Point { + type Output = Self; + + #[kani::requires(!self.x.overflowing_add(other.x).1)] + #[kani::requires(!self.y.overflowing_add(other.y).1)] + #[kani::ensures(|result| result.x == self.x + other.x)] + #[kani::ensures(|result| result.y == self.y + other.y)] + fn add(self, other: Self) -> Self { + Self { x: self.x + other.x, y: self.y + other.y } + } +} + +impl Point { + #[kani::modifies(&mut self.x)] + #[kani::requires(!self.x.overflowing_add(val).1)] + #[kani::ensures(|_| val < 0 || self.x >= old(self.x))] + #[kani::ensures(|_| val > 0 || self.x <= old(self.x))] + pub fn add_x(&mut self, val: i32) { + self.x += val; + } + + #[kani::requires(self.y < i32::MAX)] + #[kani::ensures(|result| result.y == old(self.y) + 1)] + pub fn next_y(mut self) -> Self { + self.y += 1; + self + } +} + +#[kani::proof_for_contract(Point::add_x)] +fn check_add_x() { + let mut p1: Point = kani::any(); + let _ = p1.add_x(kani::any()); +} + +#[kani::proof_for_contract(Point::next_y)] +fn check_next_y() { + let p1: Point = kani::any(); + let _ = p1.next_y(); +} + +/// We should enable this once we add support to specifying trait methods: +/// +#[cfg(ignore)] +#[kani::proof_for_contract(Point::add)] +fn check_add() { + let (p1, p2): (Point, Point) = kani::any(); + let _ = p1.add(p2); +} diff --git a/tests/kani/FunctionContracts/fn_params.rs b/tests/kani/FunctionContracts/fn_params.rs new file mode 100644 index 000000000000..b485ddf2b32b --- /dev/null +++ b/tests/kani/FunctionContracts/fn_params.rs @@ -0,0 +1,73 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Checks that function contracts work with different type of parameter expressions: +//! Source: +//! +//! Note: See `receiver_contracts` for receiver parameters. +// kani-flags: -Zfunction-contracts + +extern crate kani; +use std::convert::TryFrom; + +/// Dummy structure to check different patterns in contract. +#[derive(Copy, Clone, PartialEq, Eq, kani::Arbitrary)] +struct MyStruct { + c: char, + u: u32, +} + +/// Add contracts to ensure that all parameters are representing the same pair (char, u32). +#[kani::requires(val.u == second)] +#[kani::requires(val.u == tup_u)] +#[kani::requires(Ok(val.c) == char::try_from(first))] +#[kani::requires(val.c == tup_c)] +/// We need this extra clause due to . +#[kani::requires(char::try_from(first).is_ok())] +pub fn odd_parameters_eq( + [first, second]: [u32; 2], + (tup_c, tup_u): (char, u32), + val @ MyStruct { c: val_c, u }: MyStruct, +) { + assert_eq!(tup_c, char::try_from(first).unwrap()); + assert_eq!(tup_c, val_c); + + assert_eq!(tup_u, second); + assert_eq!(tup_u, u); + assert_eq!(val, MyStruct { c: val_c, u }); +} + +/// Similar to the function above, but with one requirement missing. +#[kani::requires(val.u == second)] +#[kani::requires(val.u == tup_u)] +#[kani::requires(Ok(val.c) == char::try_from(first))] +// MISSING: #[kani::requires(val.c == tup_c)] +// We need this extra clause due to . +#[kani::requires(char::try_from(first).is_ok())] +pub fn odd_parameters_eq_wrong( + [first, second]: [u32; 2], + (tup_c, tup_u): (char, u32), + val @ MyStruct { c: val_c, u }: MyStruct, +) { + assert_eq!(tup_c, char::try_from(first).unwrap()); + assert_eq!(tup_c, val_c); + + assert_eq!(tup_u, second); + assert_eq!(tup_u, u); + assert_eq!(val, MyStruct { c: val_c, u }); +} + +mod verify { + use super::*; + use kani::Arbitrary; + + #[kani::proof_for_contract(odd_parameters_eq)] + fn check_params() { + odd_parameters_eq(kani::any(), kani::any(), kani::any()) + } + + #[kani::should_panic] + #[kani::proof_for_contract(odd_parameters_eq_wrong)] + fn check_params_wrong() { + odd_parameters_eq_wrong(kani::any(), kani::any(), kani::any()) + } +} diff --git a/tests/kani/FunctionContracts/receiver_contracts.rs b/tests/kani/FunctionContracts/receiver_contracts.rs new file mode 100644 index 000000000000..5ca63f1558c3 --- /dev/null +++ b/tests/kani/FunctionContracts/receiver_contracts.rs @@ -0,0 +1,155 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Checks that function contracts work with different types of receivers. I.e.: +//! - &Self (i.e. &self) +//! - &mut Self (i.e &mut self) +//! - Box +//! - Rc +//! - Arc +//! - Pin

where P is one of the types above +//! Source: +// compile-flags: --edition 2021 +// kani-flags: -Zfunction-contracts + +#![feature(rustc_attrs)] + +extern crate kani; + +use std::boxed::Box; +use std::pin::Pin; +use std::rc::Rc; +use std::sync::Arc; + +/// Type representing a valid ASCII value going from `0..=128`. +#[derive(Copy, Clone, PartialEq, Eq)] +#[rustc_layout_scalar_valid_range_start(0)] +#[rustc_layout_scalar_valid_range_end(128)] +struct CharASCII(u8); + +impl kani::Arbitrary for CharASCII { + fn any() -> CharASCII { + let val = kani::any_where(|inner: &u8| *inner <= 128); + unsafe { CharASCII(val) } + } +} + +/// This type contains unsafe setter functions with the same contract but different type of +/// receivers. +impl CharASCII { + #[kani::modifies(&self.0)] + #[kani::requires(new_val <= 128)] + #[kani::ensures(|_| self.0 == new_val)] + unsafe fn set_val(&mut self, new_val: u8) { + self.0 = new_val + } + + #[kani::modifies(&self.0)] + #[kani::requires(new_val <= 128)] + #[kani::ensures(|_| self.0 == new_val)] + unsafe fn set_mut_ref(self: &mut Self, new_val: u8) { + self.0 = new_val + } + + #[kani::modifies(&self.as_ref().0)] + #[kani::requires(new_val <= 128)] + #[kani::ensures(|_| self.as_ref().0 == new_val)] + unsafe fn set_box(mut self: Box, new_val: u8) { + self.as_mut().0 = new_val + } + + #[kani::modifies(&self.as_ref().0)] + #[kani::requires(new_val <= 128)] + #[kani::ensures(|_| self.as_ref().0 == new_val)] + unsafe fn set_rc(mut self: Rc, new_val: u8) { + Rc::<_>::get_mut(&mut self).unwrap().0 = new_val + } + + /// We cannot specify the counter today which is modified in this function. + /// + #[kani::modifies(&self.as_ref().0)] + #[kani::requires(new_val <= 128)] + #[kani::ensures(|_| self.as_ref().0 == new_val)] + unsafe fn set_arc(mut self: Arc, new_val: u8) { + Arc::<_>::get_mut(&mut self).unwrap().0 = new_val; + } + + #[kani::modifies(&self.0)] + #[kani::requires(new_val <= 128)] + #[kani::ensures(|_| self.0 == new_val)] + unsafe fn set_pin(mut self: Pin<&mut Self>, new_val: u8) { + self.0 = new_val + } + + #[kani::modifies(&self.0)] + #[kani::requires(new_val <= 128)] + #[kani::ensures(|_| self.0 == new_val)] + unsafe fn set_pin_box(mut self: Pin>, new_val: u8) { + self.0 = new_val + } +} + +mod verify { + use super::*; + use kani::Arbitrary; + + #[kani::proof_for_contract(CharASCII::set_val)] + fn check_set_val() { + let mut obj = CharASCII::any(); + let original = obj.0; + let new_val = kani::any_where(|new| *new != original); + unsafe { obj.set_val(new_val) }; + } + + #[kani::proof_for_contract(CharASCII::set_mut_ref)] + fn check_mut_ref() { + let mut obj = CharASCII::any(); + let original = obj.0; + let new_val = kani::any_where(|new| *new != original); + unsafe { obj.set_mut_ref(new_val) }; + } + + #[kani::proof_for_contract(CharASCII::set_box)] + fn check_box() { + let obj = CharASCII::any(); + let original = obj.0; + let new_val = kani::any_where(|new| *new != original); + unsafe { Box::new(obj).set_box(new_val) }; + } + + #[kani::proof_for_contract(CharASCII::set_rc)] + fn check_rc() { + let obj = CharASCII::any(); + let original = obj.0; + let new_val = kani::any_where(|new| *new != original); + unsafe { Rc::new(obj).set_rc(new_val) }; + } + + /// This test currently fails because we cannot specify that the counter will be modified. + /// The counter is behind a pointer, but `Arc` only provide access to the data portion of + /// the allocation. + /// + #[cfg(arc_fails)] + #[kani::proof_for_contract(CharASCII::set_arc)] + fn check_arc() { + let obj = CharASCII::any(); + let original = obj.0; + let new_val = kani::any_where(|new| *new != original); + unsafe { Arc::new(obj).set_arc(new_val) }; + } + + #[kani::proof_for_contract(CharASCII::set_pin)] + fn check_pin() { + let mut obj = CharASCII::any(); + let original = obj.0; + let new_val = kani::any_where(|new| *new != original); + unsafe { Pin::new(&mut obj).set_pin(new_val) }; + } + + #[kani::proof_for_contract(CharASCII::set_pin_box)] + fn check_pin_box() { + let obj = CharASCII::any(); + let original = obj.0; + let new_val = kani::any_where(|new| *new != original); + unsafe { Pin::new(Box::new(obj)).set_pin_box(new_val) }; + } +} diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index 2d5e891f3fdc..75afd77dfa88 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 2d5e891f3fdc8a88b2d457baceedea5751efaa0d +Subproject commit 75afd77dfa88d696900f12ee747409ddb208a745 From d6d1ebfdeeb691e3dd8ba86f60ce3663c387af1c Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:18:55 -0700 Subject: [PATCH 06/28] Add missing padding needded for alignment (#3401) This PR adds extra padding for enums when needed for alignment. The missing alignment was exposed in #2857 where the layout size from MIR for `der::error::ErrorKind` was 48 bytes, and from `codegen_enum` was only 41 bytes. The difference turned out to be caused by an 8-byte alignment that the compiler uses for this enum. Resolves #2857 Resolves #3318 --- .../src/codegen_cprover_gotoc/codegen/typ.rs | 19 ++++++++++++++++++- tests/cargo-kani/iss2857/Cargo.toml | 9 +++++++++ tests/cargo-kani/iss2857/expected | 1 + tests/cargo-kani/iss2857/src/main.rs | 13 +++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/cargo-kani/iss2857/Cargo.toml create mode 100644 tests/cargo-kani/iss2857/expected create mode 100644 tests/cargo-kani/iss2857/src/main.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs index 1703335dda51..9baa3c59f4c2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs @@ -1140,7 +1140,7 @@ impl<'tcx> GotocCtx<'tcx> { /// Mapping enums to CBMC types is rather complicated. There are a few cases to consider: /// 1. When there is only 0 or 1 variant, this is straightforward as the code shows - /// 2. When there are more variants, rust might decides to apply the typical encoding which + /// 2. When there are more variants, rust might decide to apply the typical encoding which /// regard enums as tagged union, or an optimized form, called niche encoding. /// /// The direct encoding is straightforward. Enums are just mapped to C as a struct of union of structs. @@ -1242,6 +1242,23 @@ impl<'tcx> GotocCtx<'tcx> { ) }), )); + // Check if any padding is needed for alignment. This is needed for + // https://github.com/model-checking/kani/issues/2857 for example. + // The logic for determining the maximum variant size is taken from: + // https://github.com/rust-lang/rust/blob/e60ebb2f2c1facba87e7971798f3cbdfd309cd23/compiler/rustc_session/src/code_stats.rs#L166 + let max_variant_size = variants + .iter() + .map(|l: &LayoutS| l.size) + .max() + .unwrap(); + let max_variant_size = std::cmp::max(max_variant_size, discr_offset); + if let Some(padding) = gcx.codegen_alignment_padding( + max_variant_size, + &layout, + fields.len(), + ) { + fields.push(padding); + } fields }) } diff --git a/tests/cargo-kani/iss2857/Cargo.toml b/tests/cargo-kani/iss2857/Cargo.toml new file mode 100644 index 000000000000..9f2a72342c19 --- /dev/null +++ b/tests/cargo-kani/iss2857/Cargo.toml @@ -0,0 +1,9 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +[package] +name = "iss2857" +version = "0.1.0" +edition = "2021" + +[dependencies] +sec1 = "0.7.3" diff --git a/tests/cargo-kani/iss2857/expected b/tests/cargo-kani/iss2857/expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/cargo-kani/iss2857/expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL diff --git a/tests/cargo-kani/iss2857/src/main.rs b/tests/cargo-kani/iss2857/src/main.rs new file mode 100644 index 000000000000..6a0ed40a006e --- /dev/null +++ b/tests/cargo-kani/iss2857/src/main.rs @@ -0,0 +1,13 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// This test checks that https://github.com/model-checking/kani/issues/2857 is +// fixed + +#[kani::proof] +fn check_der_error() { + let e = sec1::der::Error::incomplete(sec1::der::Length::ZERO); + let _ = format!("{e:?}"); +} + +fn main() {} From e0141cf09819e90752dca6bf4d1b3cbb59997f17 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 2 Aug 2024 00:05:20 +0200 Subject: [PATCH 07/28] Remove unwind attributes that are no longer needed with CBMC v6 (#3403) CBMC v6 includes https://github.com/diffblue/cbmc/pull/8247, which fixes the need for unwind attributes that were newly found to be necessary when upgrading to nightly-2024-03-15 (#3084). Resolves: #3088 --- docs/src/getting-started/verification-results/src/main.rs | 1 - tests/coverage/unreachable/abort/main.rs | 2 +- tests/expected/abort/main.rs | 1 - tests/expected/iterator/main.rs | 1 - tests/kani/Coroutines/main.rs | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/src/getting-started/verification-results/src/main.rs b/docs/src/getting-started/verification-results/src/main.rs index 72653cf4dc8f..7a03b34f0f9e 100644 --- a/docs/src/getting-started/verification-results/src/main.rs +++ b/docs/src/getting-started/verification-results/src/main.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #[kani::proof] -#[kani::unwind(4)] // ANCHOR: success_example fn success_example() { let mut sum = 0; diff --git a/tests/coverage/unreachable/abort/main.rs b/tests/coverage/unreachable/abort/main.rs index 39c0b0efb54f..2941ec126f3c 100644 --- a/tests/coverage/unreachable/abort/main.rs +++ b/tests/coverage/unreachable/abort/main.rs @@ -5,7 +5,7 @@ use std::process; -#[cfg_attr(kani, kani::proof, kani::unwind(5))] +#[kani::proof] fn main() { for i in 0..4 { if i == 1 { diff --git a/tests/expected/abort/main.rs b/tests/expected/abort/main.rs index 9e2f5b7a808c..2941ec126f3c 100644 --- a/tests/expected/abort/main.rs +++ b/tests/expected/abort/main.rs @@ -6,7 +6,6 @@ use std::process; #[kani::proof] -#[kani::unwind(5)] fn main() { for i in 0..4 { if i == 1 { diff --git a/tests/expected/iterator/main.rs b/tests/expected/iterator/main.rs index 5cf9402bcb23..b1cb4a89cfbf 100644 --- a/tests/expected/iterator/main.rs +++ b/tests/expected/iterator/main.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #[kani::proof] -#[kani::unwind(4)] fn main() { let mut z = 1; for i in 1..4 { diff --git a/tests/kani/Coroutines/main.rs b/tests/kani/Coroutines/main.rs index e059305a6da2..0742d14a6ada 100644 --- a/tests/kani/Coroutines/main.rs +++ b/tests/kani/Coroutines/main.rs @@ -10,7 +10,6 @@ use std::ops::{Coroutine, CoroutineState}; use std::pin::Pin; #[kani::proof] -#[kani::unwind(3)] fn main() { let mut add_one = #[coroutine] |mut resume: u8| { From c7b315f22fa225132c87ab4051a1159b0b8ca08b Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 2 Aug 2024 15:18:34 +0200 Subject: [PATCH 08/28] Enable log2*, log10* intrinsics (#3001) Implemented in CBMC in https://github.com/diffblue/cbmc/pull/8195. --- docs/src/rust-feature-support/intrinsics.md | 8 +++---- .../codegen/intrinsic.rs | 8 +++---- tests/kani/Intrinsics/Math/Arith/log10.rs | 23 +++++++++++++++++++ tests/kani/Intrinsics/Math/Arith/log2.rs | 23 +++++++++++++++++++ 4 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 tests/kani/Intrinsics/Math/Arith/log10.rs create mode 100644 tests/kani/Intrinsics/Math/Arith/log2.rs diff --git a/docs/src/rust-feature-support/intrinsics.md b/docs/src/rust-feature-support/intrinsics.md index 49a1f0845ecc..501a9952f05e 100644 --- a/docs/src/rust-feature-support/intrinsics.md +++ b/docs/src/rust-feature-support/intrinsics.md @@ -166,10 +166,10 @@ forget | Yes | | frem_fast | No | | fsub_fast | Yes | | likely | Yes | | -log10f32 | No | | -log10f64 | No | | -log2f32 | No | | -log2f64 | No | | +log10f32 | Partial | Results are overapproximated | +log10f64 | Partial | Results are overapproximated | +log2f32 | Partial | Results are overapproximated | +log2f64 | Partial | Results are overapproximated | logf32 | Partial | Results are overapproximated | logf64 | Partial | Results are overapproximated | maxnumf32 | Yes | | diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 29ea69eeb39b..12352190e6e2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -466,10 +466,10 @@ impl<'tcx> GotocCtx<'tcx> { self.codegen_expr_to_place_stable(place, Expr::c_false(), loc) } "likely" => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), - "log10f32" => unstable_codegen!(codegen_simple_intrinsic!(Log10f)), - "log10f64" => unstable_codegen!(codegen_simple_intrinsic!(Log10)), - "log2f32" => unstable_codegen!(codegen_simple_intrinsic!(Log2f)), - "log2f64" => unstable_codegen!(codegen_simple_intrinsic!(Log2)), + "log10f32" => codegen_simple_intrinsic!(Log10f), + "log10f64" => codegen_simple_intrinsic!(Log10), + "log2f32" => codegen_simple_intrinsic!(Log2f), + "log2f64" => codegen_simple_intrinsic!(Log2), "logf32" => codegen_simple_intrinsic!(Logf), "logf64" => codegen_simple_intrinsic!(Log), "maxnumf32" => codegen_simple_intrinsic!(Fmaxf), diff --git a/tests/kani/Intrinsics/Math/Arith/log10.rs b/tests/kani/Intrinsics/Math/Arith/log10.rs new file mode 100644 index 000000000000..4670c72cfeb3 --- /dev/null +++ b/tests/kani/Intrinsics/Math/Arith/log10.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn verify_log10_32() { + let ten = 10.0f32; + + // log10(10) - 1 == 0 + let abs_difference = (ten.log10() - 1.0).abs(); + + // should be <= f32::EPSILON, but CBMC's approximation of log10 makes results less precise + assert!(abs_difference <= 0.03); +} + +#[kani::proof] +fn verify_log10_64() { + let hundred = 100.0_f64; + + // log10(100) - 2 == 0 + let abs_difference = (hundred.log10() - 2.0).abs(); + + assert!(abs_difference < 0.03); +} diff --git a/tests/kani/Intrinsics/Math/Arith/log2.rs b/tests/kani/Intrinsics/Math/Arith/log2.rs new file mode 100644 index 000000000000..52153f8b368a --- /dev/null +++ b/tests/kani/Intrinsics/Math/Arith/log2.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn verify_log2_32() { + let two = 2.0f32; + + // log2(2) - 1 == 0 + let abs_difference = (two.log2() - 1.0).abs(); + + // should be <= f32::EPSILON, but CBMC's approximation of log2 makes results less precise + assert!(abs_difference <= 0.09); +} + +#[kani::proof] +fn verify_log2_64() { + let four = 4.0_f64; + + // log2(4) - 2 == 0 + let abs_difference = (four.log2() - 2.0).abs(); + + assert!(abs_difference < 0.09); +} From 6ae3f00c88a458abc381057456780af9e7d7e451 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 2 Aug 2024 16:54:19 +0200 Subject: [PATCH 09/28] Enable powif* intrinsics (#2999) These are supported by CBMC with https://github.com/diffblue/cbmc/pull/8192 merged. Resolves: #2763 --- docs/src/rust-feature-support/intrinsics.md | 4 ++-- .../codegen/intrinsic.rs | 4 ++-- ...2.rs => cast_abstract_args_to_concrete.rs} | 3 --- .../cast_abstract_args_to_concrete_fixme.rs | 24 ------------------- tests/kani/Intrinsics/Math/Arith/powi.rs | 22 +++++++++++++++++ 5 files changed, 26 insertions(+), 31 deletions(-) rename tests/kani/Cast/{cast_abstract_args_to_concrete_fixme2.rs => cast_abstract_args_to_concrete.rs} (92%) delete mode 100644 tests/kani/Cast/cast_abstract_args_to_concrete_fixme.rs create mode 100644 tests/kani/Intrinsics/Math/Arith/powi.rs diff --git a/docs/src/rust-feature-support/intrinsics.md b/docs/src/rust-feature-support/intrinsics.md index 501a9952f05e..f97db5d8ab6c 100644 --- a/docs/src/rust-feature-support/intrinsics.md +++ b/docs/src/rust-feature-support/intrinsics.md @@ -187,8 +187,8 @@ nontemporal_store | No | | offset | Partial | Doesn't check [all UB conditions](https://doc.rust-lang.org/std/primitive.pointer.html#safety-2) | powf32 | Partial | Results are overapproximated | powf64 | Partial | Results are overapproximated | -powif32 | No | | -powif64 | No | | +powif32 | Partial | Results are overapproximated | +powif64 | Partial | Results are overapproximated | pref_align_of | Yes | | prefetch_read_data | No | | prefetch_read_instruction | No | | diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 12352190e6e2..91f7ea20c9b1 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -490,8 +490,8 @@ impl<'tcx> GotocCtx<'tcx> { ), "powf32" => codegen_simple_intrinsic!(Powf), "powf64" => codegen_simple_intrinsic!(Pow), - "powif32" => unstable_codegen!(codegen_simple_intrinsic!(Powif)), - "powif64" => unstable_codegen!(codegen_simple_intrinsic!(Powi)), + "powif32" => codegen_simple_intrinsic!(Powif), + "powif64" => codegen_simple_intrinsic!(Powi), "pref_align_of" => codegen_intrinsic_const!(), "ptr_guaranteed_cmp" => self.codegen_ptr_guaranteed_cmp(fargs, place, loc), "ptr_offset_from" => self.codegen_ptr_offset_from(fargs, place, loc), diff --git a/tests/kani/Cast/cast_abstract_args_to_concrete_fixme2.rs b/tests/kani/Cast/cast_abstract_args_to_concrete.rs similarity index 92% rename from tests/kani/Cast/cast_abstract_args_to_concrete_fixme2.rs rename to tests/kani/Cast/cast_abstract_args_to_concrete.rs index 51067e0e0a73..6694f799493a 100644 --- a/tests/kani/Cast/cast_abstract_args_to_concrete_fixme2.rs +++ b/tests/kani/Cast/cast_abstract_args_to_concrete.rs @@ -1,9 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// https://github.com/model-checking/kani/issues/555 -// kani-flags: --no-undefined-function-checks - // This regression test is in response to issue #135. // The type of the second parameter to powi is a `CInteger`, but // the type of `2` here is a `u32`. This test ensures that diff --git a/tests/kani/Cast/cast_abstract_args_to_concrete_fixme.rs b/tests/kani/Cast/cast_abstract_args_to_concrete_fixme.rs deleted file mode 100644 index faeb74c5d66a..000000000000 --- a/tests/kani/Cast/cast_abstract_args_to_concrete_fixme.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! This test is a modified version of cast_abstract_args_to_concrete_fixme.rs. -//! The original test requires --no-undefined-function-checks to pass. This is an issue that -//! require investigation. See https://github.com/model-checking/kani/issues/555. -//! -//! Once this issue is fixed. Please remove this test and remove the kani flag from the original -//! test: --no-undefined-function-check - -fn main() { - let _x32 = 1.0f32.powi(2); - let _x64 = 1.0f64.powi(2); - - unsafe { - let size: libc::size_t = mem::size_of::(); - let my_num: *mut libc::c_void = libc::malloc(size); - if my_num.is_null() { - panic!("failed to allocate memory"); - } - let my_num2 = libc::memset(my_num, 1, size); - libc::free(my_num); - } -} diff --git a/tests/kani/Intrinsics/Math/Arith/powi.rs b/tests/kani/Intrinsics/Math/Arith/powi.rs new file mode 100644 index 000000000000..c7986d18a9ef --- /dev/null +++ b/tests/kani/Intrinsics/Math/Arith/powi.rs @@ -0,0 +1,22 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn verify_powi32() { + let x: f32 = kani::any(); + kani::assume(x.is_normal()); + kani::assume(x >= 1e-19 || x <= -1e-19); + kani::assume(x <= 1.84e19 && x >= -1.84e19); + let x2 = x.powi(2); + assert!(x2 >= 0.0); +} + +#[kani::proof] +fn verify_powi64() { + let x: f64 = kani::any(); + kani::assume(x.is_normal()); + kani::assume(x >= 1e-153 || x <= -1e-153); + kani::assume(x <= 1.34e154 && x >= -1.34e154); + let x2 = x.powi(2); + assert!(x2 >= 0.0); +} From 754691122aeb0c762180c77bc1fa9fcf1edc75d5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:41:14 +0000 Subject: [PATCH 10/28] Automatic toolchain upgrade to nightly-2024-08-02 (#3407) Update Rust toolchain from nightly-2024-08-01 to nightly-2024-08-02 without any other source changes. This is an automatically generated pull request. If any of the CI checks fail, manual intervention is required. In such a case, review the changes at https://github.com/rust-lang/rust from https://github.com/rust-lang/rust/commit/28a58f2fa7f0c46b8fab8237c02471a915924fe5 up to https://github.com/rust-lang/rust/commit/8e86c9567154dc5a9ada15ab196d23eae2bd7d89. --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8753157827b5..0c0e9e9f196c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-01" +channel = "nightly-2024-08-02" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 24fc8aafb4c6a38dd182a3620e87efbd88fc0318 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 2 Aug 2024 18:32:33 +0200 Subject: [PATCH 11/28] Prepare use of GitHub merge queues (#3408) Merge queues should help us avoid having to merge from main repeatedly even when all approvals are in and all CI jobs have succeeded. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. Co-authored-by: Felipe R. Monteiro --- .github/workflows/audit.yml | 1 + .github/workflows/format-check.yml | 1 + .github/workflows/kani.yml | 1 + .github/workflows/release.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 3ae9d192f376..5b75d6162c85 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -7,6 +7,7 @@ name: Cargo Audit on: pull_request: + merge_group: push: # Run on changes to branches but not tags. branches: diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index 5ab7dcb1b9c3..cc13306a4eaf 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -3,6 +3,7 @@ name: Kani Format Check on: pull_request: + merge_group: push: # Not just any push, as that includes tags. # We don't want to re-trigger this workflow when tagging an existing commit. diff --git a/.github/workflows/kani.yml b/.github/workflows/kani.yml index dd077eff25e1..a565c9cd4cbe 100644 --- a/.github/workflows/kani.yml +++ b/.github/workflows/kani.yml @@ -3,6 +3,7 @@ name: Kani CI on: pull_request: + merge_group: push: # Not just any push, as that includes tags. # We don't want to re-trigger this workflow when tagging an existing commit. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72ef4e2de889..ad2e339f19e1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,7 @@ name: Release Bundle on: pull_request: + merge_group: push: branches: - 'main' From beccc4c42111d14f369103bb0f9a6ccb15927968 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 2 Aug 2024 20:42:31 +0200 Subject: [PATCH 12/28] Enable fma* intrinsics (#3002) Implemented in CBMC in https://github.com/diffblue/cbmc/pull/8195. --- docs/src/rust-feature-support/intrinsics.md | 4 +-- .../codegen/intrinsic.rs | 4 +-- tests/kani/Intrinsics/Math/Arith/fma.rs | 26 +++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 tests/kani/Intrinsics/Math/Arith/fma.rs diff --git a/docs/src/rust-feature-support/intrinsics.md b/docs/src/rust-feature-support/intrinsics.md index f97db5d8ab6c..77392cd37fc6 100644 --- a/docs/src/rust-feature-support/intrinsics.md +++ b/docs/src/rust-feature-support/intrinsics.md @@ -159,8 +159,8 @@ fdiv_fast | Partial | [#809](https://github.com/model-checking/kani/issues/809) float_to_int_unchecked | No | | floorf32 | Yes | | floorf64 | Yes | | -fmaf32 | No | | -fmaf64 | No | | +fmaf32 | Partial | Results are overapproximated | +fmaf64 | Partial | Results are overapproximated | fmul_fast | Partial | [#809](https://github.com/model-checking/kani/issues/809) | forget | Yes | | frem_fast | No | | diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 91f7ea20c9b1..8f93a4c61553 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -447,8 +447,8 @@ impl<'tcx> GotocCtx<'tcx> { } "floorf32" => codegen_simple_intrinsic!(Floorf), "floorf64" => codegen_simple_intrinsic!(Floor), - "fmaf32" => unstable_codegen!(codegen_simple_intrinsic!(Fmaf)), - "fmaf64" => unstable_codegen!(codegen_simple_intrinsic!(Fma)), + "fmaf32" => codegen_simple_intrinsic!(Fmaf), + "fmaf64" => codegen_simple_intrinsic!(Fma), "fmul_fast" => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(mul); diff --git a/tests/kani/Intrinsics/Math/Arith/fma.rs b/tests/kani/Intrinsics/Math/Arith/fma.rs new file mode 100644 index 000000000000..379d5aa81db5 --- /dev/null +++ b/tests/kani/Intrinsics/Math/Arith/fma.rs @@ -0,0 +1,26 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn verify_fma_32() { + let m = 10.0_f32; + let x = 4.0_f32; + let b = 60.0_f32; + + // 100.0 + let abs_difference = (m.mul_add(x, b) - ((m * x) + b)).abs(); + + assert!(abs_difference <= f32::EPSILON); +} + +#[kani::proof] +fn verify_fma_64() { + let m = 10.0_f64; + let x = 4.0_f64; + let b = 60.0_f64; + + // 100.0 + let abs_difference = (m.mul_add(x, b) - ((m * x) + b)).abs(); + + assert!(abs_difference < 1e-10); +} From 4a9255706dfcb336db59d30fbe327f9ed2680396 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 2 Aug 2024 21:30:02 +0200 Subject: [PATCH 13/28] Enable sqrt* intrinsics (#3000) CBMC's sqrt* implementations were fixed in https://github.com/diffblue/cbmc/pull/8195. --- docs/src/rust-feature-support/intrinsics.md | 4 ++-- .../codegen/intrinsic.rs | 4 ++-- tests/kani/Intrinsics/Math/Arith/sqrt.rs | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 tests/kani/Intrinsics/Math/Arith/sqrt.rs diff --git a/docs/src/rust-feature-support/intrinsics.md b/docs/src/rust-feature-support/intrinsics.md index 77392cd37fc6..ce3c9fc1b7a2 100644 --- a/docs/src/rust-feature-support/intrinsics.md +++ b/docs/src/rust-feature-support/intrinsics.md @@ -211,8 +211,8 @@ sinf32 | Partial | Results are overapproximated; [this test](https://github.com/ sinf64 | Partial | Results are overapproximated; [this test](https://github.com/model-checking/kani/blob/main/tests/kani/Intrinsics/Math/Trigonometry/sinf64.rs) explains how | size_of | Yes | | size_of_val | Yes | | -sqrtf32 | No | | -sqrtf64 | No | | +sqrtf32 | Partial | Results are overapproximated | +sqrtf64 | Partial | Results are overapproximated | sub_with_overflow | Yes | | transmute | Partial | Doesn't check [all UB conditions](https://doc.rust-lang.org/nomicon/transmutes.html) | truncf32 | Yes | | diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 8f93a4c61553..c4d5396f1fe8 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -570,8 +570,8 @@ impl<'tcx> GotocCtx<'tcx> { "simd_xor" => codegen_intrinsic_binop!(bitxor), "size_of" => unreachable!(), "size_of_val" => codegen_size_align!(size), - "sqrtf32" => unstable_codegen!(codegen_simple_intrinsic!(Sqrtf)), - "sqrtf64" => unstable_codegen!(codegen_simple_intrinsic!(Sqrt)), + "sqrtf32" => codegen_simple_intrinsic!(Sqrtf), + "sqrtf64" => codegen_simple_intrinsic!(Sqrt), "sub_with_overflow" => self.codegen_op_with_overflow( BinaryOperator::OverflowResultMinus, fargs, diff --git a/tests/kani/Intrinsics/Math/Arith/sqrt.rs b/tests/kani/Intrinsics/Math/Arith/sqrt.rs new file mode 100644 index 000000000000..31afaba8a740 --- /dev/null +++ b/tests/kani/Intrinsics/Math/Arith/sqrt.rs @@ -0,0 +1,24 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#[kani::proof] +fn verify_sqrt32() { + let positive = 4.0_f32; + let negative_zero = -0.0_f32; + + let abs_difference = (positive.sqrt() - 2.0).abs(); + + assert!(abs_difference <= f32::EPSILON); + assert!(negative_zero.sqrt() == negative_zero); +} + +#[kani::proof] +fn verify_sqrt64() { + let positive = 4.0_f64; + let negative_zero = -0.0_f64; + + let abs_difference = (positive.sqrt() - 2.0).abs(); + + assert!(abs_difference <= 1e-10); + assert!(negative_zero.sqrt() == negative_zero); +} From b33708134a19a88f15fd3b6d84a20dc48accac22 Mon Sep 17 00:00:00 2001 From: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:39:02 -0400 Subject: [PATCH 14/28] Update crates documentation with info. on `#[safety_constraint(...)]` attribute for structs (#3405) This PR updates the crates documentation we already had for the `#[safety_constraint(...)]` attribute to account for the changes in #3270. The proposal includes notes/guidelines on where to use the attribute (i.e., the struct or its fields) depending on the type safety condition to be specified. Also, it removes the `rs` annotations on the code blocks that appear in the documentation because they don't seem to have the intended effect (the blocks are not being highlighted at all). The current version of this documentation can be found [here](https://model-checking.github.io/kani/crates/doc/kani/derive.Arbitrary.html) and [here](https://model-checking.github.io/kani/crates/doc/kani/derive.Invariant.html). Related #3095 --- library/kani_macros/src/lib.rs | 113 +++++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 14 deletions(-) diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 4e3a8d6f9f5b..6fe0979f08bc 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -103,16 +103,19 @@ pub fn unstable_feature(attr: TokenStream, item: TokenStream) -> TokenStream { /// Allow users to auto generate `Arbitrary` implementations by using /// `#[derive(Arbitrary)]` macro. /// -/// When using `#[derive(Arbitrary)]` on a struct, the `#[safety_constraint()]` -/// attribute can be added to its fields to indicate a type safety invariant -/// condition ``. Since `kani::any()` is always expected to produce -/// type-safe values, **adding `#[safety_constraint(...)]` to any fields will further -/// constrain the objects generated with `kani::any()`**. +/// ## Type safety specification with the `#[safety_constraint(...)]` attribute +/// +/// When using `#[derive(Arbitrary)]` on a struct, the +/// `#[safety_constraint()]` attribute can be added to either the struct +/// or its fields (but not both) to indicate a type safety invariant condition +/// ``. Since `kani::any()` is always expected to produce type-safe +/// values, **adding `#[safety_constraint(...)]` to the struct or any of its +/// fields will further constrain the objects generated with `kani::any()`**. /// /// For example, the `check_positive` harness in this code is expected to /// pass: /// -/// ```rs +/// ```rust /// #[derive(kani::Arbitrary)] /// struct AlwaysPositive { /// #[safety_constraint(*inner >= 0)] @@ -126,11 +129,11 @@ pub fn unstable_feature(attr: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` /// -/// Therefore, using the `#[safety_constraint(...)]` attribute can lead to vacuous +/// But using the `#[safety_constraint(...)]` attribute can lead to vacuous /// results when the values are over-constrained. For example, in this code /// the `check_positive` harness will pass too: /// -/// ```rs +/// ```rust /// #[derive(kani::Arbitrary)] /// struct AlwaysPositive { /// #[safety_constraint(*inner >= 0 && *inner < i32::MIN)] @@ -158,6 +161,45 @@ pub fn unstable_feature(attr: TokenStream, item: TokenStream) -> TokenStream { /// As usual, we recommend users to defend against these behaviors by using /// `kani::cover!(...)` checks and watching out for unreachable assertions in /// their project's code. +/// +/// ### Adding `#[safety_constraint(...)]` to the struct as opposed to its fields +/// +/// As mentioned earlier, the `#[safety_constraint(...)]` attribute can be added +/// to either the struct or its fields, but not to both. Adding the +/// `#[safety_constraint(...)]` attribute to both the struct and its fields will +/// result in an error. +/// +/// In practice, only one type of specification is need. If the condition for +/// the type safety invariant involves a relation between two or more struct +/// fields, the struct-level attribute should be used. Otherwise, using the +/// `#[safety_constraint(...)]` on field(s) is recommended since it helps with readability. +/// +/// For example, if we were defining a custom vector `MyVector` and wanted to +/// specify that the inner vector's length is always less than or equal to its +/// capacity, we should do it as follows: +/// +/// ```rust +/// #[derive(Arbitrary)] +/// #[safety_constraint(vector.len() <= *capacity)] +/// struct MyVector { +/// vector: Vec, +/// capacity: usize, +/// } +/// ``` +/// +/// However, if we were defining a struct whose fields are not related in any +/// way, we would prefer using the `#[safety_constraint(...)]` attribute on its +/// fields: +/// +/// ```rust +/// #[derive(Arbitrary)] +/// struct PositivePoint { +/// #[safety_constraint(*x >= 0)] +/// x: i32, +/// #[safety_constraint(*y >= 0)] +/// y: i32, +/// } +/// ``` #[proc_macro_error] #[proc_macro_derive(Arbitrary, attributes(safety_constraint))] pub fn derive_arbitrary(item: TokenStream) -> TokenStream { @@ -167,15 +209,19 @@ pub fn derive_arbitrary(item: TokenStream) -> TokenStream { /// Allow users to auto generate `Invariant` implementations by using /// `#[derive(Invariant)]` macro. /// -/// When using `#[derive(Invariant)]` on a struct, the `#[safety_constraint()]` -/// attribute can be added to its fields to indicate a type safety invariant -/// condition ``. This will ensure that the gets additionally checked when -/// using the `is_safe()` method generated by the `#[derive(Invariant)]` macro. +/// ## Type safety specification with the `#[safety_constraint(...)]` attribute +/// +/// When using `#[derive(Invariant)]` on a struct, the +/// `#[safety_constraint()]` attribute can be added to either the struct +/// or its fields (but not both) to indicate a type safety invariant condition +/// ``. This will ensure that the type-safety condition gets additionally +/// checked when using the `is_safe()` method automatically generated by the +/// `#[derive(Invariant)]` macro. /// /// For example, the `check_positive` harness in this code is expected to /// fail: /// -/// ```rs +/// ```rust /// #[derive(kani::Invariant)] /// struct AlwaysPositive { /// #[safety_constraint(*inner >= 0)] @@ -200,7 +246,7 @@ pub fn derive_arbitrary(item: TokenStream) -> TokenStream { /// For example, for the `AlwaysPositive` struct from above, we will generate /// the following implementation: /// -/// ```rs +/// ```rust /// impl kani::Invariant for AlwaysPositive { /// fn is_safe(&self) -> bool { /// let obj = self; @@ -212,6 +258,45 @@ pub fn derive_arbitrary(item: TokenStream) -> TokenStream { /// /// Note: the assignments to `obj` and `inner` are made so that we can treat the /// fields as if they were references. +/// +/// ### Adding `#[safety_constraint(...)]` to the struct as opposed to its fields +/// +/// As mentioned earlier, the `#[safety_constraint(...)]` attribute can be added +/// to either the struct or its fields, but not to both. Adding the +/// `#[safety_constraint(...)]` attribute to both the struct and its fields will +/// result in an error. +/// +/// In practice, only one type of specification is need. If the condition for +/// the type safety invariant involves a relation between two or more struct +/// fields, the struct-level attribute should be used. Otherwise, using the +/// `#[safety_constraint(...)]` is recommended since it helps with readability. +/// +/// For example, if we were defining a custom vector `MyVector` and wanted to +/// specify that the inner vector's length is always less than or equal to its +/// capacity, we should do it as follows: +/// +/// ```rust +/// #[derive(Invariant)] +/// #[safety_constraint(vector.len() <= *capacity)] +/// struct MyVector { +/// vector: Vec, +/// capacity: usize, +/// } +/// ``` +/// +/// However, if we were defining a struct whose fields are not related in any +/// way, we would prefer using the `#[safety_constraint(...)]` attribute on its +/// fields: +/// +/// ```rust +/// #[derive(Invariant)] +/// struct PositivePoint { +/// #[safety_constraint(*x >= 0)] +/// x: i32, +/// #[safety_constraint(*y >= 0)] +/// y: i32, +/// } +/// ``` #[proc_macro_error] #[proc_macro_derive(Invariant, attributes(safety_constraint))] pub fn derive_invariant(item: TokenStream) -> TokenStream { From 550e4d4084083e22308bbd4cd13d8b4877c7143b Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 2 Aug 2024 23:27:20 +0200 Subject: [PATCH 15/28] Enable "Auto label" for merge-queue actions (#3409) This will fix merge-queue CI actions as this is a required CI action, but wasn't being run for jobs entering the queue. --- .github/workflows/extra_jobs.yml | 6 ++++-- .github/workflows/toolchain-upgrade.yml | 27 ++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/extra_jobs.yml b/.github/workflows/extra_jobs.yml index 5d92f3fff53c..ddf242ba3956 100644 --- a/.github/workflows/extra_jobs.yml +++ b/.github/workflows/extra_jobs.yml @@ -18,7 +18,9 @@ # See for more details. name: Kani Extra -on: pull_request_target +on: + pull_request_target: + merge_group: jobs: # Keep this job minimal since it requires extra permission @@ -45,5 +47,5 @@ jobs: name: Verification Benchmarks needs: auto-label permissions: {} - if: contains(needs.auto-label.outputs.all-labels, 'Z-BenchCI') + if: ${{ contains(needs.auto-label.outputs.all-labels, 'Z-BenchCI') && github.event_name != 'merge_group' }} uses: ./.github/workflows/bench.yml diff --git a/.github/workflows/toolchain-upgrade.yml b/.github/workflows/toolchain-upgrade.yml index a4b95ea195f0..1b33e3135316 100644 --- a/.github/workflows/toolchain-upgrade.yml +++ b/.github/workflows/toolchain-upgrade.yml @@ -36,6 +36,7 @@ jobs: run: git clean -f - name: Create Pull Request + id: create_pr if: ${{ env.next_step == 'create_pr' }} uses: peter-evans/create-pull-request@v6 with: @@ -47,14 +48,26 @@ jobs: Update Rust toolchain from nightly-${{ env.current_toolchain_date }} to nightly-${{ env.next_toolchain_date }} without any other source changes. - This is an automatically generated pull request. If any of the CI checks fail, - manual intervention is required. In such a case, review the changes at - https://github.com/rust-lang/rust from - https://github.com/rust-lang/rust/commit/${{ env.current_toolchain_hash }} up to - https://github.com/rust-lang/rust/commit/${{ env.next_toolchain_hash }}. The log - for this commit range is: + - name: Add debugging hints + if: ${{ steps.create_pr.outputs.pull-request-number }} + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.createComment({ + issue_number: ${{ steps.create_pr.outputs.pull-request-number }}, + owner: context.repo.owner, + repo: context.repo.repo, + body: > + This is an automatically generated pull request. If any of the CI checks fail, + manual intervention is required. In such a case, review the changes at + https://github.com/rust-lang/rust from + https://github.com/rust-lang/rust/commit/${{ env.current_toolchain_hash }} up to + https://github.com/rust-lang/rust/commit/${{ env.next_toolchain_hash }}. The log + for this commit range is: + + ${{ env.git_log }} + }) - ${{ env.git_log }} - name: Create Issue if: ${{ env.next_step == 'create_issue' }} uses: dacbd/create-issue-action@main From f71e1aba06ec907e59382d8ee4952ee9e5699b36 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:45:23 -0700 Subject: [PATCH 16/28] Automatic toolchain upgrade to nightly-2024-08-03 (#3410) Update Rust toolchain from nightly-2024-08-02 to nightly-2024-08-03 without any other source changes. Co-authored-by: celinval <35149715+celinval@users.noreply.github.com> --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0c0e9e9f196c..ab901a10d7e4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-02" +channel = "nightly-2024-08-03" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 03e2df36ca3c6d53e521e0a7145e1660e2f62ec2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:26:09 -0700 Subject: [PATCH 17/28] Automatic cargo update to 2024-08-05 (#3413) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 94 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d68b8db21918..dcd7b057b3d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,7 +59,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -69,7 +69,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -100,6 +100,12 @@ dependencies = [ "which", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "camino" version = "1.1.7" @@ -140,9 +146,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -150,9 +156,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -162,9 +168,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -223,7 +229,7 @@ dependencies = [ "lazy_static", "libc", "unicode-width", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -334,7 +340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -390,14 +396,14 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", @@ -678,7 +684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" dependencies = [ "log", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -724,9 +730,12 @@ checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro-error" @@ -831,9 +840,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -889,7 +898,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -960,9 +969,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.121" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", "memchr", @@ -1088,14 +1097,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1130,9 +1140,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -1142,18 +1152,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.17" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", @@ -1300,9 +1310,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "6.0.1" +version = "6.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" dependencies = [ "either", "home", @@ -1328,11 +1338,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1350,6 +1360,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1416,9 +1435,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.16" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -1435,6 +1454,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] From b9293db15bcdd6925fe757955d716456c6ea67b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:07:30 +0200 Subject: [PATCH 18/28] Bump tests/perf/s2n-quic from `75afd77` to `445f73b` (#3414) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `75afd77` to `445f73b`.

Commits
  • 445f73b chore(s2n-quic): remove bytes pin and fix new clippy lints (#2291)
  • 072452e chore(s2n-quic): release 1.44.0 and pin bytes dependency (#2290)
  • cc4e6d0 chore: change further solvers in harnesses used with Kani (#2284)
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/perf/s2n-quic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/perf/s2n-quic b/tests/perf/s2n-quic index 75afd77dfa88..445f73b27eae 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 75afd77dfa88d696900f12ee747409ddb208a745 +Subproject commit 445f73b27eae529bb895a7678968e4c0c215ef8a From b24c01b7da17b55d6471b2893b98ec78e0275d3a Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Mon, 5 Aug 2024 18:02:11 +0200 Subject: [PATCH 19/28] Toolchain auto-update: fix comment posting (#3415) actions/github-script uses Javascript, so we need to use Javascript rather than YAML syntax in the body of the script. Also, update to version 7 to avoid deprecation warnings. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .github/workflows/toolchain-upgrade.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/toolchain-upgrade.yml b/.github/workflows/toolchain-upgrade.yml index 1b33e3135316..b11f4e6b591a 100644 --- a/.github/workflows/toolchain-upgrade.yml +++ b/.github/workflows/toolchain-upgrade.yml @@ -50,22 +50,21 @@ jobs: - name: Add debugging hints if: ${{ steps.create_pr.outputs.pull-request-number }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | github.rest.issues.createComment({ issue_number: ${{ steps.create_pr.outputs.pull-request-number }}, owner: context.repo.owner, repo: context.repo.repo, - body: > - This is an automatically generated pull request. If any of the CI checks fail, + body: `This is an automatically generated pull request. If any of the CI checks fail, manual intervention is required. In such a case, review the changes at https://github.com/rust-lang/rust from https://github.com/rust-lang/rust/commit/${{ env.current_toolchain_hash }} up to https://github.com/rust-lang/rust/commit/${{ env.next_toolchain_hash }}. The log for this commit range is: - ${{ env.git_log }} + ${{ env.git_log }}` }) - name: Create Issue From 5424bc58ebe2d97d6746a383c3384cfe7f67fcf0 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Mon, 5 Aug 2024 12:14:53 -0400 Subject: [PATCH 20/28] Remove assigns clause for ZST pointers (#3417) This PR filters out ZST pointee types when generating CMBC assigns clauses for contracts. This prevents CMBC from complaining that the pointer doesn't point to a valid allocation. Resolves #3181 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../codegen_cprover_gotoc/codegen/contract.rs | 10 +++++++-- .../modifies/zst_pass.expected | 5 +++++ .../function-contract/modifies/zst_pass.rs | 22 +++++++++++++++++++ tests/std-checks/core/mem.expected | 6 +---- tests/std-checks/core/ptr.expected | 3 +-- tests/std-checks/core/src/mem.rs | 2 -- tests/std-checks/core/src/ptr.rs | 2 -- 7 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 tests/expected/function-contract/modifies/zst_pass.expected create mode 100644 tests/expected/function-contract/modifies/zst_pass.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index b210b2c9333e..a871cd58f94f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::codegen_cprover_gotoc::GotocCtx; +use crate::codegen_cprover_gotoc::{codegen::ty_stable::pointee_type_stable, GotocCtx}; use crate::kani_middle::attributes::KaniAttributes; use cbmc::goto_program::FunctionContract; use cbmc::goto_program::{Expr, Lambda, Location, Type}; @@ -160,11 +160,17 @@ impl<'tcx> GotocCtx<'tcx> { let TyKind::RigidTy(RigidTy::Tuple(modifies_tys)) = modifies_ty.kind() else { unreachable!("found {:?}", modifies_ty.kind()) }; + + for ty in &modifies_tys { + assert!(ty.kind().is_any_ptr(), "Expected pointer, but found {}", ty); + } + let assigns: Vec<_> = modifies_tys .into_iter() + // do not attempt to dereference (and assign) a ZST + .filter(|ty| !self.is_zst_stable(pointee_type_stable(*ty).unwrap())) .enumerate() .map(|(idx, ty)| { - assert!(ty.kind().is_any_ptr(), "Expected pointer, but found {}", ty); let ptr = modifies_args.clone().member(idx.to_string(), &self.symbol_table); if self.is_fat_pointer_stable(ty) { let unref = match ty.kind() { diff --git a/tests/expected/function-contract/modifies/zst_pass.expected b/tests/expected/function-contract/modifies/zst_pass.expected new file mode 100644 index 000000000000..ba2a8eb7decd --- /dev/null +++ b/tests/expected/function-contract/modifies/zst_pass.expected @@ -0,0 +1,5 @@ +.assertion\ +- Status: SUCCESS\ +- Description: "ptr NULL or writable up to size"\ + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/zst_pass.rs b/tests/expected/function-contract/modifies/zst_pass.rs new file mode 100644 index 000000000000..89efd85a75f9 --- /dev/null +++ b/tests/expected/function-contract/modifies/zst_pass.rs @@ -0,0 +1,22 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::modifies(dst)] +pub unsafe fn replace(dst: *mut T, src: T) -> T { + std::ptr::replace(dst, src) +} + +#[kani::proof_for_contract(replace)] +pub fn check_replace_unit() { + check_replace_impl::<()>(); +} + +fn check_replace_impl() { + let mut dst = T::any(); + let orig = dst.clone(); + let src = T::any(); + let ret = unsafe { replace(&mut dst, src.clone()) }; + assert_eq!(ret, orig); + assert_eq!(dst, src); +} diff --git a/tests/std-checks/core/mem.expected b/tests/std-checks/core/mem.expected index 1484c83901fc..285a887307f8 100644 --- a/tests/std-checks/core/mem.expected +++ b/tests/std-checks/core/mem.expected @@ -1,7 +1,3 @@ Checking harness mem::verify::check_swap_unit... -Failed Checks: ptr NULL or writable up to size - -Summary: -Verification failed for - mem::verify::check_swap_unit -Complete - 6 successfully verified harnesses, 1 failures, 7 total. +Complete - 7 successfully verified harnesses, 0 failures, 7 total. diff --git a/tests/std-checks/core/ptr.expected b/tests/std-checks/core/ptr.expected index 43d3bd6baf60..d6c2aff26442 100644 --- a/tests/std-checks/core/ptr.expected +++ b/tests/std-checks/core/ptr.expected @@ -1,4 +1,3 @@ Summary: -Verification failed for - ptr::verify::check_replace_unit Verification failed for - ptr::verify::check_as_ref_dangling -Complete - 4 successfully verified harnesses, 2 failures, 6 total. +Complete - 5 successfully verified harnesses, 1 failures, 6 total. diff --git a/tests/std-checks/core/src/mem.rs b/tests/std-checks/core/src/mem.rs index b0400d0a75f5..67b03d7a6188 100644 --- a/tests/std-checks/core/src/mem.rs +++ b/tests/std-checks/core/src/mem.rs @@ -48,8 +48,6 @@ mod verify { contracts::swap(&mut x, &mut y) } - /// FIX-ME: Modifies clause fail with pointer to ZST. - /// #[kani::proof_for_contract(contracts::swap)] pub fn check_swap_unit() { let mut x: () = kani::any(); diff --git a/tests/std-checks/core/src/ptr.rs b/tests/std-checks/core/src/ptr.rs index 49cf9e168214..1560889b56b3 100644 --- a/tests/std-checks/core/src/ptr.rs +++ b/tests/std-checks/core/src/ptr.rs @@ -89,8 +89,6 @@ mod verify { let _rf = unsafe { contracts::as_ref(&non_null) }; } - /// FIX-ME: Modifies clause fail with pointer to ZST. - /// #[kani::proof_for_contract(contracts::replace)] pub fn check_replace_unit() { check_replace_impl::<()>(); From f2831f46641ed492401a59cb77cc95cd2fad6391 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 5 Aug 2024 09:32:39 -0700 Subject: [PATCH 21/28] Instrumentation for delayed UB stemming from uninitialized memory (#3374) As #3324 mentioned, delayed UB triggered by accessing uninitialized memory was previously mitigated by injecting `assert!(false)` for every possible source of it; this PR adds all the necessary infrastructure and minimal support for detecting it properly. The process of detecting and instrumenting places is as follows: - Find all sources of delayed uninitialized memory UB (mutable pointer casts with different pointee padding and copy intrinsics); - Compute conservative aliasing graph between all memory places reachable from each harness; - Instrument all places pointed to by each source we found in step 1. Not all language constructs are currently supported -- see the tracking issue (#3300) for the full list of limitations. Resolves #3324 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Celina G. Val --- .../compiler_interface.rs | 18 +- kani-compiler/src/kani_middle/mod.rs | 1 + .../src/kani_middle/points_to/mod.rs | 11 + .../points_to/points_to_analysis.rs | 654 +++++++++++++++++ .../kani_middle/points_to/points_to_graph.rs | 222 ++++++ .../delayed_ub/initial_target_visitor.rs | 152 ++++ .../delayed_ub/instrumentation_visitor.rs | 137 ++++ .../transform/check_uninit/delayed_ub/mod.rs | 139 ++++ .../kani_middle/transform/check_uninit/mod.rs | 175 ++--- .../transform/check_uninit/ptr_uninit/mod.rs | 130 ++++ .../{ => ptr_uninit}/uninit_visitor.rs | 306 +++----- .../check_uninit/relevant_instruction.rs | 130 ++++ .../transform/check_uninit/ty_layout.rs | 55 +- .../src/kani_middle/transform/internal_mir.rs | 656 ++++++++++++++++++ .../kani_middle/transform/kani_intrinsics.rs | 7 +- .../src/kani_middle/transform/mod.rs | 6 +- kani-compiler/src/main.rs | 1 + .../uninit/access-padding-via-cast/expected | 2 +- .../delayed-ub-transmute.rs | 14 - .../uninit/delayed-ub-transmute/expected | 5 - .../expected/uninit/delayed-ub/delayed-ub.rs | 160 ++++- tests/expected/uninit/delayed-ub/expected | 48 +- tests/expected/uninit/intrinsics/expected | 84 +-- 23 files changed, 2690 insertions(+), 423 deletions(-) create mode 100644 kani-compiler/src/kani_middle/points_to/mod.rs create mode 100644 kani-compiler/src/kani_middle/points_to/points_to_analysis.rs create mode 100644 kani-compiler/src/kani_middle/points_to/points_to_graph.rs create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/mod.rs rename kani-compiler/src/kani_middle/transform/check_uninit/{ => ptr_uninit}/uninit_visitor.rs (71%) create mode 100644 kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs create mode 100644 kani-compiler/src/kani_middle/transform/internal_mir.rs delete mode 100644 tests/expected/uninit/delayed-ub-transmute/delayed-ub-transmute.rs delete mode 100644 tests/expected/uninit/delayed-ub-transmute/expected diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 0a92e07f4ab4..9f700192f2f2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -87,6 +87,13 @@ impl GotocCodegenBackend { check_contract: Option, mut transformer: BodyTransformation, ) -> (GotocCtx<'tcx>, Vec, Option) { + // This runs reachability analysis before global passes are applied. + // + // Alternatively, we could run reachability only once after the global passes are applied + // and resolve the necessary dependencies inside the passes on the fly. This, however, has a + // disadvantage of not having a precomputed call graph for the global passes to use. The + // call graph could be used, for example, in resolving function pointer or vtable calls for + // global passes that need this. let (items, call_graph) = with_timer( || collect_reachable_items(tcx, &mut transformer, starting_items), "codegen reachability analysis", @@ -115,6 +122,13 @@ impl GotocCodegenBackend { call_graph, ); + // Re-collect reachable items after global transformations were applied. This is necessary + // since global pass could add extra calls to instrumentation. + let (items, _) = with_timer( + || collect_reachable_items(tcx, &mut transformer, starting_items), + "codegen reachability analysis (second pass)", + ); + // Follow rustc naming convention (cx is abbrev for context). // https://rustc-dev-guide.rust-lang.org/conventions.html#naming-conventions let mut gcx = @@ -260,8 +274,8 @@ impl CodegenBackend for GotocCodegenBackend { for unit in units.iter() { // We reset the body cache for now because each codegen unit has different // configurations that affect how we transform the instance body. - let mut transformer = BodyTransformation::new(&queries, tcx, &unit); for harness in &unit.harnesses { + let transformer = BodyTransformation::new(&queries, tcx, &unit); let model_path = units.harness_model_path(*harness).unwrap(); let contract_metadata = contract_metadata_for_harness(tcx, harness.def.def_id()); @@ -273,7 +287,7 @@ impl CodegenBackend for GotocCodegenBackend { contract_metadata, transformer, ); - transformer = results.extend(gcx, items, None); + results.extend(gcx, items, None); if let Some(assigns_contract) = contract_info { modifies_instances.push((*harness, assigns_contract)); } diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index a7a512c86de3..a5d077d9c16e 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -32,6 +32,7 @@ pub mod codegen_units; pub mod coercion; mod intrinsics; pub mod metadata; +pub mod points_to; pub mod provide; pub mod reachability; pub mod resolve; diff --git a/kani-compiler/src/kani_middle/points_to/mod.rs b/kani-compiler/src/kani_middle/points_to/mod.rs new file mode 100644 index 000000000000..21e8bdffc7b8 --- /dev/null +++ b/kani-compiler/src/kani_middle/points_to/mod.rs @@ -0,0 +1,11 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This module contains points-to analysis primitives, such as the graph and types representing its +//! nodes, and the analysis itself. + +mod points_to_analysis; +mod points_to_graph; + +pub use points_to_analysis::run_points_to_analysis; +pub use points_to_graph::{MemLoc, PointsToGraph}; diff --git a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs new file mode 100644 index 000000000000..640318ccb584 --- /dev/null +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -0,0 +1,654 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Implementation of the points-to analysis using Rust's native dataflow framework. This provides +//! necessary aliasing information for instrumenting delayed UB later on. +//! +//! The analysis uses Rust's dataflow framework by implementing appropriate traits to leverage the +//! existing fixpoint solver infrastructure. The main trait responsible for the dataflow analysis +//! behavior is `rustc_mir_dataflow::Analysis`: it provides two methods that are responsible for +//! handling statements and terminators, which we implement. +//! +//! The analysis proceeds by looking at each instruction in the dataflow order and collecting all +//! possible aliasing relations that the instruction introduces. If a terminator is a function call, +//! the analysis recurs into the function and then joins the information retrieved from it into the +//! original graph. +//! +//! For each instruction, the analysis first resolves dereference projections for each place to +//! determine which places it could point to. This is done by finding a set of successors in the +//! graph for each dereference projection. +//! +//! Then, the analysis adds the appropriate edges into the points-to graph. It proceeds until there +//! is no new information to be discovered. +//! +//! Currently, the analysis is not field-sensitive: e.g., if a field of a place aliases to some +//! other place, we treat it as if the place itself aliases to another place. + +use crate::kani_middle::{ + points_to::{MemLoc, PointsToGraph}, + reachability::CallGraph, + transform::RustcInternalMir, +}; +use rustc_ast::Mutability; +use rustc_middle::{ + mir::{ + BasicBlock, BinOp, Body, CallReturnPlaces, Location, NonDivergingIntrinsic, Operand, Place, + ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorEdges, + TerminatorKind, + }, + ty::{Instance, InstanceKind, List, ParamEnv, TyCtxt, TyKind}, +}; +use rustc_mir_dataflow::{Analysis, AnalysisDomain, Forward, JoinSemiLattice}; +use rustc_smir::rustc_internal; +use rustc_span::{source_map::Spanned, DUMMY_SP}; +use stable_mir::mir::{mono::Instance as StableInstance, Body as StableBody}; +use std::collections::HashSet; + +/// Main points-to analysis object. +struct PointsToAnalysis<'a, 'tcx> { + instance: Instance<'tcx>, + body: &'a Body<'tcx>, + tcx: TyCtxt<'tcx>, + /// This will be used in the future to resolve function pointer and vtable calls. Currently, we + /// can resolve call graph edges just by looking at the terminators and erroring if we can't + /// resolve the callee. + call_graph: &'a CallGraph, + /// This graph should contain a subset of the points-to graph reachable from function arguments. + /// For the entry function it will be empty (as it supposedly does not have any parameters). + initial_graph: PointsToGraph<'tcx>, +} + +/// Public points-to analysis entry point. Performs the analysis on a body, outputting the graph +/// containing aliasing information of the body itself and any body reachable from it. +pub fn run_points_to_analysis<'tcx>( + body: &StableBody, + tcx: TyCtxt<'tcx>, + instance: StableInstance, + call_graph: &CallGraph, +) -> PointsToGraph<'tcx> { + // Dataflow analysis does not yet work with StableMIR, so need to perform backward + // conversion. + let internal_instance = rustc_internal::internal(tcx, instance); + let internal_body = body.internal_mir(tcx); + PointsToAnalysis::run( + &internal_body, + tcx, + internal_instance, + call_graph, + PointsToGraph::empty(), + ) +} + +impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { + /// Perform the analysis on a body, outputting the graph containing aliasing information of the + /// body itself and any body reachable from it. + pub fn run( + body: &'a Body<'tcx>, + tcx: TyCtxt<'tcx>, + instance: Instance<'tcx>, + call_graph: &'a CallGraph, + initial_graph: PointsToGraph<'tcx>, + ) -> PointsToGraph<'tcx> { + let analysis = Self { body, tcx, instance, call_graph, initial_graph }; + // This creates a fixpoint solver using the initial graph, the body, and extra information + // and solves the dataflow problem, producing the cursor, which contains dataflow state for + // each instruction in the body. + let mut cursor = + analysis.into_engine(tcx, body).iterate_to_fixpoint().into_results_cursor(body); + // We collect dataflow state at each `Return` terminator to determine the full aliasing + // graph for the function. This is sound since those are the only places where the function + // finishes, so the dataflow state at those places will be a union of dataflow states + // preceding to it, which means every possible execution is taken into account. + let mut results = PointsToGraph::empty(); + for (idx, bb) in body.basic_blocks.iter().enumerate() { + if let TerminatorKind::Return = bb.terminator().kind { + // Switch the cursor to the end of the block ending with `Return`. + cursor.seek_to_block_end(idx.into()); + // Retrieve the dataflow state and join into the results graph. + results.join(&cursor.get().clone()); + } + } + results + } +} + +impl<'a, 'tcx> AnalysisDomain<'tcx> for PointsToAnalysis<'a, 'tcx> { + /// Dataflow state at each instruction. + type Domain = PointsToGraph<'tcx>; + + type Direction = Forward; + + const NAME: &'static str = "PointsToAnalysis"; + + /// Dataflow state instantiated at the beginning of each basic block, before the state from + /// previous basic blocks gets joined into it. + fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { + PointsToGraph::empty() + } + + /// Dataflow state instantiated at the entry into the body; this should be the initial dataflow + /// graph. + fn initialize_start_block(&self, _body: &Body<'tcx>, state: &mut Self::Domain) { + state.join(&self.initial_graph.clone()); + } +} + +impl<'a, 'tcx> Analysis<'tcx> for PointsToAnalysis<'a, 'tcx> { + /// Update current dataflow state based on the information we can infer from the given + /// statement. + fn apply_statement_effect( + &mut self, + state: &mut Self::Domain, + statement: &Statement<'tcx>, + _location: Location, + ) { + // The only two statements that can introduce new aliasing information are assignments and + // copies using `copy_nonoverlapping`. + match &statement.kind { + StatementKind::Assign(assign_box) => { + let (place, rvalue) = *assign_box.clone(); + // Resolve all dereference projections for the lvalue. + let lvalue_set = state.resolve_place(place, self.instance); + // Determine all places rvalue could point to. + let rvalue_set = self.successors_for_rvalue(state, rvalue); + // Create an edge between all places which could be lvalue and all places rvalue + // could be pointing to. + state.extend(&lvalue_set, &rvalue_set); + } + StatementKind::Intrinsic(non_diverging_intrinsic) => { + match *non_diverging_intrinsic.clone() { + NonDivergingIntrinsic::CopyNonOverlapping(copy_nonoverlapping) => { + // Copy between the values pointed by `*const a` and `*mut b` is + // semantically equivalent to *b = *a with respect to aliasing. + self.apply_copy_effect( + state, + copy_nonoverlapping.src.clone(), + copy_nonoverlapping.dst.clone(), + ); + } + NonDivergingIntrinsic::Assume(..) => { /* This is a no-op. */ } + } + } + StatementKind::FakeRead(..) + | StatementKind::SetDiscriminant { .. } + | StatementKind::Deinit(..) + | StatementKind::StorageLive(..) + | StatementKind::StorageDead(..) + | StatementKind::Retag(..) + | StatementKind::PlaceMention(..) + | StatementKind::AscribeUserType(..) + | StatementKind::Coverage(..) + | StatementKind::ConstEvalCounter + | StatementKind::Nop => { /* This is a no-op with regard to aliasing. */ } + } + } + + fn apply_terminator_effect<'mir>( + &mut self, + state: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + if let TerminatorKind::Call { func, args, destination, .. } = &terminator.kind { + // Attempt to resolve callee. For now, we panic if the callee cannot be resolved (e.g., + // if a function pointer call is used), but we could leverage the call graph to resolve + // it. + let instance = match try_resolve_instance(self.body, func, self.tcx) { + Ok(instance) => instance, + Err(reason) => { + unimplemented!("{reason}") + } + }; + match instance.def { + // Intrinsics could introduce aliasing edges we care about, so need to handle them. + InstanceKind::Intrinsic(def_id) => { + match self.tcx.intrinsic(def_id).unwrap().name.to_string().as_str() { + name if name.starts_with("atomic") => { + match name { + // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + name if name.starts_with("atomic_cxchg") => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `{name}`" + ); + assert!(matches!( + args[0].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Mut) + )); + let src_set = + self.successors_for_operand(state, args[2].node.clone()); + let dst_set = + self.successors_for_deref(state, args[0].node.clone()); + let destination_set = + state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&dst_set)); + state.extend(&dst_set, &src_set); + } + // All `atomic_load` intrinsics take `src` as an argument. + // This is equivalent to `destination = *src`. + name if name.starts_with("atomic_load") => { + assert_eq!( + args.len(), + 1, + "Unexpected number of arguments for `{name}`" + ); + assert!(matches!( + args[0].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Not) + )); + let src_set = + self.successors_for_deref(state, args[0].node.clone()); + let destination_set = + state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&src_set)); + } + // All `atomic_store` intrinsics take `dst, val` as arguments. + // This is equivalent to `*dst = val`. + name if name.starts_with("atomic_store") => { + assert_eq!( + args.len(), + 2, + "Unexpected number of arguments for `{name}`" + ); + assert!(matches!( + args[0].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Mut) + )); + let dst_set = + self.successors_for_deref(state, args[0].node.clone()); + let val_set = + self.successors_for_operand(state, args[1].node.clone()); + state.extend(&dst_set, &val_set); + } + // All other `atomic` intrinsics take `dst, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + _ => { + assert_eq!( + args.len(), + 2, + "Unexpected number of arguments for `{name}`" + ); + assert!(matches!( + args[0].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Mut) + )); + let src_set = + self.successors_for_operand(state, args[1].node.clone()); + let dst_set = + self.successors_for_deref(state, args[0].node.clone()); + let destination_set = + state.resolve_place(*destination, self.instance); + state.extend(&destination_set, &state.successors(&dst_set)); + state.extend(&dst_set, &src_set); + } + }; + } + // Similar to `copy_nonoverlapping`, argument order is `src`, `dst`, `count`. + "copy" => { + assert_eq!(args.len(), 3, "Unexpected number of arguments for `copy`"); + assert!(matches!( + args[0].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Not) + )); + assert!(matches!( + args[1].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Mut) + )); + self.apply_copy_effect( + state, + args[0].node.clone(), + args[1].node.clone(), + ); + } + // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. + "volatile_copy_memory" | "volatile_copy_nonoverlapping_memory" => { + assert_eq!(args.len(), 3, "Unexpected number of arguments for `copy`"); + assert!(matches!( + args[0].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Mut) + )); + assert!(matches!( + args[1].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Not) + )); + self.apply_copy_effect( + state, + args[1].node.clone(), + args[0].node.clone(), + ); + } + // Semantically equivalent to dest = *a + "volatile_load" | "unaligned_volatile_load" => { + assert_eq!( + args.len(), + 1, + "Unexpected number of arguments for `volatile_load`" + ); + assert!(matches!( + args[0].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Not) + )); + // Destination of the return value. + let lvalue_set = state.resolve_place(*destination, self.instance); + let rvalue_set = self.successors_for_deref(state, args[0].node.clone()); + state.extend(&lvalue_set, &state.successors(&rvalue_set)); + } + // Semantically equivalent *a = b. + "volatile_store" | "unaligned_volatile_store" => { + assert_eq!( + args.len(), + 2, + "Unexpected number of arguments for `volatile_store`" + ); + assert!(matches!( + args[0].node.ty(self.body, self.tcx).kind(), + TyKind::RawPtr(_, Mutability::Mut) + )); + let lvalue_set = self.successors_for_deref(state, args[0].node.clone()); + let rvalue_set = + self.successors_for_operand(state, args[1].node.clone()); + state.extend(&lvalue_set, &rvalue_set); + } + _ => { + // TODO: this probably does not handle all relevant intrinsics, so more + // need to be added. For more information, see: + // https://github.com/model-checking/kani/issues/3300 + if self.tcx.is_mir_available(def_id) { + self.apply_regular_call_effect(state, instance, args, destination); + } + } + } + } + _ => { + if self.tcx.is_foreign_item(instance.def_id()) { + match self + .tcx + .def_path_str_with_args(instance.def_id(), instance.args) + .as_str() + { + // This is an internal function responsible for heap allocation, + // which creates a new node we need to add to the points-to graph. + "alloc::alloc::__rust_alloc" | "alloc::alloc::__rust_alloc_zeroed" => { + let lvalue_set = state.resolve_place(*destination, self.instance); + let rvalue_set = HashSet::from([MemLoc::new_heap_allocation( + self.instance, + location, + )]); + state.extend(&lvalue_set, &rvalue_set); + } + _ => {} + } + } else { + // Otherwise, handle this as a regular function call. + self.apply_regular_call_effect(state, instance, args, destination); + } + } + } + }; + terminator.edges() + } + + /// We don't care about this and just need to implement this to implement the trait. + fn apply_call_return_effect( + &mut self, + _state: &mut Self::Domain, + _block: BasicBlock, + _return_places: CallReturnPlaces<'_, 'tcx>, + ) { + } +} + +/// Try retrieving instance for the given function operand. +fn try_resolve_instance<'tcx>( + body: &Body<'tcx>, + func: &Operand<'tcx>, + tcx: TyCtxt<'tcx>, +) -> Result, String> { + let ty = func.ty(body, tcx); + match ty.kind() { + TyKind::FnDef(def, args) => { + // Span here is used for error-reporting, which we don't expect to encounter anyway, so + // it is ok to use a dummy. + Ok(Instance::expect_resolve(tcx, ParamEnv::reveal_all(), *def, &args, DUMMY_SP)) + } + _ => Err(format!( + "Kani was not able to resolve the instance of the function operand `{ty:?}`. Currently, memory initialization checks in presence of function pointers and vtable calls are not supported. For more information about planned support, see https://github.com/model-checking/kani/issues/3300." + )), + } +} + +impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { + /// Update the analysis state according to the operation, which is semantically equivalent to `*to = *from`. + fn apply_copy_effect( + &self, + state: &mut PointsToGraph<'tcx>, + from: Operand<'tcx>, + to: Operand<'tcx>, + ) { + let lvalue_set = self.successors_for_deref(state, to); + let rvalue_set = self.successors_for_deref(state, from); + state.extend(&lvalue_set, &state.successors(&rvalue_set)); + } + + /// Find all places where the operand could point to at the current stage of the program. + fn successors_for_operand( + &self, + state: &mut PointsToGraph<'tcx>, + operand: Operand<'tcx>, + ) -> HashSet> { + match operand { + Operand::Copy(place) | Operand::Move(place) => { + // Find all places which are pointed to by the place. + state.successors(&state.resolve_place(place, self.instance)) + } + Operand::Constant(const_operand) => { + // Constants could point to a static, so need to check for that. + if let Some(static_def_id) = const_operand.check_static_ptr(self.tcx) { + HashSet::from([MemLoc::new_static_allocation(static_def_id)]) + } else { + HashSet::new() + } + } + } + } + + /// Find all places where the deref of the operand could point to at the current stage of the program. + fn successors_for_deref( + &self, + state: &mut PointsToGraph<'tcx>, + operand: Operand<'tcx>, + ) -> HashSet> { + match operand { + Operand::Copy(place) | Operand::Move(place) => state.resolve_place( + place.project_deeper(&[ProjectionElem::Deref], self.tcx), + self.instance, + ), + Operand::Constant(const_operand) => { + // Constants could point to a static, so need to check for that. + if let Some(static_def_id) = const_operand.check_static_ptr(self.tcx) { + HashSet::from([MemLoc::new_static_allocation(static_def_id)]) + } else { + HashSet::new() + } + } + } + } + + /// Update the analysis state according to the regular function call. + fn apply_regular_call_effect( + &mut self, + state: &mut PointsToGraph<'tcx>, + instance: Instance<'tcx>, + args: &[Spanned>], + destination: &Place<'tcx>, + ) { + // Here we simply call another function, so need to retrieve internal body for it. + let new_body = { + let stable_instance = rustc_internal::stable(instance); + let stable_body = stable_instance.body().unwrap(); + stable_body.internal_mir(self.tcx) + }; + + // In order to be efficient, create a new graph for the function call analysis, which only + // contains arguments and statics and anything transitively reachable from them. + let mut initial_graph = PointsToGraph::empty(); + for arg in args.iter() { + match arg.node { + Operand::Copy(place) | Operand::Move(place) => { + initial_graph + .join(&state.transitive_closure(state.resolve_place(place, self.instance))); + } + Operand::Constant(_) => {} + } + } + + // A missing link is the connections between the arguments in the caller and parameters in + // the callee, add it to the graph. + if self.tcx.is_closure_like(instance.def.def_id()) { + // This means we encountered a closure call. + // Sanity check. The first argument is the closure itself and the second argument is the tupled arguments from the caller. + assert!(args.len() == 2); + // First, connect all upvars. + let lvalue_set = HashSet::from([MemLoc::new_stack_allocation( + instance, + Place { local: 1usize.into(), projection: List::empty() }, + )]); + let rvalue_set = self.successors_for_operand(state, args[0].node.clone()); + initial_graph.extend(&lvalue_set, &rvalue_set); + // Then, connect the argument tuple to each of the spread arguments. + let spread_arg_operand = args[1].node.clone(); + for i in 0..new_body.arg_count { + let lvalue_set = HashSet::from([MemLoc::new_stack_allocation( + instance, + Place { + local: (i + 1).into(), // Since arguments in the callee are starting with 1, account for that. + projection: List::empty(), + }, + )]); + // This conservatively assumes all arguments alias to all parameters. + let rvalue_set = self.successors_for_operand(state, spread_arg_operand.clone()); + initial_graph.extend(&lvalue_set, &rvalue_set); + } + } else { + // Otherwise, simply connect all arguments to parameters. + for (i, arg) in args.iter().enumerate() { + let lvalue_set = HashSet::from([MemLoc::new_stack_allocation( + instance, + Place { + local: (i + 1).into(), // Since arguments in the callee are starting with 1, account for that. + projection: List::empty(), + }, + )]); + let rvalue_set = self.successors_for_operand(state, arg.node.clone()); + initial_graph.extend(&lvalue_set, &rvalue_set); + } + } + + // Run the analysis. + let new_result = + PointsToAnalysis::run(&new_body, self.tcx, instance, self.call_graph, initial_graph); + // Merge the results into the current state. + state.join(&new_result); + + // Connect the return value to the return destination. + let lvalue_set = state.resolve_place(*destination, self.instance); + let rvalue_set = HashSet::from([MemLoc::new_stack_allocation( + instance, + Place { local: 0usize.into(), projection: List::empty() }, + )]); + state.extend(&lvalue_set, &state.successors(&rvalue_set)); + } + + /// Find all places where the rvalue could point to at the current stage of the program. + fn successors_for_rvalue( + &self, + state: &mut PointsToGraph<'tcx>, + rvalue: Rvalue<'tcx>, + ) -> HashSet> { + match rvalue { + // Using the operand unchanged requires determining where it could point, which + // `successors_for_operand` does. + Rvalue::Use(operand) + | Rvalue::ShallowInitBox(operand, _) + | Rvalue::Cast(_, operand, _) + | Rvalue::Repeat(operand, ..) => self.successors_for_operand(state, operand), + Rvalue::Ref(_, _, ref_place) | Rvalue::AddressOf(_, ref_place) => { + // Here, a reference to a place is created, which leaves the place + // unchanged. + state.resolve_place(ref_place, self.instance) + } + Rvalue::BinaryOp(bin_op, operands) => { + match bin_op { + BinOp::Offset => { + // Offsetting a pointer should still be within the boundaries of the + // same object, so we can simply use the operand unchanged. + let (ptr, _) = *operands.clone(); + self.successors_for_operand(state, ptr) + } + BinOp::Add + | BinOp::AddUnchecked + | BinOp::AddWithOverflow + | BinOp::Sub + | BinOp::SubUnchecked + | BinOp::SubWithOverflow + | BinOp::Mul + | BinOp::MulUnchecked + | BinOp::MulWithOverflow + | BinOp::Div + | BinOp::Rem + | BinOp::BitXor + | BinOp::BitAnd + | BinOp::BitOr + | BinOp::Shl + | BinOp::ShlUnchecked + | BinOp::Shr + | BinOp::ShrUnchecked => { + // While unlikely, those could be pointer addresses, so we need to + // track them. We assume that even shifted addresses will be within + // the same original object. + let (l_operand, r_operand) = *operands.clone(); + let l_operand_set = self.successors_for_operand(state, l_operand); + let r_operand_set = self.successors_for_operand(state, r_operand); + l_operand_set.union(&r_operand_set).cloned().collect() + } + BinOp::Eq + | BinOp::Lt + | BinOp::Le + | BinOp::Ne + | BinOp::Ge + | BinOp::Gt + | BinOp::Cmp => { + // None of those could yield an address as the result. + HashSet::new() + } + } + } + Rvalue::UnaryOp(_, operand) => { + // The same story from BinOp applies here, too. Need to track those things. + self.successors_for_operand(state, operand) + } + Rvalue::Len(..) | Rvalue::NullaryOp(..) | Rvalue::Discriminant(..) => { + // All of those should yield a constant. + HashSet::new() + } + Rvalue::Aggregate(_, operands) => { + // Conservatively find a union of all places mentioned here and resolve + // their pointees. + operands + .into_iter() + .flat_map(|operand| self.successors_for_operand(state, operand)) + .collect() + } + Rvalue::CopyForDeref(place) => { + // Resolve pointees of a place. + state.successors(&state.resolve_place(place, self.instance)) + } + Rvalue::ThreadLocalRef(def_id) => { + // We store a def_id of a static. + HashSet::from([MemLoc::new_static_allocation(def_id)]) + } + } + } +} diff --git a/kani-compiler/src/kani_middle/points_to/points_to_graph.rs b/kani-compiler/src/kani_middle/points_to/points_to_graph.rs new file mode 100644 index 000000000000..d2e80f24c737 --- /dev/null +++ b/kani-compiler/src/kani_middle/points_to/points_to_graph.rs @@ -0,0 +1,222 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Graph data structure to store the results of points-to analysis. + +use rustc_hir::def_id::DefId; +use rustc_middle::{ + mir::{Location, Place, ProjectionElem}, + ty::{Instance, List, TyCtxt}, +}; +use rustc_mir_dataflow::{fmt::DebugWithContext, JoinSemiLattice}; +use rustc_smir::rustc_internal; +use stable_mir::mir::{ + mono::{Instance as StableInstance, StaticDef}, + Place as StablePlace, +}; +use std::collections::{HashMap, HashSet, VecDeque}; + +/// A node in the points-to graph, which could be a place on the stack, a heap allocation, or a static. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum MemLoc<'tcx> { + /// Notice that the type of `Place` here is not restricted to references or pointers. For + /// example, we propagate aliasing information for values derived from casting a pointer to a + /// usize in order to ensure soundness, as it could later be casted back to a pointer. + Stack(Instance<'tcx>, Place<'tcx>), + /// Using a combination of the instance of the function where the allocation took place and the + /// location of the allocation inside this function implements allocation-site abstraction. + Heap(Instance<'tcx>, Location), + Static(DefId), +} + +impl<'tcx> MemLoc<'tcx> { + /// Create a memory location representing a new heap allocation site. + pub fn new_heap_allocation(instance: Instance<'tcx>, location: Location) -> Self { + MemLoc::Heap(instance, location) + } + + /// Create a memory location representing a new stack allocation. + pub fn new_stack_allocation(instance: Instance<'tcx>, place: Place<'tcx>) -> Self { + MemLoc::Stack(instance, place) + } + + /// Create a memory location representing a new static allocation. + pub fn new_static_allocation(static_def: DefId) -> Self { + MemLoc::Static(static_def) + } + + /// Create a memory location representing a new stack allocation from StableMIR values. + pub fn from_stable_stack_allocation( + instance: StableInstance, + place: StablePlace, + tcx: TyCtxt<'tcx>, + ) -> Self { + let internal_instance = rustc_internal::internal(tcx, instance); + let internal_place = rustc_internal::internal(tcx, place); + Self::new_stack_allocation(internal_instance, internal_place) + } + + /// Create a memory location representing a new static allocation from StableMIR values. + pub fn from_stable_static_allocation(static_def: StaticDef, tcx: TyCtxt<'tcx>) -> Self { + let static_def_id = rustc_internal::internal(tcx, static_def); + Self::new_static_allocation(static_def_id) + } +} + +/// Graph data structure that stores the current results of the point-to analysis. The graph is +/// directed, so having an edge between two places means that one is pointing to the other. +/// +/// For example: +/// - `a = &b` would translate to `a --> b` +/// - `a = b` would translate to `a --> {all pointees of b}` (if `a` and `b` are pointers / +/// references) +/// +/// Note that the aliasing is not field-sensitive, since the nodes in the graph are places with no +/// projections, which is sound but can be imprecise. +/// +/// For example: +/// ``` +/// let ref_pair = (&a, &b); // Will add `ref_pair --> (a | b)` edges into the graph. +/// let first = ref_pair.0; // Will add `first -> (a | b)`, which is an overapproximation. +/// ``` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PointsToGraph<'tcx> { + /// A hash map of node --> {nodes} edges. + edges: HashMap, HashSet>>, +} + +impl<'tcx> PointsToGraph<'tcx> { + pub fn empty() -> Self { + Self { edges: HashMap::new() } + } + + /// Collect all nodes which have incoming edges from `nodes`. + pub fn successors(&self, nodes: &HashSet>) -> HashSet> { + nodes.iter().flat_map(|node| self.edges.get(node).cloned().unwrap_or_default()).collect() + } + + /// For each node in `from`, add an edge to each node in `to`. + pub fn extend(&mut self, from: &HashSet>, to: &HashSet>) { + for node in from.iter() { + let node_pointees = self.edges.entry(*node).or_default(); + node_pointees.extend(to.iter()); + } + } + + /// Collect all places to which a given place can alias. + /// + /// We automatically resolve dereference projections here (by finding successors for each + /// dereference projection we encounter), which is valid as long as we do it for every place we + /// add to the graph. + pub fn resolve_place( + &self, + place: Place<'tcx>, + instance: Instance<'tcx>, + ) -> HashSet> { + let place_without_projections = Place { local: place.local, projection: List::empty() }; + let mut node_set = + HashSet::from([MemLoc::new_stack_allocation(instance, place_without_projections)]); + for projection in place.projection { + match projection { + ProjectionElem::Deref => { + node_set = self.successors(&node_set); + } + ProjectionElem::Field(..) + | ProjectionElem::Index(..) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::Downcast(..) + | ProjectionElem::OpaqueCast(..) + | ProjectionElem::Subtype(..) => { + /* There operations are no-ops w.r.t aliasing since we are tracking it on per-object basis. */ + } + } + } + node_set + } + + /// Stable interface for `resolve_place`. + pub fn resolve_place_stable( + &self, + place: StablePlace, + instance: StableInstance, + tcx: TyCtxt<'tcx>, + ) -> HashSet> { + let internal_place = rustc_internal::internal(tcx, place); + let internal_instance = rustc_internal::internal(tcx, instance); + self.resolve_place(internal_place, internal_instance) + } + + /// Dump the graph into a file using the graphviz format for later visualization. + pub fn dump(&self, file_path: &str) { + let mut nodes: Vec = + self.edges.keys().map(|from| format!("\t\"{:?}\"", from)).collect(); + nodes.sort(); + let nodes_str = nodes.join("\n"); + + let mut edges: Vec = self + .edges + .iter() + .flat_map(|(from, to)| { + let from = format!("\"{:?}\"", from); + to.iter().map(move |to| { + let to = format!("\"{:?}\"", to); + format!("\t{} -> {}", from.clone(), to) + }) + }) + .collect(); + edges.sort(); + let edges_str = edges.join("\n"); + + std::fs::write(file_path, format!("digraph {{\n{}\n{}\n}}", nodes_str, edges_str)).unwrap(); + } + + /// Find a transitive closure of the graph starting from a set of given locations; this also + /// includes statics. + pub fn transitive_closure(&self, targets: HashSet>) -> PointsToGraph<'tcx> { + let mut result = PointsToGraph::empty(); + // Working queue. + let mut queue = VecDeque::from_iter(targets); + // Add all statics, as they can be accessed at any point. + let statics = self.edges.keys().filter(|node| matches!(node, MemLoc::Static(_))); + queue.extend(statics); + // Add all entries. + while let Some(next_target) = queue.pop_front() { + result.edges.entry(next_target).or_insert_with(|| { + let outgoing_edges = + self.edges.get(&next_target).cloned().unwrap_or(HashSet::new()); + queue.extend(outgoing_edges.iter()); + outgoing_edges.clone() + }); + } + result + } + + /// Retrieve all places to which a given place is pointing to. + pub fn pointees_of(&self, target: &MemLoc<'tcx>) -> HashSet> { + self.edges.get(&target).unwrap_or(&HashSet::new()).clone() + } +} + +/// Since we are performing the analysis using a dataflow, we need to implement a proper monotonous +/// join operation. In our case, this is a simple union of two graphs. This "lattice" is finite, +/// because in the worst case all places will alias to all places, in which case the join will be a +/// no-op. +impl<'tcx> JoinSemiLattice for PointsToGraph<'tcx> { + fn join(&mut self, other: &Self) -> bool { + let mut updated = false; + // Check every node in the other graph. + for (from, to) in other.edges.iter() { + let existing_to = self.edges.entry(*from).or_default(); + let initial_size = existing_to.len(); + existing_to.extend(to); + let new_size = existing_to.len(); + updated |= initial_size != new_size; + } + updated + } +} + +/// This is a requirement for the fixpoint solver, and there is no derive macro for this, so +/// implement it manually. +impl<'tcx, C> DebugWithContext for PointsToGraph<'tcx> {} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs new file mode 100644 index 000000000000..11ac412703ae --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/initial_target_visitor.rs @@ -0,0 +1,152 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! This module contains the visitor responsible for collecting initial analysis targets for delayed +//! UB instrumentation. + +use crate::kani_middle::transform::check_uninit::ty_layout::tys_layout_equal_to_size; +use stable_mir::{ + mir::{ + alloc::GlobalAlloc, + mono::{Instance, InstanceKind, StaticDef}, + visit::Location, + Body, CastKind, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, Place, + Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + }, + ty::{ConstantKind, RigidTy, TyKind}, +}; + +/// Pointer, write through which might trigger delayed UB. +pub enum AnalysisTarget { + Place(Place), + Static(StaticDef), +} + +/// Visitor that finds initial analysis targets for delayed UB instrumentation. For our purposes, +/// analysis targets are *pointers* to places reading and writing from which should be tracked. +pub struct InitialTargetVisitor { + body: Body, + targets: Vec, +} + +impl InitialTargetVisitor { + pub fn new(body: Body) -> Self { + Self { body, targets: vec![] } + } + + pub fn into_targets(self) -> Vec { + self.targets + } + + pub fn push_operand(&mut self, operand: &Operand) { + match operand { + Operand::Copy(place) | Operand::Move(place) => { + self.targets.push(AnalysisTarget::Place(place.clone())); + } + Operand::Constant(constant) => { + // Extract the static from the constant. + if let ConstantKind::Allocated(allocation) = constant.const_.kind() { + for (_, prov) in &allocation.provenance.ptrs { + if let GlobalAlloc::Static(static_def) = GlobalAlloc::from(prov.0) { + self.targets.push(AnalysisTarget::Static(static_def)); + }; + } + } + } + } + } +} + +/// We implement MirVisitor to facilitate target finding, we look for: +/// - pointer casts where pointees have different padding; +/// - calls to `copy`-like intrinsics. +impl MirVisitor for InitialTargetVisitor { + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { + if let Rvalue::Cast(kind, operand, ty) = rvalue { + let operand_ty = operand.ty(self.body.locals()).unwrap(); + match kind { + CastKind::Transmute | CastKind::PtrToPtr => { + let operand_ty_kind = operand_ty.kind(); + let from_ty = match operand_ty_kind.rigid().unwrap() { + RigidTy::RawPtr(ty, _) | RigidTy::Ref(_, ty, _) => Some(ty), + _ => None, + }; + let ty_kind = ty.kind(); + let to_ty = match ty_kind.rigid().unwrap() { + RigidTy::RawPtr(ty, _) | RigidTy::Ref(_, ty, _) => Some(ty), + _ => None, + }; + if let (Some(from_ty), Some(to_ty)) = (from_ty, to_ty) { + if !tys_layout_equal_to_size(from_ty, to_ty) { + self.push_operand(operand); + } + } + } + _ => {} + }; + } + self.super_rvalue(rvalue, location); + } + + fn visit_statement(&mut self, stmt: &Statement, location: Location) { + if let StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) = + &stmt.kind + { + self.push_operand(©.dst); + } + self.super_statement(stmt, location); + } + + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + if let TerminatorKind::Call { func, args, .. } = &term.kind { + let instance = try_resolve_instance(self.body.locals(), func).unwrap(); + if instance.kind == InstanceKind::Intrinsic { + match instance.intrinsic_name().unwrap().as_str() { + "copy" => { + assert_eq!(args.len(), 3, "Unexpected number of arguments for `copy`"); + assert!(matches!( + args[0].ty(self.body.locals()).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + assert!(matches!( + args[1].ty(self.body.locals()).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + // Here, `dst` is the second argument. + self.push_operand(&args[1]); + } + "volatile_copy_memory" | "volatile_copy_nonoverlapping_memory" => { + assert_eq!( + args.len(), + 3, + "Unexpected number of arguments for `volatile_copy`" + ); + assert!(matches!( + args[0].ty(self.body.locals()).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + )); + assert!(matches!( + args[1].ty(self.body.locals()).unwrap().kind(), + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) + )); + // Here, `dst` is the first argument. + self.push_operand(&args[0]); + } + _ => {} + } + } + } + self.super_terminator(term, location); + } +} + +/// Try retrieving instance for the given function operand. +fn try_resolve_instance(locals: &[LocalDecl], func: &Operand) -> Result { + let ty = func.ty(locals).unwrap(); + match ty.kind() { + TyKind::RigidTy(RigidTy::FnDef(def, args)) => Ok(Instance::resolve(def, &args).unwrap()), + _ => Err(format!( + "Kani was not able to resolve the instance of the function operand `{ty:?}`. Currently, memory initialization checks in presence of function pointers and vtable calls are not supported. For more information about planned support, see https://github.com/model-checking/kani/issues/3300." + )), + } +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs new file mode 100644 index 000000000000..f295fc76d4bf --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/instrumentation_visitor.rs @@ -0,0 +1,137 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Visitor that collects all instructions relevant to uninitialized memory access caused by delayed +//! UB. In practice, that means collecting all instructions where the place is featured. + +use crate::kani_middle::{ + points_to::{MemLoc, PointsToGraph}, + transform::{ + body::{InsertPosition, MutableBody, SourceInstruction}, + check_uninit::{ + relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, + TargetFinder, + }, + }, +}; +use rustc_middle::ty::TyCtxt; +use stable_mir::mir::{ + mono::Instance, + visit::{Location, PlaceContext}, + BasicBlockIdx, MirVisitor, Operand, Place, Rvalue, Statement, Terminator, +}; +use std::collections::HashSet; + +pub struct InstrumentationVisitor<'a, 'tcx> { + /// Whether we should skip the next instruction, since it might've been instrumented already. + /// When we instrument an instruction, we partition the basic block, and the instruction that + /// may trigger UB becomes the first instruction of the basic block, which we need to skip + /// later. + skip_next: bool, + /// The instruction being visited at a given point. + current: SourceInstruction, + /// The target instruction that should be verified. + pub target: Option, + /// Aliasing analysis data. + points_to: &'a PointsToGraph<'tcx>, + /// The list of places we should be looking for, ignoring others + analysis_targets: &'a HashSet>, + current_instance: Instance, + tcx: TyCtxt<'tcx>, +} + +impl<'a, 'tcx> TargetFinder for InstrumentationVisitor<'a, 'tcx> { + fn find_next( + &mut self, + body: &MutableBody, + bb: BasicBlockIdx, + skip_first: bool, + ) -> Option { + self.skip_next = skip_first; + self.current = SourceInstruction::Statement { idx: 0, bb }; + self.target = None; + self.visit_basic_block(&body.blocks()[bb]); + self.target.clone() + } +} + +impl<'a, 'tcx> InstrumentationVisitor<'a, 'tcx> { + pub fn new( + points_to: &'a PointsToGraph<'tcx>, + analysis_targets: &'a HashSet>, + current_instance: Instance, + tcx: TyCtxt<'tcx>, + ) -> Self { + Self { + skip_next: false, + current: SourceInstruction::Statement { idx: 0, bb: 0 }, + target: None, + points_to, + analysis_targets, + current_instance, + tcx, + } + } + fn push_target(&mut self, source_op: MemoryInitOp) { + let target = self.target.get_or_insert_with(|| InitRelevantInstruction { + source: self.current, + after_instruction: vec![], + before_instruction: vec![], + }); + target.push_operation(source_op); + } +} + +impl<'a, 'tcx> MirVisitor for InstrumentationVisitor<'a, 'tcx> { + fn visit_statement(&mut self, stmt: &Statement, location: Location) { + if self.skip_next { + self.skip_next = false; + } else if self.target.is_none() { + // Check all inner places. + self.super_statement(stmt, location); + } + // Switch to the next statement. + let SourceInstruction::Statement { idx, bb } = self.current else { unreachable!() }; + self.current = SourceInstruction::Statement { idx: idx + 1, bb }; + } + + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + if !(self.skip_next || self.target.is_some()) { + self.current = SourceInstruction::Terminator { bb: self.current.bb() }; + self.super_terminator(term, location); + } + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) { + match rvalue { + Rvalue::AddressOf(..) | Rvalue::Ref(..) => { + // These operations are always legitimate for us. + } + _ => self.super_rvalue(rvalue, location), + } + } + + fn visit_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { + // Match the place by whatever it is pointing to and find an intersection with the targets. + if self + .points_to + .resolve_place_stable(place.clone(), self.current_instance, self.tcx) + .intersection(&self.analysis_targets) + .next() + .is_some() + { + // If we are mutating the place, initialize it. + if ptx.is_mutating() { + self.push_target(MemoryInitOp::SetRef { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } else { + // Otherwise, check its initialization. + self.push_target(MemoryInitOp::CheckRef { operand: Operand::Copy(place.clone()) }); + } + } + self.super_place(place, ptx, location) + } +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs new file mode 100644 index 000000000000..6b488569813f --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit/delayed_ub/mod.rs @@ -0,0 +1,139 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Global transformation pass that injects checks that catch delayed UB caused by uninitialized memory. + +use std::collections::HashMap; +use std::collections::HashSet; + +use crate::args::ExtraChecks; +use crate::kani_middle::{ + points_to::{run_points_to_analysis, MemLoc, PointsToGraph}, + reachability::CallGraph, + transform::{ + body::{CheckType, MutableBody}, + check_uninit::UninitInstrumenter, + BodyTransformation, GlobalPass, TransformationResult, + }, +}; +use crate::kani_queries::QueryDb; +use initial_target_visitor::{AnalysisTarget, InitialTargetVisitor}; +use instrumentation_visitor::InstrumentationVisitor; +use rustc_middle::ty::TyCtxt; +use rustc_mir_dataflow::JoinSemiLattice; +use rustc_session::config::OutputType; +use stable_mir::{ + mir::mono::{Instance, MonoItem}, + mir::MirVisitor, + ty::FnDef, +}; + +mod initial_target_visitor; +mod instrumentation_visitor; + +#[derive(Debug)] +pub struct DelayedUbPass { + pub check_type: CheckType, + pub mem_init_fn_cache: HashMap<&'static str, FnDef>, +} + +impl DelayedUbPass { + pub fn new(check_type: CheckType) -> Self { + Self { check_type, mem_init_fn_cache: HashMap::new() } + } +} + +impl GlobalPass for DelayedUbPass { + fn is_enabled(&self, query_db: &QueryDb) -> bool { + let args = query_db.args(); + args.ub_check.contains(&ExtraChecks::Uninit) + } + + fn transform( + &mut self, + tcx: TyCtxt, + call_graph: &CallGraph, + starting_items: &[MonoItem], + instances: Vec, + transformer: &mut BodyTransformation, + ) { + // Collect all analysis targets (pointers to places reading and writing from which should be + // tracked). + let targets: HashSet<_> = instances + .iter() + .flat_map(|instance| { + let body = instance.body().unwrap(); + let mut visitor = InitialTargetVisitor::new(body.clone()); + visitor.visit_body(&body); + // Convert all places into the format of aliasing graph for later comparison. + visitor.into_targets().into_iter().map(move |analysis_target| match analysis_target + { + AnalysisTarget::Place(place) => { + MemLoc::from_stable_stack_allocation(*instance, place, tcx) + } + AnalysisTarget::Static(static_def) => { + MemLoc::from_stable_static_allocation(static_def, tcx) + } + }) + }) + .collect(); + + // Only perform this analysis if there is something to analyze. + if !targets.is_empty() { + let mut analysis_targets = HashSet::new(); + let mut global_points_to_graph = PointsToGraph::empty(); + // Analyze aliasing for every harness. + for entry_item in starting_items { + // Convert each entry function into instance, if possible. + let entry_fn = match entry_item { + MonoItem::Fn(instance) => Some(*instance), + MonoItem::Static(static_def) => { + let instance: Instance = (*static_def).into(); + instance.has_body().then_some(instance) + } + MonoItem::GlobalAsm(_) => None, + }; + if let Some(instance) = entry_fn { + let body = instance.body().unwrap(); + let results = run_points_to_analysis(&body, tcx, instance, call_graph); + global_points_to_graph.join(&results); + } + } + + // Since analysis targets are *pointers*, need to get its successors for instrumentation. + for target in targets.iter() { + analysis_targets.extend(global_points_to_graph.pointees_of(target)); + } + + // If we are generating MIR, generate the points-to graph as well. + if tcx.sess.opts.output_types.contains_key(&OutputType::Mir) { + global_points_to_graph.dump("points-to.dot"); + } + + // Instrument each instance based on the final targets we found. + for instance in instances { + let mut instrumenter = UninitInstrumenter { + check_type: self.check_type.clone(), + mem_init_fn_cache: &mut self.mem_init_fn_cache, + }; + // Retrieve the body with all local instrumentation passes applied. + let body = MutableBody::from(transformer.body(tcx, instance)); + // Instrument for delayed UB. + let target_finder = InstrumentationVisitor::new( + &global_points_to_graph, + &analysis_targets, + instance, + tcx, + ); + let (instrumentation_added, body) = + instrumenter.instrument(tcx, body, instance, target_finder); + // If some instrumentation has been performed, update the cached body in the local transformer. + if instrumentation_added { + transformer.cache.entry(instance).and_modify(|transformation_result| { + *transformation_result = TransformationResult::Modified(body.into()); + }); + } + } + } + } +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs index f46c143f16d1..5c7194f879d1 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -1,33 +1,44 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -//! Implement a transformation pass that instruments the code to detect possible UB due to -//! the accesses to uninitialized memory. +//! Module containing multiple transformation passes that instrument the code to detect possible UB +//! due to the accesses to uninitialized memory. -use crate::args::ExtraChecks; -use crate::kani_middle::find_fn_def; -use crate::kani_middle::transform::body::{ - CheckType, InsertPosition, MutableBody, SourceInstruction, +use crate::kani_middle::{ + find_fn_def, + transform::body::{CheckType, InsertPosition, MutableBody, SourceInstruction}, }; -use crate::kani_middle::transform::{TransformPass, TransformationType}; -use crate::kani_queries::QueryDb; +use relevant_instruction::{InitRelevantInstruction, MemoryInitOp}; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; -use stable_mir::mir::mono::Instance; -use stable_mir::mir::{AggregateKind, Body, ConstOperand, Mutability, Operand, Place, Rvalue}; -use stable_mir::ty::{ - FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind, UintTy, +use stable_mir::{ + mir::{ + mono::Instance, AggregateKind, BasicBlockIdx, ConstOperand, Mutability, Operand, Place, + Rvalue, + }, + ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind, UintTy}, + CrateDef, }; -use stable_mir::CrateDef; use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; -use tracing::{debug, trace}; +pub use delayed_ub::DelayedUbPass; +pub use ptr_uninit::UninitPass; +pub use ty_layout::{PointeeInfo, PointeeLayout}; + +mod delayed_ub; +mod ptr_uninit; +mod relevant_instruction; mod ty_layout; -mod uninit_visitor; -pub use ty_layout::{PointeeInfo, PointeeLayout}; -use uninit_visitor::{CheckUninitVisitor, InitRelevantInstruction, MemoryInitOp}; +/// Trait that the instrumentation target providers must implement to work with the instrumenter. +trait TargetFinder { + fn find_next( + &mut self, + body: &MutableBody, + bb: BasicBlockIdx, + skip_first: bool, + ) -> Option; +} // Function bodies of those functions will not be instrumented as not to cause infinite recursion. const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ @@ -41,33 +52,24 @@ const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ "KaniSetStrPtrInitialized", ]; -/// Instrument the code with checks for uninitialized memory. +/// Instruments the code with checks for uninitialized memory, agnostic to the source of targets. #[derive(Debug)] -pub struct UninitPass { +pub struct UninitInstrumenter<'a> { pub check_type: CheckType, /// Used to cache FnDef lookups of injected memory initialization functions. - pub mem_init_fn_cache: HashMap<&'static str, FnDef>, + pub mem_init_fn_cache: &'a mut HashMap<&'static str, FnDef>, } -impl TransformPass for UninitPass { - fn transformation_type() -> TransformationType - where - Self: Sized, - { - TransformationType::Instrumentation - } - - fn is_enabled(&self, query_db: &QueryDb) -> bool - where - Self: Sized, - { - let args = query_db.args(); - args.ub_check.contains(&ExtraChecks::Uninit) - } - - fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { - trace!(function=?instance.name(), "transform"); - +impl<'a> UninitInstrumenter<'a> { + /// Instrument a body with memory initialization checks, the visitor that generates + /// instrumentation targets must be provided via a TF type parameter. + fn instrument( + &mut self, + tcx: TyCtxt, + mut body: MutableBody, + instance: Instance, + mut target_finder: impl TargetFinder, + ) -> (bool, MutableBody) { // Need to break infinite recursion when memory initialization checks are inserted, so the // internal functions responsible for memory initialization are skipped. if tcx @@ -80,13 +82,7 @@ impl TransformPass for UninitPass { return (false, body); } - let mut new_body = MutableBody::from(body); - let orig_len = new_body.blocks().len(); - - // Inject a call to set-up memory initialization state if the function is a harness. - if is_harness(instance, tcx) { - inject_memory_init_setup(&mut new_body, tcx, &mut self.mem_init_fn_cache); - } + let orig_len = body.blocks().len(); // Set of basic block indices for which analyzing first statement should be skipped. // @@ -100,21 +96,19 @@ impl TransformPass for UninitPass { // Do not cache body.blocks().len() since it will change as we add new checks. let mut bb_idx = 0; - while bb_idx < new_body.blocks().len() { + while bb_idx < body.blocks().len() { if let Some(candidate) = - CheckUninitVisitor::find_next(&new_body, bb_idx, skip_first.contains(&bb_idx)) + target_finder.find_next(&body, bb_idx, skip_first.contains(&bb_idx)) { - self.build_check_for_instruction(tcx, &mut new_body, candidate, &mut skip_first); + self.build_check_for_instruction(tcx, &mut body, candidate, &mut skip_first); bb_idx += 1 } else { bb_idx += 1; }; } - (orig_len != new_body.blocks().len(), new_body.into()) + (orig_len != body.blocks().len(), body) } -} -impl UninitPass { /// Inject memory initialization checks for each operation in an instruction. fn build_check_for_instruction( &mut self, @@ -123,7 +117,6 @@ impl UninitPass { instruction: InitRelevantInstruction, skip_first: &mut HashSet, ) { - debug!(?instruction, "build_check"); let mut source = instruction.source; for operation in instruction.before_instruction { self.build_check_for_operation(tcx, body, &mut source, operation, skip_first); @@ -175,7 +168,9 @@ impl UninitPass { }; match operation { - MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::Check { .. } => { + MemoryInitOp::CheckSliceChunk { .. } + | MemoryInitOp::Check { .. } + | MemoryInitOp::CheckRef { .. } => { self.build_get_and_check(tcx, body, source, operation, pointee_ty_info, skip_first) } MemoryInitOp::SetSliceChunk { .. } @@ -211,7 +206,7 @@ impl UninitPass { // Depending on whether accessing the known number of elements in the slice, need to // pass is as an argument. let (diagnostic, args) = match &operation { - MemoryInitOp::Check { .. } => { + MemoryInitOp::Check { .. } | MemoryInitOp::CheckRef { .. } => { let diagnostic = "KaniIsPtrInitialized"; let args = vec![ptr_operand.clone(), layout_operand]; (diagnostic, args) @@ -275,14 +270,22 @@ impl UninitPass { // Make sure all non-padding bytes are initialized. collect_skipped(&operation, body, skip_first); - let ptr_operand_ty = ptr_operand.ty(body.locals()).unwrap(); + // Find the real operand type for a good error message. + let operand_ty = match &operation { + MemoryInitOp::Check { operand } + | MemoryInitOp::CheckSliceChunk { operand, .. } + | MemoryInitOp::CheckRef { operand } => operand.ty(body.locals()).unwrap(), + _ => unreachable!(), + }; body.insert_check( tcx, &self.check_type, source, operation.position(), ret_place.local, - &format!("Undefined Behavior: Reading from an uninitialized pointer of type `{ptr_operand_ty}`"), + &format!( + "Undefined Behavior: Reading from an uninitialized pointer of type `{operand_ty}`" + ), ) } @@ -483,59 +486,3 @@ pub fn resolve_mem_init_fn(fn_def: FnDef, layout_size: usize, associated_type: T ) .unwrap() } - -/// Checks if the instance is a harness -- an entry point of Kani analysis. -fn is_harness(instance: Instance, tcx: TyCtxt) -> bool { - let harness_identifiers = [ - vec![ - rustc_span::symbol::Symbol::intern("kanitool"), - rustc_span::symbol::Symbol::intern("proof_for_contract"), - ], - vec![ - rustc_span::symbol::Symbol::intern("kanitool"), - rustc_span::symbol::Symbol::intern("proof"), - ], - ]; - harness_identifiers.iter().any(|attr_path| { - tcx.has_attrs_with_path(rustc_internal::internal(tcx, instance.def.def_id()), attr_path) - }) -} - -/// Inject an initial call to set-up memory initialization tracking. -fn inject_memory_init_setup( - new_body: &mut MutableBody, - tcx: TyCtxt, - mem_init_fn_cache: &mut HashMap<&'static str, FnDef>, -) { - // First statement or terminator in the harness. - let mut source = if !new_body.blocks()[0].statements.is_empty() { - SourceInstruction::Statement { idx: 0, bb: 0 } - } else { - SourceInstruction::Terminator { bb: 0 } - }; - - // Dummy return place. - let ret_place = Place { - local: new_body.new_local( - Ty::new_tuple(&[]), - source.span(new_body.blocks()), - Mutability::Not, - ), - projection: vec![], - }; - - // Resolve the instance and inject a call to set-up the memory initialization state. - let memory_initialization_init = Instance::resolve( - get_mem_init_fn_def(tcx, "KaniInitializeMemoryInitializationState", mem_init_fn_cache), - &GenericArgs(vec![]), - ) - .unwrap(); - - new_body.insert_call( - &memory_initialization_init, - &mut source, - InsertPosition::Before, - vec![], - ret_place, - ); -} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/mod.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/mod.rs new file mode 100644 index 000000000000..af2753ea7175 --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/mod.rs @@ -0,0 +1,130 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! A transformation pass that instruments the code to detect possible UB due to the accesses to +//! uninitialized memory via raw pointers. + +use crate::args::ExtraChecks; +use crate::kani_middle::transform::{ + body::{CheckType, InsertPosition, MutableBody, SourceInstruction}, + check_uninit::{get_mem_init_fn_def, UninitInstrumenter}, + TransformPass, TransformationType, +}; +use crate::kani_queries::QueryDb; +use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; +use stable_mir::{ + mir::{mono::Instance, Body, Mutability, Place}, + ty::{FnDef, GenericArgs, Ty}, + CrateDef, +}; +use std::collections::HashMap; +use std::fmt::Debug; +use tracing::trace; +use uninit_visitor::CheckUninitVisitor; + +mod uninit_visitor; + +/// Top-level pass that instruments the code with checks for uninitialized memory access through raw +/// pointers. +#[derive(Debug)] +pub struct UninitPass { + pub check_type: CheckType, + pub mem_init_fn_cache: HashMap<&'static str, FnDef>, +} + +impl TransformPass for UninitPass { + fn transformation_type() -> TransformationType + where + Self: Sized, + { + TransformationType::Instrumentation + } + + fn is_enabled(&self, query_db: &QueryDb) -> bool + where + Self: Sized, + { + let args = query_db.args(); + args.ub_check.contains(&ExtraChecks::Uninit) + } + + fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) { + trace!(function=?instance.name(), "transform"); + + let mut changed = false; + let mut new_body = MutableBody::from(body); + + // Inject a call to set-up memory initialization state if the function is a harness. + if is_harness(instance, tcx) { + inject_memory_init_setup(&mut new_body, tcx, &mut self.mem_init_fn_cache); + changed = true; + } + + // Call a helper that performs the actual instrumentation. + let mut instrumenter = UninitInstrumenter { + check_type: self.check_type.clone(), + mem_init_fn_cache: &mut self.mem_init_fn_cache, + }; + let (instrumentation_added, body) = + instrumenter.instrument(tcx, new_body, instance, CheckUninitVisitor::new()); + + (changed || instrumentation_added, body.into()) + } +} + +/// Checks if the instance is a harness -- an entry point of Kani analysis. +fn is_harness(instance: Instance, tcx: TyCtxt) -> bool { + let harness_identifiers = [ + vec![ + rustc_span::symbol::Symbol::intern("kanitool"), + rustc_span::symbol::Symbol::intern("proof_for_contract"), + ], + vec![ + rustc_span::symbol::Symbol::intern("kanitool"), + rustc_span::symbol::Symbol::intern("proof"), + ], + ]; + harness_identifiers.iter().any(|attr_path| { + tcx.has_attrs_with_path(rustc_internal::internal(tcx, instance.def.def_id()), attr_path) + }) +} + +/// Inject an initial call to set-up memory initialization tracking. +fn inject_memory_init_setup( + new_body: &mut MutableBody, + tcx: TyCtxt, + mem_init_fn_cache: &mut HashMap<&'static str, FnDef>, +) { + // First statement or terminator in the harness. + let mut source = if !new_body.blocks()[0].statements.is_empty() { + SourceInstruction::Statement { idx: 0, bb: 0 } + } else { + SourceInstruction::Terminator { bb: 0 } + }; + + // Dummy return place. + let ret_place = Place { + local: new_body.new_local( + Ty::new_tuple(&[]), + source.span(new_body.blocks()), + Mutability::Not, + ), + projection: vec![], + }; + + // Resolve the instance and inject a call to set-up the memory initialization state. + let memory_initialization_init = Instance::resolve( + get_mem_init_fn_def(tcx, "KaniInitializeMemoryInitializationState", mem_init_fn_cache), + &GenericArgs(vec![]), + ) + .unwrap(); + + new_body.insert_call( + &memory_initialization_init, + &mut source, + InsertPosition::Before, + vec![], + ret_place, + ); +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs similarity index 71% rename from kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs rename to kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index 10a93b727a77..837e14abc886 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -3,135 +3,28 @@ // //! Visitor that collects all instructions relevant to uninitialized memory access. -use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; -use stable_mir::mir::alloc::GlobalAlloc; -use stable_mir::mir::mono::{Instance, InstanceKind}; -use stable_mir::mir::visit::{Location, PlaceContext}; -use stable_mir::mir::{ - BasicBlockIdx, CastKind, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, - Place, PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, - TerminatorKind, +use crate::kani_middle::transform::{ + body::{InsertPosition, MutableBody, SourceInstruction}, + check_uninit::{ + relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, + ty_layout::tys_layout_compatible_to_size, + TargetFinder, + }, +}; +use stable_mir::{ + mir::{ + alloc::GlobalAlloc, + mono::{Instance, InstanceKind}, + visit::{Location, PlaceContext}, + BasicBlockIdx, CastKind, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, + Place, PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, + TerminatorKind, + }, + ty::{ConstantKind, RigidTy, TyKind}, }; -use stable_mir::ty::{ConstantKind, RigidTy, Ty, TyKind}; -use strum_macros::AsRefStr; - -use super::{PointeeInfo, PointeeLayout}; - -/// Memory initialization operations: set or get memory initialization state for a given pointer. -#[derive(AsRefStr, Clone, Debug)] -pub enum MemoryInitOp { - /// Check memory initialization of data bytes in a memory region starting from the pointer - /// `operand` and of length `sizeof(operand)` bytes. - Check { operand: Operand }, - /// Set memory initialization state of data bytes in a memory region starting from the pointer - /// `operand` and of length `sizeof(operand)` bytes. - Set { operand: Operand, value: bool, position: InsertPosition }, - /// Check memory initialization of data bytes in a memory region starting from the pointer - /// `operand` and of length `count * sizeof(operand)` bytes. - CheckSliceChunk { operand: Operand, count: Operand }, - /// Set memory initialization state of data bytes in a memory region starting from the pointer - /// `operand` and of length `count * sizeof(operand)` bytes. - SetSliceChunk { operand: Operand, count: Operand, value: bool, position: InsertPosition }, - /// Set memory initialization of data bytes in a memory region starting from the reference to - /// `operand` and of length `sizeof(operand)` bytes. - SetRef { operand: Operand, value: bool, position: InsertPosition }, - /// Unsupported memory initialization operation. - Unsupported { reason: String }, - /// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`. - TriviallyUnsafe { reason: String }, -} - -impl MemoryInitOp { - /// Produce an operand for the relevant memory initialization related operation. This is mostly - /// required so that the analysis can create a new local to take a reference in - /// `MemoryInitOp::SetRef`. - pub fn mk_operand(&self, body: &mut MutableBody, source: &mut SourceInstruction) -> Operand { - match self { - MemoryInitOp::Check { operand, .. } - | MemoryInitOp::Set { operand, .. } - | MemoryInitOp::CheckSliceChunk { operand, .. } - | MemoryInitOp::SetSliceChunk { operand, .. } => operand.clone(), - MemoryInitOp::SetRef { operand, .. } => Operand::Copy(Place { - local: { - let place = match operand { - Operand::Copy(place) | Operand::Move(place) => place, - Operand::Constant(_) => unreachable!(), - }; - body.insert_assignment( - Rvalue::AddressOf(Mutability::Not, place.clone()), - source, - self.position(), - ) - }, - projection: vec![], - }), - MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { - unreachable!() - } - } - } - - pub fn expect_count(&self) -> Operand { - match self { - MemoryInitOp::CheckSliceChunk { count, .. } - | MemoryInitOp::SetSliceChunk { count, .. } => count.clone(), - MemoryInitOp::Check { .. } - | MemoryInitOp::Set { .. } - | MemoryInitOp::SetRef { .. } - | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), - } - } - - pub fn expect_value(&self) -> bool { - match self { - MemoryInitOp::Set { value, .. } - | MemoryInitOp::SetSliceChunk { value, .. } - | MemoryInitOp::SetRef { value, .. } => *value, - MemoryInitOp::Check { .. } - | MemoryInitOp::CheckSliceChunk { .. } - | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), - } - } - - pub fn position(&self) -> InsertPosition { - match self { - MemoryInitOp::Set { position, .. } - | MemoryInitOp::SetSliceChunk { position, .. } - | MemoryInitOp::SetRef { position, .. } => *position, - MemoryInitOp::Check { .. } - | MemoryInitOp::CheckSliceChunk { .. } - | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before, - } - } -} - -/// Represents an instruction in the source code together with all memory initialization checks/sets -/// that are connected to the memory used in this instruction and whether they should be inserted -/// before or after the instruction. -#[derive(Clone, Debug)] -pub struct InitRelevantInstruction { - /// The instruction that affects the state of the memory. - pub source: SourceInstruction, - /// All memory-related operations that should happen after the instruction. - pub before_instruction: Vec, - /// All memory-related operations that should happen after the instruction. - pub after_instruction: Vec, -} - -impl InitRelevantInstruction { - pub fn push_operation(&mut self, source_op: MemoryInitOp) { - match source_op.position() { - InsertPosition::Before => self.before_instruction.push(source_op), - InsertPosition::After => self.after_instruction.push(source_op), - } - } -} -pub struct CheckUninitVisitor<'a> { - locals: &'a [LocalDecl], +pub struct CheckUninitVisitor { + locals: Vec, /// Whether we should skip the next instruction, since it might've been instrumented already. /// When we instrument an instruction, we partition the basic block, and the instruction that /// may trigger UB becomes the first instruction of the basic block, which we need to skip @@ -145,21 +38,32 @@ pub struct CheckUninitVisitor<'a> { bb: BasicBlockIdx, } -impl<'a> CheckUninitVisitor<'a> { - pub fn find_next( - body: &'a MutableBody, +impl TargetFinder for CheckUninitVisitor { + fn find_next( + &mut self, + body: &MutableBody, bb: BasicBlockIdx, skip_first: bool, ) -> Option { - let mut visitor = CheckUninitVisitor { - locals: body.locals(), - skip_next: skip_first, - current: SourceInstruction::Statement { idx: 0, bb }, + self.locals = body.locals().to_vec(); + self.skip_next = skip_first; + self.current = SourceInstruction::Statement { idx: 0, bb }; + self.target = None; + self.bb = bb; + self.visit_basic_block(&body.blocks()[bb]); + self.target.clone() + } +} + +impl CheckUninitVisitor { + pub fn new() -> Self { + Self { + locals: vec![], + skip_next: false, + current: SourceInstruction::Statement { idx: 0, bb: 0 }, target: None, - bb, - }; - visitor.visit_basic_block(&body.blocks()[bb]); - visitor.target + bb: 0, + } } fn push_target(&mut self, source_op: MemoryInitOp) { @@ -172,7 +76,7 @@ impl<'a> CheckUninitVisitor<'a> { } } -impl<'a> MirVisitor for CheckUninitVisitor<'a> { +impl MirVisitor for CheckUninitVisitor { fn visit_statement(&mut self, stmt: &Statement, location: Location) { if self.skip_next { self.skip_next = false; @@ -186,7 +90,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { operand: copy.src.clone(), count: copy.count.clone(), }); - // Destimation is a *mut T so it gets initialized. + // Destination is a *mut T so it gets initialized. self.push_target(MemoryInitOp::SetSliceChunk { operand: copy.dst.clone(), count: copy.count.clone(), @@ -208,7 +112,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { // if it points to initialized memory. if *projection_elem == ProjectionElem::Deref { if let TyKind::RigidTy(RigidTy::RawPtr(..)) = - place_to_add_projections.ty(&self.locals).unwrap().kind() + place_to_add_projections.ty(&&self.locals).unwrap().kind() { self.push_target(MemoryInitOp::Check { operand: Operand::Copy(place_to_add_projections.clone()), @@ -217,7 +121,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } place_to_add_projections.projection.push(projection_elem.clone()); } - if place_without_deref.ty(&self.locals).unwrap().kind().is_raw_ptr() { + if place_without_deref.ty(&&self.locals).unwrap().kind().is_raw_ptr() { self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place_without_deref), value: true, @@ -226,7 +130,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } } // Check whether Rvalue creates a new initialized pointer previously not captured inside shadow memory. - if place.ty(&self.locals).unwrap().kind().is_raw_ptr() { + if place.ty(&&self.locals).unwrap().kind().is_raw_ptr() { if let Rvalue::AddressOf(..) = rvalue { self.push_target(MemoryInitOp::Set { operand: Operand::Copy(place.clone()), @@ -268,7 +172,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { match &term.kind { TerminatorKind::Call { func, args, destination, .. } => { self.super_terminator(term, location); - let instance = match try_resolve_instance(self.locals, func) { + let instance = match try_resolve_instance(&self.locals, func) { Ok(instance) => instance, Err(reason) => { self.super_terminator(term, location); @@ -297,7 +201,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { "Unexpected number of arguments for `{name}`" ); assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), + args[0].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(..)) )); self.push_target(MemoryInitOp::Check { @@ -311,11 +215,11 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { "Unexpected number of arguments for `compare_bytes`" ); assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), + args[0].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); assert!(matches!( - args[1].ty(self.locals).unwrap().kind(), + args[1].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); self.push_target(MemoryInitOp::CheckSliceChunk { @@ -336,11 +240,11 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { "Unexpected number of arguments for `copy`" ); assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), + args[0].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); assert!(matches!( - args[1].ty(self.locals).unwrap().kind(), + args[1].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(MemoryInitOp::CheckSliceChunk { @@ -361,11 +265,11 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { "Unexpected number of arguments for `typed_swap`" ); assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), + args[0].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); assert!(matches!( - args[1].ty(self.locals).unwrap().kind(), + args[1].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(MemoryInitOp::Check { @@ -382,7 +286,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { "Unexpected number of arguments for `volatile_load`" ); assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), + args[0].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) )); self.push_target(MemoryInitOp::Check { @@ -396,7 +300,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { "Unexpected number of arguments for `volatile_store`" ); assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), + args[0].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(MemoryInitOp::Set { @@ -412,7 +316,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { "Unexpected number of arguments for `write_bytes`" ); assert!(matches!( - args[0].ty(self.locals).unwrap().kind(), + args[0].ty(&self.locals).unwrap().kind(), TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) )); self.push_target(MemoryInitOp::SetSliceChunk { @@ -463,13 +367,13 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } TerminatorKind::Drop { place, .. } => { self.super_terminator(term, location); - let place_ty = place.ty(&self.locals).unwrap(); + let place_ty = place.ty(&&self.locals).unwrap(); // When drop is codegen'ed for types that could define their own dropping // behavior, a reference is taken to the place which is later implicitly coerced // to a pointer. Hence, we need to bless this pointer as initialized. match place - .ty(&self.locals) + .ty(&&self.locals) .unwrap() .kind() .rigid() @@ -511,7 +415,7 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { Place { local: place.local, projection: place.projection[..idx].to_vec() }; match elem { ProjectionElem::Deref => { - let ptr_ty = intermediate_place.ty(self.locals).unwrap(); + let ptr_ty = intermediate_place.ty(&self.locals).unwrap(); if ptr_ty.kind().is_raw_ptr() { self.push_target(MemoryInitOp::Check { operand: Operand::Copy(intermediate_place.clone()), @@ -572,43 +476,38 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> { } } } - CastKind::PtrToPtr => { - let operand_ty = operand.ty(&self.locals).unwrap(); - if let ( - RigidTy::RawPtr(from_ty, Mutability::Mut), - RigidTy::RawPtr(to_ty, Mutability::Mut), - ) = (operand_ty.kind().rigid().unwrap(), ty.kind().rigid().unwrap()) - { - if !tys_layout_compatible(from_ty, to_ty) { - // If casting from a mutable pointer to a mutable pointer with - // different layouts, delayed UB could occur. - self.push_target(MemoryInitOp::Unsupported { - reason: "Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.".to_string(), - }); - } - } - } CastKind::Transmute => { let operand_ty = operand.ty(&self.locals).unwrap(); - if let ( - RigidTy::RawPtr(from_ty, Mutability::Mut), - RigidTy::RawPtr(to_ty, Mutability::Mut), - ) = (operand_ty.kind().rigid().unwrap(), ty.kind().rigid().unwrap()) - { - if !tys_layout_compatible(from_ty, to_ty) { - // If casting from a mutable pointer to a mutable pointer with different - // layouts, delayed UB could occur. - self.push_target(MemoryInitOp::Unsupported { - reason: "Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.".to_string(), - }); - } - } else if !tys_layout_compatible(&operand_ty, &ty) { + if !tys_layout_compatible_to_size(&operand_ty, &ty) { // If transmuting between two types of incompatible layouts, padding // bytes are exposed, which is UB. self.push_target(MemoryInitOp::TriviallyUnsafe { reason: "Transmuting between types of incompatible layouts." .to_string(), }); + } else if let ( + TyKind::RigidTy(RigidTy::Ref(_, from_ty, _)), + TyKind::RigidTy(RigidTy::Ref(_, to_ty, _)), + ) = (operand_ty.kind(), ty.kind()) + { + if !tys_layout_compatible_to_size(&from_ty, &to_ty) { + // Since references are supposed to always be initialized for its type, + // transmuting between two references of incompatible layout is UB. + self.push_target(MemoryInitOp::TriviallyUnsafe { + reason: "Transmuting between references pointing to types of incompatible layouts." + .to_string(), + }); + } + } else if let ( + TyKind::RigidTy(RigidTy::RawPtr(from_ty, _)), + TyKind::RigidTy(RigidTy::Ref(_, to_ty, _)), + ) = (operand_ty.kind(), ty.kind()) + { + // Assert that we can only cast this way if types are the same. + assert!(from_ty == to_ty); + // When transmuting from a raw pointer to a reference, need to check that + // the value pointed by the raw pointer is initialized. + self.push_target(MemoryInitOp::Check { operand: operand.clone() }); } } _ => {} @@ -769,40 +668,7 @@ fn try_resolve_instance(locals: &[LocalDecl], func: &Operand) -> Result Ok(Instance::resolve(def, &args).unwrap()), _ => Err(format!( - "Kani does not support reasoning about memory initialization of arguments to `{ty:?}`." + "Kani was not able to resolve the instance of the function operand `{ty:?}`. Currently, memory initialization checks in presence of function pointers and vtable calls are not supported. For more information about planned support, see https://github.com/model-checking/kani/issues/3300." )), } } - -/// Returns true if `to_ty` has a smaller or equal size and the same padding bytes as `from_ty` up until -/// its size. -fn tys_layout_compatible(from_ty: &Ty, to_ty: &Ty) -> bool { - // Retrieve layouts to assess compatibility. - let from_ty_info = PointeeInfo::from_ty(*from_ty); - let to_ty_info = PointeeInfo::from_ty(*to_ty); - if let (Ok(from_ty_info), Ok(to_ty_info)) = (from_ty_info, to_ty_info) { - let from_ty_layout = match from_ty_info.layout() { - PointeeLayout::Sized { layout } => layout, - PointeeLayout::Slice { element_layout } => element_layout, - PointeeLayout::TraitObject => return false, - }; - let to_ty_layout = match to_ty_info.layout() { - PointeeLayout::Sized { layout } => layout, - PointeeLayout::Slice { element_layout } => element_layout, - PointeeLayout::TraitObject => return false, - }; - // Ensure `to_ty_layout` does not have a larger size. - if to_ty_layout.len() <= from_ty_layout.len() { - // Check data and padding bytes pair-wise. - if from_ty_layout.iter().zip(to_ty_layout.iter()).all( - |(from_ty_layout_byte, to_ty_layout_byte)| { - // Make sure all data and padding bytes match. - from_ty_layout_byte == to_ty_layout_byte - }, - ) { - return true; - } - } - }; - false -} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs new file mode 100644 index 000000000000..3bc5b534a23b --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -0,0 +1,130 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Module containing data structures used in identifying places that need instrumentation and the +//! character of instrumentation needed. + +use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; +use stable_mir::mir::{Mutability, Operand, Place, Rvalue}; +use strum_macros::AsRefStr; + +/// Memory initialization operations: set or get memory initialization state for a given pointer. +#[derive(AsRefStr, Clone, Debug)] +pub enum MemoryInitOp { + /// Check memory initialization of data bytes in a memory region starting from the pointer + /// `operand` and of length `sizeof(operand)` bytes. + Check { operand: Operand }, + /// Set memory initialization state of data bytes in a memory region starting from the pointer + /// `operand` and of length `sizeof(operand)` bytes. + Set { operand: Operand, value: bool, position: InsertPosition }, + /// Check memory initialization of data bytes in a memory region starting from the pointer + /// `operand` and of length `count * sizeof(operand)` bytes. + CheckSliceChunk { operand: Operand, count: Operand }, + /// Set memory initialization state of data bytes in a memory region starting from the pointer + /// `operand` and of length `count * sizeof(operand)` bytes. + SetSliceChunk { operand: Operand, count: Operand, value: bool, position: InsertPosition }, + /// Set memory initialization of data bytes in a memory region starting from the reference to + /// `operand` and of length `sizeof(operand)` bytes. + CheckRef { operand: Operand }, + /// Set memory initialization of data bytes in a memory region starting from the reference to + /// `operand` and of length `sizeof(operand)` bytes. + SetRef { operand: Operand, value: bool, position: InsertPosition }, + /// Unsupported memory initialization operation. + Unsupported { reason: String }, + /// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`. + TriviallyUnsafe { reason: String }, +} + +impl MemoryInitOp { + /// Produce an operand for the relevant memory initialization related operation. This is mostly + /// required so that the analysis can create a new local to take a reference in + /// `MemoryInitOp::SetRef`. + pub fn mk_operand(&self, body: &mut MutableBody, source: &mut SourceInstruction) -> Operand { + match self { + MemoryInitOp::Check { operand, .. } + | MemoryInitOp::Set { operand, .. } + | MemoryInitOp::CheckSliceChunk { operand, .. } + | MemoryInitOp::SetSliceChunk { operand, .. } => operand.clone(), + MemoryInitOp::CheckRef { operand, .. } | MemoryInitOp::SetRef { operand, .. } => { + Operand::Copy(Place { + local: { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + }; + body.insert_assignment( + Rvalue::AddressOf(Mutability::Not, place.clone()), + source, + self.position(), + ) + }, + projection: vec![], + }) + } + MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { + unreachable!() + } + } + } + + pub fn expect_count(&self) -> Operand { + match self { + MemoryInitOp::CheckSliceChunk { count, .. } + | MemoryInitOp::SetSliceChunk { count, .. } => count.clone(), + MemoryInitOp::Check { .. } + | MemoryInitOp::Set { .. } + | MemoryInitOp::CheckRef { .. } + | MemoryInitOp::SetRef { .. } + | MemoryInitOp::Unsupported { .. } + | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), + } + } + + pub fn expect_value(&self) -> bool { + match self { + MemoryInitOp::Set { value, .. } + | MemoryInitOp::SetSliceChunk { value, .. } + | MemoryInitOp::SetRef { value, .. } => *value, + MemoryInitOp::Check { .. } + | MemoryInitOp::CheckSliceChunk { .. } + | MemoryInitOp::CheckRef { .. } + | MemoryInitOp::Unsupported { .. } + | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), + } + } + + pub fn position(&self) -> InsertPosition { + match self { + MemoryInitOp::Set { position, .. } + | MemoryInitOp::SetSliceChunk { position, .. } + | MemoryInitOp::SetRef { position, .. } => *position, + MemoryInitOp::Check { .. } + | MemoryInitOp::CheckSliceChunk { .. } + | MemoryInitOp::CheckRef { .. } + | MemoryInitOp::Unsupported { .. } + | MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before, + } + } +} + +/// Represents an instruction in the source code together with all memory initialization checks/sets +/// that are connected to the memory used in this instruction and whether they should be inserted +/// before or after the instruction. +#[derive(Clone, Debug)] +pub struct InitRelevantInstruction { + /// The instruction that affects the state of the memory. + pub source: SourceInstruction, + /// All memory-related operations that should happen after the instruction. + pub before_instruction: Vec, + /// All memory-related operations that should happen after the instruction. + pub after_instruction: Vec, +} + +impl InitRelevantInstruction { + pub fn push_operation(&mut self, source_op: MemoryInitOp) { + match source_op.position() { + InsertPosition::Before => self.before_instruction.push(source_op), + InsertPosition::After => self.after_instruction.push(source_op), + } + } +} diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs index 09116230af80..8a162d5944d3 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ty_layout.rs @@ -3,10 +3,12 @@ // //! Utility functions that help calculate type layout. -use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}; -use stable_mir::target::{MachineInfo, MachineSize}; -use stable_mir::ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy}; -use stable_mir::CrateDef; +use stable_mir::{ + abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}, + target::{MachineInfo, MachineSize}, + ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy}, + CrateDef, +}; /// Represents a chunk of data bytes in a data structure. #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -332,3 +334,48 @@ fn data_bytes_for_ty( FieldsShape::Array { .. } => Ok(vec![]), } } + +/// Returns true if `to_ty` has a smaller or equal size and padding bytes in `from_ty` are padding +/// bytes in `to_ty`. +pub fn tys_layout_compatible_to_size(from_ty: &Ty, to_ty: &Ty) -> bool { + tys_layout_cmp_to_size(from_ty, to_ty, |from_byte, to_byte| from_byte || !to_byte) +} + +/// Returns true if `to_ty` has a smaller or equal size and padding bytes in `from_ty` are padding +/// bytes in `to_ty`. +pub fn tys_layout_equal_to_size(from_ty: &Ty, to_ty: &Ty) -> bool { + tys_layout_cmp_to_size(from_ty, to_ty, |from_byte, to_byte| from_byte == to_byte) +} + +/// Returns true if `to_ty` has a smaller or equal size and comparator function returns true for all +/// byte initialization value pairs up to size. +fn tys_layout_cmp_to_size(from_ty: &Ty, to_ty: &Ty, cmp: impl Fn(bool, bool) -> bool) -> bool { + // Retrieve layouts to assess compatibility. + let from_ty_info = PointeeInfo::from_ty(*from_ty); + let to_ty_info = PointeeInfo::from_ty(*to_ty); + if let (Ok(from_ty_info), Ok(to_ty_info)) = (from_ty_info, to_ty_info) { + let from_ty_layout = match from_ty_info.layout() { + PointeeLayout::Sized { layout } => layout, + PointeeLayout::Slice { element_layout } => element_layout, + PointeeLayout::TraitObject => return false, + }; + let to_ty_layout = match to_ty_info.layout() { + PointeeLayout::Sized { layout } => layout, + PointeeLayout::Slice { element_layout } => element_layout, + PointeeLayout::TraitObject => return false, + }; + // Ensure `to_ty_layout` does not have a larger size. + if to_ty_layout.len() <= from_ty_layout.len() { + // Check data and padding bytes pair-wise. + if from_ty_layout.iter().zip(to_ty_layout.iter()).all( + |(from_ty_layout_byte, to_ty_layout_byte)| { + // Run comparator on each pair. + cmp(*from_ty_layout_byte, *to_ty_layout_byte) + }, + ) { + return true; + } + } + }; + false +} diff --git a/kani-compiler/src/kani_middle/transform/internal_mir.rs b/kani-compiler/src/kani_middle/transform/internal_mir.rs new file mode 100644 index 000000000000..0dcf7d47c13a --- /dev/null +++ b/kani-compiler/src/kani_middle/transform/internal_mir.rs @@ -0,0 +1,656 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This file contains conversions between from stable MIR data structures to its internal +//! counterparts. This is primarily done to facilitate using dataflow analysis, which does not yet +//! support StableMIR. We tried to contribute this back to StableMIR, but faced some push back since +//! other maintainers wanted to keep the conversions minimal. For more information, see +//! https://github.com/rust-lang/rust/pull/127782 + +use rustc_middle::ty::{self as rustc_ty, TyCtxt}; +use rustc_smir::rustc_internal::internal; +use stable_mir::mir::{ + AggregateKind, AssertMessage, Body, BorrowKind, CastKind, ConstOperand, CopyNonOverlapping, + CoroutineDesugaring, CoroutineKind, CoroutineSource, FakeBorrowKind, FakeReadCause, LocalDecl, + MutBorrowKind, NonDivergingIntrinsic, NullOp, Operand, PointerCoercion, RetagKind, Rvalue, + Statement, StatementKind, SwitchTargets, Terminator, TerminatorKind, UnwindAction, + UserTypeProjection, Variance, +}; + +pub trait RustcInternalMir { + type T<'tcx>; + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx>; +} + +impl RustcInternalMir for AggregateKind { + type T<'tcx> = rustc_middle::mir::AggregateKind<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + AggregateKind::Array(ty) => rustc_middle::mir::AggregateKind::Array(internal(tcx, ty)), + AggregateKind::Tuple => rustc_middle::mir::AggregateKind::Tuple, + AggregateKind::Adt( + adt_def, + variant_idx, + generic_args, + maybe_user_type_annotation_index, + maybe_field_idx, + ) => rustc_middle::mir::AggregateKind::Adt( + internal(tcx, adt_def.0), + internal(tcx, variant_idx), + internal(tcx, generic_args), + maybe_user_type_annotation_index + .map(rustc_middle::ty::UserTypeAnnotationIndex::from_usize), + maybe_field_idx.map(rustc_target::abi::FieldIdx::from_usize), + ), + AggregateKind::Closure(closure_def, generic_args) => { + rustc_middle::mir::AggregateKind::Closure( + internal(tcx, closure_def.0), + internal(tcx, generic_args), + ) + } + AggregateKind::Coroutine(coroutine_def, generic_args, _) => { + rustc_middle::mir::AggregateKind::Coroutine( + internal(tcx, coroutine_def.0), + internal(tcx, generic_args), + ) + } + AggregateKind::RawPtr(ty, mutability) => rustc_middle::mir::AggregateKind::RawPtr( + internal(tcx, ty), + internal(tcx, mutability), + ), + } + } +} + +impl RustcInternalMir for ConstOperand { + type T<'tcx> = rustc_middle::mir::ConstOperand<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + rustc_middle::mir::ConstOperand { + span: internal(tcx, self.span), + user_ty: self.user_ty.map(rustc_ty::UserTypeAnnotationIndex::from_usize), + const_: internal(tcx, self.const_.clone()), + } + } +} + +impl RustcInternalMir for Operand { + type T<'tcx> = rustc_middle::mir::Operand<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + Operand::Copy(place) => rustc_middle::mir::Operand::Copy(internal(tcx, place)), + Operand::Move(place) => rustc_middle::mir::Operand::Move(internal(tcx, place)), + Operand::Constant(const_operand) => { + rustc_middle::mir::Operand::Constant(Box::new(const_operand.internal_mir(tcx))) + } + } + } +} + +impl RustcInternalMir for PointerCoercion { + type T<'tcx> = rustc_middle::ty::adjustment::PointerCoercion; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + PointerCoercion::ReifyFnPointer => { + rustc_middle::ty::adjustment::PointerCoercion::ReifyFnPointer + } + PointerCoercion::UnsafeFnPointer => { + rustc_middle::ty::adjustment::PointerCoercion::UnsafeFnPointer + } + PointerCoercion::ClosureFnPointer(safety) => { + rustc_middle::ty::adjustment::PointerCoercion::ClosureFnPointer(internal( + tcx, safety, + )) + } + PointerCoercion::MutToConstPointer => { + rustc_middle::ty::adjustment::PointerCoercion::MutToConstPointer + } + PointerCoercion::ArrayToPointer => { + rustc_middle::ty::adjustment::PointerCoercion::ArrayToPointer + } + PointerCoercion::Unsize => rustc_middle::ty::adjustment::PointerCoercion::Unsize, + } + } +} + +impl RustcInternalMir for CastKind { + type T<'tcx> = rustc_middle::mir::CastKind; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + CastKind::PointerExposeAddress => rustc_middle::mir::CastKind::PointerExposeProvenance, + CastKind::PointerWithExposedProvenance => { + rustc_middle::mir::CastKind::PointerWithExposedProvenance + } + CastKind::PointerCoercion(ptr_coercion) => { + rustc_middle::mir::CastKind::PointerCoercion(ptr_coercion.internal_mir(tcx)) + } + CastKind::DynStar => rustc_middle::mir::CastKind::DynStar, + CastKind::IntToInt => rustc_middle::mir::CastKind::IntToInt, + CastKind::FloatToInt => rustc_middle::mir::CastKind::FloatToInt, + CastKind::FloatToFloat => rustc_middle::mir::CastKind::FloatToFloat, + CastKind::IntToFloat => rustc_middle::mir::CastKind::IntToFloat, + CastKind::PtrToPtr => rustc_middle::mir::CastKind::PtrToPtr, + CastKind::FnPtrToPtr => rustc_middle::mir::CastKind::FnPtrToPtr, + CastKind::Transmute => rustc_middle::mir::CastKind::Transmute, + } + } +} + +impl RustcInternalMir for FakeBorrowKind { + type T<'tcx> = rustc_middle::mir::FakeBorrowKind; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + FakeBorrowKind::Deep => rustc_middle::mir::FakeBorrowKind::Deep, + FakeBorrowKind::Shallow => rustc_middle::mir::FakeBorrowKind::Shallow, + } + } +} + +impl RustcInternalMir for MutBorrowKind { + type T<'tcx> = rustc_middle::mir::MutBorrowKind; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + MutBorrowKind::Default => rustc_middle::mir::MutBorrowKind::Default, + MutBorrowKind::TwoPhaseBorrow => rustc_middle::mir::MutBorrowKind::TwoPhaseBorrow, + MutBorrowKind::ClosureCapture => rustc_middle::mir::MutBorrowKind::ClosureCapture, + } + } +} + +impl RustcInternalMir for BorrowKind { + type T<'tcx> = rustc_middle::mir::BorrowKind; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + BorrowKind::Shared => rustc_middle::mir::BorrowKind::Shared, + BorrowKind::Fake(fake_borrow_kind) => { + rustc_middle::mir::BorrowKind::Fake(fake_borrow_kind.internal_mir(tcx)) + } + BorrowKind::Mut { kind } => { + rustc_middle::mir::BorrowKind::Mut { kind: kind.internal_mir(tcx) } + } + } + } +} + +impl RustcInternalMir for NullOp { + type T<'tcx> = rustc_middle::mir::NullOp<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + NullOp::SizeOf => rustc_middle::mir::NullOp::SizeOf, + NullOp::AlignOf => rustc_middle::mir::NullOp::AlignOf, + NullOp::OffsetOf(offsets) => rustc_middle::mir::NullOp::OffsetOf( + tcx.mk_offset_of( + offsets + .iter() + .map(|(variant_idx, field_idx)| { + ( + internal(tcx, variant_idx), + rustc_target::abi::FieldIdx::from_usize(*field_idx), + ) + }) + .collect::>() + .as_slice(), + ), + ), + NullOp::UbChecks => rustc_middle::mir::NullOp::UbChecks, + } + } +} + +impl RustcInternalMir for Rvalue { + type T<'tcx> = rustc_middle::mir::Rvalue<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + Rvalue::AddressOf(mutability, place) => rustc_middle::mir::Rvalue::AddressOf( + internal(tcx, mutability), + internal(tcx, place), + ), + Rvalue::Aggregate(aggregate_kind, operands) => rustc_middle::mir::Rvalue::Aggregate( + Box::new(aggregate_kind.internal_mir(tcx)), + rustc_index::IndexVec::from_raw( + operands.iter().map(|operand| operand.internal_mir(tcx)).collect(), + ), + ), + Rvalue::BinaryOp(bin_op, left_operand, right_operand) + | Rvalue::CheckedBinaryOp(bin_op, left_operand, right_operand) => { + rustc_middle::mir::Rvalue::BinaryOp( + internal(tcx, bin_op), + Box::new((left_operand.internal_mir(tcx), right_operand.internal_mir(tcx))), + ) + } + Rvalue::Cast(cast_kind, operand, ty) => rustc_middle::mir::Rvalue::Cast( + cast_kind.internal_mir(tcx), + operand.internal_mir(tcx), + internal(tcx, ty), + ), + Rvalue::CopyForDeref(place) => { + rustc_middle::mir::Rvalue::CopyForDeref(internal(tcx, place)) + } + Rvalue::Discriminant(place) => { + rustc_middle::mir::Rvalue::Discriminant(internal(tcx, place)) + } + Rvalue::Len(place) => rustc_middle::mir::Rvalue::Len(internal(tcx, place)), + Rvalue::Ref(region, borrow_kind, place) => rustc_middle::mir::Rvalue::Ref( + internal(tcx, region), + borrow_kind.internal_mir(tcx), + internal(tcx, place), + ), + Rvalue::Repeat(operand, ty_const) => rustc_middle::mir::Rvalue::Repeat( + operand.internal_mir(tcx), + internal(tcx, ty_const), + ), + Rvalue::ShallowInitBox(operand, ty) => rustc_middle::mir::Rvalue::ShallowInitBox( + operand.internal_mir(tcx), + internal(tcx, ty), + ), + Rvalue::ThreadLocalRef(crate_item) => { + rustc_middle::mir::Rvalue::ThreadLocalRef(internal(tcx, crate_item.0)) + } + Rvalue::NullaryOp(null_op, ty) => { + rustc_middle::mir::Rvalue::NullaryOp(null_op.internal_mir(tcx), internal(tcx, ty)) + } + Rvalue::UnaryOp(un_op, operand) => { + rustc_middle::mir::Rvalue::UnaryOp(internal(tcx, un_op), operand.internal_mir(tcx)) + } + Rvalue::Use(operand) => rustc_middle::mir::Rvalue::Use(operand.internal_mir(tcx)), + } + } +} + +impl RustcInternalMir for FakeReadCause { + type T<'tcx> = rustc_middle::mir::FakeReadCause; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + FakeReadCause::ForMatchGuard => rustc_middle::mir::FakeReadCause::ForMatchGuard, + FakeReadCause::ForMatchedPlace(_opaque) => { + unimplemented!("cannot convert back from an opaque field") + } + FakeReadCause::ForGuardBinding => rustc_middle::mir::FakeReadCause::ForGuardBinding, + FakeReadCause::ForLet(_opaque) => { + unimplemented!("cannot convert back from an opaque field") + } + FakeReadCause::ForIndex => rustc_middle::mir::FakeReadCause::ForIndex, + } + } +} + +impl RustcInternalMir for RetagKind { + type T<'tcx> = rustc_middle::mir::RetagKind; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + RetagKind::FnEntry => rustc_middle::mir::RetagKind::FnEntry, + RetagKind::TwoPhase => rustc_middle::mir::RetagKind::TwoPhase, + RetagKind::Raw => rustc_middle::mir::RetagKind::Raw, + RetagKind::Default => rustc_middle::mir::RetagKind::Default, + } + } +} + +impl RustcInternalMir for UserTypeProjection { + type T<'tcx> = rustc_middle::mir::UserTypeProjection; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + unimplemented!("cannot convert back from an opaque field") + } +} + +impl RustcInternalMir for Variance { + type T<'tcx> = rustc_middle::ty::Variance; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + Variance::Covariant => rustc_middle::ty::Variance::Covariant, + Variance::Invariant => rustc_middle::ty::Variance::Invariant, + Variance::Contravariant => rustc_middle::ty::Variance::Contravariant, + Variance::Bivariant => rustc_middle::ty::Variance::Bivariant, + } + } +} + +impl RustcInternalMir for CopyNonOverlapping { + type T<'tcx> = rustc_middle::mir::CopyNonOverlapping<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + rustc_middle::mir::CopyNonOverlapping { + src: self.src.internal_mir(tcx), + dst: self.dst.internal_mir(tcx), + count: self.count.internal_mir(tcx), + } + } +} + +impl RustcInternalMir for NonDivergingIntrinsic { + type T<'tcx> = rustc_middle::mir::NonDivergingIntrinsic<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + NonDivergingIntrinsic::Assume(operand) => { + rustc_middle::mir::NonDivergingIntrinsic::Assume(operand.internal_mir(tcx)) + } + NonDivergingIntrinsic::CopyNonOverlapping(copy_non_overlapping) => { + rustc_middle::mir::NonDivergingIntrinsic::CopyNonOverlapping( + copy_non_overlapping.internal_mir(tcx), + ) + } + } + } +} + +impl RustcInternalMir for StatementKind { + type T<'tcx> = rustc_middle::mir::StatementKind<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + StatementKind::Assign(place, rvalue) => rustc_middle::mir::StatementKind::Assign( + Box::new((internal(tcx, place), rvalue.internal_mir(tcx))), + ), + StatementKind::FakeRead(fake_read_cause, place) => { + rustc_middle::mir::StatementKind::FakeRead(Box::new(( + fake_read_cause.internal_mir(tcx), + internal(tcx, place), + ))) + } + StatementKind::SetDiscriminant { place, variant_index } => { + rustc_middle::mir::StatementKind::SetDiscriminant { + place: internal(tcx, place).into(), + variant_index: internal(tcx, variant_index), + } + } + StatementKind::Deinit(place) => { + rustc_middle::mir::StatementKind::Deinit(internal(tcx, place).into()) + } + StatementKind::StorageLive(local) => rustc_middle::mir::StatementKind::StorageLive( + rustc_middle::mir::Local::from_usize(*local), + ), + StatementKind::StorageDead(local) => rustc_middle::mir::StatementKind::StorageDead( + rustc_middle::mir::Local::from_usize(*local), + ), + StatementKind::Retag(retag_kind, place) => rustc_middle::mir::StatementKind::Retag( + retag_kind.internal_mir(tcx), + internal(tcx, place).into(), + ), + StatementKind::PlaceMention(place) => { + rustc_middle::mir::StatementKind::PlaceMention(Box::new(internal(tcx, place))) + } + StatementKind::AscribeUserType { place, projections, variance } => { + rustc_middle::mir::StatementKind::AscribeUserType( + Box::new((internal(tcx, place), projections.internal_mir(tcx))), + variance.internal_mir(tcx), + ) + } + StatementKind::Coverage(_coverage_kind) => { + unimplemented!("cannot convert back from an opaque field") + } + StatementKind::Intrinsic(non_diverging_intrinsic) => { + rustc_middle::mir::StatementKind::Intrinsic( + non_diverging_intrinsic.internal_mir(tcx).into(), + ) + } + StatementKind::ConstEvalCounter => rustc_middle::mir::StatementKind::ConstEvalCounter, + StatementKind::Nop => rustc_middle::mir::StatementKind::Nop, + } + } +} + +impl RustcInternalMir for Statement { + type T<'tcx> = rustc_middle::mir::Statement<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + rustc_middle::mir::Statement { + source_info: rustc_middle::mir::SourceInfo::outermost(internal(tcx, self.span)), + kind: self.kind.internal_mir(tcx), + } + } +} + +impl RustcInternalMir for UnwindAction { + type T<'tcx> = rustc_middle::mir::UnwindAction; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + UnwindAction::Continue => rustc_middle::mir::UnwindAction::Continue, + UnwindAction::Unreachable => rustc_middle::mir::UnwindAction::Unreachable, + UnwindAction::Terminate => rustc_middle::mir::UnwindAction::Terminate( + rustc_middle::mir::UnwindTerminateReason::Abi, + ), + UnwindAction::Cleanup(basic_block_idx) => rustc_middle::mir::UnwindAction::Cleanup( + rustc_middle::mir::BasicBlock::from_usize(*basic_block_idx), + ), + } + } +} + +impl RustcInternalMir for SwitchTargets { + type T<'tcx> = rustc_middle::mir::SwitchTargets; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + rustc_middle::mir::SwitchTargets::new( + self.branches().map(|(value, basic_block_idx)| { + (value, rustc_middle::mir::BasicBlock::from_usize(basic_block_idx)) + }), + rustc_middle::mir::BasicBlock::from_usize(self.otherwise()), + ) + } +} + +impl RustcInternalMir for CoroutineDesugaring { + type T<'tcx> = rustc_hir::CoroutineDesugaring; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + CoroutineDesugaring::Async => rustc_hir::CoroutineDesugaring::Async, + CoroutineDesugaring::Gen => rustc_hir::CoroutineDesugaring::Gen, + CoroutineDesugaring::AsyncGen => rustc_hir::CoroutineDesugaring::AsyncGen, + } + } +} + +impl RustcInternalMir for CoroutineSource { + type T<'tcx> = rustc_hir::CoroutineSource; + + fn internal_mir<'tcx>(&self, _tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + CoroutineSource::Block => rustc_hir::CoroutineSource::Block, + CoroutineSource::Closure => rustc_hir::CoroutineSource::Closure, + CoroutineSource::Fn => rustc_hir::CoroutineSource::Fn, + } + } +} + +impl RustcInternalMir for CoroutineKind { + type T<'tcx> = rustc_hir::CoroutineKind; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + CoroutineKind::Desugared(coroutine_desugaring, coroutine_source) => { + rustc_hir::CoroutineKind::Desugared( + coroutine_desugaring.internal_mir(tcx), + coroutine_source.internal_mir(tcx), + ) + } + CoroutineKind::Coroutine(movability) => { + rustc_hir::CoroutineKind::Coroutine(internal(tcx, movability)) + } + } + } +} + +impl RustcInternalMir for AssertMessage { + type T<'tcx> = rustc_middle::mir::AssertMessage<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + AssertMessage::BoundsCheck { len, index } => { + rustc_middle::mir::AssertMessage::BoundsCheck { + len: len.internal_mir(tcx), + index: index.internal_mir(tcx), + } + } + AssertMessage::Overflow(bin_op, left_operand, right_operand) => { + rustc_middle::mir::AssertMessage::Overflow( + internal(tcx, bin_op), + left_operand.internal_mir(tcx), + right_operand.internal_mir(tcx), + ) + } + AssertMessage::OverflowNeg(operand) => { + rustc_middle::mir::AssertMessage::OverflowNeg(operand.internal_mir(tcx)) + } + AssertMessage::DivisionByZero(operand) => { + rustc_middle::mir::AssertMessage::DivisionByZero(operand.internal_mir(tcx)) + } + AssertMessage::RemainderByZero(operand) => { + rustc_middle::mir::AssertMessage::RemainderByZero(operand.internal_mir(tcx)) + } + AssertMessage::ResumedAfterReturn(coroutine_kind) => { + rustc_middle::mir::AssertMessage::ResumedAfterReturn( + coroutine_kind.internal_mir(tcx), + ) + } + AssertMessage::ResumedAfterPanic(coroutine_kind) => { + rustc_middle::mir::AssertMessage::ResumedAfterPanic( + coroutine_kind.internal_mir(tcx), + ) + } + AssertMessage::MisalignedPointerDereference { required, found } => { + rustc_middle::mir::AssertMessage::MisalignedPointerDereference { + required: required.internal_mir(tcx), + found: found.internal_mir(tcx), + } + } + } + } +} + +impl RustcInternalMir for TerminatorKind { + type T<'tcx> = rustc_middle::mir::TerminatorKind<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + match self { + TerminatorKind::Goto { target } => rustc_middle::mir::TerminatorKind::Goto { + target: rustc_middle::mir::BasicBlock::from_usize(*target), + }, + TerminatorKind::SwitchInt { discr, targets } => { + rustc_middle::mir::TerminatorKind::SwitchInt { + discr: discr.internal_mir(tcx), + targets: targets.internal_mir(tcx), + } + } + TerminatorKind::Resume => rustc_middle::mir::TerminatorKind::UnwindResume, + TerminatorKind::Abort => rustc_middle::mir::TerminatorKind::UnwindTerminate( + rustc_middle::mir::UnwindTerminateReason::Abi, + ), + TerminatorKind::Return => rustc_middle::mir::TerminatorKind::Return, + TerminatorKind::Unreachable => rustc_middle::mir::TerminatorKind::Unreachable, + TerminatorKind::Drop { place, target, unwind } => { + rustc_middle::mir::TerminatorKind::Drop { + place: internal(tcx, place), + target: rustc_middle::mir::BasicBlock::from_usize(*target), + unwind: unwind.internal_mir(tcx), + replace: false, + } + } + TerminatorKind::Call { func, args, destination, target, unwind } => { + rustc_middle::mir::TerminatorKind::Call { + func: func.internal_mir(tcx), + args: Box::from_iter( + args.iter().map(|arg| { + rustc_span::source_map::dummy_spanned(arg.internal_mir(tcx)) + }), + ), + destination: internal(tcx, destination), + target: target.map(|basic_block_idx| { + rustc_middle::mir::BasicBlock::from_usize(basic_block_idx) + }), + unwind: unwind.internal_mir(tcx), + call_source: rustc_middle::mir::CallSource::Normal, + fn_span: rustc_span::DUMMY_SP, + } + } + TerminatorKind::Assert { cond, expected, msg, target, unwind } => { + rustc_middle::mir::TerminatorKind::Assert { + cond: cond.internal_mir(tcx), + expected: *expected, + msg: Box::new(msg.internal_mir(tcx)), + target: rustc_middle::mir::BasicBlock::from_usize(*target), + unwind: unwind.internal_mir(tcx), + } + } + TerminatorKind::InlineAsm { .. } => todo!(), + } + } +} + +impl RustcInternalMir for Terminator { + type T<'tcx> = rustc_middle::mir::Terminator<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + rustc_middle::mir::Terminator { + source_info: rustc_middle::mir::SourceInfo::outermost(internal(tcx, self.span)), + kind: self.kind.internal_mir(tcx), + } + } +} + +impl RustcInternalMir for LocalDecl { + type T<'tcx> = rustc_middle::mir::LocalDecl<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + rustc_middle::mir::LocalDecl { + mutability: internal(tcx, self.mutability), + local_info: rustc_middle::mir::ClearCrossCrate::Set(Box::new( + rustc_middle::mir::LocalInfo::Boring, + )), + ty: internal(tcx, self.ty), + user_ty: None, + source_info: rustc_middle::mir::SourceInfo::outermost(internal(tcx, self.span)), + } + } +} + +impl RustcInternalMir for Body { + type T<'tcx> = rustc_middle::mir::Body<'tcx>; + + fn internal_mir<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { + let internal_basic_blocks = rustc_index::IndexVec::from_raw( + self.blocks + .iter() + .map(|stable_basic_block| rustc_middle::mir::BasicBlockData { + statements: stable_basic_block + .statements + .iter() + .map(|statement| statement.internal_mir(tcx)) + .collect(), + terminator: Some(stable_basic_block.terminator.internal_mir(tcx)), + is_cleanup: false, + }) + .collect(), + ); + let local_decls = rustc_index::IndexVec::from_raw( + self.locals().iter().map(|local_decl| local_decl.internal_mir(tcx)).collect(), + ); + rustc_middle::mir::Body::new( + rustc_middle::mir::MirSource::item(rustc_hir::def_id::CRATE_DEF_ID.to_def_id()), + internal_basic_blocks, + rustc_index::IndexVec::new(), + local_decls, + rustc_index::IndexVec::new(), + self.arg_locals().len(), + Vec::new(), + rustc_span::DUMMY_SP, + None, + None, + ) + } +} diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 82ff25bb2442..cc748c39a7f5 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -13,6 +13,9 @@ use crate::kani_middle::transform::body::{ CheckType, InsertPosition, MutableBody, SourceInstruction, }; use crate::kani_middle::transform::check_uninit::PointeeInfo; +use crate::kani_middle::transform::check_uninit::{ + get_mem_init_fn_def, mk_layout_operand, resolve_mem_init_fn, PointeeLayout, +}; use crate::kani_middle::transform::check_values::{build_limits, ty_validity_per_offset}; use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; @@ -29,10 +32,6 @@ use std::fmt::Debug; use strum_macros::AsRefStr; use tracing::trace; -use super::check_uninit::{ - get_mem_init_fn_def, mk_layout_operand, resolve_mem_init_fn, PointeeLayout, -}; - /// Generate the body for a few Kani intrinsics. #[derive(Debug)] pub struct IntrinsicGeneratorPass { diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs index 4f3125e59868..2d963cd1d6eb 100644 --- a/kani-compiler/src/kani_middle/transform/mod.rs +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -19,7 +19,7 @@ use crate::kani_middle::codegen_units::CodegenUnit; use crate::kani_middle::reachability::CallGraph; use crate::kani_middle::transform::body::CheckType; -use crate::kani_middle::transform::check_uninit::UninitPass; +use crate::kani_middle::transform::check_uninit::{DelayedUbPass, UninitPass}; use crate::kani_middle::transform::check_values::ValidValuePass; use crate::kani_middle::transform::contracts::{AnyModifiesPass, FunctionWithContractPass}; use crate::kani_middle::transform::kani_intrinsics::IntrinsicGeneratorPass; @@ -32,11 +32,14 @@ use stable_mir::mir::Body; use std::collections::HashMap; use std::fmt::Debug; +pub use internal_mir::RustcInternalMir; + pub(crate) mod body; mod check_uninit; mod check_values; mod contracts; mod dump_mir_pass; +mod internal_mir; mod kani_intrinsics; mod stubs; @@ -192,6 +195,7 @@ pub struct GlobalPasses { impl GlobalPasses { pub fn new(queries: &QueryDb, tcx: TyCtxt) -> Self { let mut global_passes = GlobalPasses { global_passes: vec![] }; + global_passes.add_global_pass(queries, DelayedUbPass::new(CheckType::new_assert(tcx))); global_passes.add_global_pass(queries, DumpMirPass::new(tcx)); global_passes } diff --git a/kani-compiler/src/main.rs b/kani-compiler/src/main.rs index d2f8cf17e9e7..7f1fb144a09b 100644 --- a/kani-compiler/src/main.rs +++ b/kani-compiler/src/main.rs @@ -27,6 +27,7 @@ extern crate rustc_index; extern crate rustc_interface; extern crate rustc_metadata; extern crate rustc_middle; +extern crate rustc_mir_dataflow; extern crate rustc_session; extern crate rustc_smir; extern crate rustc_span; diff --git a/tests/expected/uninit/access-padding-via-cast/expected b/tests/expected/uninit/access-padding-via-cast/expected index e02883b26cdf..12c5c0a4a439 100644 --- a/tests/expected/uninit/access-padding-via-cast/expected +++ b/tests/expected/uninit/access-padding-via-cast/expected @@ -1,4 +1,4 @@ -Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB. +Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*mut [u8; 4]` VERIFICATION:- FAILED diff --git a/tests/expected/uninit/delayed-ub-transmute/delayed-ub-transmute.rs b/tests/expected/uninit/delayed-ub-transmute/delayed-ub-transmute.rs deleted file mode 100644 index df769e39a8b2..000000000000 --- a/tests/expected/uninit/delayed-ub-transmute/delayed-ub-transmute.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z uninit-checks - -/// Checks that Kani rejects mutable pointer casts between types of different padding. -#[kani::proof] -fn invalid_value() { - unsafe { - let mut value: u128 = 0; - let ptr: *mut (u8, u32, u64) = std::mem::transmute(&mut value as *mut _); - *ptr = (4, 4, 4); // This assignment itself does not cause UB... - let c: u128 = value; // ...but this reads a padding value! - } -} diff --git a/tests/expected/uninit/delayed-ub-transmute/expected b/tests/expected/uninit/delayed-ub-transmute/expected deleted file mode 100644 index e02883b26cdf..000000000000 --- a/tests/expected/uninit/delayed-ub-transmute/expected +++ /dev/null @@ -1,5 +0,0 @@ -Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB. - -VERIFICATION:- FAILED - -Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file diff --git a/tests/expected/uninit/delayed-ub/delayed-ub.rs b/tests/expected/uninit/delayed-ub/delayed-ub.rs index bfed0a1f39a1..feee4bcd161f 100644 --- a/tests/expected/uninit/delayed-ub/delayed-ub.rs +++ b/tests/expected/uninit/delayed-ub/delayed-ub.rs @@ -2,13 +2,165 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Z ghost-state -Z uninit-checks -/// Checks that Kani rejects mutable pointer casts between types of different padding. +//! Checks that Kani catches instances of delayed UB. + +/// Delayed UB via casted mutable pointer write. #[kani::proof] -fn invalid_value() { +fn delayed_ub() { unsafe { let mut value: u128 = 0; + // Cast between two pointers of different padding. let ptr = &mut value as *mut _ as *mut (u8, u32, u64); - *ptr = (4, 4, 4); // This assignment itself does not cause UB... - let c: u128 = value; // ...but this reads a padding value! + *ptr = (4, 4, 4); + let c: u128 = value; // UB: This reads a padding value! + } +} + +/// Delayed UB via transmuted mutable pointer write. +#[kani::proof] +fn delayed_ub_transmute() { + unsafe { + let mut value: u128 = 0; + // Transmute between two pointers of different padding. + let ptr: *mut (u8, u32, u64) = std::mem::transmute(&mut value as *mut _); + *ptr = (4, 4, 4); + let c: u128 = value; // UB: This reads a padding value! + } +} + +static mut VALUE: u128 = 42; + +/// Delayed UB via mutable pointer write into a static. +#[kani::proof] +fn delayed_ub_static() { + unsafe { + let v_ref = &mut VALUE; + // Cast reference to static to a pointer of different padding. + let ptr = &mut VALUE as *mut _ as *mut (u8, u32, u64); + *ptr = (4, 4, 4); + assert!(*v_ref > 0); // UB: This reads a padding value! + } +} + +/// Helper to launder the pointer while keeping the address. +unsafe fn launder(ptr: *mut u128) -> *mut u128 { + let a = ptr; + let b = a as *const u128; + let c: *mut i128 = std::mem::transmute(b); + let d = c as usize; + let e = d + 1; + let f = e - 1; + return f as *mut u128; +} + +/// Delayed UB via mutable pointer write with additional laundering. +#[kani::proof] +fn delayed_ub_laundered() { + unsafe { + let mut value: u128 = 0; + let ptr = &mut value as *mut u128; + // Pass pointer around in an attempt to remove the association. + let ptr = launder(ptr) as *mut (u8, u32, u64); + *ptr = (4, 4, 4); + assert!(value > 0); // UB: This reads a padding value! + } +} + +/// Delayed UB via mutable pointer write with additional laundering but via closure. +#[kani::proof] +fn delayed_ub_closure_laundered() { + unsafe { + let mut value: u128 = 0; + let ptr = &mut value as *mut u128; + // Add extra args to test spread_arg. + let launder = |arg1: bool, arg2: bool, arg3: bool, ptr: *mut u128| -> *mut u128 { + let a = ptr; + let b = a as *const u128; + let c: *mut i128 = std::mem::transmute(b); + let d = c as usize; + let e = d + 1; + let f = e - 1; + return f as *mut u128; + }; + // Pass pointer around in an attempt to remove the association. + let ptr = launder(false, true, false, ptr) as *mut (u8, u32, u64); + *ptr = (4, 4, 4); + assert!(value > 0); // UB: This reads a padding value! + } +} + +/// Delayed UB via mutable pointer write with additional laundering but via closure captures. +#[kani::proof] +fn delayed_ub_closure_capture_laundered() { + unsafe { + let mut value: u128 = 0; + let ptr = &mut value as *mut u128; + // Add extra args to test spread_arg. + let launder = |arg1: bool, arg2: bool, arg3: bool| -> *mut u128 { + let a = ptr; + let b = a as *const u128; + let c: *mut i128 = std::mem::transmute(b); + let d = c as usize; + let e = d + 1; + let f = e - 1; + return f as *mut u128; + }; + // Pass pointer around in an attempt to remove the association. + let ptr = launder(false, true, false) as *mut (u8, u32, u64); + *ptr = (4, 4, 4); + assert!(value > 0); // UB: This reads a padding value! + } +} + +/// Delayed UB via mutable pointer write using `copy_nonoverlapping` under the hood. +#[kani::proof] +fn delayed_ub_copy() { + unsafe { + let mut value: u128 = 0; + let ptr = &mut value as *mut _ as *mut (u8, u32, u64); + // Use `copy_nonoverlapping` in an attempt to remove the taint. + std::ptr::write(ptr, (4, 4, 4)); + assert!(value > 0); // UB: This reads a padding value! + } +} + +struct S { + u: U, +} + +struct U { + value1: u128, + value2: u64, + value3: u32, +} + +struct Inner(*mut T); + +/// Delayed UB via mutable pointer write into inner fields of structs. +#[kani::proof] +fn delayed_ub_structs() { + unsafe { + // Create a convoluted struct. + let mut s: S = S { u: U { value1: 0, value2: 0, value3: 0 } }; + // Get a pointer to an inner field of the struct. Then, cast between two pointers of + // different padding. + let inner = Inner(&mut s.u.value2 as *mut _); + let inner_cast = Inner(inner.0 as *mut (u8, u32)); + let ptr = inner_cast.0; + *ptr = (4, 4); + let u: U = s.u; // UB: This reads a padding value inside the inner struct! + } +} + +/// Delayed UB via mutable pointer write into a slice element. +#[kani::proof] +fn delayed_ub_slices() { + unsafe { + // Create an array. + let mut arr = [0u128; 4]; + // Get a pointer to a part of the array. + let ptr = &mut arr[0..2][0..1][0] as *mut _ as *mut (u8, u32); + *ptr = (4, 4); + let arr_copy = arr; // UB: This reads a padding value inside the array! } } diff --git a/tests/expected/uninit/delayed-ub/expected b/tests/expected/uninit/delayed-ub/expected index e02883b26cdf..46b6ababe85d 100644 --- a/tests/expected/uninit/delayed-ub/expected +++ b/tests/expected/uninit/delayed-ub/expected @@ -1,5 +1,47 @@ -Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB. +delayed_ub_slices.assertion.4\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `[u128; 4]`" -VERIFICATION:- FAILED +delayed_ub_structs.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `U`" -Complete - 0 successfully verified harnesses, 1 failures, 1 total. \ No newline at end of file +delayed_ub_copy.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +delayed_ub_closure_capture_laundered.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +delayed_ub_closure_laundered.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +delayed_ub_laundered.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +delayed_ub_static.assertion.4\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +delayed_ub_transmute.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +delayed_ub.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +Summary: +Verification failed for - delayed_ub_slices +Verification failed for - delayed_ub_structs +Verification failed for - delayed_ub_copy +Verification failed for - delayed_ub_closure_capture_laundered +Verification failed for - delayed_ub_closure_laundered +Verification failed for - delayed_ub_laundered +Verification failed for - delayed_ub_static +Verification failed for - delayed_ub_transmute +Verification failed for - delayed_ub +Complete - 0 successfully verified harnesses, 9 failures, 9 total. diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index ffa98b6f1140..cf34d305608b 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -1,64 +1,46 @@ -Checking harness check_typed_swap_safe... +std::ptr::read::>.assertion.1\ + - Status: FAILURE\ + - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." -Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB. +std::ptr::read::>.assertion.2\ + - Status: FAILURE\ + - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." -VERIFICATION:- FAILED +std::ptr::write::>.assertion.1\ + - Status: FAILURE\ + - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." -Checking harness check_typed_swap... +std::ptr::write::>.assertion.2\ + - Status: FAILURE\ + - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." -Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB. +check_typed_swap.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*mut u8`" -Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*mut u8` +check_typed_swap.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*mut u8`" -Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*mut u8` +check_volatile_load.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" -VERIFICATION:- FAILED +check_compare_bytes.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" -Checking harness check_volatile_store_and_load_safe... +check_compare_bytes.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" -VERIFICATION:- SUCCESSFUL +std::intrinsics::copy::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" -Checking harness check_volatile_load... - -Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const u8` - -VERIFICATION:- FAILED - -Checking harness check_write_bytes_safe... - -VERIFICATION:- SUCCESSFUL - -Checking harness check_compare_bytes_safe... - -VERIFICATION:- SUCCESSFUL - -Checking harness check_compare_bytes... - -Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const u8` - -Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const u8` - -VERIFICATION:- FAILED - -Checking harness check_copy_safe... - -VERIFICATION:- SUCCESSFUL - -Checking harness check_copy... - -Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const u8` - -VERIFICATION:- FAILED - -Checking harness check_copy_nonoverlapping_safe... - -VERIFICATION:- SUCCESSFUL - -Checking harness check_copy_nonoverlapping... - -Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*const u8` - -VERIFICATION:- FAILED +std::intrinsics::copy_nonoverlapping::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" Summary: Verification failed for - check_typed_swap_safe From 520cb3e01dd4a2c53dfcb1cc8c3a8ee5d6c6e29f Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Mon, 5 Aug 2024 21:01:12 +0200 Subject: [PATCH 22/28] Update toolchain to 2024-08-05 (#3416) * Remove `gen_function_local_variable` and `initializer_fn_name`, the last uses of both of which were removed in #3305. * Mark `arg_count`, which was introduced in #3363, as `allow(dead_code)` as it will soon be used. * Mark `insert_bb`, which was introduced in #3382, as `allow(dead_code)` as it will soon be used. The toolchain upgrade to 2024-08-04 includes several bugfixes to dead-code analysis in rustc, explaining why we the recent PRs as listed above weren't flagged before for introducing dead code. Resolves: #3411 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../src/codegen_cprover_gotoc/context/goto_ctx.rs | 11 ----------- .../src/codegen_cprover_gotoc/utils/names.rs | 4 ---- kani-compiler/src/kani_middle/transform/body.rs | 2 ++ rust-toolchain.toml | 2 +- 4 files changed, 3 insertions(+), 16 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs b/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs index e360cd491edd..3f17f4b87233 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs @@ -139,17 +139,6 @@ impl<'tcx> GotocCtx<'tcx> { sym } - // Generate a Symbol Expression representing a function variable from the MIR - pub fn gen_function_local_variable( - &mut self, - c: u64, - fname: &str, - t: Type, - loc: Location, - ) -> Symbol { - self.gen_stack_variable(c, fname, "var", t, loc) - } - /// Given a counter `c` a function name `fname, and a prefix `prefix`, generates a new function local variable /// It is an error to reuse an existing `c`, `fname` `prefix` tuple. fn gen_stack_variable( diff --git a/kani-compiler/src/codegen_cprover_gotoc/utils/names.rs b/kani-compiler/src/codegen_cprover_gotoc/utils/names.rs index c290f2bf6428..01f5ca4b3dce 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/utils/names.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/utils/names.rs @@ -45,10 +45,6 @@ impl<'tcx> GotocCtx<'tcx> { (name, base_name) } - pub fn initializer_fn_name(var_name: &str) -> String { - format!("{var_name}_init") - } - /// The name for a tuple field pub fn tuple_fld_name(n: usize) -> String { format!("{n}") diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index 3d110c4e9656..fa4e5eb1ad97 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -54,6 +54,7 @@ impl MutableBody { &self.locals } + #[allow(dead_code)] pub fn arg_count(&self) -> usize { self.arg_count } @@ -330,6 +331,7 @@ impl MutableBody { /// `InsertPosition` is `InsertPosition::Before`, `source` will point to the same instruction as /// before. If `InsertPosition` is `InsertPosition::After`, `source` will point to the /// terminator of the newly inserted basic block. + #[allow(dead_code)] pub fn insert_bb( &mut self, mut bb: BasicBlock, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ab901a10d7e4..4b2a2819e4ce 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-03" +channel = "nightly-2024-08-05" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 6eb7dddb0a15615ad341af2b7289def5bb4a4a43 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:11:39 +0200 Subject: [PATCH 23/28] Automatic toolchain upgrade to nightly-2024-08-06 (#3420) Update Rust toolchain from nightly-2024-08-05 to nightly-2024-08-06 without any other source changes. Co-authored-by: celinval <35149715+celinval@users.noreply.github.com> --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4b2a2819e4ce..23a3f2bc99eb 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-05" +channel = "nightly-2024-08-06" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 2a0e9bdbb65459b05f499f687285cb764e654354 Mon Sep 17 00:00:00 2001 From: Jaisurya Nanduri <91620234+jaisnan@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:59:15 -0400 Subject: [PATCH 24/28] Unify kani library and kani core logic (#3333) 1. Unifies `Kani library` and `kani_core` for `Arbitrary`, `mem` and `intrinsics` along with the `internal` module. 2. Adjusts some regression expected files to reflect the new underlying source of Arbitrary Related to #https://github.com/model-checking/kani/issues/3257 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- Cargo.lock | 1 + library/kani/Cargo.toml | 2 + library/kani/src/arbitrary.rs | 146 +----- library/kani/src/internal.rs | 160 ------- library/kani/src/lib.rs | 310 +------------ library/kani/src/mem.rs | 433 ------------------ library/kani_core/Cargo.toml | 2 +- library/kani_core/src/lib.rs | 5 + library/kani_core/src/mem.rs | 76 ++- library/kani_macros/Cargo.toml | 2 +- .../modifies/check_invalid_modifies.expected | 7 +- .../function-contract/valid_ptr.expected | 1 - .../verify_std_cmd/verify_std.sh | 2 +- .../non_arbitrary_param/expected | 6 +- 14 files changed, 71 insertions(+), 1082 deletions(-) delete mode 100644 library/kani/src/internal.rs delete mode 100644 library/kani/src/mem.rs diff --git a/Cargo.lock b/Cargo.lock index dcd7b057b3d8..7d7580e3b742 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -434,6 +434,7 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" name = "kani" version = "0.53.0" dependencies = [ + "kani_core", "kani_macros", ] diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index 91fee3dabf30..1fba7875672a 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -10,6 +10,8 @@ publish = false [dependencies] kani_macros = { path = "../kani_macros" } +kani_core = { path = "../kani_core" } [features] concrete_playback = [] +no_core=["kani_macros/no_core"] diff --git a/library/kani/src/arbitrary.rs b/library/kani/src/arbitrary.rs index 83b113d64927..f16f06165d29 100644 --- a/library/kani/src/arbitrary.rs +++ b/library/kani/src/arbitrary.rs @@ -4,151 +4,7 @@ //! This module introduces the `Arbitrary` trait as well as implementation for //! primitive types and other std containers. -use std::{ - marker::{PhantomData, PhantomPinned}, - num::*, -}; - -/// This trait should be used to generate symbolic variables that represent any valid value of -/// its type. -pub trait Arbitrary -where - Self: Sized, -{ - fn any() -> Self; - fn any_array() -> [Self; MAX_ARRAY_LENGTH] { - [(); MAX_ARRAY_LENGTH].map(|_| Self::any()) - } -} - -/// The given type can be represented by an unconstrained symbolic value of size_of::. -macro_rules! trivial_arbitrary { - ( $type: ty ) => { - impl Arbitrary for $type { - #[inline(always)] - fn any() -> Self { - // This size_of call does not use generic_const_exprs feature. It's inside a macro, and Self isn't generic. - unsafe { crate::any_raw_internal::() } - } - fn any_array() -> [Self; MAX_ARRAY_LENGTH] { - unsafe { crate::any_raw_array::() } - } - } - }; -} - -trivial_arbitrary!(u8); -trivial_arbitrary!(u16); -trivial_arbitrary!(u32); -trivial_arbitrary!(u64); -trivial_arbitrary!(u128); -trivial_arbitrary!(usize); - -trivial_arbitrary!(i8); -trivial_arbitrary!(i16); -trivial_arbitrary!(i32); -trivial_arbitrary!(i64); -trivial_arbitrary!(i128); -trivial_arbitrary!(isize); - -// We do not constrain floating points values per type spec. Users must add assumptions to their -// verification code if they want to eliminate NaN, infinite, or subnormal. -trivial_arbitrary!(f32); -trivial_arbitrary!(f64); - -// Similarly, we do not constraint values for non-standard floating types. -trivial_arbitrary!(f16); -trivial_arbitrary!(f128); - -trivial_arbitrary!(()); - -impl Arbitrary for bool { - #[inline(always)] - fn any() -> Self { - let byte = u8::any(); - crate::assume(byte < 2); - byte == 1 - } -} - -/// Validate that a char is not outside the ranges [0x0, 0xD7FF] and [0xE000, 0x10FFFF] -/// Ref: -impl Arbitrary for char { - #[inline(always)] - fn any() -> Self { - // Generate an arbitrary u32 and constrain it to make it a valid representation of char. - let val = u32::any(); - crate::assume(val <= 0xD7FF || (0xE000..=0x10FFFF).contains(&val)); - unsafe { char::from_u32_unchecked(val) } - } -} - -macro_rules! nonzero_arbitrary { - ( $type: ty, $base: ty ) => { - impl Arbitrary for $type { - #[inline(always)] - fn any() -> Self { - let val = <$base>::any(); - crate::assume(val != 0); - unsafe { <$type>::new_unchecked(val) } - } - } - }; -} - -nonzero_arbitrary!(NonZeroU8, u8); -nonzero_arbitrary!(NonZeroU16, u16); -nonzero_arbitrary!(NonZeroU32, u32); -nonzero_arbitrary!(NonZeroU64, u64); -nonzero_arbitrary!(NonZeroU128, u128); -nonzero_arbitrary!(NonZeroUsize, usize); - -nonzero_arbitrary!(NonZeroI8, i8); -nonzero_arbitrary!(NonZeroI16, i16); -nonzero_arbitrary!(NonZeroI32, i32); -nonzero_arbitrary!(NonZeroI64, i64); -nonzero_arbitrary!(NonZeroI128, i128); -nonzero_arbitrary!(NonZeroIsize, isize); - -impl Arbitrary for [T; N] -where - T: Arbitrary, -{ - fn any() -> Self { - T::any_array() - } -} - -impl Arbitrary for Option -where - T: Arbitrary, -{ - fn any() -> Self { - if bool::any() { Some(T::any()) } else { None } - } -} - -impl Arbitrary for Result -where - T: Arbitrary, - E: Arbitrary, -{ - fn any() -> Self { - if bool::any() { Ok(T::any()) } else { Err(E::any()) } - } -} - -impl Arbitrary for std::marker::PhantomData { - fn any() -> Self { - PhantomData - } -} - -impl Arbitrary for std::marker::PhantomPinned { - fn any() -> Self { - PhantomPinned - } -} +use crate::Arbitrary; impl Arbitrary for std::boxed::Box where diff --git a/library/kani/src/internal.rs b/library/kani/src/internal.rs deleted file mode 100644 index 68b15316b4c1..000000000000 --- a/library/kani/src/internal.rs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -use crate::arbitrary::Arbitrary; -use std::ptr; - -/// Helper trait for code generation for `modifies` contracts. -/// -/// We allow the user to provide us with a pointer-like object that we convert as needed. -#[doc(hidden)] -pub trait Pointer { - /// Type of the pointed-to data - type Inner: ?Sized; - - /// used for havocking on replecement of a `modifies` clause. - unsafe fn assignable(self) -> *mut Self::Inner; -} - -impl Pointer for &T { - type Inner = T; - unsafe fn assignable(self) -> *mut Self::Inner { - self as *const T as *mut T - } -} - -impl Pointer for &mut T { - type Inner = T; - - unsafe fn assignable(self) -> *mut Self::Inner { - self as *mut T - } -} - -impl Pointer for *const T { - type Inner = T; - - unsafe fn assignable(self) -> *mut Self::Inner { - self as *mut T - } -} - -impl Pointer for *mut T { - type Inner = T; - unsafe fn assignable(self) -> *mut Self::Inner { - self - } -} - -/// A way to break the ownerhip rules. Only used by contracts where we can -/// guarantee it is done safely. -/// TODO: Remove this! This is not safe. Users should be able to use `ptr::read` and `old` if -/// they really need to. See . -#[inline(never)] -#[doc(hidden)] -#[rustc_diagnostic_item = "KaniUntrackedDeref"] -pub fn untracked_deref(_: &T) -> T { - todo!() -} - -/// CBMC contracts currently has a limitation where `free` has to be in scope. -/// However, if there is no dynamic allocation in the harness, slicing removes `free` from the -/// scope. -/// -/// Thus, this function will basically translate into: -/// ```c -/// // This is a no-op. -/// free(NULL); -/// ``` -#[inline(never)] -#[doc(hidden)] -#[rustc_diagnostic_item = "KaniInitContracts"] -pub fn init_contracts() {} - -/// This should only be used within contracts. The intent is to -/// perform type inference on a closure's argument -/// TODO: This should be generated inside the function that has contract. This is used for -/// remembers. -#[doc(hidden)] -pub fn apply_closure bool>(f: U, x: &T) -> bool { - f(x) -} - -/// Recieves a reference to a pointer-like object and assigns kani::any_modifies to that object. -/// Only for use within function contracts and will not be replaced if the recursive or function stub -/// replace contracts are not used. -#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")] -#[rustc_diagnostic_item = "KaniWriteAny"] -#[inline(never)] -#[doc(hidden)] -pub unsafe fn write_any(_pointer: *mut T) { - // This function should not be reacheable. - // Users must include `#[kani::recursion]` in any function contracts for recursive functions; - // otherwise, this might not be properly instantiate. We mark this as unreachable to make - // sure Kani doesn't report any false positives. - unreachable!() -} - -/// Fill in a slice with kani::any. -/// Intended as a post compilation replacement for write_any -#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")] -#[rustc_diagnostic_item = "KaniWriteAnySlice"] -#[inline(always)] -pub unsafe fn write_any_slice(slice: *mut [T]) { - (*slice).fill_with(T::any) -} - -/// Fill in a pointer with kani::any. -/// Intended as a post compilation replacement for write_any -#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")] -#[rustc_diagnostic_item = "KaniWriteAnySlim"] -#[inline(always)] -pub unsafe fn write_any_slim(pointer: *mut T) { - ptr::write(pointer, T::any()) -} - -/// Fill in a str with kani::any. -/// Intended as a post compilation replacement for write_any. -/// Not yet implemented -#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")] -#[rustc_diagnostic_item = "KaniWriteAnyStr"] -#[inline(always)] -pub unsafe fn write_any_str(_s: *mut str) { - //TODO: strings introduce new UB - //(*s).as_bytes_mut().fill_with(u8::any) - //TODO: String validation - unimplemented!("Kani does not support creating arbitrary `str`") -} - -/// Function that calls a closure used to implement contracts. -/// -/// In contracts, we cannot invoke the generated closures directly, instead, we call register -/// contract. This function is a no-op. However, in the reality, we do want to call the closure, -/// so we swap the register body by this function body. -#[doc(hidden)] -#[allow(dead_code)] -#[rustc_diagnostic_item = "KaniRunContract"] -#[crate::unstable( - feature = "function-contracts", - issue = "none", - reason = "internal function required to run contract closure" -)] -fn run_contract_fn T>(func: F) -> T { - func() -} - -/// This is used by contracts to select which version of the contract to use during codegen. -#[doc(hidden)] -pub type Mode = u8; - -/// Keep the original body. -pub const ORIGINAL: Mode = 0; - -/// Run the check with recursion support. -pub const RECURSION_CHECK: Mode = 1; - -/// Run the simple check with no recursion support. -pub const SIMPLE_CHECK: Mode = 2; - -/// Stub the body with its contract. -pub const REPLACE: Mode = 3; diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 046c6e7a0667..59a89622a52d 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -28,19 +28,13 @@ pub mod arbitrary; mod concrete_playback; pub mod futures; pub mod invariant; -pub mod mem; pub mod shadow; pub mod slice; -pub mod tuple; pub mod vec; -#[doc(hidden)] -pub mod internal; - mod mem_init; mod models; -pub use arbitrary::Arbitrary; #[cfg(feature = "concrete_playback")] pub use concrete_playback::concrete_playback_run; pub use invariant::Invariant; @@ -53,287 +47,19 @@ pub fn concrete_playback_run(_: Vec>, _: F) { pub use futures::{block_on, block_on_with_spawn, spawn, yield_now, RoundRobin}; -/// Creates an assumption that will be valid after this statement run. Note that the assumption -/// will only be applied for paths that follow the assumption. If the assumption doesn't hold, the -/// program will exit successfully. -/// -/// # Example: -/// -/// The code snippet below should never panic. -/// -/// ```rust -/// let i : i32 = kani::any(); -/// kani::assume(i > 10); -/// if i < 0 { -/// panic!("This will never panic"); -/// } -/// ``` -/// -/// The following code may panic though: -/// -/// ```rust -/// let i : i32 = kani::any(); -/// assert!(i < 0, "This may panic and verification should fail."); -/// kani::assume(i > 10); -/// ``` -#[inline(never)] -#[rustc_diagnostic_item = "KaniAssume"] -#[cfg(not(feature = "concrete_playback"))] -pub fn assume(cond: bool) { - let _ = cond; -} - -#[inline(never)] -#[rustc_diagnostic_item = "KaniAssume"] -#[cfg(feature = "concrete_playback")] -pub fn assume(cond: bool) { - assert!(cond, "`kani::assume` should always hold"); -} - -/// `implies!(premise => conclusion)` means that if the `premise` is true, so -/// must be the `conclusion`. -/// -/// This simply expands to `!premise || conclusion` and is intended to make checks more readable, -/// as the concept of an implication is more natural to think about than its expansion. -#[macro_export] -macro_rules! implies { - ($premise:expr => $conclusion:expr) => { - !($premise) || ($conclusion) - }; -} - -/// Creates an assertion of the specified condition and message. -/// -/// # Example: -/// -/// ```rust -/// let x: bool = kani::any(); -/// let y = !x; -/// kani::assert(x || y, "ORing a boolean variable with its negation must be true") -/// ``` -#[cfg(not(feature = "concrete_playback"))] -#[inline(never)] -#[rustc_diagnostic_item = "KaniAssert"] -pub const fn assert(cond: bool, msg: &'static str) { - let _ = cond; - let _ = msg; -} - -#[cfg(feature = "concrete_playback")] -#[inline(never)] -#[rustc_diagnostic_item = "KaniAssert"] -pub const fn assert(cond: bool, msg: &'static str) { - assert!(cond, "{}", msg); -} - -/// Creates an assertion of the specified condition, but does not assume it afterwards. -/// -/// # Example: -/// -/// ```rust -/// let x: bool = kani::any(); -/// let y = !x; -/// kani::check(x || y, "ORing a boolean variable with its negation must be true") -/// ``` -#[cfg(not(feature = "concrete_playback"))] -#[inline(never)] -#[rustc_diagnostic_item = "KaniCheck"] -pub const fn check(cond: bool, msg: &'static str) { - let _ = cond; - let _ = msg; -} - -#[cfg(feature = "concrete_playback")] -#[inline(never)] -#[rustc_diagnostic_item = "KaniCheck"] -pub const fn check(cond: bool, msg: &'static str) { - assert!(cond, "{}", msg); -} - -/// Creates a cover property with the specified condition and message. -/// -/// # Example: -/// -/// ```rust -/// kani::cover(slice.len() == 0, "The slice may have a length of 0"); -/// ``` -/// -/// A cover property checks if there is at least one execution that satisfies -/// the specified condition at the location in which the function is called. -/// -/// Cover properties are reported as: -/// - SATISFIED: if Kani found an execution that satisfies the condition -/// - UNSATISFIABLE: if Kani proved that the condition cannot be satisfied -/// - UNREACHABLE: if Kani proved that the cover property itself is unreachable (i.e. it is vacuously UNSATISFIABLE) -/// -/// This function is called by the [`cover!`] macro. The macro is more -/// convenient to use. -/// -#[inline(never)] -#[rustc_diagnostic_item = "KaniCover"] -pub const fn cover(_cond: bool, _msg: &'static str) {} +// Kani proc macros must be in a separate crate +pub use kani_macros::*; -/// This creates an symbolic *valid* value of type `T`. You can assign the return value of this -/// function to a variable that you want to make symbolic. -/// -/// # Example: -/// -/// In the snippet below, we are verifying the behavior of the function `fn_under_verification` -/// under all possible `NonZeroU8` input values, i.e., all possible `u8` values except zero. -/// -/// ```rust -/// let inputA = kani::any::(); -/// fn_under_verification(inputA); -/// ``` -/// -/// Note: This is a safe construct and can only be used with types that implement the `Arbitrary` -/// trait. The Arbitrary trait is used to build a symbolic value that represents all possible -/// valid values for type `T`. -#[rustc_diagnostic_item = "KaniAny"] -#[inline(always)] -pub fn any() -> T { - T::any() -} +// Declare common Kani API such as assume, assert +kani_core::kani_lib!(kani); -/// This function is only used for function contract instrumentation. -/// It behaves exaclty like `kani::any()`, except it will check for the trait bounds -/// at compilation time. It allows us to avoid type checking errors while using function -/// contracts only for verification. -#[rustc_diagnostic_item = "KaniAnyModifies"] -#[inline(never)] +// Used to bind `core::assert` to a different name to avoid possible name conflicts if a +// crate uses `extern crate std as core`. See +// https://github.com/model-checking/kani/issues/1949 and https://github.com/model-checking/kani/issues/2187 #[doc(hidden)] -pub fn any_modifies() -> T { - // This function should not be reacheable. - // Users must include `#[kani::recursion]` in any function contracts for recursive functions; - // otherwise, this might not be properly instantiate. We mark this as unreachable to make - // sure Kani doesn't report any false positives. - unreachable!() -} - -/// This creates a symbolic *valid* value of type `T`. -/// The value is constrained to be a value accepted by the predicate passed to the filter. -/// You can assign the return value of this function to a variable that you want to make symbolic. -/// -/// # Example: -/// -/// In the snippet below, we are verifying the behavior of the function `fn_under_verification` -/// under all possible `u8` input values between 0 and 12. -/// -/// ```rust -/// let inputA: u8 = kani::any_where(|x| *x < 12); -/// fn_under_verification(inputA); -/// ``` -/// -/// Note: This is a safe construct and can only be used with types that implement the `Arbitrary` -/// trait. The Arbitrary trait is used to build a symbolic value that represents all possible -/// valid values for type `T`. -#[inline(always)] -pub fn any_where bool>(f: F) -> T { - let result = T::any(); - assume(f(&result)); - result -} - -/// This function creates a symbolic value of type `T`. This may result in an invalid value. -/// -/// # Safety -/// -/// This function is unsafe and it may represent invalid `T` values which can lead to many -/// undesirable undefined behaviors. Because of that, this function can only be used -/// internally when we can guarantee that the type T has no restriction regarding its bit level -/// representation. -/// -/// This function is also used to find concrete values in the CBMC output trace -/// and return those concrete values in concrete playback mode. -/// -/// Note that SIZE_T must be equal the size of type T in bytes. -#[inline(never)] -#[cfg(not(feature = "concrete_playback"))] -unsafe fn any_raw_internal() -> T { - any_raw::() -} - -/// This is the same as [any_raw_internal] for verification flow, but not for concrete playback. -#[inline(never)] #[cfg(not(feature = "concrete_playback"))] -unsafe fn any_raw_array() -> [T; N] { - any_raw::<[T; N]>() -} - -#[cfg(feature = "concrete_playback")] -use concrete_playback::{any_raw_array, any_raw_internal}; - -/// This low-level function returns nondet bytes of size T. -#[rustc_diagnostic_item = "KaniAnyRaw"] -#[inline(never)] -#[allow(dead_code)] -fn any_raw() -> T { - kani_intrinsic() -} - -/// Function used to generate panic with a static message as this is the only one currently -/// supported by Kani display. -/// -/// During verification this will get replaced by `assert(false)`. For concrete executions, we just -/// invoke the regular `std::panic!()` function. This function is used by our standard library -/// overrides, but not the other way around. -#[inline(never)] -#[rustc_diagnostic_item = "KaniPanic"] -#[doc(hidden)] -pub const fn panic(message: &'static str) -> ! { - panic!("{}", message) -} +pub use core::assert as __kani__workaround_core_assert; -/// An empty body that can be used to define Kani intrinsic functions. -/// -/// A Kani intrinsic is a function that is interpreted by Kani compiler. -/// While we could use `unreachable!()` or `panic!()` as the body of a kani intrinsic -/// function, both cause Kani to produce a warning since we don't support caller location. -/// (see https://github.com/model-checking/kani/issues/2010). -/// -/// This function is dead, since its caller is always handled via a hook anyway, -/// so we just need to put a body that rustc does not complain about. -/// An infinite loop works out nicely. -fn kani_intrinsic() -> T { - #[allow(clippy::empty_loop)] - loop {} -} -/// A macro to check if a condition is satisfiable at a specific location in the -/// code. -/// -/// # Example 1: -/// -/// ```rust -/// let mut set: BTreeSet = BTreeSet::new(); -/// set.insert(kani::any()); -/// set.insert(kani::any()); -/// // check if the set can end up with a single element (if both elements -/// // inserted were the same) -/// kani::cover!(set.len() == 1); -/// ``` -/// The macro can also be called without any arguments to check if a location is -/// reachable. -/// -/// # Example 2: -/// -/// ```rust -/// match e { -/// MyEnum::A => { /* .. */ } -/// MyEnum::B => { -/// // make sure the `MyEnum::B` variant is possible -/// kani::cover!(); -/// // .. -/// } -/// } -/// ``` -/// -/// A custom message can also be passed to the macro. -/// -/// # Example 3: -/// -/// ```rust -/// kani::cover!(x > y, "x can be greater than y") -/// ``` #[macro_export] macro_rules! cover { () => { @@ -347,15 +73,17 @@ macro_rules! cover { }; } -// Used to bind `core::assert` to a different name to avoid possible name conflicts if a -// crate uses `extern crate std as core`. See -// https://github.com/model-checking/kani/issues/1949 and https://github.com/model-checking/kani/issues/2187 -#[doc(hidden)] -#[cfg(not(feature = "concrete_playback"))] -pub use core::assert as __kani__workaround_core_assert; - -// Kani proc macros must be in a separate crate -pub use kani_macros::*; +/// `implies!(premise => conclusion)` means that if the `premise` is true, so +/// must be the `conclusion`. +/// +/// This simply expands to `!premise || conclusion` and is intended to make checks more readable, +/// as the concept of an implication is more natural to think about than its expansion. +#[macro_export] +macro_rules! implies { + ($premise:expr => $conclusion:expr) => { + !($premise) || ($conclusion) + }; +} pub(crate) use kani_macros::unstable_feature as unstable; diff --git a/library/kani/src/mem.rs b/library/kani/src/mem.rs deleted file mode 100644 index f718c09ec38d..000000000000 --- a/library/kani/src/mem.rs +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -//! This module contains functions useful for checking unsafe memory access. -//! -//! Given the following validity rules provided in the Rust documentation: -//! (accessed Feb 6th, 2024) -//! -//! 1. A null pointer is never valid, not even for accesses of size zero. -//! 2. For a pointer to be valid, it is necessary, but not always sufficient, that the pointer -//! be dereferenceable: the memory range of the given size starting at the pointer must all be -//! within the bounds of a single allocated object. Note that in Rust, every (stack-allocated) -//! variable is considered a separate allocated object. -//! ~~Even for operations of size zero, the pointer must not be pointing to deallocated memory, -//! i.e., deallocation makes pointers invalid even for zero-sized operations.~~ -//! ZST access is not OK for any pointer. -//! See: -//! 3. However, casting any non-zero integer literal to a pointer is valid for zero-sized -//! accesses, even if some memory happens to exist at that address and gets deallocated. -//! This corresponds to writing your own allocator: allocating zero-sized objects is not very -//! hard. The canonical way to obtain a pointer that is valid for zero-sized accesses is -//! `NonNull::dangling`. -//! 4. All accesses performed by functions in this module are non-atomic in the sense of atomic -//! operations used to synchronize between threads. -//! This means it is undefined behavior to perform two concurrent accesses to the same location -//! from different threads unless both accesses only read from memory. -//! Notice that this explicitly includes `read_volatile` and `write_volatile`: -//! Volatile accesses cannot be used for inter-thread synchronization. -//! 5. The result of casting a reference to a pointer is valid for as long as the underlying -//! object is live and no reference (just raw pointers) is used to access the same memory. -//! That is, reference and pointer accesses cannot be interleaved. -//! -//! Kani is able to verify #1 and #2 today. -//! -//! For #3, we are overly cautious, and Kani will only consider zero-sized pointer access safe if -//! the address matches `NonNull::<()>::dangling()`. -//! The way Kani tracks provenance is not enough to check if the address was the result of a cast -//! from a non-zero integer literal. - -use crate::kani_intrinsic; -use crate::mem::private::Internal; -use std::mem::{align_of, size_of}; -use std::ptr::{DynMetadata, NonNull, Pointee}; - -/// Check if the pointer is valid for write access according to [crate::mem] conditions 1, 2 -/// and 3. -/// -/// Note this function also checks for pointer alignment. Use [self::can_write_unaligned] -/// if you don't want to fail for unaligned pointers. -/// -/// This function does not check if the value stored is valid for the given type. Use -/// [self::can_dereference] for that. -/// -/// This function will panic today if the pointer is not null, and it points to an unallocated or -/// deallocated memory location. This is an existing Kani limitation. -/// See for more details. -#[crate::unstable( - feature = "mem-predicates", - issue = 2690, - reason = "experimental memory predicate API" -)] -pub fn can_write(ptr: *mut T) -> bool -where - T: ?Sized, - ::Metadata: PtrProperties, -{ - // The interface takes a mutable pointer to improve readability of the signature. - // However, using constant pointer avoid unnecessary instrumentation, and it is as powerful. - // Hence, cast to `*const T`. - let ptr: *const T = ptr; - let (thin_ptr, metadata) = ptr.to_raw_parts(); - metadata.is_ptr_aligned(thin_ptr, Internal) && is_inbounds(&metadata, thin_ptr) -} - -/// Check if the pointer is valid for unaligned write access according to [crate::mem] conditions -/// 1, 2 and 3. -/// -/// Note this function succeeds for unaligned pointers. See [self::can_write] if you also -/// want to check pointer alignment. -/// -/// This function will panic today if the pointer is not null, and it points to an unallocated or -/// deallocated memory location. This is an existing Kani limitation. -/// See for more details. -#[crate::unstable( - feature = "mem-predicates", - issue = 2690, - reason = "experimental memory predicate API" -)] -pub fn can_write_unaligned(ptr: *const T) -> bool -where - T: ?Sized, - ::Metadata: PtrProperties, -{ - let (thin_ptr, metadata) = ptr.to_raw_parts(); - is_inbounds(&metadata, thin_ptr) -} - -/// Checks that pointer `ptr` point to a valid value of type `T`. -/// -/// For that, the pointer has to be a valid pointer according to [crate::mem] conditions 1, 2 -/// and 3, -/// and the value stored must respect the validity invariants for type `T`. -/// -/// TODO: Kani should automatically add those checks when a de-reference happens. -/// -/// -/// This function will panic today if the pointer is not null, and it points to an unallocated or -/// deallocated memory location. This is an existing Kani limitation. -/// See for more details. -#[crate::unstable( - feature = "mem-predicates", - issue = 2690, - reason = "experimental memory predicate API" -)] -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub fn can_dereference(ptr: *const T) -> bool -where - T: ?Sized, - ::Metadata: PtrProperties, -{ - let (thin_ptr, metadata) = ptr.to_raw_parts(); - // Need to assert `is_initialized` because non-determinism is used under the hood, so it does - // not make sense to use it inside assumption context. - metadata.is_ptr_aligned(thin_ptr, Internal) - && is_inbounds(&metadata, thin_ptr) - && assert_is_initialized(ptr) - && unsafe { has_valid_value(ptr) } -} - -/// Checks that pointer `ptr` point to a valid value of type `T`. -/// -/// For that, the pointer has to be a valid pointer according to [crate::mem] conditions 1, 2 -/// and 3, -/// and the value stored must respect the validity invariants for type `T`. -/// -/// Note this function succeeds for unaligned pointers. See [self::can_dereference] if you also -/// want to check pointer alignment. -/// -/// This function will panic today if the pointer is not null, and it points to an unallocated or -/// deallocated memory location. This is an existing Kani limitation. -/// See for more details. -#[crate::unstable( - feature = "mem-predicates", - issue = 2690, - reason = "experimental memory predicate API" -)] -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub fn can_read_unaligned(ptr: *const T) -> bool -where - T: ?Sized, - ::Metadata: PtrProperties, -{ - let (thin_ptr, metadata) = ptr.to_raw_parts(); - // Need to assert `is_initialized` because non-determinism is used under the hood, so it does - // not make sense to use it inside assumption context. - is_inbounds(&metadata, thin_ptr) - && assert_is_initialized(ptr) - && unsafe { has_valid_value(ptr) } -} - -/// Checks that `data_ptr` points to an allocation that can hold data of size calculated from `T`. -/// -/// This will panic if `data_ptr` points to an invalid `non_null` -fn is_inbounds(metadata: &M, data_ptr: *const ()) -> bool -where - M: PtrProperties, - T: ?Sized, -{ - let sz = metadata.pointee_size(Internal); - if sz == 0 { - true // ZST pointers are always valid including nullptr. - } else if data_ptr.is_null() { - false - } else { - // Note that this branch can't be tested in concrete execution as `is_read_ok` needs to be - // stubbed. - // We first assert that the data_ptr - crate::assert( - unsafe { is_allocated(data_ptr, 0) }, - "Kani does not support reasoning about pointer to unallocated memory", - ); - unsafe { is_allocated(data_ptr, sz) } - } -} - -mod private { - /// Define like this to restrict usage of PtrProperties functions outside Kani. - #[derive(Copy, Clone)] - pub struct Internal; -} - -/// Trait that allow us to extract information from pointers without de-referencing them. -#[doc(hidden)] -pub trait PtrProperties { - fn pointee_size(&self, _: Internal) -> usize; - - /// A pointer is aligned if its address is a multiple of its minimum alignment. - fn is_ptr_aligned(&self, ptr: *const (), internal: Internal) -> bool { - let min = self.min_alignment(internal); - ptr as usize % min == 0 - } - - fn min_alignment(&self, _: Internal) -> usize; - - fn dangling(&self, _: Internal) -> *const (); -} - -/// Get the information for sized types (they don't have metadata). -impl PtrProperties for () { - fn pointee_size(&self, _: Internal) -> usize { - size_of::() - } - - fn min_alignment(&self, _: Internal) -> usize { - align_of::() - } - - fn dangling(&self, _: Internal) -> *const () { - NonNull::::dangling().as_ptr() as *const _ - } -} - -/// Get the information from the str metadata. -impl PtrProperties for usize { - #[inline(always)] - fn pointee_size(&self, _: Internal) -> usize { - *self - } - - /// String slices are a UTF-8 representation of characters that have the same layout as slices - /// of type [u8]. - /// - fn min_alignment(&self, _: Internal) -> usize { - align_of::() - } - - fn dangling(&self, _: Internal) -> *const () { - NonNull::::dangling().as_ptr() as _ - } -} - -/// Get the information from the slice metadata. -impl PtrProperties<[T]> for usize { - fn pointee_size(&self, _: Internal) -> usize { - *self * size_of::() - } - - fn min_alignment(&self, _: Internal) -> usize { - align_of::() - } - - fn dangling(&self, _: Internal) -> *const () { - NonNull::::dangling().as_ptr() as _ - } -} - -/// Get the information from the vtable. -impl PtrProperties for DynMetadata -where - T: ?Sized, -{ - fn pointee_size(&self, _: Internal) -> usize { - self.size_of() - } - - fn min_alignment(&self, _: Internal) -> usize { - self.align_of() - } - - fn dangling(&self, _: Internal) -> *const () { - NonNull::<&T>::dangling().as_ptr() as _ - } -} - -/// Check if the pointer `_ptr` contains an allocated address of size equal or greater than `_size`. -/// -/// # Safety -/// -/// This function should only be called to ensure a pointer is always valid, i.e., in an assertion -/// context. -/// -/// I.e.: This function always returns `true` if the pointer is valid. -/// Otherwise, it returns non-det boolean. -#[rustc_diagnostic_item = "KaniIsAllocated"] -#[inline(never)] -unsafe fn is_allocated(_ptr: *const (), _size: usize) -> bool { - kani_intrinsic() -} - -/// Check if the value stored in the given location satisfies type `T` validity requirements. -/// -/// # Safety -/// -/// - Users have to ensure that the pointer is aligned the pointed memory is allocated. -#[rustc_diagnostic_item = "KaniValidValue"] -#[inline(never)] -unsafe fn has_valid_value(_ptr: *const T) -> bool { - kani_intrinsic() -} - -/// Check whether `len * size_of::()` bytes are initialized starting from `ptr`. -#[rustc_diagnostic_item = "KaniIsInitialized"] -#[inline(never)] -pub(crate) fn is_initialized(_ptr: *const T) -> bool { - kani_intrinsic() -} - -/// A helper to assert `is_initialized` to use it as a part of other predicates. -fn assert_is_initialized(ptr: *const T) -> bool { - crate::check(is_initialized(ptr), "Undefined Behavior: Reading from an uninitialized pointer"); - true -} - -/// Get the object ID of the given pointer. -#[doc(hidden)] -#[crate::unstable( - feature = "ghost-state", - issue = 3184, - reason = "experimental ghost state/shadow memory API" -)] -#[rustc_diagnostic_item = "KaniPointerObject"] -#[inline(never)] -pub fn pointer_object(_ptr: *const T) -> usize { - kani_intrinsic() -} - -/// Get the object offset of the given pointer. -#[doc(hidden)] -#[crate::unstable( - feature = "ghost-state", - issue = 3184, - reason = "experimental ghost state/shadow memory API" -)] -#[rustc_diagnostic_item = "KaniPointerOffset"] -#[inline(never)] -pub fn pointer_offset(_ptr: *const T) -> usize { - kani_intrinsic() -} - -#[cfg(test)] -mod tests { - use super::{can_dereference, can_write, PtrProperties}; - use crate::mem::private::Internal; - use std::fmt::Debug; - use std::intrinsics::size_of; - use std::mem::{align_of, align_of_val, size_of_val}; - use std::ptr; - use std::ptr::{NonNull, Pointee}; - - fn size_of_t(ptr: *const T) -> usize - where - T: ?Sized, - ::Metadata: PtrProperties, - { - let (_, metadata) = ptr.to_raw_parts(); - metadata.pointee_size(Internal) - } - - fn align_of_t(ptr: *const T) -> usize - where - T: ?Sized, - ::Metadata: PtrProperties, - { - let (_, metadata) = ptr.to_raw_parts(); - metadata.min_alignment(Internal) - } - - #[test] - fn test_size_of() { - assert_eq!(size_of_t("hi"), size_of_val("hi")); - assert_eq!(size_of_t(&0u8), size_of_val(&0u8)); - assert_eq!(size_of_t(&0u8 as *const dyn std::fmt::Display), size_of_val(&0u8)); - assert_eq!(size_of_t(&[0u8, 1u8] as &[u8]), size_of_val(&[0u8, 1u8])); - assert_eq!(size_of_t(&[] as &[u8]), size_of_val::<[u8; 0]>(&[])); - assert_eq!( - size_of_t(NonNull::::dangling().as_ptr() as *const dyn std::fmt::Display), - size_of::() - ); - } - - #[test] - fn test_alignment() { - assert_eq!(align_of_t("hi"), align_of_val("hi")); - assert_eq!(align_of_t(&0u8), align_of_val(&0u8)); - assert_eq!(align_of_t(&0u32 as *const dyn std::fmt::Display), align_of_val(&0u32)); - assert_eq!(align_of_t(&[0isize, 1isize] as &[isize]), align_of_val(&[0isize, 1isize])); - assert_eq!(align_of_t(&[] as &[u8]), align_of_val::<[u8; 0]>(&[])); - assert_eq!( - align_of_t(NonNull::::dangling().as_ptr() as *const dyn std::fmt::Display), - align_of::() - ); - } - - #[test] - pub fn test_empty_slice() { - let slice_ptr = Vec::::new().as_mut_slice() as *mut [char]; - assert!(can_write(slice_ptr)); - } - - #[test] - pub fn test_empty_str() { - let slice_ptr = String::new().as_mut_str() as *mut str; - assert!(can_write(slice_ptr)); - } - - #[test] - fn test_dangling_zst() { - test_dangling_of_zst::<()>(); - test_dangling_of_zst::<[(); 10]>(); - } - - fn test_dangling_of_zst() { - let dangling: *mut T = NonNull::::dangling().as_ptr(); - assert!(can_write(dangling)); - - let vec_ptr = Vec::::new().as_mut_ptr(); - assert!(can_write(vec_ptr)); - } - - #[test] - fn test_null_fat_ptr() { - assert!(!can_dereference(ptr::null::() as *const dyn Debug)); - } - - #[test] - fn test_null_char() { - assert!(!can_dereference(ptr::null::())); - } - - #[test] - fn test_null_mut() { - assert!(!can_write(ptr::null_mut::())); - } -} diff --git a/library/kani_core/Cargo.toml b/library/kani_core/Cargo.toml index ec12209f0e08..5388dcfb9427 100644 --- a/library/kani_core/Cargo.toml +++ b/library/kani_core/Cargo.toml @@ -10,7 +10,7 @@ publish = false description = "Define core constructs to use with Kani" [dependencies] -kani_macros = { path = "../kani_macros", features = ["no_core"] } +kani_macros = { path = "../kani_macros"} [features] no_core=["kani_macros/no_core"] diff --git a/library/kani_core/src/lib.rs b/library/kani_core/src/lib.rs index b7263816800a..6cbe98d30df2 100644 --- a/library/kani_core/src/lib.rs +++ b/library/kani_core/src/lib.rs @@ -52,6 +52,10 @@ macro_rules! kani_lib { pub use kani_core::*; kani_core::kani_intrinsics!(std); kani_core::generate_arbitrary!(std); + + pub mod mem { + kani_core::kani_mem!(std); + } }; } @@ -298,6 +302,7 @@ macro_rules! kani_intrinsics { loop {} } + #[doc(hidden)] pub mod internal { use crate::kani::Arbitrary; use core::ptr; diff --git a/library/kani_core/src/mem.rs b/library/kani_core/src/mem.rs index 0b029ad53089..34cff4b17ad7 100644 --- a/library/kani_core/src/mem.rs +++ b/library/kani_core/src/mem.rs @@ -36,6 +36,7 @@ //! The way Kani tracks provenance is not enough to check if the address was the result of a cast //! from a non-zero integer literal. +#[allow(clippy::crate_in_macro_def)] #[macro_export] macro_rules! kani_mem { ($core:tt) => { @@ -56,12 +57,11 @@ macro_rules! kani_mem { /// This function will panic today if the pointer is not null, and it points to an unallocated or /// deallocated memory location. This is an existing Kani limitation. /// See for more details. - // TODO: Add this back! We might need to rename the attribute. - //#[crate::unstable( - // feature = "mem-predicates", - // issue = 2690, - // reason = "experimental memory predicate API" - //)] + #[crate::kani::unstable_feature( + feature = "mem-predicates", + issue = 2690, + reason = "experimental memory predicate API" + )] pub fn can_write(ptr: *mut T) -> bool where T: ?Sized, @@ -84,12 +84,11 @@ macro_rules! kani_mem { /// This function will panic today if the pointer is not null, and it points to an unallocated or /// deallocated memory location. This is an existing Kani limitation. /// See for more details. - // TODO: Add this back! We might need to rename the attribute. - //#[crate::unstable( - // feature = "mem-predicates", - // issue = 2690, - // reason = "experimental memory predicate API" - //)] + #[crate::kani::unstable_feature( + feature = "mem-predicates", + issue = 2690, + reason = "experimental memory predicate API" + )] pub fn can_write_unaligned(ptr: *const T) -> bool where T: ?Sized, @@ -111,11 +110,11 @@ macro_rules! kani_mem { /// This function will panic today if the pointer is not null, and it points to an unallocated or /// deallocated memory location. This is an existing Kani limitation. /// See for more details. - //#[crate::unstable( - // feature = "mem-predicates", - // issue = 2690, - // reason = "experimental memory predicate API" - //)] + #[crate::kani::unstable_feature( + feature = "mem-predicates", + issue = 2690, + reason = "experimental memory predicate API" + )] #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn can_dereference(ptr: *const T) -> bool where @@ -143,12 +142,11 @@ macro_rules! kani_mem { /// This function will panic today if the pointer is not null, and it points to an unallocated or /// deallocated memory location. This is an existing Kani limitation. /// See for more details. - // TODO: Add this back! We might need to rename the attribute. - //#[crate::unstable( - // feature = "mem-predicates", - // issue = 2690, - // reason = "experimental memory predicate API" - //)] + #[crate::kani::unstable_feature( + feature = "mem-predicates", + issue = 2690, + reason = "experimental memory predicate API" + )] #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn can_read_unaligned(ptr: *const T) -> bool where @@ -180,7 +178,7 @@ macro_rules! kani_mem { // Note that this branch can't be tested in concrete execution as `is_read_ok` needs to be // stubbed. // We first assert that the data_ptr - assert!( + super::assert( unsafe { is_allocated(data_ptr, 0) }, "Kani does not support reasoning about pointer to unallocated memory", ); @@ -320,30 +318,28 @@ macro_rules! kani_mem { } /// Get the object ID of the given pointer. - // TODO: Add this back later, as there is no unstable attribute here. - // #[doc(hidden)] - // #[crate::unstable( - // feature = "ghost-state", - // issue = 3184, - // reason = "experimental ghost state/shadow memory API" - // )] + #[doc(hidden)] + #[crate::kani::unstable_feature( + feature = "ghost-state", + issue = 3184, + reason = "experimental ghost state/shadow memory API" + )] #[rustc_diagnostic_item = "KaniPointerObject"] #[inline(never)] - pub(crate) fn pointer_object(_ptr: *const T) -> usize { + pub fn pointer_object(_ptr: *const T) -> usize { kani_intrinsic() } /// Get the object offset of the given pointer. - // TODO: Add this back later, as there is no unstable attribute here. - // #[doc(hidden)] - // #[crate::unstable( - // feature = "ghost-state", - // issue = 3184, - // reason = "experimental ghost state/shadow memory API" - // )] + #[doc(hidden)] + #[crate::kani::unstable_feature( + feature = "ghost-state", + issue = 3184, + reason = "experimental ghost state/shadow memory API" + )] #[rustc_diagnostic_item = "KaniPointerOffset"] #[inline(never)] - pub(crate) fn pointer_offset(_ptr: *const T) -> usize { + pub fn pointer_offset(_ptr: *const T) -> usize { kani_intrinsic() } }; diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 5917c322729e..42eb37a56584 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -22,4 +22,4 @@ syn = { version = "2.0.18", features = ["full", "visit-mut", "visit", "extra-tra rustc_private = true [features] -no_core = [] \ No newline at end of file +no_core = [] diff --git a/tests/expected/function-contract/modifies/check_invalid_modifies.expected b/tests/expected/function-contract/modifies/check_invalid_modifies.expected index 660430705aa2..c0ce839c3aae 100644 --- a/tests/expected/function-contract/modifies/check_invalid_modifies.expected +++ b/tests/expected/function-contract/modifies/check_invalid_modifies.expected @@ -1,7 +1,2 @@ -error: `&str` doesn't implement `kani::Arbitrary`\ - -->\ -| -| T::any() -| ^^^^^^^^ -| +error: `&str` doesn't implement `kani::Arbitrary`. = help: All objects in the modifies clause must implement the Arbitrary. The return type must also implement the Arbitrary trait if you are checking recursion or using verified stub. diff --git a/tests/expected/function-contract/valid_ptr.expected b/tests/expected/function-contract/valid_ptr.expected index 1b62781adaaf..4014a0723029 100644 --- a/tests/expected/function-contract/valid_ptr.expected +++ b/tests/expected/function-contract/valid_ptr.expected @@ -1,5 +1,4 @@ Checking harness pre_condition::harness_invalid_ptr... -Failed Checks: Kani does not support reasoning about pointer to unallocated memory VERIFICATION:- SUCCESSFUL (encountered one or more panics as expected) Checking harness pre_condition::harness_stack_ptr... diff --git a/tests/script-based-pre/verify_std_cmd/verify_std.sh b/tests/script-based-pre/verify_std_cmd/verify_std.sh index 3a24bf15241e..6a95c667b71b 100755 --- a/tests/script-based-pre/verify_std_cmd/verify_std.sh +++ b/tests/script-based-pre/verify_std_cmd/verify_std.sh @@ -51,7 +51,7 @@ cat ${TMP_DIR}/std_lib.rs >> ${TMP_DIR}/library/std/src/lib.rs echo "[TEST] Run kani verify-std" export RUST_BACKTRACE=1 -kani verify-std -Z unstable-options "${TMP_DIR}/library" --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing +kani verify-std -Z unstable-options "${TMP_DIR}/library" --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing -Z mem-predicates # Cleanup rm -r ${TMP_DIR} diff --git a/tests/ui/derive-arbitrary/non_arbitrary_param/expected b/tests/ui/derive-arbitrary/non_arbitrary_param/expected index 55f12678cf9a..68e3710d6dcb 100644 --- a/tests/ui/derive-arbitrary/non_arbitrary_param/expected +++ b/tests/ui/derive-arbitrary/non_arbitrary_param/expected @@ -1,4 +1,4 @@ error[E0277]: the trait bound `Void: kani::Arbitrary` is not satisfied - |\ -14 | let _wrapper: Wrapper = kani::any();\ - | ^^^^^^^^^^^ the trait `kani::Arbitrary` is not implemented for `Void`, which is required by `Wrapper: kani::Arbitrary`\ +|\ +| let _wrapper: Wrapper = kani::any();\ +| ^^^^^^^^^^^ the trait `kani::Arbitrary` is not implemented for `Void`, which is required by `Wrapper: kani::Arbitrary`\ From c515d662fff970ae4e5f50dfffa01ab77375bf34 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Tue, 6 Aug 2024 18:05:08 +0200 Subject: [PATCH 25/28] Toolchain update: avoid Javascript interpreting git log (#3421) git log entries may themselves use backticks, which prematurely ended the string (and then caused Javascript syntax errors). Avoid this problem by using the environment variable rather than having Javascript see the string contents (of that environment variable). By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. Co-authored-by: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> --- .github/workflows/toolchain-upgrade.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/toolchain-upgrade.yml b/.github/workflows/toolchain-upgrade.yml index b11f4e6b591a..76c3a5484414 100644 --- a/.github/workflows/toolchain-upgrade.yml +++ b/.github/workflows/toolchain-upgrade.yml @@ -64,7 +64,7 @@ jobs: https://github.com/rust-lang/rust/commit/${{ env.next_toolchain_hash }}. The log for this commit range is: - ${{ env.git_log }}` + ` + process.env.git_log }) - name: Create Issue From fdce213672724675d1f76f05ede100172ae3ff9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:09:48 -0400 Subject: [PATCH 26/28] Automatic toolchain upgrade to nightly-2024-08-07 (#3423) Update Rust toolchain from nightly-2024-08-06 to nightly-2024-08-07 without any other source changes. Co-authored-by: celinval <35149715+celinval@users.noreply.github.com> --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 23a3f2bc99eb..e3b6229d73c6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [toolchain] -channel = "nightly-2024-08-06" +channel = "nightly-2024-08-07" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 3fb3a73d6d563e92eb447b32ec963ea77853ec00 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 7 Aug 2024 13:34:46 -0700 Subject: [PATCH 27/28] Stabilize pointer-to-reference cast validity checks (#3426) This PR stabilizes pointer-to-reference cast validity checks, so that they are run by default. Resolves #3425 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .github/workflows/verify-std-check.yml | 4 ++-- kani-compiler/src/args.rs | 3 --- .../codegen_cprover_gotoc/codegen/rvalue.rs | 21 +++++++------------ kani-driver/src/call_single_file.rs | 4 ---- kani_metadata/src/unstable.rs | 2 -- tests/expected/dangling-ptr-println/main.rs | 1 - .../ptr_to_ref_cast/ptr_to_ref_cast.rs | 1 - tests/std-checks/core/Cargo.toml | 2 +- 8 files changed, 11 insertions(+), 27 deletions(-) diff --git a/.github/workflows/verify-std-check.yml b/.github/workflows/verify-std-check.yml index 1bad09cd3c6d..6935b2bb4073 100644 --- a/.github/workflows/verify-std-check.yml +++ b/.github/workflows/verify-std-check.yml @@ -59,7 +59,7 @@ jobs: continue-on-error: true run: | kani verify-std -Z unstable-options ./library --target-dir ${{ runner.temp }} -Z function-contracts \ - -Z mem-predicates -Z ptr-to-ref-cast-checks + -Z mem-predicates # If the head failed, check if it's a new failure. - name: Checkout base @@ -77,7 +77,7 @@ jobs: continue-on-error: true run: | kani verify-std -Z unstable-options ./library --target-dir ${{ runner.temp }} -Z function-contracts \ - -Z mem-predicates -Z ptr-to-ref-cast-checks + -Z mem-predicates - name: Compare PR results if: steps.check-head.outcome != 'success' && steps.check-head.outcome != steps.check-base.outcome diff --git a/kani-compiler/src/args.rs b/kani-compiler/src/args.rs index 3efc5c0f4f61..3fa74b0e5aba 100644 --- a/kani-compiler/src/args.rs +++ b/kani-compiler/src/args.rs @@ -82,9 +82,6 @@ pub enum ExtraChecks { /// Check that produced values are valid except for uninitialized values. /// See https://github.com/model-checking/kani/issues/920. Validity, - /// Check pointer validity when casting pointers to references. - /// See https://github.com/model-checking/kani/issues/2975. - PtrToRefCast, /// Check for using uninitialized memory. Uninit, } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index 4883c608f482..a30eeb7639ce 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -1,7 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::args::ExtraChecks; use crate::codegen_cprover_gotoc::codegen::place::ProjectedPlace; use crate::codegen_cprover_gotoc::codegen::ty_stable::pointee_type_stable; use crate::codegen_cprover_gotoc::codegen::PropertyClass; @@ -730,18 +729,14 @@ impl<'tcx> GotocCtx<'tcx> { Rvalue::Repeat(op, sz) => self.codegen_rvalue_repeat(op, sz, loc), Rvalue::Ref(_, _, p) | Rvalue::AddressOf(_, p) => { let place_ref = self.codegen_place_ref_stable(&p, loc); - if self.queries.args().ub_check.contains(&ExtraChecks::PtrToRefCast) { - let place_ref_type = place_ref.typ().clone(); - match self.codegen_raw_ptr_deref_validity_check(&p, &loc) { - Some(ptr_validity_check_expr) => Expr::statement_expression( - vec![ptr_validity_check_expr, place_ref.as_stmt(loc)], - place_ref_type, - loc, - ), - None => place_ref, - } - } else { - place_ref + let place_ref_type = place_ref.typ().clone(); + match self.codegen_raw_ptr_deref_validity_check(&p, &loc) { + Some(ptr_validity_check_expr) => Expr::statement_expression( + vec![ptr_validity_check_expr, place_ref.as_stmt(loc)], + place_ref_type, + loc, + ), + None => place_ref, } } Rvalue::Len(p) => self.codegen_rvalue_len(p, loc), diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index bbeb5bfa417d..4b30fe877507 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -135,10 +135,6 @@ impl KaniSession { flags.push("--ub-check=validity".into()) } - if self.args.common_args.unstable_features.contains(UnstableFeature::PtrToRefCastChecks) { - flags.push("--ub-check=ptr_to_ref_cast".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_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index 68e4fba28819..120ab0a9e55c 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -89,8 +89,6 @@ pub enum UnstableFeature { ValidValueChecks, /// Ghost state and shadow memory APIs. GhostState, - /// Automatically check that pointers are valid when casting them to references. - PtrToRefCastChecks, /// Automatically check that uninitialized memory is not used. UninitChecks, /// Enable an unstable option or subcommand. diff --git a/tests/expected/dangling-ptr-println/main.rs b/tests/expected/dangling-ptr-println/main.rs index a83afa6f8313..04328c92cb52 100644 --- a/tests/expected/dangling-ptr-println/main.rs +++ b/tests/expected/dangling-ptr-println/main.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ptr-to-ref-cast-checks //! These tests check that Kani correctly detects dangling pointer dereference inside println macro. //! Related issue: . diff --git a/tests/expected/ptr_to_ref_cast/ptr_to_ref_cast.rs b/tests/expected/ptr_to_ref_cast/ptr_to_ref_cast.rs index 3b713a34d967..e04ac9cd3169 100644 --- a/tests/expected/ptr_to_ref_cast/ptr_to_ref_cast.rs +++ b/tests/expected/ptr_to_ref_cast/ptr_to_ref_cast.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Z ptr-to-ref-cast-checks //! This test case checks that raw pointer validity is checked before converting it to a reference, e.g., &(*ptr). diff --git a/tests/std-checks/core/Cargo.toml b/tests/std-checks/core/Cargo.toml index f6e1645c3a39..9cacaa1368e3 100644 --- a/tests/std-checks/core/Cargo.toml +++ b/tests/std-checks/core/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" description = "This crate contains contracts and harnesses for core library" [package.metadata.kani] -unstable = { function-contracts = true, mem-predicates = true, ptr-to-ref-cast-checks = true } +unstable = { function-contracts = true, mem-predicates = true } [package.metadata.kani.flags] output-format = "terse" From 2a3538dc4a63bd52677738da017ec019caf5d71b Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Wed, 7 Aug 2024 18:35:35 -0400 Subject: [PATCH 28/28] Update depencencies (#3428) By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. Signed-off-by: Felipe R. Monteiro --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d7580e3b742..52250d2468c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -991,9 +991,9 @@ dependencies = [ [[package]] name = "serde_test" -version = "1.0.176" +version = "1.0.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" +checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" dependencies = [ "serde", ] @@ -1098,15 +1098,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]]