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 001/159] 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 002/159] 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 003/159] 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 004/159] 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 005/159] 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 006/159] 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 007/159] 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 008/159] 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 009/159] 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 010/159] 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 011/159] 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 012/159] 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 013/159] 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 014/159] 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 015/159] 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 016/159] 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 017/159] 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 018/159] 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 019/159] 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]] From 7a6f1a445066c7c6720cd0583cf1cf56f6646955 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Thu, 8 Aug 2024 21:07:32 -0400 Subject: [PATCH 020/159] Bump Kani version to 0.54.0 (#3430) ## 0.54.0 ### Major Changes * We added support for slices in the `#[kani::modifies(...)]` clauses when using function contracts. * We introduce an `#[safety_constraint(...)]` attribute helper for the `Arbitrary` and `Invariant` macros. * We enabled support for concrete playback for harness that contains stubs or function contracts. * We added support for log2*, log10*, powif*, fma*, and sqrt* intrisincs. ### Breaking Changes * The `-Z ptr-to-ref-cast-checks` option has been removed, and pointer validity checks when casting raw pointers to references are now run by default. ## What's Changed * Make Kani reject mutable pointer casts if padding is incompatible and memory initialization is checked by @artemagvanian in https://github.com/model-checking/kani/pull/3332 * Fix visibility of some Kani intrinsics by @artemagvanian in https://github.com/model-checking/kani/pull/3323 * Function Contracts: Modify Slices by @pi314mm in https://github.com/model-checking/kani/pull/3295 * Support for disabling automatically generated pointer checks to avoid reinstrumentation by @artemagvanian in https://github.com/model-checking/kani/pull/3344 * Add support for global transformations by @artemagvanian in https://github.com/model-checking/kani/pull/3348 * Enable an `#[safety_constraint(...)]` attribute helper for the `Arbitrary` and `Invariant` macros by @adpaco-aws in https://github.com/model-checking/kani/pull/3283 * Fix contract handling of promoted constants and constant static by @celinval in https://github.com/model-checking/kani/pull/3305 * Bump CBMC Viewer to 3.9 by @tautschnig in https://github.com/model-checking/kani/pull/3373 * Update to CBMC version 6.1.1 by @tautschnig in https://github.com/model-checking/kani/pull/2995 * Define a struct-level `#[safety_constraint(...)]` attribute by @adpaco-aws in https://github.com/model-checking/kani/pull/3270 * Enable concrete playback for contract and stubs by @celinval in https://github.com/model-checking/kani/pull/3389 * Add code scanner tool by @celinval in https://github.com/model-checking/kani/pull/3120 * Enable contracts in associated functions by @celinval in https://github.com/model-checking/kani/pull/3363 * Enable log2*, log10* intrinsics by @tautschnig in https://github.com/model-checking/kani/pull/3001 * Enable powif* intrinsics by @tautschnig in https://github.com/model-checking/kani/pull/2999 * Enable fma* intrinsics by @tautschnig in https://github.com/model-checking/kani/pull/3002 * Enable sqrt* intrinsics by @tautschnig in https://github.com/model-checking/kani/pull/3000 * Remove assigns clause for ZST pointers by @carolynzech in https://github.com/model-checking/kani/pull/3417 * Instrumentation for delayed UB stemming from uninitialized memory by @artemagvanian in https://github.com/model-checking/kani/pull/3374 * Unify kani library and kani core logic by @jaisnan in https://github.com/model-checking/kani/pull/3333 * Stabilize pointer-to-reference cast validity checks by @artemagvanian in https://github.com/model-checking/kani/pull/3426 * Rust toolchain upgraded to `nightly-2024-08-07` by @jaisnan @qinheping @tautschnig @feliperodri ## New Contributors * @carolynzech made their first contribution in https://github.com/model-checking/kani/pull/3387 **Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.53.0...kani-0.54.0 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 --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++ Cargo.lock | 20 ++++++++--------- Cargo.toml | 2 +- cprover_bindings/Cargo.toml | 2 +- kani-compiler/Cargo.toml | 2 +- kani-driver/Cargo.toml | 2 +- kani_metadata/Cargo.toml | 2 +- library/kani/Cargo.toml | 2 +- library/kani_core/Cargo.toml | 2 +- library/kani_macros/Cargo.toml | 2 +- library/std/Cargo.toml | 2 +- tools/build-kani/Cargo.toml | 2 +- 12 files changed, 60 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ae290853a3..d36def1a45f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,46 @@ This file contains notable changes (e.g. breaking changes, major changes, etc.) This file was introduced starting Kani 0.23.0, so it only contains changes from version 0.23.0 onwards. +## [0.54.0] + +### Major Changes +* We added support for slices in the `#[kani::modifies(...)]` clauses when using function contracts. +* We introduce an `#[safety_constraint(...)]` attribute helper for the `Arbitrary` and `Invariant` macros. +* We enabled support for concrete playback for harness that contains stubs or function contracts. +* We added support for log2*, log10*, powif*, fma*, and sqrt* intrisincs. + +### Breaking Changes +* The `-Z ptr-to-ref-cast-checks` option has been removed, and pointer validity checks when casting raw pointers to references are now run by default. + +## What's Changed +* Make Kani reject mutable pointer casts if padding is incompatible and memory initialization is checked by @artemagvanian in https://github.com/model-checking/kani/pull/3332 +* Fix visibility of some Kani intrinsics by @artemagvanian in https://github.com/model-checking/kani/pull/3323 +* Function Contracts: Modify Slices by @pi314mm in https://github.com/model-checking/kani/pull/3295 +* Support for disabling automatically generated pointer checks to avoid reinstrumentation by @artemagvanian in https://github.com/model-checking/kani/pull/3344 +* Add support for global transformations by @artemagvanian in https://github.com/model-checking/kani/pull/3348 +* Enable an `#[safety_constraint(...)]` attribute helper for the `Arbitrary` and `Invariant` macros by @adpaco-aws in https://github.com/model-checking/kani/pull/3283 +* Fix contract handling of promoted constants and constant static by @celinval in https://github.com/model-checking/kani/pull/3305 +* Bump CBMC Viewer to 3.9 by @tautschnig in https://github.com/model-checking/kani/pull/3373 +* Update to CBMC version 6.1.1 by @tautschnig in https://github.com/model-checking/kani/pull/2995 +* Define a struct-level `#[safety_constraint(...)]` attribute by @adpaco-aws in https://github.com/model-checking/kani/pull/3270 +* Enable concrete playback for contract and stubs by @celinval in https://github.com/model-checking/kani/pull/3389 +* Add code scanner tool by @celinval in https://github.com/model-checking/kani/pull/3120 +* Enable contracts in associated functions by @celinval in https://github.com/model-checking/kani/pull/3363 +* Enable log2*, log10* intrinsics by @tautschnig in https://github.com/model-checking/kani/pull/3001 +* Enable powif* intrinsics by @tautschnig in https://github.com/model-checking/kani/pull/2999 +* Enable fma* intrinsics by @tautschnig in https://github.com/model-checking/kani/pull/3002 +* Enable sqrt* intrinsics by @tautschnig in https://github.com/model-checking/kani/pull/3000 +* Remove assigns clause for ZST pointers by @carolynzech in https://github.com/model-checking/kani/pull/3417 +* Instrumentation for delayed UB stemming from uninitialized memory by @artemagvanian in https://github.com/model-checking/kani/pull/3374 +* Unify kani library and kani core logic by @jaisnan in https://github.com/model-checking/kani/pull/3333 +* Stabilize pointer-to-reference cast validity checks by @artemagvanian in https://github.com/model-checking/kani/pull/3426 +* Rust toolchain upgraded to `nightly-2024-08-07` by @jaisnan @qinheping @tautschnig @feliperodri + +## New Contributors +* @carolynzech made their first contribution in https://github.com/model-checking/kani/pull/3387 + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.53.0...kani-0.54.0 + ## [0.53.0] ### Major Changes diff --git a/Cargo.lock b/Cargo.lock index 52250d2468c8..12c28c49c1bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,7 +92,7 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "build-kani" -version = "0.53.0" +version = "0.54.0" dependencies = [ "anyhow", "cargo_metadata", @@ -234,7 +234,7 @@ dependencies = [ [[package]] name = "cprover_bindings" -version = "0.53.0" +version = "0.54.0" dependencies = [ "lazy_static", "linear-map", @@ -432,7 +432,7 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "kani" -version = "0.53.0" +version = "0.54.0" dependencies = [ "kani_core", "kani_macros", @@ -440,7 +440,7 @@ dependencies = [ [[package]] name = "kani-compiler" -version = "0.53.0" +version = "0.54.0" dependencies = [ "clap", "cprover_bindings", @@ -461,7 +461,7 @@ dependencies = [ [[package]] name = "kani-driver" -version = "0.53.0" +version = "0.54.0" dependencies = [ "anyhow", "cargo_metadata", @@ -489,7 +489,7 @@ dependencies = [ [[package]] name = "kani-verifier" -version = "0.53.0" +version = "0.54.0" dependencies = [ "anyhow", "home", @@ -498,14 +498,14 @@ dependencies = [ [[package]] name = "kani_core" -version = "0.53.0" +version = "0.54.0" dependencies = [ "kani_macros", ] [[package]] name = "kani_macros" -version = "0.53.0" +version = "0.54.0" dependencies = [ "proc-macro-error", "proc-macro2", @@ -515,7 +515,7 @@ dependencies = [ [[package]] name = "kani_metadata" -version = "0.53.0" +version = "0.54.0" dependencies = [ "clap", "cprover_bindings", @@ -1034,7 +1034,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "std" -version = "0.53.0" +version = "0.54.0" dependencies = [ "kani", ] diff --git a/Cargo.toml b/Cargo.toml index 68b5bcc20ff3..f2301983fcb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-verifier" -version = "0.53.0" +version = "0.54.0" edition = "2021" description = "A bit-precise model checker for Rust." readme = "README.md" diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index aced9e5b9b65..c53ffb207fcf 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cprover_bindings" -version = "0.53.0" +version = "0.54.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 23389c156302..24ed00f54338 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-compiler" -version = "0.53.0" +version = "0.54.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index d58d686d7d43..c57ec8e8e2f2 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-driver" -version = "0.53.0" +version = "0.54.0" edition = "2021" description = "Build a project with Kani and run all proof harnesses" license = "MIT OR Apache-2.0" diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index 816752a58e03..de91900d6d9c 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_metadata" -version = "0.53.0" +version = "0.54.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index 1fba7875672a..7d7ced8ee0b7 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani" -version = "0.53.0" +version = "0.54.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_core/Cargo.toml b/library/kani_core/Cargo.toml index 5388dcfb9427..8928992c3f16 100644 --- a/library/kani_core/Cargo.toml +++ b/library/kani_core/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_core" -version = "0.53.0" +version = "0.54.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 42eb37a56584..475e2978df91 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_macros" -version = "0.53.0" +version = "0.54.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index ae70767f6781..9f0d09b0d7bb 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -5,7 +5,7 @@ # Note: this package is intentionally named std to make sure the names of # standard library symbols are preserved name = "std" -version = "0.53.0" +version = "0.54.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/tools/build-kani/Cargo.toml b/tools/build-kani/Cargo.toml index 7c8e6eef122a..cd2985e4ad68 100644 --- a/tools/build-kani/Cargo.toml +++ b/tools/build-kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "build-kani" -version = "0.53.0" +version = "0.54.0" edition = "2021" description = "Builds Kani, Sysroot and release bundle." license = "MIT OR Apache-2.0" From bec5fd1a2298f7f455c559ddfc938fd7a24e9b4d Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 9 Aug 2024 16:37:57 +0200 Subject: [PATCH 021/159] Update CBMC build instructions for Amazon Linux 2 (#3431) The default compiler on Amazon Linux 2 is GCC 7, which is not sufficiently recent for building CBMC v6+. Install and use GCC 10, adjust warnings to cope with the flex version provided by Amazon Linux 2, and handle the non-default std::filesystem support. Furthermore, apply a workaround until https://github.com/diffblue/cbmc/issues/8357 has been fixed on the CBMC side. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- scripts/setup/al2/install_cbmc.sh | 79 +++++++++++++++++++++++++++++-- scripts/setup/al2/install_deps.sh | 1 + 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/scripts/setup/al2/install_cbmc.sh b/scripts/setup/al2/install_cbmc.sh index 34abcc52db93..de4b5215e083 100755 --- a/scripts/setup/al2/install_cbmc.sh +++ b/scripts/setup/al2/install_cbmc.sh @@ -21,10 +21,83 @@ git clone \ pushd "${WORK_DIR}" -mkdir build -git submodule update --init +# apply workaround for https://github.com/diffblue/cbmc/issues/8357 until it is +# properly fixed in CBMC +cat > varargs.patch << "EOF" +--- a/src/ansi-c/library/stdio.c ++++ b/src/ansi-c/library/stdio.c +@@ -1135,7 +1135,7 @@ int vfscanf(FILE *restrict stream, const char *restrict format, va_list arg) -cmake3 -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" + (void)*format; + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&arg) < +- __CPROVER_OBJECT_SIZE(arg)) ++ __CPROVER_OBJECT_SIZE(*(void **)&arg)) + { + void *a = va_arg(arg, void *); + __CPROVER_havoc_object(a); +@@ -1233,7 +1233,7 @@ int __stdio_common_vfscanf( + + (void)*format; + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&args) < +- __CPROVER_OBJECT_SIZE(args)) ++ __CPROVER_OBJECT_SIZE(*(void **)&args)) + { + void *a = va_arg(args, void *); + __CPROVER_havoc_object(a); +@@ -1312,7 +1312,7 @@ __CPROVER_HIDE:; + (void)*s; + (void)*format; + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&arg) < +- __CPROVER_OBJECT_SIZE(arg)) ++ __CPROVER_OBJECT_SIZE(*(void **)&arg)) + { + void *a = va_arg(arg, void *); + __CPROVER_havoc_object(a); +@@ -1388,7 +1388,7 @@ int __stdio_common_vsscanf( + (void)*s; + (void)*format; + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&args) < +- __CPROVER_OBJECT_SIZE(args)) ++ __CPROVER_OBJECT_SIZE(*(void **)&args)) + { + void *a = va_arg(args, void *); + __CPROVER_havoc_object(a); +@@ -1774,12 +1774,12 @@ int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) + (void)*fmt; + + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&ap) < +- __CPROVER_OBJECT_SIZE(ap)) ++ __CPROVER_OBJECT_SIZE(*(void **)&ap)) + + { + (void)va_arg(ap, int); + __CPROVER_precondition( +- __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(ap), ++ __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(*(void **)&ap), + "vsnprintf object overlap"); + } + +@@ -1822,12 +1822,12 @@ int __builtin___vsnprintf_chk( + (void)*fmt; + + while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&ap) < +- __CPROVER_OBJECT_SIZE(ap)) ++ __CPROVER_OBJECT_SIZE(*(void **)&ap)) + + { + (void)va_arg(ap, int); + __CPROVER_precondition( +- __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(ap), ++ __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(*(void **)&ap), + "vsnprintf object overlap"); + } + +EOF + +cmake3 -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" \ + -DCMAKE_C_COMPILER=gcc10-cc -DCMAKE_CXX_COMPILER=gcc10-c++ \ + -DCMAKE_CXX_STANDARD_LIBRARIES=-lstdc++fs \ + -DCMAKE_CXX_FLAGS=-Wno-error=register cmake3 --build build -- -j$(nproc) sudo make -C build install diff --git a/scripts/setup/al2/install_deps.sh b/scripts/setup/al2/install_deps.sh index 0010aa58bab4..ce901ab8afa5 100755 --- a/scripts/setup/al2/install_deps.sh +++ b/scripts/setup/al2/install_deps.sh @@ -11,6 +11,7 @@ set -eu DEPS=( cmake cmake3 + gcc10-c++ git openssl-devel python3-pip From 6553afa4dc854478da1570517f522776ae0601e2 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 9 Aug 2024 11:47:40 -0700 Subject: [PATCH 022/159] Handle intrinsics systematically (#3422) Since compiler intrinsics are handled both in codegen and instrumentation, it would make sense to have a single source of truth with regard to which intrinsics we support and update it systematically. This PR introduces an `Intrinsic` enum, which allows to parse intrinsic name into one of the predefined variants we support or a catch-all `Unsupported` variant. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../codegen/intrinsic.rs | 414 ++++----- kani-compiler/src/intrinsics.rs | 798 ++++++++++++++++++ .../points_to/points_to_analysis.rs | 411 +++++---- .../delayed_ub/initial_target_visitor.rs | 37 +- .../check_uninit/ptr_uninit/uninit_visitor.rs | 378 ++++----- kani-compiler/src/main.rs | 1 + 6 files changed, 1393 insertions(+), 646 deletions(-) create mode 100644 kani-compiler/src/intrinsics.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index c4d5396f1fe8..421d79e943c4 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -5,6 +5,7 @@ use super::typ; use super::{bb_label, PropertyClass}; use crate::codegen_cprover_gotoc::codegen::ty_stable::pointee_type_stable; use crate::codegen_cprover_gotoc::{utils, GotocCtx}; +use crate::intrinsics::Intrinsic; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{ ArithmeticOverflowResult, BinaryOperator, BuiltinFn, Expr, Location, Stmt, Type, @@ -114,7 +115,7 @@ impl<'tcx> GotocCtx<'tcx> { span: Span, ) -> Stmt { let intrinsic_name = instance.intrinsic_name().unwrap(); - let intrinsic = intrinsic_name.as_str(); + let intrinsic_str = intrinsic_name.as_str(); let loc = self.codegen_span_stable(span); debug!(?instance, "codegen_intrinsic"); debug!(?fargs, "codegen_intrinsic"); @@ -163,7 +164,7 @@ impl<'tcx> GotocCtx<'tcx> { let div_overflow_check = self.codegen_assert_assume( div_does_not_overflow, PropertyClass::ArithmeticOverflow, - format!("attempt to compute {} which would overflow", intrinsic).as_str(), + format!("attempt to compute {} which would overflow", intrinsic_str).as_str(), loc, ); let res = a.$f(b); @@ -257,7 +258,7 @@ impl<'tcx> GotocCtx<'tcx> { macro_rules! codegen_atomic_binop { ($op: ident) => {{ let loc = self.codegen_span_stable(span); - self.store_concurrent_construct(intrinsic, loc); + self.store_concurrent_construct(intrinsic_str, loc); let var1_ref = fargs.remove(0); let var1 = var1_ref.dereference(); let (tmp, decl_stmt) = @@ -280,7 +281,7 @@ impl<'tcx> GotocCtx<'tcx> { macro_rules! unstable_codegen { ($($tt:tt)*) => {{ let expr = self.codegen_unimplemented_expr( - &format!("'{}' intrinsic", intrinsic), + &format!("'{}' intrinsic", intrinsic_str), cbmc_ret_ty, loc, "https://github.com/model-checking/kani/issues/new/choose", @@ -289,342 +290,273 @@ impl<'tcx> GotocCtx<'tcx> { }}; } - if let Some(stripped) = intrinsic.strip_prefix("simd_shuffle") { - assert!(fargs.len() == 3, "`simd_shuffle` had unexpected arguments {fargs:?}"); - let n: u64 = self.simd_shuffle_length(stripped, farg_types, span); - return self.codegen_intrinsic_simd_shuffle(fargs, place, farg_types, ret_ty, n, span); - } + let intrinsic = Intrinsic::from_instance(&instance); match intrinsic { - "add_with_overflow" => { + Intrinsic::AddWithOverflow => { self.codegen_op_with_overflow(BinaryOperator::OverflowResultPlus, fargs, place, loc) } - "arith_offset" => self.codegen_offset(intrinsic, instance, fargs, place, loc), - "assert_inhabited" => self.codegen_assert_intrinsic(instance, intrinsic, span), - "assert_mem_uninitialized_valid" => { - self.codegen_assert_intrinsic(instance, intrinsic, span) + Intrinsic::ArithOffset => { + self.codegen_offset(intrinsic_str, instance, fargs, place, loc) + } + Intrinsic::AssertInhabited => { + self.codegen_assert_intrinsic(instance, intrinsic_str, span) + } + Intrinsic::AssertMemUninitializedValid => { + self.codegen_assert_intrinsic(instance, intrinsic_str, span) + } + Intrinsic::AssertZeroValid => { + self.codegen_assert_intrinsic(instance, intrinsic_str, span) } - "assert_zero_valid" => self.codegen_assert_intrinsic(instance, intrinsic, span), // https://doc.rust-lang.org/core/intrinsics/fn.assume.html // Informs the optimizer that a condition is always true. // If the condition is false, the behavior is undefined. - "assume" => self.codegen_assert_assume( + Intrinsic::Assume => self.codegen_assert_assume( fargs.remove(0).cast_to(Type::bool()), PropertyClass::Assume, "assumption failed", loc, ), - "atomic_and_seqcst" => codegen_atomic_binop!(bitand), - "atomic_and_acquire" => codegen_atomic_binop!(bitand), - "atomic_and_acqrel" => codegen_atomic_binop!(bitand), - "atomic_and_release" => codegen_atomic_binop!(bitand), - "atomic_and_relaxed" => codegen_atomic_binop!(bitand), - name if name.starts_with("atomic_cxchg") => { - self.codegen_atomic_cxchg(intrinsic, fargs, place, loc) + Intrinsic::AtomicAnd(_) => codegen_atomic_binop!(bitand), + Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { + self.codegen_atomic_cxchg(intrinsic_str, fargs, place, loc) } - "atomic_fence_seqcst" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_fence_acquire" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_fence_acqrel" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_fence_release" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_load_seqcst" => self.codegen_atomic_load(intrinsic, fargs, place, loc), - "atomic_load_acquire" => self.codegen_atomic_load(intrinsic, fargs, place, loc), - "atomic_load_relaxed" => self.codegen_atomic_load(intrinsic, fargs, place, loc), - "atomic_load_unordered" => self.codegen_atomic_load(intrinsic, fargs, place, loc), - "atomic_max_seqcst" => codegen_atomic_binop!(max), - "atomic_max_acquire" => codegen_atomic_binop!(max), - "atomic_max_acqrel" => codegen_atomic_binop!(max), - "atomic_max_release" => codegen_atomic_binop!(max), - "atomic_max_relaxed" => codegen_atomic_binop!(max), - "atomic_min_seqcst" => codegen_atomic_binop!(min), - "atomic_min_acquire" => codegen_atomic_binop!(min), - "atomic_min_acqrel" => codegen_atomic_binop!(min), - "atomic_min_release" => codegen_atomic_binop!(min), - "atomic_min_relaxed" => codegen_atomic_binop!(min), - "atomic_nand_seqcst" => codegen_atomic_binop!(bitnand), - "atomic_nand_acquire" => codegen_atomic_binop!(bitnand), - "atomic_nand_acqrel" => codegen_atomic_binop!(bitnand), - "atomic_nand_release" => codegen_atomic_binop!(bitnand), - "atomic_nand_relaxed" => codegen_atomic_binop!(bitnand), - "atomic_or_seqcst" => codegen_atomic_binop!(bitor), - "atomic_or_acquire" => codegen_atomic_binop!(bitor), - "atomic_or_acqrel" => codegen_atomic_binop!(bitor), - "atomic_or_release" => codegen_atomic_binop!(bitor), - "atomic_or_relaxed" => codegen_atomic_binop!(bitor), - "atomic_singlethreadfence_seqcst" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_singlethreadfence_acquire" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_singlethreadfence_acqrel" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_singlethreadfence_release" => self.codegen_atomic_noop(intrinsic, loc), - "atomic_store_seqcst" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_store_release" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_store_relaxed" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_store_unordered" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_umax_seqcst" => codegen_atomic_binop!(max), - "atomic_umax_acquire" => codegen_atomic_binop!(max), - "atomic_umax_acqrel" => codegen_atomic_binop!(max), - "atomic_umax_release" => codegen_atomic_binop!(max), - "atomic_umax_relaxed" => codegen_atomic_binop!(max), - "atomic_umin_seqcst" => codegen_atomic_binop!(min), - "atomic_umin_acquire" => codegen_atomic_binop!(min), - "atomic_umin_acqrel" => codegen_atomic_binop!(min), - "atomic_umin_release" => codegen_atomic_binop!(min), - "atomic_umin_relaxed" => codegen_atomic_binop!(min), - "atomic_xadd_seqcst" => codegen_atomic_binop!(plus), - "atomic_xadd_acquire" => codegen_atomic_binop!(plus), - "atomic_xadd_acqrel" => codegen_atomic_binop!(plus), - "atomic_xadd_release" => codegen_atomic_binop!(plus), - "atomic_xadd_relaxed" => codegen_atomic_binop!(plus), - "atomic_xchg_seqcst" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xchg_acquire" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xchg_acqrel" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xchg_release" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xchg_relaxed" => self.codegen_atomic_store(intrinsic, fargs, place, loc), - "atomic_xor_seqcst" => codegen_atomic_binop!(bitxor), - "atomic_xor_acquire" => codegen_atomic_binop!(bitxor), - "atomic_xor_acqrel" => codegen_atomic_binop!(bitxor), - "atomic_xor_release" => codegen_atomic_binop!(bitxor), - "atomic_xor_relaxed" => codegen_atomic_binop!(bitxor), - "atomic_xsub_seqcst" => codegen_atomic_binop!(sub), - "atomic_xsub_acquire" => codegen_atomic_binop!(sub), - "atomic_xsub_acqrel" => codegen_atomic_binop!(sub), - "atomic_xsub_release" => codegen_atomic_binop!(sub), - "atomic_xsub_relaxed" => codegen_atomic_binop!(sub), - "bitreverse" => { + + Intrinsic::AtomicFence(_) => self.codegen_atomic_noop(intrinsic_str, loc), + Intrinsic::AtomicLoad(_) => self.codegen_atomic_load(intrinsic_str, fargs, place, loc), + Intrinsic::AtomicMax(_) => codegen_atomic_binop!(max), + Intrinsic::AtomicMin(_) => codegen_atomic_binop!(min), + Intrinsic::AtomicNand(_) => codegen_atomic_binop!(bitnand), + Intrinsic::AtomicOr(_) => codegen_atomic_binop!(bitor), + Intrinsic::AtomicSingleThreadFence(_) => self.codegen_atomic_noop(intrinsic_str, loc), + Intrinsic::AtomicStore(_) => { + self.codegen_atomic_store(intrinsic_str, fargs, place, loc) + } + Intrinsic::AtomicUmax(_) => codegen_atomic_binop!(max), + Intrinsic::AtomicUmin(_) => codegen_atomic_binop!(min), + Intrinsic::AtomicXadd(_) => codegen_atomic_binop!(plus), + Intrinsic::AtomicXchg(_) => self.codegen_atomic_store(intrinsic_str, fargs, place, loc), + Intrinsic::AtomicXor(_) => codegen_atomic_binop!(bitxor), + Intrinsic::AtomicXsub(_) => codegen_atomic_binop!(sub), + Intrinsic::Bitreverse => { self.codegen_expr_to_place_stable(place, fargs.remove(0).bitreverse(), loc) } // black_box is an identity function that hints to the compiler // to be maximally pessimistic to limit optimizations - "black_box" => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), - "breakpoint" => Stmt::skip(loc), - "bswap" => self.codegen_expr_to_place_stable(place, fargs.remove(0).bswap(), loc), - "caller_location" => self.codegen_unimplemented_stmt( - intrinsic, - loc, - "https://github.com/model-checking/kani/issues/374", - ), - "catch_unwind" => self.codegen_unimplemented_stmt( - intrinsic, - loc, - "https://github.com/model-checking/kani/issues/267", - ), - "ceilf32" => codegen_simple_intrinsic!(Ceilf), - "ceilf64" => codegen_simple_intrinsic!(Ceil), - "compare_bytes" => self.codegen_compare_bytes(fargs, place, loc), - "copy" => self.codegen_copy(intrinsic, false, fargs, farg_types, Some(place), loc), - "copy_nonoverlapping" => unreachable!( - "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" - ), - "copysignf32" => codegen_simple_intrinsic!(Copysignf), - "copysignf64" => codegen_simple_intrinsic!(Copysign), - "cosf32" => codegen_simple_intrinsic!(Cosf), - "cosf64" => codegen_simple_intrinsic!(Cos), - "ctlz" => codegen_count_intrinsic!(ctlz, true), - "ctlz_nonzero" => codegen_count_intrinsic!(ctlz, false), - "ctpop" => self.codegen_ctpop(place, span, fargs.remove(0), farg_types[0]), - "cttz" => codegen_count_intrinsic!(cttz, true), - "cttz_nonzero" => codegen_count_intrinsic!(cttz, false), - "discriminant_value" => { + Intrinsic::BlackBox => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), + Intrinsic::Breakpoint => Stmt::skip(loc), + Intrinsic::Bswap => { + self.codegen_expr_to_place_stable(place, fargs.remove(0).bswap(), loc) + } + Intrinsic::CeilF32 => codegen_simple_intrinsic!(Ceilf), + Intrinsic::CeilF64 => codegen_simple_intrinsic!(Ceil), + Intrinsic::CompareBytes => self.codegen_compare_bytes(fargs, place, loc), + Intrinsic::Copy => { + self.codegen_copy(intrinsic_str, false, fargs, farg_types, Some(place), loc) + } + Intrinsic::CopySignF32 => codegen_simple_intrinsic!(Copysignf), + Intrinsic::CopySignF64 => codegen_simple_intrinsic!(Copysign), + Intrinsic::CosF32 => codegen_simple_intrinsic!(Cosf), + Intrinsic::CosF64 => codegen_simple_intrinsic!(Cos), + Intrinsic::Ctlz => codegen_count_intrinsic!(ctlz, true), + Intrinsic::CtlzNonZero => codegen_count_intrinsic!(ctlz, false), + Intrinsic::Ctpop => self.codegen_ctpop(place, span, fargs.remove(0), farg_types[0]), + Intrinsic::Cttz => codegen_count_intrinsic!(cttz, true), + Intrinsic::CttzNonZero => codegen_count_intrinsic!(cttz, false), + Intrinsic::DiscriminantValue => { let sig = instance.ty().kind().fn_sig().unwrap().skip_binder(); let ty = pointee_type_stable(sig.inputs()[0]).unwrap(); let e = self.codegen_get_discriminant(fargs.remove(0).dereference(), ty, ret_ty); self.codegen_expr_to_place_stable(place, e, loc) } - "exact_div" => self.codegen_exact_div(fargs, place, loc), - "exp2f32" => codegen_simple_intrinsic!(Exp2f), - "exp2f64" => codegen_simple_intrinsic!(Exp2), - "expf32" => codegen_simple_intrinsic!(Expf), - "expf64" => codegen_simple_intrinsic!(Exp), - "fabsf32" => codegen_simple_intrinsic!(Fabsf), - "fabsf64" => codegen_simple_intrinsic!(Fabs), - "fadd_fast" => { + Intrinsic::ExactDiv => self.codegen_exact_div(fargs, place, loc), + Intrinsic::Exp2F32 => codegen_simple_intrinsic!(Exp2f), + Intrinsic::Exp2F64 => codegen_simple_intrinsic!(Exp2), + Intrinsic::ExpF32 => codegen_simple_intrinsic!(Expf), + Intrinsic::ExpF64 => codegen_simple_intrinsic!(Exp), + Intrinsic::FabsF32 => codegen_simple_intrinsic!(Fabsf), + Intrinsic::FabsF64 => codegen_simple_intrinsic!(Fabs), + Intrinsic::FaddFast => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(plus); - self.add_finite_args_checks(intrinsic, fargs_clone, binop_stmt, span) + self.add_finite_args_checks(intrinsic_str, fargs_clone, binop_stmt, span) } - "fdiv_fast" => { + Intrinsic::FdivFast => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(div); - self.add_finite_args_checks(intrinsic, fargs_clone, binop_stmt, span) + self.add_finite_args_checks(intrinsic_str, fargs_clone, binop_stmt, span) } - "floorf32" => codegen_simple_intrinsic!(Floorf), - "floorf64" => codegen_simple_intrinsic!(Floor), - "fmaf32" => codegen_simple_intrinsic!(Fmaf), - "fmaf64" => codegen_simple_intrinsic!(Fma), - "fmul_fast" => { + Intrinsic::FloorF32 => codegen_simple_intrinsic!(Floorf), + Intrinsic::FloorF64 => codegen_simple_intrinsic!(Floor), + Intrinsic::FmafF32 => codegen_simple_intrinsic!(Fmaf), + Intrinsic::FmafF64 => codegen_simple_intrinsic!(Fma), + Intrinsic::FmulFast => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(mul); - self.add_finite_args_checks(intrinsic, fargs_clone, binop_stmt, span) + self.add_finite_args_checks(intrinsic_str, fargs_clone, binop_stmt, span) } - "forget" => Stmt::skip(loc), - "fsub_fast" => { + Intrinsic::Forget => Stmt::skip(loc), + Intrinsic::FsubFast => { let fargs_clone = fargs.clone(); let binop_stmt = codegen_intrinsic_binop!(sub); - self.add_finite_args_checks(intrinsic, fargs_clone, binop_stmt, span) + self.add_finite_args_checks(intrinsic_str, fargs_clone, binop_stmt, span) } - "is_val_statically_known" => { + Intrinsic::IsValStaticallyKnown => { // Returning false is sound according do this intrinsic's documentation: // https://doc.rust-lang.org/nightly/std/intrinsics/fn.is_val_statically_known.html self.codegen_expr_to_place_stable(place, Expr::c_false(), loc) } - "likely" => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), - "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), - "maxnumf64" => codegen_simple_intrinsic!(Fmax), - "min_align_of" => codegen_intrinsic_const!(), - "min_align_of_val" => codegen_size_align!(align), - "minnumf32" => codegen_simple_intrinsic!(Fminf), - "minnumf64" => codegen_simple_intrinsic!(Fmin), - "mul_with_overflow" => { + Intrinsic::Likely => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), + Intrinsic::Log10F32 => codegen_simple_intrinsic!(Log10f), + Intrinsic::Log10F64 => codegen_simple_intrinsic!(Log10), + Intrinsic::Log2F32 => codegen_simple_intrinsic!(Log2f), + Intrinsic::Log2F64 => codegen_simple_intrinsic!(Log2), + Intrinsic::LogF32 => codegen_simple_intrinsic!(Logf), + Intrinsic::LogF64 => codegen_simple_intrinsic!(Log), + Intrinsic::MaxNumF32 => codegen_simple_intrinsic!(Fmaxf), + Intrinsic::MaxNumF64 => codegen_simple_intrinsic!(Fmax), + Intrinsic::MinAlignOf => codegen_intrinsic_const!(), + Intrinsic::MinAlignOfVal => codegen_size_align!(align), + Intrinsic::MinNumF32 => codegen_simple_intrinsic!(Fminf), + Intrinsic::MinNumF64 => codegen_simple_intrinsic!(Fmin), + Intrinsic::MulWithOverflow => { self.codegen_op_with_overflow(BinaryOperator::OverflowResultMult, fargs, place, loc) } - "nearbyintf32" => codegen_simple_intrinsic!(Nearbyintf), - "nearbyintf64" => codegen_simple_intrinsic!(Nearbyint), - "needs_drop" => codegen_intrinsic_const!(), - // As of https://github.com/rust-lang/rust/pull/110822 the `offset` intrinsic is lowered to `mir::BinOp::Offset` - "offset" => unreachable!( - "Expected `core::intrinsics::unreachable` to be handled by `BinOp::OffSet`" - ), - "powf32" => codegen_simple_intrinsic!(Powf), - "powf64" => codegen_simple_intrinsic!(Pow), - "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), - "ptr_offset_from_unsigned" => self.codegen_ptr_offset_from_unsigned(fargs, place, loc), - "raw_eq" => self.codegen_intrinsic_raw_eq(instance, fargs, place, loc), - "retag_box_to_raw" => self.codegen_retag_box_to_raw(fargs, place, loc), - "rintf32" => codegen_simple_intrinsic!(Rintf), - "rintf64" => codegen_simple_intrinsic!(Rint), - "rotate_left" => codegen_intrinsic_binop!(rol), - "rotate_right" => codegen_intrinsic_binop!(ror), - "roundf32" => codegen_simple_intrinsic!(Roundf), - "roundf64" => codegen_simple_intrinsic!(Round), - "saturating_add" => codegen_intrinsic_binop_with_mm!(saturating_add), - "saturating_sub" => codegen_intrinsic_binop_with_mm!(saturating_sub), - "sinf32" => codegen_simple_intrinsic!(Sinf), - "sinf64" => codegen_simple_intrinsic!(Sin), - "simd_add" => self.codegen_simd_op_with_overflow( + Intrinsic::NearbyIntF32 => codegen_simple_intrinsic!(Nearbyintf), + Intrinsic::NearbyIntF64 => codegen_simple_intrinsic!(Nearbyint), + Intrinsic::NeedsDrop => codegen_intrinsic_const!(), + Intrinsic::PowF32 => codegen_simple_intrinsic!(Powf), + Intrinsic::PowF64 => codegen_simple_intrinsic!(Pow), + Intrinsic::PowIF32 => codegen_simple_intrinsic!(Powif), + Intrinsic::PowIF64 => codegen_simple_intrinsic!(Powi), + Intrinsic::PrefAlignOf => codegen_intrinsic_const!(), + Intrinsic::PtrGuaranteedCmp => self.codegen_ptr_guaranteed_cmp(fargs, place, loc), + Intrinsic::PtrOffsetFrom => self.codegen_ptr_offset_from(fargs, place, loc), + Intrinsic::PtrOffsetFromUnsigned => { + self.codegen_ptr_offset_from_unsigned(fargs, place, loc) + } + Intrinsic::RawEq => self.codegen_intrinsic_raw_eq(instance, fargs, place, loc), + Intrinsic::RetagBoxToRaw => self.codegen_retag_box_to_raw(fargs, place, loc), + Intrinsic::RintF32 => codegen_simple_intrinsic!(Rintf), + Intrinsic::RintF64 => codegen_simple_intrinsic!(Rint), + Intrinsic::RotateLeft => codegen_intrinsic_binop!(rol), + Intrinsic::RotateRight => codegen_intrinsic_binop!(ror), + Intrinsic::RoundF32 => codegen_simple_intrinsic!(Roundf), + Intrinsic::RoundF64 => codegen_simple_intrinsic!(Round), + Intrinsic::SaturatingAdd => codegen_intrinsic_binop_with_mm!(saturating_add), + Intrinsic::SaturatingSub => codegen_intrinsic_binop_with_mm!(saturating_sub), + Intrinsic::SinF32 => codegen_simple_intrinsic!(Sinf), + Intrinsic::SinF64 => codegen_simple_intrinsic!(Sin), + Intrinsic::SimdAdd => self.codegen_simd_op_with_overflow( Expr::plus, Expr::add_overflow_p, fargs, - intrinsic, + intrinsic_str, place, loc, ), - "simd_and" => codegen_intrinsic_binop!(bitand), + Intrinsic::SimdAnd => codegen_intrinsic_binop!(bitand), // TODO: `simd_rem` doesn't check for overflow cases for floating point operands. // - "simd_div" | "simd_rem" => { - self.codegen_simd_div_with_overflow(fargs, intrinsic, place, loc) + Intrinsic::SimdDiv | Intrinsic::SimdRem => { + self.codegen_simd_div_with_overflow(fargs, intrinsic_str, place, loc) } - "simd_eq" => { + Intrinsic::SimdEq => { self.codegen_simd_cmp(Expr::vector_eq, fargs, place, span, farg_types, ret_ty) } - "simd_extract" => { + Intrinsic::SimdExtract => { self.codegen_intrinsic_simd_extract(fargs, place, farg_types, ret_ty, span) } - "simd_ge" => { + Intrinsic::SimdGe => { self.codegen_simd_cmp(Expr::vector_ge, fargs, place, span, farg_types, ret_ty) } - "simd_gt" => { + Intrinsic::SimdGt => { self.codegen_simd_cmp(Expr::vector_gt, fargs, place, span, farg_types, ret_ty) } - "simd_insert" => { + Intrinsic::SimdInsert => { self.codegen_intrinsic_simd_insert(fargs, place, cbmc_ret_ty, farg_types, span, loc) } - "simd_le" => { + Intrinsic::SimdLe => { self.codegen_simd_cmp(Expr::vector_le, fargs, place, span, farg_types, ret_ty) } - "simd_lt" => { + Intrinsic::SimdLt => { self.codegen_simd_cmp(Expr::vector_lt, fargs, place, span, farg_types, ret_ty) } - "simd_mul" => self.codegen_simd_op_with_overflow( + Intrinsic::SimdMul => self.codegen_simd_op_with_overflow( Expr::mul, Expr::mul_overflow_p, fargs, - intrinsic, + intrinsic_str, place, loc, ), - "simd_ne" => { + Intrinsic::SimdNe => { self.codegen_simd_cmp(Expr::vector_neq, fargs, place, span, farg_types, ret_ty) } - "simd_or" => codegen_intrinsic_binop!(bitor), - "simd_shl" | "simd_shr" => { - self.codegen_simd_shift_with_distance_check(fargs, intrinsic, place, loc) + Intrinsic::SimdOr => codegen_intrinsic_binop!(bitor), + Intrinsic::SimdShl | Intrinsic::SimdShr => { + self.codegen_simd_shift_with_distance_check(fargs, intrinsic_str, place, loc) + } + Intrinsic::SimdShuffle(stripped) => { + let n: u64 = self.simd_shuffle_length(stripped.as_str(), farg_types, span); + self.codegen_intrinsic_simd_shuffle(fargs, place, farg_types, ret_ty, n, span) } - // "simd_shuffle#" => handled in an `if` preceding this match - "simd_sub" => self.codegen_simd_op_with_overflow( + Intrinsic::SimdSub => self.codegen_simd_op_with_overflow( Expr::sub, Expr::sub_overflow_p, fargs, - intrinsic, + intrinsic_str, place, loc, ), - "simd_xor" => codegen_intrinsic_binop!(bitxor), - "size_of" => unreachable!(), - "size_of_val" => codegen_size_align!(size), - "sqrtf32" => codegen_simple_intrinsic!(Sqrtf), - "sqrtf64" => codegen_simple_intrinsic!(Sqrt), - "sub_with_overflow" => self.codegen_op_with_overflow( + Intrinsic::SimdXor => codegen_intrinsic_binop!(bitxor), + Intrinsic::SizeOfVal => codegen_size_align!(size), + Intrinsic::SqrtF32 => codegen_simple_intrinsic!(Sqrtf), + Intrinsic::SqrtF64 => codegen_simple_intrinsic!(Sqrt), + Intrinsic::SubWithOverflow => self.codegen_op_with_overflow( BinaryOperator::OverflowResultMinus, fargs, place, loc, ), - "transmute" => self.codegen_intrinsic_transmute(fargs, ret_ty, place, loc), - "truncf32" => codegen_simple_intrinsic!(Truncf), - "truncf64" => codegen_simple_intrinsic!(Trunc), - "type_id" => codegen_intrinsic_const!(), - "type_name" => codegen_intrinsic_const!(), - "typed_swap" => self.codegen_swap(fargs, farg_types, loc), - "unaligned_volatile_load" => { + Intrinsic::Transmute => self.codegen_intrinsic_transmute(fargs, ret_ty, place, loc), + Intrinsic::TruncF32 => codegen_simple_intrinsic!(Truncf), + Intrinsic::TruncF64 => codegen_simple_intrinsic!(Trunc), + Intrinsic::TypeId => codegen_intrinsic_const!(), + Intrinsic::TypeName => codegen_intrinsic_const!(), + Intrinsic::TypedSwap => self.codegen_swap(fargs, farg_types, loc), + Intrinsic::UnalignedVolatileLoad => { unstable_codegen!(self.codegen_expr_to_place_stable( place, fargs.remove(0).dereference(), loc )) } - "unchecked_add" | "unchecked_mul" | "unchecked_shl" | "unchecked_shr" - | "unchecked_sub" => { - unreachable!("Expected intrinsic `{intrinsic}` to be lowered before codegen") - } - "unchecked_div" => codegen_op_with_div_overflow_check!(div), - "unchecked_rem" => codegen_op_with_div_overflow_check!(rem), - "unlikely" => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), - "unreachable" => unreachable!( - "Expected `std::intrinsics::unreachable` to be handled by `TerminatorKind::Unreachable`" - ), - "volatile_copy_memory" => unstable_codegen!(codegen_intrinsic_copy!(Memmove)), - "volatile_copy_nonoverlapping_memory" => { + Intrinsic::UncheckedDiv => codegen_op_with_div_overflow_check!(div), + Intrinsic::UncheckedRem => codegen_op_with_div_overflow_check!(rem), + Intrinsic::Unlikely => self.codegen_expr_to_place_stable(place, fargs.remove(0), loc), + Intrinsic::VolatileCopyMemory => unstable_codegen!(codegen_intrinsic_copy!(Memmove)), + Intrinsic::VolatileCopyNonOverlappingMemory => { unstable_codegen!(codegen_intrinsic_copy!(Memcpy)) } - "volatile_load" => self.codegen_volatile_load(fargs, farg_types, place, loc), - "volatile_store" => { + Intrinsic::VolatileLoad => self.codegen_volatile_load(fargs, farg_types, place, loc), + Intrinsic::VolatileStore => { assert!(self.place_ty_stable(place).kind().is_unit()); self.codegen_volatile_store(fargs, farg_types, loc) } - "vtable_size" => self.vtable_info(VTableInfo::Size, fargs, place, loc), - "vtable_align" => self.vtable_info(VTableInfo::Align, fargs, place, loc), - "wrapping_add" => codegen_wrapping_op!(plus), - "wrapping_mul" => codegen_wrapping_op!(mul), - "wrapping_sub" => codegen_wrapping_op!(sub), - "write_bytes" => { + Intrinsic::VtableSize => self.vtable_info(VTableInfo::Size, fargs, place, loc), + Intrinsic::VtableAlign => self.vtable_info(VTableInfo::Align, fargs, place, loc), + Intrinsic::WrappingAdd => codegen_wrapping_op!(plus), + Intrinsic::WrappingMul => codegen_wrapping_op!(mul), + Intrinsic::WrappingSub => codegen_wrapping_op!(sub), + Intrinsic::WriteBytes => { assert!(self.place_ty_stable(place).kind().is_unit()); self.codegen_write_bytes(fargs, farg_types, loc) } // Unimplemented - _ => self.codegen_unimplemented_stmt( - intrinsic, - loc, - "https://github.com/model-checking/kani/issues/new/choose", - ), + Intrinsic::Unimplemented { name, issue_link } => { + self.codegen_unimplemented_stmt(&name, loc, &issue_link) + } } } diff --git a/kani-compiler/src/intrinsics.rs b/kani-compiler/src/intrinsics.rs new file mode 100644 index 000000000000..9485d8525410 --- /dev/null +++ b/kani-compiler/src/intrinsics.rs @@ -0,0 +1,798 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Single source of truth about which intrinsics we support. + +use stable_mir::{ + mir::{mono::Instance, Mutability}, + ty::{FloatTy, IntTy, RigidTy, TyKind, UintTy}, +}; + +// Enumeration of all intrinsics we support right now, with the last option being a catch-all. This +// way, adding an intrinsic would highlight all places where they are used. +#[allow(unused)] +#[derive(Clone, Debug)] +pub enum Intrinsic { + AddWithOverflow, + ArithOffset, + AssertInhabited, + AssertMemUninitializedValid, + AssertZeroValid, + Assume, + AtomicAnd(String), + AtomicCxchg(String), + AtomicCxchgWeak(String), + AtomicFence(String), + AtomicLoad(String), + AtomicMax(String), + AtomicMin(String), + AtomicNand(String), + AtomicOr(String), + AtomicSingleThreadFence(String), + AtomicStore(String), + AtomicUmax(String), + AtomicUmin(String), + AtomicXadd(String), + AtomicXchg(String), + AtomicXor(String), + AtomicXsub(String), + Bitreverse, + BlackBox, + Breakpoint, + Bswap, + CeilF32, + CeilF64, + CompareBytes, + Copy, + CopySignF32, + CopySignF64, + CosF32, + CosF64, + Ctlz, + CtlzNonZero, + Ctpop, + Cttz, + CttzNonZero, + DiscriminantValue, + ExactDiv, + Exp2F32, + Exp2F64, + ExpF32, + ExpF64, + FabsF32, + FabsF64, + FaddFast, + FdivFast, + FloorF32, + FloorF64, + FmafF32, + FmafF64, + FmulFast, + Forget, + FsubFast, + IsValStaticallyKnown, + Likely, + Log10F32, + Log10F64, + Log2F32, + Log2F64, + LogF32, + LogF64, + MaxNumF32, + MaxNumF64, + MinAlignOf, + MinAlignOfVal, + MinNumF32, + MinNumF64, + MulWithOverflow, + NearbyIntF32, + NearbyIntF64, + NeedsDrop, + PowF32, + PowF64, + PowIF32, + PowIF64, + PrefAlignOf, + PtrGuaranteedCmp, + PtrOffsetFrom, + PtrOffsetFromUnsigned, + RawEq, + RetagBoxToRaw, + RintF32, + RintF64, + RotateLeft, + RotateRight, + RoundF32, + RoundF64, + SaturatingAdd, + SaturatingSub, + SinF32, + SinF64, + SimdAdd, + SimdAnd, + SimdDiv, + SimdRem, + SimdEq, + SimdExtract, + SimdGe, + SimdGt, + SimdInsert, + SimdLe, + SimdLt, + SimdMul, + SimdNe, + SimdOr, + SimdShl, + SimdShr, + SimdShuffle(String), + SimdSub, + SimdXor, + SizeOfVal, + SqrtF32, + SqrtF64, + SubWithOverflow, + Transmute, + TruncF32, + TruncF64, + TypeId, + TypeName, + TypedSwap, + UnalignedVolatileLoad, + UncheckedDiv, + UncheckedRem, + Unlikely, + VolatileCopyMemory, + VolatileCopyNonOverlappingMemory, + VolatileLoad, + VolatileStore, + VtableSize, + VtableAlign, + WrappingAdd, + WrappingMul, + WrappingSub, + WriteBytes, + Unimplemented { name: String, issue_link: String }, +} + +/// Assert that top-level types of a function signature match the given patterns. +macro_rules! assert_sig_matches { + ($sig:expr, $($input_type:pat),* => $output_type:pat) => { + let inputs = $sig.inputs(); + let output = $sig.output(); + #[allow(unused_mut)] + let mut index = 0; + $( + #[allow(unused_assignments)] + { + assert!(matches!(inputs[index].kind(), TyKind::RigidTy($input_type))); + index += 1; + } + )* + assert!(inputs.len() == index); + assert!(matches!(output.kind(), TyKind::RigidTy($output_type))); + } +} + +impl Intrinsic { + /// Create an intrinsic enum from a given intrinsic instance, shallowly validating the argument types. + pub fn from_instance(intrinsic_instance: &Instance) -> Self { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + match intrinsic_str.as_str() { + "add_with_overflow" => { + assert_sig_matches!(sig, _, _ => RigidTy::Tuple(_)); + Self::AddWithOverflow + } + "arith_offset" => { + assert_sig_matches!(sig, + RigidTy::RawPtr(_, Mutability::Not), + RigidTy::Int(IntTy::Isize) + => RigidTy::RawPtr(_, Mutability::Not)); + Self::ArithOffset + } + "assert_inhabited" => { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Self::AssertInhabited + } + "assert_mem_uninitialized_valid" => { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Self::AssertMemUninitializedValid + } + "assert_zero_valid" => { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Self::AssertZeroValid + } + "assume" => { + assert_sig_matches!(sig, RigidTy::Bool => RigidTy::Tuple(_)); + Self::Assume + } + "bitreverse" => { + assert_sig_matches!(sig, _ => _); + Self::Bitreverse + } + "black_box" => { + assert_sig_matches!(sig, _ => _); + Self::BlackBox + } + "breakpoint" => { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Self::Breakpoint + } + "bswap" => { + assert_sig_matches!(sig, _ => _); + Self::Bswap + } + "caller_location" => { + assert_sig_matches!(sig, => RigidTy::Ref(_, _, Mutability::Not)); + Self::Unimplemented { + name: intrinsic_str, + issue_link: "https://github.com/model-checking/kani/issues/374".into(), + } + } + "catch_unwind" => { + assert_sig_matches!(sig, RigidTy::FnPtr(_), RigidTy::RawPtr(_, Mutability::Mut), RigidTy::FnPtr(_) => RigidTy::Int(IntTy::I32)); + Self::Unimplemented { + name: intrinsic_str, + issue_link: "https://github.com/model-checking/kani/issues/267".into(), + } + } + "compare_bytes" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Not), RigidTy::Uint(UintTy::Usize) => RigidTy::Int(IntTy::I32)); + Self::CompareBytes + } + "copy" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Mut), RigidTy::Uint(UintTy::Usize) => RigidTy::Tuple(_)); + Self::Copy + } + "copy_nonoverlapping" => unreachable!( + "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" + ), + "ctlz" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::Ctlz + } + "ctlz_nonzero" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::CtlzNonZero + } + "ctpop" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::Ctpop + } + "cttz" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::Cttz + } + "cttz_nonzero" => { + assert_sig_matches!(sig, _ => RigidTy::Uint(UintTy::U32)); + Self::CttzNonZero + } + "discriminant_value" => { + assert_sig_matches!(sig, RigidTy::Ref(_, _, Mutability::Not) => _); + Self::DiscriminantValue + } + "exact_div" => { + assert_sig_matches!(sig, _, _ => _); + Self::ExactDiv + } + "fadd_fast" => { + assert_sig_matches!(sig, _, _ => _); + Self::FaddFast + } + "fdiv_fast" => { + assert_sig_matches!(sig, _, _ => _); + Self::FdivFast + } + "fmul_fast" => { + assert_sig_matches!(sig, _, _ => _); + Self::FmulFast + } + "forget" => { + assert_sig_matches!(sig, _ => RigidTy::Tuple(_)); + Self::Forget + } + "fsub_fast" => { + assert_sig_matches!(sig, _, _ => _); + Self::FsubFast + } + "is_val_statically_known" => { + assert_sig_matches!(sig, _ => RigidTy::Bool); + Self::IsValStaticallyKnown + } + "likely" => { + assert_sig_matches!(sig, RigidTy::Bool => RigidTy::Bool); + Self::Likely + } + "min_align_of" => { + assert_sig_matches!(sig, => RigidTy::Uint(UintTy::Usize)); + Self::MinAlignOf + } + "min_align_of_val" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::MinAlignOfVal + } + "mul_with_overflow" => { + assert_sig_matches!(sig, _, _ => RigidTy::Tuple(_)); + Self::MulWithOverflow + } + "needs_drop" => { + assert_sig_matches!(sig, => RigidTy::Bool); + Self::NeedsDrop + } + // As of https://github.com/rust-lang/rust/pull/110822 the `offset` intrinsic is lowered to `mir::BinOp::Offset` + "offset" => unreachable!( + "Expected `core::intrinsics::unreachable` to be handled by `BinOp::OffSet`" + ), + "pref_align_of" => { + assert_sig_matches!(sig, => RigidTy::Uint(UintTy::Usize)); + Self::PrefAlignOf + } + "ptr_guaranteed_cmp" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::U8)); + Self::PtrGuaranteedCmp + } + "ptr_offset_from" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Int(IntTy::Isize)); + Self::PtrOffsetFrom + } + "ptr_offset_from_unsigned" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not), RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::PtrOffsetFromUnsigned + } + "raw_eq" => { + assert_sig_matches!(sig, RigidTy::Ref(_, _, Mutability::Not), RigidTy::Ref(_, _, Mutability::Not) => RigidTy::Bool); + Self::RawEq + } + "rotate_left" => { + assert_sig_matches!(sig, _, RigidTy::Uint(UintTy::U32) => _); + Self::RotateLeft + } + "rotate_right" => { + assert_sig_matches!(sig, _, RigidTy::Uint(UintTy::U32) => _); + Self::RotateRight + } + "saturating_add" => { + assert_sig_matches!(sig, _, _ => _); + Self::SaturatingAdd + } + "saturating_sub" => { + assert_sig_matches!(sig, _, _ => _); + Self::SaturatingSub + } + "size_of" => unreachable!(), + "size_of_val" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::SizeOfVal + } + "sub_with_overflow" => { + assert_sig_matches!(sig, _, _ => RigidTy::Tuple(_)); + Self::SubWithOverflow + } + "transmute" => { + assert_sig_matches!(sig, _ => _); + Self::Transmute + } + "type_id" => { + assert_sig_matches!(sig, => RigidTy::Uint(UintTy::U128)); + Self::TypeId + } + "type_name" => { + assert_sig_matches!(sig, => RigidTy::Ref(_, _, Mutability::Not)); + Self::TypeName + } + "typed_swap" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), RigidTy::RawPtr(_, Mutability::Mut) => RigidTy::Tuple(_)); + Self::TypedSwap + } + "unaligned_volatile_load" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => _); + Self::UnalignedVolatileLoad + } + "unchecked_add" | "unchecked_mul" | "unchecked_shl" | "unchecked_shr" + | "unchecked_sub" => { + unreachable!("Expected intrinsic `{intrinsic_str}` to be lowered before codegen") + } + "unchecked_div" => { + assert_sig_matches!(sig, _, _ => _); + Self::UncheckedDiv + } + "unchecked_rem" => { + assert_sig_matches!(sig, _, _ => _); + Self::UncheckedRem + } + "unlikely" => { + assert_sig_matches!(sig, RigidTy::Bool => RigidTy::Bool); + Self::Unlikely + } + "unreachable" => unreachable!( + "Expected `std::intrinsics::unreachable` to be handled by `TerminatorKind::Unreachable`" + ), + "volatile_copy_memory" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), RigidTy::RawPtr(_, Mutability::Not), RigidTy::Uint(UintTy::Usize) => RigidTy::Tuple(_)); + Self::VolatileCopyMemory + } + "volatile_copy_nonoverlapping_memory" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), RigidTy::RawPtr(_, Mutability::Not), RigidTy::Uint(UintTy::Usize) => RigidTy::Tuple(_)); + Self::VolatileCopyNonOverlappingMemory + } + "volatile_load" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => _); + Self::VolatileLoad + } + "volatile_store" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => RigidTy::Tuple(_)); + Self::VolatileStore + } + "vtable_size" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::VtableSize + } + "vtable_align" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => RigidTy::Uint(UintTy::Usize)); + Self::VtableAlign + } + "wrapping_add" => { + assert_sig_matches!(sig, _, _ => _); + Self::WrappingAdd + } + "wrapping_mul" => { + assert_sig_matches!(sig, _, _ => _); + Self::WrappingMul + } + "wrapping_sub" => { + assert_sig_matches!(sig, _, _ => _); + Self::WrappingSub + } + "write_bytes" => { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), RigidTy::Uint(UintTy::U8), RigidTy::Uint(UintTy::Usize) => RigidTy::Tuple(_)); + Self::WriteBytes + } + _ => try_match_atomic(intrinsic_instance) + .or_else(|| try_match_simd(intrinsic_instance)) + .or_else(|| try_match_f32(intrinsic_instance)) + .or_else(|| try_match_f64(intrinsic_instance)) + .unwrap_or(Self::Unimplemented { + name: intrinsic_str, + issue_link: "https://github.com/model-checking/kani/issues/new/choose".into(), + }), + } + } +} + +/// Match atomic intrinsics by instance, returning an instance of the intrinsics enum if the match +/// is successful. +fn try_match_atomic(intrinsic_instance: &Instance) -> Option { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + if let Some(suffix) = intrinsic_str.strip_prefix("atomic_and_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicAnd(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_cxchgweak_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _, _ => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicCxchgWeak(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_cxchg_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _, _ => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicCxchg(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_fence_") { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicFence(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_load_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Not) => _); + Some(Intrinsic::AtomicLoad(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_max_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicMax(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_min_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicMin(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_nand_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicNand(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_or_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicOr(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_singlethreadfence_") { + assert_sig_matches!(sig, => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicSingleThreadFence(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_store_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => RigidTy::Tuple(_)); + Some(Intrinsic::AtomicStore(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_umax_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicUmax(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_umin_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicUmin(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_xadd_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicXadd(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_xchg_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicXchg(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_xor_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicXor(suffix.into())) + } else if let Some(suffix) = intrinsic_str.strip_prefix("atomic_xsub_") { + assert_sig_matches!(sig, RigidTy::RawPtr(_, Mutability::Mut), _ => _); + Some(Intrinsic::AtomicXsub(suffix.into())) + } else { + None + } +} + +/// Match SIMD intrinsics by instance, returning an instance of the intrinsics enum if the match +/// is successful. +fn try_match_simd(intrinsic_instance: &Instance) -> Option { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + match intrinsic_str.as_str() { + "simd_add" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdAdd) + } + "simd_and" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdAnd) + } + "simd_div" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdDiv) + } + "simd_rem" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdRem) + } + "simd_eq" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdEq) + } + "simd_extract" => { + assert_sig_matches!(sig, _, RigidTy::Uint(UintTy::U32) => _); + Some(Intrinsic::SimdExtract) + } + "simd_ge" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdGe) + } + "simd_gt" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdGt) + } + "simd_insert" => { + assert_sig_matches!(sig, _, RigidTy::Uint(UintTy::U32), _ => _); + Some(Intrinsic::SimdInsert) + } + "simd_le" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdLe) + } + "simd_lt" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdLt) + } + "simd_mul" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdMul) + } + "simd_ne" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdNe) + } + "simd_or" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdOr) + } + "simd_shl" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdShl) + } + "simd_shr" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdShr) + } + "simd_sub" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdSub) + } + "simd_xor" => { + assert_sig_matches!(sig, _, _ => _); + Some(Intrinsic::SimdXor) + } + name => { + if let Some(suffix) = name.strip_prefix("simd_shuffle") { + assert_sig_matches!(sig, _, _, _ => _); + Some(Intrinsic::SimdShuffle(suffix.into())) + } else { + None + } + } + } +} + +/// Match f32 arithmetic intrinsics by instance, returning an instance of the intrinsics enum if the match +/// is successful. +fn try_match_f32(intrinsic_instance: &Instance) -> Option { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + match intrinsic_str.as_str() { + "ceilf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::CeilF32) + } + "copysignf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::CopySignF32) + } + "cosf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::CosF32) + } + "exp2f32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::Exp2F32) + } + "expf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::ExpF32) + } + "fabsf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::FabsF32) + } + "floorf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::FloorF32) + } + "fmaf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::FmafF32) + } + "log10f32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::Log10F32) + } + "log2f32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::Log2F32) + } + "logf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::LogF32) + } + "maxnumf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::MaxNumF32) + } + "minnumf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::MinNumF32) + } + "nearbyintf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::NearbyIntF32) + } + "powf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::PowF32) + } + "powif32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32), RigidTy::Int(IntTy::I32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::PowIF32) + } + "rintf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::RintF32) + } + "roundf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::RoundF32) + } + "sinf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::SinF32) + } + "sqrtf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::SqrtF32) + } + "truncf32" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F32) => RigidTy::Float(FloatTy::F32)); + Some(Intrinsic::TruncF32) + } + _ => None, + } +} + +/// Match f64 arithmetic intrinsics by instance, returning an instance of the intrinsics enum if the match +/// is successful. +fn try_match_f64(intrinsic_instance: &Instance) -> Option { + let intrinsic_str = intrinsic_instance.intrinsic_name().unwrap(); + let sig = intrinsic_instance.ty().kind().fn_sig().unwrap().skip_binder(); + match intrinsic_str.as_str() { + "ceilf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::CeilF64) + } + "copysignf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::CopySignF64) + } + "cosf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::CosF64) + } + "exp2f64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::Exp2F64) + } + "expf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::ExpF64) + } + "fabsf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::FabsF64) + } + "floorf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::FloorF64) + } + "fmaf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::FmafF64) + } + "log10f64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::Log10F64) + } + "log2f64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::Log2F64) + } + "logf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::LogF64) + } + "maxnumf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::MaxNumF64) + } + "minnumf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::MinNumF64) + } + "nearbyintf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::NearbyIntF64) + } + "powf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::PowF64) + } + "powif64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64), RigidTy::Int(IntTy::I32) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::PowIF64) + } + "rintf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::RintF64) + } + "roundf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::RoundF64) + } + "sinf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::SinF64) + } + "sqrtf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::SqrtF64) + } + "truncf64" => { + assert_sig_matches!(sig, RigidTy::Float(FloatTy::F64) => RigidTy::Float(FloatTy::F64)); + Some(Intrinsic::TruncF64) + } + _ => None, + } +} 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 index 640318ccb584..15593549b690 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -24,12 +24,14 @@ //! 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 crate::{ + intrinsics::Intrinsic, + 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, @@ -202,161 +204,117 @@ impl<'a, 'tcx> Analysis<'tcx> for PointsToAnalysis<'a, 'tcx> { 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); + // Check if the intrinsic has a body we can analyze. + if self.tcx.is_mir_available(def_id) { + self.apply_regular_call_effect(state, instance, args, destination); + } else { + // Check all of the other intrinsics. + match Intrinsic::from_instance(&rustc_internal::stable(instance)) { + intrinsic if is_identity_aliasing_intrinsic(intrinsic.clone()) => { + // Treat the intrinsic as an aggregate, taking a union of all of the + // arguments' aliases. + let destination_set = + state.resolve_place(*destination, self.instance); + let operands_set = args + .into_iter() + .flat_map(|operand| { + self.successors_for_operand(state, operand.node.clone()) + }) + .collect(); + state.extend(&destination_set, &operands_set); + } + // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { + 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`. + Intrinsic::AtomicLoad(_) => { + 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`. + Intrinsic::AtomicStore(_) => { + 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`. + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { + 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`. + Intrinsic::Copy => { + self.apply_copy_effect( + state, + args[0].node.clone(), + args[1].node.clone(), + ); + } + // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + self.apply_copy_effect( + state, + args[1].node.clone(), + args[0].node.clone(), + ); + } + // Semantically equivalent to dest = *a + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { + // 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. + Intrinsic::VolatileStore => { + 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); + } + Intrinsic::Unimplemented { .. } => { + // This will be taken care of at the codegen level. + } + intrinsic => { + unimplemented!( + "Kani does not support reasoning about aliasing in presence of intrinsic `{intrinsic:?}`. For more information about the state of uninitialized memory checks implementation, see: https://github.com/model-checking/kani/issues/3300." + ); } } } @@ -652,3 +610,136 @@ impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { } } } + +/// Determines if the intrinsic does not influence aliasing beyond being treated as an identity +/// function (i.e. propagate aliasing without changes). +fn is_identity_aliasing_intrinsic(intrinsic: Intrinsic) -> bool { + match intrinsic { + Intrinsic::AddWithOverflow + | Intrinsic::ArithOffset + | Intrinsic::AssertInhabited + | Intrinsic::AssertMemUninitializedValid + | Intrinsic::AssertZeroValid + | Intrinsic::Assume + | Intrinsic::Bitreverse + | Intrinsic::BlackBox + | Intrinsic::Breakpoint + | Intrinsic::Bswap + | Intrinsic::CeilF32 + | Intrinsic::CeilF64 + | Intrinsic::CompareBytes + | Intrinsic::CopySignF32 + | Intrinsic::CopySignF64 + | Intrinsic::CosF32 + | Intrinsic::CosF64 + | Intrinsic::Ctlz + | Intrinsic::CtlzNonZero + | Intrinsic::Ctpop + | Intrinsic::Cttz + | Intrinsic::CttzNonZero + | Intrinsic::DiscriminantValue + | Intrinsic::ExactDiv + | Intrinsic::Exp2F32 + | Intrinsic::Exp2F64 + | Intrinsic::ExpF32 + | Intrinsic::ExpF64 + | Intrinsic::FabsF32 + | Intrinsic::FabsF64 + | Intrinsic::FaddFast + | Intrinsic::FdivFast + | Intrinsic::FloorF32 + | Intrinsic::FloorF64 + | Intrinsic::FmafF32 + | Intrinsic::FmafF64 + | Intrinsic::FmulFast + | Intrinsic::Forget + | Intrinsic::FsubFast + | Intrinsic::IsValStaticallyKnown + | Intrinsic::Likely + | Intrinsic::Log10F32 + | Intrinsic::Log10F64 + | Intrinsic::Log2F32 + | Intrinsic::Log2F64 + | Intrinsic::LogF32 + | Intrinsic::LogF64 + | Intrinsic::MaxNumF32 + | Intrinsic::MaxNumF64 + | Intrinsic::MinAlignOf + | Intrinsic::MinAlignOfVal + | Intrinsic::MinNumF32 + | Intrinsic::MinNumF64 + | Intrinsic::MulWithOverflow + | Intrinsic::NearbyIntF32 + | Intrinsic::NearbyIntF64 + | Intrinsic::NeedsDrop + | Intrinsic::PowF32 + | Intrinsic::PowF64 + | Intrinsic::PowIF32 + | Intrinsic::PowIF64 + | Intrinsic::PrefAlignOf + | Intrinsic::PtrGuaranteedCmp + | Intrinsic::PtrOffsetFrom + | Intrinsic::PtrOffsetFromUnsigned + | Intrinsic::RawEq + | Intrinsic::RintF32 + | Intrinsic::RintF64 + | Intrinsic::RotateLeft + | Intrinsic::RotateRight + | Intrinsic::RoundF32 + | Intrinsic::RoundF64 + | Intrinsic::SaturatingAdd + | Intrinsic::SaturatingSub + | Intrinsic::SinF32 + | Intrinsic::SinF64 + | Intrinsic::SizeOfVal + | Intrinsic::SqrtF32 + | Intrinsic::SqrtF64 + | Intrinsic::SubWithOverflow + | Intrinsic::TruncF32 + | Intrinsic::TruncF64 + | Intrinsic::TypeId + | Intrinsic::TypeName + | Intrinsic::UncheckedDiv + | Intrinsic::UncheckedRem + | Intrinsic::Unlikely + | Intrinsic::VtableSize + | Intrinsic::VtableAlign + | Intrinsic::WrappingAdd + | Intrinsic::WrappingMul + | Intrinsic::WrappingSub + | Intrinsic::WriteBytes => { + /* Intrinsics that do not interact with aliasing beyond propagating it. */ + true + } + Intrinsic::SimdAdd + | Intrinsic::SimdAnd + | Intrinsic::SimdDiv + | Intrinsic::SimdRem + | Intrinsic::SimdEq + | Intrinsic::SimdExtract + | Intrinsic::SimdGe + | Intrinsic::SimdGt + | Intrinsic::SimdInsert + | Intrinsic::SimdLe + | Intrinsic::SimdLt + | Intrinsic::SimdMul + | Intrinsic::SimdNe + | Intrinsic::SimdOr + | Intrinsic::SimdShl + | Intrinsic::SimdShr + | Intrinsic::SimdShuffle(_) + | Intrinsic::SimdSub + | Intrinsic::SimdXor => { + /* SIMD operations */ + true + } + Intrinsic::AtomicFence(_) | Intrinsic::AtomicSingleThreadFence(_) => { + /* Atomic fences */ + true + } + _ => { + /* Everything else */ + false + } + } +} 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 index 11ac412703ae..35d0e7041704 100644 --- 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 @@ -4,14 +4,17 @@ //! 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 crate::{ + intrinsics::Intrinsic, + 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, + Body, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, Rvalue, + Statement, StatementKind, Terminator, TerminatorKind, }, ty::{ConstantKind, RigidTy, TyKind}, }; @@ -101,34 +104,12 @@ impl MirVisitor for InitialTargetVisitor { 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)) - )); + match Intrinsic::from_instance(&instance) { + Intrinsic::Copy => { // 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)) - )); + Intrinsic::VolatileCopyMemory | Intrinsic::VolatileCopyNonOverlappingMemory => { // Here, `dst` is the first argument. self.push_operand(&args[0]); } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index 837e14abc886..f682f93a261e 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -3,12 +3,15 @@ // //! Visitor that collects all instructions relevant to uninitialized memory access. -use crate::kani_middle::transform::{ - body::{InsertPosition, MutableBody, SourceInstruction}, - check_uninit::{ - relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, - ty_layout::tys_layout_compatible_to_size, - TargetFinder, +use crate::{ + intrinsics::Intrinsic, + kani_middle::transform::{ + body::{InsertPosition, MutableBody, SourceInstruction}, + check_uninit::{ + relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, + ty_layout::tys_layout_compatible_to_size, + TargetFinder, + }, }, }; use stable_mir::{ @@ -16,8 +19,8 @@ use stable_mir::{ alloc::GlobalAlloc, mono::{Instance, InstanceKind}, visit::{Location, PlaceContext}, - BasicBlockIdx, CastKind, LocalDecl, MirVisitor, Mutability, NonDivergingIntrinsic, Operand, - Place, PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, + BasicBlockIdx, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, + PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }, ty::{ConstantKind, RigidTy, TyKind}, @@ -182,46 +185,30 @@ impl MirVisitor for CheckUninitVisitor { }; match instance.kind { InstanceKind::Intrinsic => { - match instance.intrinsic_name().unwrap().as_str() { - intrinsic_name if can_skip_intrinsic(intrinsic_name) => { + match Intrinsic::from_instance(&instance) { + intrinsic_name if can_skip_intrinsic(intrinsic_name.clone()) => { /* Intrinsics that can be safely skipped */ } - name if name.starts_with("atomic") => { - let num_args = match name { - // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. - name if name.starts_with("atomic_cxchg") => 3, - // All `atomic_load` intrinsics take `src` as an argument. - name if name.starts_with("atomic_load") => 1, - // All other `atomic` intrinsics take `dst, src` as arguments. - _ => 2, - }; - assert_eq!( - args.len(), - num_args, - "Unexpected number of arguments for `{name}`" - ); - assert!(matches!( - args[0].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(..)) - )); + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicCxchg(_) + | Intrinsic::AtomicCxchgWeak(_) + | Intrinsic::AtomicLoad(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicStore(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { self.push_target(MemoryInitOp::Check { operand: args[0].clone(), }); } - "compare_bytes" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `compare_bytes`" - ); - assert!(matches!( - args[0].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - assert!(matches!( - args[1].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); + Intrinsic::CompareBytes => { self.push_target(MemoryInitOp::CheckSliceChunk { operand: args[0].clone(), count: args[2].clone(), @@ -231,22 +218,7 @@ impl MirVisitor for CheckUninitVisitor { count: args[2].clone(), }); } - "copy" - | "volatile_copy_memory" - | "volatile_copy_nonoverlapping_memory" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `copy`" - ); - assert!(matches!( - args[0].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); - assert!(matches!( - args[1].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); + Intrinsic::Copy => { self.push_target(MemoryInitOp::CheckSliceChunk { operand: args[0].clone(), count: args[2].clone(), @@ -258,20 +230,20 @@ impl MirVisitor for CheckUninitVisitor { position: InsertPosition::After, }); } - "typed_swap" => { - assert_eq!( - args.len(), - 2, - "Unexpected number of arguments for `typed_swap`" - ); - assert!(matches!( - args[0].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); - assert!(matches!( - args[1].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + self.push_target(MemoryInitOp::CheckSliceChunk { + operand: args[1].clone(), + count: args[2].clone(), + }); + self.push_target(MemoryInitOp::SetSliceChunk { + operand: args[0].clone(), + count: args[2].clone(), + value: true, + position: InsertPosition::After, + }); + } + Intrinsic::TypedSwap => { self.push_target(MemoryInitOp::Check { operand: args[0].clone(), }); @@ -279,46 +251,19 @@ impl MirVisitor for CheckUninitVisitor { operand: args[1].clone(), }); } - "volatile_load" | "unaligned_volatile_load" => { - assert_eq!( - args.len(), - 1, - "Unexpected number of arguments for `volatile_load`" - ); - assert!(matches!( - args[0].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Not)) - )); + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { self.push_target(MemoryInitOp::Check { operand: args[0].clone(), }); } - "volatile_store" => { - assert_eq!( - args.len(), - 2, - "Unexpected number of arguments for `volatile_store`" - ); - assert!(matches!( - args[0].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); + Intrinsic::VolatileStore => { self.push_target(MemoryInitOp::Set { operand: args[0].clone(), value: true, position: InsertPosition::After, }); } - "write_bytes" => { - assert_eq!( - args.len(), - 3, - "Unexpected number of arguments for `write_bytes`" - ); - assert!(matches!( - args[0].ty(&self.locals).unwrap().kind(), - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - )); + Intrinsic::WriteBytes => { self.push_target(MemoryInitOp::SetSliceChunk { operand: args[0].clone(), count: args[2].clone(), @@ -328,7 +273,7 @@ impl MirVisitor for CheckUninitVisitor { } intrinsic => { self.push_target(MemoryInitOp::Unsupported { - reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic}`."), + reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic:?}`."), }); } } @@ -519,132 +464,131 @@ impl MirVisitor for CheckUninitVisitor { /// Determines if the intrinsic has no memory initialization related function and hence can be /// safely skipped. -fn can_skip_intrinsic(intrinsic_name: &str) -> bool { - match intrinsic_name { - "add_with_overflow" - | "arith_offset" - | "assert_inhabited" - | "assert_mem_uninitialized_valid" - | "assert_zero_valid" - | "assume" - | "bitreverse" - | "black_box" - | "breakpoint" - | "bswap" - | "caller_location" - | "ceilf32" - | "ceilf64" - | "copysignf32" - | "copysignf64" - | "cosf32" - | "cosf64" - | "ctlz" - | "ctlz_nonzero" - | "ctpop" - | "cttz" - | "cttz_nonzero" - | "discriminant_value" - | "exact_div" - | "exp2f32" - | "exp2f64" - | "expf32" - | "expf64" - | "fabsf32" - | "fabsf64" - | "fadd_fast" - | "fdiv_fast" - | "floorf32" - | "floorf64" - | "fmaf32" - | "fmaf64" - | "fmul_fast" - | "forget" - | "fsub_fast" - | "is_val_statically_known" - | "likely" - | "log10f32" - | "log10f64" - | "log2f32" - | "log2f64" - | "logf32" - | "logf64" - | "maxnumf32" - | "maxnumf64" - | "min_align_of" - | "min_align_of_val" - | "minnumf32" - | "minnumf64" - | "mul_with_overflow" - | "nearbyintf32" - | "nearbyintf64" - | "needs_drop" - | "powf32" - | "powf64" - | "powif32" - | "powif64" - | "pref_align_of" - | "raw_eq" - | "rintf32" - | "rintf64" - | "rotate_left" - | "rotate_right" - | "roundf32" - | "roundf64" - | "saturating_add" - | "saturating_sub" - | "sinf32" - | "sinf64" - | "sqrtf32" - | "sqrtf64" - | "sub_with_overflow" - | "truncf32" - | "truncf64" - | "type_id" - | "type_name" - | "unchecked_div" - | "unchecked_rem" - | "unlikely" - | "vtable_size" - | "vtable_align" - | "wrapping_add" - | "wrapping_mul" - | "wrapping_sub" => { +fn can_skip_intrinsic(intrinsic: Intrinsic) -> bool { + match intrinsic { + Intrinsic::AddWithOverflow + | Intrinsic::ArithOffset + | Intrinsic::AssertInhabited + | Intrinsic::AssertMemUninitializedValid + | Intrinsic::AssertZeroValid + | Intrinsic::Assume + | Intrinsic::Bitreverse + | Intrinsic::BlackBox + | Intrinsic::Breakpoint + | Intrinsic::Bswap + | Intrinsic::CeilF32 + | Intrinsic::CeilF64 + | Intrinsic::CopySignF32 + | Intrinsic::CopySignF64 + | Intrinsic::CosF32 + | Intrinsic::CosF64 + | Intrinsic::Ctlz + | Intrinsic::CtlzNonZero + | Intrinsic::Ctpop + | Intrinsic::Cttz + | Intrinsic::CttzNonZero + | Intrinsic::DiscriminantValue + | Intrinsic::ExactDiv + | Intrinsic::Exp2F32 + | Intrinsic::Exp2F64 + | Intrinsic::ExpF32 + | Intrinsic::ExpF64 + | Intrinsic::FabsF32 + | Intrinsic::FabsF64 + | Intrinsic::FaddFast + | Intrinsic::FdivFast + | Intrinsic::FloorF32 + | Intrinsic::FloorF64 + | Intrinsic::FmafF32 + | Intrinsic::FmafF64 + | Intrinsic::FmulFast + | Intrinsic::Forget + | Intrinsic::FsubFast + | Intrinsic::IsValStaticallyKnown + | Intrinsic::Likely + | Intrinsic::Log10F32 + | Intrinsic::Log10F64 + | Intrinsic::Log2F32 + | Intrinsic::Log2F64 + | Intrinsic::LogF32 + | Intrinsic::LogF64 + | Intrinsic::MaxNumF32 + | Intrinsic::MaxNumF64 + | Intrinsic::MinAlignOf + | Intrinsic::MinAlignOfVal + | Intrinsic::MinNumF32 + | Intrinsic::MinNumF64 + | Intrinsic::MulWithOverflow + | Intrinsic::NearbyIntF32 + | Intrinsic::NearbyIntF64 + | Intrinsic::NeedsDrop + | Intrinsic::PowF32 + | Intrinsic::PowF64 + | Intrinsic::PowIF32 + | Intrinsic::PowIF64 + | Intrinsic::PrefAlignOf + | Intrinsic::RawEq + | Intrinsic::RintF32 + | Intrinsic::RintF64 + | Intrinsic::RotateLeft + | Intrinsic::RotateRight + | Intrinsic::RoundF32 + | Intrinsic::RoundF64 + | Intrinsic::SaturatingAdd + | Intrinsic::SaturatingSub + | Intrinsic::SinF32 + | Intrinsic::SinF64 + | Intrinsic::SqrtF32 + | Intrinsic::SqrtF64 + | Intrinsic::SubWithOverflow + | Intrinsic::TruncF32 + | Intrinsic::TruncF64 + | Intrinsic::TypeId + | Intrinsic::TypeName + | Intrinsic::UncheckedDiv + | Intrinsic::UncheckedRem + | Intrinsic::Unlikely + | Intrinsic::VtableSize + | Intrinsic::VtableAlign + | Intrinsic::WrappingAdd + | Intrinsic::WrappingMul + | Intrinsic::WrappingSub => { /* Intrinsics that do not interact with memory initialization. */ true } - "ptr_guaranteed_cmp" | "ptr_offset_from" | "ptr_offset_from_unsigned" | "size_of_val" => { + Intrinsic::PtrGuaranteedCmp + | Intrinsic::PtrOffsetFrom + | Intrinsic::PtrOffsetFromUnsigned + | Intrinsic::SizeOfVal => { /* AFAICS from the documentation, none of those require the pointer arguments to be actually initialized. */ true } - name if name.starts_with("simd") => { + Intrinsic::SimdAdd + | Intrinsic::SimdAnd + | Intrinsic::SimdDiv + | Intrinsic::SimdRem + | Intrinsic::SimdEq + | Intrinsic::SimdExtract + | Intrinsic::SimdGe + | Intrinsic::SimdGt + | Intrinsic::SimdInsert + | Intrinsic::SimdLe + | Intrinsic::SimdLt + | Intrinsic::SimdMul + | Intrinsic::SimdNe + | Intrinsic::SimdOr + | Intrinsic::SimdShl + | Intrinsic::SimdShr + | Intrinsic::SimdShuffle(_) + | Intrinsic::SimdSub + | Intrinsic::SimdXor => { /* SIMD operations */ true } - name if name.starts_with("atomic_fence") - || name.starts_with("atomic_singlethreadfence") => - { + Intrinsic::AtomicFence(_) | Intrinsic::AtomicSingleThreadFence(_) => { /* Atomic fences */ true } - "copy_nonoverlapping" => unreachable!( - "Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`" - ), - "offset" => unreachable!( - "Expected `core::intrinsics::unreachable` to be handled by `BinOp::OffSet`" - ), - "unreachable" => unreachable!( - "Expected `std::intrinsics::unreachable` to be handled by `TerminatorKind::Unreachable`" - ), - "transmute" | "transmute_copy" | "unchecked_add" | "unchecked_mul" | "unchecked_shl" - | "size_of" | "unchecked_shr" | "unchecked_sub" => { - unreachable!("Expected intrinsic to be lowered before codegen") - } - "catch_unwind" => { - unimplemented!("") - } - "retag_box_to_raw" => { - unreachable!("This was removed in the latest Rust version.") - } _ => { /* Everything else */ false diff --git a/kani-compiler/src/main.rs b/kani-compiler/src/main.rs index 7f1fb144a09b..e47483fb4fa5 100644 --- a/kani-compiler/src/main.rs +++ b/kani-compiler/src/main.rs @@ -39,6 +39,7 @@ extern crate tempfile; mod args; #[cfg(feature = "cprover")] mod codegen_cprover_gotoc; +mod intrinsics; mod kani_compiler; mod kani_middle; mod kani_queries; From a55834b8715a0ef1a9b51c7d15f0e72d28b028b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:44:16 +0000 Subject: [PATCH 023/159] Bump tests/perf/s2n-quic from `445f73b` to `ab9723a` (#3434) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `445f73b` to `ab9723a`.
Commits

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 445f73b27eae..ab9723a772f0 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 445f73b27eae529bb895a7678968e4c0c215ef8a +Subproject commit ab9723a772f03a9793c9863e73c9a48fab3c5235 From 952e75389251532259b266d9b4c27756f08b222d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:12:09 -0400 Subject: [PATCH 024/159] Automatic cargo update to 2024-08-12 (#3433) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12c28c49c1bf..1e8e743d5e56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,9 +146,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.13" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" dependencies = [ "clap_builder", "clap_derive", @@ -156,9 +156,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -175,7 +175,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -510,7 +510,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -950,29 +950,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "itoa", "memchr", @@ -1072,7 +1072,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1087,9 +1087,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", @@ -1126,7 +1126,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1192,7 +1192,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -1467,5 +1467,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] From e2a209bcb847803e8a5abb20ef7002b109ed0bf6 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Mon, 12 Aug 2024 23:27:29 +0200 Subject: [PATCH 025/159] Actually apply CBMC patch (#3436) The patch introduced in #3431 not only needs to be created, but also needs to be applied. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- scripts/setup/al2/install_cbmc.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/setup/al2/install_cbmc.sh b/scripts/setup/al2/install_cbmc.sh index de4b5215e083..3bac22ace3db 100755 --- a/scripts/setup/al2/install_cbmc.sh +++ b/scripts/setup/al2/install_cbmc.sh @@ -93,6 +93,7 @@ cat > varargs.patch << "EOF" } EOF +patch -p1 < varargs.patch cmake3 -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" \ -DCMAKE_C_COMPILER=gcc10-cc -DCMAKE_CXX_COMPILER=gcc10-c++ \ From f27a5ed1426c705ec6faa181f080579b90983436 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 15 Aug 2024 07:18:06 -0700 Subject: [PATCH 026/159] Add test related to issue 3432 (#3439) In some cases, Kani would report a spurious counter example for cases where a match arm contained more than one pattern. This was fixed by changing how we handle storage lifecycle in #2995. This PR is only adding the related test to the regression. Resolves #3432 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- tests/kani/Match/match_pattern.rs | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/kani/Match/match_pattern.rs diff --git a/tests/kani/Match/match_pattern.rs b/tests/kani/Match/match_pattern.rs new file mode 100644 index 000000000000..1b8689aee881 --- /dev/null +++ b/tests/kani/Match/match_pattern.rs @@ -0,0 +1,57 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Test that Kani can correctly handle match patterns joined with the `|` operator. +//! It contains two equivalent methods that only differ by grouping march patterns. +//! Kani used to only be able to verify one as reported in: +//! + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(kani, derive(kani::Arbitrary))] +pub enum AbstractInt { + Bottom = 0, + Zero = 1, + Top = 2, +} + +impl AbstractInt { + /// Code with exhausive match expression where each arm contains one pattern. + pub fn merge(self, other: Self) -> Self { + use AbstractInt::*; + match (self, other) { + (Bottom, x) => x, + (x, Bottom) => x, + (Zero, Zero) => Zero, + (Top, _) => Top, + (_, Top) => Top, + } + } + + /// Code with exhausive match expression where an arm may contain multiple patterns. + pub fn merge_joined(self, other: Self) -> Self { + use AbstractInt::*; + match (self, other) { + (Bottom, x) | (x, Bottom) => x, + (Zero, Zero) => Zero, + (Top, _) | (_, Top) => Top, + } + } +} + +#[cfg(kani)] +mod test { + use super::*; + + #[kani::proof] + fn merge_with_bottom() { + let x: AbstractInt = kani::any(); + assert!(x.merge(AbstractInt::Bottom) == x); + assert!(AbstractInt::Bottom.merge(x) == x) + } + + #[kani::proof] + fn check_equivalence() { + let x: AbstractInt = kani::any(); + let y: AbstractInt = kani::any(); + assert_eq!(x.merge(y), x.merge_joined(y)); + } +} From e6f8a62d689d0b0ebcbabe3661be6273c5ab9be8 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 15 Aug 2024 10:31:09 -0700 Subject: [PATCH 027/159] Implement memory initialization state copy functionality (#3350) This PR adds support of copying memory initialization state without checks in-between. Every time a copy is performed, the tracked byte is non-deterministically switched. Resolves #3347 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../kani_middle/transform/check_uninit/mod.rs | 35 ++++++++++++++++ .../check_uninit/ptr_uninit/uninit_visitor.rs | 42 +++++++------------ .../check_uninit/relevant_instruction.rs | 31 ++++++++++++-- library/kani/src/mem_init.rs | 40 ++++++++++++++++++ .../uninit/copy/copy_without_padding.expected | 1 + .../uninit/copy/copy_without_padding.rs | 23 ++++++++++ .../copy/expose_padding_via_copy.expected | 11 +++++ .../uninit/copy/expose_padding_via_copy.rs | 23 ++++++++++ ...xpose_padding_via_copy_convoluted.expected | 11 +++++ .../expose_padding_via_copy_convoluted.rs | 42 +++++++++++++++++++ .../expose_padding_via_non_byte_copy.expected | 11 +++++ .../copy/expose_padding_via_non_byte_copy.rs | 23 ++++++++++ .../non_byte_copy_without_padding.expected | 1 + .../copy/non_byte_copy_without_padding.rs | 23 ++++++++++ .../uninit/copy/read_after_copy.expected | 11 +++++ tests/expected/uninit/copy/read_after_copy.rs | 23 ++++++++++ .../expected/uninit/delayed-ub/delayed-ub.rs | 31 ++++++++++++++ tests/expected/uninit/delayed-ub/expected | 12 +++++- tests/expected/uninit/intrinsics/expected | 12 +++--- .../expected/uninit/intrinsics/intrinsics.rs | 30 ++++++++++++- 20 files changed, 397 insertions(+), 39 deletions(-) create mode 100644 tests/expected/uninit/copy/copy_without_padding.expected create mode 100644 tests/expected/uninit/copy/copy_without_padding.rs create mode 100644 tests/expected/uninit/copy/expose_padding_via_copy.expected create mode 100644 tests/expected/uninit/copy/expose_padding_via_copy.rs create mode 100644 tests/expected/uninit/copy/expose_padding_via_copy_convoluted.expected create mode 100644 tests/expected/uninit/copy/expose_padding_via_copy_convoluted.rs create mode 100644 tests/expected/uninit/copy/expose_padding_via_non_byte_copy.expected create mode 100644 tests/expected/uninit/copy/expose_padding_via_non_byte_copy.rs create mode 100644 tests/expected/uninit/copy/non_byte_copy_without_padding.expected create mode 100644 tests/expected/uninit/copy/non_byte_copy_without_padding.rs create mode 100644 tests/expected/uninit/copy/read_after_copy.expected create mode 100644 tests/expected/uninit/copy/read_after_copy.rs 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 5c7194f879d1..0a0d3c786ea9 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -178,6 +178,9 @@ impl<'a> UninitInstrumenter<'a> { | MemoryInitOp::SetRef { .. } => { self.build_set(tcx, body, source, operation, pointee_ty_info, skip_first) } + MemoryInitOp::Copy { .. } => { + self.build_copy(tcx, body, source, operation, pointee_ty_info, skip_first) + } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() } @@ -397,6 +400,38 @@ impl<'a> UninitInstrumenter<'a> { }; } + /// Copy memory initialization state from one pointer to the other. + fn build_copy( + &mut self, + tcx: TyCtxt, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: MemoryInitOp, + pointee_info: PointeeInfo, + skip_first: &mut HashSet, + ) { + let ret_place = Place { + local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + let PointeeLayout::Sized { layout } = pointee_info.layout() else { unreachable!() }; + let copy_init_state_instance = resolve_mem_init_fn( + get_mem_init_fn_def(tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache), + layout.len(), + *pointee_info.ty(), + ); + collect_skipped(&operation, body, skip_first); + let position = operation.position(); + let MemoryInitOp::Copy { from, to, count } = operation else { unreachable!() }; + body.insert_call( + ©_init_state_instance, + source, + position, + vec![from, to, count], + ret_place.clone(), + ); + } + fn inject_assert_false( &self, tcx: TyCtxt, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index f682f93a261e..1afb151a09b5 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -88,18 +88,13 @@ impl MirVisitor for CheckUninitVisitor { match &stmt.kind { StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => { self.super_statement(stmt, location); - // Source is a *const T and it must be initialized. - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: copy.src.clone(), + // The copy is untyped, so we should copy memory initialization state from `src` + // to `dst`. + self.push_target(MemoryInitOp::Copy { + from: copy.src.clone(), + to: copy.dst.clone(), count: copy.count.clone(), }); - // Destination is a *mut T so it gets initialized. - self.push_target(MemoryInitOp::SetSliceChunk { - operand: copy.dst.clone(), - count: copy.count.clone(), - value: true, - position: InsertPosition::After, - }); } StatementKind::Assign(place, rvalue) => { // First check rvalue. @@ -219,29 +214,24 @@ impl MirVisitor for CheckUninitVisitor { }); } Intrinsic::Copy => { - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: args[0].clone(), + // The copy is untyped, so we should copy memory + // initialization state from `src` to `dst`. + self.push_target(MemoryInitOp::Copy { + from: args[0].clone(), + to: args[1].clone(), count: args[2].clone(), }); - self.push_target(MemoryInitOp::SetSliceChunk { - operand: args[1].clone(), - count: args[2].clone(), - value: true, - position: InsertPosition::After, - }); } Intrinsic::VolatileCopyMemory | Intrinsic::VolatileCopyNonOverlappingMemory => { - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: args[1].clone(), + // The copy is untyped, so we should copy initialization state + // from `src` to `dst`. Note that the `dst` comes before `src` + // in this case. + self.push_target(MemoryInitOp::Copy { + from: args[1].clone(), + to: args[0].clone(), count: args[2].clone(), }); - self.push_target(MemoryInitOp::SetSliceChunk { - operand: args[0].clone(), - count: args[2].clone(), - value: true, - position: InsertPosition::After, - }); } Intrinsic::TypedSwap => { self.push_target(MemoryInitOp::Check { 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 index 3bc5b534a23b..cc5a27e09925 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -5,7 +5,10 @@ //! character of instrumentation needed. use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; -use stable_mir::mir::{Mutability, Operand, Place, Rvalue}; +use stable_mir::{ + mir::{Mutability, Operand, Place, Rvalue}, + ty::RigidTy, +}; use strum_macros::AsRefStr; /// Memory initialization operations: set or get memory initialization state for a given pointer. @@ -33,6 +36,8 @@ pub enum MemoryInitOp { Unsupported { reason: String }, /// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`. TriviallyUnsafe { reason: String }, + /// Operation that copies memory initialization state over to another operand. + Copy { from: Operand, to: Operand, count: Operand }, } impl MemoryInitOp { @@ -62,7 +67,22 @@ impl MemoryInitOp { }) } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { - unreachable!() + unreachable!("operands do not exist for this operation") + } + MemoryInitOp::Copy { from, to, .. } => { + // It does not matter which operand to return for layout generation, since both of + // them have the same pointee type, so we assert that. + let from_kind = from.ty(body.locals()).unwrap().kind(); + let to_kind = to.ty(body.locals()).unwrap().kind(); + + let RigidTy::RawPtr(from_pointee_ty, _) = from_kind.rigid().unwrap().clone() else { + unreachable!() + }; + let RigidTy::RawPtr(to_pointee_ty, _) = to_kind.rigid().unwrap().clone() else { + unreachable!() + }; + assert!(from_pointee_ty == to_pointee_ty); + from.clone() } } } @@ -70,7 +90,8 @@ impl MemoryInitOp { pub fn expect_count(&self) -> Operand { match self { MemoryInitOp::CheckSliceChunk { count, .. } - | MemoryInitOp::SetSliceChunk { count, .. } => count.clone(), + | MemoryInitOp::SetSliceChunk { count, .. } + | MemoryInitOp::Copy { count, .. } => count.clone(), MemoryInitOp::Check { .. } | MemoryInitOp::Set { .. } | MemoryInitOp::CheckRef { .. } @@ -89,7 +110,8 @@ impl MemoryInitOp { | MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), + | MemoryInitOp::TriviallyUnsafe { .. } + | MemoryInitOp::Copy { .. } => unreachable!(), } } @@ -103,6 +125,7 @@ impl MemoryInitOp { | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before, + MemoryInitOp::Copy { .. } => InsertPosition::After, } } } diff --git a/library/kani/src/mem_init.rs b/library/kani/src/mem_init.rs index 88847e9c4f3c..3755fc59a510 100644 --- a/library/kani/src/mem_init.rs +++ b/library/kani/src/mem_init.rs @@ -91,6 +91,33 @@ impl MemoryInitializationState { } } + /// Copy memory initialization state by non-deterministically switching the tracked object and + /// adjusting the tracked offset. + pub fn copy( + &mut self, + from_ptr: *const u8, + to_ptr: *const u8, + num_elts: usize, + ) { + let from_obj = crate::mem::pointer_object(from_ptr); + let from_offset = crate::mem::pointer_offset(from_ptr); + + let to_obj = crate::mem::pointer_object(to_ptr); + let to_offset = crate::mem::pointer_offset(to_ptr); + + if self.tracked_object_id == from_obj + && self.tracked_offset >= from_offset + && self.tracked_offset < from_offset + num_elts * LAYOUT_SIZE + { + let should_reset: bool = crate::any(); + if should_reset { + self.tracked_object_id = to_obj; + self.tracked_offset += to_offset - from_offset; + // Note that this preserves the value. + } + } + } + /// Return currently tracked memory initialization state if `ptr` points to the currently /// tracked object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. /// Return `true` otherwise. @@ -281,3 +308,16 @@ fn set_str_ptr_initialized( MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); } } + +/// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. +#[rustc_diagnostic_item = "KaniCopyInitState"] +fn copy_init_state(from: *const T, to: *const T, num_elts: usize) { + if LAYOUT_SIZE == 0 { + return; + } + let (from_ptr, _) = from.to_raw_parts(); + let (to_ptr, _) = to.to_raw_parts(); + unsafe { + MEM_INIT_STATE.copy::(from_ptr as *const u8, to_ptr as *const u8, num_elts); + } +} diff --git a/tests/expected/uninit/copy/copy_without_padding.expected b/tests/expected/uninit/copy/copy_without_padding.expected new file mode 100644 index 000000000000..01a90d50b557 --- /dev/null +++ b/tests/expected/uninit/copy/copy_without_padding.expected @@ -0,0 +1 @@ +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/expected/uninit/copy/copy_without_padding.rs b/tests/expected/uninit/copy/copy_without_padding.rs new file mode 100644 index 000000000000..16df1dd5d2d0 --- /dev/null +++ b/tests/expected/uninit/copy/copy_without_padding.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +#[kani::proof] +/// This checks that reading copied initialized bytes verifies correctly. +unsafe fn copy_without_padding() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + + // Since the previous copy only copied 4 bytes, no padding was copied, so no padding is read. + let data: u64 = std::ptr::read(to_ptr); +} diff --git a/tests/expected/uninit/copy/expose_padding_via_copy.expected b/tests/expected/uninit/copy/expose_padding_via_copy.expected new file mode 100644 index 000000000000..83d8badc8bf5 --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_copy.expected @@ -0,0 +1,11 @@ +std::ptr::read::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u64`"\ + +std::ptr::read::.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`"\ + +Summary: +Verification failed for - expose_padding_via_copy +Complete - 0 successfully verified harnesses, 1 failures, 1 total. diff --git a/tests/expected/uninit/copy/expose_padding_via_copy.rs b/tests/expected/uninit/copy/expose_padding_via_copy.rs new file mode 100644 index 000000000000..8adb772037ca --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_copy.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +/// This checks that reading copied uninitialized bytes fails an assertion. +#[kani::proof] +unsafe fn expose_padding_via_copy() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + + // This reads uninitialized bytes, which is UB. + let padding: u64 = std::ptr::read(to_ptr); +} diff --git a/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.expected b/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.expected new file mode 100644 index 000000000000..cbe7ec97cb7b --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.expected @@ -0,0 +1,11 @@ +std::ptr::read::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u64`"\ + +std::ptr::read::.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`"\ + +Summary: +Verification failed for - expose_padding_via_copy_convoluted +Complete - 0 successfully verified harnesses, 1 failures, 1 total. diff --git a/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.rs b/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.rs new file mode 100644 index 000000000000..5feadace245d --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_copy_convoluted.rs @@ -0,0 +1,42 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +/// This checks that reading copied uninitialized bytes fails an assertion even if pointer are +/// passed around different functions. +#[kani::proof] +unsafe fn expose_padding_via_copy_convoluted() { + unsafe fn copy_and_read_helper(from_ptr: *const S, to_ptr: *mut u64) -> u64 { + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + // This reads uninitialized bytes, which is UB. + let padding: u64 = std::ptr::read(to_ptr); + padding + } + + unsafe fn partial_copy_and_read_helper(from_ptr: *const S, to_ptr: *mut u64) -> u32 { + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + // This does not read uninitialized bytes. + let not_padding: u32 = std::ptr::read(to_ptr as *mut u32); + not_padding + } + + let flag: bool = kani::any(); + + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + if flag { + copy_and_read_helper(from_ptr, to_ptr); + } else { + partial_copy_and_read_helper(from_ptr, to_ptr); + } +} diff --git a/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.expected b/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.expected new file mode 100644 index 000000000000..3fc86e45a46e --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.expected @@ -0,0 +1,11 @@ +std::ptr::read::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u64`"\ + +std::ptr::read::.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`"\ + +Summary: +Verification failed for - expose_padding_via_non_byte_copy +Complete - 0 successfully verified harnesses, 1 failures, 1 total. diff --git a/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.rs b/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.rs new file mode 100644 index 000000000000..685239b267b1 --- /dev/null +++ b/tests/expected/uninit/copy/expose_padding_via_non_byte_copy.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +/// This checks that reading copied uninitialized bytes after a multi-byte copy fails an assertion. +#[kani::proof] +unsafe fn expose_padding_via_non_byte_copy() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u64, to_ptr as *mut u64, 1); + + // This reads uninitialized bytes, which is UB. + let padding: u64 = std::ptr::read(to_ptr); +} diff --git a/tests/expected/uninit/copy/non_byte_copy_without_padding.expected b/tests/expected/uninit/copy/non_byte_copy_without_padding.expected new file mode 100644 index 000000000000..01a90d50b557 --- /dev/null +++ b/tests/expected/uninit/copy/non_byte_copy_without_padding.expected @@ -0,0 +1 @@ +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/expected/uninit/copy/non_byte_copy_without_padding.rs b/tests/expected/uninit/copy/non_byte_copy_without_padding.rs new file mode 100644 index 000000000000..6f3b380cd81f --- /dev/null +++ b/tests/expected/uninit/copy/non_byte_copy_without_padding.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +#[kani::proof] +/// This checks that reading copied initialized bytes after a multi-byte copy verifies correctly. +unsafe fn non_byte_copy_without_padding() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u32, to_ptr as *mut u32, 1); + + // Since the previous copy only copied 4 bytes, no padding was copied, so no padding is read. + let data: u64 = std::ptr::read(to_ptr); +} diff --git a/tests/expected/uninit/copy/read_after_copy.expected b/tests/expected/uninit/copy/read_after_copy.expected new file mode 100644 index 000000000000..56a3460a1d7b --- /dev/null +++ b/tests/expected/uninit/copy/read_after_copy.expected @@ -0,0 +1,11 @@ +std::ptr::read::.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u64`"\ + +std::ptr::read::.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`"\ + +Summary: +Verification failed for - read_after_copy +Complete - 0 successfully verified harnesses, 1 failures, 1 total. diff --git a/tests/expected/uninit/copy/read_after_copy.rs b/tests/expected/uninit/copy/read_after_copy.rs new file mode 100644 index 000000000000..742b74099acc --- /dev/null +++ b/tests/expected/uninit/copy/read_after_copy.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +#[repr(C)] +#[derive(kani::Arbitrary)] +struct S(u32, u8); // 5 bytes of data + 3 bytes of padding. + +/// This checks that reading uninitialized bytes fails an assertion even after copy. +#[kani::proof] +unsafe fn read_after_copy() { + let from: S = kani::any(); + let mut to: u64 = kani::any(); + + let from_ptr = &from as *const S; + let to_ptr = &mut to as *mut u64; + + // This should not cause UB since `copy` is untyped. + std::ptr::copy(from_ptr as *const u8, to_ptr as *mut u8, std::mem::size_of::()); + + // Reading padding from the previous place should be UB even after copy. + let data: u64 = std::ptr::read(from_ptr as *const u64); +} diff --git a/tests/expected/uninit/delayed-ub/delayed-ub.rs b/tests/expected/uninit/delayed-ub/delayed-ub.rs index feee4bcd161f..d7f258c27ba5 100644 --- a/tests/expected/uninit/delayed-ub/delayed-ub.rs +++ b/tests/expected/uninit/delayed-ub/delayed-ub.rs @@ -124,6 +124,24 @@ fn delayed_ub_copy() { } } +/// Delayed UB via multiple mutable pointers write using `copy_nonoverlapping` and `copy` under the +/// hood. +#[kani::proof] +fn delayed_ub_double_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)); + // Instead of assigning the value into a delayed UB place, copy it from another delayed UB + // place. + let mut value_2: u128 = 0; + let ptr_2 = &mut value_2 as *mut _ as *mut (u8, u32, u64); + std::ptr::copy(ptr, ptr_2, 1); // This should not trigger UB since the copy is untyped. + assert!(value_2 > 0); // UB: This reads a padding value! + } +} + struct S { u: U, } @@ -164,3 +182,16 @@ fn delayed_ub_slices() { let arr_copy = arr; // UB: This reads a padding value inside the array! } } + +/// Delayed UB via mutable pointer copy, which should be the only delayed UB trigger in this case. +#[kani::proof] +fn delayed_ub_trigger_copy() { + unsafe { + let mut value: u128 = 0; + let ptr = &mut value as *mut _ as *mut u8; // This cast should not be a delayed UB source. + let mut value_different_padding: (u8, u32, u64) = (4, 4, 4); + let ptr_different_padding = &mut value_different_padding as *mut _ as *mut u8; + std::ptr::copy(ptr_different_padding, ptr, std::mem::size_of::()); // This is a delayed UB source. + assert!(value > 0); // UB: This reads a padding value! + } +} diff --git a/tests/expected/uninit/delayed-ub/expected b/tests/expected/uninit/delayed-ub/expected index 46b6ababe85d..dc0411bdba9c 100644 --- a/tests/expected/uninit/delayed-ub/expected +++ b/tests/expected/uninit/delayed-ub/expected @@ -1,3 +1,7 @@ +delayed_ub_trigger_copy.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`"\ + delayed_ub_slices.assertion.4\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `[u128; 4]`" @@ -6,6 +10,10 @@ delayed_ub_structs.assertion.2\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `U`" +delayed_ub_double_copy.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`"\ + delayed_ub_copy.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" @@ -35,8 +43,10 @@ delayed_ub.assertion.2\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" Summary: +Verification failed for - delayed_ub_trigger_copy Verification failed for - delayed_ub_slices Verification failed for - delayed_ub_structs +Verification failed for - delayed_ub_double_copy Verification failed for - delayed_ub_copy Verification failed for - delayed_ub_closure_capture_laundered Verification failed for - delayed_ub_closure_laundered @@ -44,4 +54,4 @@ 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. +Complete - 0 successfully verified harnesses, 11 failures, 11 total. diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index cf34d305608b..e428aa605887 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -34,19 +34,19 @@ check_compare_bytes.assertion.2\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" -std::intrinsics::copy::.assertion.1\ +std::ptr::read::.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" -std::intrinsics::copy_nonoverlapping::.assertion.1\ +std::ptr::read::.assertion.2\ - Status: FAILURE\ - - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*const u8`" + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u8`" Summary: Verification failed for - check_typed_swap_safe Verification failed for - check_typed_swap Verification failed for - check_volatile_load Verification failed for - check_compare_bytes -Verification failed for - check_copy -Verification failed for - check_copy_nonoverlapping -Complete - 5 successfully verified harnesses, 6 failures, 11 total. +Verification failed for - check_copy_read +Verification failed for - check_copy_nonoverlapping_read +Complete - 7 successfully verified harnesses, 6 failures, 13 total. diff --git a/tests/expected/uninit/intrinsics/intrinsics.rs b/tests/expected/uninit/intrinsics/intrinsics.rs index aa8a89b7b959..b023853b2fbc 100644 --- a/tests/expected/uninit/intrinsics/intrinsics.rs +++ b/tests/expected/uninit/intrinsics/intrinsics.rs @@ -14,7 +14,20 @@ fn check_copy_nonoverlapping() { let layout = Layout::from_size_align(16, 8).unwrap(); let src: *mut u8 = alloc(layout); let dst: *mut u8 = alloc(layout); - copy_nonoverlapping(src as *const u8, dst, 2); // ~ERROR: Accessing `src` here, which is uninitialized. + // This does not fail, since `copy_nonoverlapping` is untyped. + copy_nonoverlapping(src as *const u8, dst, 2); + } +} + +#[kani::proof] +fn check_copy_nonoverlapping_read() { + unsafe { + let layout = Layout::from_size_align(16, 8).unwrap(); + let src: *mut u8 = alloc(layout); + let dst: *mut u8 = alloc_zeroed(layout); + copy_nonoverlapping(src as *const u8, dst, 2); + // ~ERROR: Accessing `dst` here, which became uninitialized after copy. + let uninit = std::ptr::read(dst); } } @@ -35,7 +48,20 @@ fn check_copy() { let layout = Layout::from_size_align(16, 8).unwrap(); let src: *mut u8 = alloc(layout); let dst: *mut u8 = alloc(layout); - copy(src as *const u8, dst, 2); // ~ERROR: Accessing `src` here, which is uninitialized. + // This does not fail, since `copy` is untyped. + copy(src as *const u8, dst, 2); + } +} + +#[kani::proof] +fn check_copy_read() { + unsafe { + let layout = Layout::from_size_align(16, 8).unwrap(); + let src: *mut u8 = alloc(layout); + let dst: *mut u8 = alloc_zeroed(layout); + copy(src as *const u8, dst, 2); + // ~ERROR: Accessing `dst` here, which became uninitialized after copy. + let uninit = std::ptr::read(dst); } } From a5d4406642d2dde335a84bcd17c27622c403623c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:25:50 -0700 Subject: [PATCH 028/159] Bump tests/perf/s2n-quic from `ab9723a` to `80b93a7` (#3453) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `ab9723a` to `80b93a7`.
Commits

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 ab9723a772f0..80b93a7f1d18 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit ab9723a772f03a9793c9863e73c9a48fab3c5235 +Subproject commit 80b93a7f1d187fef005c8c896ab99278b6865dbe From 621519aac197692c3ff92337516d8d1a8fa52dba Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 19 Aug 2024 16:01:33 -0700 Subject: [PATCH 029/159] Make points-to analysis handle all intrinsics explicitly (#3452) Initially, points-to analysis tried to determine the body of an intrinsic (if it was available) to avoid enumerating them all. However, it turned out this logic was faulty, and the analysis attempted to query the body for intrinsics that didn't have it and ICEd. I added a couple of missing intrinsics, which had a side benefit of removing some duplicate assertion failures. Resolves #3447 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../points_to/points_to_analysis.rs | 217 +++++++++--------- tests/expected/uninit/intrinsics/expected | 8 - 2 files changed, 104 insertions(+), 121 deletions(-) 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 index 15593549b690..eff7dd1fc486 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -203,119 +203,108 @@ impl<'a, 'tcx> Analysis<'tcx> for PointsToAnalysis<'a, 'tcx> { }; match instance.def { // Intrinsics could introduce aliasing edges we care about, so need to handle them. - InstanceKind::Intrinsic(def_id) => { - // Check if the intrinsic has a body we can analyze. - if self.tcx.is_mir_available(def_id) { - self.apply_regular_call_effect(state, instance, args, destination); - } else { - // Check all of the other intrinsics. - match Intrinsic::from_instance(&rustc_internal::stable(instance)) { - intrinsic if is_identity_aliasing_intrinsic(intrinsic.clone()) => { - // Treat the intrinsic as an aggregate, taking a union of all of the - // arguments' aliases. - let destination_set = - state.resolve_place(*destination, self.instance); - let operands_set = args - .into_iter() - .flat_map(|operand| { - self.successors_for_operand(state, operand.node.clone()) - }) - .collect(); - state.extend(&destination_set, &operands_set); - } - // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. - // This is equivalent to `destination = *dst; *dst = src`. - Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { - 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`. - Intrinsic::AtomicLoad(_) => { - 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`. - Intrinsic::AtomicStore(_) => { - 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`. - Intrinsic::AtomicAnd(_) - | Intrinsic::AtomicMax(_) - | Intrinsic::AtomicMin(_) - | Intrinsic::AtomicNand(_) - | Intrinsic::AtomicOr(_) - | Intrinsic::AtomicUmax(_) - | Intrinsic::AtomicUmin(_) - | Intrinsic::AtomicXadd(_) - | Intrinsic::AtomicXchg(_) - | Intrinsic::AtomicXor(_) - | Intrinsic::AtomicXsub(_) => { - 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`. - Intrinsic::Copy => { - self.apply_copy_effect( - state, - args[0].node.clone(), - args[1].node.clone(), - ); - } - // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. - Intrinsic::VolatileCopyMemory - | Intrinsic::VolatileCopyNonOverlappingMemory => { - self.apply_copy_effect( - state, - args[1].node.clone(), - args[0].node.clone(), - ); - } - // Semantically equivalent to dest = *a - Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { - // 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. - Intrinsic::VolatileStore => { - 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); - } - Intrinsic::Unimplemented { .. } => { - // This will be taken care of at the codegen level. - } - intrinsic => { - unimplemented!( - "Kani does not support reasoning about aliasing in presence of intrinsic `{intrinsic:?}`. For more information about the state of uninitialized memory checks implementation, see: https://github.com/model-checking/kani/issues/3300." - ); - } + InstanceKind::Intrinsic(_) => { + match Intrinsic::from_instance(&rustc_internal::stable(instance)) { + intrinsic if is_identity_aliasing_intrinsic(intrinsic.clone()) => { + // Treat the intrinsic as an aggregate, taking a union of all of the + // arguments' aliases. + let destination_set = state.resolve_place(*destination, self.instance); + let operands_set = args + .into_iter() + .flat_map(|operand| { + self.successors_for_operand(state, operand.node.clone()) + }) + .collect(); + state.extend(&destination_set, &operands_set); + } + // All `atomic_cxchg` intrinsics take `dst, old, src` as arguments. + // This is equivalent to `destination = *dst; *dst = src`. + Intrinsic::AtomicCxchg(_) | Intrinsic::AtomicCxchgWeak(_) => { + 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`. + Intrinsic::AtomicLoad(_) => { + 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`. + Intrinsic::AtomicStore(_) => { + 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`. + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { + 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`. + Intrinsic::Copy => { + self.apply_copy_effect( + state, + args[0].node.clone(), + args[1].node.clone(), + ); + } + Intrinsic::TypedSwap => { + // Extend from x_set to y_set and vice-versa so that both x and y alias + // to a union of places each of them alias to. + let x_set = self.successors_for_deref(state, args[0].node.clone()); + let y_set = self.successors_for_deref(state, args[1].node.clone()); + state.extend(&x_set, &state.successors(&y_set)); + state.extend(&y_set, &state.successors(&x_set)); + } + // Similar to `copy_nonoverlapping`, argument order is `dst`, `src`, `count`. + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + self.apply_copy_effect( + state, + args[1].node.clone(), + args[0].node.clone(), + ); + } + // Semantically equivalent to dest = *a + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { + // 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. + Intrinsic::VolatileStore => { + 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); + } + Intrinsic::Unimplemented { .. } => { + // This will be taken care of at the codegen level. + } + intrinsic => { + unimplemented!( + "Kani does not support reasoning about aliasing in presence of intrinsic `{intrinsic:?}`. For more information about the state of uninitialized memory checks implementation, see: https://github.com/model-checking/kani/issues/3300." + ); } } } @@ -681,6 +670,7 @@ fn is_identity_aliasing_intrinsic(intrinsic: Intrinsic) -> bool { | Intrinsic::PtrOffsetFrom | Intrinsic::PtrOffsetFromUnsigned | Intrinsic::RawEq + | Intrinsic::RetagBoxToRaw | Intrinsic::RintF32 | Intrinsic::RintF64 | Intrinsic::RotateLeft @@ -695,6 +685,7 @@ fn is_identity_aliasing_intrinsic(intrinsic: Intrinsic) -> bool { | Intrinsic::SqrtF32 | Intrinsic::SqrtF64 | Intrinsic::SubWithOverflow + | Intrinsic::Transmute | Intrinsic::TruncF32 | Intrinsic::TruncF64 | Intrinsic::TypeId diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index e428aa605887..33392337c30b 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -2,18 +2,10 @@ std::ptr::read::>.assertion.1\ - Status: FAILURE\ - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." -std::ptr::read::>.assertion.2\ - - Status: FAILURE\ - - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." - std::ptr::write::>.assertion.1\ - Status: FAILURE\ - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." -std::ptr::write::>.assertion.2\ - - Status: FAILURE\ - - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." - check_typed_swap.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `*mut u8`" From 17dc239487c0f6277c3995bc9bf1fb560682f1b5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 20:50:33 +0200 Subject: [PATCH 030/159] Automatic cargo update to 2024-08-19 (#3450) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e8e743d5e56..931ae0bfa4fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -146,9 +146,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.15" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -175,7 +175,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", @@ -510,7 +510,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -532,9 +532,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" [[package]] name = "linear-map" @@ -950,29 +950,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.206" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.206" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", "memchr", @@ -1072,7 +1072,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1087,9 +1087,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -1126,7 +1126,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1192,7 +1192,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1311,9 +1311,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "6.0.2" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", @@ -1467,5 +1467,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] From d2141e4c5f596833d25cc89c082beec6b6889bb0 Mon Sep 17 00:00:00 2001 From: Qinheping Hu Date: Tue, 20 Aug 2024 16:18:55 -0500 Subject: [PATCH 031/159] Add loop scanner to tool-scanner (#3443) This extend #3120 with loop scanner that counts the number of loops in each function and the number of functions that contain loops. 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 | 30 ++++++++ .../tool-scanner/scanner-test.expected | 6 +- tests/script-based-pre/tool-scanner/test.rs | 29 ++++++- tools/scanner/Cargo.toml | 2 + tools/scanner/src/analysis.rs | 77 +++++++++++++++++-- 5 files changed, 132 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 931ae0bfa4fa..4f371c8356e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -349,6 +350,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "getopts" version = "0.2.21" @@ -375,6 +382,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "graph-cycles" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6ad932c6dd3cfaf16b66754a42f87bbeefd591530c4b6a8334270a7df3e853" +dependencies = [ + "ahash", + "petgraph", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -723,6 +741,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -928,6 +956,8 @@ name = "scanner" version = "0.0.0" dependencies = [ "csv", + "graph-cycles", + "petgraph", "serde", "strum", "strum_macros", diff --git a/tests/script-based-pre/tool-scanner/scanner-test.expected b/tests/script-based-pre/tool-scanner/scanner-test.expected index c8f9af0ef1b7..f55a883434ee 100644 --- a/tests/script-based-pre/tool-scanner/scanner-test.expected +++ b/tests/script-based-pre/tool-scanner/scanner-test.expected @@ -1,6 +1,6 @@ -2 test_scan_fn_loops.csv -16 test_scan_functions.csv +5 test_scan_fn_loops.csv +19 test_scan_functions.csv 5 test_scan_input_tys.csv -14 test_scan_overall.csv +16 test_scan_overall.csv 3 test_scan_recursion.csv 5 test_scan_unsafe_ops.csv diff --git a/tests/script-based-pre/tool-scanner/test.rs b/tests/script-based-pre/tool-scanner/test.rs index 24b346e535b5..f6a141f2a708 100644 --- a/tests/script-based-pre/tool-scanner/test.rs +++ b/tests/script-based-pre/tool-scanner/test.rs @@ -33,7 +33,34 @@ pub fn with_iterator(input: &[usize]) -> usize { .iter() .copied() .find(|e| *e == 0) - .unwrap_or_else(|| input.iter().fold(0, |acc, i| acc + 1)) + .unwrap_or_else(|| input.iter().fold(0, |acc, _| acc + 1)) +} + +pub fn with_for_loop(input: &[usize]) -> usize { + let mut res = 0; + for _ in input { + res += 1; + } + res +} + +pub fn with_while_loop(input: &[usize]) -> usize { + let mut res = 0; + while res < input.len() { + res += 1; + } + return res; +} + +pub fn with_loop_loop(input: &[usize]) -> usize { + let mut res = 0; + loop { + if res == input.len() { + break; + } + res += 1; + } + res } static mut COUNTER: Option = Some(0); diff --git a/tools/scanner/Cargo.toml b/tools/scanner/Cargo.toml index edbd330bea47..f27e9e06c72c 100644 --- a/tools/scanner/Cargo.toml +++ b/tools/scanner/Cargo.toml @@ -15,6 +15,8 @@ csv = "1.3" serde = {version = "1", features = ["derive"]} strum = "0.26" strum_macros = "0.26" +petgraph = "0.6.5" +graph-cycles = "0.1.0" [package.metadata.rust-analyzer] # This crate uses rustc crates. diff --git a/tools/scanner/src/analysis.rs b/tools/scanner/src/analysis.rs index c376af9662f8..b32627939bf5 100644 --- a/tools/scanner/src/analysis.rs +++ b/tools/scanner/src/analysis.rs @@ -5,11 +5,13 @@ use crate::info; use csv::WriterBuilder; +use graph_cycles::Cycles; +use petgraph::graph::Graph; 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, + BasicBlock, 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}; @@ -159,6 +161,7 @@ impl OverallStats { pub fn loops(&mut self, filename: PathBuf) { let all_items = stable_mir::all_local_items(); let (has_loops, no_loops) = all_items + .clone() .into_iter() .filter_map(|item| { let kind = item.ty().kind(); @@ -168,9 +171,37 @@ impl OverallStats { 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); + + let (has_iterators, no_iterators) = all_items + .clone() + .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_iterators()); + + let (has_either, _) = 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_iterators() || props.has_loops()); + + self.counters.extend_from_slice(&[ + ("has_loops", has_loops.len()), + ("no_loops", no_loops.len()), + ("has_iterators", has_iterators.len()), + ("no_iterators", no_iterators.len()), + ]); + dump_csv(filename, &has_either); } /// Create a callgraph for this crate and try to find recursive calls. @@ -436,21 +467,26 @@ impl<'a> MirVisitor for BodyVisitor<'a> { fn_props! { struct FnLoops { iterators, - nested_loops, - /// TODO: Collect loops. loops, + // TODO: Collect nested loops. + nested_loops, } } impl FnLoops { pub fn collect(self, body: &Body) -> FnLoops { - let mut visitor = IteratorVisitor { props: self, body }; + let mut visitor = + IteratorVisitor { props: self, body, graph: Vec::new(), current_bbidx: 0 }; visitor.visit_body(body); visitor.props } pub fn has_loops(&self) -> bool { - (self.iterators + self.loops + self.nested_loops) > 0 + (self.loops + self.nested_loops) > 0 + } + + pub fn has_iterators(&self) -> bool { + (self.iterators) > 0 } } @@ -461,12 +497,36 @@ impl FnLoops { struct IteratorVisitor<'a> { props: FnLoops, body: &'a Body, + graph: Vec<(u32, u32)>, + current_bbidx: u32, } impl<'a> MirVisitor for IteratorVisitor<'a> { + fn visit_body(&mut self, body: &Body) { + // First visit the body to build the control flow graph + self.super_body(body); + // Build the petgraph from the adj vec + let g = Graph::<(), ()>::from_edges(self.graph.clone()); + self.props.loops += g.cycles().len(); + } + + fn visit_basic_block(&mut self, bb: &BasicBlock) { + self.current_bbidx = self.body.blocks.iter().position(|b| *b == *bb).unwrap() as u32; + self.super_basic_block(bb); + } + fn visit_terminator(&mut self, term: &Terminator, location: Location) { + // Add edges between basic block into the adj table + let successors = term.kind.successors(); + for target in successors { + self.graph.push((self.current_bbidx, target as u32)); + } + if let TerminatorKind::Call { func, .. } = &term.kind { let kind = func.ty(self.body.locals()).unwrap().kind(); + // Check if the target is a visited block. + + // Check if the call is an iterator function that contains loops. if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = kind { let fullname = def.name(); let names = fullname.split("::").collect::>(); @@ -505,6 +565,7 @@ impl<'a> MirVisitor for IteratorVisitor<'a> { } } } + self.super_terminator(term, location) } } From 0ed9a624244e666232e402bb4abdce864bae7f41 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Thu, 22 Aug 2024 12:37:59 -0400 Subject: [PATCH 032/159] Avoid corner-cases by grouping instrumentation into basic blocks and using backward iteration (#3438) This PR is a follow-up to https://github.com/model-checking/kani/pull/3374. Its main purpose is to properly handle a corner-case when multiple instrumentation instructions are added to the same instruction and not all of them are skipped during subsequent instrumentation. For example, if instrumentation is added after a terminator, a new basic block will be created, containing the added instrumentation. However, we currently only mark the first statement (or the terminator, if there are none) of the new basic block as skipped for subsequent instrumentation. That means that if instrumentation in this case contains some instrumentation targets itself, it will never terminate. Coincidentally, this is not currently the case, but could lead to subtle bugs if we decide to change instrumentation. In fact, this bug was only surfaced when I experimented with checking all memory accesses, which introduced significantly more checks. Ultimately, this shows that the current way to avoiding instrumentation is limited and needs to be changed. The PR introduces the following changes: - Group instrumentation into separate basic blocks instead of adding instructions one-by-one. - Use backward iteration to avoid having to reason about which instructions need to be skipped. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../src/kani_middle/transform/body.rs | 14 +- .../delayed_ub/instrumentation_visitor.rs | 88 +-- .../transform/check_uninit/delayed_ub/mod.rs | 23 +- .../kani_middle/transform/check_uninit/mod.rs | 322 +++++------ .../transform/check_uninit/ptr_uninit/mod.rs | 16 +- .../check_uninit/ptr_uninit/uninit_visitor.rs | 525 +++++++++--------- .../check_uninit/relevant_instruction.rs | 67 ++- .../kani_middle/transform/kani_intrinsics.rs | 134 +++-- .../uninit/multiple-instrumentations.expected | 20 + .../uninit/multiple-instrumentations.rs | 42 ++ 10 files changed, 697 insertions(+), 554 deletions(-) create mode 100644 tests/expected/uninit/multiple-instrumentations.expected create mode 100644 tests/expected/uninit/multiple-instrumentations.rs diff --git a/kani-compiler/src/kani_middle/transform/body.rs b/kani-compiler/src/kani_middle/transform/body.rs index fa4e5eb1ad97..24fe5d8b2b2e 100644 --- a/kani-compiler/src/kani_middle/transform/body.rs +++ b/kani-compiler/src/kani_middle/transform/body.rs @@ -310,16 +310,16 @@ impl MutableBody { // Update the source to point at the terminator. *source = SourceInstruction::Terminator { bb: orig_bb }; } - // Make the terminator at `source` point at the new block, - // the terminator of which is a simple Goto instruction. + // Make the terminator at `source` point at the new block, the terminator of which is + // provided by the caller. SourceInstruction::Terminator { bb } => { let current_term = &mut self.blocks.get_mut(*bb).unwrap().terminator; let target_bb = get_mut_target_ref(current_term); let new_target_bb = get_mut_target_ref(&mut new_term); - // Set the new terminator to point where previous terminator pointed. - *new_target_bb = *target_bb; - // Point the current terminator to the new terminator's basic block. - *target_bb = new_bb_idx; + // Swap the targets of the newly inserted terminator and the original one. This is + // an easy way to make the original terminator point to the new basic block with the + // new terminator. + std::mem::swap(new_target_bb, target_bb); // Update the source to point at the terminator. *bb = new_bb_idx; self.blocks.push(BasicBlock { statements: vec![], terminator: new_term }); @@ -484,7 +484,7 @@ impl CheckType { } /// We store the index of an instruction to avoid borrow checker issues and unnecessary copies. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum SourceInstruction { Statement { idx: usize, bb: BasicBlockIdx }, Terminator { bb: BasicBlockIdx }, 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 index f295fc76d4bf..25059297d3d5 100644 --- 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 @@ -18,20 +18,15 @@ use rustc_middle::ty::TyCtxt; use stable_mir::mir::{ mono::Instance, visit::{Location, PlaceContext}, - BasicBlockIdx, MirVisitor, Operand, Place, Rvalue, Statement, Terminator, + 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, + /// All target instructions in the body. + targets: Vec, + /// Current analysis target, eventually needs to be added to a list of all targets. + current_target: InitRelevantInstruction, /// Aliasing analysis data. points_to: &'a PointsToGraph<'tcx>, /// The list of places we should be looking for, ignoring others @@ -41,17 +36,16 @@ pub struct InstrumentationVisitor<'a, '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() + fn find_all(mut self, body: &MutableBody) -> Vec { + for (bb_idx, bb) in body.blocks().iter().enumerate() { + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: bb_idx }, + before_instruction: vec![], + after_instruction: vec![], + }; + self.visit_basic_block(bb); + } + self.targets } } @@ -63,43 +57,55 @@ impl<'a, 'tcx> InstrumentationVisitor<'a, 'tcx> { tcx: TyCtxt<'tcx>, ) -> Self { Self { - skip_next: false, - current: SourceInstruction::Statement { idx: 0, bb: 0 }, - target: None, + targets: vec![], + current_target: InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: 0 }, + before_instruction: vec![], + after_instruction: vec![], + }, 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); + self.current_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); - } + 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 }; + if let SourceInstruction::Statement { idx, bb } = self.current_target.source { + self.targets.push(self.current_target.clone()); + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Statement { idx: idx + 1, bb }, + after_instruction: vec![], + before_instruction: vec![], + }; + } else { + unreachable!() + } } 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); + if let SourceInstruction::Statement { bb, .. } = self.current_target.source { + // We don't have to push the previous target, since it already happened in the statement + // handling code. + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Terminator { bb }, + after_instruction: vec![], + before_instruction: vec![], + }; + } else { + unreachable!() } + self.super_terminator(term, location); + // Push the current target from the terminator onto the list. + self.targets.push(self.current_target.clone()); } fn visit_rvalue(&mut self, rvalue: &Rvalue, location: 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 index 6b488569813f..e179947d2777 100644 --- 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 @@ -11,9 +11,8 @@ use crate::kani_middle::{ points_to::{run_points_to_analysis, MemLoc, PointsToGraph}, reachability::CallGraph, transform::{ - body::{CheckType, MutableBody}, - check_uninit::UninitInstrumenter, - BodyTransformation, GlobalPass, TransformationResult, + body::CheckType, check_uninit::UninitInstrumenter, BodyTransformation, GlobalPass, + TransformationResult, }, }; use crate::kani_queries::QueryDb; @@ -112,12 +111,8 @@ impl GlobalPass for DelayedUbPass { // 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)); + let body = transformer.body(tcx, instance); // Instrument for delayed UB. let target_finder = InstrumentationVisitor::new( &global_points_to_graph, @@ -125,12 +120,18 @@ impl GlobalPass for DelayedUbPass { instance, tcx, ); - let (instrumentation_added, body) = - instrumenter.instrument(tcx, body, instance, target_finder); + let (instrumentation_added, body) = UninitInstrumenter::run( + body, + tcx, + instance, + self.check_type.clone(), + &mut self.mem_init_fn_cache, + 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()); + *transformation_result = TransformationResult::Modified(body); }); } } 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 0a0d3c786ea9..88d906aa3134 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -13,13 +13,13 @@ use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::{ mir::{ - mono::Instance, AggregateKind, BasicBlockIdx, ConstOperand, Mutability, Operand, Place, - Rvalue, + mono::Instance, AggregateKind, BasicBlock, Body, ConstOperand, Mutability, Operand, Place, + Rvalue, Statement, StatementKind, Terminator, TerminatorKind, UnwindAction, }, ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind, UintTy}, CrateDef, }; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; pub use delayed_ub::DelayedUbPass; pub use ptr_uninit::UninitPass; @@ -31,13 +31,8 @@ mod relevant_instruction; mod ty_layout; /// 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; +pub trait TargetFinder { + fn find_all(self, body: &MutableBody) -> Vec; } // Function bodies of those functions will not be instrumented as not to cause infinite recursion. @@ -53,27 +48,42 @@ const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ ]; /// Instruments the code with checks for uninitialized memory, agnostic to the source of targets. -#[derive(Debug)] -pub struct UninitInstrumenter<'a> { - pub check_type: CheckType, +pub struct UninitInstrumenter<'a, 'tcx> { + check_type: CheckType, /// Used to cache FnDef lookups of injected memory initialization functions. - pub mem_init_fn_cache: &'a mut HashMap<&'static str, FnDef>, + mem_init_fn_cache: &'a mut HashMap<&'static str, FnDef>, + tcx: TyCtxt<'tcx>, } -impl<'a> UninitInstrumenter<'a> { +impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { + /// Create the instrumenter and run it with the given parameters. + pub(crate) fn run( + body: Body, + tcx: TyCtxt<'tcx>, + instance: Instance, + check_type: CheckType, + mem_init_fn_cache: &'a mut HashMap<&'static str, FnDef>, + target_finder: impl TargetFinder, + ) -> (bool, Body) { + let mut instrumenter = Self { check_type, mem_init_fn_cache, tcx }; + let body = MutableBody::from(body); + let (changed, new_body) = instrumenter.instrument(body, instance, target_finder); + (changed, new_body.into()) + } + /// 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, + 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 - .get_diagnostic_name(rustc_internal::internal(tcx, instance.def.def_id())) + if self + .tcx + .get_diagnostic_name(rustc_internal::internal(self.tcx, instance.def.def_id())) .map(|diagnostic_name| { SKIPPED_DIAGNOSTIC_ITEMS.contains(&diagnostic_name.to_ident_string().as_str()) }) @@ -83,28 +93,9 @@ impl<'a> UninitInstrumenter<'a> { } let orig_len = body.blocks().len(); - - // Set of basic block indices for which analyzing first statement should be skipped. - // - // This is necessary because some checks are inserted before the source instruction, which, in - // turn, gets moved to the next basic block. Hence, we would not need to look at the - // instruction again as a part of new basic block. However, if the check is inserted after the - // source instruction, we still need to look at the first statement of the new basic block, so - // we need to keep track of which basic blocks were created as a part of injecting checks after - // the source instruction. - let mut skip_first = HashSet::new(); - - // Do not cache body.blocks().len() since it will change as we add new checks. - let mut bb_idx = 0; - while bb_idx < body.blocks().len() { - if let Some(candidate) = - target_finder.find_next(&body, bb_idx, skip_first.contains(&bb_idx)) - { - self.build_check_for_instruction(tcx, &mut body, candidate, &mut skip_first); - bb_idx += 1 - } else { - bb_idx += 1; - }; + for instruction in target_finder.find_all(&body).into_iter().rev() { + let source = instruction.source; + self.build_check_for_instruction(&mut body, instruction, source); } (orig_len != body.blocks().len(), body) } @@ -112,40 +103,38 @@ impl<'a> UninitInstrumenter<'a> { /// Inject memory initialization checks for each operation in an instruction. fn build_check_for_instruction( &mut self, - tcx: TyCtxt, body: &mut MutableBody, instruction: InitRelevantInstruction, - skip_first: &mut HashSet, + mut source: SourceInstruction, ) { - let mut source = instruction.source; for operation in instruction.before_instruction { - self.build_check_for_operation(tcx, body, &mut source, operation, skip_first); + self.build_check_for_operation(body, &mut source, operation); } for operation in instruction.after_instruction { - self.build_check_for_operation(tcx, body, &mut source, operation, skip_first); + self.build_check_for_operation(body, &mut source, operation); } } /// Inject memory initialization check for an operation. fn build_check_for_operation( &mut self, - tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, operation: MemoryInitOp, - skip_first: &mut HashSet, ) { if let MemoryInitOp::Unsupported { reason } | MemoryInitOp::TriviallyUnsafe { reason } = &operation { - collect_skipped(&operation, body, skip_first); - self.inject_assert_false(tcx, body, source, operation.position(), reason); + // If the operation is unsupported or trivially accesses uninitialized memory, encode + // the check as `assert!(false)`. + self.inject_assert_false(self.tcx, body, source, operation.position(), reason); return; }; - let pointee_ty_info = { - let ptr_operand = operation.mk_operand(body, source); - let ptr_operand_ty = ptr_operand.ty(body.locals()).unwrap(); + let pointee_info = { + // Sanity check: since CBMC memory object primitives only accept pointers, need to + // ensure the correct type. + let ptr_operand_ty = operation.operand_ty(body); let pointee_ty = match ptr_operand_ty.kind() { TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) => pointee_ty, _ => { @@ -154,58 +143,55 @@ impl<'a> UninitInstrumenter<'a> { ) } }; + // Calculate pointee layout for byte-by-byte memory initialization checks. match PointeeInfo::from_ty(pointee_ty) { Ok(type_info) => type_info, Err(_) => { let reason = format!( "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}.", ); - collect_skipped(&operation, body, skip_first); - self.inject_assert_false(tcx, body, source, operation.position(), &reason); + self.inject_assert_false(self.tcx, body, source, operation.position(), &reason); return; } } }; - match operation { + match &operation { MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::Check { .. } | MemoryInitOp::CheckRef { .. } => { - self.build_get_and_check(tcx, body, source, operation, pointee_ty_info, skip_first) + self.build_get_and_check(body, source, operation, pointee_info) } MemoryInitOp::SetSliceChunk { .. } | MemoryInitOp::Set { .. } - | MemoryInitOp::SetRef { .. } => { - self.build_set(tcx, body, source, operation, pointee_ty_info, skip_first) - } - MemoryInitOp::Copy { .. } => { - self.build_copy(tcx, body, source, operation, pointee_ty_info, skip_first) - } + | MemoryInitOp::SetRef { .. } => self.build_set(body, source, operation, pointee_info), + MemoryInitOp::Copy { .. } => self.build_copy(body, source, operation, pointee_info), MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() } - } + }; } /// Inject a load from memory initialization state and an assertion that all non-padding bytes /// are initialized. fn build_get_and_check( &mut self, - tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, operation: MemoryInitOp, pointee_info: PointeeInfo, - skip_first: &mut HashSet, ) { let ret_place = Place { local: body.new_local(Ty::bool_ty(), source.span(body.blocks()), Mutability::Not), projection: vec![], }; - let ptr_operand = operation.mk_operand(body, source); - match pointee_info.layout() { + // Instead of injecting the instrumentation immediately, collect it into a list of + // statements and a terminator to construct a basic block and inject it at the end. + let mut statements = vec![]; + let ptr_operand = operation.mk_operand(body, &mut statements, source); + let terminator = match pointee_info.layout() { PointeeLayout::Sized { layout } => { - let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); + let layout_operand = mk_layout_operand(body, &mut statements, source, &layout); // Depending on whether accessing the known number of elements in the slice, need to // pass is as an argument. let (diagnostic, args) = match &operation { @@ -223,18 +209,24 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let is_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); - collect_skipped(&operation, body, skip_first); - body.insert_call( - &is_ptr_initialized_instance, - source, - operation.position(), - args, - ret_place.clone(), - ); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + is_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args, + destination: ret_place.clone(), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } } PointeeLayout::Slice { element_layout } => { // Since `str`` is a separate type, need to differentiate between [T] and str. @@ -248,32 +240,39 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let is_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), element_layout.len(), slicee_ty, ); let layout_operand = - mk_layout_operand(body, source, operation.position(), &element_layout); - collect_skipped(&operation, body, skip_first); - body.insert_call( - &is_ptr_initialized_instance, - source, - operation.position(), - vec![ptr_operand.clone(), layout_operand], - ret_place.clone(), - ); + mk_layout_operand(body, &mut statements, source, &element_layout); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + is_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args: vec![ptr_operand.clone(), layout_operand], + destination: ret_place.clone(), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } } PointeeLayout::TraitObject => { - collect_skipped(&operation, body, skip_first); let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects."; - self.inject_assert_false(tcx, body, source, operation.position(), reason); + self.inject_assert_false(self.tcx, body, source, operation.position(), reason); return; } }; - // Make sure all non-padding bytes are initialized. - collect_skipped(&operation, body, skip_first); - // Find the real operand type for a good error message. + // Construct the basic block and insert it into the body. + body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); + + // Since the check involves a terminator, we cannot add it to the previously constructed + // basic block. Instead, we insert the check after the basic block. let operand_ty = match &operation { MemoryInitOp::Check { operand } | MemoryInitOp::CheckSliceChunk { operand, .. } @@ -281,7 +280,7 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; body.insert_check( - tcx, + self.tcx, &self.check_type, source, operation.position(), @@ -296,23 +295,24 @@ impl<'a> UninitInstrumenter<'a> { /// non-padding bytes. fn build_set( &mut self, - tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, operation: MemoryInitOp, pointee_info: PointeeInfo, - skip_first: &mut HashSet, ) { let ret_place = Place { local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), projection: vec![], }; - let ptr_operand = operation.mk_operand(body, source); - let value = operation.expect_value(); - match pointee_info.layout() { + // Instead of injecting the instrumentation immediately, collect it into a list of + // statements and a terminator to construct a basic block and inject it at the end. + let mut statements = vec![]; + let ptr_operand = operation.mk_operand(body, &mut statements, source); + let value = operation.expect_value(); + let terminator = match pointee_info.layout() { PointeeLayout::Sized { layout } => { - let layout_operand = mk_layout_operand(body, source, operation.position(), &layout); + let layout_operand = mk_layout_operand(body, &mut statements, source, &layout); // Depending on whether writing to the known number of elements in the slice, need to // pass is as an argument. let (diagnostic, args) = match &operation { @@ -346,18 +346,24 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let set_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); - collect_skipped(&operation, body, skip_first); - body.insert_call( - &set_ptr_initialized_instance, - source, - operation.position(), - args, - ret_place, - ); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + set_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args, + destination: ret_place.clone(), + target: Some(0), // this will be overriden in add_bb + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } } PointeeLayout::Slice { element_layout } => { // Since `str`` is a separate type, need to differentiate between [T] and str. @@ -371,44 +377,50 @@ impl<'a> UninitInstrumenter<'a> { _ => unreachable!(), }; let set_ptr_initialized_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, diagnostic, &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), element_layout.len(), slicee_ty, ); let layout_operand = - mk_layout_operand(body, source, operation.position(), &element_layout); - collect_skipped(&operation, body, skip_first); - body.insert_call( - &set_ptr_initialized_instance, - source, - operation.position(), - vec![ - ptr_operand, - layout_operand, - Operand::Constant(ConstOperand { - span: source.span(body.blocks()), - user_ty: None, - const_: MirConst::from_bool(value), - }), - ], - ret_place, - ); + mk_layout_operand(body, &mut statements, source, &element_layout); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + set_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args: vec![ + ptr_operand, + layout_operand, + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(value), + }), + ], + destination: ret_place.clone(), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } } PointeeLayout::TraitObject => { unreachable!("Cannot change the initialization state of a trait object directly."); } }; + // Construct the basic block and insert it into the body. + body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); } /// Copy memory initialization state from one pointer to the other. fn build_copy( &mut self, - tcx: TyCtxt, body: &mut MutableBody, source: &mut SourceInstruction, operation: MemoryInitOp, pointee_info: PointeeInfo, - skip_first: &mut HashSet, ) { let ret_place = Place { local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), @@ -416,11 +428,10 @@ impl<'a> UninitInstrumenter<'a> { }; let PointeeLayout::Sized { layout } = pointee_info.layout() else { unreachable!() }; let copy_init_state_instance = resolve_mem_init_fn( - get_mem_init_fn_def(tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache), + get_mem_init_fn_def(self.tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache), layout.len(), *pointee_info.ty(), ); - collect_skipped(&operation, body, skip_first); let position = operation.position(); let MemoryInitOp::Copy { from, to, count } = operation else { unreachable!() }; body.insert_call( @@ -465,39 +476,30 @@ impl<'a> UninitInstrumenter<'a> { /// will have the following byte mask `[true, true, true, false]`. pub fn mk_layout_operand( body: &mut MutableBody, + statements: &mut Vec, source: &mut SourceInstruction, - position: InsertPosition, layout_byte_mask: &[bool], ) -> Operand { - Operand::Move(Place { - local: body.insert_assignment( - Rvalue::Aggregate( - AggregateKind::Array(Ty::bool_ty()), - layout_byte_mask - .iter() - .map(|byte| { - Operand::Constant(ConstOperand { - span: source.span(body.blocks()), - user_ty: None, - const_: MirConst::from_bool(*byte), - }) - }) - .collect(), - ), - source, - position, - ), - projection: vec![], - }) -} + let span = source.span(body.blocks()); + let rvalue = Rvalue::Aggregate( + AggregateKind::Array(Ty::bool_ty()), + layout_byte_mask + .iter() + .map(|byte| { + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(*byte), + }) + }) + .collect(), + ); + let ret_ty = rvalue.ty(body.locals()).unwrap(); + let result = body.new_local(ret_ty, span, Mutability::Not); + let stmt = Statement { kind: StatementKind::Assign(Place::from(result), rvalue), span }; + statements.push(stmt); -/// If injecting a new call to the function before the current statement, need to skip the original -/// statement when analyzing it as a part of the new basic block. -fn collect_skipped(operation: &MemoryInitOp, body: &MutableBody, skip_first: &mut HashSet) { - if operation.position() == InsertPosition::Before { - let new_bb_idx = body.blocks().len(); - skip_first.insert(new_bb_idx); - } + Operand::Move(Place { local: result, projection: vec![] }) } /// Retrieve a function definition by diagnostic string, caching the result. 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 index af2753ea7175..37f1d94ed744 100644 --- 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 @@ -62,14 +62,16 @@ impl TransformPass for UninitPass { } // 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()); + let (instrumentation_added, body) = UninitInstrumenter::run( + new_body.into(), + tcx, + instance, + self.check_type.clone(), + &mut self.mem_init_fn_cache, + CheckUninitVisitor::new(), + ); - (changed || instrumentation_added, body.into()) + (changed || instrumentation_added, body) } } diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index 1afb151a09b5..a1689bdfe8c4 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -19,42 +19,32 @@ use stable_mir::{ alloc::GlobalAlloc, mono::{Instance, InstanceKind}, visit::{Location, PlaceContext}, - BasicBlockIdx, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, - PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, - TerminatorKind, + CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, PointerCoercion, + ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }, ty::{ConstantKind, RigidTy, TyKind}, }; 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 - /// later. - skip_next: bool, - /// The instruction being visited at a given point. - current: SourceInstruction, - /// The target instruction that should be verified. - pub target: Option, - /// The basic block being visited. - bb: BasicBlockIdx, + /// All target instructions in the body. + targets: Vec, + /// Current analysis target, eventually needs to be added to a list of all targets. + current_target: InitRelevantInstruction, } impl TargetFinder for CheckUninitVisitor { - fn find_next( - &mut self, - body: &MutableBody, - bb: BasicBlockIdx, - skip_first: bool, - ) -> Option { + fn find_all(mut self, body: &MutableBody) -> Vec { 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() + for (bb_idx, bb) in body.blocks().iter().enumerate() { + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: bb_idx }, + before_instruction: vec![], + after_instruction: vec![], + }; + self.visit_basic_block(bb); + } + self.targets } } @@ -62,286 +52,289 @@ impl CheckUninitVisitor { pub fn new() -> Self { Self { locals: vec![], - skip_next: false, - current: SourceInstruction::Statement { idx: 0, bb: 0 }, - target: None, - bb: 0, + targets: vec![], + current_target: InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: 0 }, + before_instruction: vec![], + after_instruction: vec![], + }, } } 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); + self.current_target.push_operation(source_op); } } impl MirVisitor for CheckUninitVisitor { fn visit_statement(&mut self, stmt: &Statement, location: Location) { - if self.skip_next { - self.skip_next = false; - } else if self.target.is_none() { - // Leave it as an exhaustive match to be notified when a new kind is added. - match &stmt.kind { - StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => { - self.super_statement(stmt, location); - // The copy is untyped, so we should copy memory initialization state from `src` - // to `dst`. - self.push_target(MemoryInitOp::Copy { - from: copy.src.clone(), - to: copy.dst.clone(), - count: copy.count.clone(), - }); - } - StatementKind::Assign(place, rvalue) => { - // First check rvalue. - self.visit_rvalue(rvalue, location); - // Check whether we are assigning into a dereference (*ptr = _). - if let Some(place_without_deref) = try_remove_topmost_deref(place) { - // First, check that we are not dereferencing extra pointers along the way - // (e.g., **ptr = _). If yes, check whether these pointers are initialized. - let mut place_to_add_projections = - Place { local: place_without_deref.local, projection: vec![] }; - for projection_elem in place_without_deref.projection.iter() { - // If the projection is Deref and the current type is raw pointer, check - // 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() - { - self.push_target(MemoryInitOp::Check { - operand: Operand::Copy(place_to_add_projections.clone()), - }); - }; - } - place_to_add_projections.projection.push(projection_elem.clone()); - } - 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, - position: InsertPosition::After, - }); + // Leave it as an exhaustive match to be notified when a new kind is added. + match &stmt.kind { + StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => { + self.super_statement(stmt, location); + // The copy is untyped, so we should copy memory initialization state from `src` + // to `dst`. + self.push_target(MemoryInitOp::Copy { + from: copy.src.clone(), + to: copy.dst.clone(), + count: copy.count.clone(), + }); + } + StatementKind::Assign(place, rvalue) => { + // First check rvalue. + self.visit_rvalue(rvalue, location); + // Check whether we are assigning into a dereference (*ptr = _). + if let Some(place_without_deref) = try_remove_topmost_deref(place) { + // First, check that we are not dereferencing extra pointers along the way + // (e.g., **ptr = _). If yes, check whether these pointers are initialized. + let mut place_to_add_projections = + Place { local: place_without_deref.local, projection: vec![] }; + for projection_elem in place_without_deref.projection.iter() { + // If the projection is Deref and the current type is raw pointer, check + // 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() + { + self.push_target(MemoryInitOp::Check { + operand: Operand::Copy(place_to_add_projections.clone()), + }); + }; } + place_to_add_projections.projection.push(projection_elem.clone()); } - // 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 let Rvalue::AddressOf(..) = rvalue { - self.push_target(MemoryInitOp::Set { - operand: Operand::Copy(place.clone()), - value: true, - position: InsertPosition::After, - }); - } + 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, + position: InsertPosition::After, + }); } } - StatementKind::Deinit(place) => { - self.super_statement(stmt, location); - self.push_target(MemoryInitOp::Set { - operand: Operand::Copy(place.clone()), - value: false, - position: InsertPosition::After, - }); + // 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 let Rvalue::AddressOf(..) = rvalue { + self.push_target(MemoryInitOp::Set { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } } - StatementKind::FakeRead(_, _) - | StatementKind::SetDiscriminant { .. } - | StatementKind::StorageLive(_) - | StatementKind::StorageDead(_) - | StatementKind::Retag(_, _) - | StatementKind::PlaceMention(_) - | StatementKind::AscribeUserType { .. } - | StatementKind::Coverage(_) - | StatementKind::ConstEvalCounter - | StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(_)) - | StatementKind::Nop => self.super_statement(stmt, location), } + StatementKind::Deinit(place) => { + self.super_statement(stmt, location); + self.push_target(MemoryInitOp::Set { + operand: Operand::Copy(place.clone()), + value: false, + position: InsertPosition::After, + }); + } + StatementKind::FakeRead(_, _) + | StatementKind::SetDiscriminant { .. } + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Retag(_, _) + | StatementKind::PlaceMention(_) + | StatementKind::AscribeUserType { .. } + | StatementKind::Coverage(_) + | StatementKind::ConstEvalCounter + | StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(_)) + | StatementKind::Nop => self.super_statement(stmt, location), + } + // Switch to the next statement. + if let SourceInstruction::Statement { idx, bb } = self.current_target.source { + self.targets.push(self.current_target.clone()); + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Statement { idx: idx + 1, bb }, + after_instruction: vec![], + before_instruction: vec![], + }; + } else { + unreachable!() } - 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.bb }; - // Leave it as an exhaustive match to be notified when a new kind is added. - match &term.kind { - TerminatorKind::Call { func, args, destination, .. } => { - self.super_terminator(term, location); - let instance = match try_resolve_instance(&self.locals, func) { - Ok(instance) => instance, - Err(reason) => { - self.super_terminator(term, location); - self.push_target(MemoryInitOp::Unsupported { reason }); - return; + if let SourceInstruction::Statement { bb, .. } = self.current_target.source { + // We don't have to push the previous target, since it already happened in the statement + // handling code. + self.current_target = InitRelevantInstruction { + source: SourceInstruction::Terminator { bb }, + after_instruction: vec![], + before_instruction: vec![], + }; + } else { + unreachable!() + } + // Leave it as an exhaustive match to be notified when a new kind is added. + match &term.kind { + TerminatorKind::Call { func, args, destination, .. } => { + self.super_terminator(term, location); + let instance = match try_resolve_instance(&self.locals, func) { + Ok(instance) => instance, + Err(reason) => { + self.super_terminator(term, location); + self.push_target(MemoryInitOp::Unsupported { reason }); + return; + } + }; + match instance.kind { + InstanceKind::Intrinsic => { + match Intrinsic::from_instance(&instance) { + intrinsic_name if can_skip_intrinsic(intrinsic_name.clone()) => { + /* Intrinsics that can be safely skipped */ + } + Intrinsic::AtomicAnd(_) + | Intrinsic::AtomicCxchg(_) + | Intrinsic::AtomicCxchgWeak(_) + | Intrinsic::AtomicLoad(_) + | Intrinsic::AtomicMax(_) + | Intrinsic::AtomicMin(_) + | Intrinsic::AtomicNand(_) + | Intrinsic::AtomicOr(_) + | Intrinsic::AtomicStore(_) + | Intrinsic::AtomicUmax(_) + | Intrinsic::AtomicUmin(_) + | Intrinsic::AtomicXadd(_) + | Intrinsic::AtomicXchg(_) + | Intrinsic::AtomicXor(_) + | Intrinsic::AtomicXsub(_) => { + self.push_target(MemoryInitOp::Check { operand: args[0].clone() }); + } + Intrinsic::CompareBytes => { + self.push_target(MemoryInitOp::CheckSliceChunk { + operand: args[0].clone(), + count: args[2].clone(), + }); + self.push_target(MemoryInitOp::CheckSliceChunk { + operand: args[1].clone(), + count: args[2].clone(), + }); + } + Intrinsic::Copy => { + // The copy is untyped, so we should copy memory + // initialization state from `src` to `dst`. + self.push_target(MemoryInitOp::Copy { + from: args[0].clone(), + to: args[1].clone(), + count: args[2].clone(), + }); + } + Intrinsic::VolatileCopyMemory + | Intrinsic::VolatileCopyNonOverlappingMemory => { + // The copy is untyped, so we should copy initialization state + // from `src` to `dst`. Note that the `dst` comes before `src` + // in this case. + self.push_target(MemoryInitOp::Copy { + from: args[1].clone(), + to: args[0].clone(), + count: args[2].clone(), + }); + } + Intrinsic::TypedSwap => { + self.push_target(MemoryInitOp::Check { operand: args[0].clone() }); + self.push_target(MemoryInitOp::Check { operand: args[1].clone() }); + } + Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { + self.push_target(MemoryInitOp::Check { operand: args[0].clone() }); + } + Intrinsic::VolatileStore => { + self.push_target(MemoryInitOp::Set { + operand: args[0].clone(), + value: true, + position: InsertPosition::After, + }); + } + Intrinsic::WriteBytes => { + self.push_target(MemoryInitOp::SetSliceChunk { + operand: args[0].clone(), + count: args[2].clone(), + value: true, + position: InsertPosition::After, + }) + } + intrinsic => { + self.push_target(MemoryInitOp::Unsupported { + reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic:?}`."), + }); + } } - }; - match instance.kind { - InstanceKind::Intrinsic => { - match Intrinsic::from_instance(&instance) { - intrinsic_name if can_skip_intrinsic(intrinsic_name.clone()) => { - /* Intrinsics that can be safely skipped */ - } - Intrinsic::AtomicAnd(_) - | Intrinsic::AtomicCxchg(_) - | Intrinsic::AtomicCxchgWeak(_) - | Intrinsic::AtomicLoad(_) - | Intrinsic::AtomicMax(_) - | Intrinsic::AtomicMin(_) - | Intrinsic::AtomicNand(_) - | Intrinsic::AtomicOr(_) - | Intrinsic::AtomicStore(_) - | Intrinsic::AtomicUmax(_) - | Intrinsic::AtomicUmin(_) - | Intrinsic::AtomicXadd(_) - | Intrinsic::AtomicXchg(_) - | Intrinsic::AtomicXor(_) - | Intrinsic::AtomicXsub(_) => { - self.push_target(MemoryInitOp::Check { - operand: args[0].clone(), - }); - } - Intrinsic::CompareBytes => { - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: args[0].clone(), - count: args[2].clone(), - }); - self.push_target(MemoryInitOp::CheckSliceChunk { - operand: args[1].clone(), - count: args[2].clone(), - }); - } - Intrinsic::Copy => { - // The copy is untyped, so we should copy memory - // initialization state from `src` to `dst`. - self.push_target(MemoryInitOp::Copy { - from: args[0].clone(), - to: args[1].clone(), - count: args[2].clone(), - }); - } - Intrinsic::VolatileCopyMemory - | Intrinsic::VolatileCopyNonOverlappingMemory => { - // The copy is untyped, so we should copy initialization state - // from `src` to `dst`. Note that the `dst` comes before `src` - // in this case. - self.push_target(MemoryInitOp::Copy { - from: args[1].clone(), - to: args[0].clone(), - count: args[2].clone(), - }); - } - Intrinsic::TypedSwap => { - self.push_target(MemoryInitOp::Check { - operand: args[0].clone(), - }); - self.push_target(MemoryInitOp::Check { - operand: args[1].clone(), - }); - } - Intrinsic::VolatileLoad | Intrinsic::UnalignedVolatileLoad => { - self.push_target(MemoryInitOp::Check { - operand: args[0].clone(), - }); + } + InstanceKind::Item => { + if instance.is_foreign_item() { + match instance.name().as_str() { + "alloc::alloc::__rust_alloc" | "alloc::alloc::__rust_realloc" => { + /* Memory is uninitialized, nothing to do here. */ } - Intrinsic::VolatileStore => { - self.push_target(MemoryInitOp::Set { - operand: args[0].clone(), + "alloc::alloc::__rust_alloc_zeroed" => { + /* Memory is initialized here, need to update shadow memory. */ + self.push_target(MemoryInitOp::SetSliceChunk { + operand: Operand::Copy(destination.clone()), + count: args[0].clone(), value: true, position: InsertPosition::After, }); } - Intrinsic::WriteBytes => { + "alloc::alloc::__rust_dealloc" => { + /* Memory is uninitialized here, need to update shadow memory. */ self.push_target(MemoryInitOp::SetSliceChunk { operand: args[0].clone(), - count: args[2].clone(), - value: true, + count: args[1].clone(), + value: false, position: InsertPosition::After, - }) - } - intrinsic => { - self.push_target(MemoryInitOp::Unsupported { - reason: format!("Kani does not support reasoning about memory initialization of intrinsic `{intrinsic:?}`."), - }); - } - } - } - InstanceKind::Item => { - if instance.is_foreign_item() { - match instance.name().as_str() { - "alloc::alloc::__rust_alloc" - | "alloc::alloc::__rust_realloc" => { - /* Memory is uninitialized, nothing to do here. */ - } - "alloc::alloc::__rust_alloc_zeroed" => { - /* Memory is initialized here, need to update shadow memory. */ - self.push_target(MemoryInitOp::SetSliceChunk { - operand: Operand::Copy(destination.clone()), - count: args[0].clone(), - value: true, - position: InsertPosition::After, - }); - } - "alloc::alloc::__rust_dealloc" => { - /* Memory is uninitialized here, need to update shadow memory. */ - self.push_target(MemoryInitOp::SetSliceChunk { - operand: args[0].clone(), - count: args[1].clone(), - value: false, - position: InsertPosition::After, - }); - } - _ => {} + }); } + _ => {} } } - _ => {} } + _ => {} } - TerminatorKind::Drop { place, .. } => { - self.super_terminator(term, location); - 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) - .unwrap() - .kind() - .rigid() - .expect("should be working with monomorphized code") - { - RigidTy::Adt(..) | RigidTy::Dynamic(_, _, _) => { - self.push_target(MemoryInitOp::SetRef { - operand: Operand::Copy(place.clone()), - value: true, - position: InsertPosition::Before, - }); - } - _ => {} - } + } + TerminatorKind::Drop { place, .. } => { + self.super_terminator(term, location); + let place_ty = place.ty(&&self.locals).unwrap(); - if place_ty.kind().is_raw_ptr() { - self.push_target(MemoryInitOp::Set { + // 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) + .unwrap() + .kind() + .rigid() + .expect("should be working with monomorphized code") + { + RigidTy::Adt(..) | RigidTy::Dynamic(_, _, _) => { + self.push_target(MemoryInitOp::SetRef { operand: Operand::Copy(place.clone()), - value: false, - position: InsertPosition::After, + value: true, + position: InsertPosition::Before, }); } + _ => {} + } + + if place_ty.kind().is_raw_ptr() { + self.push_target(MemoryInitOp::Set { + operand: Operand::Copy(place.clone()), + value: false, + position: InsertPosition::After, + }); } - TerminatorKind::Goto { .. } - | TerminatorKind::SwitchInt { .. } - | TerminatorKind::Resume - | TerminatorKind::Abort - | TerminatorKind::Return - | TerminatorKind::Unreachable - | TerminatorKind::Assert { .. } - | TerminatorKind::InlineAsm { .. } => self.super_terminator(term, location), } + TerminatorKind::Goto { .. } + | TerminatorKind::SwitchInt { .. } + | TerminatorKind::Resume + | TerminatorKind::Abort + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Assert { .. } + | TerminatorKind::InlineAsm { .. } => self.super_terminator(term, location), } + // Push the current target from the terminator onto the list. + self.targets.push(self.current_target.clone()); } fn visit_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { 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 index cc5a27e09925..05826969262d 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -6,8 +6,9 @@ use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; use stable_mir::{ - mir::{Mutability, Operand, Place, Rvalue}, + mir::{Mutability, Operand, Place, Rvalue, Statement, StatementKind}, ty::RigidTy, + ty::Ty, }; use strum_macros::AsRefStr; @@ -44,27 +45,59 @@ 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 { + pub fn mk_operand( + &self, + body: &mut MutableBody, + statements: &mut Vec, + 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![], - }) + let span = source.span(body.blocks()); + + let ref_local = { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + }; + let rvalue = Rvalue::AddressOf(Mutability::Not, place.clone()); + let ret_ty = rvalue.ty(body.locals()).unwrap(); + let result = body.new_local(ret_ty, span, Mutability::Not); + let stmt = Statement { + kind: StatementKind::Assign(Place::from(result), rvalue), + span, + }; + statements.push(stmt); + result + }; + + Operand::Copy(Place { local: ref_local, projection: vec![] }) + } + MemoryInitOp::Copy { .. } + | MemoryInitOp::Unsupported { .. } + | MemoryInitOp::TriviallyUnsafe { .. } => { + unreachable!() + } + } + } + + pub fn operand_ty(&self, body: &MutableBody) -> Ty { + match self { + MemoryInitOp::Check { operand, .. } + | MemoryInitOp::Set { operand, .. } + | MemoryInitOp::CheckSliceChunk { operand, .. } + | MemoryInitOp::SetSliceChunk { operand, .. } => operand.ty(body.locals()).unwrap(), + MemoryInitOp::SetRef { operand, .. } | MemoryInitOp::CheckRef { operand, .. } => { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + }; + let rvalue = Rvalue::AddressOf(Mutability::Not, place.clone()); + rvalue.ty(body.locals()).unwrap() } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!("operands do not exist for this operation") @@ -82,7 +115,7 @@ impl MemoryInitOp { unreachable!() }; assert!(from_pointee_ty == to_pointee_ty); - from.clone() + from.ty(body.locals()).unwrap() } } } diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index cc748c39a7f5..dd804b7379f2 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -22,8 +22,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, TerminatorKind, - RETURN_LOCAL, + BasicBlock, BinOp, Body, ConstOperand, Mutability, Operand, Place, Rvalue, Statement, + StatementKind, Terminator, TerminatorKind, UnwindAction, RETURN_LOCAL, }; use stable_mir::target::MachineInfo; use stable_mir::ty::{FnDef, MirConst, RigidTy, Ty, TyKind, UintTy}; @@ -164,30 +164,39 @@ impl IntrinsicGeneratorPass { fn is_initialized_body(&mut self, tcx: TyCtxt, body: Body) -> Body { let mut new_body = MutableBody::from(body); new_body.clear_body(TerminatorKind::Return); - - // Initialize return variable with True. let ret_var = RETURN_LOCAL; - let mut terminator = SourceInstruction::Terminator { bb: 0 }; - let span = new_body.locals()[ret_var].span; - let assign = StatementKind::Assign( - Place::from(ret_var), - Rvalue::Use(Operand::Constant(ConstOperand { - span, - user_ty: None, - const_: MirConst::from_bool(true), - })), - ); - let stmt = Statement { kind: assign, span }; - new_body.insert_stmt(stmt, &mut terminator, InsertPosition::Before); + let mut source = SourceInstruction::Terminator { bb: 0 }; + // Short-circut if uninitialized memory checks are not enabled. if !self.arguments.ub_check.contains(&ExtraChecks::Uninit) { - // Short-circut if uninitialized memory checks are not enabled. + // Initialize return variable with True. + let span = new_body.locals()[ret_var].span; + let assign = StatementKind::Assign( + Place::from(ret_var), + Rvalue::Use(Operand::Constant(ConstOperand { + span, + user_ty: None, + const_: MirConst::from_bool(true), + })), + ); + new_body.insert_stmt( + Statement { kind: assign, span }, + &mut source, + InsertPosition::Before, + ); return new_body.into(); } + // Instead of injecting the instrumentation immediately, collect it into a list of + // statements and a terminator to construct a basic block and inject it at the end. + let mut statements = vec![]; + // The first argument type. let arg_ty = new_body.locals()[1].ty; + // Sanity check: since CBMC memory object primitives only accept pointers, need to + // ensure the correct type. let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; + // Calculate pointee layout for byte-by-byte memory initialization checks. let pointee_info = PointeeInfo::from_ty(target_ty); match pointee_info { Ok(pointee_info) => { @@ -195,6 +204,21 @@ impl IntrinsicGeneratorPass { PointeeLayout::Sized { layout } => { if layout.is_empty() { // Encountered a ZST, so we can short-circut here. + // Initialize return variable with True. + let span = new_body.locals()[ret_var].span; + let assign = StatementKind::Assign( + Place::from(ret_var), + Rvalue::Use(Operand::Constant(ConstOperand { + span, + user_ty: None, + const_: MirConst::from_bool(true), + })), + ); + new_body.insert_stmt( + Statement { kind: assign, span }, + &mut source, + InsertPosition::Before, + ); return new_body.into(); } let is_ptr_initialized_instance = resolve_mem_init_fn( @@ -206,18 +230,28 @@ impl IntrinsicGeneratorPass { layout.len(), *pointee_info.ty(), ); - let layout_operand = mk_layout_operand( - &mut new_body, - &mut terminator, - InsertPosition::Before, - &layout, - ); - new_body.insert_call( - &is_ptr_initialized_instance, - &mut terminator, + let layout_operand = + mk_layout_operand(&mut new_body, &mut statements, &mut source, &layout); + + let terminator = Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(new_body.new_local( + is_ptr_initialized_instance.ty(), + source.span(new_body.blocks()), + Mutability::Not, + ))), + args: vec![Operand::Copy(Place::from(1)), layout_operand], + destination: Place::from(ret_var), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(new_body.blocks()), + }; + // Construct the basic block and insert it into the body. + new_body.insert_bb( + BasicBlock { statements, terminator }, + &mut source, InsertPosition::Before, - vec![Operand::Copy(Place::from(1)), layout_operand], - Place::from(ret_var), ); } PointeeLayout::Slice { element_layout } => { @@ -238,35 +272,45 @@ impl IntrinsicGeneratorPass { ); let layout_operand = mk_layout_operand( &mut new_body, - &mut terminator, - InsertPosition::Before, + &mut statements, + &mut source, &element_layout, ); - new_body.insert_call( - &is_ptr_initialized_instance, - &mut terminator, + let terminator = Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(new_body.new_local( + is_ptr_initialized_instance.ty(), + source.span(new_body.blocks()), + Mutability::Not, + ))), + args: vec![Operand::Copy(Place::from(1)), layout_operand], + destination: Place::from(ret_var), + target: Some(0), // The current value does not matter, since it will be overwritten in add_bb. + unwind: UnwindAction::Terminate, + }, + span: source.span(new_body.blocks()), + }; + // Construct the basic block and insert it into the body. + new_body.insert_bb( + BasicBlock { statements, terminator }, + &mut source, InsertPosition::Before, - vec![Operand::Copy(Place::from(1)), layout_operand], - Place::from(ret_var), ); } PointeeLayout::TraitObject => { let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { const_: MirConst::from_bool(false), - span, + span: source.span(new_body.blocks()), user_ty: None, })); - let result = new_body.insert_assignment( - rvalue, - &mut terminator, - InsertPosition::Before, - ); + let result = + new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); let reason: &str = "Kani does not support reasoning about memory initialization of pointers to trait objects."; new_body.insert_check( tcx, &self.check_type, - &mut terminator, + &mut source, InsertPosition::Before, result, &reason, @@ -278,18 +322,18 @@ impl IntrinsicGeneratorPass { // We failed to retrieve the type layout. let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { const_: MirConst::from_bool(false), - span, + span: source.span(new_body.blocks()), user_ty: None, })); let result = - new_body.insert_assignment(rvalue, &mut terminator, InsertPosition::Before); + new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); let reason = format!( "Kani currently doesn't support checking memory initialization of `{target_ty}`. {msg}" ); new_body.insert_check( tcx, &self.check_type, - &mut terminator, + &mut source, InsertPosition::Before, result, &reason, diff --git a/tests/expected/uninit/multiple-instrumentations.expected b/tests/expected/uninit/multiple-instrumentations.expected new file mode 100644 index 000000000000..153024dc692b --- /dev/null +++ b/tests/expected/uninit/multiple-instrumentations.expected @@ -0,0 +1,20 @@ +multiple_instrumentations_different_vars.assertion.3\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +multiple_instrumentations_different_vars.assertion.4\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u64`" + +multiple_instrumentations.assertion.2\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +multiple_instrumentations.assertion.3\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + +Summary: +Verification failed for - multiple_instrumentations_different_vars +Verification failed for - multiple_instrumentations +Complete - 0 successfully verified harnesses, 2 failures, 2 total. diff --git a/tests/expected/uninit/multiple-instrumentations.rs b/tests/expected/uninit/multiple-instrumentations.rs new file mode 100644 index 000000000000..8f61bb82b6d6 --- /dev/null +++ b/tests/expected/uninit/multiple-instrumentations.rs @@ -0,0 +1,42 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +/// Ensure instrumentation works correctly when a single instruction gets multiple instrumentations. +#[kani::proof] +fn multiple_instrumentations() { + 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); + // Here, instrumentation is added 2 times before the function call and 1 time after. + value = helper_1(value, value); + } +} + +fn helper_1(a: u128, b: u128) -> u128 { + a + b +} + +/// Ensure instrumentation works correctly when a single instruction gets multiple instrumentations +/// (and variables are different). +#[kani::proof] +fn multiple_instrumentations_different_vars() { + unsafe { + let mut a: u128 = 0; + let mut b: u64 = 0; + // Cast between two pointers of different padding. + let ptr_a = &mut a as *mut _ as *mut (u8, u32, u64); + *ptr_a = (4, 4, 4); + // Cast between two pointers of different padding. + let ptr_b = &mut b as *mut _ as *mut (u8, u32); + *ptr_b = (4, 4); + // Here, instrumentation is added 2 times before the function call and 1 time after. + a = helper_2(a, b); + } +} + +fn helper_2(a: u128, b: u64) -> u128 { + a + (b as u128) +} From 56e2a2f21f36e1f2dea5113aa907b2db8748f635 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 22 Aug 2024 11:14:13 -0700 Subject: [PATCH 033/159] Re-enabled hierarchical logs in the compiler (#3449) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We reverted the hierarchical logs a while ago (#2581) due to an outdated dependency that has since been fixed. I think this makes the logs much more readable, by indenting the logs given the scope. More than one scope makes the lines way too long, which I think it's harder to read. This is how the logs look without this change: ``` 2024-08-17T02:42:21.874979Z DEBUG CodegenFunction{name="kani::assert"}: kani_compiler::codegen_cprover_gotoc::utils::debug: handling kani::assert 2024-08-17T02:42:21.875008Z DEBUG CodegenFunction{name="kani::assert"}: kani_compiler::codegen_cprover_gotoc::utils::debug: variables: 2024-08-17T02:42:21.875026Z DEBUG CodegenFunction{name="kani::assert"}: kani_compiler::codegen_cprover_gotoc::utils::debug: let _0: Ty { id: 4, kind: RigidTy(Tuple([])) } ``` This is how it looks after this change: ``` ┐kani_compiler::codegen_cprover_gotoc::codegen::function::CodegenFunction name="kani::assert" ├─── DEBUG kani_compiler::codegen_cprover_gotoc::utils::debug handling kani::assert ├─── DEBUG kani_compiler::codegen_cprover_gotoc::utils::debug variables: ├─── DEBUG kani_compiler::codegen_cprover_gotoc::utils::debug let _0: Ty { id: 4, kind: RigidTy(Tuple([])) } ├─── DEBUG kani_compiler::codegen_cprover_gotoc::utils::debug let _1: Ty { id: 6, kind: RigidTy(Bool) } ``` 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 | 24 +++++++++++++++++++++++- kani-compiler/Cargo.toml | 1 + kani-compiler/src/session.rs | 8 ++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f371c8356e5..878d61a62889 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,6 +475,7 @@ dependencies = [ "strum_macros", "tracing", "tracing-subscriber", + "tracing-tree", ] [[package]] @@ -617,6 +618,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num" version = "0.4.3" @@ -1263,7 +1273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", - "nu-ansi-term", + "nu-ansi-term 0.46.0", "once_cell", "parking_lot", "regex", @@ -1278,6 +1288,18 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-tree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f459ca79f1b0d5f71c54ddfde6debfc59c8b6eeb46808ae492077f739dc7b49c" +dependencies = [ + "nu-ansi-term 0.50.1", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 24ed00f54338..fcb33b8074e4 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -24,6 +24,7 @@ strum_macros = "0.26" shell-words = "1.0.0" tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_debug"]} tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} +tracing-tree = "0.4.0" # Future proofing: enable backend dependencies using feature. [features] diff --git a/kani-compiler/src/session.rs b/kani-compiler/src/session.rs index 7ec2b79a0469..ecc084e0cc85 100644 --- a/kani-compiler/src/session.rs +++ b/kani-compiler/src/session.rs @@ -19,6 +19,7 @@ use std::io::IsTerminal; use std::panic; use std::sync::LazyLock; use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; +use tracing_tree::HierarchicalLayer; /// Environment variable used to control this session log tracing. const LOG_ENV_VAR: &str = "KANI_LOG"; @@ -114,10 +115,13 @@ fn hier_logs(args: &Arguments, filter: EnvFilter) { let use_colors = std::io::stdout().is_terminal() || args.color_output; let subscriber = Registry::default().with(filter); let subscriber = subscriber.with( - tracing_subscriber::fmt::layer() + HierarchicalLayer::default() .with_writer(std::io::stderr) + .with_indent_lines(true) .with_ansi(use_colors) - .with_target(true), + .with_targets(true) + .with_verbose_exit(true) + .with_indent_amount(4), ); tracing::subscriber::set_global_default(subscriber).unwrap(); } From 7a2866754c34cf6e47bb5d431469a1716f17a980 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 23 Aug 2024 08:00:52 -0700 Subject: [PATCH 034/159] Fix ICE due to mishandling of Aggregate rvalue for raw pointers to `str` (#3448) We were missing a match arm for the case where a raw pointer to a string slice was created from a thin pointer and the string size. Resolves #3312 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/rvalue.rs | 8 ++++++++ tests/kani/Str/raw_ptr.rs | 20 +++++++++++++++++++ tests/perf/smol_str/Cargo.toml | 11 ++++++++++ tests/perf/smol_str/expected | 4 ++++ tests/perf/smol_str/src/lib.rs | 13 ++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 tests/kani/Str/raw_ptr.rs create mode 100644 tests/perf/smol_str/Cargo.toml create mode 100644 tests/perf/smol_str/expected create mode 100644 tests/perf/smol_str/src/lib.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index a30eeb7639ce..e8f1424f09a3 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -694,6 +694,14 @@ impl<'tcx> GotocCtx<'tcx> { let meta = self.codegen_operand_stable(&operands[1]); slice_fat_ptr(typ, data_cast, meta, &self.symbol_table) } + TyKind::RigidTy(RigidTy::Str) => { + let pointee_goto_typ = Type::unsigned_int(8); + // cast data to pointer with specified type + let data_cast = + data.cast_to(Type::Pointer { typ: Box::new(pointee_goto_typ) }); + let meta = self.codegen_operand_stable(&operands[1]); + slice_fat_ptr(typ, data_cast, meta, &self.symbol_table) + } TyKind::RigidTy(RigidTy::Adt(..)) => { let pointee_goto_typ = self.codegen_ty_stable(pointee_ty); let data_cast = diff --git a/tests/kani/Str/raw_ptr.rs b/tests/kani/Str/raw_ptr.rs new file mode 100644 index 000000000000..84d33bc608cc --- /dev/null +++ b/tests/kani/Str/raw_ptr.rs @@ -0,0 +1,20 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Checks that Kani can handle creating pointers for slices from raw parts. +//! This used to trigger an ICE reported in . +#![feature(ptr_metadata)] + +#[derive(kani::Arbitrary)] +struct AscII { + #[safety_constraint(*inner < 128)] + inner: u8, +} + +#[kani::proof] +fn check_from_raw() { + let ascii: [AscII; 5] = kani::any(); + let slice_ptr: *const [AscII] = &ascii; + let (ptr, metadata) = slice_ptr.to_raw_parts(); + let str_ptr: *const str = std::ptr::from_raw_parts(ptr, metadata); + assert!(unsafe { (&*str_ptr).is_ascii() }); +} diff --git a/tests/perf/smol_str/Cargo.toml b/tests/perf/smol_str/Cargo.toml new file mode 100644 index 000000000000..2edab49870ea --- /dev/null +++ b/tests/perf/smol_str/Cargo.toml @@ -0,0 +1,11 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "check_smol_str" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Make dependency fixed to ensure the test stays the same. +smol_str = "=0.2.2" diff --git a/tests/perf/smol_str/expected b/tests/perf/smol_str/expected new file mode 100644 index 000000000000..af045d69d75b --- /dev/null +++ b/tests/perf/smol_str/expected @@ -0,0 +1,4 @@ +Checking harness check_new... +VERIFICATION:- SUCCESSFUL + +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/perf/smol_str/src/lib.rs b/tests/perf/smol_str/src/lib.rs new file mode 100644 index 000000000000..7fe771c1fc07 --- /dev/null +++ b/tests/perf/smol_str/src/lib.rs @@ -0,0 +1,13 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Test that Kani can correctly verify the cedar implementation of `SmolStr` +//! An ICE was initially reported for this case in: +//! + +#[kani::proof] +#[kani::unwind(4)] +fn check_new() { + let data: [char; 3] = kani::any(); + let input: String = data.iter().collect(); + smol_str::SmolStr::new(&input); +} From 1f0b47f98383b4c9901c64ab708eb68b2ec5525f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:59:34 +0200 Subject: [PATCH 035/159] Automatic cargo update to 2024-08-26 (#3459) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 878d61a62889..d13ff68b32ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fixedbitset" @@ -529,7 +529,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -551,9 +551,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.157" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "linear-map" @@ -811,9 +811,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -990,29 +990,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", "memchr", @@ -1112,7 +1112,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1127,9 +1127,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -1166,7 +1166,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1232,7 +1232,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1519,5 +1519,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] From ac1016416a2414f088c7ffaa2cadc2267f6a0721 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:31:30 -0400 Subject: [PATCH 036/159] Bump tests/perf/s2n-quic from `80b93a7` to `8f7c04b` (#3460) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `80b93a7` to `8f7c04b`.
Commits

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 80b93a7f1d18..8f7c04be292f 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 80b93a7f1d187fef005c8c896ab99278b6865dbe +Subproject commit 8f7c04be292f6419b38e37bd7ff67f15833735d7 From 28f8f22c855e8276f04afe9600f8ae7b7f913aba Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:50:40 -0700 Subject: [PATCH 037/159] Update deny action (#3461) The current `cargo deny` configuration in `deny.toml` uses several keys that have been deprecated. This PR removes the deprecated keys, and updates the deny action to use v2 (as well as renames it from `audit.yml` to `deny.yml`). The only semantic difference is that `cargo deny` will now reject crates that are maintained or have a notice on them, whereas previously, our configuration set both to "warn". As mentioned in the docs though, one can add an "ignore" if needed to bypass those advisories: https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html#the-version-field-optional 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/{audit.yml => deny.yml} | 4 ++-- deny.toml | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) rename .github/workflows/{audit.yml => deny.yml} (87%) diff --git a/.github/workflows/audit.yml b/.github/workflows/deny.yml similarity index 87% rename from .github/workflows/audit.yml rename to .github/workflows/deny.yml index 5b75d6162c85..7ce00cabd2f9 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/deny.yml @@ -4,7 +4,7 @@ # 1. Checks licenses for allowed license. # 2. Checks Rust-Sec registry for security advisories. -name: Cargo Audit +name: Cargo Deny on: pull_request: merge_group: @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 with: arguments: --all-features --workspace command-arguments: -s diff --git a/deny.toml b/deny.toml index 39be523ebd19..733f91e12f36 100644 --- a/deny.toml +++ b/deny.toml @@ -7,10 +7,7 @@ [advisories] db-path = "~/.cargo/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"] -vulnerability = "deny" -unmaintained = "warn" yanked = "deny" -notice = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ @@ -21,19 +18,14 @@ ignore = [ # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -default = "deny" -unlicensed = "deny" -copyleft = "deny" allow = [ "MIT", "Apache-2.0", ] -allow-osi-fsf-free = "neither" confidence-threshold = 0.8 # All these exceptions should probably appear in: tools/build-kani/license-notes.txt exceptions = [ - { name = "Inflector", allow=["BSD-2-Clause"] }, { name = "unicode-ident", allow=["Unicode-DFS-2016"] }, ] From db9c45c49525819b7d1de63a14efa7fb4df4b03c Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Mon, 26 Aug 2024 22:00:26 -0400 Subject: [PATCH 038/159] Basic support for memory initialization checks for unions (#3444) This PR introduces very basic support for memory initialization checks for unions. As of now, the following is supported: - Unions can be created - Union fields can be assigned to - Union fields can be read from - Unions can be assigned directly to another union For more information about planned functionality, see #3300 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../kani_middle/transform/check_uninit/mod.rs | 174 +++++++++++++++--- .../check_uninit/ptr_uninit/uninit_visitor.rs | 81 +++++++- .../check_uninit/relevant_instruction.rs | 174 ++++++++++++++---- .../transform/check_uninit/ty_layout.rs | 43 ++++- .../kani_middle/transform/kani_intrinsics.rs | 20 ++ library/kani/src/mem_init.rs | 12 +- tests/expected/uninit/fixme_unions.rs | 61 ++++++ tests/expected/uninit/intrinsics/expected | 6 +- tests/expected/uninit/unions.expected | 17 ++ tests/expected/uninit/unions.rs | 68 +++++++ 10 files changed, 583 insertions(+), 73 deletions(-) create mode 100644 tests/expected/uninit/fixme_unions.rs create mode 100644 tests/expected/uninit/unions.expected create mode 100644 tests/expected/uninit/unions.rs 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 88d906aa3134..0130c00d1a71 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -35,16 +35,29 @@ pub trait TargetFinder { fn find_all(self, body: &MutableBody) -> Vec; } +const KANI_IS_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsPtrInitialized"; +const KANI_SET_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetPtrInitialized"; +const KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsSliceChunkPtrInitialized"; +const KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetSliceChunkPtrInitialized"; +const KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsSlicePtrInitialized"; +const KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetSlicePtrInitialized"; +const KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsStrPtrInitialized"; +const KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetStrPtrInitialized"; +const KANI_COPY_INIT_STATE_DIAGNOSTIC: &str = "KaniCopyInitState"; +const KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC: &str = "KaniCopyInitStateSingle"; + // Function bodies of those functions will not be instrumented as not to cause infinite recursion. const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ - "KaniIsPtrInitialized", - "KaniSetPtrInitialized", - "KaniIsSliceChunkPtrInitialized", - "KaniSetSliceChunkPtrInitialized", - "KaniIsSlicePtrInitialized", - "KaniSetSlicePtrInitialized", - "KaniIsStrPtrInitialized", - "KaniSetStrPtrInitialized", + KANI_IS_PTR_INITIALIZED_DIAGNOSTIC, + KANI_SET_PTR_INITIALIZED_DIAGNOSTIC, + KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC, + KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC, + KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC, + KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC, + KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC, + KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC, + KANI_COPY_INIT_STATE_DIAGNOSTIC, + KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC, ]; /// Instruments the code with checks for uninitialized memory, agnostic to the source of targets. @@ -164,8 +177,14 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { } MemoryInitOp::SetSliceChunk { .. } | MemoryInitOp::Set { .. } - | MemoryInitOp::SetRef { .. } => self.build_set(body, source, operation, pointee_info), + | MemoryInitOp::SetRef { .. } + | MemoryInitOp::CreateUnion { .. } => { + self.build_set(body, source, operation, pointee_info) + } MemoryInitOp::Copy { .. } => self.build_copy(body, source, operation, pointee_info), + MemoryInitOp::AssignUnion { .. } => { + self.build_assign_union(body, source, operation, pointee_info) + } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() } @@ -196,12 +215,12 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // pass is as an argument. let (diagnostic, args) = match &operation { MemoryInitOp::Check { .. } | MemoryInitOp::CheckRef { .. } => { - let diagnostic = "KaniIsPtrInitialized"; + let diagnostic = KANI_IS_PTR_INITIALIZED_DIAGNOSTIC; let args = vec![ptr_operand.clone(), layout_operand]; (diagnostic, args) } MemoryInitOp::CheckSliceChunk { .. } => { - let diagnostic = "KaniIsSliceChunkPtrInitialized"; + let diagnostic = KANI_IS_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC; let args = vec![ptr_operand.clone(), layout_operand, operation.expect_count()]; (diagnostic, args) @@ -232,10 +251,10 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // Since `str`` is a separate type, need to differentiate between [T] and str. let (slicee_ty, diagnostic) = match pointee_info.ty().kind() { TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => { - (slicee_ty, "KaniIsSlicePtrInitialized") + (slicee_ty, KANI_IS_SLICE_PTR_INITIALIZED_DIAGNOSTIC) } TyKind::RigidTy(RigidTy::Str) => { - (Ty::unsigned_ty(UintTy::U8), "KaniIsStrPtrInitialized") + (Ty::unsigned_ty(UintTy::U8), KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC) } _ => unreachable!(), }; @@ -266,6 +285,14 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { self.inject_assert_false(self.tcx, body, source, operation.position(), reason); return; } + PointeeLayout::Union { .. } => { + // Here we are reading from a pointer to a union. + // TODO: we perhaps need to check that the union at least contains an intersection + // of all layouts initialized. + let reason = "Interaction between raw pointers and unions is not yet supported."; + self.inject_assert_false(self.tcx, body, source, operation.position(), reason); + return; + } }; // Construct the basic block and insert it into the body. @@ -317,7 +344,7 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // pass is as an argument. let (diagnostic, args) = match &operation { MemoryInitOp::Set { .. } | MemoryInitOp::SetRef { .. } => { - let diagnostic = "KaniSetPtrInitialized"; + let diagnostic = KANI_SET_PTR_INITIALIZED_DIAGNOSTIC; let args = vec![ ptr_operand, layout_operand, @@ -330,7 +357,7 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { (diagnostic, args) } MemoryInitOp::SetSliceChunk { .. } => { - let diagnostic = "KaniSetSliceChunkPtrInitialized"; + let diagnostic = KANI_SET_SLICE_CHUNK_PTR_INITIALIZED_DIAGNOSTIC; let args = vec![ ptr_operand, layout_operand, @@ -369,10 +396,10 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // Since `str`` is a separate type, need to differentiate between [T] and str. let (slicee_ty, diagnostic) = match pointee_info.ty().kind() { TyKind::RigidTy(RigidTy::Slice(slicee_ty)) => { - (slicee_ty, "KaniSetSlicePtrInitialized") + (slicee_ty, KANI_SET_SLICE_PTR_INITIALIZED_DIAGNOSTIC) } TyKind::RigidTy(RigidTy::Str) => { - (Ty::unsigned_ty(UintTy::U8), "KaniSetStrPtrInitialized") + (Ty::unsigned_ty(UintTy::U8), KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC) } _ => unreachable!(), }; @@ -409,6 +436,63 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { PointeeLayout::TraitObject => { unreachable!("Cannot change the initialization state of a trait object directly."); } + PointeeLayout::Union { field_layouts } => { + // Writing union data, which could be either creating a union from scratch or + // performing some pointer operations with it. If we are creating a union from + // scratch, an operation will contain a union field. + + // TODO: If we don't have a union field, we are either creating a pointer to a union + // or assigning to one. In the former case, it is safe to return from this function, + // since the union must be already tracked (on creation and update). In the latter + // case, we should have been using union assignment instead. Nevertheless, this is + // currently mitigated by injecting `assert!(false)`. + let union_field = match operation.union_field() { + Some(field) => field, + None => { + let reason = + "Interaction between raw pointers and unions is not yet supported."; + self.inject_assert_false( + self.tcx, + body, + source, + operation.position(), + reason, + ); + return; + } + }; + let layout = &field_layouts[union_field]; + let layout_operand = mk_layout_operand(body, &mut statements, source, layout); + let diagnostic = KANI_SET_PTR_INITIALIZED_DIAGNOSTIC; + let args = vec![ + ptr_operand, + layout_operand, + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::from_bool(value), + }), + ]; + let set_ptr_initialized_instance = resolve_mem_init_fn( + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), + layout.len(), + *pointee_info.ty(), + ); + Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + set_ptr_initialized_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args, + destination: ret_place.clone(), + target: Some(0), // this will be overriden in add_bb + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + } + } }; // Construct the basic block and insert it into the body. body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); @@ -426,14 +510,19 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), projection: vec![], }; - let PointeeLayout::Sized { layout } = pointee_info.layout() else { unreachable!() }; + let layout_size = pointee_info.layout().maybe_size().unwrap(); let copy_init_state_instance = resolve_mem_init_fn( - get_mem_init_fn_def(self.tcx, "KaniCopyInitState", &mut self.mem_init_fn_cache), - layout.len(), + get_mem_init_fn_def( + self.tcx, + KANI_COPY_INIT_STATE_DIAGNOSTIC, + &mut self.mem_init_fn_cache, + ), + layout_size, *pointee_info.ty(), ); let position = operation.position(); - let MemoryInitOp::Copy { from, to, count } = operation else { unreachable!() }; + let (from, to) = operation.expect_copy_operands(); + let count = operation.expect_count(); body.insert_call( ©_init_state_instance, source, @@ -443,6 +532,49 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { ); } + /// Copy memory initialization state from one union variable to another. + fn build_assign_union( + &mut self, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: MemoryInitOp, + pointee_info: PointeeInfo, + ) { + let ret_place = Place { + local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + let mut statements = vec![]; + let layout_size = pointee_info.layout().maybe_size().unwrap(); + let copy_init_state_instance = resolve_mem_init_fn( + get_mem_init_fn_def( + self.tcx, + KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC, + &mut self.mem_init_fn_cache, + ), + layout_size, + *pointee_info.ty(), + ); + let (from, to) = operation.expect_assign_union_operands(body, &mut statements, source); + let terminator = Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + copy_init_state_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args: vec![from, to], + destination: ret_place.clone(), + target: Some(0), // this will be overriden in add_bb + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + }; + + // Construct the basic block and insert it into the body. + body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); + } + fn inject_assert_false( &self, tcx: TyCtxt, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index a1689bdfe8c4..207005dafb27 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -19,10 +19,11 @@ use stable_mir::{ alloc::GlobalAlloc, mono::{Instance, InstanceKind}, visit::{Location, PlaceContext}, - CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, PointerCoercion, - ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + AggregateKind, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, + PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, + TerminatorKind, }, - ty::{ConstantKind, RigidTy, TyKind}, + ty::{AdtKind, ConstantKind, RigidTy, TyKind}, }; pub struct CheckUninitVisitor { @@ -112,7 +113,7 @@ impl MirVisitor for CheckUninitVisitor { } } // 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()), @@ -121,6 +122,58 @@ impl MirVisitor for CheckUninitVisitor { }); } } + + // TODO: add support for ADTs which could have unions as subfields. Currently, + // if a union as a subfield is detected, `assert!(false)` will be injected from + // the type layout code. + let is_inside_union = { + let mut contains_union = false; + let mut place_to_add_projections = + Place { local: place.local, projection: vec![] }; + for projection_elem in place.projection.iter() { + if place_to_add_projections.ty(&self.locals).unwrap().kind().is_union() { + contains_union = true; + break; + } + place_to_add_projections.projection.push(projection_elem.clone()); + } + contains_union + }; + + // Need to copy some information about union initialization, since lvalue is + // either a union or a field inside a union. + if is_inside_union { + if let Rvalue::Use(operand) = rvalue { + // This is a union-to-union assignment, so we need to copy the + // initialization state. + if place.ty(&self.locals).unwrap().kind().is_union() { + self.push_target(MemoryInitOp::AssignUnion { + lvalue: place.clone(), + rvalue: operand.clone(), + }); + } else { + // This is assignment to a field of a union. + self.push_target(MemoryInitOp::SetRef { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } + } + } + + // Create a union from scratch as an aggregate. We handle it here because we + // need to know which field is getting assigned. + if let Rvalue::Aggregate(AggregateKind::Adt(adt_def, _, _, _, union_field), _) = + rvalue + { + if adt_def.kind() == AdtKind::Union { + self.push_target(MemoryInitOp::CreateUnion { + operand: Operand::Copy(place.clone()), + field: union_field.unwrap(), // Safe to unwrap because we know this is a union. + }); + } + } } StatementKind::Deinit(place) => { self.super_statement(stmt, location); @@ -350,12 +403,22 @@ impl MirVisitor for CheckUninitVisitor { }); } } - ProjectionElem::Field(idx, target_ty) => { - if target_ty.kind().is_union() - && (!ptx.is_mutating() || place.projection.len() > idx + 1) + ProjectionElem::Field(_, _) => { + if intermediate_place.ty(&self.locals).unwrap().kind().is_union() + && !ptx.is_mutating() { - self.push_target(MemoryInitOp::Unsupported { - reason: "Kani does not support reasoning about memory initialization of unions.".to_string(), + let contains_deref_projection = + { place.projection.iter().any(|elem| *elem == ProjectionElem::Deref) }; + if contains_deref_projection { + // We do not currently support having a deref projection in the same + // place as union field access. + self.push_target(MemoryInitOp::Unsupported { + reason: "Kani does not yet support performing a dereference on a union field".to_string(), + }); + } + // Accessing a place inside the union, need to check if it is initialized. + self.push_target(MemoryInitOp::CheckRef { + operand: Operand::Copy(place.clone()), }); } } 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 index 05826969262d..6120f2d3d4c4 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -6,9 +6,8 @@ use crate::kani_middle::transform::body::{InsertPosition, MutableBody, SourceInstruction}; use stable_mir::{ - mir::{Mutability, Operand, Place, Rvalue, Statement, StatementKind}, - ty::RigidTy, - ty::Ty, + mir::{FieldIdx, Mutability, Operand, Place, Rvalue, Statement, StatementKind}, + ty::{RigidTy, Ty}, }; use strum_macros::AsRefStr; @@ -17,28 +16,65 @@ use strum_macros::AsRefStr; 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 }, + 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 }, + 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 }, + 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 }, + 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 }, + 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 }, + SetRef { + operand: Operand, + value: bool, + position: InsertPosition, + }, /// Unsupported memory initialization operation. - Unsupported { reason: String }, + Unsupported { + reason: String, + }, /// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`. - TriviallyUnsafe { reason: String }, + TriviallyUnsafe { + reason: String, + }, /// Operation that copies memory initialization state over to another operand. - Copy { from: Operand, to: Operand, count: Operand }, + Copy { + from: Operand, + to: Operand, + count: Operand, + }, + + AssignUnion { + lvalue: Place, + rvalue: Operand, + }, + CreateUnion { + operand: Operand, + field: FieldIdx, + }, } impl MemoryInitOp { @@ -56,28 +92,13 @@ impl MemoryInitOp { | MemoryInitOp::Set { operand, .. } | MemoryInitOp::CheckSliceChunk { operand, .. } | MemoryInitOp::SetSliceChunk { operand, .. } => operand.clone(), - MemoryInitOp::CheckRef { operand, .. } | MemoryInitOp::SetRef { operand, .. } => { - let span = source.span(body.blocks()); - - let ref_local = { - let place = match operand { - Operand::Copy(place) | Operand::Move(place) => place, - Operand::Constant(_) => unreachable!(), - }; - let rvalue = Rvalue::AddressOf(Mutability::Not, place.clone()); - let ret_ty = rvalue.ty(body.locals()).unwrap(); - let result = body.new_local(ret_ty, span, Mutability::Not); - let stmt = Statement { - kind: StatementKind::Assign(Place::from(result), rvalue), - span, - }; - statements.push(stmt); - result - }; - - Operand::Copy(Place { local: ref_local, projection: vec![] }) + MemoryInitOp::CheckRef { operand, .. } + | MemoryInitOp::SetRef { operand, .. } + | MemoryInitOp::CreateUnion { operand, .. } => { + mk_ref(operand, body, statements, source) } MemoryInitOp::Copy { .. } + | MemoryInitOp::AssignUnion { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() @@ -85,13 +106,42 @@ impl MemoryInitOp { } } + /// A helper to access operands of copy operation. + pub fn expect_copy_operands(&self) -> (Operand, Operand) { + match self { + MemoryInitOp::Copy { from, to, .. } => (from.clone(), to.clone()), + _ => unreachable!(), + } + } + + /// A helper to access operands of union assign, automatically creates references to them. + pub fn expect_assign_union_operands( + &self, + body: &mut MutableBody, + statements: &mut Vec, + source: &mut SourceInstruction, + ) -> (Operand, Operand) { + match self { + MemoryInitOp::AssignUnion { lvalue, rvalue } => { + let lvalue_as_operand = Operand::Copy(lvalue.clone()); + ( + mk_ref(rvalue, body, statements, source), + mk_ref(&lvalue_as_operand, body, statements, source), + ) + } + _ => unreachable!(), + } + } + pub fn operand_ty(&self, body: &MutableBody) -> Ty { match self { MemoryInitOp::Check { operand, .. } | MemoryInitOp::Set { operand, .. } | MemoryInitOp::CheckSliceChunk { operand, .. } | MemoryInitOp::SetSliceChunk { operand, .. } => operand.ty(body.locals()).unwrap(), - MemoryInitOp::SetRef { operand, .. } | MemoryInitOp::CheckRef { operand, .. } => { + MemoryInitOp::SetRef { operand, .. } + | MemoryInitOp::CheckRef { operand, .. } + | MemoryInitOp::CreateUnion { operand, .. } => { let place = match operand { Operand::Copy(place) | Operand::Move(place) => place, Operand::Constant(_) => unreachable!(), @@ -117,6 +167,12 @@ impl MemoryInitOp { assert!(from_pointee_ty == to_pointee_ty); from.ty(body.locals()).unwrap() } + MemoryInitOp::AssignUnion { lvalue, .. } => { + // It does not matter which operand to return for layout generation, since both of + // them have the same pointee type. + let address_of = Rvalue::AddressOf(Mutability::Not, lvalue.clone()); + address_of.ty(body.locals()).unwrap() + } } } @@ -129,6 +185,8 @@ impl MemoryInitOp { | MemoryInitOp::Set { .. } | MemoryInitOp::CheckRef { .. } | MemoryInitOp::SetRef { .. } + | MemoryInitOp::CreateUnion { .. } + | MemoryInitOp::AssignUnion { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), } @@ -139,12 +197,30 @@ impl MemoryInitOp { MemoryInitOp::Set { value, .. } | MemoryInitOp::SetSliceChunk { value, .. } | MemoryInitOp::SetRef { value, .. } => *value, + MemoryInitOp::CreateUnion { .. } => true, MemoryInitOp::Check { .. } | MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } - | MemoryInitOp::Copy { .. } => unreachable!(), + | MemoryInitOp::Copy { .. } + | MemoryInitOp::AssignUnion { .. } => unreachable!(), + } + } + + pub fn union_field(&self) -> Option { + match self { + MemoryInitOp::CreateUnion { field, .. } => Some(*field), + MemoryInitOp::Check { .. } + | MemoryInitOp::CheckSliceChunk { .. } + | MemoryInitOp::CheckRef { .. } + | MemoryInitOp::Set { .. } + | MemoryInitOp::SetSliceChunk { .. } + | MemoryInitOp::SetRef { .. } + | MemoryInitOp::Unsupported { .. } + | MemoryInitOp::TriviallyUnsafe { .. } + | MemoryInitOp::Copy { .. } + | MemoryInitOp::AssignUnion { .. } => None, } } @@ -158,7 +234,9 @@ impl MemoryInitOp { | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before, - MemoryInitOp::Copy { .. } => InsertPosition::After, + MemoryInitOp::Copy { .. } + | MemoryInitOp::AssignUnion { .. } + | MemoryInitOp::CreateUnion { .. } => InsertPosition::After, } } } @@ -184,3 +262,29 @@ impl InitRelevantInstruction { } } } + +/// A helper to generate instrumentation for taking a reference to a given operand. Returns the +/// operand which is a reference and stores all instrumentation in the statements vector passed. +fn mk_ref( + operand: &Operand, + body: &mut MutableBody, + statements: &mut Vec, + source: &mut SourceInstruction, +) -> Operand { + let span = source.span(body.blocks()); + + let ref_local = { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) => place, + Operand::Constant(_) => unreachable!(), + }; + let rvalue = Rvalue::AddressOf(Mutability::Not, place.clone()); + let ret_ty = rvalue.ty(body.locals()).unwrap(); + let result = body.new_local(ret_ty, span, Mutability::Not); + let stmt = Statement { kind: StatementKind::Assign(Place::from(result), rvalue), span }; + statements.push(stmt); + result + }; + + Operand::Copy(Place { local: ref_local, projection: vec![] }) +} 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 8a162d5944d3..dac130f11545 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 @@ -6,7 +6,7 @@ use stable_mir::{ abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}, target::{MachineInfo, MachineSize}, - ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy}, + ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy, VariantIdx}, CrateDef, }; @@ -43,10 +43,26 @@ pub enum PointeeLayout { Sized { layout: Layout }, /// Layout of slices, *const/mut str is included in this case and treated as *const/mut [u8]. Slice { element_layout: Layout }, + /// Layout of unions, which are shared storage for multiple fields of potentially different layouts. + Union { field_layouts: Vec }, /// Trait objects have an arbitrary layout. TraitObject, } +impl PointeeLayout { + /// Returns the size of the layout, if available. + pub fn maybe_size(&self) -> Option { + match self { + PointeeLayout::Sized { layout } => Some(layout.len()), + PointeeLayout::Slice { element_layout } => Some(element_layout.len()), + PointeeLayout::Union { field_layouts } => { + Some(field_layouts.iter().map(|field_layout| field_layout.len()).max().unwrap()) + } + PointeeLayout::TraitObject => None, + } + } +} + pub struct PointeeInfo { pointee_ty: Ty, layout: PointeeLayout, @@ -56,6 +72,25 @@ impl PointeeInfo { pub fn from_ty(ty: Ty) -> Result { match ty.kind() { TyKind::RigidTy(rigid_ty) => match rigid_ty { + RigidTy::Adt(adt_def, args) if adt_def.kind() == AdtKind::Union => { + assert!(adt_def.variants().len() == 1); + let fields: Result<_, _> = adt_def + .variant(VariantIdx::to_val(0)) + .unwrap() + .fields() + .into_iter() + .map(|field_def| { + let ty = field_def.ty_with_args(&args); + let size_in_bytes = ty.layout().unwrap().shape().size.bytes(); + data_bytes_for_ty(&MachineInfo::target(), ty, 0) + .map(|data_chunks| generate_byte_mask(size_in_bytes, data_chunks)) + }) + .collect(); + Ok(PointeeInfo { + pointee_ty: ty, + layout: PointeeLayout::Union { field_layouts: fields? }, + }) + } RigidTy::Str => { let slicee_ty = Ty::unsigned_ty(UintTy::U8); let size_in_bytes = slicee_ty.layout().unwrap().shape().size.bytes(); @@ -330,7 +365,7 @@ fn data_bytes_for_ty( | RigidTy::Dynamic(_, _, _) => Err(format!("Unsupported {ty:?}")), } } - FieldsShape::Union(_) => Err(format!("Unsupported {ty:?}")), + FieldsShape::Union(_) => Err(format!("Unions as fields of unions are unsupported {ty:?}")), FieldsShape::Array { .. } => Ok(vec![]), } } @@ -357,12 +392,12 @@ fn tys_layout_cmp_to_size(from_ty: &Ty, to_ty: &Ty, cmp: impl Fn(bool, bool) -> let from_ty_layout = match from_ty_info.layout() { PointeeLayout::Sized { layout } => layout, PointeeLayout::Slice { element_layout } => element_layout, - PointeeLayout::TraitObject => return false, + PointeeLayout::TraitObject | PointeeLayout::Union { .. } => 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, + PointeeLayout::TraitObject | PointeeLayout::Union { .. } => return false, }; // Ensure `to_ty_layout` does not have a larger size. if to_ty_layout.len() <= from_ty_layout.len() { diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index dd804b7379f2..9210c43fb163 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -307,6 +307,26 @@ impl IntrinsicGeneratorPass { new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); let reason: &str = "Kani does not support reasoning about memory initialization of pointers to trait objects."; + new_body.insert_check( + tcx, + &self.check_type, + &mut source, + InsertPosition::Before, + result, + &reason, + ); + } + PointeeLayout::Union { .. } => { + let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { + const_: MirConst::from_bool(false), + span: source.span(new_body.blocks()), + user_ty: None, + })); + let result = + new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); + let reason: &str = + "Kani does not yet support using initialization predicates on unions."; + new_body.insert_check( tcx, &self.check_type, diff --git a/library/kani/src/mem_init.rs b/library/kani/src/mem_init.rs index 3755fc59a510..a6118c472a92 100644 --- a/library/kani/src/mem_init.rs +++ b/library/kani/src/mem_init.rs @@ -309,7 +309,9 @@ fn set_str_ptr_initialized( } } -/// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. +/// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. Note +/// that in this case `LAYOUT_SIZE == size_of::`. +#[kanitool::disable_checks(pointer)] #[rustc_diagnostic_item = "KaniCopyInitState"] fn copy_init_state(from: *const T, to: *const T, num_elts: usize) { if LAYOUT_SIZE == 0 { @@ -321,3 +323,11 @@ fn copy_init_state(from: *const T, to: *const T, nu MEM_INIT_STATE.copy::(from_ptr as *const u8, to_ptr as *const u8, num_elts); } } + +/// Copy initialization state of `size_of::` bytes from one pointer to the other. Note that in +/// this case `LAYOUT_SIZE == size_of::`. +#[kanitool::disable_checks(pointer)] +#[rustc_diagnostic_item = "KaniCopyInitStateSingle"] +fn copy_init_state_single(from: *const T, to: *const T) { + copy_init_state::(from, to, 1); +} diff --git a/tests/expected/uninit/fixme_unions.rs b/tests/expected/uninit/fixme_unions.rs new file mode 100644 index 000000000000..ec86e5e7e07f --- /dev/null +++ b/tests/expected/uninit/fixme_unions.rs @@ -0,0 +1,61 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +//! Tests for handling potentially uninitialized memory access via unions. +//! TODO: add a `.expected` file for this test. + +use std::ptr::addr_of; + +#[repr(C)] +#[derive(Clone, Copy)] +union U { + a: u16, + b: u32, +} + +/// Reading padding data via simple union access if union is passed to another function. +#[kani::proof] +unsafe fn cross_function_union_should_fail() { + unsafe fn helper(u: U) { + let padding = u.b; // Read 4 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading padding data but a union is behind a pointer. +#[kani::proof] +unsafe fn pointer_union_should_fail() { + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + let u_ptr = addr_of!(u); + let u1 = *u_ptr; + let padding = u1.b; // Read 4 bytes from `u`. +} + +#[repr(C)] +struct S { + u: U, +} + +/// Tests uninitialized access if unions are top-level subfields. +#[kani::proof] +unsafe fn union_as_subfields_should_pass() { + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + let s = S { u }; + let s1 = s; + let u1 = s1.u; // `u1` is initialized for 2 bytes. + let padding = u1.a; // Read 2 bytes from `u`. +} + +union Outer { + u: U, + a: u32, +} + +/// Tests unions composing with other unions. +#[kani::proof] +unsafe fn uber_union_should_pass() { + let u = Outer { u: U { b: 0 } }; // `u` is initialized for 4 bytes. + let non_padding = u.a; // Read 4 bytes from `u`. +} diff --git a/tests/expected/uninit/intrinsics/expected b/tests/expected/uninit/intrinsics/expected index 33392337c30b..b5555785e8af 100644 --- a/tests/expected/uninit/intrinsics/expected +++ b/tests/expected/uninit/intrinsics/expected @@ -1,10 +1,10 @@ -std::ptr::read::>.assertion.1\ +std::ptr::write::>.assertion.1\ - Status: FAILURE\ - - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." + - Description: "Interaction between raw pointers and unions is not yet supported." std::ptr::write::>.assertion.1\ - Status: FAILURE\ - - Description: "Kani currently doesn't support checking memory initialization for pointers to `std::mem::MaybeUninit." + - Description: "Interaction between raw pointers and unions is not yet supported."\ check_typed_swap.assertion.1\ - Status: FAILURE\ diff --git a/tests/expected/uninit/unions.expected b/tests/expected/uninit/unions.expected new file mode 100644 index 000000000000..05fdac3a8765 --- /dev/null +++ b/tests/expected/uninit/unions.expected @@ -0,0 +1,17 @@ +union_update_should_fail.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +union_complex_subfields_should_fail.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u16`" + +basic_union_should_fail.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +Summary: +Verification failed for - union_update_should_fail +Verification failed for - union_complex_subfields_should_fail +Verification failed for - basic_union_should_fail +Complete - 3 successfully verified harnesses, 3 failures, 6 total. diff --git a/tests/expected/uninit/unions.rs b/tests/expected/uninit/unions.rs new file mode 100644 index 000000000000..c623f9fcea94 --- /dev/null +++ b/tests/expected/uninit/unions.rs @@ -0,0 +1,68 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +//! Tests for handling potentially uninitialized memory access via unions. + +use std::ptr::addr_of; + +#[repr(C)] +#[derive(Clone, Copy)] +union U { + a: u16, + b: u32, +} + +/// Simple and correct union access. +#[kani::proof] +unsafe fn basic_union_should_pass() { + let u = U { b: 0 }; + let u1 = u; + let non_padding = u1.a; + assert!(non_padding == 0); +} + +/// Reading padding data via simple union access. +#[kani::proof] +unsafe fn basic_union_should_fail() { + let u = U { a: 0 }; + let u1 = u; + let padding = u1.b; +} + +#[repr(C)] +union U1 { + a: (u32, u8), + b: (u32, u16, u8), +} + +/// Tests accessing initialized data via subfields of a union. +#[kani::proof] +unsafe fn union_complex_subfields_should_pass() { + let u = U1 { a: (0, 0) }; + let non_padding = u.b.0; +} + +/// Tests accessing uninit data via subfields of a union. +#[kani::proof] +unsafe fn union_complex_subfields_should_fail() { + let u = U1 { a: (0, 0) }; + let padding = u.b.1; +} + +/// Tests overwriting data inside unions. +#[kani::proof] +unsafe fn union_update_should_pass() { + let mut u = U { a: 0 }; + u.b = 0; + let non_padding = u.b; + assert!(non_padding == 0); +} + +/// Tests overwriting data inside unions. +#[kani::proof] +unsafe fn union_update_should_fail() { + let mut u = U { a: 0 }; + u.a = 0; + let padding = u.b; +} From d5c1b71c3da1d3d1e84235487b5b4acfed41595f Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Tue, 27 Aug 2024 15:51:33 +0200 Subject: [PATCH 039/159] Adjust test patterns so as not to check for trivial properties (#3464) With diffblue/cbmc#8413, CBMC will no longer create property checks for assigns clauses that are trivially true. We had CBMC-Nightly failing since August 17th given the CBMC change. This will bring CBMC-Nightly back to a passing state. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- tests/expected/function-contract/modifies/havoc_pass.expected | 2 +- .../function-contract/modifies/havoc_pass_reordered.expected | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/expected/function-contract/modifies/havoc_pass.expected b/tests/expected/function-contract/modifies/havoc_pass.expected index d5679f71b74c..f7bbb8202c46 100644 --- a/tests/expected/function-contract/modifies/havoc_pass.expected +++ b/tests/expected/function-contract/modifies/havoc_pass.expected @@ -7,7 +7,7 @@ VERIFICATION:- SUCCESSFUL .assigns\ - Status: SUCCESS\ -- Description: "Check that var_4 is assignable"\ +- Description: "Check that *dst 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 2ea601aa26a0..7036111c76bf 100644 --- a/tests/expected/function-contract/modifies/havoc_pass_reordered.expected +++ b/tests/expected/function-contract/modifies/havoc_pass_reordered.expected @@ -7,7 +7,7 @@ VERIFICATION:- SUCCESSFUL copy.assigns\ - Status: SUCCESS\ -- Description: "Check that var_5 is assignable"\ +- Description: "Check that *dst is assignable"\ in function copy VERIFICATION:- SUCCESSFUL From 0adc107628bc225ecd97f4efc092ff4d6918021d Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 27 Aug 2024 11:33:01 -0400 Subject: [PATCH 040/159] Clarify comment in RFC Template (#3462) Looking at the RFC template file, I thought that the recommendation to leave the software design section empty referred to everything below the comment, rather than just that section. This PR updates the comment. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rfc/src/template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/src/template.md b/rfc/src/template.md index 2628bed3c54e..e2755e90ea27 100644 --- a/rfc/src/template.md +++ b/rfc/src/template.md @@ -43,6 +43,8 @@ No further explanation is needed. ## Software Design +**We recommend that you leave the Software Design section empty for the first version of your RFC**. + This is the beginning of the technical portion of the RFC. From now on, your main audience is Kani developers, so it's OK to assume readers know Kani architecture. @@ -53,8 +55,6 @@ Please provide a high level description your design. - Any changes to how these components communicate? - What corner cases do you anticipate? -**We recommend you to leave this empty for the first version of your RFC**. - ## Rationale and alternatives This is the section where you discuss the decisions you made. From 7a02955bcf5423daf176a91f2bbfee9f29a54839 Mon Sep 17 00:00:00 2001 From: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:24:40 -0400 Subject: [PATCH 041/159] RFC: Source-based code coverage (#3143) Upgrades the Kani coverage feature with the source-based code coverage implementation used in the Rust compiler. Rendered version available [here](https://github.com/adpaco-aws/rmc/blob/rfc-region-cov/rfc/src/rfcs/0011-source-coverage.md). Related to #2640 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rfc/src/SUMMARY.md | 1 + rfc/src/rfcs/0008-line-coverage.md | 2 +- rfc/src/rfcs/0011-source-coverage.md | 647 +++++++++++++++++++++++++++ 3 files changed, 649 insertions(+), 1 deletion(-) create mode 100644 rfc/src/rfcs/0011-source-coverage.md diff --git a/rfc/src/SUMMARY.md b/rfc/src/SUMMARY.md index e0b31761ae62..0b38dac9cbb5 100644 --- a/rfc/src/SUMMARY.md +++ b/rfc/src/SUMMARY.md @@ -16,3 +16,4 @@ - [0008-line-coverage](rfcs/0008-line-coverage.md) - [0009-function-contracts](rfcs/0009-function-contracts.md) - [0010-quantifiers](rfcs/0010-quantifiers.md) +- [0011-source-coverage](rfcs/0011-source-coverage.md) diff --git a/rfc/src/rfcs/0008-line-coverage.md b/rfc/src/rfcs/0008-line-coverage.md index d2bedab8bdef..a0f287bee96e 100644 --- a/rfc/src/rfcs/0008-line-coverage.md +++ b/rfc/src/rfcs/0008-line-coverage.md @@ -1,7 +1,7 @@ - **Feature Name:** Line coverage (`line-coverage`) - **Feature Request Issue:** - **RFC PR:** -- **Status:** Unstable +- **Status:** Cancelled - **Version:** 0 - **Proof-of-concept:** (Kani) + (Kani VS Code Extension) diff --git a/rfc/src/rfcs/0011-source-coverage.md b/rfc/src/rfcs/0011-source-coverage.md new file mode 100644 index 000000000000..9ea23c72a9d1 --- /dev/null +++ b/rfc/src/rfcs/0011-source-coverage.md @@ -0,0 +1,647 @@ +- **Feature Name:** Source-based code coverage (`source-coverage`) +- **Feature Request Issue:** +- **RFC PR:** +- **Status:** Under Review +- **Version:** 1 +- **Proof-of-concept:** (Kani) + (`kani-cov`) + +------------------- + +## Summary + +A source-based code coverage feature for Kani built on top of Rust's coverage instrumentation. + +## User Impact + +In our first attempt to add a coverage feature fully managed by Kani, we introduced and made available a line coverage option +(see [RFC: Line coverage](0008-line-coverage.md) for more details). +This option has since then allowed us to gather more data around the expectations for a coverage feature in Kani. + +For example, the line coverage output we produced was not easy to interpret +without knowing some implementation details. +Aside from that, the feature requested in +[#2795](https://github.com/model-checking/kani/issues/2795) alludes to the need +of providing coverage-specific tooling in Kani. +Nevertheless, as captured in +[#2640](https://github.com/model-checking/kani/issues/2640), source-based +coverage results provide the clearest and most precise coverage information. + +In this RFC, we propose an integration with [Rust's source-based code coverage +instrumentation](https://doc.rust-lang.org/rustc/instrument-coverage.html). +This integration would allow us to report source-based code coverage results from Kani. +Also, we propose adding a new user-facing, coverage-focused tool called `kani-cov`. +The tool would allow users to process coverage results generated by Kani and produce +coverage artifacts such as summaries and reports according to their preferences. +In the [next section](#user-experience), we will explain in more detail how we +expect `kani-cov` to assist with coverage-related tasks. + +With these changes, we expect our coverage options to become more flexible, +precise and efficient. These options are expected to replace the previous +options available through the line coverage feature. +In the [last section](#future-possibilities) of this RFC, we will also discuss +the requirements for a potential integration of this coverage feature with the +LLVM toolchain. + +## User Experience + +The proposed experience is partially inspired by that of the most popular +coverage frameworks. +First, let us delve into the LLVM coverage workflow, followed by an explanation +of our proposal. + +### The LLVM code coverage workflow + +The LLVM project is home to one of the most popular code coverage frameworks. +The workflow associated to the LLVM framework is described in the documentation for +[source-based code coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html)[^note-source], +but we briefly describe it here to better relate it with our proposal. + +In short, the LLVM code coverage workflow follows three steps: + 1. **Compiling with coverage enabled.** This causes the compiler to generate an instrumented program. + 2. **Running the instrumented program.** This generates binary-encoded `.profraw` files. + 3. **Using tools to aggregate and export coverage information into other formats.** + +When working in a `cargo` project, step 1 can be done through this command: + +```sh +RUSTFLAGS='-Cinstrument-coverage' cargo build +``` + +The same flag must to be used for step 2: + +```sh +RUSTFLAGS='-Cinstrument-coverage' cargo run +``` + +This should populate the directory with at least one `.profraw` file. +Each `.profraw` file corresponds to a specific source code file in your project. + +At this point, we will have produced the artifacts that we generally require for +the LLVM tools: + 1. **The instrumented binary** which, in addition to the instrumented program, + contains additional information (e.g., the coverage mappings) required to + interpret the profiling results. + 2. **The `.profraw` files** which essentially includes the profiling results + (e.g., counter values) for each function of the corresponding source code file. + +For step 3, the commands will depend on what kind of results we want. +Most likely we will have to merge the `.profraw` files and produce a `.profdata` +file as follows: + +```sh +llvm-profdata merge -sparse *.profraw -o output.profdata +``` + +The resulting `.profdata` file will contain the aggregated coverage results from +the `.profraw` files passed to the `merge` command. + +Then, we can use a command such as + +```sh +llvm-cov show target/debug/binary —instr-profile=output.profdata -show-line-counts-or-regions +``` + +to visualize the code coverage through the terminal as in the image: + +![Source-based code coverage with `llvm-cov`](https://github.com/model-checking/kani/assets/73246657/4f8a973d-8977-4c0b-822d-e73ed6d223aa) + +or the command + +```sh +llvm-cov report target/debug/binary --instr-profile=output.profdata --show-region-summary +``` + +to produce coverage summaries like this: + +``` +Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover Branches Missed Branches Cover +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/long/long/path/to/my/project/binary/src/main.rs 9 3 66.67% 3 1 66.67% 14 4 71.43% 0 0 - +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +TOTAL 9 3 66.67% 3 1 66.67% 14 4 71.43% 0 0 - +``` + +[^note-source]: The LLVM project refers to their own coverage feature as + *source-based code coverage*. It is not rare to see the term *region + coverage* being used instead to refer to the same thing. That is because + LLVM's source-based code coverage feature can report coverage for code + regions, but other coverage frameworks do not support the concept of code + regions. + +### The Kani coverage workflow + +The two main components of the Kani coverage workflow that we propose are the +following: + 1. The existing `--coverage` flag that drives the coverage workflow in Kani, + emits raw coverage data (as in the `.profraw` files), and produces basic + coverage results by default. + 2. A new subcommand `cov` that allows users to further process raw coverage + information emitted by Kani to produce customized coverage results (i.e., + different to the ones produced by default with the `--coverage` option). + The `cov` subcommand is an alias for the `kani-cov` tool. + +In contrast to the LLVM workflow, where human-readable coverage results can +be produced only after a sequence of LLVM tool commands, we provide some +coverage results by default. +This aligns better with our UX philosophy, and removes the need for a wrapper +around our coverage features like +[`cargo-llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov). +Alternatively, the `cov` subcommand offers the ability of producing more +specific coverage results if needed. +We anticipate the `cov` subcommand being particularly useful in less standard +project setups, giving the users the flexibility required to produce coverage +results tailored to their specific needs. + +In the following, we describe each one of these components in more detail. + +#### The `--coverage` option + +The default coverage workflow will be kicked off through the unstable +`--coverage` option: + +```sh +cargo kani --coverage -Zsource-coverage +``` + +The main difference with respect to the regular verification workflow is that, +at the end of the verification-based coverage run, Kani will generate two coverage +results: + - A coverage summary corresponding to the coverage achieved by the harnesses + included in the verification run. This summary will be printed after the + verification output. + - A coverage report corresponding to the coverage achieved by the harnesses + included in the verification run. The report will be placed in the same target + directory where the raw coverage files are put. The path to the report will + also be printed after the verification output. + +Therefore, a typical `--coverage` run could look like this: + +``` +VERIFICATION:- SUCCESSFUL + +Coverage Results: + +| Filename | Regions | Missed Regions | Cover | Functions | Missed Functions | Cover | +| -------- | ------- | -------------- | ----- | --------- | ---------------- | ----- | +| main.rs | 9 | 3 | 66.67 | 3 | 1 | 33.33 | +| file.rs | 11 | 5 | 45.45 | 2 | 1 | 50.00 | + +Coverage report available in target/kani/x86_64-unknown-linux-gnu/cov/kani_2024-04-26_15-30-00/report/index.html +``` + +#### The `cov` subcommand + +The `cov` subcommand will be used to process raw coverage information generated +by Kani and produce coverage outputs as indicated by the user. +Hence, the `cov` subcommand corresponds to the set of LLVM tools +(`llvm-profdata`, `llvm-cov`, etc.) that are used to produce coverage outputs +through the LLVM coverage workflow. + +In contrast to LLVM, we will have a single subcommand for all Kani +coverage-related needs. The `cov` subcommand will just call the `kani-cov` tool, +which is expected to be shipped along the rest of Kani binaries. + +We suggest that the subcommand initially offers two options: + 1. An option to merge the coverage results from one or more files and coverage + mappings[^note-snapshot] into a single file. + 2. An option to produce coverage outputs from coverage results, including summaries + or coverage reports in human-readable formats (e.g., HTML). + +Let's assume that we have run `cargo kani --coverage -Zsource-coverage` and +generated coverage files in the `my-coverage` folder. Then, we would use `cargo +kani cov` as follows to combine the coverage results[^note-exclude] for all +harnesses: + +```sh +cargo kani cov --merge my-coverage/*.kaniraw -o my-coverage.kanicov +``` + +Let's say the user is first interested in reading a coverage summary through the +terminal. +They can use the `--summary` option for that: + +```sh +cargo kani cov --summary my-coverage/default.kanimap -instr-profile=my-coverage.kanicov +``` + +The command could print a coverage summary like: + +``` +| Filename | Regions | Missed Regions | Cover | Functions | ... +| -------- | ------- | -------------- | ----- | --------- | ... +| main.rs | 9 | 3 | 66.67 | 3 | ... +[...] +``` + +Now, let's say the user wants to produce an HTML report of the coverage results. +They will have to use the `--report` option for that: + +```sh +cargo kani cov --report my-coverage/default.kanimap -format=html -instr-profile=my-coverage.kanicov -o coverage-report +``` + +This time, the command will generate a `coverage-report` folder including a +browsable HTML webpage that highlights the regions covered in the source +according to the coverage results in `my-coverage.kanicov`. + +[^note-export]: The `llvm-cov` tool includes the option + [`gcov`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-gcov) to + export into GCC's coverage format [Gcov](https://en.wikipedia.org/wiki/Gcov), + and the option + [`export`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-export) + to export into the LCOV format. These may be good options to consider for + `kani-cov` in the future but we should focus on basic formats for now. + +[^note-exclude]: Options to exclude certain coverage results (e.g, from the + standard library) will likely be part of this option. + +[^note-snapshot]: Coverage mappings essentially provide a snapshot of the source + code reports for items that otherwise are unreachable or have been sliced + away during the compilation process. + +#### Integration with the Kani VS Code Extension + +We will update the coverage feature of the +[Kani VS Code Extension](https://github.com/model-checking/kani-vscode-extension) +to follow this new coverage workflow. +In other words, the extension will first run Kani with the `--coverage` option and +use `kani cov` to produce a `.kanicov` file with the coverage results. +The extension will consume the source-based code coverage results and +highlight region coverage in the source code seen from VS Code. + +We could also consider other coverage-related features in order to enhance the +experience through the Kani VS Code Extension. For example, we could +automatically show the percentage of covered regions in the status bar by +additionally extracting a summary of the coverage results. + +Finally, we could also consider an integration with other code coverage tools. +For example, if we wanted to integrate with the VS Code extensions +[Code Coverage](https://marketplace.visualstudio.com/items?itemName=markis.code-coverage) or +[Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters), +we would only need to extend `kani-cov` to export coverage results to the LCOV +format or integrate Kani with LLVM tools as discussed in [Integration with LLVM](#integration-with-llvm). + +## Detailed Design + +In this section, we provide more details on: + - The Rust coverage instrumentation and how it can be integrated into + Kani to produce source-based code coverage results. + - The proposed coverage workflow to be run by default in Kani when the + `--coverage` option is used. + +This information is mostly intended as a reference for Kani contributors. +Currently, the Rust coverage instrumentation continues to be developed. Because +of that, Rust toolchain upgrades may result in breaking changes to our own +coverage feature. This section should help developers to understand the general +approach and resolve such issues by themselves. + +### The Rust coverage instrumentation + +The Rust compiler includes two code coverage implementations: + * A source-based coverage implementation which uses LLVM's coverage + instrumentation to generate precise coverage data. This implementation can be + enabled with `-C instrument-coverage`. + * A Gcov-based coverage implementation that derives coverage data based on + DebugInfo. This implementation can be enabled with `-Z profile`. + +The [Instrumentation-based Code Coverage](https://doc.rust-lang.org/rustc/instrument-coverage.html) +chapter from the `rustc` book describes in detail how to enable and use the LLVM +instrumentation-based coverage feature. In contrast, the +[LLVM Source-Based Code Coverage](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html) +chapter from the `rustc` development guide documents how the LLVM +coverage instrumentation is performed in the Rust compiler. + +In this section, we will first summarize some information from the +[LLVM Source-Based Code Coverage](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html) +chapter, limited to details which are relevant to the development of the +source-based coverage feature in Kani. Then, we will explain how Kani taps into +the Rust coverage instrumentation to perform its own coverage instrumentation +and be able to report source-based code coverage results. This will also include +mentions to current issues with this implementation, which we plan to further +discuss in [Future possibilities](#future-possibilities). + +#### Understanding the Rust coverage instrumentation + +The LLVM coverage instrumentation is implemented in the Rust compiler as a +[MIR pass called `InstrumentCoverage`](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html#mir-pass-instrumentcoverage). + +The MIR pass first builds a coverage-specific version of the MIR Control Flow +Graph (CFG) from the MIR. The initial version of this CFG is based on the MIR's +`BasicBlock`s, which then gets refined by combining blocks that can be chained +from a coverage-relevant point of view. The final version of the coverage CFG is +then used to determine where to inject the +[`StatementKind::Coverage`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.StatementKind.html#variant.Coverage) +statements in order to measure coverage for a single region coverage span. + +The injection of `StatementKind::Coverage` statements is the main result we are +interested in for the integration with Kani. Additionally, the instrumentation +will also attach the +[`FunctionCoverageInfo`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/struct.FunctionCoverageInfo.html) +structure to each function's body.[^note-coverage-info] +This result is also needed at the moment because coverage statements do not +include information on the code region they are supposed to cover. +However, `FunctionCoverageInfo` contains the +[coverage mappings](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/struct.Mapping.html), +which represent the relation between +[coverage counters](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CovTerm.html) +and code regions. + +As explained in [MIR Pass: +`InstrumentCoverage`](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html#mir-pass-instrumentcoverage), +many coverage statements will not be converted into a physical +counter[^note-physical-counter]. Instead, they will be converted into a +*coverage-counter expression* that can be calculated based on other coverage +counters. We highly recommend looking at the example in [MIR Pass: +`InstrumentCoverage`](https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html#mir-pass-instrumentcoverage) +to better understand how this works. This optimization is mainly done for +performance reasons because incrementing a physical counter causes a +non-negligible overhead, especially within loops. + +The (`StatementKind::`)`Coverage` statements that are injected by the Rust coverage +instrumentation contain a [`CoverageKind`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CoverageKind.html) field indicating the type of coverage counter. The variant +[`CounterIncrement`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CoverageKind.html#variant.CounterIncrement) +represents physical counters, while +[`ExpressionUsed`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CoverageKind.html#variant.ExpressionUsed) +represents the counter expressions that we just discussed. +Other variants such as `SpanMarker` or `BlockMarker` are not relevant to this +work since they should have been erased after the `InstrumentCoverage` pass. + +[^note-coverage-info]: It is important to note that the StableMIR interface does + not include `FunctionCoverageInfo` in function bodies. Because of that, we + need to pull it from the internal `rustc` function bodies. + +[^note-physical-counter]: By *physical counter*, we refer to a global program + variable that is initialized to zero and incremented by one each time that + the execution passes through. + +#### Integrating the instrumentation into Kani + +Now that we have explained what the Rust coverage instrumentation does at a high +level, we should be ready to discuss how it can be used from Kani. Here, we will +follow an approach where, during the codegen stage, we generate a Kani +reachability check for each code region and, after the verification stage, we +postprocess the information in those checks to generate the coverage +information. So this section will essentially be a retelling of the +implementation in [#3119](https://github.com/model-checking/kani/pull/3119), and +we will discuss variations/extensions of this approach in the +[appropriate](#rationale-and-alternatives) [sections](#future-possibilities). + +Clearly, the first step is adding `-C instrument-coverage` to the `rustc` flags +we use when calling the compiler to codegen. This flag enables the Rust coverage +instrumentation that we discussed earlier, resulting in + 1. the injection of + [`Coverage`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.StatementKind.html#variant.Coverage) + statements in the MIR code, and + 2. the inclusion of [`FunctionCoverageInfo`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/struct.FunctionCoverageInfo.html) in function bodies. + +The next step is handling the `Coverage` statements from `codegen_statement`. + +Each `Coverage` statement contains opaque coverage +information[^note-opaque-coverage] of the +[`CoverageKind`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/coverage/enum.CoverageKind.html) +type which can be processed to determine the type of coverage counter +(`CounterIncrement` for physical counters, `ExpressionUsed` for counter +expressions) and the ID number of the counter. These two pieces of information allow us to +uniquely identify the counter within a given function. For example, +`CounterIncrement(0)` would generally refer to the first physical counter in the +function. + +Unfortunately, the `CoverageKind` information does not tell us anything about +the code region that the counter covers. However, data about the code region can +be pulled from the coverage mappings included in the `FunctionCoverageInfo` that +is attached to the (internal) function body. Note that the coverage mappings +includes information about all the coverage counters in a function, even for +counters which have been dropped. Matching the `CoverageKind` information with +that of the counters in the coverage mappings allows us to retrieve the code +region for any counter. + +Using all this data, for each coverage statement[^note-expression-integration] we generate a coverage check +that maintains the essence of the coverage checks described in the [RFC for line +coverage](https://model-checking.github.io/kani/rfc/rfcs/0008-line-coverage.html): + +> Coverage checks are a new class of checks similar to [`cover` checks](https://model-checking.github.io/kani/rfc/rfcs/0003-cover-statement.html). +> The main difference is that users cannot directly interact with coverage checks (i.e., they cannot add or remove them manually). +> Coverage checks are encoded as an `assert(false)` statement (to test reachability) with a fixed description. +> In addition, coverage checks are: +> * Hidden from verification results. +> * Postprocessed to produce coverage results. + +Therefore, the last step is to postprocess the results from coverage checks to +produce coverage results. This is not too complicated to do since the checks +already include the counter information (type + ID) and the function name in +the check's description. If the span of the code region is also included +(this is what [#3119](https://github.com/model-checking/kani/pull/3119) is +currently doing), we can directly generate a primitive output like this: + +``` + () + * - + * ... + * - +``` + +For example, for the test case in +[#3119](https://github.com/model-checking/kani/pull/3119) we report this: + +``` +src/main.rs (main) + * 14:1 - 19:2 COVERED + +src/main.rs (test_cov) + * 5:1 - 6:15 COVERED + * 6:19 - 6:28 UNCOVERED + * 7:9 - 7:13 COVERED + * 9:9 - 9:14 UNCOVERED + * 11:1 - 11:2 COVERED +``` + +> **NOTE: This section has been written according to the implementation in +> [#3119](https://github.com/model-checking/kani/pull/3119), which currently +> produces a text-based output like the one shown above. There is ongoing work to +> store the coverage mappings in a separate file (as described in the next +> section), which would save us the need to attach code region data to the +> coverage checks.** + +[^note-opaque-coverage]: The Rust compiler uses the `Opaque` type to prevent +others from interfacing with unstable types (e.g., the `Coverage` type +[here](https://github.com/rust-lang/rust/blob/f7eefec4e03f5ba723fbc04d94dbc1203b7c9bff/compiler/stable_mir/src/mir/body.rs#L389)). +Nonetheless, this can be worked around by serializing its contents and parsing +it back into an internal data type. + +[^note-expression-integration]: We could follow an alternative approach where we +do not instrument each coverage statement, but only those that correspond to +physical counters. Unfortunately, doing so would lead to incorrect coverage +results due to the arithmetic nature of expression-based counters. We elaborate +on this topic in the later parts of this document. + +### The default coverage workflow in Kani + +In this section, we describe the default `--coverage` workflow from a +developer's point of view. This will hopefully help developers understand how +the different coverage components in Kani are connected. For example, we'll +describe the raw coverage information that gets produced throughout the default +`--coverage` workflow and define the basic `cov` commands that it will execute. + +The main difference with respect to the regular verification workflow is that, +at the end of the verification-based coverage run, Kani will generate two types +of files: + - One single file `.kanimap` file for the project. This file will contain the + coverage mappings for the project's source code. + - One `.kaniraw` file for each harness. This file will contain the + verification-based results for the coverage-oriented properties corresponding + to a given harness. + +Note that `.kaniraw` files correspond to `.profraw` files in the LLVM coverage +workflow. Similarly, the `.kanimap` file corresponds to the coverage-related +information that's embedded into the project's binaries in the LLVM coverage +workflow.[^note-kanimap] + +The files will be written into a new timestamped directory associated with the +coverage run. The path to this directory will be printed to standard output in +by default. For example, the [draft implementation](https://github.com/model-checking/kani/pull/3119) +writes the coverage files into the `target/kani//cov/` directory. + +Users aren't expected to read the information in any of these files. +Therefore, there's no need to restrict their format. +The [draft implementation](https://github.com/model-checking/kani/pull/3119) +uses the JSON format but we might consider using a binary format if it doesn't +scale. + +In addition, Kani will produce two types of coverage results: + 1. A coverage summary with the default options. + 2. A terminal-based coverage report with the default options. However, we will + only do this if the program is composed of a single source + file[^note-conditional-report]. + +[^note-kanimap]: Note that the `.kanimap` generation isn't implemented in + [#3119](https://github.com/model-checking/kani/pull/3119). The [draft + implementation of + `kani-cov`](https://github.com/model-checking/kani/pull/3121) simply reads + the source files referred to by the code coverage checks, but it doesn't get + information about code trimmed out by the MIR linker. + +[^note-conditional-report]: In other words, standalone `kani` would always emit +these terminal-based reports, but `cargo kani` would not unless the project +contains a single Rust file (for example, `src/main.rs`). + +## Rationale and alternatives + +### Other coverage implementations + +In a previous version of this feature, we used an ad-hoc coverage implementation. +In addition to being very inefficient[^note-benchmarks], the line-based coverage +results were not trivial to interpret by users. +At the moment, there's only another unstable, GCC-compatible code coverage implementation +based on the Gcov format. The Gcov format is line-based so it's not able +to report region coverage results. +In other words, it's not as advanced nor precise as the source-based implementation. + +[^note-benchmarks]: Actual performance benchmarks to follow in + [#3119](https://github.com/model-checking/kani/pull/3119). + +## Open questions + + - Do we want to instrument dependencies by default? Preliminary benchmarking results show a slowdown of 100% and greater. + More evaluations are required to determine how we handle instrumentation for dependencies, and what options we might want + to provide to users. + - How do we handle features/options for `kani-cov`? In particular, do we need more details in this RFC? + +## Future possibilities + +### Integration with LLVM + +As part of this work, we explored a potential integration with the LLVM +framework. The idea behind such an integration would essentially involve +producing coverage results in formats compatible with the LLVM framework (e.g., +the `.profraw` format). The main advantage of integrating with the LLVM +framework in this way is that we would not need a tool like `kani-cov` to +aggregate coverage results; we could just use LLVM tools such as `llvm-profdata` +and `llvm-cov` to consume them. + +However, at this time we recommend against integrating with LLVM due to these reasons: + 1. Generating the instrumented binary used in the [LLVM coverage + workflow](#the-llvm-code-coverage-workflow) requires a standard `rustc` + compilation with `--cfg kani` in addition to other flags including `-C + instrument-coverage`. This is likely to result in compilation errors since the + standard `rustc` backend cannot produce code for Kani APIs, for example. + 2. Producing the `.profraw` files requires executing the instrumented binary at + least once. This would be an issue for Rust projects which assume a particular + environment for their execution. + 3. There are no stable interfaces to create or modify files in formats + compatible with the LLVM framework. Even though the documentation for the [LLVM + Code Coverage Mapping Format](https://llvm.org/docs/CoverageMappingFormat.html) + is excellent, the easiest way to interact with files on these format is through + LLVM tools (e.g., + [`llvm-cov`](https://github.com/llvm/llvm-project/tree/main/llvm/tools/llvm-cov)) + which bring in many other LLVM dependencies. During our exploration, we + attempted to decode and re-encode files in the `.profraw` to set the counter + data to the values obtained during verification. To this end, we tried tools + like [`llvm-profparser`](https://github.com/xd009642/llvm-profparser/) which + can be used as a replacement for `llvm-profdata` and `llvm-cov` but failed to + parse coverage files emitted by the Rust compiler (this is also related to the + next point). Another crate that we used is + [`coverage-dump`](https://github.com/rust-lang/rust/tree/master/src/tools/coverage-dump), + a recent tool in the Rust compiler used for testing purposes. `coverage-dump` + extracts coverage mappings from LLVM IR assembly files (i.e., human-readable + `*.ll` files) but does not work with the binary-encoded formats. Finally, we + also built some ad-hoc tooling to perform these modifications but it soon + became evident that we would need to develop it further in order to handle any + program. + 4. LLVM releases a new version approximately every six months. This would + likely result in another "toolchain update" problem for Kani in order to + provide compatibility with newer LLVM versions. Moreover, the Rust compiler + supplies their own version of LLVM tools (`rust-profdata`, `rust-cov`, etc.) + which are fully compatible with coverage-related artifacts produced by `rustc`. + + +### Optimization with coverage-counter expressions + +In the [subsection related to the +integration](#integrating-the-instrumentation-into-kani), we noted that we could +follow an alternative approach where we only instrument coverage statements that +correspond to physical counters. In fact, this would be the logical choice since +the replacement of physical counters by expression-based counters would also be +a performance optimization for us. + +However, the expressions used in expression-based counters are built with the +arithmetic operators `Add` (`+`) and `Sub` (`-`). On the other hand, the +coverage checks performed by Kani have a boolean meaning: you either cover a +region or you do not. Thus, there are many cases where these two notions of +coverage counters are incompatible. For example, let's say we have this +function: + +```rust +fn check_value(val: u32) { + if val == VALUE { + do_this(); + } else { + do_that(); + } + do_something_else(); +} +``` + +One way to optimize the counters in this function is to have two physical +counters for the branches of the `if` statement (`c1` and `c2`), and then an +expression-based counter associated to the `do_something_else()` statement +adding those (i.e., `c3 = c1 + c2`). If we have, for example, two executions for +this program, with each one taking a different branch, then the results for the +coverage counters will be `c1 = 1`, `c2 = 1` and `c3 = c1 + c2 = 2`. + +But what does `c3 = 2` mean in the context of a verification-based coverage +result? That is not clear. For instance, in a Kani trace, you could have a +nondeterministic value for `val` which just happens to be `val == VALUE` and not +at the same time. This would result in the same counters (`c1 = 1`, `c2 = 1` and +`c3 = 2`), but the program is being run only once! + +Note that finding a verification-based replacement for the runtime operators in +counter-based expressions is an interesting research topic. If we could +establish a relation between the runtime and verification expressions, then we +could avoid the instrumentation of coverage checks for expression-based +counters. For example, could we replace the `Add` operator (`+`) with an `Or` +operator (`||`)? Intuitively, this makes sense since verification-based coverage +counters are binary. It also seems to work for our example since covering any of +the branches should result in the `do_something_else()` statement being covered +as well, with the counter values now being `c1 = 1`, `c2 = 1` and `c3 = 1`. +However, it is not clear that this would work for all cases, nor it is clear +that we can replace `Sub` with another verification-based operator. From 8c1784946f3adf4b820d238f95e8865a5af229ab Mon Sep 17 00:00:00 2001 From: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:35:00 -0400 Subject: [PATCH 042/159] Adopt Rust's source-based code coverage instrumentation (#3119) This PR replaces the line-based coverage instrumentation we introduced in #2609 with the standard source-based code coverage instrumentation performed by the Rust compiler. As a result, we now insert code coverage checks in the `StatementKind::Coverage(..)` statements produced by the Rust compiler during compilation. These checks include coverage-relevant information[^note-internal] such as the coverage counter/expression they represent [^note-instrument]. Both the coverage metadata (`kanimap`) and coverage results (`kaniraw`) are saved into files after the verification stage. Unfortunately, we currently have a chicken-egg problem with this PR and #3121, where we introduce a tool named `kani-cov` to postprocess coverage results. As explained in #3143, `kani-cov` is expected to be an alias for the `cov` subcommand and provide most of the postprocessing features for coverage-related purposes. But, the tool will likely be introduced after this change. Therefore, we propose to temporarily print a list of the regions in each function with their associated coverage status (i.e., `COVERED` or `UNCOVERED`). ### Source-based code coverage: An example The main advantage of source-based coverage results is their precision with respect to the source code. The [Source-based Code Coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html) documentation explains more details about the LLVM coverage workflow and its different options. For example, let's take this Rust code: ```rust 1 fn _other_function() { 2 println!("Hello, world!"); 3 } 4 5 fn test_cov(val: u32) -> bool { 6 if val < 3 || val == 42 { 7 true 8 } else { 9 false 10 } 11 } 12 13 #[cfg_attr(kani, kani::proof)] 14 fn main() { 15 let test1 = test_cov(1); 16 let test2 = test_cov(2); 17 assert!(test1); 18 assert!(test2); 19 } ``` Compiling and running the program with `rustc` and the `-C instrument-coverage` flag, and using the LLVM tools can get us the following coverage result: ![Image](https://github.com/model-checking/kani/assets/73246657/9070e390-6e0b-4add-828d-d9f9caacad07) In contrast, the `cargo kani --coverage -Zsource-coverage` command currently generates: ``` src/main.rs (main) * 14:1 - 19:2 COVERED src/main.rs (test_cov) * 5:1 - 6:15 COVERED * 6:19 - 6:28 UNCOVERED * 7:9 - 7:13 COVERED * 9:9 - 9:14 UNCOVERED * 11:1 - 11:2 COVERED ``` which is a verification-based coverage result almost equivalent to the runtime coverage results. ### Benchmarking We have evaluated the performance impact of the instrumentation using the `kani-perf.sh` suite (14 benchmarks). For each test, we compare the average time to run standard verification against the average time to run verification with the source-based code coverage feature enabled[^note-line-evaluation]. The evaluation has been performed on an EC2 `m5a.4xlarge` instance running Ubuntu 22.04. The experimental data has been obtained by running the `kani-perf.sh` script 10 times for each version (`only verification` and `verification + coverage`), computing the average and standard deviation. We've split this data into `small` (tests taking 60s or less) and `large` (tests taking more than 60s) and drawn the two graphs below. #### Performance comparison - `small` benchmarks ![performance_comparison_small](https://github.com/user-attachments/assets/679cf412-0193-4b0c-a78c-2d0fb702706f) #### Performance comparison - `large` benchmarks ![performance_comparison_large](https://github.com/user-attachments/assets/4bb5a895-7f57-49e0-86b5-5fea67fad939) #### Comments on performance Looking at the small tests, the performance impact seems negligible in such cases. The difference is more noticeable in the large tests, where the time to run verification and coverage can take 2x or even more. It wouldn't be surprising that, as programs become larger, the complexity of the coverage checking grows exponentially as well. However, since most verification jobs don't take longer than 30min (1800s), it's OK to say that coverage checking represents a 100-200% slowdown in the worst case w.r.t. standard verification. It's also worth noting a few other things: * The standard deviation remains similar in most cases, meaning that the coverage feature doesn't have an impact on their stability. * We haven't tried any SAT solvers other than the ones used by default for each benchmark. It's possible that other solvers perform better/worse with the coverage feature enabled. ### Call-outs * The soundness issue documented in #3441. * The issue with saving coverage mappings for non-reachable functions documented in #3445. * I've modified the test cases in `tests/coverage/` to test this feature. Since this technique is simpler, we don't need that many test cases. However, it's possible I've left some test cases which don't contribute much. Please let me know if you want to add/remove a test case. [^note-internal]: The coverage mappings can't be accessed through the StableMIR interface so we retrieve them through the internal API. [^note-instrument]: The instrumentation replaces certain counters with expressions based on other counters when possible to avoid a part of the runtime overhead. More details can be found [here](https://github.com/rust-lang/rustc-dev-guide/blob/master/src/llvm-coverage-instrumentation.md#mir-pass-instrumentcoverage). Unfortunately, we can't avoid instrumenting expressions at the moment. [^note-line-evaluation]: We have not compared performance against the line-based code coverage feature because it doesn't seem worth it. The line-based coverage feature is guaranteed to include more coverage checks than the source-based one for any function. In addition, source-based results are more precise than line-based ones. So this change represents both a quantitative and qualitative improvement. 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 | 53 ++++++ .../codegen_cprover_gotoc/codegen/assert.rs | 16 +- .../codegen_cprover_gotoc/codegen/block.rs | 33 +--- .../codegen_cprover_gotoc/codegen/function.rs | 71 +++++++ .../codegen/statement.rs | 19 +- kani-driver/Cargo.toml | 1 + kani-driver/src/args/mod.rs | 4 +- kani-driver/src/call_cargo.rs | 2 +- kani-driver/src/call_cbmc.rs | 100 +++++++++- kani-driver/src/call_single_file.rs | 5 + kani-driver/src/cbmc_output_parser.rs | 4 +- kani-driver/src/cbmc_property_renderer.rs | 74 ++------ kani-driver/src/coverage/cov_results.rs | 96 ++++++++++ kani-driver/src/coverage/cov_session.rs | 176 ++++++++++++++++++ kani-driver/src/coverage/mod.rs | 5 + kani-driver/src/harness_runner.rs | 17 +- kani-driver/src/main.rs | 19 ++ kani-driver/src/project.rs | 18 +- kani_metadata/src/unstable.rs | 5 +- tests/coverage/abort/expected | 13 ++ .../coverage/{unreachable => }/abort/main.rs | 0 tests/coverage/assert/expected | 9 + .../coverage/{unreachable => }/assert/test.rs | 0 tests/coverage/assert_eq/expected | 8 + .../{unreachable => }/assert_eq/test.rs | 0 tests/coverage/assert_ne/expected | 9 + .../{unreachable => }/assert_ne/test.rs | 0 tests/coverage/break/expected | 13 ++ .../coverage/{unreachable => }/break/main.rs | 2 +- tests/coverage/compare/expected | 11 ++ .../{unreachable => }/compare/main.rs | 2 +- tests/coverage/contradiction/expected | 9 + .../{unreachable => }/contradiction/main.rs | 0 tests/coverage/debug-assert/expected | 10 + tests/coverage/debug-assert/main.rs | 15 ++ tests/coverage/div-zero/expected | 9 + .../reachable_pass => div-zero}/test.rs | 2 +- tests/coverage/early-return/expected | 12 ++ .../{unreachable => }/early-return/main.rs | 0 tests/coverage/if-statement-multi/expected | 11 ++ tests/coverage/if-statement-multi/test.rs | 26 +++ tests/coverage/if-statement/expected | 14 ++ .../{unreachable => }/if-statement/main.rs | 2 +- .../assert_uncovered_end/expected | 9 + .../known_issues/assert_uncovered_end/test.rs | 14 ++ .../known_issues/assume_assert/expected | 4 + .../known_issues/assume_assert/main.rs | 15 ++ .../known_issues/out-of-bounds/expected | 7 + .../known_issues/out-of-bounds/test.rs | 15 ++ tests/coverage/known_issues/variant/expected | 14 ++ .../variant/main.rs | 5 +- tests/coverage/multiple-harnesses/expected | 37 ++++ .../multiple-harnesses/main.rs | 0 tests/coverage/overflow-failure/expected | 9 + .../test.rs | 4 +- .../coverage/overflow-full-coverage/expected | 9 + .../test.rs | 4 +- .../coverage/reachable/assert-false/expected | 8 - tests/coverage/reachable/assert-false/main.rs | 19 -- .../reachable/assert/reachable_pass/expected | 4 - .../reachable/assert/reachable_pass/test.rs | 10 - .../reachable/bounds/reachable_fail/expected | 4 - .../reachable/bounds/reachable_fail/test.rs | 11 -- .../div-zero/reachable_fail/expected | 4 - .../reachable/div-zero/reachable_fail/test.rs | 11 -- .../div-zero/reachable_pass/expected | 4 - .../overflow/reachable_fail/expected | 5 - .../overflow/reachable_pass/expected | 7 - .../rem-zero/reachable_fail/expected | 4 - .../reachable/rem-zero/reachable_fail/test.rs | 11 -- .../rem-zero/reachable_pass/expected | 4 - .../reachable/rem-zero/reachable_pass/test.rs | 11 -- tests/coverage/unreachable/abort/expected | 7 - tests/coverage/unreachable/assert/expected | 7 - tests/coverage/unreachable/assert_eq/expected | 5 - tests/coverage/unreachable/assert_ne/expected | 6 - .../unreachable/assume_assert/expected | 4 - .../unreachable/assume_assert/main.rs | 8 - tests/coverage/unreachable/bounds/expected | 4 - tests/coverage/unreachable/bounds/test.rs | 12 -- tests/coverage/unreachable/break/expected | 9 - tests/coverage/unreachable/check_id/expected | 16 -- tests/coverage/unreachable/check_id/main.rs | 25 --- tests/coverage/unreachable/compare/expected | 7 - .../unreachable/contradiction/expected | 7 - .../unreachable/debug-assert/expected | 4 - .../coverage/unreachable/debug-assert/main.rs | 10 - tests/coverage/unreachable/divide/expected | 7 - tests/coverage/unreachable/divide/main.rs | 19 -- .../unreachable/early-return/expected | 10 - .../unreachable/if-statement/expected | 10 - .../unreachable/multiple-harnesses/expected | 39 ---- tests/coverage/unreachable/return/expected | 8 - tests/coverage/unreachable/return/main.rs | 17 -- .../unreachable/tutorial_unreachable/expected | 5 - .../unreachable/tutorial_unreachable/main.rs | 11 -- tests/coverage/unreachable/variant/expected | 10 - tests/coverage/unreachable/vectors/expected | 6 - tests/coverage/unreachable/vectors/main.rs | 19 -- .../unreachable/while-loop-break/expected | 11 -- tests/coverage/while-loop-break/expected | 13 ++ .../while-loop-break/main.rs | 0 tests/ui/save-coverage-results/expected | 3 + tests/ui/save-coverage-results/test.rs | 25 +++ tools/compiletest/src/runtest.rs | 2 +- 105 files changed, 950 insertions(+), 554 deletions(-) create mode 100644 kani-driver/src/coverage/cov_results.rs create mode 100644 kani-driver/src/coverage/cov_session.rs create mode 100644 kani-driver/src/coverage/mod.rs create mode 100644 tests/coverage/abort/expected rename tests/coverage/{unreachable => }/abort/main.rs (100%) create mode 100644 tests/coverage/assert/expected rename tests/coverage/{unreachable => }/assert/test.rs (100%) create mode 100644 tests/coverage/assert_eq/expected rename tests/coverage/{unreachable => }/assert_eq/test.rs (100%) create mode 100644 tests/coverage/assert_ne/expected rename tests/coverage/{unreachable => }/assert_ne/test.rs (100%) create mode 100644 tests/coverage/break/expected rename tests/coverage/{unreachable => }/break/main.rs (82%) create mode 100644 tests/coverage/compare/expected rename tests/coverage/{unreachable => }/compare/main.rs (73%) create mode 100644 tests/coverage/contradiction/expected rename tests/coverage/{unreachable => }/contradiction/main.rs (100%) create mode 100644 tests/coverage/debug-assert/expected create mode 100644 tests/coverage/debug-assert/main.rs create mode 100644 tests/coverage/div-zero/expected rename tests/coverage/{reachable/div-zero/reachable_pass => div-zero}/test.rs (64%) create mode 100644 tests/coverage/early-return/expected rename tests/coverage/{unreachable => }/early-return/main.rs (100%) create mode 100644 tests/coverage/if-statement-multi/expected create mode 100644 tests/coverage/if-statement-multi/test.rs create mode 100644 tests/coverage/if-statement/expected rename tests/coverage/{unreachable => }/if-statement/main.rs (78%) create mode 100644 tests/coverage/known_issues/assert_uncovered_end/expected create mode 100644 tests/coverage/known_issues/assert_uncovered_end/test.rs create mode 100644 tests/coverage/known_issues/assume_assert/expected create mode 100644 tests/coverage/known_issues/assume_assert/main.rs create mode 100644 tests/coverage/known_issues/out-of-bounds/expected create mode 100644 tests/coverage/known_issues/out-of-bounds/test.rs create mode 100644 tests/coverage/known_issues/variant/expected rename tests/coverage/{unreachable => known_issues}/variant/main.rs (72%) create mode 100644 tests/coverage/multiple-harnesses/expected rename tests/coverage/{unreachable => }/multiple-harnesses/main.rs (100%) create mode 100644 tests/coverage/overflow-failure/expected rename tests/coverage/{reachable/overflow/reachable_fail => overflow-failure}/test.rs (66%) create mode 100644 tests/coverage/overflow-full-coverage/expected rename tests/coverage/{reachable/overflow/reachable_pass => overflow-full-coverage}/test.rs (62%) delete mode 100644 tests/coverage/reachable/assert-false/expected delete mode 100644 tests/coverage/reachable/assert-false/main.rs delete mode 100644 tests/coverage/reachable/assert/reachable_pass/expected delete mode 100644 tests/coverage/reachable/assert/reachable_pass/test.rs delete mode 100644 tests/coverage/reachable/bounds/reachable_fail/expected delete mode 100644 tests/coverage/reachable/bounds/reachable_fail/test.rs delete mode 100644 tests/coverage/reachable/div-zero/reachable_fail/expected delete mode 100644 tests/coverage/reachable/div-zero/reachable_fail/test.rs delete mode 100644 tests/coverage/reachable/div-zero/reachable_pass/expected delete mode 100644 tests/coverage/reachable/overflow/reachable_fail/expected delete mode 100644 tests/coverage/reachable/overflow/reachable_pass/expected delete mode 100644 tests/coverage/reachable/rem-zero/reachable_fail/expected delete mode 100644 tests/coverage/reachable/rem-zero/reachable_fail/test.rs delete mode 100644 tests/coverage/reachable/rem-zero/reachable_pass/expected delete mode 100644 tests/coverage/reachable/rem-zero/reachable_pass/test.rs delete mode 100644 tests/coverage/unreachable/abort/expected delete mode 100644 tests/coverage/unreachable/assert/expected delete mode 100644 tests/coverage/unreachable/assert_eq/expected delete mode 100644 tests/coverage/unreachable/assert_ne/expected delete mode 100644 tests/coverage/unreachable/assume_assert/expected delete mode 100644 tests/coverage/unreachable/assume_assert/main.rs delete mode 100644 tests/coverage/unreachable/bounds/expected delete mode 100644 tests/coverage/unreachable/bounds/test.rs delete mode 100644 tests/coverage/unreachable/break/expected delete mode 100644 tests/coverage/unreachable/check_id/expected delete mode 100644 tests/coverage/unreachable/check_id/main.rs delete mode 100644 tests/coverage/unreachable/compare/expected delete mode 100644 tests/coverage/unreachable/contradiction/expected delete mode 100644 tests/coverage/unreachable/debug-assert/expected delete mode 100644 tests/coverage/unreachable/debug-assert/main.rs delete mode 100644 tests/coverage/unreachable/divide/expected delete mode 100644 tests/coverage/unreachable/divide/main.rs delete mode 100644 tests/coverage/unreachable/early-return/expected delete mode 100644 tests/coverage/unreachable/if-statement/expected delete mode 100644 tests/coverage/unreachable/multiple-harnesses/expected delete mode 100644 tests/coverage/unreachable/return/expected delete mode 100644 tests/coverage/unreachable/return/main.rs delete mode 100644 tests/coverage/unreachable/tutorial_unreachable/expected delete mode 100644 tests/coverage/unreachable/tutorial_unreachable/main.rs delete mode 100644 tests/coverage/unreachable/variant/expected delete mode 100644 tests/coverage/unreachable/vectors/expected delete mode 100644 tests/coverage/unreachable/vectors/main.rs delete mode 100644 tests/coverage/unreachable/while-loop-break/expected create mode 100644 tests/coverage/while-loop-break/expected rename tests/coverage/{unreachable => }/while-loop-break/main.rs (100%) create mode 100644 tests/ui/save-coverage-results/expected create mode 100644 tests/ui/save-coverage-results/test.rs diff --git a/Cargo.lock b/Cargo.lock index d13ff68b32ff..0b8f681ae6c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,6 +316,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "either" version = "1.13.0" @@ -500,6 +509,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", + "time", "toml", "tracing", "tracing-subscriber", @@ -660,6 +670,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -767,6 +783,12 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1179,6 +1201,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "toml" version = "0.8.19" diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs index f78cf3eba707..4ee81d0c7d3e 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs @@ -21,6 +21,7 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::{Expr, Location, Stmt, Type}; use cbmc::InternedString; +use rustc_middle::mir::coverage::CodeRegion; use stable_mir::mir::{Place, ProjectionElem}; use stable_mir::ty::{Span as SpanStable, TypeAndMut}; use strum_macros::{AsRefStr, EnumString}; @@ -148,18 +149,19 @@ impl<'tcx> GotocCtx<'tcx> { } /// Generate a cover statement for code coverage reports. - pub fn codegen_coverage(&self, span: SpanStable) -> Stmt { + pub fn codegen_coverage( + &self, + counter_data: &str, + span: SpanStable, + code_region: CodeRegion, + ) -> Stmt { let loc = self.codegen_caller_span_stable(span); // Should use Stmt::cover, but currently this doesn't work with CBMC // unless it is run with '--cover cover' (see // https://github.com/diffblue/cbmc/issues/6613). So for now use // `assert(false)`. - self.codegen_assert( - Expr::bool_false(), - PropertyClass::CodeCoverage, - "code coverage for location", - loc, - ) + let msg = format!("{counter_data} - {code_region:?}"); + self.codegen_assert(Expr::bool_false(), PropertyClass::CodeCoverage, &msg, loc) } // The above represent the basic operations we can perform w.r.t. assert/assume/cover diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs index 5fe28097a2e0..1b28de887002 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/block.rs @@ -20,53 +20,24 @@ impl<'tcx> GotocCtx<'tcx> { pub fn codegen_block(&mut self, bb: BasicBlockIdx, bbd: &BasicBlock) { debug!(?bb, "codegen_block"); let label = bb_label(bb); - let check_coverage = self.queries.args().check_coverage; // the first statement should be labelled. if there is no statements, then the // terminator should be labelled. match bbd.statements.len() { 0 => { let term = &bbd.terminator; let tcode = self.codegen_terminator(term); - // When checking coverage, the `coverage` check should be - // labelled instead. - if check_coverage { - let span = term.span; - let cover = self.codegen_coverage(span); - self.current_fn_mut().push_onto_block(cover.with_label(label)); - self.current_fn_mut().push_onto_block(tcode); - } else { - self.current_fn_mut().push_onto_block(tcode.with_label(label)); - } + self.current_fn_mut().push_onto_block(tcode.with_label(label)); } _ => { let stmt = &bbd.statements[0]; let scode = self.codegen_statement(stmt); - // When checking coverage, the `coverage` check should be - // labelled instead. - if check_coverage { - let span = stmt.span; - let cover = self.codegen_coverage(span); - self.current_fn_mut().push_onto_block(cover.with_label(label)); - self.current_fn_mut().push_onto_block(scode); - } else { - self.current_fn_mut().push_onto_block(scode.with_label(label)); - } + self.current_fn_mut().push_onto_block(scode.with_label(label)); for s in &bbd.statements[1..] { - if check_coverage { - let span = s.span; - let cover = self.codegen_coverage(span); - self.current_fn_mut().push_onto_block(cover); - } let stmt = self.codegen_statement(s); self.current_fn_mut().push_onto_block(stmt); } let term = &bbd.terminator; - if check_coverage { - let span = term.span; - let cover = self.codegen_coverage(span); - self.current_fn_mut().push_onto_block(cover); - } let tcode = self.codegen_terminator(term); self.current_fn_mut().push_onto_block(tcode); } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index a1afa343a6e7..0793e0c4688f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -216,3 +216,74 @@ impl<'tcx> GotocCtx<'tcx> { self.reset_current_fn(); } } + +pub mod rustc_smir { + use crate::stable_mir::CrateDef; + use rustc_middle::mir::coverage::CodeRegion; + use rustc_middle::mir::coverage::CovTerm; + use rustc_middle::mir::coverage::MappingKind::Code; + use rustc_middle::ty::TyCtxt; + use stable_mir::mir::mono::Instance; + use stable_mir::Opaque; + + type CoverageOpaque = stable_mir::Opaque; + + /// Retrieves the `CodeRegion` associated with the data in a + /// `CoverageOpaque` object. + pub fn region_from_coverage_opaque( + tcx: TyCtxt, + coverage_opaque: &CoverageOpaque, + instance: Instance, + ) -> Option { + let cov_term = parse_coverage_opaque(coverage_opaque); + region_from_coverage(tcx, cov_term, instance) + } + + /// Retrieves the `CodeRegion` associated with a `CovTerm` object. + /// + /// Note: This function could be in the internal `rustc` impl for `Coverage`. + pub fn region_from_coverage( + tcx: TyCtxt<'_>, + coverage: CovTerm, + instance: Instance, + ) -> Option { + // We need to pull the coverage info from the internal MIR instance. + let instance_def = rustc_smir::rustc_internal::internal(tcx, instance.def.def_id()); + let body = tcx.instance_mir(rustc_middle::ty::InstanceKind::Item(instance_def)); + + // Some functions, like `std` ones, may not have coverage info attached + // to them because they have been compiled without coverage flags. + if let Some(cov_info) = &body.function_coverage_info { + // Iterate over the coverage mappings and match with the coverage term. + for mapping in &cov_info.mappings { + let Code(term) = mapping.kind else { unreachable!() }; + if term == coverage { + return Some(mapping.code_region.clone()); + } + } + } + None + } + + /// Parse a `CoverageOpaque` item and return the corresponding `CovTerm`: + /// + /// + /// At present, a `CovTerm` can be one of the following: + /// - `CounterIncrement()`: A physical counter. + /// - `ExpressionUsed()`: An expression-based counter. + /// - `Zero`: A counter with a constant zero value. + fn parse_coverage_opaque(coverage_opaque: &Opaque) -> CovTerm { + let coverage_str = coverage_opaque.to_string(); + if let Some(rest) = coverage_str.strip_prefix("CounterIncrement(") { + let (num_str, _rest) = rest.split_once(')').unwrap(); + let num = num_str.parse::().unwrap(); + CovTerm::Counter(num.into()) + } else if let Some(rest) = coverage_str.strip_prefix("ExpressionUsed(") { + let (num_str, _rest) = rest.split_once(')').unwrap(); + let num = num_str.parse::().unwrap(); + CovTerm::Expression(num.into()) + } else { + CovTerm::Zero + } + } +} diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs index b8db1a3d52b6..81407c4dc704 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs @@ -3,6 +3,7 @@ use super::typ::TypeExt; use super::typ::FN_RETURN_VOID_VAR_NAME; use super::{bb_label, PropertyClass}; +use crate::codegen_cprover_gotoc::codegen::function::rustc_smir::region_from_coverage_opaque; use crate::codegen_cprover_gotoc::{GotocCtx, VtableCtx}; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{Expr, Location, Stmt, Type}; @@ -158,12 +159,28 @@ impl<'tcx> GotocCtx<'tcx> { location, ) } + StatementKind::Coverage(coverage_opaque) => { + let function_name = self.current_fn().readable_name(); + let instance = self.current_fn().instance_stable(); + let counter_data = format!("{coverage_opaque:?} ${function_name}$"); + let maybe_code_region = + region_from_coverage_opaque(self.tcx, &coverage_opaque, instance); + if let Some(code_region) = maybe_code_region { + let coverage_stmt = + self.codegen_coverage(&counter_data, stmt.span, code_region); + // TODO: Avoid single-statement blocks when conversion of + // standalone statements to the irep format is fixed. + // More details in + Stmt::block(vec![coverage_stmt], location) + } else { + Stmt::skip(location) + } + } StatementKind::PlaceMention(_) => todo!(), StatementKind::FakeRead(..) | StatementKind::Retag(_, _) | StatementKind::AscribeUserType { .. } | StatementKind::Nop - | StatementKind::Coverage { .. } | StatementKind::ConstEvalCounter => Stmt::skip(location), } .with_location(location) diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index c57ec8e8e2f2..27fef66ffb65 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -34,6 +34,7 @@ tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_de tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} rand = "0.8" which = "6" +time = {version = "0.3.36", features = ["formatting"]} # A good set of suggested dependencies can be found in rustup: # https://github.com/rust-lang/rustup/blob/master/Cargo.toml diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 2d7593e8050a..c3cfc113af64 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -615,12 +615,12 @@ impl ValidateArgs for VerificationArgs { } if self.coverage - && !self.common_args.unstable_features.contains(UnstableFeature::LineCoverage) + && !self.common_args.unstable_features.contains(UnstableFeature::SourceCoverage) { return Err(Error::raw( ErrorKind::MissingRequiredArgument, "The `--coverage` argument is unstable and requires `-Z \ - line-coverage` to be used.", + source-coverage` to be used.", )); } diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index 4e8e83b562af..e69062a0cd4f 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -206,7 +206,7 @@ impl KaniSession { }) } - fn cargo_metadata(&self, build_target: &str) -> Result { + pub fn cargo_metadata(&self, build_target: &str) -> Result { let mut cmd = MetadataCommand::new(); // restrict metadata command to host platform. References: diff --git a/kani-driver/src/call_cbmc.rs b/kani-driver/src/call_cbmc.rs index 387a9723fcdb..bc4424aeb231 100644 --- a/kani-driver/src/call_cbmc.rs +++ b/kani-driver/src/call_cbmc.rs @@ -3,10 +3,15 @@ use anyhow::{bail, Result}; use kani_metadata::{CbmcSolver, HarnessMetadata}; +use regex::Regex; +use rustc_demangle::demangle; +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; use std::ffi::OsString; use std::fmt::Write; use std::path::Path; use std::process::Command; +use std::sync::OnceLock; use std::time::{Duration, Instant}; use crate::args::{OutputFormat, VerificationArgs}; @@ -14,6 +19,8 @@ use crate::cbmc_output_parser::{ extract_results, process_cbmc_output, CheckStatus, Property, VerificationOutput, }; use crate::cbmc_property_renderer::{format_coverage, format_result, kani_cbmc_output_filter}; +use crate::coverage::cov_results::{CoverageCheck, CoverageResults}; +use crate::coverage::cov_results::{CoverageRegion, CoverageTerm}; use crate::session::KaniSession; /// We will use Cadical by default since it performed better than MiniSAT in our analysis. @@ -54,6 +61,8 @@ pub struct VerificationResult { pub runtime: Duration, /// Whether concrete playback generated a test pub generated_concrete_test: bool, + /// The coverage results + pub coverage_results: Option, } impl KaniSession { @@ -273,12 +282,14 @@ impl VerificationResult { if let Some(results) = results { let (status, failed_properties) = verification_outcome_from_properties(&results, should_panic); + let coverage_results = coverage_results_from_properties(&results); VerificationResult { status, failed_properties, results: Ok(results), runtime, generated_concrete_test: false, + coverage_results, } } else { // We never got results from CBMC - something went wrong (e.g. crash) so it's failure @@ -288,6 +299,7 @@ impl VerificationResult { results: Err(output.process_status), runtime, generated_concrete_test: false, + coverage_results: None, } } } @@ -299,6 +311,7 @@ impl VerificationResult { results: Ok(vec![]), runtime: Duration::from_secs(0), generated_concrete_test: false, + coverage_results: None, } } @@ -312,23 +325,26 @@ impl VerificationResult { results: Err(42), runtime: Duration::from_secs(0), generated_concrete_test: false, + coverage_results: None, } } - pub fn render( - &self, - output_format: &OutputFormat, - should_panic: bool, - coverage_mode: bool, - ) -> String { + pub fn render(&self, output_format: &OutputFormat, should_panic: bool) -> String { match &self.results { Ok(results) => { let status = self.status; let failed_properties = self.failed_properties; let show_checks = matches!(output_format, OutputFormat::Regular); - let mut result = if coverage_mode { - format_coverage(results, status, should_panic, failed_properties, show_checks) + let mut result = if let Some(cov_results) = &self.coverage_results { + format_coverage( + results, + cov_results, + status, + should_panic, + failed_properties, + show_checks, + ) } else { format_result(results, status, should_panic, failed_properties, show_checks) }; @@ -404,6 +420,74 @@ fn determine_failed_properties(properties: &[Property]) -> FailedProperties { } } +fn coverage_results_from_properties(properties: &[Property]) -> Option { + let cov_properties: Vec<&Property> = + properties.iter().filter(|p| p.is_code_coverage_property()).collect(); + + if cov_properties.is_empty() { + return None; + } + + // Postprocessing the coverage results involves matching on the descriptions + // of code coverage properties with the `counter_re` regex. These are two + // real examples of such descriptions: + // + // ``` + // CounterIncrement(0) $test_cov$ - src/main.rs:5:1 - 6:15 + // ExpressionUsed(0) $test_cov$ - src/main.rs:6:19 - 6:28 + // ``` + // + // The span is further processed to extract the code region attributes. + // Ideally, we should have coverage mappings (i.e., the relation between + // counters and code regions) available in the coverage metadata: + // . If that were the + // case, we would not need the spans in these descriptions. + let counter_re = { + static COUNTER_RE: OnceLock = OnceLock::new(); + COUNTER_RE.get_or_init(|| { + Regex::new( + r#"^(?CounterIncrement|ExpressionUsed)\((?[0-9]+)\) \$(?[^\$]+)\$ - (?.+)"#, + ) + .unwrap() + }) + }; + + let mut coverage_results: BTreeMap> = BTreeMap::default(); + + for prop in cov_properties { + let mut prop_processed = false; + + if let Some(captures) = counter_re.captures(&prop.description) { + let kind = &captures["kind"]; + let counter_num = &captures["counter_num"]; + let function = demangle(&captures["func_name"]).to_string(); + let status = prop.status; + let span = captures["span"].to_string(); + + let counter_id = counter_num.parse().unwrap(); + let term = match kind { + "CounterIncrement" => CoverageTerm::Counter(counter_id), + "ExpressionUsed" => CoverageTerm::Expression(counter_id), + _ => unreachable!("counter kind could not be recognized: {:?}", kind), + }; + let region = CoverageRegion::from_str(span); + + let cov_check = CoverageCheck::new(function, term, region, status); + let file = cov_check.region.file.clone(); + + if let Entry::Vacant(e) = coverage_results.entry(file.clone()) { + e.insert(vec![cov_check]); + } else { + coverage_results.entry(file).and_modify(|checks| checks.push(cov_check)); + } + prop_processed = true; + } + + assert!(prop_processed, "error: coverage property not processed\n{prop:?}"); + } + + Some(CoverageResults::new(coverage_results)) +} /// Solve Unwind Value from conflicting inputs of unwind values. (--default-unwind, annotation-unwind, --unwind) pub fn resolve_unwind_value( args: &VerificationArgs, diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index 4b30fe877507..868d1adce636 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -155,6 +155,11 @@ impl KaniSession { pub fn kani_rustc_flags(&self, lib_config: LibConfig) -> Vec { let mut flags: Vec<_> = base_rustc_flags(lib_config); // We only use panic abort strategy for verification since we cannot handle unwind logic. + if self.args.coverage { + flags.extend_from_slice( + &["-C", "instrument-coverage", "-Z", "no-profiler-runtime"].map(OsString::from), + ); + } flags.extend_from_slice( &[ "-C", diff --git a/kani-driver/src/cbmc_output_parser.rs b/kani-driver/src/cbmc_output_parser.rs index b3a78e8d03e2..8ef1ee153106 100644 --- a/kani-driver/src/cbmc_output_parser.rs +++ b/kani-driver/src/cbmc_output_parser.rs @@ -27,7 +27,7 @@ use anyhow::Result; use console::style; use pathdiff::diff_paths; use rustc_demangle::demangle; -use serde::{Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize}; use std::env; use std::io::{BufRead, BufReader}; @@ -321,7 +321,7 @@ impl std::fmt::Display for TraceData { } } -#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "UPPERCASE")] pub enum CheckStatus { Failure, diff --git a/kani-driver/src/cbmc_property_renderer.rs b/kani-driver/src/cbmc_property_renderer.rs index 4f32028b5866..a0202169863c 100644 --- a/kani-driver/src/cbmc_property_renderer.rs +++ b/kani-driver/src/cbmc_property_renderer.rs @@ -4,12 +4,12 @@ use crate::args::OutputFormat; use crate::call_cbmc::{FailedProperties, VerificationStatus}; use crate::cbmc_output_parser::{CheckStatus, ParserItem, Property, TraceItem}; +use crate::coverage::cov_results::CoverageResults; use console::style; use once_cell::sync::Lazy; use regex::Regex; use rustc_demangle::demangle; -use std::collections::{BTreeMap, HashMap}; -use strum_macros::{AsRefStr, Display}; +use std::collections::HashMap; type CbmcAltDescriptions = HashMap<&'static str, Vec<(&'static str, Option<&'static str>)>>; @@ -150,15 +150,6 @@ static CBMC_ALT_DESCRIPTIONS: Lazy = Lazy::new(|| { map }); -#[derive(PartialEq, Eq, AsRefStr, Clone, Copy, Display)] -#[strum(serialize_all = "UPPERCASE")] -// The status of coverage reported by Kani -enum CoverageStatus { - Full, - Partial, - None, -} - const UNSUPPORTED_CONSTRUCT_DESC: &str = "is not currently supported by Kani"; const UNWINDING_ASSERT_DESC: &str = "unwinding assertion loop"; const UNWINDING_ASSERT_REC_DESC: &str = "recursion unwinding assertion"; @@ -431,72 +422,31 @@ pub fn format_result( result_str } -/// Separate checks into coverage and non-coverage based on property class and format them separately for --coverage. We report both verification and processed coverage -/// results +/// Separate checks into coverage and non-coverage based on property class and +/// format them separately for `--coverage`. Then we report both verification +/// and processed coverage results. +/// +/// Note: The reporting of coverage results should be removed once `kani-cov` is +/// introduced. pub fn format_coverage( properties: &[Property], + cov_results: &CoverageResults, status: VerificationStatus, should_panic: bool, failed_properties: FailedProperties, show_checks: bool, ) -> String { - let (coverage_checks, non_coverage_checks): (Vec, Vec) = + let (_coverage_checks, non_coverage_checks): (Vec, Vec) = properties.iter().cloned().partition(|x| x.property_class() == "code_coverage"); let verification_output = format_result(&non_coverage_checks, status, should_panic, failed_properties, show_checks); - let coverage_output = format_result_coverage(&coverage_checks); - let result = format!("{}\n{}", verification_output, coverage_output); + let cov_results_intro = "Source-based code coverage results:"; + let result = format!("{}\n{}\n\n{}", verification_output, cov_results_intro, cov_results); result } -/// Generate coverage result from all coverage properties (i.e., checks with `code_coverage` property class). -/// Loops through each of the checks with the `code_coverage` property class on a line and gives: -/// - A status `FULL` if all checks pertaining to a line number are `COVERED` -/// - A status `NONE` if all checks related to a line are `UNCOVERED` -/// - Otherwise (i.e., if the line contains both) it reports `PARTIAL`. -/// -/// Used when the user requests coverage information with `--coverage`. -/// Output is tested through the `coverage-based` testing suite, not the regular -/// `expected` suite. -fn format_result_coverage(properties: &[Property]) -> String { - let mut formatted_output = String::new(); - formatted_output.push_str("\nCoverage Results:\n"); - - let mut coverage_results: BTreeMap> = - BTreeMap::default(); - for prop in properties { - let src = prop.source_location.clone(); - let file_entries = coverage_results.entry(src.file.unwrap()).or_default(); - let check_status = if prop.status == CheckStatus::Covered { - CoverageStatus::Full - } else { - CoverageStatus::None - }; - - // Create Map> - file_entries - .entry(src.line.unwrap().parse().unwrap()) - .and_modify(|line_status| { - if *line_status != check_status { - *line_status = CoverageStatus::Partial - } - }) - .or_insert(check_status); - } - - // Create formatted string that is returned to the user as output - for (file, checks) in coverage_results.iter() { - for (line_number, coverage_status) in checks { - formatted_output.push_str(&format!("{}, {}, {}\n", file, line_number, coverage_status)); - } - formatted_output.push('\n'); - } - - formatted_output -} - /// Attempts to build a message for a failed property with as much detailed /// information on the source location as possible. fn build_failure_message(description: String, trace: &Option>) -> String { diff --git a/kani-driver/src/coverage/cov_results.rs b/kani-driver/src/coverage/cov_results.rs new file mode 100644 index 000000000000..845ae7de21bb --- /dev/null +++ b/kani-driver/src/coverage/cov_results.rs @@ -0,0 +1,96 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::cbmc_output_parser::CheckStatus; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt, fmt::Display}; + +/// The coverage data maps a function name to a set of coverage checks. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CoverageResults { + pub data: BTreeMap>, +} + +impl CoverageResults { + pub fn new(data: BTreeMap>) -> Self { + Self { data } + } +} + +impl fmt::Display for CoverageResults { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (file, checks) in self.data.iter() { + let mut checks_by_function: BTreeMap> = BTreeMap::new(); + + // Group checks by function + for check in checks { + // Insert the check into the vector corresponding to its function + checks_by_function.entry(check.function.clone()).or_default().push(check.clone()); + } + + for (function, checks) in checks_by_function { + writeln!(f, "{file} ({function})")?; + let mut sorted_checks: Vec = checks.to_vec(); + sorted_checks.sort_by(|a, b| a.region.start.cmp(&b.region.start)); + for check in sorted_checks.iter() { + writeln!(f, " * {} {}", check.region, check.status)?; + } + writeln!(f)?; + } + } + Ok(()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CoverageCheck { + pub function: String, + term: CoverageTerm, + pub region: CoverageRegion, + status: CheckStatus, +} + +impl CoverageCheck { + pub fn new( + function: String, + term: CoverageTerm, + region: CoverageRegion, + status: CheckStatus, + ) -> Self { + Self { function, term, region, status } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CoverageTerm { + Counter(u32), + Expression(u32), +} + +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct CoverageRegion { + pub file: String, + pub start: (u32, u32), + pub end: (u32, u32), +} + +impl Display for CoverageRegion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{} - {}:{}", self.start.0, self.start.1, self.end.0, self.end.1) + } +} + +impl CoverageRegion { + pub fn from_str(str: String) -> Self { + let blank_splits: Vec<&str> = str.split_whitespace().map(|s| s.trim()).collect(); + assert!(blank_splits[1] == "-"); + let str_splits1: Vec<&str> = blank_splits[0].split([':']).collect(); + let str_splits2: Vec<&str> = blank_splits[2].split([':']).collect(); + assert_eq!(str_splits1.len(), 3, "{str:?}"); + assert_eq!(str_splits2.len(), 2, "{str:?}"); + let file = str_splits1[0].to_string(); + let start = (str_splits1[1].parse().unwrap(), str_splits1[2].parse().unwrap()); + let end = (str_splits2[0].parse().unwrap(), str_splits2[1].parse().unwrap()); + Self { file, start, end } + } +} diff --git a/kani-driver/src/coverage/cov_session.rs b/kani-driver/src/coverage/cov_session.rs new file mode 100644 index 000000000000..df82d982bd72 --- /dev/null +++ b/kani-driver/src/coverage/cov_session.rs @@ -0,0 +1,176 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use std::fs; +use std::fs::File; +use std::io::Write; + +use crate::harness_runner::HarnessResult; +use crate::project::Project; +use crate::KaniSession; +use anyhow::{bail, Result}; + +impl KaniSession { + /// Saves metadata required for coverage-related features. + /// At present, this metadata consists of the following: + /// - The file names of the project's source code. + /// + /// Note: Currently, coverage mappings are not included due to technical + /// limitations. But this is where we should save them. + pub fn save_coverage_metadata(&self, project: &Project, stamp: &String) -> Result<()> { + if self.args.target_dir.is_some() { + self.save_coverage_metadata_cargo(project, stamp) + } else { + self.save_coverage_metadata_standalone(project, stamp) + } + } + + fn save_coverage_metadata_cargo(&self, project: &Project, stamp: &String) -> Result<()> { + let build_target = env!("TARGET"); + let metadata = self.cargo_metadata(build_target)?; + let target_dir = self + .args + .target_dir + .as_ref() + .unwrap_or(&metadata.target_directory.clone().into()) + .clone() + .join("kani"); + + let outdir = target_dir.join(build_target).join(format!("kanicov_{stamp}")); + + // Generally we don't expect this directory to exist, but there's no + // reason to delete it if it does. + if !outdir.exists() { + fs::create_dir(&outdir)?; + } + + // Collect paths to source files in the project + let mut source_targets = Vec::new(); + if let Some(metadata) = &project.cargo_metadata { + for package in &metadata.packages { + for target in &package.targets { + source_targets.push(target.src_path.clone()); + } + } + } else { + bail!("could not find project metadata required for coverage metadata"); + } + + let kanimap_name = format!("kanicov_{stamp}_kanimap"); + let file_name = outdir.join(kanimap_name).with_extension("json"); + let mut kanimap_file = File::create(file_name)?; + + let serialized_data = serde_json::to_string(&source_targets)?; + kanimap_file.write_all(serialized_data.as_bytes())?; + + Ok(()) + } + + fn save_coverage_metadata_standalone(&self, project: &Project, stamp: &String) -> Result<()> { + let input = project.input.clone().unwrap().canonicalize().unwrap(); + let input_dir = input.parent().unwrap().to_path_buf(); + let outdir = input_dir.join(format!("kanicov_{stamp}")); + + // Generally we don't expect this directory to exist, but there's no + // reason to delete it if it does. + if !outdir.exists() { + fs::create_dir(&outdir)?; + } + + // In this case, the source files correspond to the input file + let source_targets = vec![input]; + + let kanimap_name = format!("kanicov_{stamp}_kanimap"); + let file_name = outdir.join(kanimap_name).with_extension("json"); + let mut kanimap_file = File::create(file_name)?; + + let serialized_data = serde_json::to_string(&source_targets)?; + kanimap_file.write_all(serialized_data.as_bytes())?; + + Ok(()) + } + + /// Saves raw coverage check results required for coverage-related features. + pub fn save_coverage_results( + &self, + project: &Project, + results: &Vec, + stamp: &String, + ) -> Result<()> { + if self.args.target_dir.is_some() { + self.save_coverage_results_cargo(results, stamp) + } else { + self.save_coverage_results_standalone(project, results, stamp) + } + } + + pub fn save_coverage_results_cargo( + &self, + results: &Vec, + stamp: &String, + ) -> Result<()> { + let build_target = env!("TARGET"); + let metadata = self.cargo_metadata(build_target)?; + let target_dir = self + .args + .target_dir + .as_ref() + .unwrap_or(&metadata.target_directory.clone().into()) + .clone() + .join("kani"); + + let outdir = target_dir.join(build_target).join(format!("kanicov_{stamp}")); + + // This directory should have been created by `save_coverage_metadata`, + // so now we expect it to exist. + if !outdir.exists() { + bail!("directory associated to coverage run does not exist") + } + + for harness_res in results { + let harness_name = harness_res.harness.mangled_name.clone(); + let kaniraw_name = format!("{harness_name}_kaniraw"); + let file_name = outdir.join(kaniraw_name).with_extension("json"); + let mut cov_file = File::create(file_name)?; + + let cov_results = &harness_res.result.coverage_results.clone().unwrap(); + let serialized_data = serde_json::to_string(&cov_results)?; + cov_file.write_all(serialized_data.as_bytes())?; + } + + println!("[info] Coverage results saved to {}", &outdir.display()); + Ok(()) + } + + pub fn save_coverage_results_standalone( + &self, + project: &Project, + results: &Vec, + stamp: &String, + ) -> Result<()> { + let input = project.input.clone().unwrap().canonicalize().unwrap(); + let input_dir = input.parent().unwrap().to_path_buf(); + let outdir = input_dir.join(format!("kanicov_{stamp}")); + + // This directory should have been created by `save_coverage_metadata`, + // so now we expect it to exist. + if !outdir.exists() { + bail!("directory associated to coverage run does not exist") + } + + for harness_res in results { + let harness_name = harness_res.harness.mangled_name.clone(); + let kaniraw_name = format!("{harness_name}_kaniraw"); + let file_name = outdir.join(kaniraw_name).with_extension("json"); + let mut cov_file = File::create(file_name)?; + + let cov_results = &harness_res.result.coverage_results.clone().unwrap(); + let serialized_data = serde_json::to_string(&cov_results)?; + cov_file.write_all(serialized_data.as_bytes())?; + } + + println!("[info] Coverage results saved to {}", &outdir.display()); + + Ok(()) + } +} diff --git a/kani-driver/src/coverage/mod.rs b/kani-driver/src/coverage/mod.rs new file mode 100644 index 000000000000..2f7072aa82aa --- /dev/null +++ b/kani-driver/src/coverage/mod.rs @@ -0,0 +1,5 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +pub mod cov_results; +pub mod cov_session; diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index 992e226e45db..7e432932ab29 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -124,11 +124,7 @@ impl KaniSession { if !self.args.common_args.quiet && self.args.output_format != OutputFormat::Old { println!( "{}", - result.render( - &self.args.output_format, - harness.attributes.should_panic, - self.args.coverage - ) + result.render(&self.args.output_format, harness.attributes.should_panic) ); } self.gen_and_add_concrete_playback(harness, &mut result)?; @@ -192,6 +188,10 @@ impl KaniSession { } } + if self.args.coverage { + self.show_coverage_summary()?; + } + if failing > 0 { // Failure exit code without additional error message drop(self); @@ -200,4 +200,11 @@ impl KaniSession { Ok(()) } + + /// Show a coverage summary. + /// + /// This is just a placeholder for now. + fn show_coverage_summary(&self) -> Result<()> { + Ok(()) + } } diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index 3bb38ed1294c..d3bd697d2ea4 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -5,6 +5,7 @@ use std::ffi::OsString; use std::process::ExitCode; use anyhow::Result; +use time::{format_description, OffsetDateTime}; use args::{check_is_valid, CargoKaniSubcommand}; use args_toml::join_args; @@ -30,6 +31,7 @@ mod call_single_file; mod cbmc_output_parser; mod cbmc_property_renderer; mod concrete_playback; +mod coverage; mod harness_runner; mod metadata; mod project; @@ -129,6 +131,23 @@ fn verify_project(project: Project, session: KaniSession) -> Result<()> { let runner = harness_runner::HarnessRunner { sess: &session, project: &project }; let results = runner.check_all_harnesses(&harnesses)?; + if session.args.coverage { + // We generate a timestamp to save the coverage data in a folder named + // `kanicov_` where `` is the current date based on `format` + // below. The purpose of adding timestamps to the folder name is to make + // coverage results easily identifiable. Using a timestamp makes + // coverage results not only distinguishable, but also easy to relate to + // verification runs. We expect this to be particularly helpful for + // users in a proof debugging session, who are usually interested in the + // most recent results. + let time_now = OffsetDateTime::now_utc(); + let format = format_description::parse("[year]-[month]-[day]_[hour]-[minute]").unwrap(); + let timestamp = time_now.format(&format).unwrap(); + + session.save_coverage_metadata(&project, ×tamp)?; + session.save_coverage_results(&project, &results, ×tamp)?; + } + session.print_final_summary(&results) } diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index 2bc0845cdb46..e0d2d2edd139 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -33,6 +33,9 @@ pub struct Project { /// The directory where all outputs should be directed to. This path represents the canonical /// version of outdir. pub outdir: PathBuf, + /// The path to the input file the project was built from. + /// Note that it will only be `Some(...)` if this was built from a standalone project. + pub input: Option, /// The collection of artifacts kept as part of this project. artifacts: Vec, /// Records the cargo metadata from the build, if there was any @@ -82,6 +85,7 @@ impl Project { fn try_new( session: &KaniSession, outdir: PathBuf, + input: Option, metadata: Vec, cargo_metadata: Option, failed_targets: Option>, @@ -115,7 +119,7 @@ impl Project { } } - Ok(Project { outdir, metadata, artifacts, cargo_metadata, failed_targets }) + Ok(Project { outdir, input, metadata, artifacts, cargo_metadata, failed_targets }) } } @@ -178,6 +182,7 @@ pub fn cargo_project(session: &KaniSession, keep_going: bool) -> Result Project::try_new( session, outdir, + None, metadata, Some(outputs.cargo_metadata), outputs.failed_targets, @@ -243,7 +248,14 @@ impl<'a> StandaloneProjectBuilder<'a> { let metadata = from_json(&self.metadata)?; // Create the project with the artifacts built by the compiler. - let result = Project::try_new(self.session, self.outdir, vec![metadata], None, None); + let result = Project::try_new( + self.session, + self.outdir, + Some(self.input), + vec![metadata], + None, + None, + ); if let Ok(project) = &result { self.session.record_temporary_files(&project.artifacts); } @@ -297,5 +309,5 @@ pub(crate) fn std_project(std_path: &Path, session: &KaniSession) -> Result>>()?; - Project::try_new(session, outdir, metadata, None, None) + Project::try_new(session, outdir, None, metadata, None, None) } diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index 120ab0a9e55c..11df998c820f 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -78,8 +78,9 @@ pub enum UnstableFeature { ConcretePlayback, /// Enable Kani's unstable async library. AsyncLib, - /// Enable line coverage instrumentation/reports. - LineCoverage, + /// Enable source-based code coverage workflow. + /// See [RFC-0011](https://model-checking.github.io/kani/rfc/rfcs/0011-source-coverage.html) + SourceCoverage, /// Enable function contracts [RFC 9](https://model-checking.github.io/kani/rfc/rfcs/0009-function-contracts.html) FunctionContracts, /// Memory predicate APIs. diff --git a/tests/coverage/abort/expected b/tests/coverage/abort/expected new file mode 100644 index 000000000000..91142ebf94fc --- /dev/null +++ b/tests/coverage/abort/expected @@ -0,0 +1,13 @@ +Source-based code coverage results: + +main.rs (main)\ + * 9:1 - 9:11 COVERED\ + * 10:9 - 10:10 COVERED\ + * 10:14 - 10:18 COVERED\ + * 13:13 - 13:29 COVERED\ + * 14:10 - 15:18 COVERED\ + * 17:13 - 17:29 UNCOVERED\ + * 18:10 - 18:11 COVERED\ + * 20:5 - 20:12 UNCOVERED\ + * 20:20 - 20:41 UNCOVERED\ + * 21:1 - 21:2 UNCOVERED diff --git a/tests/coverage/unreachable/abort/main.rs b/tests/coverage/abort/main.rs similarity index 100% rename from tests/coverage/unreachable/abort/main.rs rename to tests/coverage/abort/main.rs diff --git a/tests/coverage/assert/expected b/tests/coverage/assert/expected new file mode 100644 index 000000000000..46bb664cf6f5 --- /dev/null +++ b/tests/coverage/assert/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (foo) + * 5:1 - 7:13 COVERED\ + * 9:9 - 10:17 COVERED\ + * 10:18 - 13:10 UNCOVERED\ + * 13:10 - 13:11 UNCOVERED\ + * 14:12 - 17:6 COVERED\ + * 18:1 - 18:2 COVERED diff --git a/tests/coverage/unreachable/assert/test.rs b/tests/coverage/assert/test.rs similarity index 100% rename from tests/coverage/unreachable/assert/test.rs rename to tests/coverage/assert/test.rs diff --git a/tests/coverage/assert_eq/expected b/tests/coverage/assert_eq/expected new file mode 100644 index 000000000000..c2eee7adf803 --- /dev/null +++ b/tests/coverage/assert_eq/expected @@ -0,0 +1,8 @@ +Source-based code coverage results: + +test.rs (main)\ + * 5:1 - 6:29 COVERED\ + * 7:25 - 7:27 COVERED\ + * 7:37 - 7:39 COVERED\ + * 8:15 - 10:6 UNCOVERED\ + * 10:6 - 10:7 COVERED diff --git a/tests/coverage/unreachable/assert_eq/test.rs b/tests/coverage/assert_eq/test.rs similarity index 100% rename from tests/coverage/unreachable/assert_eq/test.rs rename to tests/coverage/assert_eq/test.rs diff --git a/tests/coverage/assert_ne/expected b/tests/coverage/assert_ne/expected new file mode 100644 index 000000000000..c9b727da0f82 --- /dev/null +++ b/tests/coverage/assert_ne/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (main)\ + * 5:1 - 7:13 COVERED\ + * 8:13 - 10:18 COVERED\ + * 10:19 - 12:10 UNCOVERED\ + * 12:10 - 12:11 COVERED\ + * 13:6 - 13:7 COVERED\ + * 14:1 - 14:2 COVERED diff --git a/tests/coverage/unreachable/assert_ne/test.rs b/tests/coverage/assert_ne/test.rs similarity index 100% rename from tests/coverage/unreachable/assert_ne/test.rs rename to tests/coverage/assert_ne/test.rs diff --git a/tests/coverage/break/expected b/tests/coverage/break/expected new file mode 100644 index 000000000000..739735cdf1a2 --- /dev/null +++ b/tests/coverage/break/expected @@ -0,0 +1,13 @@ +Source-based code coverage results: + +main.rs (find_positive)\ + * 4:1 - 4:47 COVERED\ + * 5:10 - 5:13 COVERED\ + * 5:17 - 5:21 COVERED\ + * 7:20 - 7:29 COVERED\ + * 8:10 - 8:11 COVERED\ + * 11:5 - 11:9 UNCOVERED\ + * 12:1 - 12:2 COVERED + +main.rs (main)\ + * 15:1 - 19:2 COVERED diff --git a/tests/coverage/unreachable/break/main.rs b/tests/coverage/break/main.rs similarity index 82% rename from tests/coverage/unreachable/break/main.rs rename to tests/coverage/break/main.rs index 4e795bd2a6ea..79f422a0c283 100644 --- a/tests/coverage/unreachable/break/main.rs +++ b/tests/coverage/break/main.rs @@ -7,7 +7,7 @@ fn find_positive(nums: &[i32]) -> Option { return Some(num); } } - // This part is unreachable if there is at least one positive number. + // `None` is unreachable because there is at least one positive number. None } diff --git a/tests/coverage/compare/expected b/tests/coverage/compare/expected new file mode 100644 index 000000000000..153dbfa37d80 --- /dev/null +++ b/tests/coverage/compare/expected @@ -0,0 +1,11 @@ +Source-based code coverage results: + +main.rs (compare)\ + * 4:1 - 6:14 COVERED\ + * 6:17 - 6:18 COVERED\ + * 6:28 - 6:29 UNCOVERED + +main.rs (main)\ + * 10:1 - 13:14 COVERED\ + * 13:15 - 15:6 COVERED\ + * 15:6 - 15:7 COVERED diff --git a/tests/coverage/unreachable/compare/main.rs b/tests/coverage/compare/main.rs similarity index 73% rename from tests/coverage/unreachable/compare/main.rs rename to tests/coverage/compare/main.rs index a10fb84e29a8..43318ac0547e 100644 --- a/tests/coverage/unreachable/compare/main.rs +++ b/tests/coverage/compare/main.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT fn compare(x: u16, y: u16) -> u16 { - // The line below should be reported as PARTIAL for having both REACHABLE and UNREACHABLE checks + // The case where `x < y` isn't possible so its region is `UNCOVERED` if x >= y { 1 } else { 0 } } diff --git a/tests/coverage/contradiction/expected b/tests/coverage/contradiction/expected new file mode 100644 index 000000000000..db3676d7da15 --- /dev/null +++ b/tests/coverage/contradiction/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +main.rs (contradiction)\ + * 4:1 - 7:13 COVERED\ + * 8:12 - 8:17 COVERED\ + * 8:18 - 10:10 UNCOVERED\ + * 10:10 - 10:11 COVERED\ + * 11:12 - 13:6 COVERED\ + * 14:1 - 14:2 COVERED diff --git a/tests/coverage/unreachable/contradiction/main.rs b/tests/coverage/contradiction/main.rs similarity index 100% rename from tests/coverage/unreachable/contradiction/main.rs rename to tests/coverage/contradiction/main.rs diff --git a/tests/coverage/debug-assert/expected b/tests/coverage/debug-assert/expected new file mode 100644 index 000000000000..fbe57690d347 --- /dev/null +++ b/tests/coverage/debug-assert/expected @@ -0,0 +1,10 @@ +Source-based code coverage results: + +main.rs (main)\ + * 10:1 - 10:11 COVERED\ + * 11:9 - 11:10 COVERED\ + * 11:14 - 11:18 COVERED\ + * 12:30 - 12:71 UNCOVERED\ + * 13:9 - 13:23 UNCOVERED\ + * 13:25 - 13:53 UNCOVERED\ + * 15:1 - 15:2 UNCOVERED diff --git a/tests/coverage/debug-assert/main.rs b/tests/coverage/debug-assert/main.rs new file mode 100644 index 000000000000..a57a45c8c724 --- /dev/null +++ b/tests/coverage/debug-assert/main.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test checks that the regions after the `debug_assert` macro are +//! `UNCOVERED`. In fact, for this example, the region associated to `"This +//! should fail and stop the execution"` is also `UNCOVERED` because the macro +//! calls span two regions each. + +#[kani::proof] +fn main() { + for i in 0..4 { + debug_assert!(i > 0, "This should fail and stop the execution"); + assert!(i == 0, "This should be unreachable"); + } +} diff --git a/tests/coverage/div-zero/expected b/tests/coverage/div-zero/expected new file mode 100644 index 000000000000..f351005f4f22 --- /dev/null +++ b/tests/coverage/div-zero/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (div)\ + * 4:1 - 5:14 COVERED\ + * 5:17 - 5:22 COVERED\ + * 5:32 - 5:33 UNCOVERED + +test.rs (main)\ + * 9:1 - 11:2 COVERED diff --git a/tests/coverage/reachable/div-zero/reachable_pass/test.rs b/tests/coverage/div-zero/test.rs similarity index 64% rename from tests/coverage/reachable/div-zero/reachable_pass/test.rs rename to tests/coverage/div-zero/test.rs index 766fb5a89d88..e858b57a12be 100644 --- a/tests/coverage/reachable/div-zero/reachable_pass/test.rs +++ b/tests/coverage/div-zero/test.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT fn div(x: u16, y: u16) -> u16 { - if y != 0 { x / y } else { 0 } // PARTIAL: some cases are `COVERED`, others are not + if y != 0 { x / y } else { 0 } } #[kani::proof] diff --git a/tests/coverage/early-return/expected b/tests/coverage/early-return/expected new file mode 100644 index 000000000000..53cde3abeaf8 --- /dev/null +++ b/tests/coverage/early-return/expected @@ -0,0 +1,12 @@ +Source-based code coverage results: + +main.rs (find_index)\ + * 4:1 - 4:59 COVERED\ + * 5:10 - 5:21 COVERED\ + * 7:20 - 7:31 COVERED\ + * 8:10 - 8:11 COVERED\ + * 10:5 - 10:9 UNCOVERED\ + * 11:1 - 11:2 COVERED + +main.rs (main)\ + * 14:1 - 19:2 COVERED diff --git a/tests/coverage/unreachable/early-return/main.rs b/tests/coverage/early-return/main.rs similarity index 100% rename from tests/coverage/unreachable/early-return/main.rs rename to tests/coverage/early-return/main.rs diff --git a/tests/coverage/if-statement-multi/expected b/tests/coverage/if-statement-multi/expected new file mode 100644 index 000000000000..4e8382d10a6f --- /dev/null +++ b/tests/coverage/if-statement-multi/expected @@ -0,0 +1,11 @@ +Source-based code coverage results: + +test.rs (main)\ + * 21:1 - 26:2 COVERED + +test.rs (test_cov)\ + * 16:1 - 17:15 COVERED\ + * 17:19 - 17:28 UNCOVERED\ + * 17:31 - 17:35 COVERED\ + * 17:45 - 17:50 UNCOVERED\ + * 18:1 - 18:2 COVERED diff --git a/tests/coverage/if-statement-multi/test.rs b/tests/coverage/if-statement-multi/test.rs new file mode 100644 index 000000000000..ac00dec3f451 --- /dev/null +++ b/tests/coverage/if-statement-multi/test.rs @@ -0,0 +1,26 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: --coverage -Zsource-coverage + +//! Checks that we are covering all regions except +//! * the `val == 42` condition +//! * the `false` branch +//! +//! No coverage information is shown for `_other_function` because it's sliced +//! off: + +fn _other_function() { + println!("Hello, world!"); +} + +fn test_cov(val: u32) -> bool { + if val < 3 || val == 42 { true } else { false } +} + +#[cfg_attr(kani, kani::proof)] +fn main() { + let test1 = test_cov(1); + let test2 = test_cov(2); + assert!(test1); + assert!(test2); +} diff --git a/tests/coverage/if-statement/expected b/tests/coverage/if-statement/expected new file mode 100644 index 000000000000..b85b95de9c84 --- /dev/null +++ b/tests/coverage/if-statement/expected @@ -0,0 +1,14 @@ +Source-based code coverage results: + +main.rs (check_number)\ + * 4:1 - 5:15 COVERED\ + * 7:12 - 7:24 COVERED\ + * 7:27 - 7:46 UNCOVERED\ + * 7:56 - 7:74 COVERED\ + * 8:15 - 8:22 UNCOVERED\ + * 9:9 - 9:19 UNCOVERED\ + * 11:9 - 11:15 UNCOVERED\ + * 13:1 - 13:2 COVERED + +main.rs (main)\ + * 16:1 - 20:2 COVERED diff --git a/tests/coverage/unreachable/if-statement/main.rs b/tests/coverage/if-statement/main.rs similarity index 78% rename from tests/coverage/unreachable/if-statement/main.rs rename to tests/coverage/if-statement/main.rs index f497cd76808e..c18d4dd4a5e4 100644 --- a/tests/coverage/unreachable/if-statement/main.rs +++ b/tests/coverage/if-statement/main.rs @@ -3,7 +3,7 @@ fn check_number(num: i32) -> &'static str { if num > 0 { - // The line is partially covered because the if statement is UNREACHABLE while the else statement is reachable + // The next line is partially covered if num % 2 == 0 { "Positive and Even" } else { "Positive and Odd" } } else if num < 0 { "Negative" diff --git a/tests/coverage/known_issues/assert_uncovered_end/expected b/tests/coverage/known_issues/assert_uncovered_end/expected new file mode 100644 index 000000000000..ceba065ce424 --- /dev/null +++ b/tests/coverage/known_issues/assert_uncovered_end/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (check_assert)\ + * 9:1 - 10:34 COVERED\ + * 11:14 - 13:6 COVERED\ + * 13:6 - 13:7 UNCOVERED + +test.rs (check_assert::{closure#0})\ + * 10:40 - 10:49 COVERED diff --git a/tests/coverage/known_issues/assert_uncovered_end/test.rs b/tests/coverage/known_issues/assert_uncovered_end/test.rs new file mode 100644 index 000000000000..c3da20c8b00b --- /dev/null +++ b/tests/coverage/known_issues/assert_uncovered_end/test.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Checks that `check_assert` is fully covered. At present, the coverage for +//! this test reports an uncovered single-column region at the end of the `if` +//! statement: + +#[kani::proof] +fn check_assert() { + let x: u32 = kani::any_where(|val| *val == 5); + if x > 3 { + assert!(x > 4); + } +} diff --git a/tests/coverage/known_issues/assume_assert/expected b/tests/coverage/known_issues/assume_assert/expected new file mode 100644 index 000000000000..55f3235d7d24 --- /dev/null +++ b/tests/coverage/known_issues/assume_assert/expected @@ -0,0 +1,4 @@ +Source-based code coverage results: + +main.rs (check_assume_assert)\ + * 11:1 - 15:2 COVERED diff --git a/tests/coverage/known_issues/assume_assert/main.rs b/tests/coverage/known_issues/assume_assert/main.rs new file mode 100644 index 000000000000..90f26b2121fa --- /dev/null +++ b/tests/coverage/known_issues/assume_assert/main.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test should check that the region after `kani::assume(false)` is +//! `UNCOVERED`. However, due to a technical limitation in `rustc`'s coverage +//! instrumentation, only one `COVERED` region is reported for the whole +//! function. More details in +//! . + +#[kani::proof] +fn check_assume_assert() { + let a: u8 = kani::any(); + kani::assume(false); + assert!(a < 5); +} diff --git a/tests/coverage/known_issues/out-of-bounds/expected b/tests/coverage/known_issues/out-of-bounds/expected new file mode 100644 index 000000000000..8ab9e2e15627 --- /dev/null +++ b/tests/coverage/known_issues/out-of-bounds/expected @@ -0,0 +1,7 @@ +Source-based code coverage results: + +test.rs (get)\ + * 8:1 - 10:2 COVERED + +test.rs (main)\ + * 13:1 - 15:2 COVERED diff --git a/tests/coverage/known_issues/out-of-bounds/test.rs b/tests/coverage/known_issues/out-of-bounds/test.rs new file mode 100644 index 000000000000..83242590815b --- /dev/null +++ b/tests/coverage/known_issues/out-of-bounds/test.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test should check that the return in `get` is `UNCOVERED`. However, the +//! coverage results currently report that the whole function is `COVERED`, +//! likely due to + +fn get(s: &[i16], index: usize) -> i16 { + s[index] +} + +#[kani::proof] +fn main() { + get(&[7, -83, 19], 15); +} diff --git a/tests/coverage/known_issues/variant/expected b/tests/coverage/known_issues/variant/expected new file mode 100644 index 000000000000..13383ed3bab0 --- /dev/null +++ b/tests/coverage/known_issues/variant/expected @@ -0,0 +1,14 @@ +Source-based code coverage results: + +main.rs (main)\ + * 29:1 - 32:2 COVERED + +main.rs (print_direction)\ + * 16:1 - 16:36 COVERED\ + * 18:11 - 18:14 UNCOVERED\ + * 19:26 - 19:47 UNCOVERED\ + * 20:28 - 20:51 UNCOVERED\ + * 21:28 - 21:51 COVERED\ + * 22:34 - 22:63 UNCOVERED\ + * 24:14 - 24:45 UNCOVERED\ + * 26:1 - 26:2 COVERED diff --git a/tests/coverage/unreachable/variant/main.rs b/tests/coverage/known_issues/variant/main.rs similarity index 72% rename from tests/coverage/unreachable/variant/main.rs rename to tests/coverage/known_issues/variant/main.rs index 76a589147bca..c654ca355c45 100644 --- a/tests/coverage/unreachable/variant/main.rs +++ b/tests/coverage/known_issues/variant/main.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Checks coverage results in an example with a `match` statement matching on -//! all enum variants. +//! all enum variants. Currently, it does not yield the expected results because +//! it reports the `dir` in the match statement as `UNCOVERED`: +//! enum Direction { Up, @@ -12,6 +14,7 @@ enum Direction { } fn print_direction(dir: Direction) { + // For some reason, `dir`'s span is reported as `UNCOVERED` too match dir { Direction::Up => println!("Going up!"), Direction::Down => println!("Going down!"), diff --git a/tests/coverage/multiple-harnesses/expected b/tests/coverage/multiple-harnesses/expected new file mode 100644 index 000000000000..b5362147fed1 --- /dev/null +++ b/tests/coverage/multiple-harnesses/expected @@ -0,0 +1,37 @@ +Source-based code coverage results: + +main.rs (estimate_size)\ + * 4:1 - 7:15 COVERED\ + * 8:12 - 8:19 COVERED\ + * 9:20 - 9:21 COVERED\ + * 11:20 - 11:21 COVERED\ + * 13:15 - 13:23 COVERED\ + * 14:12 - 14:20 COVERED\ + * 15:20 - 15:21 COVERED\ + * 17:20 - 17:21 COVERED\ + * 20:12 - 20:20 COVERED\ + * 21:20 - 21:21 COVERED\ + * 23:20 - 23:21 COVERED\ + * 26:1 - 26:2 COVERED + +main.rs (fully_covered)\ + * 39:1 - 44:2 COVERED + +Source-based code coverage results: + +main.rs (estimate_size)\ + * 4:1 - 7:15 COVERED\ + * 8:12 - 8:19 COVERED\ + * 9:20 - 9:21 COVERED\ + * 11:20 - 11:21 COVERED\ + * 13:15 - 13:23 COVERED\ + * 14:12 - 14:20 COVERED\ + * 15:20 - 15:21 COVERED\ + * 17:20 - 17:21 COVERED\ + * 20:12 - 20:20 COVERED\ + * 21:20 - 21:21 COVERED\ + * 23:20 - 23:21 UNCOVERED\ + * 26:1 - 26:2 COVERED + +main.rs (mostly_covered)\ + * 30:1 - 35:2 COVERED diff --git a/tests/coverage/unreachable/multiple-harnesses/main.rs b/tests/coverage/multiple-harnesses/main.rs similarity index 100% rename from tests/coverage/unreachable/multiple-harnesses/main.rs rename to tests/coverage/multiple-harnesses/main.rs diff --git a/tests/coverage/overflow-failure/expected b/tests/coverage/overflow-failure/expected new file mode 100644 index 000000000000..db4f29d51336 --- /dev/null +++ b/tests/coverage/overflow-failure/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (cond_reduce)\ + * 7:1 - 8:18 COVERED\ + * 8:21 - 8:27 COVERED\ + * 8:37 - 8:38 UNCOVERED + +test.rs (main)\ + * 12:1 - 15:2 COVERED diff --git a/tests/coverage/reachable/overflow/reachable_fail/test.rs b/tests/coverage/overflow-failure/test.rs similarity index 66% rename from tests/coverage/reachable/overflow/reachable_fail/test.rs rename to tests/coverage/overflow-failure/test.rs index d435612f2342..cd711f3aeb9e 100644 --- a/tests/coverage/reachable/overflow/reachable_fail/test.rs +++ b/tests/coverage/overflow-failure/test.rs @@ -5,11 +5,11 @@ //! arithmetic overflow failure (caused by the second call to `cond_reduce`). fn cond_reduce(thresh: u32, x: u32) -> u32 { - if x > thresh { x - 50 } else { x } // PARTIAL: some cases are `COVERED`, others are not + if x > thresh { x - 50 } else { x } } #[kani::proof] fn main() { cond_reduce(60, 70); cond_reduce(40, 42); -} // NONE: Caused by the arithmetic overflow failure from the second call to `cond_reduce` +} diff --git a/tests/coverage/overflow-full-coverage/expected b/tests/coverage/overflow-full-coverage/expected new file mode 100644 index 000000000000..4d17761505eb --- /dev/null +++ b/tests/coverage/overflow-full-coverage/expected @@ -0,0 +1,9 @@ +Source-based code coverage results: + +test.rs (main)\ + * 12:1 - 17:2 COVERED + +test.rs (reduce)\ + * 7:1 - 8:16 COVERED\ + * 8:19 - 8:27 COVERED\ + * 8:37 - 8:38 COVERED diff --git a/tests/coverage/reachable/overflow/reachable_pass/test.rs b/tests/coverage/overflow-full-coverage/test.rs similarity index 62% rename from tests/coverage/reachable/overflow/reachable_pass/test.rs rename to tests/coverage/overflow-full-coverage/test.rs index 0b05874efc84..1c3467275b33 100644 --- a/tests/coverage/reachable/overflow/reachable_pass/test.rs +++ b/tests/coverage/overflow-full-coverage/test.rs @@ -1,8 +1,8 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -//! Checks that Kani reports the correct coverage results (`FULL` for all lines) -//! in a case where arithmetic overflow failures are prevented. +//! Checks that Kani reports all regions as `COVERED` as expected in this case +//! where arithmetic overflow failures are prevented. fn reduce(x: u32) -> u32 { if x > 1000 { x - 1000 } else { x } diff --git a/tests/coverage/reachable/assert-false/expected b/tests/coverage/reachable/assert-false/expected deleted file mode 100644 index 97ffbe1d96e4..000000000000 --- a/tests/coverage/reachable/assert-false/expected +++ /dev/null @@ -1,8 +0,0 @@ -coverage/reachable/assert-false/main.rs, 6, FULL -coverage/reachable/assert-false/main.rs, 7, FULL -coverage/reachable/assert-false/main.rs, 11, PARTIAL -coverage/reachable/assert-false/main.rs, 12, PARTIAL -coverage/reachable/assert-false/main.rs, 15, PARTIAL -coverage/reachable/assert-false/main.rs, 16, FULL -coverage/reachable/assert-false/main.rs, 17, PARTIAL -coverage/reachable/assert-false/main.rs, 19, FULL diff --git a/tests/coverage/reachable/assert-false/main.rs b/tests/coverage/reachable/assert-false/main.rs deleted file mode 100644 index 42563b1cd518..000000000000 --- a/tests/coverage/reachable/assert-false/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! Check that the assert is reported as `PARTIAL` for having both `COVERED` and `UNCOVERED` coverage checks -fn any_bool() -> bool { - kani::any() -} - -#[kani::proof] -fn main() { - if any_bool() { - assert!(false); - } - - if any_bool() { - let s = "Fail with custom runtime message"; - assert!(false, "{}", s); - } -} diff --git a/tests/coverage/reachable/assert/reachable_pass/expected b/tests/coverage/reachable/assert/reachable_pass/expected deleted file mode 100644 index 9d21185b3a83..000000000000 --- a/tests/coverage/reachable/assert/reachable_pass/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/assert/reachable_pass/test.rs, 6, FULL -coverage/reachable/assert/reachable_pass/test.rs, 7, PARTIAL -coverage/reachable/assert/reachable_pass/test.rs, 8, FULL -coverage/reachable/assert/reachable_pass/test.rs, 10, FULL diff --git a/tests/coverage/reachable/assert/reachable_pass/test.rs b/tests/coverage/reachable/assert/reachable_pass/test.rs deleted file mode 100644 index 72acca2b764c..000000000000 --- a/tests/coverage/reachable/assert/reachable_pass/test.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#[kani::proof] -fn main() { - let x: u32 = kani::any_where(|val| *val == 5); - if x > 3 { - assert!(x > 4); // FULL: `x > 4` since `x = 5` - } -} diff --git a/tests/coverage/reachable/bounds/reachable_fail/expected b/tests/coverage/reachable/bounds/reachable_fail/expected deleted file mode 100644 index fedfec8b2a1e..000000000000 --- a/tests/coverage/reachable/bounds/reachable_fail/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/bounds/reachable_fail/test.rs, 5, PARTIAL -coverage/reachable/bounds/reachable_fail/test.rs, 6, NONE -coverage/reachable/bounds/reachable_fail/test.rs, 10, PARTIAL -coverage/reachable/bounds/reachable_fail/test.rs, 11, NONE diff --git a/tests/coverage/reachable/bounds/reachable_fail/test.rs b/tests/coverage/reachable/bounds/reachable_fail/test.rs deleted file mode 100644 index cebd78b2d5d9..000000000000 --- a/tests/coverage/reachable/bounds/reachable_fail/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn get(s: &[i16], index: usize) -> i16 { - s[index] // PARTIAL: `s[index]` is covered, but `index = 15` induces a failure -} // NONE: `index = 15` caused failure earlier - -#[kani::proof] -fn main() { - get(&[7, -83, 19], 15); -} // NONE: `index = 15` caused failure earlier diff --git a/tests/coverage/reachable/div-zero/reachable_fail/expected b/tests/coverage/reachable/div-zero/reachable_fail/expected deleted file mode 100644 index c1ac77404680..000000000000 --- a/tests/coverage/reachable/div-zero/reachable_fail/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/div-zero/reachable_fail/test.rs, 5, PARTIAL -coverage/reachable/div-zero/reachable_fail/test.rs, 6, NONE -coverage/reachable/div-zero/reachable_fail/test.rs, 10, PARTIAL -coverage/reachable/div-zero/reachable_fail/test.rs, 11, NONE diff --git a/tests/coverage/reachable/div-zero/reachable_fail/test.rs b/tests/coverage/reachable/div-zero/reachable_fail/test.rs deleted file mode 100644 index 5f69005ee712..000000000000 --- a/tests/coverage/reachable/div-zero/reachable_fail/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn div(x: u16, y: u16) -> u16 { - x / y // PARTIAL: `y = 0` causes failure, but `x / y` is `COVERED` -} - -#[kani::proof] -fn main() { - div(678, 0); -} diff --git a/tests/coverage/reachable/div-zero/reachable_pass/expected b/tests/coverage/reachable/div-zero/reachable_pass/expected deleted file mode 100644 index c7bfb5961c0b..000000000000 --- a/tests/coverage/reachable/div-zero/reachable_pass/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/div-zero/reachable_pass/test.rs, 5, PARTIAL -coverage/reachable/div-zero/reachable_pass/test.rs, 6, FULL -coverage/reachable/div-zero/reachable_pass/test.rs, 10, FULL -coverage/reachable/div-zero/reachable_pass/test.rs, 11, FULL diff --git a/tests/coverage/reachable/overflow/reachable_fail/expected b/tests/coverage/reachable/overflow/reachable_fail/expected deleted file mode 100644 index d45edcc37a63..000000000000 --- a/tests/coverage/reachable/overflow/reachable_fail/expected +++ /dev/null @@ -1,5 +0,0 @@ -coverage/reachable/overflow/reachable_fail/test.rs, 8, PARTIAL -coverage/reachable/overflow/reachable_fail/test.rs, 9, FULL -coverage/reachable/overflow/reachable_fail/test.rs, 13, FULL -coverage/reachable/overflow/reachable_fail/test.rs, 14, PARTIAL -coverage/reachable/overflow/reachable_fail/test.rs, 15, NONE diff --git a/tests/coverage/reachable/overflow/reachable_pass/expected b/tests/coverage/reachable/overflow/reachable_pass/expected deleted file mode 100644 index 5becf2cd23e7..000000000000 --- a/tests/coverage/reachable/overflow/reachable_pass/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/reachable/overflow/reachable_pass/test.rs, 8, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 9, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 13, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 14, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 15, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 16, FULL -coverage/reachable/overflow/reachable_pass/test.rs, 17, FULL diff --git a/tests/coverage/reachable/rem-zero/reachable_fail/expected b/tests/coverage/reachable/rem-zero/reachable_fail/expected deleted file mode 100644 index 7852461e4f57..000000000000 --- a/tests/coverage/reachable/rem-zero/reachable_fail/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/rem-zero/reachable_fail/test.rs, 5, PARTIAL -coverage/reachable/rem-zero/reachable_fail/test.rs, 6, NONE -coverage/reachable/rem-zero/reachable_fail/test.rs, 10, PARTIAL -coverage/reachable/rem-zero/reachable_fail/test.rs, 11, NONE diff --git a/tests/coverage/reachable/rem-zero/reachable_fail/test.rs b/tests/coverage/reachable/rem-zero/reachable_fail/test.rs deleted file mode 100644 index 400c7e02340b..000000000000 --- a/tests/coverage/reachable/rem-zero/reachable_fail/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn rem(x: u16, y: u16) -> u16 { - x % y // PARTIAL: `x % y` is covered but induces a division failure -} // NONE: Caused by division failure earlier - -#[kani::proof] -fn main() { - rem(678, 0); -} // NONE: Caused by division failure earlier diff --git a/tests/coverage/reachable/rem-zero/reachable_pass/expected b/tests/coverage/reachable/rem-zero/reachable_pass/expected deleted file mode 100644 index f3d5934d785d..000000000000 --- a/tests/coverage/reachable/rem-zero/reachable_pass/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/reachable/rem-zero/reachable_pass/test.rs, 5, PARTIAL -coverage/reachable/rem-zero/reachable_pass/test.rs, 6, FULL -coverage/reachable/rem-zero/reachable_pass/test.rs, 10, FULL -coverage/reachable/rem-zero/reachable_pass/test.rs, 11, FULL diff --git a/tests/coverage/reachable/rem-zero/reachable_pass/test.rs b/tests/coverage/reachable/rem-zero/reachable_pass/test.rs deleted file mode 100644 index 41ed28f7b903..000000000000 --- a/tests/coverage/reachable/rem-zero/reachable_pass/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn rem(x: u16, y: u16) -> u16 { - if y != 0 { x % y } else { 0 } -} - -#[kani::proof] -fn main() { - rem(11, 3); -} diff --git a/tests/coverage/unreachable/abort/expected b/tests/coverage/unreachable/abort/expected deleted file mode 100644 index 473b0f5a8d4d..000000000000 --- a/tests/coverage/unreachable/abort/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/abort/main.rs, 10, PARTIAL -coverage/unreachable/abort/main.rs, 11, FULL -coverage/unreachable/abort/main.rs, 13, FULL -coverage/unreachable/abort/main.rs, 15, FULL -coverage/unreachable/abort/main.rs, 17, NONE -coverage/unreachable/abort/main.rs, 20, NONE -coverage/unreachable/abort/main.rs, 21, NONE diff --git a/tests/coverage/unreachable/assert/expected b/tests/coverage/unreachable/assert/expected deleted file mode 100644 index 9bc6d8faa4f9..000000000000 --- a/tests/coverage/unreachable/assert/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/assert/test.rs, 6, FULL -coverage/unreachable/assert/test.rs, 7, PARTIAL -coverage/unreachable/assert/test.rs, 9, PARTIAL -coverage/unreachable/assert/test.rs, 10, NONE -coverage/unreachable/assert/test.rs, 12, NONE -coverage/unreachable/assert/test.rs, 16, FULL -coverage/unreachable/assert/test.rs, 18, FULL diff --git a/tests/coverage/unreachable/assert_eq/expected b/tests/coverage/unreachable/assert_eq/expected deleted file mode 100644 index 9b13c3c96ded..000000000000 --- a/tests/coverage/unreachable/assert_eq/expected +++ /dev/null @@ -1,5 +0,0 @@ -coverage/unreachable/assert_eq/test.rs, 6, FULL -coverage/unreachable/assert_eq/test.rs, 7, FULL -coverage/unreachable/assert_eq/test.rs, 8, PARTIAL -coverage/unreachable/assert_eq/test.rs, 9, NONE -coverage/unreachable/assert_eq/test.rs, 11, FULL diff --git a/tests/coverage/unreachable/assert_ne/expected b/tests/coverage/unreachable/assert_ne/expected deleted file mode 100644 index f027f432e280..000000000000 --- a/tests/coverage/unreachable/assert_ne/expected +++ /dev/null @@ -1,6 +0,0 @@ -coverage/unreachable/assert_ne/test.rs, 6, FULL -coverage/unreachable/assert_ne/test.rs, 7, FULL -coverage/unreachable/assert_ne/test.rs, 8, FULL -coverage/unreachable/assert_ne/test.rs, 10, PARTIAL -coverage/unreachable/assert_ne/test.rs, 11, NONE -coverage/unreachable/assert_ne/test.rs, 14, FULL diff --git a/tests/coverage/unreachable/assume_assert/expected b/tests/coverage/unreachable/assume_assert/expected deleted file mode 100644 index 8c1ae8a247d2..000000000000 --- a/tests/coverage/unreachable/assume_assert/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/unreachable/assume_assert/main.rs, 5, FULL -coverage/unreachable/assume_assert/main.rs, 6, FULL -coverage/unreachable/assume_assert/main.rs, 7, NONE -coverage/unreachable/assume_assert/main.rs, 8, NONE diff --git a/tests/coverage/unreachable/assume_assert/main.rs b/tests/coverage/unreachable/assume_assert/main.rs deleted file mode 100644 index c4d5d65c6640..000000000000 --- a/tests/coverage/unreachable/assume_assert/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -#[kani::proof] -fn check_assume_assert() { - let a: u8 = kani::any(); - kani::assume(false); - assert!(a < 5); -} diff --git a/tests/coverage/unreachable/bounds/expected b/tests/coverage/unreachable/bounds/expected deleted file mode 100644 index 610372000a01..000000000000 --- a/tests/coverage/unreachable/bounds/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/unreachable/bounds/test.rs, 5, PARTIAL -coverage/unreachable/bounds/test.rs, 6, FULL -coverage/unreachable/bounds/test.rs, 11, FULL -coverage/unreachable/bounds/test.rs, 12, FULL diff --git a/tests/coverage/unreachable/bounds/test.rs b/tests/coverage/unreachable/bounds/test.rs deleted file mode 100644 index c37c9d0dcad6..000000000000 --- a/tests/coverage/unreachable/bounds/test.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn get(s: &[i16], index: usize) -> i16 { - if index < s.len() { s[index] } else { -1 } -} - -#[kani::proof] -fn main() { - //get(&[7, -83, 19], 2); - get(&[5, 206, -46, 321, 8], 8); -} diff --git a/tests/coverage/unreachable/break/expected b/tests/coverage/unreachable/break/expected deleted file mode 100644 index dcb013c50c3d..000000000000 --- a/tests/coverage/unreachable/break/expected +++ /dev/null @@ -1,9 +0,0 @@ -coverage/unreachable/break/main.rs, 5, PARTIAL -coverage/unreachable/break/main.rs, 6, FULL -coverage/unreachable/break/main.rs, 7, FULL -coverage/unreachable/break/main.rs, 11, NONE -coverage/unreachable/break/main.rs, 12, PARTIAL -coverage/unreachable/break/main.rs, 16, FULL -coverage/unreachable/break/main.rs, 17, FULL -coverage/unreachable/break/main.rs, 18, FULL -coverage/unreachable/break/main.rs, 19, FULL diff --git a/tests/coverage/unreachable/check_id/expected b/tests/coverage/unreachable/check_id/expected deleted file mode 100644 index a2d296f0f9a3..000000000000 --- a/tests/coverage/unreachable/check_id/expected +++ /dev/null @@ -1,16 +0,0 @@ -coverage/unreachable/check_id/main.rs, 5, FULL -coverage/unreachable/check_id/main.rs, 6, PARTIAL -coverage/unreachable/check_id/main.rs, 8, NONE -coverage/unreachable/check_id/main.rs, 10, FULL -coverage/unreachable/check_id/main.rs, 14, FULL -coverage/unreachable/check_id/main.rs, 15, FULL -coverage/unreachable/check_id/main.rs, 16, FULL -coverage/unreachable/check_id/main.rs, 17, FULL -coverage/unreachable/check_id/main.rs, 18, FULL -coverage/unreachable/check_id/main.rs, 19, FULL -coverage/unreachable/check_id/main.rs, 20, FULL -coverage/unreachable/check_id/main.rs, 21, FULL -coverage/unreachable/check_id/main.rs, 22, FULL -coverage/unreachable/check_id/main.rs, 23, FULL -coverage/unreachable/check_id/main.rs, 24, PARTIAL -coverage/unreachable/check_id/main.rs, 25, NONE diff --git a/tests/coverage/unreachable/check_id/main.rs b/tests/coverage/unreachable/check_id/main.rs deleted file mode 100644 index 8273a9bcc679..000000000000 --- a/tests/coverage/unreachable/check_id/main.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn foo(x: i32) { - assert!(1 + 1 == 2); - if x < 9 { - // unreachable - assert!(2 + 2 == 4); - } -} - -#[kani::proof] -fn main() { - assert!(1 + 1 == 2); - let x = if kani::any() { 33 } else { 57 }; - foo(x); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(1 + 1 == 2); - assert!(3 + 3 == 5); -} diff --git a/tests/coverage/unreachable/compare/expected b/tests/coverage/unreachable/compare/expected deleted file mode 100644 index 6187e232cbe7..000000000000 --- a/tests/coverage/unreachable/compare/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/compare/main.rs, 6, PARTIAL -coverage/unreachable/compare/main.rs, 7, FULL -coverage/unreachable/compare/main.rs, 11, FULL -coverage/unreachable/compare/main.rs, 12, FULL -coverage/unreachable/compare/main.rs, 13, FULL -coverage/unreachable/compare/main.rs, 14, FULL -coverage/unreachable/compare/main.rs, 16, FULL diff --git a/tests/coverage/unreachable/contradiction/expected b/tests/coverage/unreachable/contradiction/expected deleted file mode 100644 index 4234fc328e1e..000000000000 --- a/tests/coverage/unreachable/contradiction/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/contradiction/main.rs, 5, FULL -coverage/unreachable/contradiction/main.rs, 6, FULL -coverage/unreachable/contradiction/main.rs, 7, FULL -coverage/unreachable/contradiction/main.rs, 8, PARTIAL -coverage/unreachable/contradiction/main.rs, 9, NONE -coverage/unreachable/contradiction/main.rs, 12, FULL -coverage/unreachable/contradiction/main.rs, 14, FULL diff --git a/tests/coverage/unreachable/debug-assert/expected b/tests/coverage/unreachable/debug-assert/expected deleted file mode 100644 index 25fdfed4c863..000000000000 --- a/tests/coverage/unreachable/debug-assert/expected +++ /dev/null @@ -1,4 +0,0 @@ -coverage/unreachable/debug-assert/main.rs, 6, PARTIAL -coverage/unreachable/debug-assert/main.rs, 7, PARTIAL -coverage/unreachable/debug-assert/main.rs, 8, NONE -coverage/unreachable/debug-assert/main.rs, 10, NONE diff --git a/tests/coverage/unreachable/debug-assert/main.rs b/tests/coverage/unreachable/debug-assert/main.rs deleted file mode 100644 index ab3ab41e47d0..000000000000 --- a/tests/coverage/unreachable/debug-assert/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#[kani::proof] -fn main() { - for i in 0..4 { - debug_assert!(i > 0, "This should fail and stop the execution"); - assert!(i == 0, "This should be unreachable"); - } -} diff --git a/tests/coverage/unreachable/divide/expected b/tests/coverage/unreachable/divide/expected deleted file mode 100644 index 63081358941a..000000000000 --- a/tests/coverage/unreachable/divide/expected +++ /dev/null @@ -1,7 +0,0 @@ -coverage/unreachable/divide/main.rs, 6, FULL -coverage/unreachable/divide/main.rs, 7, FULL -coverage/unreachable/divide/main.rs, 9, NONE -coverage/unreachable/divide/main.rs, 11, FULL -coverage/unreachable/divide/main.rs, 15, FULL -coverage/unreachable/divide/main.rs, 16, FULL -coverage/unreachable/divide/main.rs, 17, FULL diff --git a/tests/coverage/unreachable/divide/main.rs b/tests/coverage/unreachable/divide/main.rs deleted file mode 100644 index ba6afab83135..000000000000 --- a/tests/coverage/unreachable/divide/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -// Test that checks for UNREACHABLE panics. The panic is reported as NONE for the assumption that the divisor is not zero. -fn divide(a: i32, b: i32) -> i32 { - if b != 0 { - return a / b; - } else { - panic!("Division by zero"); - } -} - -#[kani::proof] -fn main() { - let y: i32 = kani::any(); - kani::assume(y != 0); - let result = divide(10, y); - assert_eq!(result, 5); -} diff --git a/tests/coverage/unreachable/early-return/expected b/tests/coverage/unreachable/early-return/expected deleted file mode 100644 index 466c1775408b..000000000000 --- a/tests/coverage/unreachable/early-return/expected +++ /dev/null @@ -1,10 +0,0 @@ -coverage/unreachable/early-return/main.rs, 5, PARTIAL -coverage/unreachable/early-return/main.rs, 6, FULL -coverage/unreachable/early-return/main.rs, 7, FULL -coverage/unreachable/early-return/main.rs, 10, NONE -coverage/unreachable/early-return/main.rs, 11, PARTIAL -coverage/unreachable/early-return/main.rs, 15, FULL -coverage/unreachable/early-return/main.rs, 16, FULL -coverage/unreachable/early-return/main.rs, 17, FULL -coverage/unreachable/early-return/main.rs, 18, FULL -coverage/unreachable/early-return/main.rs, 19, FULL diff --git a/tests/coverage/unreachable/if-statement/expected b/tests/coverage/unreachable/if-statement/expected deleted file mode 100644 index 8b481863a163..000000000000 --- a/tests/coverage/unreachable/if-statement/expected +++ /dev/null @@ -1,10 +0,0 @@ -coverage/unreachable/if-statement/main.rs, 5, PARTIAL -coverage/unreachable/if-statement/main.rs, 7, PARTIAL -coverage/unreachable/if-statement/main.rs, 8, NONE -coverage/unreachable/if-statement/main.rs, 9, NONE -coverage/unreachable/if-statement/main.rs, 11, NONE -coverage/unreachable/if-statement/main.rs, 13, FULL -coverage/unreachable/if-statement/main.rs, 17, FULL -coverage/unreachable/if-statement/main.rs, 18, FULL -coverage/unreachable/if-statement/main.rs, 19, FULL -coverage/unreachable/if-statement/main.rs, 20, FULL diff --git a/tests/coverage/unreachable/multiple-harnesses/expected b/tests/coverage/unreachable/multiple-harnesses/expected deleted file mode 100644 index 17a52666c08d..000000000000 --- a/tests/coverage/unreachable/multiple-harnesses/expected +++ /dev/null @@ -1,39 +0,0 @@ -Checking harness fully_covered... -coverage/unreachable/multiple-harnesses/main.rs, 5, FULL -coverage/unreachable/multiple-harnesses/main.rs, 7, FULL -coverage/unreachable/multiple-harnesses/main.rs, 8, FULL -coverage/unreachable/multiple-harnesses/main.rs, 9, FULL -coverage/unreachable/multiple-harnesses/main.rs, 11, FULL -coverage/unreachable/multiple-harnesses/main.rs, 13, FULL -coverage/unreachable/multiple-harnesses/main.rs, 14, FULL -coverage/unreachable/multiple-harnesses/main.rs, 15, FULL -coverage/unreachable/multiple-harnesses/main.rs, 17, FULL -coverage/unreachable/multiple-harnesses/main.rs, 20, FULL -coverage/unreachable/multiple-harnesses/main.rs, 21, FULL -coverage/unreachable/multiple-harnesses/main.rs, 23, FULL -coverage/unreachable/multiple-harnesses/main.rs, 26, FULL -coverage/unreachable/multiple-harnesses/main.rs, 40, FULL -coverage/unreachable/multiple-harnesses/main.rs, 41, FULL -coverage/unreachable/multiple-harnesses/main.rs, 42, FULL -coverage/unreachable/multiple-harnesses/main.rs, 43, FULL -coverage/unreachable/multiple-harnesses/main.rs, 44, FULL - -Checking harness mostly_covered... -coverage/unreachable/multiple-harnesses/main.rs, 5, FULL -coverage/unreachable/multiple-harnesses/main.rs, 7, FULL -coverage/unreachable/multiple-harnesses/main.rs, 8, FULL -coverage/unreachable/multiple-harnesses/main.rs, 9, FULL -coverage/unreachable/multiple-harnesses/main.rs, 11, FULL -coverage/unreachable/multiple-harnesses/main.rs, 13, FULL -coverage/unreachable/multiple-harnesses/main.rs, 14, FULL -coverage/unreachable/multiple-harnesses/main.rs, 15, FULL -coverage/unreachable/multiple-harnesses/main.rs, 17, FULL -coverage/unreachable/multiple-harnesses/main.rs, 20, FULL -coverage/unreachable/multiple-harnesses/main.rs, 21, FULL -coverage/unreachable/multiple-harnesses/main.rs, 23, NONE -coverage/unreachable/multiple-harnesses/main.rs, 26, FULL -coverage/unreachable/multiple-harnesses/main.rs, 31, FULL -coverage/unreachable/multiple-harnesses/main.rs, 32, FULL -coverage/unreachable/multiple-harnesses/main.rs, 33, FULL -coverage/unreachable/multiple-harnesses/main.rs, 34, FULL -coverage/unreachable/multiple-harnesses/main.rs, 35, FULL diff --git a/tests/coverage/unreachable/return/expected b/tests/coverage/unreachable/return/expected deleted file mode 100644 index 139f81840aab..000000000000 --- a/tests/coverage/unreachable/return/expected +++ /dev/null @@ -1,8 +0,0 @@ -coverage/unreachable/return/main.rs, 5, FULL -coverage/unreachable/return/main.rs, 6, FULL -coverage/unreachable/return/main.rs, 9, NONE -coverage/unreachable/return/main.rs, 10, PARTIAL -coverage/unreachable/return/main.rs, 14, FULL -coverage/unreachable/return/main.rs, 15, FULL -coverage/unreachable/return/main.rs, 16, FULL -coverage/unreachable/return/main.rs, 17, FULL diff --git a/tests/coverage/unreachable/return/main.rs b/tests/coverage/unreachable/return/main.rs deleted file mode 100644 index ccd76a5b4f8e..000000000000 --- a/tests/coverage/unreachable/return/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -fn greet(is_guest: bool) -> &'static str { - if is_guest { - return "Welcome, Guest!"; - } - // This part is unreachable if is_guest is true. - "Hello, User!" -} - -#[kani::proof] -fn main() { - let is_guest = true; - let message = greet(is_guest); - assert_eq!(message, "Welcome, Guest!"); -} diff --git a/tests/coverage/unreachable/tutorial_unreachable/expected b/tests/coverage/unreachable/tutorial_unreachable/expected deleted file mode 100644 index 624aa520edc9..000000000000 --- a/tests/coverage/unreachable/tutorial_unreachable/expected +++ /dev/null @@ -1,5 +0,0 @@ -coverage/unreachable/tutorial_unreachable/main.rs, 6, FULL -coverage/unreachable/tutorial_unreachable/main.rs, 7, FULL -coverage/unreachable/tutorial_unreachable/main.rs, 8, PARTIAL -coverage/unreachable/tutorial_unreachable/main.rs, 9, NONE -coverage/unreachable/tutorial_unreachable/main.rs, 11, FULL diff --git a/tests/coverage/unreachable/tutorial_unreachable/main.rs b/tests/coverage/unreachable/tutorial_unreachable/main.rs deleted file mode 100644 index c56e591446cf..000000000000 --- a/tests/coverage/unreachable/tutorial_unreachable/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -#[kani::proof] -fn unreachable_example() { - let x = 5; - let y = x + 2; - if x > y { - assert!(x < 8); - } -} diff --git a/tests/coverage/unreachable/variant/expected b/tests/coverage/unreachable/variant/expected deleted file mode 100644 index 8fa3ec8b870f..000000000000 --- a/tests/coverage/unreachable/variant/expected +++ /dev/null @@ -1,10 +0,0 @@ -coverage/unreachable/variant/main.rs, 15, FULL -coverage/unreachable/variant/main.rs, 16, NONE -coverage/unreachable/variant/main.rs, 17, NONE -coverage/unreachable/variant/main.rs, 18, FULL -coverage/unreachable/variant/main.rs, 19, NONE -coverage/unreachable/variant/main.rs, 21, NONE -coverage/unreachable/variant/main.rs, 23, FULL -coverage/unreachable/variant/main.rs, 27, FULL -coverage/unreachable/variant/main.rs, 28, FULL -coverage/unreachable/variant/main.rs, 29, FULL diff --git a/tests/coverage/unreachable/vectors/expected b/tests/coverage/unreachable/vectors/expected deleted file mode 100644 index e47941f17db2..000000000000 --- a/tests/coverage/unreachable/vectors/expected +++ /dev/null @@ -1,6 +0,0 @@ -coverage/unreachable/vectors/main.rs, 8, FULL -coverage/unreachable/vectors/main.rs, 11, FULL -coverage/unreachable/vectors/main.rs, 13, PARTIAL -coverage/unreachable/vectors/main.rs, 15, NONE -coverage/unreachable/vectors/main.rs, 17, FULL -coverage/unreachable/vectors/main.rs, 19, FULL diff --git a/tests/coverage/unreachable/vectors/main.rs b/tests/coverage/unreachable/vectors/main.rs deleted file mode 100644 index a44c4bb47c3d..000000000000 --- a/tests/coverage/unreachable/vectors/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! Checks coverage results in an example with a guarded out-of-bounds access. - -#[kani::proof] -fn main() { - let numbers = vec![1, 2, 3, 4, 5]; - - // Attempt to access the 10th element of the vector, which is out of bounds. - let tenth_element = numbers.get(9); - - if let Some(value) = tenth_element { - // This part is unreachable since the vector has only 5 elements (indices 0 to 4). - println!("The 10th element is: {}", value); - } else { - println!("The 10th element is out of bounds!"); - } -} diff --git a/tests/coverage/unreachable/while-loop-break/expected b/tests/coverage/unreachable/while-loop-break/expected deleted file mode 100644 index dc66d3e823d3..000000000000 --- a/tests/coverage/unreachable/while-loop-break/expected +++ /dev/null @@ -1,11 +0,0 @@ -coverage/unreachable/while-loop-break/main.rs, 8, FULL -coverage/unreachable/while-loop-break/main.rs, 9, PARTIAL -coverage/unreachable/while-loop-break/main.rs, 10, FULL -coverage/unreachable/while-loop-break/main.rs, 11, FULL -coverage/unreachable/while-loop-break/main.rs, 13, FULL -coverage/unreachable/while-loop-break/main.rs, 15, NONE -coverage/unreachable/while-loop-break/main.rs, 16, PARTIAL -coverage/unreachable/while-loop-break/main.rs, 20, FULL -coverage/unreachable/while-loop-break/main.rs, 21, FULL -coverage/unreachable/while-loop-break/main.rs, 22, FULL -coverage/unreachable/while-loop-break/main.rs, 23, FULL diff --git a/tests/coverage/while-loop-break/expected b/tests/coverage/while-loop-break/expected new file mode 100644 index 000000000000..34afef9ee12c --- /dev/null +++ b/tests/coverage/while-loop-break/expected @@ -0,0 +1,13 @@ +Source-based code coverage results: + +main.rs (find_first_negative)\ + * 7:1 - 8:22 COVERED\ + * 9:11 - 9:29 COVERED\ + * 10:12 - 10:27 COVERED\ + * 11:20 - 11:37 COVERED\ + * 12:10 - 13:19 COVERED\ + * 15:5 - 15:9 UNCOVERED\ + * 16:1 - 16:2 COVERED + +main.rs (main)\ + * 19:1 - 23:2 COVERED diff --git a/tests/coverage/unreachable/while-loop-break/main.rs b/tests/coverage/while-loop-break/main.rs similarity index 100% rename from tests/coverage/unreachable/while-loop-break/main.rs rename to tests/coverage/while-loop-break/main.rs diff --git a/tests/ui/save-coverage-results/expected b/tests/ui/save-coverage-results/expected new file mode 100644 index 000000000000..77c9a0c33dcb --- /dev/null +++ b/tests/ui/save-coverage-results/expected @@ -0,0 +1,3 @@ +Source-based code coverage results: + +[info] Coverage results saved to diff --git a/tests/ui/save-coverage-results/test.rs b/tests/ui/save-coverage-results/test.rs new file mode 100644 index 000000000000..0a422280e51d --- /dev/null +++ b/tests/ui/save-coverage-results/test.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: --coverage -Zsource-coverage + +//! Checks that we print a line which points the user to the path where coverage +//! results have been saved. The line should look like: +//! ``` +//! [info] Coverage results saved to /path/to/outdir/kanicov_YYYY-MM-DD_hh-mm +//! ``` + +fn _other_function() { + println!("Hello, world!"); +} + +fn test_cov(val: u32) -> bool { + if val < 3 || val == 42 { true } else { false } +} + +#[cfg_attr(kani, kani::proof)] +fn main() { + let test1 = test_cov(1); + let test2 = test_cov(2); + assert!(test1); + assert!(test2); +} diff --git a/tools/compiletest/src/runtest.rs b/tools/compiletest/src/runtest.rs index 50f1e3035ac8..c8387c691296 100644 --- a/tools/compiletest/src/runtest.rs +++ b/tools/compiletest/src/runtest.rs @@ -320,7 +320,7 @@ impl<'test> TestCx<'test> { kani.env("RUSTFLAGS", self.props.compile_flags.join(" ")); } kani.arg(&self.testpaths.file).args(&self.props.kani_flags); - kani.arg("--coverage").args(["-Z", "line-coverage"]); + kani.arg("--coverage").args(["-Z", "source-coverage"]); if !self.props.cbmc_flags.is_empty() { kani.arg("--cbmc-args").args(&self.props.cbmc_flags); From 5aad1a9ebc28332756eba58bef314c2b1499ea8f Mon Sep 17 00:00:00 2001 From: Jaisurya Nanduri <91620234+jaisnan@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:47:31 -0400 Subject: [PATCH 043/159] Upgrade toolchain to 08/28 (#3454) Upgrades toolchain to 08/28 Culprit upstream changes: 1. https://github.com/rust-lang/rust/pull/128812 2. https://github.com/rust-lang/rust/pull/128703 3. https://github.com/rust-lang/rust/pull/127679 4. https://github.com/rust-lang/rust-clippy/pull/12993 5. https://github.com/rust-lang/cargo/pull/14370 6. https://github.com/rust-lang/rust/pull/128806 Resolves https://github.com/model-checking/kani/issues/3429 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- Cargo.toml | 3 +++ cprover_bindings/Cargo.toml | 3 +++ kani-compiler/Cargo.toml | 3 +++ kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs | 7 +++++-- kani-compiler/src/kani_middle/coercion.rs | 2 +- .../src/kani_middle/points_to/points_to_analysis.rs | 4 ++-- kani-compiler/src/kani_middle/transform/internal_mir.rs | 7 +++---- kani-compiler/src/session.rs | 3 ++- kani-driver/src/call_cargo.rs | 7 ++++++- kani_metadata/Cargo.toml | 3 +++ library/kani/Cargo.toml | 3 +++ library/kani_core/Cargo.toml | 3 +++ library/kani_macros/Cargo.toml | 3 +++ library/kani_macros/src/lib.rs | 2 +- rust-toolchain.toml | 2 +- tools/compiletest/Cargo.toml | 3 +++ tools/scanner/src/bin/scan.rs | 1 + 17 files changed, 46 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2301983fcb4..149b8d2c93c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,3 +71,6 @@ exclude = [ "tests/script-based-pre/build-cache-dirty/target/new_dep", "tests/script-based-pre/verify_std_cmd/tmp_dir/target/kani_verify_std", ] + +[workspace.lints.clippy] +too_long_first_doc_paragraph = "allow" diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index c53ffb207fcf..b0e3c578bbcf 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -24,3 +24,6 @@ linear-map = {version = "1.2", features = ["serde_impl"]} [dev-dependencies] serde_test = "1" memuse = "0.2.1" + +[lints] +workspace = true diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index fcb33b8074e4..fc06c393f3eb 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -35,3 +35,6 @@ write_json_symtab = [] [package.metadata.rust-analyzer] # This package uses rustc crates. rustc_private=true + +[lints] +workspace = true diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs index 9baa3c59f4c2..016bf0d3a261 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs @@ -579,7 +579,10 @@ impl<'tcx> GotocCtx<'tcx> { .unwrap(); self.codegen_fndef_type(instance) } - ty::FnPtr(sig) => self.codegen_function_sig(*sig).to_pointer(), + ty::FnPtr(sig_tys, hdr) => { + let sig = sig_tys.with(*hdr); + self.codegen_function_sig(sig).to_pointer() + } ty::Closure(_, subst) => self.codegen_ty_closure(ty, subst), ty::Coroutine(..) => self.codegen_ty_coroutine(ty), ty::Never => self.ensure_struct(NEVER_TYPE_EMPTY_STRUCT_NAME, "!", |_, _| vec![]), @@ -1014,7 +1017,7 @@ impl<'tcx> GotocCtx<'tcx> { // These types were blocking stdlib. Doing the default thing to unblock. // https://github.com/model-checking/kani/issues/214 - ty::FnPtr(_) => self.codegen_ty(pointee_type).to_pointer(), + ty::FnPtr(_, _) => self.codegen_ty(pointee_type).to_pointer(), // These types have no regression tests for them. // For soundness, hold off on generating them till we have test-cases. diff --git a/kani-compiler/src/kani_middle/coercion.rs b/kani-compiler/src/kani_middle/coercion.rs index 822f32631e0c..38760fca300d 100644 --- a/kani-compiler/src/kani_middle/coercion.rs +++ b/kani-compiler/src/kani_middle/coercion.rs @@ -99,7 +99,7 @@ pub fn extract_unsize_casting<'tcx>( coerce_info.dst_ty )); // Find the tail of the coercion that determines the type of metadata to be stored. - let (src_base_ty, dst_base_ty) = tcx.struct_lockstep_tails_erasing_lifetimes( + let (src_base_ty, dst_base_ty) = tcx.struct_lockstep_tails_for_codegen( src_pointee_ty, dst_pointee_ty, ParamEnv::reveal_all(), 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 index eff7dd1fc486..68343ccaad37 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -53,7 +53,7 @@ struct PointsToAnalysis<'a, '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. + /// 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). @@ -521,7 +521,7 @@ impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { | Rvalue::ShallowInitBox(operand, _) | Rvalue::Cast(_, operand, _) | Rvalue::Repeat(operand, ..) => self.successors_for_operand(state, operand), - Rvalue::Ref(_, _, ref_place) | Rvalue::AddressOf(_, ref_place) => { + Rvalue::Ref(_, _, ref_place) | Rvalue::RawPtr(_, ref_place) => { // Here, a reference to a place is created, which leaves the place // unchanged. state.resolve_place(ref_place, self.instance) diff --git a/kani-compiler/src/kani_middle/transform/internal_mir.rs b/kani-compiler/src/kani_middle/transform/internal_mir.rs index 0dcf7d47c13a..ba23fbf2dddf 100644 --- a/kani-compiler/src/kani_middle/transform/internal_mir.rs +++ b/kani-compiler/src/kani_middle/transform/internal_mir.rs @@ -210,10 +210,9 @@ impl RustcInternalMir for Rvalue { 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::AddressOf(mutability, place) => { + rustc_middle::mir::Rvalue::RawPtr(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( diff --git a/kani-compiler/src/session.rs b/kani-compiler/src/session.rs index ecc084e0cc85..f5dd78ff86b0 100644 --- a/kani-compiler/src/session.rs +++ b/kani-compiler/src/session.rs @@ -61,7 +61,8 @@ static JSON_PANIC_HOOK: LazyLock) + Sync + Lrc::new(SourceMap::new(FilePathMapping::empty())), fallback_bundle, false, - HumanReadableErrorType::Default(ColorConfig::Never), + HumanReadableErrorType::Default, + ColorConfig::Never, ); let diagnostic = DiagInner::new(rustc_errors::Level::Bug, msg); emitter.emit_diagnostic(diagnostic); diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index e69062a0cd4f..ef289cead4c2 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -73,6 +73,11 @@ impl KaniSession { cargo_args.push("-v".into()); } + // We need this suffix push because of https://github.com/rust-lang/cargo/pull/14370 + // which removes the library suffix from the build-std command + let mut full_path = std_path.to_path_buf(); + full_path.push("library"); + // Since we are verifying the standard library, we set the reachability to all crates. let mut cmd = setup_cargo_command()?; cmd.args(&cargo_args) @@ -82,7 +87,7 @@ impl KaniSession { // https://doc.rust-lang.org/cargo/reference/environment-variables.html .env("CARGO_ENCODED_RUSTFLAGS", rustc_args.join(OsStr::new("\x1f"))) .env("CARGO_TERM_PROGRESS_WHEN", "never") - .env("__CARGO_TESTS_ONLY_SRC_ROOT", std_path.as_os_str()); + .env("__CARGO_TESTS_ONLY_SRC_ROOT", full_path.as_os_str()); Ok(self .run_build(cmd)? diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index de91900d6d9c..efa28288d148 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -16,3 +16,6 @@ cbmc = { path = "../cprover_bindings", package = "cprover_bindings" } strum = "0.26" strum_macros = "0.26" clap = { version = "4.4.11", features = ["derive"] } + +[lints] +workspace = true diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index 7d7ced8ee0b7..a3692f95e3f5 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -15,3 +15,6 @@ kani_core = { path = "../kani_core" } [features] concrete_playback = [] no_core=["kani_macros/no_core"] + +[lints] +workspace = true diff --git a/library/kani_core/Cargo.toml b/library/kani_core/Cargo.toml index 8928992c3f16..9df828e77c5a 100644 --- a/library/kani_core/Cargo.toml +++ b/library/kani_core/Cargo.toml @@ -14,3 +14,6 @@ kani_macros = { path = "../kani_macros"} [features] no_core=["kani_macros/no_core"] + +[lints] +workspace = true diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 475e2978df91..574960e5fc0a 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -23,3 +23,6 @@ rustc_private = true [features] no_core = [] + +[lints] +workspace = true diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 6fe0979f08bc..63ed990a4840 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -8,7 +8,6 @@ // So we have to enable this on the commandline (see kani-rustc) with: // RUSTFLAGS="-Zcrate-attr=feature(register_tool) -Zcrate-attr=register_tool(kanitool)" #![feature(proc_macro_diagnostic)] - mod derive; // proc_macro::quote is nightly-only, so we'll cobble things together instead @@ -65,6 +64,7 @@ pub fn recursion(attr: TokenStream, item: TokenStream) -> TokenStream { /// Set Loop unwind limit for proof harnesses /// The attribute `#[kani::unwind(arg)]` can only be called alongside `#[kani::proof]`. /// arg - Takes in a integer value (u32) that represents the unwind value for the harness. +#[allow(clippy::too_long_first_doc_paragraph)] #[proc_macro_attribute] pub fn unwind(attr: TokenStream, item: TokenStream) -> TokenStream { attr_impl::unwind(attr, item) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e3b6229d73c6..3421b9b3adc9 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-07" +channel = "nightly-2024-08-28" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] diff --git a/tools/compiletest/Cargo.toml b/tools/compiletest/Cargo.toml index 78a90d9aae38..967bc1715525 100644 --- a/tools/compiletest/Cargo.toml +++ b/tools/compiletest/Cargo.toml @@ -30,3 +30,6 @@ wait-timeout = "0.2.0" [target.'cfg(unix)'.dependencies] libc = "0.2" + +[lints] +workspace = true diff --git a/tools/scanner/src/bin/scan.rs b/tools/scanner/src/bin/scan.rs index 92b5319ec780..197b34d23422 100644 --- a/tools/scanner/src/bin/scan.rs +++ b/tools/scanner/src/bin/scan.rs @@ -14,6 +14,7 @@ //! together with the name of the analysis. //! //! Look at each analysis documentation to see which files an analysis produces. +#![feature(rustc_private)] use scanner::run_all; use std::process::ExitCode; From e28f3db81d9a5c8e5a2dcf9ed344dbeb11dbef06 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Wed, 28 Aug 2024 18:06:19 -0400 Subject: [PATCH 044/159] Extra tests and bug fixes to the delayed UB instrumentation (#3419) This PR is a follow-up to #3374. It introduces the following changes: - Instrument more writes to avoid the case when points-to analysis overapproximates too much. - Add extra tests featuring safe abstractions from standard library. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../kani_middle/points_to/points_to_graph.rs | 78 ++++++++++++------ .../delayed_ub/instrumentation_visitor.rs | 81 ++++++++++++++----- .../transform/check_uninit/delayed_ub/mod.rs | 4 +- .../uninit/delayed-ub-overapprox.expected | 1 + .../expected/uninit/delayed-ub-overapprox.rs | 32 ++++++++ tests/std-checks/std/Cargo.toml | 13 +++ tests/std-checks/std/atomic.expected | 1 + tests/std-checks/std/boxed.expected | 1 + tests/std-checks/std/src/boxed.rs | 40 +++++++++ tests/std-checks/std/src/lib.rs | 9 +++ tests/std-checks/std/src/sync/atomic.rs | 77 ++++++++++++++++++ tests/std-checks/std/src/sync/mod.rs | 4 + 12 files changed, 295 insertions(+), 46 deletions(-) create mode 100644 tests/expected/uninit/delayed-ub-overapprox.expected create mode 100644 tests/expected/uninit/delayed-ub-overapprox.rs create mode 100644 tests/std-checks/std/Cargo.toml create mode 100644 tests/std-checks/std/atomic.expected create mode 100644 tests/std-checks/std/boxed.expected create mode 100644 tests/std-checks/std/src/boxed.rs create mode 100644 tests/std-checks/std/src/lib.rs create mode 100644 tests/std-checks/std/src/sync/atomic.rs create mode 100644 tests/std-checks/std/src/sync/mod.rs 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 index d2e80f24c737..61804d0ff71a 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_graph.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_graph.rs @@ -63,6 +63,27 @@ impl<'tcx> MemLoc<'tcx> { } } +/// Data structure to keep track of both successors and ancestors of the node. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +struct NodeData<'tcx> { + successors: HashSet>, + ancestors: HashSet>, +} + +impl<'tcx> NodeData<'tcx> { + /// Merge two instances of NodeData together, return true if the original one was updated and + /// false otherwise. + fn merge(&mut self, other: Self) -> bool { + let successors_before = self.successors.len(); + let ancestors_before = self.ancestors.len(); + self.successors.extend(other.successors); + self.ancestors.extend(other.ancestors); + let successors_after = self.successors.len(); + let ancestors_after = self.ancestors.len(); + successors_before != successors_after || ancestors_before != ancestors_after + } +} + /// 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. /// @@ -82,24 +103,39 @@ impl<'tcx> MemLoc<'tcx> { #[derive(Clone, Debug, PartialEq, Eq)] pub struct PointsToGraph<'tcx> { /// A hash map of node --> {nodes} edges. - edges: HashMap, HashSet>>, + nodes: HashMap, NodeData<'tcx>>, } impl<'tcx> PointsToGraph<'tcx> { pub fn empty() -> Self { - Self { edges: HashMap::new() } + Self { nodes: 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() + nodes + .iter() + .flat_map(|node| self.nodes.get(node).cloned().unwrap_or_default().successors) + .collect() } - /// For each node in `from`, add an edge to each node in `to`. + /// Collect all nodes which have outgoing edges to `nodes`. + pub fn ancestors(&self, nodes: &HashSet>) -> HashSet> { + nodes + .iter() + .flat_map(|node| self.nodes.get(node).cloned().unwrap_or_default().ancestors) + .collect() + } + + /// For each node in `from`, add an edge to each node in `to` (and the reverse for ancestors). 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()); + let node_pointees = self.nodes.entry(*node).or_default(); + node_pointees.successors.extend(to.iter()); + } + for node in to.iter() { + let node_pointees = self.nodes.entry(*node).or_default(); + node_pointees.ancestors.extend(from.iter()); } } @@ -150,16 +186,16 @@ impl<'tcx> PointsToGraph<'tcx> { /// 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(); + self.nodes.keys().map(|from| format!("\t\"{:?}\"", from)).collect(); nodes.sort(); let nodes_str = nodes.join("\n"); let mut edges: Vec = self - .edges + .nodes .iter() .flat_map(|(from, to)| { let from = format!("\"{:?}\"", from); - to.iter().map(move |to| { + to.successors.iter().map(move |to| { let to = format!("\"{:?}\"", to); format!("\t{} -> {}", from.clone(), to) }) @@ -178,24 +214,18 @@ impl<'tcx> PointsToGraph<'tcx> { // 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(_))); + let statics = self.nodes.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()); + result.nodes.entry(next_target).or_insert_with(|| { + let outgoing_edges = self.nodes.get(&next_target).cloned().unwrap_or_default(); + queue.extend(outgoing_edges.successors.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 @@ -206,12 +236,10 @@ 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; + for (node, data) in other.nodes.iter() { + let existing_node = self.nodes.entry(*node).or_default(); + let changed = existing_node.merge(data.clone()); + updated |= changed; } updated } 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 index 25059297d3d5..41a2b69d1fdf 100644 --- 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 @@ -118,25 +118,70 @@ impl<'a, 'tcx> MirVisitor for InstrumentationVisitor<'a, 'tcx> { } 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()) }); + // In order to check whether we should get-instrument the place, see if it resolves to the + // analysis target. + let needs_get = { + self.points_to + .resolve_place_stable(place.clone(), self.current_instance, self.tcx) + .intersection(&self.analysis_targets) + .next() + .is_some() + }; + + // In order to check whether we should set-instrument the place, we need to figure out if + // the place has a common ancestor of the same level with the target. + // + // This is needed because instrumenting the place only if it resolves to the target could give + // false positives in presence of some aliasing relations. + // + // Here is a simple example: + // ``` + // fn foo(val_1: u32, val_2: u32, flag: bool) { + // let reference = if flag { + // &val_1 + // } else { + // &val_2 + // }; + // let _ = *reference; + // } + // ``` + // It yields the following aliasing graph: + // + // `val_1 <-- reference --> val_2` + // + // If `val_1` is a legitimate instrumentation target, we would get-instrument an instruction + // that reads from `*reference`, but that could mean that `val_2` is checked, too. Hence, + // if we don't set-instrument `val_2` we will get a false-positive. + // + // See `tests/expected/uninit/delayed-ub-overapprox.rs` for a more specific example. + let needs_set = { + let mut has_common_ancestor = false; + let mut self_ancestors = + self.points_to.resolve_place_stable(place.clone(), self.current_instance, self.tcx); + let mut target_ancestors = self.analysis_targets.clone(); + + while !self_ancestors.is_empty() || !target_ancestors.is_empty() { + if self_ancestors.intersection(&target_ancestors).next().is_some() { + has_common_ancestor = true; + break; + } + self_ancestors = self.points_to.ancestors(&self_ancestors); + target_ancestors = self.points_to.ancestors(&target_ancestors); } + + has_common_ancestor + }; + + // If we are mutating the place, initialize it. + if ptx.is_mutating() && needs_set { + self.push_target(MemoryInitOp::SetRef { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } else if !ptx.is_mutating() && needs_get { + // 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 index e179947d2777..c31f72c9b5c3 100644 --- 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 @@ -100,9 +100,7 @@ impl GlobalPass for DelayedUbPass { } // 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)); - } + analysis_targets.extend(global_points_to_graph.successors(&targets)); // If we are generating MIR, generate the points-to graph as well. if tcx.sess.opts.output_types.contains_key(&OutputType::Mir) { diff --git a/tests/expected/uninit/delayed-ub-overapprox.expected b/tests/expected/uninit/delayed-ub-overapprox.expected new file mode 100644 index 000000000000..01a90d50b557 --- /dev/null +++ b/tests/expected/uninit/delayed-ub-overapprox.expected @@ -0,0 +1 @@ +Complete - 1 successfully verified harnesses, 0 failures, 1 total. diff --git a/tests/expected/uninit/delayed-ub-overapprox.rs b/tests/expected/uninit/delayed-ub-overapprox.rs new file mode 100644 index 000000000000..68585443fd38 --- /dev/null +++ b/tests/expected/uninit/delayed-ub-overapprox.rs @@ -0,0 +1,32 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Z uninit-checks + +//! Make sure that no false positives are generated when points-to analysis overapproximates +//! aliasing. + +#[kani::proof] +fn check_delayed_ub_overapprox() { + unsafe { + let mut value: u128 = 0; + let value_ref = &mut value; + // Perform a call to the helper before mutable pointer cast. This way, a check inserted into + // the helper will pass. + helper(value_ref); + // Cast between two pointers of different padding, which will mark `value` as a possible + // delayed UB analysis target. + let ptr = value_ref as *mut _ as *mut (u8, u32, u64); + *ptr = (4, 4, 4); // Note that since we never read from `value` after overwriting it, no delayed UB occurs. + // Create another `value` and call helper. Note that since helper could potentially + // dereference a delayed-UB pointer, an initialization check will be added to the helper. + // Hence, delayed UB analysis needs to mark the value as properly initialized in shadow + // memory to avoid the spurious failure. + let mut value2: u128 = 0; + helper(&value2); + } +} + +/// A helper that could trigger delayed UB. +fn helper(reference: &u128) -> bool { + *reference == 42 +} diff --git a/tests/std-checks/std/Cargo.toml b/tests/std-checks/std/Cargo.toml new file mode 100644 index 000000000000..6c2b4e0073cb --- /dev/null +++ b/tests/std-checks/std/Cargo.toml @@ -0,0 +1,13 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +[package] +name = "verify-std" +version = "0.1.0" +edition = "2021" +description = "This crate contains contracts and harnesses for std library" + +[package.metadata.kani] +unstable = { function-contracts = true, mem-predicates = true, uninit-checks = true } + +[package.metadata.kani.flags] +output-format = "terse" diff --git a/tests/std-checks/std/atomic.expected b/tests/std-checks/std/atomic.expected new file mode 100644 index 000000000000..158e99910c43 --- /dev/null +++ b/tests/std-checks/std/atomic.expected @@ -0,0 +1 @@ +Complete - 5 successfully verified harnesses, 0 failures, 5 total. diff --git a/tests/std-checks/std/boxed.expected b/tests/std-checks/std/boxed.expected new file mode 100644 index 000000000000..4426ff6c02cd --- /dev/null +++ b/tests/std-checks/std/boxed.expected @@ -0,0 +1 @@ +Complete - 2 successfully verified harnesses, 0 failures, 2 total. diff --git a/tests/std-checks/std/src/boxed.rs b/tests/std-checks/std/src/boxed.rs new file mode 100644 index 000000000000..112b076ae4a3 --- /dev/null +++ b/tests/std-checks/std/src/boxed.rs @@ -0,0 +1,40 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +extern crate kani; + +/// Create wrapper functions to standard library functions that contains their contract. +pub mod contracts { + use kani::{mem::*, requires}; + + /// The actual pre-condition is more complicated: + /// + /// "For non-zero-sized values, ... a value: *mut T that has been allocated with the Global + /// allocator with Layout::for_value(&*value) may be converted into a box using + /// Box::::from_raw(value)." + /// + /// "For zero-sized values, the Box pointer still has to be valid for reads and writes and + /// sufficiently aligned." + #[requires(can_dereference(raw))] + pub unsafe fn from_raw(raw: *mut T) -> Box { + std::boxed::Box::from_raw(raw) + } +} + +#[cfg(kani)] +mod verify { + use super::*; + + #[kani::proof_for_contract(contracts::from_raw)] + pub fn check_from_raw_u32() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u32 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_raw(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_raw)] + pub fn check_from_raw_unit() { + let ptr = kani::any::() as *mut (); + let _ = unsafe { contracts::from_raw(ptr) }; + } +} diff --git a/tests/std-checks/std/src/lib.rs b/tests/std-checks/std/src/lib.rs new file mode 100644 index 000000000000..d78cf75a14ed --- /dev/null +++ b/tests/std-checks/std/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Top file that includes all sub-modules mimicking std structure. + +extern crate kani; + +mod boxed; +mod sync; diff --git a/tests/std-checks/std/src/sync/atomic.rs b/tests/std-checks/std/src/sync/atomic.rs new file mode 100644 index 000000000000..85c9a3380775 --- /dev/null +++ b/tests/std-checks/std/src/sync/atomic.rs @@ -0,0 +1,77 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +extern crate kani; + +use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8, AtomicUsize}; + +/// Create wrapper functions to standard library functions that contains their contract. +pub mod contracts { + use super::*; + use kani::{mem::*, requires}; + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_u8<'a>(ptr: *mut u8) -> &'a AtomicU8 { + AtomicU8::from_ptr(ptr) + } + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_u16<'a>(ptr: *mut u16) -> &'a AtomicU16 { + AtomicU16::from_ptr(ptr) + } + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_u32<'a>(ptr: *mut u32) -> &'a AtomicU32 { + AtomicU32::from_ptr(ptr) + } + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_u64<'a>(ptr: *mut u64) -> &'a AtomicU64 { + AtomicU64::from_ptr(ptr) + } + + #[requires(can_dereference(ptr))] + pub unsafe fn from_ptr_usize<'a>(ptr: *mut usize) -> &'a AtomicUsize { + AtomicUsize::from_ptr(ptr) + } +} + +#[cfg(kani)] +mod verify { + use super::*; + + #[kani::proof_for_contract(contracts::from_ptr_u8)] + pub fn check_from_ptr_u8() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u8 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_u8(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_ptr_u16)] + pub fn check_from_ptr_u16() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u16 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_u16(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_ptr_u32)] + pub fn check_from_ptr_u32() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u32 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_u32(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_ptr_u64)] + pub fn check_from_ptr_u64() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut u64 }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_u64(ptr) }; + } + + #[kani::proof_for_contract(contracts::from_ptr_usize)] + pub fn check_from_ptr_usize() { + let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::()) as *mut usize }; + unsafe { ptr.write(kani::any()) }; + let _ = unsafe { contracts::from_ptr_usize(ptr) }; + } +} diff --git a/tests/std-checks/std/src/sync/mod.rs b/tests/std-checks/std/src/sync/mod.rs new file mode 100644 index 000000000000..14b3b086e487 --- /dev/null +++ b/tests/std-checks/std/src/sync/mod.rs @@ -0,0 +1,4 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +mod atomic; From 691f81e7732152479b7065fddf26227a1ee675c1 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Thu, 29 Aug 2024 15:56:26 -0400 Subject: [PATCH 045/159] Upgrade Toolchain to 8/29 (#3468) Upgrade toolchain to 8/29. Culprit upstream changes: https://github.com/rust-lang/rust/pull/129686 Resolves #3466 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/codegen/assert.rs | 6 +++--- .../src/codegen_cprover_gotoc/codegen/function.rs | 12 ++++++------ .../src/codegen_cprover_gotoc/codegen/statement.rs | 6 +++--- rust-toolchain.toml | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs index 4ee81d0c7d3e..72ccb95cf97b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs @@ -21,7 +21,7 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::{Expr, Location, Stmt, Type}; use cbmc::InternedString; -use rustc_middle::mir::coverage::CodeRegion; +use rustc_middle::mir::coverage::SourceRegion; use stable_mir::mir::{Place, ProjectionElem}; use stable_mir::ty::{Span as SpanStable, TypeAndMut}; use strum_macros::{AsRefStr, EnumString}; @@ -153,14 +153,14 @@ impl<'tcx> GotocCtx<'tcx> { &self, counter_data: &str, span: SpanStable, - code_region: CodeRegion, + source_region: SourceRegion, ) -> Stmt { let loc = self.codegen_caller_span_stable(span); // Should use Stmt::cover, but currently this doesn't work with CBMC // unless it is run with '--cover cover' (see // https://github.com/diffblue/cbmc/issues/6613). So for now use // `assert(false)`. - let msg = format!("{counter_data} - {code_region:?}"); + let msg = format!("{counter_data} - {source_region:?}"); self.codegen_assert(Expr::bool_false(), PropertyClass::CodeCoverage, &msg, loc) } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 0793e0c4688f..34f8363f4948 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -219,34 +219,34 @@ impl<'tcx> GotocCtx<'tcx> { pub mod rustc_smir { use crate::stable_mir::CrateDef; - use rustc_middle::mir::coverage::CodeRegion; use rustc_middle::mir::coverage::CovTerm; use rustc_middle::mir::coverage::MappingKind::Code; + use rustc_middle::mir::coverage::SourceRegion; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::Opaque; type CoverageOpaque = stable_mir::Opaque; - /// Retrieves the `CodeRegion` associated with the data in a + /// Retrieves the `SourceRegion` associated with the data in a /// `CoverageOpaque` object. pub fn region_from_coverage_opaque( tcx: TyCtxt, coverage_opaque: &CoverageOpaque, instance: Instance, - ) -> Option { + ) -> Option { let cov_term = parse_coverage_opaque(coverage_opaque); region_from_coverage(tcx, cov_term, instance) } - /// Retrieves the `CodeRegion` associated with a `CovTerm` object. + /// Retrieves the `SourceRegion` associated with a `CovTerm` object. /// /// Note: This function could be in the internal `rustc` impl for `Coverage`. pub fn region_from_coverage( tcx: TyCtxt<'_>, coverage: CovTerm, instance: Instance, - ) -> Option { + ) -> Option { // We need to pull the coverage info from the internal MIR instance. let instance_def = rustc_smir::rustc_internal::internal(tcx, instance.def.def_id()); let body = tcx.instance_mir(rustc_middle::ty::InstanceKind::Item(instance_def)); @@ -258,7 +258,7 @@ pub mod rustc_smir { for mapping in &cov_info.mappings { let Code(term) = mapping.kind else { unreachable!() }; if term == coverage { - return Some(mapping.code_region.clone()); + return Some(mapping.source_region.clone()); } } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs index 81407c4dc704..ed0178511126 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs @@ -163,11 +163,11 @@ impl<'tcx> GotocCtx<'tcx> { let function_name = self.current_fn().readable_name(); let instance = self.current_fn().instance_stable(); let counter_data = format!("{coverage_opaque:?} ${function_name}$"); - let maybe_code_region = + let maybe_source_region = region_from_coverage_opaque(self.tcx, &coverage_opaque, instance); - if let Some(code_region) = maybe_code_region { + if let Some(source_region) = maybe_source_region { let coverage_stmt = - self.codegen_coverage(&counter_data, stmt.span, code_region); + self.codegen_coverage(&counter_data, stmt.span, source_region); // TODO: Avoid single-statement blocks when conversion of // standalone statements to the irep format is fixed. // More details in diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 3421b9b3adc9..1494baafeb51 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-28" +channel = "nightly-2024-08-29" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 6ac5183eabd704c04b88f80552bedf24f7c8e4e9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:43:33 -0400 Subject: [PATCH 046/159] Automatic toolchain upgrade to nightly-2024-08-30 (#3469) Update Rust toolchain from nightly-2024-08-29 to nightly-2024-08-30 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 1494baafeb51..503c4611e3a0 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-29" +channel = "nightly-2024-08-30" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 01a00b069d3fb81cf3a94ac0fc8e2a18507e8746 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 30 Aug 2024 15:07:39 -0700 Subject: [PATCH 047/159] Extend name resolution to support qualified paths (Partial Fix) (#3457) This is the first part needed for us to add support to stubbing trait implementations (https://github.com/model-checking/kani/issues/1997) and primitive types (https://github.com/model-checking/kani/issues/2646). After this change, we need to change stubs to accept a `FnResolution` instead of `FnDef`. We also need to find out how to retrieve methods of primitive types. 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: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Co-authored-by: Felipe R. Monteiro --- Cargo.lock | 2 + kani-compiler/Cargo.toml | 6 +- kani-compiler/src/kani_middle/attributes.rs | 163 ++++++---- kani-compiler/src/kani_middle/mod.rs | 20 +- kani-compiler/src/kani_middle/resolve.rs | 300 ++++++++++++++---- kani-compiler/src/main.rs | 1 + .../{expected => invalid_stub_args.expected} | 0 .../{main.rs => invalid_stub_args.rs} | 2 +- .../unsupported_resolutions.expected | 8 + .../unsupported_resolutions.rs | 42 +++ tests/ui/invalid-attribute/expected | 2 +- 11 files changed, 410 insertions(+), 136 deletions(-) rename tests/ui/function-stubbing-error/{expected => invalid_stub_args.expected} (100%) rename tests/ui/function-stubbing-error/{main.rs => invalid_stub_args.rs} (87%) create mode 100644 tests/ui/function-stubbing-error/unsupported_resolutions.expected create mode 100644 tests/ui/function-stubbing-error/unsupported_resolutions.rs diff --git a/Cargo.lock b/Cargo.lock index 0b8f681ae6c1..0da9ff2e2159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -476,12 +476,14 @@ dependencies = [ "kani_metadata", "lazy_static", "num", + "quote", "regex", "serde", "serde_json", "shell-words", "strum", "strum_macros", + "syn 2.0.76", "tracing", "tracing-subscriber", "tracing-tree", diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index fc06c393f3eb..e8e564f9616a 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -13,14 +13,16 @@ cbmc = { path = "../cprover_bindings", package = "cprover_bindings", optional = clap = { version = "4.4.11", features = ["derive", "cargo"] } home = "0.5" itertools = "0.13" -kani_metadata = {path = "../kani_metadata"} +kani_metadata = { path = "../kani_metadata" } lazy_static = "1.4.0" num = { version = "0.4.0", optional = true } +quote = "1.0.36" regex = "1.7.0" serde = { version = "1", optional = true } serde_json = "1" strum = "0.26" strum_macros = "0.26" +syn = { version = "2.0.72", features = ["parsing", "extra-traits"] } shell-words = "1.0.0" tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_debug"]} tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} @@ -34,7 +36,7 @@ write_json_symtab = [] [package.metadata.rust-analyzer] # This package uses rustc crates. -rustc_private=true +rustc_private = true [lints] workspace = true diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index f75c0607e1bc..9a3ff7c1d6a6 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -5,9 +5,9 @@ use std::collections::BTreeMap; use kani_metadata::{CbmcSolver, HarnessAttributes, HarnessKind, Stub}; +use quote::ToTokens; use rustc_ast::{ attr, AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, - NestedMetaItem, }; use rustc_errors::ErrorGuaranteed; use rustc_hir::{def::DefKind, def_id::DefId}; @@ -19,10 +19,13 @@ use stable_mir::mir::mono::Instance as InstanceStable; use stable_mir::{CrateDef, DefId as StableDefId}; use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; +use syn::parse::Parser; +use syn::punctuated::Punctuated; +use syn::{PathSegment, TypePath}; use tracing::{debug, trace}; -use super::resolve::{self, resolve_fn, ResolveError}; +use super::resolve::{resolve_fn, resolve_fn_path, FnResolution, ResolveError}; #[derive(Debug, Clone, Copy, AsRefStr, EnumString, PartialEq, Eq, PartialOrd, Ord)] #[strum(serialize_all = "snake_case")] @@ -555,19 +558,19 @@ impl<'tcx> KaniAttributes<'tcx> { 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, + span, + format!( + "Failed to generate verified stub: Function `{}` has no contract.", + self.item_name(), + ), + ) + .with_span_note( + self.tcx.def_span(def_id), format!( - "Failed to generate verified stub: Function `{}` has no contract.", - self.item_name(), + "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(); return; } @@ -787,20 +790,48 @@ fn parse_unwind(tcx: TyCtxt, attr: &Attribute) -> Option { fn parse_stubs(tcx: TyCtxt, harness: DefId, attributes: &[&Attribute]) -> Vec { let current_module = tcx.parent_module_from_def_id(harness.expect_local()); - let check_resolve = |attr: &Attribute, name: &str| { - let result = resolve::resolve_fn(tcx, current_module.to_local_def_id(), name); - if let Err(err) = result { - tcx.dcx().span_err(attr.span, format!("failed to resolve `{name}`: {err}")); + let check_resolve = |attr: &Attribute, path: &TypePath| { + let result = resolve_fn_path(tcx, current_module.to_local_def_id(), path); + match result { + Ok(FnResolution::Fn(_)) => { /* no-op */ } + Ok(FnResolution::FnImpl { .. }) => { + tcx.dcx().span_err( + attr.span, + "Kani currently does not support stubbing trait implementations.", + ); + } + Err(err) => { + tcx.dcx().span_err( + attr.span, + format!("failed to resolve `{}`: {err}", pretty_type_path(path)), + ); + } } }; attributes .iter() - .filter_map(|attr| match parse_paths(attr) { - Ok(paths) => match paths.as_slice() { + .filter_map(|attr| { + let paths = parse_paths(attr).unwrap_or_else(|_| { + tcx.dcx().span_err( + attr.span, + format!( + "attribute `kani::{}` takes two path arguments; found argument that is not a path", + KaniAttributeKind::Stub.as_ref()) + ); + vec![] + }); + match paths.as_slice() { [orig, replace] => { check_resolve(attr, orig); check_resolve(attr, replace); - Some(Stub { original: orig.clone(), replacement: replace.clone() }) + Some(Stub { + original: orig.to_token_stream().to_string(), + replacement: replace.to_token_stream().to_string(), + }) + } + [] => { + /* Error was already emitted */ + None } _ => { tcx.dcx().span_err( @@ -812,13 +843,6 @@ 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", - ); - None } }) .collect() @@ -896,35 +920,13 @@ fn parse_integer(attr: &Attribute) -> Option { } /// Extracts a vector with the path arguments of an attribute. -/// Emits an error if it couldn't convert any of the arguments. -fn parse_paths(attr: &Attribute) -> Result, Span> { - let attr_args = attr.meta_item_list(); - attr_args - .unwrap_or_default() - .iter() - .map(|arg| match arg { - NestedMetaItem::Lit(item) => Err(item.span), - NestedMetaItem::MetaItem(item) => parse_path(item).ok_or(item.span), - }) - .collect() -} - -/// Extracts a path from an attribute item, returning `None` if the item is not -/// syntactically a path. -fn parse_path(meta_item: &MetaItem) -> Option { - if meta_item.is_word() { - Some( - meta_item - .path - .segments - .iter() - .map(|seg| seg.ident.as_str()) - .collect::>() - .join("::"), - ) - } else { - None - } +/// +/// Emits an error if it couldn't convert any of the arguments and return an empty vector. +fn parse_paths(attr: &Attribute) -> Result, syn::Error> { + let syn_attr = syn_attr(attr); + let parser = Punctuated::::parse_terminated; + let paths = syn_attr.parse_args_with(parser)?; + Ok(paths.into_iter().collect()) } /// Parse the arguments of the attribute into a (key, value) map. @@ -989,3 +991,54 @@ pub fn matches_diagnostic(tcx: TyCtxt, def: T, attr_name: &str) -> } false } + +/// Parse an attribute using `syn`. +/// +/// This provides a user-friendly interface to manipulate than the internal compiler AST. +fn syn_attr(attr: &Attribute) -> syn::Attribute { + let attr_str = rustc_ast_pretty::pprust::attribute_to_string(attr); + let parser = syn::Attribute::parse_outer; + parser.parse_str(&attr_str).unwrap().pop().unwrap() +} + +/// Return a more user-friendly string for path by trying to remove unneeded whitespace. +/// +/// `quote!()` and `TokenString::to_string()` introduce unnecessary space around separators. +/// This happens because these methods end up using TokenStream display, which has no +/// guarantees on the format printed. +/// +/// +/// E.g.: The path `<[char; 10]>::foo` printed with token stream becomes `< [ char ; 10 ] > :: foo`. +/// while this function turns this into `<[char ; 10]>::foo`. +/// +/// Thus, this can still be improved to handle the `qself.ty`. +/// +/// We also don't handle path segments, but users shouldn't pass generic arguments to our +/// attributes. +fn pretty_type_path(path: &TypePath) -> String { + fn segments_str<'a, I>(segments: I) -> String + where + I: IntoIterator, + { + // We don't bother with path arguments for now since users shouldn't provide them. + segments + .into_iter() + .map(|segment| segment.to_token_stream().to_string()) + .intersperse("::".to_string()) + .collect() + } + let leading = if path.path.leading_colon.is_some() { "::" } else { "" }; + if let Some(qself) = &path.qself { + let pos = qself.position; + let qself_str = qself.ty.to_token_stream().to_string(); + if pos == 0 { + format!("<{qself_str}>::{}", segments_str(&path.path.segments)) + } else { + let before = segments_str(path.path.segments.iter().take(pos)); + let after = segments_str(path.path.segments.iter().skip(pos)); + format!("<{qself_str} as {before}>::{after}") + } + } else { + format!("{leading}{}", segments_str(&path.path.segments)) + } +} diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index a5d077d9c16e..f281e5de5e88 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -6,7 +6,7 @@ use std::collections::HashSet; use crate::kani_queries::QueryDb; -use rustc_hir::{def::DefKind, def_id::LOCAL_CRATE}; +use rustc_hir::{def::DefKind, def_id::DefId as InternalDefId, def_id::LOCAL_CRATE}; use rustc_middle::span_bug; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOf, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, @@ -225,10 +225,16 @@ fn find_fn_def(tcx: TyCtxt, diagnostic: &str) -> Option { .all_diagnostic_items(()) .name_to_id .get(&rustc_span::symbol::Symbol::intern(diagnostic))?; - let TyKind::RigidTy(RigidTy::FnDef(def, _)) = - rustc_internal::stable(tcx.type_of(attr_id)).value.kind() - else { - return None; - }; - Some(def) + stable_fn_def(tcx, *attr_id) +} + +/// Try to convert an internal `DefId` to a `FnDef`. +pub fn stable_fn_def(tcx: TyCtxt, def_id: InternalDefId) -> Option { + if let TyKind::RigidTy(RigidTy::FnDef(def, _)) = + rustc_internal::stable(tcx.type_of(def_id)).value.kind() + { + Some(def) + } else { + None + } } diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index ca4e8e749b75..88b1af86efe9 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -9,45 +9,110 @@ //! //! 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 crate::kani_middle::stable_fn_def; +use quote::ToTokens; 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 rustc_smir::rustc_internal; +use stable_mir::ty::{FnDef, RigidTy, Ty, TyKind}; use stable_mir::CrateDef; +use std::collections::HashSet; +use std::fmt; +use std::iter::Peekable; +use std::str::FromStr; +use strum_macros::{EnumString, IntoStaticStr}; +use syn::{Ident, PathSegment, Type, TypePath}; use tracing::debug; +#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString)] +#[strum(serialize_all = "lowercase")] +enum PrimitiveIdent { + Bool, + Char, + F16, + F32, + F64, + F128, + I8, + I16, + I32, + I64, + I128, + Isize, + Str, + U8, + U16, + U32, + U64, + U128, + Usize, +} + +macro_rules! validate_kind { + ($tcx:ident, $id:ident, $expected:literal, $kind:pat) => {{ + let def_kind = $tcx.def_kind($id); + if matches!(def_kind, $kind) { + Ok($id) + } else { + Err(ResolveError::UnexpectedType { $tcx, item: $id, expected: $expected }) + } + }}; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FnResolution { + Fn(FnDef), + FnImpl { def: FnDef, ty: Ty }, +} + +/// Resolve a path to a function / method. +/// +/// The path can either be a simple path or a qualified path. +pub fn resolve_fn_path<'tcx>( + tcx: TyCtxt<'tcx>, + current_module: LocalDefId, + path: &TypePath, +) -> Result> { + match (&path.qself, &path.path.leading_colon) { + (Some(qself), Some(_)) => { + // Qualified path that does not define a trait. + resolve_ty(tcx, current_module, &qself.ty)?; + Err(ResolveError::UnsupportedPath { kind: "qualified bare function paths" }) + } + (Some(qself), None) => { + let ty = resolve_ty(tcx, current_module, &qself.ty)?; + let def_id = resolve_path(tcx, current_module, &path.path)?; + validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; + Ok(FnResolution::FnImpl { def: stable_fn_def(tcx, def_id).unwrap(), ty }) + } + (None, _) => { + // Simple path + let def_id = resolve_path(tcx, current_module, &path.path)?; + validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; + Ok(FnResolution::Fn(stable_fn_def(tcx, def_id).unwrap())) + } + } +} + /// Attempts to resolve a simple path (in the form of a string) to a function / method `DefId`. /// -/// TODO: Extend this implementation to handle qualified paths and simple paths -/// corresponding to trait methods. -/// +/// Use `[resolve_fn_path]` if you want to handle qualified paths and simple paths pub fn resolve_fn<'tcx>( tcx: TyCtxt<'tcx>, current_module: LocalDefId, path_str: &str, ) -> Result> { - let result = resolve_path(tcx, current_module, path_str); - match result { - Ok(def_id) => { - let def_kind = tcx.def_kind(def_id); - if matches!(def_kind, DefKind::AssocFn | DefKind::Fn) { - Ok(def_id) - } else { - Err(ResolveError::UnexpectedType { - tcx, - item: def_id, - expected: "function / method", - }) - } - } - err => err, + let path = syn::parse_str(path_str).map_err(|err| ResolveError::InvalidPath { + msg: format!("Expected a path, but found `{path_str}`. {err}"), + })?; + let result = resolve_fn_path(tcx, current_module, &path)?; + if let FnResolution::Fn(def) = result { + Ok(rustc_internal::internal(tcx, def.def_id())) + } else { + Err(ResolveError::UnsupportedPath { kind: "qualified paths" }) } } @@ -78,25 +143,93 @@ pub fn expect_resolve_fn( } } +/// Attempts to resolve a type. +pub fn resolve_ty<'tcx>( + tcx: TyCtxt<'tcx>, + current_module: LocalDefId, + typ: &syn::Type, +) -> Result> { + debug!(?typ, ?current_module, "resolve_ty"); + let unsupported = |kind: &'static str| Err(ResolveError::UnsupportedPath { kind }); + let invalid = |kind: &'static str| { + Err(ResolveError::InvalidPath { + msg: format!("Expected a type, but found {kind} `{}`", typ.to_token_stream()), + }) + }; + #[warn(non_exhaustive_omitted_patterns)] + match typ { + Type::Path(path) if path.qself.is_none() => { + let def_id = resolve_path(tcx, current_module, &path.path)?; + validate_kind!(tcx, def_id, "type", DefKind::Struct | DefKind::Union | DefKind::Enum)?; + Ok(rustc_internal::stable(tcx.type_of(def_id)).value) + } + Type::Path(_) => unsupported("qualified paths"), + Type::Array(_) + | Type::BareFn(_) + | Type::Macro(_) + | Type::Never(_) + | Type::Paren(_) + | Type::Ptr(_) + | Type::Reference(_) + | Type::Slice(_) + | Type::Tuple(_) => unsupported("path including primitive types"), + Type::Verbatim(_) => unsupported("unknown paths"), + Type::Group(_) => invalid("group paths"), + Type::ImplTrait(_) => invalid("trait impl paths"), + Type::Infer(_) => invalid("inferred paths"), + Type::TraitObject(_) => invalid("trait object paths"), + _ => { + unreachable!() + } + } +} + +/// Checks if a Path segment represents a primitive +fn is_primitive(ident: &Ident) -> bool { + let token = ident.to_string(); + let Ok(typ) = syn::parse_str(&token) else { return false }; + #[warn(non_exhaustive_omitted_patterns)] + match typ { + Type::Array(_) + | Type::Ptr(_) + | Type::Reference(_) + | Type::Slice(_) + | Type::Never(_) + | Type::Tuple(_) => true, + Type::Path(_) => PrimitiveIdent::from_str(&token).is_ok(), + Type::BareFn(_) + | Type::Group(_) + | Type::ImplTrait(_) + | Type::Infer(_) + | Type::Macro(_) + | Type::Paren(_) + | Type::TraitObject(_) + | Type::Verbatim(_) => false, + _ => { + unreachable!() + } + } +} + /// 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. -/// -/// Note: This function was written to be generic, however, it has only been tested for functions. -pub(crate) fn resolve_path<'tcx>( +fn resolve_path<'tcx>( tcx: TyCtxt<'tcx>, current_module: LocalDefId, - path_str: &str, + path: &syn::Path, ) -> Result> { + debug!(?path, "resolve_path"); let _span = tracing::span!(tracing::Level::DEBUG, "path_resolution").entered(); - let path = resolve_prefix(tcx, current_module, path_str)?; - path.segments.into_iter().try_fold(path.base, |base, name| { - debug!(?base, ?name, "resolve_path"); + let path = resolve_prefix(tcx, current_module, path)?; + path.segments.into_iter().try_fold(path.base, |base, segment| { + let name = segment.ident.to_string(); let def_kind = tcx.def_kind(base); let next_item = match def_kind { DefKind::ForeignMod | DefKind::Mod => resolve_in_module(tcx, base, &name), DefKind::Struct | DefKind::Enum | DefKind::Union => resolve_in_type(tcx, base, &name), + DefKind::Trait => resolve_in_trait(tcx, base, &name), kind => { debug!(?base, ?kind, "resolve_path: unexpected item"); Err(ResolveError::UnexpectedType { tcx, item: base, expected: "module" }) @@ -119,6 +252,8 @@ pub enum ResolveError<'tcx> { MissingItem { tcx: TyCtxt<'tcx>, base: DefId, unresolved: String }, /// Error triggered when the identifier points to an item with unexpected type. UnexpectedType { tcx: TyCtxt<'tcx>, item: DefId, expected: &'static str }, + /// Error triggered when the identifier is not currently supported. + UnsupportedPath { kind: &'static str }, } impl<'tcx> fmt::Debug for ResolveError<'tcx> { @@ -156,12 +291,15 @@ impl<'tcx> fmt::Display for ResolveError<'tcx> { let def_desc = description(*tcx, *base); write!(f, "unable to find `{unresolved}` inside {def_desc}") } + ResolveError::UnsupportedPath { kind } => { + write!(f, "Kani currently cannot resolve {kind}") + } } } } /// The segments of a path. -type Segments = Vec; +type Segments = Vec; /// A path consisting of a starting point and a bunch of segments. If `base` /// matches `Base::LocalModule { id: _, may_be_external_path : true }`, then @@ -174,8 +312,6 @@ struct Path { /// Identifier for the top module of the crate. const CRATE: &str = "crate"; -/// rustc represents initial `::` as `{{root}}`. -const ROOT: &str = "{{root}}"; /// Identifier for the current module. const SELF: &str = "self"; /// Identifier for the parent of the current module. @@ -186,56 +322,56 @@ const SUPER: &str = "super"; fn resolve_prefix<'tcx>( tcx: TyCtxt<'tcx>, current_module: LocalDefId, - name: &str, + path: &syn::Path, ) -> Result> { - debug!(?name, ?current_module, "resolve_prefix"); + debug!(?path, ?current_module, "resolve_prefix"); // Split the string into segments separated by `::`. Trim the whitespace // since path strings generated from macros sometimes add spaces around // `::`. - let mut segments = name.split("::").map(|s| s.trim().to_string()).peekable(); - assert!(segments.peek().is_some(), "expected identifier, found `{name}`"); + let mut segments = path.segments.iter(); // Resolve qualifiers `crate`, initial `::`, and `self`. The qualifier // `self` may be followed be `super` (handled below). - let first = segments.peek().unwrap().as_str(); - match first { - ROOT => { + match (path.leading_colon, segments.next()) { + (Some(_), Some(segment)) => { // Skip root and get the external crate from the name that follows `::`. - let next = segments.nth(1); - if let Some(next_name) = next { - let result = resolve_external(tcx, &next_name); - if let Some(def_id) = result { - Ok(Path { base: def_id, segments: segments.collect() }) - } else { - Err(ResolveError::MissingItem { - tcx, - base: current_module.to_def_id(), - unresolved: next_name, - }) - } + let next_name = segment.ident.to_string(); + let result = resolve_external(tcx, &next_name); + if let Some(def_id) = result { + Ok(Path { base: def_id, segments: segments.cloned().collect() }) } else { - Err(ResolveError::InvalidPath { msg: "expected identifier after `::`".to_string() }) + Err(ResolveError::MissingItem { + tcx, + base: current_module.to_def_id(), + unresolved: next_name, + }) } } - CRATE => { - segments.next(); + (Some(_), None) => { + Err(ResolveError::InvalidPath { msg: "expected identifier after `::`".to_string() }) + } + (None, Some(segment)) if segment.ident == CRATE => { // Find the module at the root of the crate. let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); let crate_root = match tcx.hir().parent_iter(current_module_hir_id).last() { None => current_module, Some((hir_id, _)) => hir_id.owner.def_id, }; - Ok(Path { base: crate_root.to_def_id(), segments: segments.collect() }) + Ok(Path { base: crate_root.to_def_id(), segments: segments.cloned().collect() }) } - SELF => { - segments.next(); - resolve_super(tcx, current_module, segments) + (None, Some(segment)) if segment.ident == SELF => { + resolve_super(tcx, current_module, segments.peekable()) } - SUPER => resolve_super(tcx, current_module, segments), - _ => { + (None, Some(segment)) if segment.ident == SUPER => { + resolve_super(tcx, current_module, path.segments.iter().peekable()) + } + (None, Some(segment)) if is_primitive(&segment.ident) => { + Err(ResolveError::UnsupportedPath { kind: "path including primitive types" }) + } + (None, Some(segment)) => { // No special key word was used. Try local first otherwise try external name. - let next_name = segments.next().unwrap(); + let next_name = segment.ident.to_string(); let def_id = resolve_in_module(tcx, current_module.to_def_id(), &next_name).or_else(|err| { if matches!(err, ResolveError::MissingItem { .. }) { @@ -245,25 +381,28 @@ fn resolve_prefix<'tcx>( Err(err) } })?; - Ok(Path { base: def_id, segments: segments.collect() }) + Ok(Path { base: def_id, segments: segments.cloned().collect() }) + } + _ => { + unreachable!("Empty path: `{path:?}`") } } } /// Pop up the module stack until we account for all the `super` prefixes. /// This method will error out if it tries to backtrace from the root crate. -fn resolve_super<'tcx, I>( +fn resolve_super<'tcx, 'a, I>( tcx: TyCtxt, current_module: LocalDefId, mut segments: Peekable, ) -> Result> where - I: Iterator, + I: Iterator, { let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); let mut parents = tcx.hir().parent_iter(current_module_hir_id); let mut base_module = current_module; - while segments.next_if(|segment| segment == SUPER).is_some() { + while segments.next_if(|segment| segment.ident == SUPER).is_some() { if let Some((parent, _)) = parents.next() { debug!("parent: {parent:?}"); base_module = parent.owner.def_id; @@ -272,7 +411,7 @@ where } } debug!("base: {base_module:?}"); - Ok(Path { base: base_module.to_def_id(), segments: segments.collect() }) + Ok(Path { base: base_module.to_def_id(), segments: segments.cloned().collect() }) } /// Resolves an external crate name. @@ -422,8 +561,7 @@ fn resolve_in_glob_use(tcx: TyCtxt, res: &Res, name: &str) -> RelativeResolution } } -/// Resolves a method in a type. It currently does not resolve trait methods -/// (see ). +/// Resolves a function in a type. fn resolve_in_type<'tcx>( tcx: TyCtxt<'tcx>, type_id: DefId, @@ -445,3 +583,25 @@ fn resolve_in_type<'tcx>( }) .ok_or_else(missing_item_err) } + +/// Resolves a function in a trait. +fn resolve_in_trait<'tcx>( + tcx: TyCtxt<'tcx>, + trait_id: DefId, + name: &str, +) -> Result> { + debug!(?name, ?trait_id, "resolve_in_trait"); + let missing_item_err = + || ResolveError::MissingItem { tcx, base: trait_id, unresolved: name.to_string() }; + let trait_def = tcx.trait_def(trait_id); + // Try the inherent `impl` blocks (i.e., non-trait `impl`s). + tcx.associated_item_def_ids(trait_def.def_id) + .iter() + .copied() + .find(|item| { + let item_path = tcx.def_path_str(*item); + let last = item_path.split("::").last().unwrap(); + last == name + }) + .ok_or_else(missing_item_err) +} diff --git a/kani-compiler/src/main.rs b/kani-compiler/src/main.rs index e47483fb4fa5..3a7816c1b084 100644 --- a/kani-compiler/src/main.rs +++ b/kani-compiler/src/main.rs @@ -15,6 +15,7 @@ #![feature(let_chains)] #![feature(f128)] #![feature(f16)] +#![feature(non_exhaustive_omitted_patterns_lint)] extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_ast_pretty; diff --git a/tests/ui/function-stubbing-error/expected b/tests/ui/function-stubbing-error/invalid_stub_args.expected similarity index 100% rename from tests/ui/function-stubbing-error/expected rename to tests/ui/function-stubbing-error/invalid_stub_args.expected diff --git a/tests/ui/function-stubbing-error/main.rs b/tests/ui/function-stubbing-error/invalid_stub_args.rs similarity index 87% rename from tests/ui/function-stubbing-error/main.rs rename to tests/ui/function-stubbing-error/invalid_stub_args.rs index c433e352e740..baed360b2dda 100644 --- a/tests/ui/function-stubbing-error/main.rs +++ b/tests/ui/function-stubbing-error/invalid_stub_args.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --harness main -Z stubbing +// kani-flags: -Z stubbing // //! This tests whether we detect syntactically misformed `kani::stub` annotations. diff --git a/tests/ui/function-stubbing-error/unsupported_resolutions.expected b/tests/ui/function-stubbing-error/unsupported_resolutions.expected new file mode 100644 index 000000000000..1b4c00d22f4f --- /dev/null +++ b/tests/ui/function-stubbing-error/unsupported_resolutions.expected @@ -0,0 +1,8 @@ +error: failed to resolve `<[char ; 10]>::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `<& [u32]>::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `<& [u32] as Foo>::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `<(i32 , i32) as Foo>::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `u8::foo`: Kani currently cannot resolve path including primitive types +error: failed to resolve `::bar`: unable to find `bar` inside trait `Foo` +error: Kani currently does not support stubbing trait implementations +error: failed to resolve `::foo`: Kani currently cannot resolve qualified bare function paths diff --git a/tests/ui/function-stubbing-error/unsupported_resolutions.rs b/tests/ui/function-stubbing-error/unsupported_resolutions.rs new file mode 100644 index 000000000000..2fc74a0fd44f --- /dev/null +++ b/tests/ui/function-stubbing-error/unsupported_resolutions.rs @@ -0,0 +1,42 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we emit a nice error message for unsupported paths. + +/// Dummy structure +pub struct Bar; + +/// Dummy trait +pub trait Foo { + fn foo() -> bool { + false + } +} + +impl Foo for Bar {} + +impl Foo for u8 {} + +impl Foo for &[T] {} + +impl Foo for [char; 10] {} + +impl Foo for (i32, i32) {} + +/// Dummy stub +pub fn stub_foo() -> bool { + true +} + +#[kani::proof] +#[kani::stub(::foo, stub_foo)] +#[kani::stub(::foo, stub_foo)] +#[kani::stub(::bar, stub_foo)] +#[kani::stub(u8::foo, stub_foo)] +#[kani::stub(<(i32, i32) as Foo>::foo, stub_foo)] +#[kani::stub(<&[u32] as Foo>::foo, stub_foo)] +#[kani::stub(<&[u32]>::foo, stub_foo)] +#[kani::stub(<[char; 10]>::foo, stub_foo)] +fn unsupported_args() {} diff --git a/tests/ui/invalid-attribute/expected b/tests/ui/invalid-attribute/expected index 75be29c13e83..a5623dba5b6e 100644 --- a/tests/ui/invalid-attribute/expected +++ b/tests/ui/invalid-attribute/expected @@ -5,7 +5,7 @@ attrs.rs | ^^^^^^^^^^^^^^^^^^^^^^^^^^\ | -error: attribute `kani::stub` takes two path arguments; found 0\ +error: attribute `kani::stub` takes two path arguments; found argument that is not a path\ attrs.rs |\ | #[kani::stub(invalid=opt)]\ From 2960f80be858d51c86dd3f641624d55ddfdf65d2 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Fri, 30 Aug 2024 21:08:06 -0400 Subject: [PATCH 048/159] Partially integrate uninit memory checks into `verify_std` (#3470) This PR partially integrates uninitialized memory checks into the `verify_std` pipeline, which makes it possible to enable them for the Rust Standard Library verification. Changes: - Move `mem_init.rs` library code into `kani_core`. - Add a conditional compilation flag to disable uninitialized memory checks whenever some functionality is not yet supported. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- library/kani/src/lib.rs | 1 - library/kani/src/mem_init.rs | 333 ----------------- library/kani_core/src/lib.rs | 9 + library/kani_core/src/mem_init.rs | 347 ++++++++++++++++++ .../verify_std_cmd/verify_core.rs | 4 + .../verify_std_cmd/verify_std.expected | 13 + .../verify_std_cmd/verify_std.sh | 4 + 7 files changed, 377 insertions(+), 334 deletions(-) delete mode 100644 library/kani/src/mem_init.rs create mode 100644 library/kani_core/src/mem_init.rs diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 59a89622a52d..c3e9ae5497cc 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -32,7 +32,6 @@ pub mod shadow; pub mod slice; pub mod vec; -mod mem_init; mod models; #[cfg(feature = "concrete_playback")] diff --git a/library/kani/src/mem_init.rs b/library/kani/src/mem_init.rs deleted file mode 100644 index a6118c472a92..000000000000 --- a/library/kani/src/mem_init.rs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! This module provides instrumentation for tracking memory initialization of raw pointers. -//! -//! Currently, memory initialization is tracked on per-byte basis, so each byte of memory pointed to -//! by raw pointers could be either initialized or uninitialized. Padding bytes are always -//! considered uninitialized when read as data bytes. Each type has a type layout to specify which -//! bytes are considered to be data and which -- padding. This is determined at compile time and -//! statically injected into the program (see `Layout`). -//! -//! Compiler automatically inserts calls to `is_xxx_initialized` and `set_xxx_initialized` at -//! appropriate locations to get or set the initialization status of the memory pointed to. -//! -//! Note that for each harness, tracked object and tracked offset are chosen non-deterministically, -//! so calls to `is_xxx_initialized` should be only used in assertion contexts. - -// Definitions in this module are not meant to be visible to the end user, only the compiler. -#![allow(dead_code)] - -/// Bytewise mask, representing which bytes of a type are data and which are padding. -/// For example, for a type like this: -/// ``` -/// #[repr(C)] -/// struct Foo { -/// a: u16, -/// b: u8, -/// } -/// ``` -/// the layout would be [true, true, true, false]; -type Layout = [bool; LAYOUT_SIZE]; - -/// Currently tracked non-deterministically chosen memory initialization state. -struct MemoryInitializationState { - pub tracked_object_id: usize, - pub tracked_offset: usize, - pub value: bool, -} - -impl MemoryInitializationState { - /// This is a dummy initialization function -- the values will be eventually overwritten by a - /// call to `initialize_memory_initialization_state`. - pub const fn new() -> Self { - Self { tracked_object_id: 0, tracked_offset: 0, value: false } - } - - /// Return currently tracked memory initialization state if `ptr` points to the currently - /// tracked object and the tracked offset lies within `LAYOUT_SIZE` bytes of `ptr`. Return - /// `true` otherwise. - /// - /// Such definition is necessary since both tracked object and tracked offset are chosen - /// non-deterministically. - #[kanitool::disable_checks(pointer)] - pub fn get( - &mut self, - ptr: *const u8, - layout: Layout, - ) -> bool { - let obj = crate::mem::pointer_object(ptr); - let offset = crate::mem::pointer_offset(ptr); - if self.tracked_object_id == obj - && self.tracked_offset >= offset - && self.tracked_offset < offset + LAYOUT_SIZE - { - !layout[self.tracked_offset - offset] || self.value - } else { - true - } - } - - /// Set currently tracked memory initialization state if `ptr` points to the currently tracked - /// object and the tracked offset lies within `LAYOUT_SIZE` bytes of `ptr`. Do nothing - /// otherwise. - /// - /// Such definition is necessary since both tracked object and tracked offset are chosen - /// non-deterministically. - #[kanitool::disable_checks(pointer)] - pub fn set( - &mut self, - ptr: *const u8, - layout: Layout, - value: bool, - ) { - let obj = crate::mem::pointer_object(ptr); - let offset = crate::mem::pointer_offset(ptr); - if self.tracked_object_id == obj - && self.tracked_offset >= offset - && self.tracked_offset < offset + LAYOUT_SIZE - { - self.value = layout[self.tracked_offset - offset] && value; - } - } - - /// Copy memory initialization state by non-deterministically switching the tracked object and - /// adjusting the tracked offset. - pub fn copy( - &mut self, - from_ptr: *const u8, - to_ptr: *const u8, - num_elts: usize, - ) { - let from_obj = crate::mem::pointer_object(from_ptr); - let from_offset = crate::mem::pointer_offset(from_ptr); - - let to_obj = crate::mem::pointer_object(to_ptr); - let to_offset = crate::mem::pointer_offset(to_ptr); - - if self.tracked_object_id == from_obj - && self.tracked_offset >= from_offset - && self.tracked_offset < from_offset + num_elts * LAYOUT_SIZE - { - let should_reset: bool = crate::any(); - if should_reset { - self.tracked_object_id = to_obj; - self.tracked_offset += to_offset - from_offset; - // Note that this preserves the value. - } - } - } - - /// Return currently tracked memory initialization state if `ptr` points to the currently - /// tracked object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. - /// Return `true` otherwise. - /// - /// Such definition is necessary since both tracked object and tracked offset are chosen - /// non-deterministically. - #[kanitool::disable_checks(pointer)] - pub fn get_slice( - &mut self, - ptr: *const u8, - layout: Layout, - num_elts: usize, - ) -> bool { - let obj = crate::mem::pointer_object(ptr); - let offset = crate::mem::pointer_offset(ptr); - if self.tracked_object_id == obj - && self.tracked_offset >= offset - && self.tracked_offset < offset + num_elts * LAYOUT_SIZE - { - !layout[(self.tracked_offset - offset) % LAYOUT_SIZE] || self.value - } else { - true - } - } - - /// Set currently tracked memory initialization state if `ptr` points to the currently tracked - /// object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. Do - /// nothing otherwise. - /// - /// Such definition is necessary since both tracked object and tracked offset are chosen - /// non-deterministically. - #[kanitool::disable_checks(pointer)] - pub fn set_slice( - &mut self, - ptr: *const u8, - layout: Layout, - num_elts: usize, - value: bool, - ) { - let obj = crate::mem::pointer_object(ptr); - let offset = crate::mem::pointer_offset(ptr); - if self.tracked_object_id == obj - && self.tracked_offset >= offset - && self.tracked_offset < offset + num_elts * LAYOUT_SIZE - { - self.value = layout[(self.tracked_offset - offset) % LAYOUT_SIZE] && value; - } - } -} - -/// Global object for tracking memory initialization state. -#[rustc_diagnostic_item = "KaniMemoryInitializationState"] -static mut MEM_INIT_STATE: MemoryInitializationState = MemoryInitializationState::new(); - -/// Set tracked object and tracked offset to a non-deterministic value. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniInitializeMemoryInitializationState"] -fn initialize_memory_initialization_state() { - unsafe { - MEM_INIT_STATE.tracked_object_id = crate::any(); - MEM_INIT_STATE.tracked_offset = crate::any(); - MEM_INIT_STATE.value = false; - } -} - -/// Get initialization state of `num_elts` items laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniIsPtrInitialized"] -fn is_ptr_initialized( - ptr: *const T, - layout: Layout, -) -> bool { - if LAYOUT_SIZE == 0 { - return true; - } - let (ptr, _) = ptr.to_raw_parts(); - unsafe { MEM_INIT_STATE.get(ptr as *const u8, layout) } -} - -/// Set initialization state to `value` for `num_elts` items laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniSetPtrInitialized"] -fn set_ptr_initialized( - ptr: *const T, - layout: Layout, - value: bool, -) { - if LAYOUT_SIZE == 0 { - return; - } - let (ptr, _) = ptr.to_raw_parts(); - unsafe { - MEM_INIT_STATE.set(ptr as *const u8, layout, value); - } -} - -/// Get initialization state of `num_elts` items laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniIsSliceChunkPtrInitialized"] -fn is_slice_chunk_ptr_initialized( - ptr: *const T, - layout: Layout, - num_elts: usize, -) -> bool { - if LAYOUT_SIZE == 0 { - return true; - } - let (ptr, _) = ptr.to_raw_parts(); - unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } -} - -/// Set initialization state to `value` for `num_elts` items laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniSetSliceChunkPtrInitialized"] -fn set_slice_chunk_ptr_initialized( - ptr: *const T, - layout: Layout, - num_elts: usize, - value: bool, -) { - if LAYOUT_SIZE == 0 { - return; - } - let (ptr, _) = ptr.to_raw_parts(); - unsafe { - MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); - } -} - -/// Get initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniIsSlicePtrInitialized"] -fn is_slice_ptr_initialized( - ptr: *const [T], - layout: Layout, -) -> bool { - if LAYOUT_SIZE == 0 { - return true; - } - let (ptr, num_elts) = ptr.to_raw_parts(); - unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } -} - -/// Set initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniSetSlicePtrInitialized"] -fn set_slice_ptr_initialized( - ptr: *const [T], - layout: Layout, - value: bool, -) { - if LAYOUT_SIZE == 0 { - return; - } - let (ptr, num_elts) = ptr.to_raw_parts(); - unsafe { - MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); - } -} - -/// Get initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniIsStrPtrInitialized"] -fn is_str_ptr_initialized( - ptr: *const str, - layout: Layout, -) -> bool { - if LAYOUT_SIZE == 0 { - return true; - } - let (ptr, num_elts) = ptr.to_raw_parts(); - unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } -} - -/// Set initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniSetStrPtrInitialized"] -fn set_str_ptr_initialized( - ptr: *const str, - layout: Layout, - value: bool, -) { - if LAYOUT_SIZE == 0 { - return; - } - let (ptr, num_elts) = ptr.to_raw_parts(); - unsafe { - MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); - } -} - -/// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. Note -/// that in this case `LAYOUT_SIZE == size_of::`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniCopyInitState"] -fn copy_init_state(from: *const T, to: *const T, num_elts: usize) { - if LAYOUT_SIZE == 0 { - return; - } - let (from_ptr, _) = from.to_raw_parts(); - let (to_ptr, _) = to.to_raw_parts(); - unsafe { - MEM_INIT_STATE.copy::(from_ptr as *const u8, to_ptr as *const u8, num_elts); - } -} - -/// Copy initialization state of `size_of::` bytes from one pointer to the other. Note that in -/// this case `LAYOUT_SIZE == size_of::`. -#[kanitool::disable_checks(pointer)] -#[rustc_diagnostic_item = "KaniCopyInitStateSingle"] -fn copy_init_state_single(from: *const T, to: *const T) { - copy_init_state::(from, to, 1); -} diff --git a/library/kani_core/src/lib.rs b/library/kani_core/src/lib.rs index 6cbe98d30df2..5df8f2228c62 100644 --- a/library/kani_core/src/lib.rs +++ b/library/kani_core/src/lib.rs @@ -22,6 +22,7 @@ mod arbitrary; mod mem; +mod mem_init; pub use kani_macros::*; @@ -45,6 +46,10 @@ macro_rules! kani_lib { pub mod mem { kani_core::kani_mem!(core); } + + mod mem_init { + kani_core::kani_mem_init!(core); + } } }; @@ -56,6 +61,10 @@ macro_rules! kani_lib { pub mod mem { kani_core::kani_mem!(std); } + + mod mem_init { + kani_core::kani_mem_init!(std); + } }; } diff --git a/library/kani_core/src/mem_init.rs b/library/kani_core/src/mem_init.rs new file mode 100644 index 000000000000..cec23c14263b --- /dev/null +++ b/library/kani_core/src/mem_init.rs @@ -0,0 +1,347 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This module provides instrumentation for tracking memory initialization of raw pointers. +//! +//! Currently, memory initialization is tracked on per-byte basis, so each byte of memory pointed to +//! by raw pointers could be either initialized or uninitialized. Padding bytes are always +//! considered uninitialized when read as data bytes. Each type has a type layout to specify which +//! bytes are considered to be data and which -- padding. This is determined at compile time and +//! statically injected into the program (see `Layout`). +//! +//! Compiler automatically inserts calls to `is_xxx_initialized` and `set_xxx_initialized` at +//! appropriate locations to get or set the initialization status of the memory pointed to. +//! +//! Note that for each harness, tracked object and tracked offset are chosen non-deterministically, +//! so calls to `is_xxx_initialized` should be only used in assertion contexts. + +// Definitions in this module are not meant to be visible to the end user, only the compiler. +#![allow(dead_code)] + +#[macro_export] +#[allow(clippy::crate_in_macro_def)] +macro_rules! kani_mem_init { + ($core:path) => { + /// Bytewise mask, representing which bytes of a type are data and which are padding. + /// For example, for a type like this: + /// ``` + /// #[repr(C)] + /// struct Foo { + /// a: u16, + /// b: u8, + /// } + /// ``` + /// the layout would be [true, true, true, false]; + type Layout = [bool; LAYOUT_SIZE]; + + /// Currently tracked non-deterministically chosen memory initialization state. + struct MemoryInitializationState { + pub tracked_object_id: usize, + pub tracked_offset: usize, + pub value: bool, + } + + impl MemoryInitializationState { + /// This is a dummy initialization function -- the values will be eventually overwritten by a + /// call to `initialize_memory_initialization_state`. + pub const fn new() -> Self { + Self { tracked_object_id: 0, tracked_offset: 0, value: false } + } + + /// Return currently tracked memory initialization state if `ptr` points to the currently + /// tracked object and the tracked offset lies within `LAYOUT_SIZE` bytes of `ptr`. Return + /// `true` otherwise. + /// + /// Such definition is necessary since both tracked object and tracked offset are chosen + /// non-deterministically. + #[kanitool::disable_checks(pointer)] + pub fn get( + &mut self, + ptr: *const u8, + layout: Layout, + ) -> bool { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + LAYOUT_SIZE + { + !layout[self.tracked_offset - offset] || self.value + } else { + true + } + } + + /// Set currently tracked memory initialization state if `ptr` points to the currently tracked + /// object and the tracked offset lies within `LAYOUT_SIZE` bytes of `ptr`. Do nothing + /// otherwise. + /// + /// Such definition is necessary since both tracked object and tracked offset are chosen + /// non-deterministically. + #[kanitool::disable_checks(pointer)] + pub fn set( + &mut self, + ptr: *const u8, + layout: Layout, + value: bool, + ) { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + LAYOUT_SIZE + { + self.value = layout[self.tracked_offset - offset] && value; + } + } + + /// Copy memory initialization state by non-deterministically switching the tracked object and + /// adjusting the tracked offset. + pub fn copy( + &mut self, + from_ptr: *const u8, + to_ptr: *const u8, + num_elts: usize, + ) { + let from_obj = super::mem::pointer_object(from_ptr); + let from_offset = super::mem::pointer_offset(from_ptr); + + let to_obj = super::mem::pointer_object(to_ptr); + let to_offset = super::mem::pointer_offset(to_ptr); + + if self.tracked_object_id == from_obj + && self.tracked_offset >= from_offset + && self.tracked_offset < from_offset + num_elts * LAYOUT_SIZE + { + let should_reset: bool = super::any(); + if should_reset { + self.tracked_object_id = to_obj; + self.tracked_offset += to_offset - from_offset; + // Note that this preserves the value. + } + } + } + + /// Return currently tracked memory initialization state if `ptr` points to the currently + /// tracked object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. + /// Return `true` otherwise. + /// + /// Such definition is necessary since both tracked object and tracked offset are chosen + /// non-deterministically. + #[kanitool::disable_checks(pointer)] + pub fn get_slice( + &mut self, + ptr: *const u8, + layout: Layout, + num_elts: usize, + ) -> bool { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + num_elts * LAYOUT_SIZE + { + !layout[(self.tracked_offset - offset) % LAYOUT_SIZE] || self.value + } else { + true + } + } + + /// Set currently tracked memory initialization state if `ptr` points to the currently tracked + /// object and the tracked offset lies within `LAYOUT_SIZE * num_elts` bytes of `ptr`. Do + /// nothing otherwise. + /// + /// Such definition is necessary since both tracked object and tracked offset are chosen + /// non-deterministically. + #[kanitool::disable_checks(pointer)] + pub fn set_slice( + &mut self, + ptr: *const u8, + layout: Layout, + num_elts: usize, + value: bool, + ) { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + num_elts * LAYOUT_SIZE + { + self.value = layout[(self.tracked_offset - offset) % LAYOUT_SIZE] && value; + } + } + } + + /// Global object for tracking memory initialization state. + #[rustc_diagnostic_item = "KaniMemoryInitializationState"] + static mut MEM_INIT_STATE: MemoryInitializationState = MemoryInitializationState::new(); + + /// Set tracked object and tracked offset to a non-deterministic value. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniInitializeMemoryInitializationState"] + fn initialize_memory_initialization_state() { + unsafe { + MEM_INIT_STATE.tracked_object_id = super::any(); + MEM_INIT_STATE.tracked_offset = super::any(); + MEM_INIT_STATE.value = false; + } + } + + /// Get initialization state of `num_elts` items laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniIsPtrInitialized"] + fn is_ptr_initialized( + ptr: *const T, + layout: Layout, + ) -> bool { + if LAYOUT_SIZE == 0 { + return true; + } + let (ptr, _) = ptr.to_raw_parts(); + unsafe { MEM_INIT_STATE.get(ptr as *const u8, layout) } + } + + /// Set initialization state to `value` for `num_elts` items laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniSetPtrInitialized"] + fn set_ptr_initialized( + ptr: *const T, + layout: Layout, + value: bool, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (ptr, _) = ptr.to_raw_parts(); + unsafe { + MEM_INIT_STATE.set(ptr as *const u8, layout, value); + } + } + + /// Get initialization state of `num_elts` items laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniIsSliceChunkPtrInitialized"] + fn is_slice_chunk_ptr_initialized( + ptr: *const T, + layout: Layout, + num_elts: usize, + ) -> bool { + if LAYOUT_SIZE == 0 { + return true; + } + let (ptr, _) = ptr.to_raw_parts(); + unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } + } + + /// Set initialization state to `value` for `num_elts` items laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniSetSliceChunkPtrInitialized"] + fn set_slice_chunk_ptr_initialized( + ptr: *const T, + layout: Layout, + num_elts: usize, + value: bool, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (ptr, _) = ptr.to_raw_parts(); + unsafe { + MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); + } + } + + /// Get initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniIsSlicePtrInitialized"] + fn is_slice_ptr_initialized( + ptr: *const [T], + layout: Layout, + ) -> bool { + if LAYOUT_SIZE == 0 { + return true; + } + let (ptr, num_elts) = ptr.to_raw_parts(); + unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } + } + + /// Set initialization state of the slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniSetSlicePtrInitialized"] + fn set_slice_ptr_initialized( + ptr: *const [T], + layout: Layout, + value: bool, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (ptr, num_elts) = ptr.to_raw_parts(); + unsafe { + MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); + } + } + + /// Get initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniIsStrPtrInitialized"] + fn is_str_ptr_initialized( + ptr: *const str, + layout: Layout, + ) -> bool { + if LAYOUT_SIZE == 0 { + return true; + } + let (ptr, num_elts) = ptr.to_raw_parts(); + unsafe { MEM_INIT_STATE.get_slice(ptr as *const u8, layout, num_elts) } + } + + /// Set initialization state of the string slice, items of which are laid out according to the `layout` starting at address `ptr` to `value`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniSetStrPtrInitialized"] + fn set_str_ptr_initialized( + ptr: *const str, + layout: Layout, + value: bool, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (ptr, num_elts) = ptr.to_raw_parts(); + unsafe { + MEM_INIT_STATE.set_slice(ptr as *const u8, layout, num_elts, value); + } + } + + /// Copy initialization state of `size_of:: * num_elts` bytes from one pointer to the other. Note + /// that in this case `LAYOUT_SIZE == size_of::`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniCopyInitState"] + fn copy_init_state( + from: *const T, + to: *const T, + num_elts: usize, + ) { + if LAYOUT_SIZE == 0 { + return; + } + let (from_ptr, _) = from.to_raw_parts(); + let (to_ptr, _) = to.to_raw_parts(); + unsafe { + MEM_INIT_STATE.copy::( + from_ptr as *const u8, + to_ptr as *const u8, + num_elts, + ); + } + } + + /// Copy initialization state of `size_of::` bytes from one pointer to the other. Note that in + /// this case `LAYOUT_SIZE == size_of::`. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniCopyInitStateSingle"] + fn copy_init_state_single(from: *const T, to: *const T) { + copy_init_state::(from, to, 1); + } + }; +} diff --git a/tests/script-based-pre/verify_std_cmd/verify_core.rs b/tests/script-based-pre/verify_std_cmd/verify_core.rs index 28cf113a9210..9bdabb32dde3 100644 --- a/tests/script-based-pre/verify_std_cmd/verify_core.rs +++ b/tests/script-based-pre/verify_std_cmd/verify_core.rs @@ -29,6 +29,7 @@ pub mod verify { } #[kani::proof_for_contract(dummy_read)] + #[cfg(not(uninit_checks))] fn check_dummy_read() { let val: char = kani::any(); assert_eq!(unsafe { dummy_read(&val) }, val); @@ -37,16 +38,19 @@ pub mod verify { /// Ensure we can verify constant functions. #[kani::requires(kani::mem::can_dereference(ptr))] #[rustc_diagnostic_item = "dummy_read"] + #[cfg(not(uninit_checks))] const unsafe fn dummy_read(ptr: *const T) -> T { *ptr } + #[cfg(not(uninit_checks))] #[kani::proof_for_contract(swap_tuple)] fn check_swap_tuple() { let initial: (char, NonZeroU8) = kani::any(); let _swapped = swap_tuple(initial); } + #[cfg(not(uninit_checks))] #[kani::ensures(| result | result.0 == second && result.1 == first)] fn swap_tuple((first, second): (char, NonZeroU8)) -> (NonZeroU8, char) { (second, first) diff --git a/tests/script-based-pre/verify_std_cmd/verify_std.expected b/tests/script-based-pre/verify_std_cmd/verify_std.expected index 22e8fb2e6375..16705ffcfefa 100644 --- a/tests/script-based-pre/verify_std_cmd/verify_std.expected +++ b/tests/script-based-pre/verify_std_cmd/verify_std.expected @@ -19,3 +19,16 @@ Checking harness num::verify::check_non_zero... VERIFICATION:- SUCCESSFUL Complete - 6 successfully verified harnesses, 0 failures, 6 total. + +[TEST] Run kani verify-std -Z uninit-checks + +Checking harness verify::check_add_one... +VERIFICATION:- SUCCESSFUL + +Checking harness verify::dummy_proof... +VERIFICATION:- SUCCESSFUL + +Checking harness verify::harness... +VERIFICATION:- SUCCESSFUL + +Complete - 3 successfully verified harnesses, 0 failures, 3 total. 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 6a95c667b71b..f06e8a8f9921 100755 --- a/tests/script-based-pre/verify_std_cmd/verify_std.sh +++ b/tests/script-based-pre/verify_std_cmd/verify_std.sh @@ -25,6 +25,7 @@ CORE_CODE=$(cat verify_core.rs) STD_CODE=' #[cfg(kani)] +#[cfg(not(uninit_checks))] mod verify { use core::kani; #[kani::proof] @@ -53,5 +54,8 @@ 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 -Z mem-predicates +echo "[TEST] Run kani verify-std -Z uninit-checks" +RUSTFLAGS="--cfg=uninit_checks" kani verify-std -Z unstable-options "${TMP_DIR}/library" --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing -Z mem-predicates -Z uninit-checks + # Cleanup rm -r ${TMP_DIR} From 5449c3cabb36713a050e8c3978bea0d72695b0db Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Sun, 1 Sep 2024 11:50:20 -0400 Subject: [PATCH 049/159] Update Toolchain to 9/1 (#3478) Updates toolchain to 9/1. This couldn't run automatically because of #3476. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 503c4611e3a0..ca038ea7a812 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-30" +channel = "nightly-2024-09-01" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 6bc48a96cfa9b9a5777189381580e72c6bd676f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 06:40:59 -0700 Subject: [PATCH 050/159] Automatic cargo update to 2024-09-02 (#3480) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0da9ff2e2159..c180c66dde90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -483,7 +483,7 @@ dependencies = [ "shell-words", "strum", "strum_macros", - "syn 2.0.76", + "syn 2.0.77", "tracing", "tracing-subscriber", "tracing-tree", @@ -541,7 +541,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -953,9 +953,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags", "errno", @@ -1029,7 +1029,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1136,7 +1136,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1151,9 +1151,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1190,7 +1190,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1287,7 +1287,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1574,5 +1574,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] From cc47dd126afab6fd3aa7cdbe1d8e4678c0ced475 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:13:26 -0700 Subject: [PATCH 051/159] Bump tests/perf/s2n-quic from `8f7c04b` to `1ff3a9c` (#3481) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `8f7c04b` to `1ff3a9c`.
Commits
  • 1ff3a9c chore: import 8/29 version (#2311)
  • b6ca57b test(s2n-quic-core): revert to bigger length for Kani test (#2312)
  • 24089b4 chore: release 1.45.0 (#2310)
  • a74e9cb fix(s2n-quic-transport): don't let cwnd drop below initial cwnd when MTU decr...
  • 9af5d19 docs(s2n-quic): fix link to compliance report in CI doc
  • acd3fe4 build(deps): update ring requirement from 0.16 to 0.17 (#2287)
  • 2869c19 build(deps): update lru requirement from 0.10 to 0.12 (#2286)
  • b3a10a1 fix(s2n-quic): ConfirmComplete with handshake de-duplication (#2307)
  • b4ba62d fix(s2n-quic-transport): wait until handshake is confirmed to start MTU probi...
  • 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 8f7c04be292f..1ff3a9c8adee 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 8f7c04be292f6419b38e37bd7ff67f15833735d7 +Subproject commit 1ff3a9c8adee30346537698c7b2a12279facd2d3 From ec5bfc6a3dac88c4110b5e0e727b3bbd82215ab8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:21:30 +0200 Subject: [PATCH 052/159] Automatic toolchain upgrade to nightly-2024-09-02 (#3479) Update Rust toolchain from nightly-2024-09-01 to nightly-2024-09-02 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 ca038ea7a812..ae9b702c4c05 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-09-01" +channel = "nightly-2024-09-02" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 2c939abcd956247676a5f727f5433e7a10a66cc1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:00:18 +0200 Subject: [PATCH 053/159] Automatic toolchain upgrade to nightly-2024-09-03 (#3482) Update Rust toolchain from nightly-2024-09-02 to nightly-2024-09-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 ae9b702c4c05..a9bc7f1dd533 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-09-02" +channel = "nightly-2024-09-03" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From eb4d5a6e7523bc42e865c1f410569d3de0576e77 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 3 Sep 2024 10:10:24 -0400 Subject: [PATCH 054/159] RFC for List Subcommand (#3463) RFC for the `kani list` subcommand. I saw the comment [in the template](https://github.com/model-checking/kani/blob/main/rfc/src/template.md#software-design) to leave the Software Design section empty, but I was looking at the raw MD file and made the mistake of thinking it referred to all sections below the comment. In other words, I thought when it said "We recommend you to leave this empty for the first version of your RFC" it meant that everything below the comment should be empty (so Rationale, Open Questions, and Future Work) and not Software Design. I realized my mistake when I was making this PR, but I figured I may as well leave it since I already wrote it. I [opened a PR](https://github.com/model-checking/kani/pull/3462) to update the comment. Resolves [#2573](https://github.com/model-checking/kani/issues/2573), [#1612](#https://github.com/model-checking/kani/issues/1612). By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rfc/src/SUMMARY.md | 1 + rfc/src/rfcs/0012-list.md | 175 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 rfc/src/rfcs/0012-list.md diff --git a/rfc/src/SUMMARY.md b/rfc/src/SUMMARY.md index 0b38dac9cbb5..c5a7161d0d45 100644 --- a/rfc/src/SUMMARY.md +++ b/rfc/src/SUMMARY.md @@ -17,3 +17,4 @@ - [0009-function-contracts](rfcs/0009-function-contracts.md) - [0010-quantifiers](rfcs/0010-quantifiers.md) - [0011-source-coverage](rfcs/0011-source-coverage.md) +- [0012-list](rfcs/0012-list.md) diff --git a/rfc/src/rfcs/0012-list.md b/rfc/src/rfcs/0012-list.md new file mode 100644 index 000000000000..fea4a65be7f3 --- /dev/null +++ b/rfc/src/rfcs/0012-list.md @@ -0,0 +1,175 @@ +- **Feature Name:** List Subcommand +- **Feature Request Issue:** [#2573](https://github.com/model-checking/kani/issues/2573), [#1612](https://github.com/model-checking/kani/issues/1612) +- **RFC PR:** #3463 +- **Status:** Under Review +- **Version:** 0 + +------------------- + +## Summary + +Add a subcommand `list` that, for each crate under verification, lists the information relevant to its verification. + +## User Impact + +Currently, there is no automated way for a user to gather metadata about Kani's integration with their project. If, for example, a user wants a list of harnesses for their project, they must search for all the relevant contract attributes (currently `#[proof]` or `#[proof_for_contract]`) themselves. If done manually, this process is tedious, especially for large projects. Even with a shell script, it is error-prone--if, for example, we introduce a new type of proof harness, users would have to account for it when searching their project. + +Internally, this feature will be useful for tracking our customers' use of Kani and our progress with standard library verification. Externally, users can leverage this feature to get a high-level view of which areas of their projects have harnesses (and, by extension, which areas are still in need of verification). + +This feature will not cause any regressions for exisiting users. + +## User Experience + +Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand will take the option `--message-format=[human|json]`, which changes the output format. The default is `human`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. + +This subcommand will not fail. In the case that it does not find any harnesses or contracts, it will print a message informing the user of that fact. + +### Human Format + +The default format, `human`, will print the harnesses and contracts in a project, along with the total counts of each. + +For example: + +``` +Kani Version: 0.5.4 + +Standard Harnesses: +- example::verify::check_new +- example::verify::check_modify + +Contract Harnesses: +- example::verify::check_foo_u32 +- example::verify::check_foo_u64 +- example::verify::check_func +- example::verify::check_bar + +Contracts: +|--------------------------|-----------------------------------------------| +| Function | Contract Harnesses | +|--------------------------|-----------------------------------------------| +| example::impl::func | example::verify::check_func | +|--------------------------|-----------------------------------------------| +| example::impl::bar | example::verify::check_bar | +|--------------------------|-----------------------------------------------| +| example::impl::foo | example::verify::check_foo_u32, | +| | example::verify::check_foo_u64 | +|--------------------------|-----------------------------------------------| +| example::prep::parse | NONE | +|--------------------------|-----------------------------------------------| + +Totals: +- Standard Harnesses: 2 +- Contract Harnesses: 4 +- Functions with Contracts: 4 +- Contracts: 10 +``` + +A "Standard Harness" is a `#[proof]` harness, while a "Contract Harness" is a `#[proof_for_contract]` harness. + +### JSON Format + +As the name implies, the goal of the `human` output is to be friendly for human readers. If the user wants an output format that's more easily parsed by a script, they can use the `json` option. + +The JSON format will contain the same information as the human format, with the addition of file paths and file version. The file version will start at zero and increment whenever we make an update to the format. This way, any users relying on this format for their scripts can realize that changes have occurred and update their logic accordingly. + +For example: + +```json +{ + kani-version: 0.5.4, + file-version: 0, + standard-harnesses: [ + { + file: /Users/johnsmith/example/kani_standard_proofs.rs + harnesses: [ + example::verify::check_modify, + example::verify::check_new + ] + }, + ], + contract-harnesses: [ + { + file: /Users/johnsmith/example/kani_contract_proofs.rs + harnesses: [ + example::verify::check_bar, + example::verify::check_foo_u32, + example::verify::check_foo_u64, + example::verify::check_func + ] + }, + ], + contracts: [ + { + function: example::impl::func + file: /Users/johnsmith/example/impl.rs + harnesses: [example::verify::check_func] + }, + { + function: example::impl::bar + file: /Users/johnsmith/example/impl.rs + harnesses: [example::verify::check_bar] + }, + { + function: example::impl::foo + file: /Users/johnsmith/example/impl.rs + harnesses: [ + example::verify::check_foo_u32, + example::verify::check_foo_u64 + ] + }, + { + function: example::prep::parse + file: /Users/johnsmith/example/prep.rs + harnesses: [] + } + ], + totals: { + standard-harnesses: 2, + contract-harnesses: 4, + functions-with-contracts: 4, + contracts: 10, + } +} +``` + +## Software Design + +We will add a new subcommand to `kani-driver`. + +*We will update this section once the UX is finalized*. + +## Rationale and alternatives + +Users of Kani may have many questions about their project--not only where their contracts and harnesses are, but also where their stubs are, what kinds of contracts they have, etc. Rather than try to answer every question a user might have, which would make the output quite verbose, we focus on these four: + +1. Where are the harnesses? +2. Where are the contracts? +3. Which contracts are verified, and by which harnesses? +4. How many harnesses and contracts are there? + +We believe these questions are the most important for our use cases of tracking verification progress for customers and the standard library. The UX is designed to answer these questions clearly and concisely. + +We could have a more verbose or granular output, e.g., printing the metadata on a per-crate or per-module level, or including stubs or other attributes. Such a design would have the benefit of providing more information, with the disadvantage of being more complex to implement and more information for the user to process. + +If we do not implement this feature, users will have to obtain this metadata through manual searching, or by writing a script to do it themselves. This feature will improve our internal productivity by automating the process. + +## Open questions + +1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts, the number of `requires`, `ensures`, or `modifies` contracts, or the locations of the contracts. +2. More generally, we could introduce additional options that collect information about other Kani attributes (e.g., stubs). The default would be to leave them out, but this way a user could get more verbose output if they so choose. +3. Do we want to add a filtering option? For instance, `--harnesses ` and `--contracts `, where `pattern` corresponds to a Rust-style path. For example, `kani list --harnesses "my_crate::my_module::*"` would include all harnesses with that path prefix, while `kani list --contracts "my_crate::my_module::*"` would include all functions under contract with that path prefix. + +## Out of scope / Future Improvements + +It would be nice to differentiate between regular Kani harnesses and Bolero harnesses. Bolero harnesses invoke Kani using conditional compilation, e.g.: + +```rust +#[cfg_attr(kani, kani::proof)] +fn check() { + bolero::check!()... +} +``` + +See [this blog post](https://model-checking.github.io/kani-verifier-blog/2022/10/27/using-kani-with-the-bolero-property-testing-framework.html) for more information. + +There's no easy way for us to know whether a harness comes from Bolero, since Bolero takes care of rewriting the test to use Kani syntax and invoking the Kani engine. By the time the harness gets to Kani, there's no way for us to tell it apart from a regular harness. Fixing this would require some changes to our Bolero integration. \ No newline at end of file From 6cf4ea46924f17c148cc590fe99b40c6a5282a52 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 3 Sep 2024 13:12:22 -0400 Subject: [PATCH 055/159] Add tests for fixed issues. (#3484) #2239 and #3022 are resolved in Kani v0.5.4. Add tests for them. Resolves #2239 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- tests/expected/issue-2239/issue_2239.expected | 5 ++++ tests/expected/issue-2239/issue_2239.rs | 15 ++++++++++++ tests/expected/issue-3022/issue_3022.expected | 5 ++++ tests/expected/issue-3022/issue_3022.rs | 24 +++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 tests/expected/issue-2239/issue_2239.expected create mode 100644 tests/expected/issue-2239/issue_2239.rs create mode 100644 tests/expected/issue-3022/issue_3022.expected create mode 100644 tests/expected/issue-3022/issue_3022.rs diff --git a/tests/expected/issue-2239/issue_2239.expected b/tests/expected/issue-2239/issue_2239.expected new file mode 100644 index 000000000000..8bdab0df1862 --- /dev/null +++ b/tests/expected/issue-2239/issue_2239.expected @@ -0,0 +1,5 @@ +test_trivial_bounds.unreachable.1\ +- Status: FAILURE\ +- Description: "unreachable code" + +VERIFICATION:- FAILED \ No newline at end of file diff --git a/tests/expected/issue-2239/issue_2239.rs b/tests/expected/issue-2239/issue_2239.rs new file mode 100644 index 000000000000..36c41dec604d --- /dev/null +++ b/tests/expected/issue-2239/issue_2239.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![feature(trivial_bounds)] +#![allow(unused, trivial_bounds)] + +#[kani::proof] +fn test_trivial_bounds() +where + i32: Iterator, +{ + for _ in 2i32 {} +} + +fn main() {} diff --git a/tests/expected/issue-3022/issue_3022.expected b/tests/expected/issue-3022/issue_3022.expected new file mode 100644 index 000000000000..9600182a5209 --- /dev/null +++ b/tests/expected/issue-3022/issue_3022.expected @@ -0,0 +1,5 @@ +main.assertion\ +- Status: SUCCESS\ +- Description: "assertion failed: inner == func2.inner" + +VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/issue-3022/issue_3022.rs b/tests/expected/issue-3022/issue_3022.rs new file mode 100644 index 000000000000..6ef6f944395f --- /dev/null +++ b/tests/expected/issue-3022/issue_3022.rs @@ -0,0 +1,24 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +type BuiltIn = for<'a> fn(&str); + +struct Function { + inner: BuiltIn, +} + +impl Function { + fn new(subr: BuiltIn) -> Self { + Self { inner: subr } + } +} + +fn dummy(_: &str) {} + +#[kani::proof] +fn main() { + let func1 = Function::new(dummy); + let func2 = Function::new(dummy); + let inner: fn(&'static _) -> _ = func1.inner; + assert!(inner == func2.inner); +} From edded56800e355867c439137e3de83b39b9252ad Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Tue, 3 Sep 2024 21:46:12 -0400 Subject: [PATCH 056/159] Update Kani Book (#3474) Add the following to the book: - **Reference section on contracts**. The point of this section is not to be an exhaustive explanation--we have good documentation on contracts already, and we don't want to maintain it in two places. The goal of the section is to give readers an intuition for the main idea and then direct them to more detailed resources. - **Add a reference to the blog**--it's a good resource to learn about Kani and it would be nice to have an easier way to find it. Inspired by [this post](https://users.rust-lang.org/t/can-somebody-explain-how-kani-verifier-works/113918). - **Move the crates documentation earlier.** This partly addresses [#3029](https://github.com/model-checking/kani/issues/3029) by at least making our crates documentation more visible in the book. (I don't have this PR as resolving that issue, since we would need to actually publish the docs on `crates.io` to do that.) By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- docs/src/SUMMARY.md | 5 +- docs/src/getting-started.md | 1 + docs/src/reference/experimental/contracts.md | 66 ++++++++++++++++++++ docs/src/reference/experimental/coverage.md | 2 +- 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 docs/src/reference/experimental/contracts.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 784ec075d183..d534cf1f7f22 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,6 +7,8 @@ - [Using Kani](./usage.md) - [Verification results](./verification-results.md) +- [Crates Documentation](./crates/index.md) + - [Tutorial](./kani-tutorial.md) - [First steps](./tutorial-first-steps.md) - [Failures that Kani can spot](./tutorial-kinds-of-failure.md) @@ -18,6 +20,7 @@ - [Experimental features](./reference/experimental/experimental-features.md) - [Coverage](./reference/experimental/coverage.md) - [Stubbing](./reference/experimental/stubbing.md) + - [Contracts](./reference/experimental/contracts.md) - [Concrete Playback](./reference/experimental/concrete-playback.md) - [Application](./application.md) - [Comparison with other tools](./tool-comparison.md) @@ -45,8 +48,6 @@ - [Unstable features](./rust-feature-support/unstable.md) - [Overrides](./overrides.md) -- [Crates Documentation](./crates/index.md) - --- - [FAQ](./faq.md) diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 5de7ae431850..1acfd039ce97 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -15,6 +15,7 @@ Proof harnesses are similar to test harnesses, especially property-based test ha Kani is currently under active development. Releases are published [here](https://github.com/model-checking/kani/releases). Major changes to Kani are documented in the [RFC Book](https://model-checking.github.io/kani/rfc). +We also publish updates on Kani use cases and features on our [blog](https://model-checking.github.io/kani-verifier-blog/). There is support for a fair amount of Rust language features, but not all (e.g., concurrency). Please see [Limitations](./limitations.md) for a detailed list of supported features. diff --git a/docs/src/reference/experimental/contracts.md b/docs/src/reference/experimental/contracts.md new file mode 100644 index 000000000000..fd4e8169bc34 --- /dev/null +++ b/docs/src/reference/experimental/contracts.md @@ -0,0 +1,66 @@ +# Contracts + +Consider the following example: + +```rust +fn gcd(mut max: u8, mut min: u8) -> u8 { + if min > max { + std::mem::swap(&mut max, &mut min); + } + + let rest = max % min; + if rest == 0 { min } else { gcd(min, rest) } +} +``` +Let's assume we want to verify some code that calls `gcd`. +In the [worst case](https://en.wikipedia.org/wiki/Euclidean_algorithm#Worst-case), the number of steps (recursions) in `gcd` approaches 1.5 times the number of bits needed to represent the input numbers. +So, for two large 64-bit numbers, a single call to `gcd` can take almost 96 iterations. +It would be very expensive for Kani to unroll each of these iterations and then perform symbolic execution. + +Instead, we can write *contracts* with guarantees about `gcd`'s behavior. +Once Kani verifies that `gcd`'s contracts are correct, it can replace each invocation of `gcd` with its contracts, which reduces verification time for `gcd`'s callers. +For example, perhaps we want to ensure that the returned `result` does indeed divide both `max` and `min`. +In that case, we could write contracts like these: + +```rust +#[kani::requires(min != 0 && max != 0)] +#[kani::ensures(|result| *result != 0 && max % *result == 0 && min % *result == 0)] +#[kani::recursion] +fn gcd(mut max: u8, mut min: u8) -> u8 { ... } +``` + +Since `gcd` performs `max % min` (and perhaps swaps those values), passing zero as an argument could cause a division by zero. +The `requires` contract tells Kani to restrict the range of nondeterministic inputs to nonzero ones so that we don't run into this error. +The `ensures` contract is what actually checks that the result is a correct divisor for the inputs. +(The `recursion` attribute is required when using contracts on recursive functions). + +Then, we would write a harness to *verify* those contracts, like so: + +```rust +#[kani::proof_for_contract(gcd)] +fn check_gcd() { + let max: u8 = kani::any(); + let min: u8 = kani::any(); + gcd(max, min); +} +``` + +and verify it by running `kani -Z function-contracts`. + +Once Kani verifies the contracts, we can use Kani's [stubbing feature](stubbing.md) to replace all invocations to `gcd` with its contracts, for instance: + +```rust +// Assume foo() invokes gcd(). +// By using stub_verified, we tell Kani to replace +// invocations of gcd() with its verified contracts. +#[kani::proof] +#[kani::stub_verified(gcd)] +fn check_foo() { + let x: u8 = kani::any(); + foo(x); +} +``` +By leveraging the stubbing feature, we can replace the (expensive) `gcd` call with a *verified abstraction* of its behavior, greatly reducing verification time for `foo`. + +There is far more to learn about contracts. +We highly recommend reading our [blog post about contracts](https://model-checking.github.io/kani-verifier-blog/2024/01/29/function-contracts.html) (from which this `gcd` example is taken). We also recommend looking at the `contracts` module in our [documentation](../../crates/index.md). diff --git a/docs/src/reference/experimental/coverage.md b/docs/src/reference/experimental/coverage.md index fb73d5f7c05b..5e536b3992b6 100644 --- a/docs/src/reference/experimental/coverage.md +++ b/docs/src/reference/experimental/coverage.md @@ -1,4 +1,4 @@ -## Coverage +# 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: From 93a29af23c569c51ddc56d1a2d22bec1d21e01c2 Mon Sep 17 00:00:00 2001 From: Artem Agvanian Date: Tue, 3 Sep 2024 23:00:36 -0400 Subject: [PATCH 057/159] Cross-function union instrumentation (#3465) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces support for memory initialization checks for unions passed across the function boundary. Whenever a union is passed as an argument, we need to make sure that its initialization state is preserved. Unlike pointers, unions do not have a stable memory address which could identify them in shadow memory. Hence, we need to pass extra information across function boundary since unions are passed “by value”. We introduce a global variable to store the previous address of unions passed as function arguments, which allows us to effectively tie the initialization state of unions passed between functions. This struct is written to by the caller and read from by the callee. For more information about planned functionality, see https://github.com/model-checking/kani/issues/3300 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../kani_middle/transform/check_uninit/mod.rs | 68 ++++++++- .../check_uninit/ptr_uninit/uninit_visitor.rs | 132 +++++++++++++----- .../check_uninit/relevant_instruction.rs | 99 ++++++------- .../transform/check_uninit/ty_layout.rs | 59 ++++++-- .../kani_middle/transform/kani_intrinsics.rs | 8 +- library/kani_core/src/mem_init.rs | 81 ++++++++++- tests/expected/uninit/fixme_unions.rs | 58 +++++++- tests/expected/uninit/unions.expected | 22 ++- tests/expected/uninit/unions.rs | 109 +++++++++++++++ 9 files changed, 516 insertions(+), 120 deletions(-) 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 0130c00d1a71..726dc1965770 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -45,6 +45,9 @@ const KANI_IS_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniIsStrPtrInitialized"; const KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC: &str = "KaniSetStrPtrInitialized"; const KANI_COPY_INIT_STATE_DIAGNOSTIC: &str = "KaniCopyInitState"; const KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC: &str = "KaniCopyInitStateSingle"; +const KANI_LOAD_ARGUMENT_DIAGNOSTIC: &str = "KaniLoadArgument"; +const KANI_STORE_ARGUMENT_DIAGNOSTIC: &str = "KaniStoreArgument"; +const KANI_RESET_ARGUMENT_BUFFER_DIAGNOSTIC: &str = "KaniResetArgumentBuffer"; // Function bodies of those functions will not be instrumented as not to cause infinite recursion. const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ @@ -58,6 +61,9 @@ const SKIPPED_DIAGNOSTIC_ITEMS: &[&str] = &[ KANI_SET_STR_PTR_INITIALIZED_DIAGNOSTIC, KANI_COPY_INIT_STATE_DIAGNOSTIC, KANI_COPY_INIT_STATE_SINGLE_DIAGNOSTIC, + KANI_LOAD_ARGUMENT_DIAGNOSTIC, + KANI_STORE_ARGUMENT_DIAGNOSTIC, + KANI_RESET_ARGUMENT_BUFFER_DIAGNOSTIC, ]; /// Instruments the code with checks for uninitialized memory, agnostic to the source of targets. @@ -159,9 +165,9 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { // Calculate pointee layout for byte-by-byte memory initialization checks. match PointeeInfo::from_ty(pointee_ty) { Ok(type_info) => type_info, - Err(_) => { + Err(reason) => { let reason = format!( - "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}.", + "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}. {reason}", ); self.inject_assert_false(self.tcx, body, source, operation.position(), &reason); return; @@ -185,6 +191,9 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { MemoryInitOp::AssignUnion { .. } => { self.build_assign_union(body, source, operation, pointee_info) } + MemoryInitOp::StoreArgument { .. } | MemoryInitOp::LoadArgument { .. } => { + self.build_argument_operation(body, source, operation, pointee_info) + } MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => { unreachable!() } @@ -532,6 +541,61 @@ impl<'a, 'tcx> UninitInstrumenter<'a, 'tcx> { ); } + /// Instrument the code to pass information about arguments containing unions. Whenever a + /// function is called and some of the arguments contain unions, we store the information. And + /// when we enter the callee, we load the information. + fn build_argument_operation( + &mut self, + body: &mut MutableBody, + source: &mut SourceInstruction, + operation: MemoryInitOp, + pointee_info: PointeeInfo, + ) { + let ret_place = Place { + local: body.new_local(Ty::new_tuple(&[]), source.span(body.blocks()), Mutability::Not), + projection: vec![], + }; + let mut statements = vec![]; + let layout_size = pointee_info.layout().maybe_size().unwrap(); + let diagnostic = match operation { + MemoryInitOp::LoadArgument { .. } => KANI_LOAD_ARGUMENT_DIAGNOSTIC, + MemoryInitOp::StoreArgument { .. } => KANI_STORE_ARGUMENT_DIAGNOSTIC, + _ => unreachable!(), + }; + let argument_operation_instance = resolve_mem_init_fn( + get_mem_init_fn_def(self.tcx, diagnostic, &mut self.mem_init_fn_cache), + layout_size, + *pointee_info.ty(), + ); + let operand = operation.mk_operand(body, &mut statements, source); + let argument_no = operation.expect_argument_no(); + let terminator = Terminator { + kind: TerminatorKind::Call { + func: Operand::Copy(Place::from(body.new_local( + argument_operation_instance.ty(), + source.span(body.blocks()), + Mutability::Not, + ))), + args: vec![ + operand, + Operand::Constant(ConstOperand { + span: source.span(body.blocks()), + user_ty: None, + const_: MirConst::try_from_uint(argument_no as u128, UintTy::Usize) + .unwrap(), + }), + ], + destination: ret_place.clone(), + target: Some(0), // this will be overriden in add_bb + unwind: UnwindAction::Terminate, + }, + span: source.span(body.blocks()), + }; + + // Construct the basic block and insert it into the body. + body.insert_bb(BasicBlock { statements, terminator }, source, operation.position()); + } + /// Copy memory initialization state from one union variable to another. fn build_assign_union( &mut self, diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index 207005dafb27..bd4356017b6b 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -9,8 +9,8 @@ use crate::{ body::{InsertPosition, MutableBody, SourceInstruction}, check_uninit::{ relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, - ty_layout::tys_layout_compatible_to_size, - TargetFinder, + ty_layout::{tys_layout_compatible_to_size, LayoutComputationError}, + PointeeInfo, TargetFinder, }, }, }; @@ -38,11 +38,38 @@ impl TargetFinder for CheckUninitVisitor { fn find_all(mut self, body: &MutableBody) -> Vec { self.locals = body.locals().to_vec(); for (bb_idx, bb) in body.blocks().iter().enumerate() { - self.current_target = InitRelevantInstruction { - source: SourceInstruction::Statement { idx: 0, bb: bb_idx }, - before_instruction: vec![], - after_instruction: vec![], + // Set the first target to start iterating from. + self.current_target = if !bb.statements.is_empty() { + InitRelevantInstruction { + source: SourceInstruction::Statement { idx: 0, bb: bb_idx }, + before_instruction: vec![], + after_instruction: vec![], + } + } else { + InitRelevantInstruction { + source: SourceInstruction::Terminator { bb: bb_idx }, + before_instruction: vec![], + after_instruction: vec![], + } }; + if bb_idx == 0 { + let union_args: Vec<_> = body + .locals() + .iter() + .enumerate() + .skip(1) + .take(body.arg_count()) + .filter(|(_, local)| local.ty.kind().is_union()) + .collect(); + if !union_args.is_empty() { + for (idx, _) in union_args { + self.push_target(MemoryInitOp::LoadArgument { + operand: Operand::Copy(Place { local: idx, projection: vec![] }), + argument_no: idx, + }) + } + } + } self.visit_basic_block(bb); } self.targets @@ -84,6 +111,22 @@ impl MirVisitor for CheckUninitVisitor { StatementKind::Assign(place, rvalue) => { // First check rvalue. self.visit_rvalue(rvalue, location); + + // Preemptively check if we do not support computing the layout of a type because of + // inner union fields. This allows to inject `assert!(false)` early. + if let Err(reason @ LayoutComputationError::UnionAsField(_)) = + PointeeInfo::from_ty(place.ty(&self.locals).unwrap()) + { + self.push_target(MemoryInitOp::Unsupported { + reason: format!( + "Checking memory initialization of type {} is not supported. {}", + place.ty(&self.locals).unwrap(), + reason + ), + }); + return; + } + // Check whether we are assigning into a dereference (*ptr = _). if let Some(place_without_deref) = try_remove_topmost_deref(place) { // First, check that we are not dereferencing extra pointers along the way @@ -127,9 +170,10 @@ impl MirVisitor for CheckUninitVisitor { // if a union as a subfield is detected, `assert!(false)` will be injected from // the type layout code. let is_inside_union = { - let mut contains_union = false; let mut place_to_add_projections = Place { local: place.local, projection: vec![] }; + let mut contains_union = + place_to_add_projections.ty(&self.locals).unwrap().kind().is_union(); for projection_elem in place.projection.iter() { if place_to_add_projections.ty(&self.locals).unwrap().kind().is_union() { contains_union = true; @@ -143,35 +187,37 @@ impl MirVisitor for CheckUninitVisitor { // Need to copy some information about union initialization, since lvalue is // either a union or a field inside a union. if is_inside_union { - if let Rvalue::Use(operand) = rvalue { - // This is a union-to-union assignment, so we need to copy the - // initialization state. - if place.ty(&self.locals).unwrap().kind().is_union() { - self.push_target(MemoryInitOp::AssignUnion { - lvalue: place.clone(), - rvalue: operand.clone(), - }); - } else { - // This is assignment to a field of a union. - self.push_target(MemoryInitOp::SetRef { - operand: Operand::Copy(place.clone()), - value: true, - position: InsertPosition::After, - }); + match rvalue { + Rvalue::Use(operand) => { + // This is a union-to-union assignment, so we need to copy the + // initialization state. + if place.ty(&self.locals).unwrap().kind().is_union() { + self.push_target(MemoryInitOp::AssignUnion { + lvalue: place.clone(), + rvalue: operand.clone(), + }); + } else { + // This is assignment to a field of a union. + self.push_target(MemoryInitOp::SetRef { + operand: Operand::Copy(place.clone()), + value: true, + position: InsertPosition::After, + }); + } } - } - } - - // Create a union from scratch as an aggregate. We handle it here because we - // need to know which field is getting assigned. - if let Rvalue::Aggregate(AggregateKind::Adt(adt_def, _, _, _, union_field), _) = - rvalue - { - if adt_def.kind() == AdtKind::Union { - self.push_target(MemoryInitOp::CreateUnion { - operand: Operand::Copy(place.clone()), - field: union_field.unwrap(), // Safe to unwrap because we know this is a union. - }); + Rvalue::Aggregate(AggregateKind::Adt(adt_def, _, _, _, union_field), _) => { + // Create a union from scratch as an aggregate. We handle it here because we + // need to know which field is getting assigned. + if adt_def.kind() == AdtKind::Union { + self.push_target(MemoryInitOp::CreateUnion { + operand: Operand::Copy(place.clone()), + field: union_field.unwrap(), // Safe to unwrap because we know this is a union. + }); + } + } + // TODO: add support for Rvalue::Cast, etc. + _ => self + .push_target(MemoryInitOp::Unsupported { reason: "Performing a union assignment with a non-supported construct as an Rvalue".to_string() }), } } } @@ -218,7 +264,7 @@ impl MirVisitor for CheckUninitVisitor { before_instruction: vec![], }; } else { - unreachable!() + // The only instruction in this basic block is the terminator, which was already set. } // Leave it as an exhaustive match to be notified when a new kind is added. match &term.kind { @@ -340,6 +386,20 @@ impl MirVisitor for CheckUninitVisitor { } _ => {} } + } else { + let union_args: Vec<_> = args + .iter() + .enumerate() + .filter(|(_, arg)| arg.ty(&self.locals).unwrap().kind().is_union()) + .collect(); + if !union_args.is_empty() { + for (idx, operand) in union_args { + self.push_target(MemoryInitOp::StoreArgument { + operand: operand.clone(), + argument_no: idx + 1, // since arguments are 1-indexed + }) + } + } } } _ => {} 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 index 6120f2d3d4c4..9ad23071df4a 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/relevant_instruction.rs @@ -16,65 +16,38 @@ use strum_macros::AsRefStr; 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, - }, + 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, - }, + 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, - }, + 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, - }, + 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, - }, + 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, - }, + SetRef { operand: Operand, value: bool, position: InsertPosition }, /// Unsupported memory initialization operation. - Unsupported { - reason: String, - }, + Unsupported { reason: String }, /// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`. - TriviallyUnsafe { - reason: String, - }, - /// Operation that copies memory initialization state over to another operand. - Copy { - from: Operand, - to: Operand, - count: Operand, - }, - - AssignUnion { - lvalue: Place, - rvalue: Operand, - }, - CreateUnion { - operand: Operand, - field: FieldIdx, - }, + TriviallyUnsafe { reason: String }, + /// Copy memory initialization state over to another operand. + Copy { from: Operand, to: Operand, count: Operand }, + /// Copy memory initialization state over from one union variable to another. + AssignUnion { lvalue: Place, rvalue: Operand }, + /// Create a union from scratch with a given field index and store it in the provided operand. + CreateUnion { operand: Operand, field: FieldIdx }, + /// Load argument containing a union from the argument buffer together if the argument number + /// provided matches. + LoadArgument { operand: Operand, argument_no: usize }, + /// Store argument containing a union into the argument buffer together with the argument number + /// provided. + StoreArgument { operand: Operand, argument_no: usize }, } impl MemoryInitOp { @@ -94,7 +67,9 @@ impl MemoryInitOp { | MemoryInitOp::SetSliceChunk { operand, .. } => operand.clone(), MemoryInitOp::CheckRef { operand, .. } | MemoryInitOp::SetRef { operand, .. } - | MemoryInitOp::CreateUnion { operand, .. } => { + | MemoryInitOp::CreateUnion { operand, .. } + | MemoryInitOp::LoadArgument { operand, .. } + | MemoryInitOp::StoreArgument { operand, .. } => { mk_ref(operand, body, statements, source) } MemoryInitOp::Copy { .. } @@ -141,7 +116,9 @@ impl MemoryInitOp { | MemoryInitOp::SetSliceChunk { operand, .. } => operand.ty(body.locals()).unwrap(), MemoryInitOp::SetRef { operand, .. } | MemoryInitOp::CheckRef { operand, .. } - | MemoryInitOp::CreateUnion { operand, .. } => { + | MemoryInitOp::CreateUnion { operand, .. } + | MemoryInitOp::LoadArgument { operand, .. } + | MemoryInitOp::StoreArgument { operand, .. } => { let place = match operand { Operand::Copy(place) | Operand::Move(place) => place, Operand::Constant(_) => unreachable!(), @@ -188,7 +165,9 @@ impl MemoryInitOp { | MemoryInitOp::CreateUnion { .. } | MemoryInitOp::AssignUnion { .. } | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(), + | MemoryInitOp::TriviallyUnsafe { .. } + | MemoryInitOp::StoreArgument { .. } + | MemoryInitOp::LoadArgument { .. } => unreachable!(), } } @@ -204,7 +183,9 @@ impl MemoryInitOp { | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } | MemoryInitOp::Copy { .. } - | MemoryInitOp::AssignUnion { .. } => unreachable!(), + | MemoryInitOp::AssignUnion { .. } + | MemoryInitOp::StoreArgument { .. } + | MemoryInitOp::LoadArgument { .. } => unreachable!(), } } @@ -220,7 +201,9 @@ impl MemoryInitOp { | MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } | MemoryInitOp::Copy { .. } - | MemoryInitOp::AssignUnion { .. } => None, + | MemoryInitOp::AssignUnion { .. } + | MemoryInitOp::StoreArgument { .. } + | MemoryInitOp::LoadArgument { .. } => None, } } @@ -233,12 +216,22 @@ impl MemoryInitOp { | MemoryInitOp::CheckSliceChunk { .. } | MemoryInitOp::CheckRef { .. } | MemoryInitOp::Unsupported { .. } - | MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before, + | MemoryInitOp::TriviallyUnsafe { .. } + | MemoryInitOp::StoreArgument { .. } + | MemoryInitOp::LoadArgument { .. } => InsertPosition::Before, MemoryInitOp::Copy { .. } | MemoryInitOp::AssignUnion { .. } | MemoryInitOp::CreateUnion { .. } => InsertPosition::After, } } + + pub fn expect_argument_no(&self) -> usize { + match self { + MemoryInitOp::LoadArgument { argument_no, .. } + | MemoryInitOp::StoreArgument { argument_no, .. } => *argument_no, + _ => unreachable!(), + } + } } /// Represents an instruction in the source code together with all memory initialization checks/sets 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 dac130f11545..8a9e3ac094d5 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,11 +3,12 @@ // //! Utility functions that help calculate type layout. +use std::fmt::Display; + use stable_mir::{ abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape}, target::{MachineInfo, MachineSize}, ty::{AdtKind, IndexedVal, RigidTy, Ty, TyKind, UintTy, VariantIdx}, - CrateDef, }; /// Represents a chunk of data bytes in a data structure. @@ -68,8 +69,41 @@ pub struct PointeeInfo { layout: PointeeLayout, } +/// Different layout computation errors that could arise from the currently unsupported constructs. +pub enum LayoutComputationError { + UnknownUnsizedLayout(Ty), + EnumWithNicheEncoding(Ty), + EnumWithMultiplePaddingVariants(Ty), + UnsupportedType(Ty), + UnionAsField(Ty), +} + +impl Display for LayoutComputationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LayoutComputationError::UnknownUnsizedLayout(ty) => { + write!(f, "Cannot determine layout for an unsized type {ty}") + } + LayoutComputationError::EnumWithNicheEncoding(ty) => { + write!(f, "Cannot determine layout for an Enum with niche encoding of type {ty}") + } + LayoutComputationError::EnumWithMultiplePaddingVariants(ty) => write!( + f, + "Cannot determine layout for an Enum of type {ty}, as it has multiple variants that have different padding." + ), + LayoutComputationError::UnsupportedType(ty) => { + write!(f, "Cannot determine layout for an unsupported type {ty}.") + } + LayoutComputationError::UnionAsField(ty) => write!( + f, + "Cannot determine layout for a type that contains union of type {ty} as a field." + ), + } + } +} + impl PointeeInfo { - pub fn from_ty(ty: Ty) -> Result { + pub fn from_ty(ty: Ty) -> Result { match ty.kind() { TyKind::RigidTy(rigid_ty) => match rigid_ty { RigidTy::Adt(adt_def, args) if adt_def.kind() == AdtKind::Union => { @@ -120,7 +154,7 @@ impl PointeeInfo { }; Ok(PointeeInfo { pointee_ty: ty, layout }) } else { - Err(format!("Cannot determine type layout for type `{ty}`")) + Err(LayoutComputationError::UnknownUnsizedLayout(ty)) } } }, @@ -144,7 +178,7 @@ fn data_bytes_for_ty( machine_info: &MachineInfo, ty: Ty, current_offset: usize, -) -> Result, String> { +) -> Result, LayoutComputationError> { let layout = ty.layout().unwrap().shape(); match layout.fields { @@ -201,9 +235,7 @@ fn data_bytes_for_ty( VariantsShape::Multiple { tag_encoding: TagEncoding::Niche { .. }, .. - } => { - Err(format!("Unsupported Enum `{}` check", def.trimmed_name()))? - } + } => Err(LayoutComputationError::EnumWithNicheEncoding(ty)), VariantsShape::Multiple { variants, tag, .. } => { // Retrieve data bytes for the tag. let tag_size = match tag { @@ -273,10 +305,11 @@ fn data_bytes_for_ty( } else { // Struct has multiple padding variants, Kani cannot // differentiate between them. - Err(format!( - "Unsupported Enum `{}` check", - def.trimmed_name() - )) + Err( + LayoutComputationError::EnumWithMultiplePaddingVariants( + ty, + ), + ) } } } @@ -362,10 +395,10 @@ fn data_bytes_for_ty( | RigidTy::Coroutine(_, _, _) | RigidTy::CoroutineWitness(_, _) | RigidTy::Foreign(_) - | RigidTy::Dynamic(_, _, _) => Err(format!("Unsupported {ty:?}")), + | RigidTy::Dynamic(_, _, _) => Err(LayoutComputationError::UnsupportedType(ty)), } } - FieldsShape::Union(_) => Err(format!("Unions as fields of unions are unsupported {ty:?}")), + FieldsShape::Union(_) => Err(LayoutComputationError::UnionAsField(ty)), FieldsShape::Array { .. } => Ok(vec![]), } } diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 9210c43fb163..5a026db525a7 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -195,9 +195,9 @@ impl IntrinsicGeneratorPass { let arg_ty = new_body.locals()[1].ty; // Sanity check: since CBMC memory object primitives only accept pointers, need to // ensure the correct type. - let TyKind::RigidTy(RigidTy::RawPtr(target_ty, _)) = arg_ty.kind() else { unreachable!() }; + let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = arg_ty.kind() else { unreachable!() }; // Calculate pointee layout for byte-by-byte memory initialization checks. - let pointee_info = PointeeInfo::from_ty(target_ty); + let pointee_info = PointeeInfo::from_ty(pointee_ty); match pointee_info { Ok(pointee_info) => { match pointee_info.layout() { @@ -338,7 +338,7 @@ impl IntrinsicGeneratorPass { } }; } - Err(msg) => { + Err(reason) => { // We failed to retrieve the type layout. let rvalue = Rvalue::Use(Operand::Constant(ConstOperand { const_: MirConst::from_bool(false), @@ -348,7 +348,7 @@ impl IntrinsicGeneratorPass { let result = new_body.insert_assignment(rvalue, &mut source, InsertPosition::Before); let reason = format!( - "Kani currently doesn't support checking memory initialization of `{target_ty}`. {msg}" + "Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}. {reason}", ); new_body.insert_check( tcx, diff --git a/library/kani_core/src/mem_init.rs b/library/kani_core/src/mem_init.rs index cec23c14263b..6475c62e31ae 100644 --- a/library/kani_core/src/mem_init.rs +++ b/library/kani_core/src/mem_init.rs @@ -22,6 +22,13 @@ #[allow(clippy::crate_in_macro_def)] macro_rules! kani_mem_init { ($core:path) => { + /// Global object for tracking memory initialization state. + #[rustc_diagnostic_item = "KaniMemoryInitializationState"] + static mut MEM_INIT_STATE: MemoryInitializationState = MemoryInitializationState::new(); + + /// Global object for tracking union initialization state across function boundaries. + static mut ARGUMENT_BUFFER: Option = None; + /// Bytewise mask, representing which bytes of a type are data and which are padding. /// For example, for a type like this: /// ``` @@ -97,6 +104,7 @@ macro_rules! kani_mem_init { /// Copy memory initialization state by non-deterministically switching the tracked object and /// adjusting the tracked offset. + #[kanitool::disable_checks(pointer)] pub fn copy( &mut self, from_ptr: *const u8, @@ -119,6 +127,24 @@ macro_rules! kani_mem_init { self.tracked_offset += to_offset - from_offset; // Note that this preserves the value. } + } else { + self.bless::(to_ptr, 1); + } + } + + /// Set currently tracked memory initialization state to `true` if `ptr` points to the + /// currently tracked object and the tracked offset lies within `LAYOUT_SIZE * num_elts` + /// bytes of `ptr`. + #[kanitool::disable_checks(pointer)] + pub fn bless(&mut self, ptr: *const u8, num_elts: usize) { + let obj = super::mem::pointer_object(ptr); + let offset = super::mem::pointer_offset(ptr); + + if self.tracked_object_id == obj + && self.tracked_offset >= offset + && self.tracked_offset < offset + num_elts * LAYOUT_SIZE + { + self.value = true; } } @@ -172,10 +198,6 @@ macro_rules! kani_mem_init { } } - /// Global object for tracking memory initialization state. - #[rustc_diagnostic_item = "KaniMemoryInitializationState"] - static mut MEM_INIT_STATE: MemoryInitializationState = MemoryInitializationState::new(); - /// Set tracked object and tracked offset to a non-deterministic value. #[kanitool::disable_checks(pointer)] #[rustc_diagnostic_item = "KaniInitializeMemoryInitializationState"] @@ -343,5 +365,56 @@ macro_rules! kani_mem_init { fn copy_init_state_single(from: *const T, to: *const T) { copy_init_state::(from, to, 1); } + + /// Information about currently tracked argument, used for passing union initialization + /// state across function boundaries. This struct is written to by the caller and read from + /// by the callee. + #[derive(Clone, Copy)] + struct ArgumentBuffer { + selected_argument: usize, + saved_address: *const (), + layout_size: usize, + } + + /// Non-deterministically store information about currently tracked argument in the argument + /// buffer. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniStoreArgument"] + fn store_argument(from: *const T, selected_argument: usize) { + let (from_ptr, _) = from.to_raw_parts(); + let should_store: bool = super::any(); + if should_store { + unsafe { + ARGUMENT_BUFFER = Some(ArgumentBuffer { + selected_argument, + saved_address: from_ptr, + layout_size: LAYOUT_SIZE, + }) + } + } + } + + /// Load information from the argument buffer (if the argument position matches) via copying + /// the memory initialization information from an address in the caller to an address in the + /// callee. Otherwise, mark that the argument as initialized, as it will be checked by + /// another non-deterministic branch. Reset the argument buffer after loading from it. + #[kanitool::disable_checks(pointer)] + #[rustc_diagnostic_item = "KaniLoadArgument"] + fn load_argument(to: *const T, selected_argument: usize) { + let (to_ptr, _) = to.to_raw_parts(); + if let Some(buffer) = unsafe { ARGUMENT_BUFFER } { + if buffer.selected_argument == selected_argument { + assert!(buffer.layout_size == LAYOUT_SIZE); + copy_init_state_single::(buffer.saved_address, to_ptr); + unsafe { + ARGUMENT_BUFFER = None; + } + return; + } + } + unsafe { + MEM_INIT_STATE.bless::(to_ptr as *const u8, 1); + } + } }; } diff --git a/tests/expected/uninit/fixme_unions.rs b/tests/expected/uninit/fixme_unions.rs index ec86e5e7e07f..b0e7d600187d 100644 --- a/tests/expected/uninit/fixme_unions.rs +++ b/tests/expected/uninit/fixme_unions.rs @@ -14,14 +14,13 @@ union U { b: u32, } -/// Reading padding data via simple union access if union is passed to another function. +/// Reading non-padding data but a union is behind a pointer. #[kani::proof] -unsafe fn cross_function_union_should_fail() { - unsafe fn helper(u: U) { - let padding = u.b; // Read 4 bytes from `u`. - } +unsafe fn pointer_union_should_pass() { let u = U { a: 0 }; // `u` is initialized for 2 bytes. - helper(u); + let u_ptr = addr_of!(u); + let u1 = *u_ptr; + let padding = u1.a; // Read 2 bytes from `u`. } /// Reading padding data but a union is behind a pointer. @@ -48,14 +47,59 @@ unsafe fn union_as_subfields_should_pass() { let padding = u1.a; // Read 2 bytes from `u`. } +/// Tests initialized access if unions are top-level subfields. +#[kani::proof] +unsafe fn union_as_subfields_should_fail() { + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + let s = S { u }; + let s1 = s; + let u1 = s1.u; // `u1` is initialized for 2 bytes. + let padding = u1.b; // Read 4 bytes from `u`. +} + union Outer { u: U, a: u32, } -/// Tests unions composing with other unions. +/// Tests unions composing with other unions and reading non-padding data. #[kani::proof] unsafe fn uber_union_should_pass() { let u = Outer { u: U { b: 0 } }; // `u` is initialized for 4 bytes. let non_padding = u.a; // Read 4 bytes from `u`. } + +/// Tests unions composing with other unions and reading padding data. +#[kani::proof] +unsafe fn uber_union_should_fail() { + let u = Outer { u: U { a: 0 } }; // `u` is initialized for 2 bytes. + let padding = u.a; // Read 4 bytes from `u`. +} + +/// Attempting to read initialized data via transmuting a union. +#[kani::proof] +unsafe fn transmute_union_should_pass() { + let u = U { b: 0 }; // `u` is initialized for 4 bytes. + let non_padding: u32 = std::mem::transmute(u); // Transmute `u` into a value of 4 bytes. +} + +/// Attempting to read uninitialized data via transmuting a union. +#[kani::proof] +unsafe fn transmute_union_should_fail() { + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + let padding: u32 = std::mem::transmute(u); // Transmute `u` into a value of 4 bytes. +} + +/// Attempting to transmute into union and read initialized data. +#[kani::proof] +unsafe fn transmute_into_union_should_pass() { + let u: U = std::mem::transmute(0u32); // `u` is initialized for 4 bytes. + let non_padding = u.b; // Read 4 bytes from `u`. +} + +/// Attempting to transmute into union and read uninitialized data. +#[kani::proof] +unsafe fn transmute_into_union_should_fail() { + let u: U = std::mem::transmute_copy(&0u16); // `u` is initialized for 2 bytes. + let padding = u.b; // Read 4 bytes from `u`. +} diff --git a/tests/expected/uninit/unions.expected b/tests/expected/uninit/unions.expected index 05fdac3a8765..ca7f777c4065 100644 --- a/tests/expected/uninit/unions.expected +++ b/tests/expected/uninit/unions.expected @@ -10,8 +10,28 @@ basic_union_should_fail.assertion.1\ - Status: FAILURE\ - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" +cross_function_union_should_fail::helper.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +cross_function_multi_union_should_fail::helper.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +multi_cross_function_union_should_fail::sub_helper.assertion.1\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u32`" + +basic_multifield_union_should_fail.assertion.7\ + - Status: FAILURE\ + - Description: "Undefined Behavior: Reading from an uninitialized pointer of type `u128`" + Summary: +Verification failed for - cross_function_multi_union_should_fail +Verification failed for - multi_cross_function_union_should_fail +Verification failed for - cross_function_union_should_fail Verification failed for - union_update_should_fail Verification failed for - union_complex_subfields_should_fail +Verification failed for - basic_multifield_union_should_fail Verification failed for - basic_union_should_fail -Complete - 3 successfully verified harnesses, 3 failures, 6 total. +Complete - 7 successfully verified harnesses, 7 failures, 14 total. diff --git a/tests/expected/uninit/unions.rs b/tests/expected/uninit/unions.rs index c623f9fcea94..979120fb566c 100644 --- a/tests/expected/uninit/unions.rs +++ b/tests/expected/uninit/unions.rs @@ -30,6 +30,42 @@ unsafe fn basic_union_should_fail() { let padding = u1.b; } +#[repr(C)] +#[derive(Clone, Copy)] +union MultiU { + d: u128, + a: u16, + c: u64, + b: u32, +} + +/// Simple and correct multifield union access. +#[kani::proof] +unsafe fn basic_multifield_union_should_pass() { + let u = MultiU { c: 0 }; + let mut u1 = u; + let non_padding_a = u1.a; + assert!(non_padding_a == 0); + let non_padding_b = u1.b; + assert!(non_padding_b == 0); + let non_padding_c = u1.c; + assert!(non_padding_c == 0); +} + +/// Reading padding data via simple multifield union access. +#[kani::proof] +unsafe fn basic_multifield_union_should_fail() { + let u = MultiU { c: 0 }; + let mut u1 = u; + let non_padding_a = u1.a; + assert!(non_padding_a == 0); + let non_padding_b = u1.b; + assert!(non_padding_b == 0); + let non_padding_c = u1.c; + assert!(non_padding_c == 0); + let padding = u1.d; // Accessing uninitialized data. +} + #[repr(C)] union U1 { a: (u32, u8), @@ -66,3 +102,76 @@ unsafe fn union_update_should_fail() { u.a = 0; let padding = u.b; } + +/// Reading padding data via simple union access if union is passed to another function. +#[kani::proof] +unsafe fn cross_function_union_should_fail() { + unsafe fn helper(u: U) { + let padding = u.b; // Read 4 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading non-padding data via simple union access if union is passed to another function. +#[kani::proof] +unsafe fn cross_function_union_should_pass() { + unsafe fn helper(u: U) { + let non_padding = u.a; // Read 2 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading padding data via simple union access if union is passed to another function multiple +/// times. +#[kani::proof] +unsafe fn multi_cross_function_union_should_fail() { + unsafe fn helper(u: U) { + sub_helper(u); + } + unsafe fn sub_helper(u: U) { + let padding = u.b; // Read 4 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading non-padding data via simple union access if union is passed to another function multiple +/// times. +#[kani::proof] +unsafe fn multi_cross_function_union_should_pass() { + unsafe fn helper(u: U) { + sub_helper(u); + } + unsafe fn sub_helper(u: U) { + let non_padding = u.a; // Read 2 bytes from `u`. + } + let u = U { a: 0 }; // `u` is initialized for 2 bytes. + helper(u); +} + +/// Reading padding data via simple union access if multiple unions are passed to another function. +#[kani::proof] +unsafe fn cross_function_multi_union_should_fail() { + unsafe fn helper(u1: U, u2: U) { + let padding = u1.b; // Read 4 bytes from `u1`. + let non_padding = u2.b; // Read 4 bytes from `u2`. + } + let u1 = U { a: 0 }; // `u1` is initialized for 2 bytes. + let u2 = U { b: 0 }; // `u2` is initialized for 4 bytes. + helper(u1, u2); +} + +/// Reading non-padding data via simple union access if multiple unions are passed to another +/// function. +#[kani::proof] +unsafe fn cross_function_multi_union_should_pass() { + unsafe fn helper(u1: U, u2: U) { + let padding = u1.b; // Read 4 bytes from `u1`. + let non_padding = u2.b; // Read 4 bytes from `u2`. + } + let u1 = U { b: 0 }; // `u1` is initialized for 4 bytes. + let u2 = U { b: 0 }; // `u2` is initialized for 4 bytes. + helper(u1, u2); +} From 4a9a70c1a2119c47004adcb1e9f4bd9ebcbe8eec Mon Sep 17 00:00:00 2001 From: rahulku Date: Wed, 4 Sep 2024 05:18:07 -0700 Subject: [PATCH 058/159] vstte paper (#3473) 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: Michael Tautschnig Co-authored-by: Michael Tautschnig --- papers/vstte2024/README.md | 1 + papers/vstte2024/paper.bib | 135 ++++++++++++++++++++++++++ papers/vstte2024/paper.tex | 172 ++++++++++++++++++++++++++++++++++ scripts/ci/copyright_check.py | 12 +-- 4 files changed, 314 insertions(+), 6 deletions(-) create mode 100644 papers/vstte2024/README.md create mode 100644 papers/vstte2024/paper.bib create mode 100644 papers/vstte2024/paper.tex diff --git a/papers/vstte2024/README.md b/papers/vstte2024/README.md new file mode 100644 index 000000000000..568b501138b5 --- /dev/null +++ b/papers/vstte2024/README.md @@ -0,0 +1 @@ +This contains the contents for a short paper at VSTTE2024. In order to build this, please download the LLNCS style file available at https://resource-cms.springernature.com/springer-cms/rest/v1/content/19238648/data/v8, unpack the resulting llncs.zip, and use standard LaTeX tools to build the paper. \ No newline at end of file diff --git a/papers/vstte2024/paper.bib b/papers/vstte2024/paper.bib new file mode 100644 index 000000000000..dafdbc98cbb2 --- /dev/null +++ b/papers/vstte2024/paper.bib @@ -0,0 +1,135 @@ +% Copyright Kani Contributors +% SPDX-License-Identifier: Apache-2.0 OR MIT + +\begin{paper}{8} +@inproceedings{verus-sys, + author = {Lattuada, Andrea and Hance, Travis and Bosamiya, Jay and Brun, Matthias and Cho, Chanhee and LeBlanc, Hayley and Srinivasan, Pranav and Achermann, Reto and Chajed, Tej and Hawblitzel, Chris and Howell, Jon and Lorch, Jay and Padon, Oded and Parno, Bryan}, + booktitle = {Proceedings of the ACM Symposium on Operating Systems Principles (SOSP)}, + code = {https://github.com/verus-lang/verus}, + month = {November}, + title = {Verus: A Practical Foundation for Systems Verification}, + year = {2024} +} + +@inproceedings{denis2022creusot, + title={Creusot: a foundry for the deductive verification of {R}ust programs}, + author={Denis, Xavier and Jourdan, Jacques-Henri and March{'e}, Claude}, + booktitle={International Conference on Formal Engineering Methods}, + pages={90--105}, + year={2022}, + organization={Springer} +} + +@inproceedings{astrauskas2022prusti, + title={The {P}rusti project: Formal verification for {R}ust}, + author={Astrauskas, Vytautas and B{\'\i}l{\`y}, Aurel and Fiala, Jon{\'a}{\v{s}} and Grannan, Zachary and Matheja, Christoph and M{\"u}ller, Peter and Poli, Federico and Summers, Alexander J}, + booktitle={NASA Formal Methods Symposium}, + pages={88--108}, + year={2022}, + organization={Springer} +} + +@inproceedings{vanhattum2022verifying, + title={Verifying dynamic trait objects in {R}ust}, + author={VanHattum, Alexa and Schwartz-Narbonne, Daniel and Chong, Nathan and Sampson, Adrian}, + booktitle={Proceedings of the 44th International Conference on Software Engineering: Software Engineering in Practice}, + pages={321--330}, + year={2022} +} + +@manual{superPower, + title = {Unsafe Rust}, + url = {https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html}, + author = {Rust Documentation} +} + +@inproceedings{li2021mirchecker, + title={MirChecker: detecting bugs in {R}ust programs via static analysis}, + author={Li, Zhuohua and Wang, Jincheng and Sun, Mingshen and Lui, John CS}, + booktitle={Proceedings of the 2021 ACM SIGSAC conference on computer and communications security}, + pages={2183--2196}, + year={2021} +} + +@manual{stringChallenge, + title = {Memory Safety of String}, + year = {2024}, + author = {Zyad Hassan}, + url = {https://model-checking.github.io/verify-rust-std/challenges/0010-string.html} +} + +@manual{anderson20medium, + title = {The Rust Compilation Model Calamity}, + year = {2020}, + url = {https://pingcap.medium.com/the-rust-compilation-model-calamity-1a8ce781cf6cb}, + author = {Brian Anderson} +} + +@article{matsakis2014rust, + title={The rust language}, + author={Matsakis, Nicholas D and Klock, Felix S}, + journal={ACM SIGAda Ada Letters}, + volume={34}, + number={3}, + pages={103--104}, + year={2014}, + publisher={ACM New York, NY, USA} +} + +@manual{challenge, + title = {Memory Safety of String}, + url = {https://github.com/model-checking/verify-rust-std/issues/61}, + author = {Zyad Hassan}, + year = {2024} +} + +@manual{solution, + title = {char and ascii{\_}char contracts}, + url = {https://github.com/model-checking/verify-rust-std/pull/48}, + year = {2024}, + author = {Carolyn Zech} +} + +@manual{rustAndroid, + title = {The Impact of Rust on Security Development}, + year = {2024}, + url = {https://www.riscure.com/the-impact-of-rust-on-security-development}, + author = {Christiaan Biesterbosch and Valeria Vatolina} +} +@manual{liam2022android, + title = {How Google is using Rust to reduce memory safety vulnerabilities in Android}, + year = {2020}, + url = {https://www.zdnet.com/article/google-after-using-rust-we-slashed-android-memory-safety-vulnerabilities/}, + author = {Liam Tung} +} + +@article{jung2017rustbelt, + title={RustBelt: Securing the foundations of the Rust programming language}, + author={Jung, Ralf and Jourdan, Jacques-Henri and Krebbers, Robbert and Dreyer, Derek}, + journal={Proceedings of the ACM on Programming Languages}, + volume={2}, + number={POPL}, + pages={1--34}, + year={2017}, + publisher={ACM New York, NY, USA} +} + +@article{ayoun2024hybrid, + title={A hybrid approach to semi-automated Rust verification}, + author={Ayoun, Sacha-{\'E}lie and Denis, Xavier and Maksimovi{\'c}, Petar and Gardner, Philippa}, + journal={arXiv preprint arXiv:2403.15122}, + year={2024} +} + +@article{ho2022aeneas, + title={Aeneas: Rust verification by functional translation}, + author={Ho, Son and Protzenko, Jonathan}, + journal={Proceedings of the ACM on Programming Languages}, + volume={6}, + number={ICFP}, + pages={711--741}, + year={2022}, + publisher={ACM New York, NY, USA} +} + +\end{thebibliography} \ No newline at end of file diff --git a/papers/vstte2024/paper.tex b/papers/vstte2024/paper.tex new file mode 100644 index 000000000000..a523bbf1de5b --- /dev/null +++ b/papers/vstte2024/paper.tex @@ -0,0 +1,172 @@ +% Copyright Kani Contributors +% SPDX-License-Identifier: Apache-2.0 OR MIT + +\documentclass[runningheads]{llncs} +% +\usepackage[T1]{fontenc} +\usepackage{graphicx} +\usepackage{amsmath,amsfonts} +\usepackage{hyperref} +\usepackage{listings} +\usepackage{xcolor} +\usepackage{color} +\usepackage{listings} +\definecolor{GrayCodeBlock}{RGB}{241,241,241} +\definecolor{BlackText}{RGB}{110,107,94} +\definecolor{RedTypename}{RGB}{182,86,17} +\definecolor{GreenString}{RGB}{96,172,57} +\definecolor{PurpleKeyword}{RGB}{184,84,212} +\definecolor{GrayComment}{RGB}{170,170,170} +\definecolor{GoldDocumentation}{RGB}{180,165,45} +\lstdefinelanguage{rust} +{ + columns=fullflexible, + keepspaces=true, + frame=single, + framesep=0pt, + framerule=0pt, + framexleftmargin=4pt, + framexrightmargin=4pt, + framextopmargin=5pt, + framexbottommargin=3pt, + xleftmargin=4pt, + xrightmargin=4pt, + backgroundcolor=\color{GrayCodeBlock}, + basicstyle=\ttfamily\color{BlackText}, + keywords={ + true,false, + unsafe,async,await,move, + use,pub,crate,super,self,mod, + struct,enum,fn,const,static,let,mut,ref,type,impl,dyn,trait,where,as, + break,continue,if,else,while,for,loop,match,return,yield,in + }, + keywordstyle=\color{PurpleKeyword}, + ndkeywords={ + bool,u8,u16,u32,u64,u128,i8,i16,i32,i64,i128,char,str, + Self,Option,Some,None,Result,Ok,Err,String,Box,Vec,Rc,Arc,Cell,RefCell,HashMap,BTreeMap, + macro_rules + }, + ndkeywordstyle=\color{RedTypename}, + comment=[l][\color{GrayComment}\slshape]{//}, + morecomment=[s][\color{GrayComment}\slshape]{/*}{*/}, + morecomment=[l][\color{GoldDocumentation}\slshape]{///}, + morecomment=[s][\color{GoldDocumentation}\slshape]{/*!}{*/}, + morecomment=[l][\color{GoldDocumentation}\slshape]{//!}, + morecomment=[s][\color{RedTypename}]{\#![}{]}, + morecomment=[s][\color{RedTypename}]{\#[}{]}, + stringstyle=\color{GreenString}, + string=[b]" +} + +\begin{document} +% +\title{Verifying the Rust Standard Library} +%\titlerunning{Verifying the Rust} + +\author{ +Rahul Kumar \and +Celina Val \and +Felipe Monteiro \and +Michael Tautschnig \and +Zyad Hassan \and +Qinheping Hu \and +Adrian Palacios \and +Remi Delmas \and +Jaisurya Nanduri \and +Felix Klock \and +Justus Adam \and +Carolyn Zech \and +Artem Agvanian +} +% +\authorrunning{R. Kumar et al.} + +\institute{Amazon Web Services, USA\\ \url{https://aws.amazon.com/} +} + +\maketitle + +\begin{abstract} +The Rust programming language is growing fast and seeing increased adoption due to performance and speed-of-development benefits. It provides strong compile-time guarantees along with blazing performance and an active community of support. The Rust language has experienced steady growth in the last few years with a total developer size of close to 3M developers. Several large projects such as Servo, TiKV, and the Rust compiler itself are in the millions of lines of code. Although Rust provides strong safety guarantees for \texttt{safe} code, the story with \texttt{unsafe} code is incomplete. In this short paper, we motivate the case for verifying the Rust standard library and how we are approaching this endeavor. We describe our effort to verify the Rust standard library via a crowd-sourced verification effort, wherein verifying the Rust standard library is specified as a set of challenges open to all. + +\keywords{Rust \and standard library \and verification \and formal methods \and safe +\and unsafe \and memory safety \and correctness \and challenge} +\end{abstract} + +\section{Rust} + +Rust~\cite{matsakis2014rust} is a modern programming language designed to enable developers to efficiently create high performance reliable systems. Rust delivers high performance because it does not use a garbage collector. Combined with a powerful type system that enforces ownership of memory wherein memory can be shared or mutable, but never both. This helps avoid data-races and memory errors, thereby reducing the trade-off between high-level safety guarantees and low-level controls -- a highly desired property of programming languages. Unlike C/C++, the Rust language aims to minimize undefined behavior statically by employing a strong type system and an \textit{extensible} ownership model for memory. + +The extensible model of ownership relies on the simple (yet difficult) principle of enforcing that an object can be accessed by multiple aliases/references only for read purposes. To write to an object, there can only be one reference to it at any given time. Such a principle in practice eliminates significant amounts of memory-related errors~\cite{rustAndroid}. In spite of the great benefits in practice, this principle tends to be restrictive for a certain subset of implementations that are too low-level or require very specific types of synchronization. As a result, the Rust language introduced the \texttt{unsafe} keyword. When used, the compiler may not be able to prove the memory safety rules that are enforced on \texttt{safe} code blocks. Alias tracking is not performed for raw pointers which can only be used in \texttt{unsafe} code blocks, which enables developers to perform actions that would be rejected by the compiler in \texttt{safe} code blocks. This is also referred to as \textit{superpowers}~\cite{superPower} of \texttt{unsafe} code blocks. Examples of these superpowers include dereferencing a raw pointer, calling an unsafe function or method, and accessing fields of unions etc. A clear side-effect of this choice is that most if not all memory related errors in the code are due to the \texttt{unsafe} code blocks introduced by the developer. + +Rust developers use \textit{encapsulation} as a common design pattern to mask unsafe code blocks. The safe abstractions allow \texttt{unsafe} code blocks to be limited in number and not leak into all parts of the codebase. The Rust standard library itself has widespread use of \texttt{unsafe} code blocks, with almost 5.5K \texttt{unsafe} functions and 4.8K \texttt{unsafe} code blocks. In the last 3 years, 40 soundness issues have been filed in the Rust standard library along with 17 reported CVEs, even with the extensive testing and usage of the library. The onus of proving the safety and correctness of these \texttt{unsafe} code blocks is on the developers. Some such efforts have been made, but there is still a lot of ground to cover~\cite{jung2017rustbelt}. + +Verifying the Rust standard library is important and rewarding along multiple dimensions such as improving Rust, creating better verification tools, and enabling a safer ecosystem. Given the size and scope of this exercise, we believe doing this in isolation would be expensive and counter-productive. Ergo, we believe that motivating the community and creating a unified crowd-sourced effort is the desirable method, which we hope to catalyze via our proposed effort. + + +\section{Rust Verification Landscape} + +A common misconception Rust developers have is that they are producing \texttt{safe} memory-safe code by simply using Rust as their development language. To counter this, there have been significant efforts to create tools and techniques that enable verification of Rust code. Here we list (alphabetically) some tools: + +\begin{itemize} + + \item \textbf{Creusot}~\cite{denis2022creusot} is a Rust verifier that also employs deductive-style verification for \texttt{safe} Rust code. Creusot also introduces \textbf{Pearlite} - a specification language for specifying function and loop contracts. + + \item \textbf{Gillian-Rust}~\cite{ayoun2024hybrid} is a separation logic based hybrid verification tool for Rust programs with support for \texttt{unsafe} code. Gillian-Rust is also linked to Creusot, but does in certain cases require manual intervention. + + \item \textbf{Kani}~\cite{vanhattum2022verifying} uses bounded model checking to verify generic memory safety properties and user specified assertions. Kani supports both \texttt{unsafe} and \texttt{safe} code, but cannot guarantee unbounded verification in all cases. + + \item \textbf{Prusti}~\cite{astrauskas2022prusti} employs deductive verification to prove functional correctness of \texttt{safe} Rust code. Specifically, it targets certain type of \textit{panics} and allows users to specify properties of interest. + + \item \textbf{Verus}~\cite{verus-sys} is an SMT-based tool used to verify Rust code and can support \texttt{unsafe} in certain situations such as the use of raw pointers and unsafe cells. + + \item There are several other tools which are in the related space, but we do not list them here explicitly. +\end{itemize} + +\section{Verifying the Rust Standard Library} + +We are proposing the creation of a crowd-sourced verification effort, wherein verifying the Rust standard library is specified as a set of challenges. Each challenge describes the goal and the success criteria. Currently, we are focusing on doing verification for memory-safety. The challenges are open to anyone. This effort aims to be \textit{tool agnostic} to facilitate the introduction of verification solutions into the Rust mainline and making verification an integral part of the Rust ecosystem. Towards this, we have been working with the Rust language team to introduce function and loop contracts into the Rust mainline and have created a fork of the Rust standard library repository \url{https://github.com/model-checking/verify-rust-std/} wherein all solutions to challenges and verification artifacts are stored. Challenges can come in various flavors: 1/ specifying contracts for a part of the Rust standard library, 2/ specify and verify a part of the Rust standard library, and 3/ introduce new tools/techniques to verify parts of the Rust standard library. The repository provides templates for introducing new challenges, new tools, and instructions on how to submit solutions to challenges. To date, we have over 20 students, academics, and researchers engaging. + +As part of this effort, we are also creating challenges. For example, we have created a challenge to verify the String library in the standard library~\cite{stringChallenge}. In this challenge, the goal is to verify the memory safety of \texttt{std::string::String} and prove the absence of undefined behavior (UB). Even though the majority of \texttt{String} methods are safe, many of them are safe abstractions over unsafe code. For instance, the insert method is implemented as follows : +\begin{lstlisting}[language=rust, caption=Unsafe usage in String, frame=single, numbers=left] + pub fn insert(&mut self, idx: usize, ch: char) { + assert!(self.is_char_boundary(idx)); + let mut bits = [0; 4]; + let bits = ch.encode_utf8(&mut bits).as_bytes(); + + unsafe { + self.insert_bytes(idx, bits); + } + } +\end{lstlisting} + +The goal also specifies the \textit{success criteria} that must be met for the solution to be reviewed and merged into the CI pipeline. +\begin{lstlisting}[caption=Success criteria for the String challenge.,frame=single] +Verify the memory safety of all public functions that are +safe abstractions over unsafe code: + unbounded: from_utf16le, from_utf16le_lossy, + from_utf16be, from_utf16be_lossy, + remove_matches, insert_str, + split_off, replace_range, retain + others: pop, remove, insert, drain, leak, + into_boxed_str +Ones marked as unbounded must be verified for any +string/slice length. +\end{lstlisting} + +Example of a solution for a challenge can be found in~\cite{solution}. This particular solution introduces new contracts for \texttt{char} and \texttt{ascii\_char}. The contracts are also verified using Kani. + +\noindent \textbf{Our call to action} to you is to come and be a part of this effort and contribute by solving challenges, introducing new challenges, introducing new tools, or helping review and refine the current processes! + +\begin{credits} +\subsubsection{\ackname} We would like to thank all the academic partners that have helped us shape challenges, started contributing to challenges, and provide invaluable advice throughout the process of jump starting this initiative. We also would like to thank Niko Matsakis, Byron Cook, and Kurt Kufeld for their support and leadership. +\end{credits} + +% +% Bibliography +% +\bibliographystyle{splncs04} +\bibliography{paper} + + +\end{document} diff --git a/scripts/ci/copyright_check.py b/scripts/ci/copyright_check.py index 3b07fb56da58..64fd92d06712 100755 --- a/scripts/ci/copyright_check.py +++ b/scripts/ci/copyright_check.py @@ -8,15 +8,15 @@ from itertools import chain -COMMENT_OR_EMPTY_PATTERN = '^(//.*$|#.*$|\\s*$)' +COMMENT_OR_EMPTY_PATTERN = '^(//.*$|#.*$|%.*$|\\s*$)' -STANDARD_HEADER_PATTERN_1 = '(//|#) Copyright Kani Contributors' -STANDARD_HEADER_PATTERN_2 = '(//|#) SPDX-License-Identifier: Apache-2.0 OR MIT' +STANDARD_HEADER_PATTERN_1 = '(//|#|%) Copyright Kani Contributors' +STANDARD_HEADER_PATTERN_2 = '(//|#|%) SPDX-License-Identifier: Apache-2.0 OR MIT' -MODIFIED_HEADER_PATTERN_1 = '(//|#) SPDX-License-Identifier: Apache-2.0 OR MIT' +MODIFIED_HEADER_PATTERN_1 = '(//|#|%) SPDX-License-Identifier: Apache-2.0 OR MIT' MODIFIED_HEADER_PATTERN_2 = COMMENT_OR_EMPTY_PATTERN -MODIFIED_HEADER_PATTERN_3 = '(//|#) Modifications Copyright Kani Contributors' -MODIFIED_HEADER_PATTERN_4 = '(//|#) See GitHub history for details.' +MODIFIED_HEADER_PATTERN_3 = '(//|#|%) Modifications Copyright Kani Contributors' +MODIFIED_HEADER_PATTERN_4 = '(//|#|%) See GitHub history for details.' class CheckResult(Enum): FAIL = 1 From 33e3c361d8b0ba404e51e7a119edabad41b1e86b Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 4 Sep 2024 14:44:42 -0400 Subject: [PATCH 059/159] Fix contract expansion for `old` (#3491) Fixes the macro expansion for contracts to properly place history expressions. ## Problem Before this PR, instantiations of "remembers variables" (i.e., the variables that save the value before the function executes) were always put *above* any statements from previous macro expansions. For example, for this code (from #3359): ```rust #[kani::requires(val < i32::MAX)] #[kani::ensures(|result| *result == old(val + 1))] pub fn next(mut val: i32) -> i32 { val + 1 } ``` Kani would first expand the `requires` attribute and insert `kani::assume(val < i32::MAX)`. The expansion of `ensures` would then put the remembers variables first, generating this: ``` let remember_kani_internal_1e725538cd5566b8 = val + 1; kani::assume(val < i32::MAX); ``` which causes an integer overflow because we don't restrict the value of `val` before adding 1. Instead, we want: ``` kani::assume(val < i32::MAX); let remember_kani_internal_1e725538cd5566b8 = val + 1; ``` ## Solution The solution is to insert the remembers variables immediately after preconditions--that way, they respect the preconditions but are still declared before the function under contract executes. When we're expanding an `ensures` clause, we iterate through each of the already-generated statements, find the position where the preconditions end, then insert the remembers variables there. For instance: ``` kani::assume(x < 100); kani::assume(y < 10); kani::assume(x + y < 105); <-- remembers variables go here --> let _wrapper_arg = ... ``` --- Resolves #3359 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../src/sysroot/contracts/check.rs | 11 ++-- .../src/sysroot/contracts/helpers.rs | 53 ++++++++++++++++++- .../kani_macros/src/sysroot/contracts/mod.rs | 7 +++ .../src/sysroot/contracts/replace.rs | 8 ++- .../ensures_before_requires.expected | 5 ++ .../ensures_before_requires.rs | 18 +++++++ .../respects-preconditions/modifies.expected | 5 ++ .../respects-preconditions/modifies.rs | 29 ++++++++++ .../requires_before_ensures.expected | 5 ++ .../requires_before_ensures.rs | 17 ++++++ 10 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.expected create mode 100644 tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.rs create mode 100644 tests/expected/function-contract/history/respects-preconditions/modifies.expected create mode 100644 tests/expected/function-contract/history/respects-preconditions/modifies.rs create mode 100644 tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.expected create mode 100644 tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.rs diff --git a/library/kani_macros/src/sysroot/contracts/check.rs b/library/kani_macros/src/sysroot/contracts/check.rs index 69f280ad9335..314a5e74bc98 100644 --- a/library/kani_macros/src/sysroot/contracts/check.rs +++ b/library/kani_macros/src/sysroot/contracts/check.rs @@ -9,8 +9,8 @@ use std::mem; use syn::{parse_quote, Block, Expr, FnArg, Local, LocalInit, Pat, PatIdent, ReturnType, Stmt}; use super::{ - helpers::*, shared::build_ensures, ContractConditionsData, ContractConditionsHandler, - INTERNAL_RESULT_IDENT, + helpers::*, shared::build_ensures, ClosureType, ContractConditionsData, + ContractConditionsHandler, INTERNAL_RESULT_IDENT, }; const WRAPPER_ARG: &str = "_wrapper_arg"; @@ -38,9 +38,14 @@ impl<'a> ContractConditionsHandler<'a> { ); let return_expr = body_stmts.pop(); + + let (assumes, rest_of_body) = + split_for_remembers(&body_stmts[..], ClosureType::Check); + quote!({ + #(#assumes)* #remembers - #(#body_stmts)* + #(#rest_of_body)* #exec_postconditions #return_expr }) diff --git a/library/kani_macros/src/sysroot/contracts/helpers.rs b/library/kani_macros/src/sysroot/contracts/helpers.rs index 410c2d971c44..8db6218e2693 100644 --- a/library/kani_macros/src/sysroot/contracts/helpers.rs +++ b/library/kani_macros/src/sysroot/contracts/helpers.rs @@ -4,10 +4,14 @@ //! Functions that operate third party data structures with no logic that is //! specific to Kani and contracts. +use crate::attr_impl::contracts::ClosureType; use proc_macro2::{Ident, Span}; use std::borrow::Cow; use syn::spanned::Spanned; -use syn::{parse_quote, Attribute, Expr, ExprBlock, Local, LocalInit, PatIdent, Stmt}; +use syn::{ + parse_quote, Attribute, Expr, ExprBlock, ExprCall, ExprPath, Local, LocalInit, PatIdent, Path, + Stmt, +}; /// If an explicit return type was provided it is returned, otherwise `()`. pub fn return_type_to_type(return_type: &syn::ReturnType) -> Cow { @@ -169,6 +173,53 @@ pub fn chunks_by<'a, T, C: Default + Extend>( }) } +/// Splits `stmts` into (preconditions, rest). +/// For example, ClosureType::Check assumes preconditions, so given this sequence of statements: +/// ```ignore +/// kani::assume(.. precondition_1); +/// kani::assume(.. precondition_2); +/// let _wrapper_arg = (ptr as * const _,); +/// ... +/// ``` +/// This function would return the two kani::assume statements in the former slice +/// and the remaining statements in the latter. +/// The flow for ClosureType::Replace is the same, except preconditions are asserted rather than assumed. +/// +/// The caller can use the returned tuple to insert remembers statements after `preconditions` and before `rest`. +/// Inserting the remembers statements after `preconditions` ensures that they are bound by the preconditions. +/// To understand why this is important, take the following example: +/// ```ignore +/// #[kani::requires(x < u32::MAX)] +/// #[kani::ensures(|result| old(x + 1) == *result)] +/// fn add_one(x: u32) -> u32 {...} +/// ``` +/// If the `old(x + 1)` statement didn't respect the precondition's upper bound on `x`, Kani would encounter an integer overflow. +/// +/// Inserting the remembers statements before `rest` ensures that they are declared before the original function executes, +/// so that they will store historical, pre-computation values as intended. +pub fn split_for_remembers(stmts: &[Stmt], closure_type: ClosureType) -> (&[Stmt], &[Stmt]) { + let mut pos = 0; + + let check_str = match closure_type { + ClosureType::Check => "assume", + ClosureType::Replace => "assert", + }; + + for stmt in stmts { + if let Stmt::Expr(Expr::Call(ExprCall { func, .. }), _) = stmt { + if let Expr::Path(ExprPath { path: Path { segments, .. }, .. }) = func.as_ref() { + let first_two_idents = + segments.iter().take(2).map(|sgmt| sgmt.ident.to_string()).collect::>(); + + if first_two_idents == vec!["kani", check_str] { + pos += 1; + } + } + } + } + stmts.split_at(pos) +} + 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/mod.rs b/library/kani_macros/src/sysroot/contracts/mod.rs index db5f30131405..281960bf041b 100644 --- a/library/kani_macros/src/sysroot/contracts/mod.rs +++ b/library/kani_macros/src/sysroot/contracts/mod.rs @@ -514,6 +514,13 @@ enum ContractConditionsData { }, } +/// Which function are we currently generating? +#[derive(Copy, Clone, Eq, PartialEq)] +enum ClosureType { + Check, + Replace, +} + impl<'a> ContractConditionsHandler<'a> { /// Handle the contract state and return the generated code fn dispatch_on(mut self, state: ContractFunctionState) -> TokenStream2 { diff --git a/library/kani_macros/src/sysroot/contracts/replace.rs b/library/kani_macros/src/sysroot/contracts/replace.rs index 02ac4a772348..71913e630622 100644 --- a/library/kani_macros/src/sysroot/contracts/replace.rs +++ b/library/kani_macros/src/sysroot/contracts/replace.rs @@ -11,7 +11,7 @@ use syn::Stmt; use super::{ helpers::*, shared::{build_ensures, try_as_result_assign}, - ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT, + ClosureType, ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT, }; impl<'a> ContractConditionsHandler<'a> { @@ -84,9 +84,13 @@ impl<'a> ContractConditionsHandler<'a> { ContractConditionsData::Ensures { attr } => { let (remembers, ensures_clause) = build_ensures(attr); let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site()); + + let (asserts, rest_of_before) = split_for_remembers(before, ClosureType::Replace); + quote!({ + #(#asserts)* #remembers - #(#before)* + #(#rest_of_before)* #(#after)* kani::assume(#ensures_clause); #result diff --git a/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.expected b/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.expected new file mode 100644 index 000000000000..a9e6a1a6a601 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.expected @@ -0,0 +1,5 @@ +next\ + - Status: SUCCESS\ + - Description: "attempt to add with overflow" + +VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.rs b/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.rs new file mode 100644 index 000000000000..380da8898e71 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/ensures_before_requires.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// Demonstrate that when the ensures contract is before the requires contract, +// the history expression respects the upper bound on x, so x + 1 doesn't overflow +// This example is taken from https://github.com/model-checking/kani/issues/3359 + +#[kani::ensures(|result| *result == old(val + 1))] +#[kani::requires(val < i32::MAX)] +pub fn next(val: i32) -> i32 { + val + 1 +} + +#[kani::proof_for_contract(next)] +pub fn check_next() { + let _ = next(kani::any()); +} diff --git a/tests/expected/function-contract/history/respects-preconditions/modifies.expected b/tests/expected/function-contract/history/respects-preconditions/modifies.expected new file mode 100644 index 000000000000..40dd76d9ce4d --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/modifies.expected @@ -0,0 +1,5 @@ +modify\ + - Status: SUCCESS\ + - Description: "attempt to add with overflow" + +VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/function-contract/history/respects-preconditions/modifies.rs b/tests/expected/function-contract/history/respects-preconditions/modifies.rs new file mode 100644 index 000000000000..b20b8396e3d6 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/modifies.rs @@ -0,0 +1,29 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// Demonstrate the the history expression respects preconditions +// with multiple interleaved preconditions, modifies contracts, and history expressions + +#[derive(kani::Arbitrary)] +struct Point { + x: X, + y: Y, +} + +#[kani::requires(ptr.x < 100)] +#[kani::ensures(|result| old(ptr.x + 1) == ptr.x)] +#[kani::modifies(&mut ptr.x)] +#[kani::ensures(|result| old(ptr.y - 1) == ptr.y)] +#[kani::modifies(&mut ptr.y)] +#[kani::requires(ptr.y > 0)] +fn modify(ptr: &mut Point) { + ptr.x += 1; + ptr.y -= 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + let mut p: Point = kani::any(); + modify(&mut p); +} diff --git a/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.expected b/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.expected new file mode 100644 index 000000000000..a9e6a1a6a601 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.expected @@ -0,0 +1,5 @@ +next\ + - Status: SUCCESS\ + - Description: "attempt to add with overflow" + +VERIFICATION:- SUCCESSFUL \ No newline at end of file diff --git a/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.rs b/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.rs new file mode 100644 index 000000000000..4f8f6871f8f9 --- /dev/null +++ b/tests/expected/function-contract/history/respects-preconditions/requires_before_ensures.rs @@ -0,0 +1,17 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// Demonstrate that when the requires contract is before the ensures contract, the history expression respects the upper bound on x, so x + 1 doesn't overflow +// This example is taken from https://github.com/model-checking/kani/issues/3359 + +#[kani::requires(val < i32::MAX)] +#[kani::ensures(|result| *result == old(val + 1))] +pub fn next(val: i32) -> i32 { + val + 1 +} + +#[kani::proof_for_contract(next)] +pub fn check_next() { + let _ = next(kani::any()); +} From 603f9bf6c86aa3956acd0f686ea7ac094362360e Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:00:01 -0700 Subject: [PATCH 060/159] Bump Kani version to 0.55.0 (#3486) These are the auto-generated release notes: ## What's Changed * Update CBMC build instructions for Amazon Linux 2 by @tautschnig in https://github.com/model-checking/kani/pull/3431 * Handle intrinsics systematically by @artemagvanian in https://github.com/model-checking/kani/pull/3422 * Bump tests/perf/s2n-quic from `445f73b` to `ab9723a` by @dependabot in https://github.com/model-checking/kani/pull/3434 * Automatic cargo update to 2024-08-12 by @github-actions in https://github.com/model-checking/kani/pull/3433 * Actually apply CBMC patch by @tautschnig in https://github.com/model-checking/kani/pull/3436 * Update features/verify-rust-std branch by @feliperodri in https://github.com/model-checking/kani/pull/3435 * Add test related to issue 3432 by @celinval in https://github.com/model-checking/kani/pull/3439 * Implement memory initialization state copy functionality by @artemagvanian in https://github.com/model-checking/kani/pull/3350 * Bump tests/perf/s2n-quic from `ab9723a` to `80b93a7` by @dependabot in https://github.com/model-checking/kani/pull/3453 * Make points-to analysis handle all intrinsics explicitly by @artemagvanian in https://github.com/model-checking/kani/pull/3452 * Automatic cargo update to 2024-08-19 by @github-actions in https://github.com/model-checking/kani/pull/3450 * Add loop scanner to tool-scanner by @qinheping in https://github.com/model-checking/kani/pull/3443 * Avoid corner-cases by grouping instrumentation into basic blocks and using backward iteration by @artemagvanian in https://github.com/model-checking/kani/pull/3438 * Re-enabled hierarchical logs in the compiler by @celinval in https://github.com/model-checking/kani/pull/3449 * Fix ICE due to mishandling of Aggregate rvalue for raw pointers to `str` by @celinval in https://github.com/model-checking/kani/pull/3448 * Automatic cargo update to 2024-08-26 by @github-actions in https://github.com/model-checking/kani/pull/3459 * Bump tests/perf/s2n-quic from `80b93a7` to `8f7c04b` by @dependabot in https://github.com/model-checking/kani/pull/3460 * Update deny action by @zhassan-aws in https://github.com/model-checking/kani/pull/3461 * Basic support for memory initialization checks for unions by @artemagvanian in https://github.com/model-checking/kani/pull/3444 * Adjust test patterns so as not to check for trivial properties by @tautschnig in https://github.com/model-checking/kani/pull/3464 * Clarify comment in RFC Template by @carolynzech in https://github.com/model-checking/kani/pull/3462 * RFC: Source-based code coverage by @adpaco-aws in https://github.com/model-checking/kani/pull/3143 * Adopt Rust's source-based code coverage instrumentation by @adpaco-aws in https://github.com/model-checking/kani/pull/3119 * Upgrade toolchain to 08/28 by @jaisnan in https://github.com/model-checking/kani/pull/3454 * Extra tests and bug fixes to the delayed UB instrumentation by @artemagvanian in https://github.com/model-checking/kani/pull/3419 * Upgrade Toolchain to 8/29 by @carolynzech in https://github.com/model-checking/kani/pull/3468 * Automatic toolchain upgrade to nightly-2024-08-30 by @github-actions in https://github.com/model-checking/kani/pull/3469 * Extend name resolution to support qualified paths (Partial Fix) by @celinval in https://github.com/model-checking/kani/pull/3457 * Partially integrate uninit memory checks into `verify_std` by @artemagvanian in https://github.com/model-checking/kani/pull/3470 * Update Toolchain to 9/1 by @carolynzech in https://github.com/model-checking/kani/pull/3478 * Automatic cargo update to 2024-09-02 by @github-actions in https://github.com/model-checking/kani/pull/3480 * Bump tests/perf/s2n-quic from `8f7c04b` to `1ff3a9c` by @dependabot in https://github.com/model-checking/kani/pull/3481 * Automatic toolchain upgrade to nightly-2024-09-02 by @github-actions in https://github.com/model-checking/kani/pull/3479 * Automatic toolchain upgrade to nightly-2024-09-03 by @github-actions in https://github.com/model-checking/kani/pull/3482 * RFC for List Subcommand by @carolynzech in https://github.com/model-checking/kani/pull/3463 * Add tests for fixed issues. by @carolynzech in https://github.com/model-checking/kani/pull/3484 **Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.54.0...kani-0.55.0 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- CHANGELOG.md | 22 ++++++++++++++++++++++ Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- cprover_bindings/Cargo.toml | 2 +- kani-compiler/Cargo.toml | 2 +- kani-driver/Cargo.toml | 2 +- kani_metadata/Cargo.toml | 2 +- library/kani/Cargo.toml | 2 +- library/kani_core/Cargo.toml | 2 +- library/kani_macros/Cargo.toml | 2 +- library/std/Cargo.toml | 2 +- tools/build-kani/Cargo.toml | 2 +- 12 files changed, 42 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d36def1a45f1..e35af4c8fa24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ This file contains notable changes (e.g. breaking changes, major changes, etc.) This file was introduced starting Kani 0.23.0, so it only contains changes from version 0.23.0 onwards. +## [0.55.0] + +### Major/Breaking Changes +* Coverage reporting in Kani is now source-based instead of line-based. +Consequently, the unstable `-Zline-coverage` flag has been replaced with a `-Zsource-coverage` one. +Check the [Source-Coverage RFC](https://model-checking.github.io/kani/rfc/rfcs/0011-source-coverage.html) for more details. +* Several improvements were made to the memory initialization checks. The current state is summarized in https://github.com/model-checking/kani/issues/3300. We welcome your feedback! + +### What's Changed +* Update CBMC build instructions for Amazon Linux 2 by @tautschnig in https://github.com/model-checking/kani/pull/3431 +* Implement memory initialization state copy functionality by @artemagvanian in https://github.com/model-checking/kani/pull/3350 +* Make points-to analysis handle all intrinsics explicitly by @artemagvanian in https://github.com/model-checking/kani/pull/3452 +* Avoid corner-cases by grouping instrumentation into basic blocks and using backward iteration by @artemagvanian in https://github.com/model-checking/kani/pull/3438 +* Fix ICE due to mishandling of Aggregate rvalue for raw pointers to `str` by @celinval in https://github.com/model-checking/kani/pull/3448 +* Basic support for memory initialization checks for unions by @artemagvanian in https://github.com/model-checking/kani/pull/3444 +* Adopt Rust's source-based code coverage instrumentation by @adpaco-aws in https://github.com/model-checking/kani/pull/3119 +* Extra tests and bug fixes to the delayed UB instrumentation by @artemagvanian in https://github.com/model-checking/kani/pull/3419 +* Partially integrate uninit memory checks into `verify_std` by @artemagvanian in https://github.com/model-checking/kani/pull/3470 +* Rust toolchain upgraded to `nightly-2024-09-03` by @jaisnan @carolynzech + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.54.0...kani-0.55.0 + ## [0.54.0] ### Major Changes diff --git a/Cargo.lock b/Cargo.lock index c180c66dde90..1c371b85fba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,7 +93,7 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "build-kani" -version = "0.54.0" +version = "0.55.0" dependencies = [ "anyhow", "cargo_metadata", @@ -235,7 +235,7 @@ dependencies = [ [[package]] name = "cprover_bindings" -version = "0.54.0" +version = "0.55.0" dependencies = [ "lazy_static", "linear-map", @@ -459,7 +459,7 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "kani" -version = "0.54.0" +version = "0.55.0" dependencies = [ "kani_core", "kani_macros", @@ -467,7 +467,7 @@ dependencies = [ [[package]] name = "kani-compiler" -version = "0.54.0" +version = "0.55.0" dependencies = [ "clap", "cprover_bindings", @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "kani-driver" -version = "0.54.0" +version = "0.55.0" dependencies = [ "anyhow", "cargo_metadata", @@ -520,7 +520,7 @@ dependencies = [ [[package]] name = "kani-verifier" -version = "0.54.0" +version = "0.55.0" dependencies = [ "anyhow", "home", @@ -529,14 +529,14 @@ dependencies = [ [[package]] name = "kani_core" -version = "0.54.0" +version = "0.55.0" dependencies = [ "kani_macros", ] [[package]] name = "kani_macros" -version = "0.54.0" +version = "0.55.0" dependencies = [ "proc-macro-error", "proc-macro2", @@ -546,7 +546,7 @@ dependencies = [ [[package]] name = "kani_metadata" -version = "0.54.0" +version = "0.55.0" dependencies = [ "clap", "cprover_bindings", @@ -1098,7 +1098,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "std" -version = "0.54.0" +version = "0.55.0" dependencies = [ "kani", ] diff --git a/Cargo.toml b/Cargo.toml index 149b8d2c93c8..ee9848b578dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-verifier" -version = "0.54.0" +version = "0.55.0" edition = "2021" description = "A bit-precise model checker for Rust." readme = "README.md" diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index b0e3c578bbcf..008c81aef2ad 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cprover_bindings" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index e8e564f9616a..9ca8d10f5275 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-compiler" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index 27fef66ffb65..7485d2279ad6 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-driver" -version = "0.54.0" +version = "0.55.0" edition = "2021" description = "Build a project with Kani and run all proof harnesses" license = "MIT OR Apache-2.0" diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index efa28288d148..18eadc4095ed 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_metadata" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index a3692f95e3f5..fa50783516f4 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_core/Cargo.toml b/library/kani_core/Cargo.toml index 9df828e77c5a..447cd0b3f298 100644 --- a/library/kani_core/Cargo.toml +++ b/library/kani_core/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_core" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 574960e5fc0a..a6b20a68bc39 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_macros" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 9f0d09b0d7bb..12c923e9b655 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -5,7 +5,7 @@ # Note: this package is intentionally named std to make sure the names of # standard library symbols are preserved name = "std" -version = "0.54.0" +version = "0.55.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/tools/build-kani/Cargo.toml b/tools/build-kani/Cargo.toml index cd2985e4ad68..41095f1d7c3c 100644 --- a/tools/build-kani/Cargo.toml +++ b/tools/build-kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "build-kani" -version = "0.54.0" +version = "0.55.0" edition = "2021" description = "Builds Kani, Sysroot and release bundle." license = "MIT OR Apache-2.0" From 701f6fb8e26acf0c31f053cc1503a4139b277e29 Mon Sep 17 00:00:00 2001 From: Qinheping Hu Date: Wed, 4 Sep 2024 16:54:24 -0500 Subject: [PATCH 061/159] RFC: Loop Contracts (#3167) RFC for loop contracts in Kani. Rendered version available [here](https://github.com/qinheping/kani/blob/rfc-loop-contracts/rfc/src/rfcs/0012-loop-contracts.md). 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 --- rfc/src/rfcs/0012-loop-contracts.md | 255 ++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 rfc/src/rfcs/0012-loop-contracts.md diff --git a/rfc/src/rfcs/0012-loop-contracts.md b/rfc/src/rfcs/0012-loop-contracts.md new file mode 100644 index 000000000000..1c0bcdafd750 --- /dev/null +++ b/rfc/src/rfcs/0012-loop-contracts.md @@ -0,0 +1,255 @@ +- **Feature Name:** Loop Contracts +- **Feature Request Issue:** [#3168](https://github.com/model-checking/kani/issues/3168) +- **RFC PR:** [#3167](https://github.com/model-checking/kani/pull/3167) +- **Status:** Under Review +- **Version:** 1 +- **Proof-of-concept:** + +------------------- + +## Summary + +Loop contracts provide way to safely abstract loops of a program, typically +in order to accelerate the verification process, and remove the loop unwinding +bounds. The key idea is to over-approximate the possible set of program states, +while still being precise enough to be able to prove the desired property. + +## User Impact + +Loop contracts provide an interface for a verified, sound abstraction. +The goal for specifying loop contracts in the source code is two fold: + +* Unbounded verification: Currently, proving correctness + (i.e. assertions never fail) on programs with unbounded control flow (e.g. + loops with dynamic bounds) Kani requires unwinding loops for a large number of + times, which is not always feasible. Loop contracts provide a way to abstract + out loops, and hence remove the need for unwinding loops. +* Faster CI runs: In most cases, the provided contracts would also significantly + improve Kani's verification time since all loops would be unrolled only to + a single iteration. + + + +Loop contracts are completely optional with no user impact if unused. This +RFC proposes the addition of new attributes, and functions, that shouldn't +interfere with existing functionalities. + + +## User Experience + +A loop contract specifies the behavior of a loop as a boolean predicate +(loop invariants clauses) with certain frames conditions (loop modifies clauses) +that can be checked against the loop implementation, and used to abstract out +the loop in the verification process. + +We illustrate the usage of loop contracts with an example. +Consider the following program: +```rs +fn simple_loop() { + let mut x: u64 = kani::any_where(|i| *i >= 1); + + while x > 1{ + x = x - 1; + }; + + assert!(x == 1); +} +``` +The loop in the `simple_loop` function keep subtracting 1 from `x` until `x` is 1. +However, Kani currently needs to unroll the loop for `u64::MAX` number of times +to verify the assertion at the end of the program. + +With loop contracts, the user can specify the behavior of the loop as follows: +```rs +fn simple_loop_with_loop_contracts() { + let mut x: u64 = kani::any_where(|i| *i >= 1); + + #[kani::loop_invariant(x >= 1)] + while x > 1{ + x = x - 1; + }; + + assert!(x == 1); +} +``` +The loop invariant clause `#[kani::loop_invariant(x >= 1)]` specifies the loop +invariants that must hold at the beginning of each iteration of the loop right before +checking the loop guard. + +In this case, Kani verifies that the loop invariant `x >= 1` is inductive, i.e., +`x` is always greater than or equal to 1 at each iteration before checking `x > 1`. + + +Also, once Kani proved that the loop invariant is inductive, it can safely use the loop +invariants to abstract the loop out of the verification process. +The idea is, instead of exploring all possible branches of the loop, Kani only needs to +prove those branches reached from an arbitrary program state that satisfies the loop contracts, +after the execution of one iteration of the loop. + +So, for loops without break statements, we can assume all post-states of the loop satisfying +`inv && !loop_guard` for proving post-loops properties. +The requirement of satisfying the negation of the loop guard comes from the fact that a path +exits loops without break statements must fail the loop guard. + +For example, applying loop contracts in `simple_loop` function is equivalent to the following: +```rs +fn simple_loop_transformed() { + let mut x: u64 = kani::any_where(|i| *i >= 1); + + x = kani::any(); // Arbitrary program state that + kani::assume( !(x > 1) && x >= 1); // satisfies !`guard` && `inv` + + assert!(x == 1); +} +``` +The assumption above is actually equivalent to `x == 1`, hence the assertion at the end +of the program is proved. + +### Write Sets and Havocking + +For those memory locations that are not modified in the loop, loop invariants state +that they stay unchanged throughout the loop are inductive. In other words, Kani should +only havoc the memory locations that are modified in the loop. This is achieved by +specifying the `modifies` clause for the loop. For example, the following program: +```rs +fn simple_loop_two_vars() { + let mut x: u64 = kani::any_where(|i| *i >= 1); + let mut y: u64 = 1; + + #[kani::loop_invariant(x >= 1)] + #[kani::loop_modifies(x)] + while x > 1{ + x = x - 1; + }; + + assert!(x == 1); + assert!(y == 1); +} +``` +write to only `x` in the loop, hence the `modifies` clause contains only `x`. +Then when use the loop contracts to abstract the loop, Kani will only havoc the memory +location `x` and keep `y` unchanged. Note that if the `modifies` clause contains also +`y`, Kani will havoc both `x` and `y`, and hence violate the assertion `y == 1`. + +Kani can employs CBMC's write set inference to infer the write set of the loop. +So users have to specify the `modifies` clauses by their self only when the inferred write +sets are not complete---there exists some target that could be written to in the loop but +is not in the inferred write set. + +### Proof of termination + +Loop contracts also provide a way to prove the termination of the loop. +Without the proof of termination, Kani could report success of some assertions that +are actually unreachable due to non-terminating loops. +For example, consider the following program: + +```rs +fn simple_loop_non_terminating() { + let mut x: u64 = kani::any_where(|i| *i >= 1); + + #[kani::loop_invariant(x >= 1)] + while true{ + x = x; + }; + + assert!(x >= 1); +} +``` +After abstracting the loop, the loop will be transformed to no-op, and the assertion +`x >= 1` will be proved. However, the loop is actually an infinite loop, and the +assertion will never be reached. + +For this reason, Kani will also require the user to provide a `decreases` clause that +specifies a decreasing expression to prove the termination of the loop. For example, in +```rs +fn simple_loop_terminating() { + let mut x: u64 = kani::any_where(|i| *i >= 1); + + #[kani::loop_invariant(x >= 1)] + #[kani::loop_decreases(x)] + while x > 1{ + x = x - 1; + }; + + assert!(x >= 1); +} +``` +, the `decreases` clause `#[kani::loop_decreases(x)]` specifies that the value of `x` +decreases at each iteration of the loop, and hence the loop will terminate. + + +## Detailed Design + + +Kani implements the functionality of loop contracts in three places. + +1. Procedural macros `loop_invariant`, `loop_modifies`, and `loop_decreases`. +2. Code generation for builtin functions expanded from the above macros. +3. GOTO-level loop contracts using CBMC's contract language generated in + `kani-compiler`. + +### Procedural macros `loop_invariant`, `loop_modifies`, and `loop_decreases`. + +We will implement the three proc-macros `loop_invariant`, `loop_modifies`, and `loop_decreases` to +embed the annotation logic as Rust code. Kani will then compile them into MIR-level code. + + +### Code Generation for Builtin Functions + +Then in the MIR, we codegen the loop contracts as GOTO-level expressions and annotate them +into the corresponding loop latches---the jumps back to the loop head. + +The artifact `goto-instrument` in CBMC will extract the loop contracts from the named-subs +of the loop latch, and then apply and prove the extracted loop contracts. + + +### GOTO-Level Havocing + +The ordinary havocing in CBMC is not aware of the type constraints of Rust type. +Hence, we will use customized havocing functions for modifies targets. In detail, +Kani will generate code for the definition of corresponding `kani::any()` functions +for each modifies target. Then Kani will create a map from the modifies target to the +the name of its `kani::any()` function, and add the map to the loop latch too. + +On the CBMC site, `goto-instrument` will extract the map and instrument the customized +havocing functions for the modifies targets. + +## Rationale and alternatives + + + +### Rust-Level Transformation vs CBMC + +Besides transforming the loops in GOTO level using `goto-instrument`, +we could also do the transformation in Rust level using procedural macros, or +in MIR level. + +There are two reasons we prefer the GOTO-level transformation. +First, `goto-instrument` is a mature tool that can correctly instrument the frame +condition checking for the transformed loop, which will save us from reinventing +the error-prone wheel. Second, the loop contracts synthesis tool we developed and +are developing are all based on GOTO level. Hence, doing the transformation in +the GOTO level will make the integration of loop contracts with the synthesis tool +easier. + +## Open questions + +- How do we integrate loop contracts with the synthesis tool? When the user-provided + loop contracts are not enough prove the harness, we expect the loop-contract synthesizer + can fix the loop contracts. +- How do we translate back modify targets that inferred by CBMC to Rust level? +- It is not clear how the CBMC loop modifies inference works for Rust code. We need to + experiment more to decide what would be the best UX of using loop modifies. +- How do we handle havocing in unsafe code where it is fine to break the safety invariant + of Rust? In that case, we may need havocing function that preserves validity invariant + but not safety invariant. +- What is the proper mechanism for users to specify the loops that they want to opt-out from applying loop contracts, and (optionally) the unwind numbers for them. Such options should be per-harness. + +## Future possibilities + +- We can employ CBMC's decreases inference to infer the decreases clauses to reduce the + user burden of specifying the decreases clauses. + + + +--- From e3a5e8ec23f40ed1cc7a700cc57630161449d728 Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:47:26 -0700 Subject: [PATCH 062/159] Replace `proc-macro-error` with `proc-macro-error2` (#3493) The following security advisory has been issued today regarding the `proc-macro-error` crate which is causing our CI to fail: https://rustsec.org/advisories/RUSTSEC-2024-0370 Replacing the crate with `proc-macro-error2` which is a fork of that crate. 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 | 46 +++++++++--------------- library/kani_macros/Cargo.toml | 2 +- library/kani_macros/src/derive.rs | 2 +- library/kani_macros/src/lib.rs | 4 +-- tests/ui/derive-arbitrary/union/expected | 5 +-- 5 files changed, 22 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c371b85fba9..560ad32c310c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,7 +176,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] @@ -483,7 +483,7 @@ dependencies = [ "shell-words", "strum", "strum_macros", - "syn 2.0.77", + "syn", "tracing", "tracing-subscriber", "tracing-tree", @@ -538,10 +538,10 @@ dependencies = [ name = "kani_macros" version = "0.55.0" dependencies = [ - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] @@ -801,27 +801,25 @@ dependencies = [ ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "74cdd32837fa2e86ec09c8266e5aad92400ac934c6dbca83d54673b298db3e45" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn", ] [[package]] @@ -1029,7 +1027,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] @@ -1136,17 +1134,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", + "syn", ] [[package]] @@ -1190,7 +1178,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] @@ -1287,7 +1275,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] @@ -1574,5 +1562,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index a6b20a68bc39..a1aeb743952b 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -13,7 +13,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" -proc-macro-error = "1.0.4" +proc-macro-error2 = "2.0.0" quote = "1.0.20" syn = { version = "2.0.18", features = ["full", "visit-mut", "visit", "extra-traits"] } diff --git a/library/kani_macros/src/derive.rs b/library/kani_macros/src/derive.rs index cc936560e510..e89f6f089795 100644 --- a/library/kani_macros/src/derive.rs +++ b/library/kani_macros/src/derive.rs @@ -10,7 +10,7 @@ //! //! ``` use proc_macro2::{Ident, Span, TokenStream}; -use proc_macro_error::abort; +use proc_macro_error2::abort; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::{ diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 63ed990a4840..8a1d2d80f864 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -12,7 +12,7 @@ mod derive; // proc_macro::quote is nightly-only, so we'll cobble things together instead use proc_macro::TokenStream; -use proc_macro_error::proc_macro_error; +use proc_macro_error2::proc_macro_error; #[cfg(kani_sysroot)] use sysroot as attr_impl; @@ -398,7 +398,7 @@ pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { /// This code should only be activated when pre-building Kani's sysroot. #[cfg(kani_sysroot)] mod sysroot { - use proc_macro_error::{abort, abort_call_site}; + use proc_macro_error2::{abort, abort_call_site}; mod contracts; diff --git a/tests/ui/derive-arbitrary/union/expected b/tests/ui/derive-arbitrary/union/expected index 3acea286d7ca..e54592a47ca6 100644 --- a/tests/ui/derive-arbitrary/union/expected +++ b/tests/ui/derive-arbitrary/union/expected @@ -3,10 +3,7 @@ error: Cannot derive `Arbitrary` for `Wrapper` union |\ | #[derive(kani::Arbitrary)]\ | ^^^^^^^^^^^^^^^\ -|\ +| note: `#[derive(Arbitrary)]` cannot be used for unions such as `Wrapper` -|\ -| union Wrapper {\ -| ^^^^^^^\ = note: this error originates in the derive macro `kani::Arbitrary` From 031587ab87879f005b946870eafbe03b7686210e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:49:35 -0700 Subject: [PATCH 063/159] Automatic toolchain upgrade to nightly-2024-09-04 (#3488) Update Rust toolchain from nightly-2024-09-03 to nightly-2024-09-04 without any other source changes. Co-authored-by: celinval <35149715+celinval@users.noreply.github.com> Co-authored-by: Zyad Hassan <88045115+zhassan-aws@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 a9bc7f1dd533..a8c4175df12a 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-09-03" +channel = "nightly-2024-09-04" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From faa47f7614b9d426f6de1a985a55c5abe1283619 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 06:27:55 -0700 Subject: [PATCH 064/159] Automatic toolchain upgrade to nightly-2024-09-05 (#3500) Update Rust toolchain from nightly-2024-09-04 to nightly-2024-09-05 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 a8c4175df12a..9880980d2d48 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-09-04" +channel = "nightly-2024-09-05" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 0775ca80099dac32a09216520969a444da66114d Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:41:39 -0700 Subject: [PATCH 065/159] Fix cmake configuration for perf job (#3499) Update the `cmake` configuration step used for building CBMC to include cadical. Resolves #3497 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/cbmc-latest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cbmc-latest.yml b/.github/workflows/cbmc-latest.yml index 696f08fd44b8..b71ad55ec130 100644 --- a/.github/workflows/cbmc-latest.yml +++ b/.github/workflows/cbmc-latest.yml @@ -87,7 +87,7 @@ jobs: - name: Build CBMC working-directory: ./cbmc run: | - cmake -S . -Bbuild -DWITH_JBMC=OFF + cmake -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" cmake --build build -- -j 4 # Prepend the bin directory to $PATH echo "${GITHUB_WORKSPACE}/cbmc/build/bin" >> $GITHUB_PATH From 2536f64bd79c371416cf27a7ab6729b8f76b38d6 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Fri, 6 Sep 2024 14:39:35 -0400 Subject: [PATCH 066/159] List RFC revisions (#3490) Addresses @jaisnan's feedback on list RFC--change `human` output to `pretty` and fix Kani version typo. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rfc/src/SUMMARY.md | 3 ++- rfc/src/rfcs/{0012-list.md => 0013-list.md} | 25 +++++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) rename rfc/src/rfcs/{0012-list.md => 0013-list.md} (86%) diff --git a/rfc/src/SUMMARY.md b/rfc/src/SUMMARY.md index c5a7161d0d45..2fb7d0b133cd 100644 --- a/rfc/src/SUMMARY.md +++ b/rfc/src/SUMMARY.md @@ -17,4 +17,5 @@ - [0009-function-contracts](rfcs/0009-function-contracts.md) - [0010-quantifiers](rfcs/0010-quantifiers.md) - [0011-source-coverage](rfcs/0011-source-coverage.md) -- [0012-list](rfcs/0012-list.md) +- [0012-loop-contracts](rfcs/0012-loop-contracts.md) +- [0013-list](rfcs/0013-list.md) diff --git a/rfc/src/rfcs/0012-list.md b/rfc/src/rfcs/0013-list.md similarity index 86% rename from rfc/src/rfcs/0012-list.md rename to rfc/src/rfcs/0013-list.md index fea4a65be7f3..bdbf4681f430 100644 --- a/rfc/src/rfcs/0012-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -2,7 +2,7 @@ - **Feature Request Issue:** [#2573](https://github.com/model-checking/kani/issues/2573), [#1612](https://github.com/model-checking/kani/issues/1612) - **RFC PR:** #3463 - **Status:** Under Review -- **Version:** 0 +- **Version:** 1 ------------------- @@ -20,18 +20,18 @@ This feature will not cause any regressions for exisiting users. ## User Experience -Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand will take the option `--message-format=[human|json]`, which changes the output format. The default is `human`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. +Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand will take the option `--message-format=[pretty|json]`, which changes the output format. The default is `pretty`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. This subcommand will not fail. In the case that it does not find any harnesses or contracts, it will print a message informing the user of that fact. -### Human Format +### Pretty Format -The default format, `human`, will print the harnesses and contracts in a project, along with the total counts of each. +The default format, `pretty`, will print the harnesses and contracts in a project, along with the total counts of each. For example: ``` -Kani Version: 0.5.4 +Kani Rust Verifier 0.54.0 (standalone) Standard Harnesses: - example::verify::check_new @@ -66,18 +66,22 @@ Totals: A "Standard Harness" is a `#[proof]` harness, while a "Contract Harness" is a `#[proof_for_contract]` harness. +All sections will be present in the output, regardless of the result. If a list is empty, Kani will output a `NONE` string. + ### JSON Format -As the name implies, the goal of the `human` output is to be friendly for human readers. If the user wants an output format that's more easily parsed by a script, they can use the `json` option. +If the user wants an output format that's more easily parsed by a script, they can use the `json` option. -The JSON format will contain the same information as the human format, with the addition of file paths and file version. The file version will start at zero and increment whenever we make an update to the format. This way, any users relying on this format for their scripts can realize that changes have occurred and update their logic accordingly. +The JSON format will contain the same information as the pretty format, with the addition of file paths and file version. +The file version will use semantic versioning. +This way, any users relying on this format for their scripts can detect when we've released a new major version and update their logic accordingly. For example: ```json { - kani-version: 0.5.4, - file-version: 0, + kani-version: 0.54, + file-version: 0.1, standard-harnesses: [ { file: /Users/johnsmith/example/kani_standard_proofs.rs @@ -132,6 +136,9 @@ For example: } ``` +All sections will be present in the output, regardless of the result. +If there is no result for a given field (e.g., there are no contracts), Kani will output an empty list (or zero for totals). + ## Software Design We will add a new subcommand to `kani-driver`. From 6f8ca94edf70eb1fd95716dbedef1e40e9bef701 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 6 Sep 2024 12:16:19 -0700 Subject: [PATCH 067/159] Enable stubbing and function contracts for primitive types (#3496) Previously, Kani was not able to resolve methods that belong to primitive types, which would not allow users to specify primitive types as stub target, neither for contracts. Thus, extend the name resolution to be able to convert a type expression into a `Ty` for primitive types, and add a new method to search for function implementation for primitive types. I also moved the type resolution logic to its own module to make it a bit cleaner. Resolves #2646 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- kani-compiler/src/kani_middle/resolve.rs | 267 +++++++++--------- .../kani_middle/resolve/type_resolution.rs | 202 +++++++++++++ .../fixme_stub_lowered_methods.rs | 26 ++ .../StubPrimitives/stub_bool_methods.rs | 35 +++ .../StubPrimitives/stub_char_methods.rs | 18 ++ .../StubPrimitives/stub_float_methods.rs | 32 +++ .../StubPrimitives/stub_int_methods.rs | 39 +++ .../StubPrimitives/stub_ptr_methods.rs | 38 +++ .../StubPrimitives/stub_slice_methods.rs | 37 +++ .../resolution_errors.expected | 36 +++ .../resolution_errors.rs | 23 ++ .../unsupported_resolutions.expected | 10 +- .../unsupported_resolutions.rs | 10 +- 13 files changed, 630 insertions(+), 143 deletions(-) create mode 100644 kani-compiler/src/kani_middle/resolve/type_resolution.rs create mode 100644 tests/kani/Stubbing/StubPrimitives/fixme_stub_lowered_methods.rs create mode 100644 tests/kani/Stubbing/StubPrimitives/stub_bool_methods.rs create mode 100644 tests/kani/Stubbing/StubPrimitives/stub_char_methods.rs create mode 100644 tests/kani/Stubbing/StubPrimitives/stub_float_methods.rs create mode 100644 tests/kani/Stubbing/StubPrimitives/stub_int_methods.rs create mode 100644 tests/kani/Stubbing/StubPrimitives/stub_ptr_methods.rs create mode 100644 tests/kani/Stubbing/StubPrimitives/stub_slice_methods.rs create mode 100644 tests/ui/function-stubbing-error/resolution_errors.expected create mode 100644 tests/ui/function-stubbing-error/resolution_errors.rs diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index 88b1af86efe9..b2b00c1e4a40 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -1,10 +1,11 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -//! This module contains code for resolving strings representing simple paths to -//! `DefId`s for functions and methods. For the definition of a simple path, see -//! . +// +//! This module contains code for resolving strings representing paths (simple and qualified) to +//! `DefId`s for functions and methods. For the definition of a path, see +//! . //! -//! TODO: Extend this logic to support resolving qualified paths. +//! TODO: Change `resolve_fn` in order to return information about trait implementations. //! //! //! Note that glob use statements can form loops. The paths can also walk through the loop. @@ -15,6 +16,7 @@ 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::fast_reject::{self, TreatParams}; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::ty::{FnDef, RigidTy, Ty, TyKind}; @@ -22,34 +24,10 @@ use stable_mir::CrateDef; use std::collections::HashSet; use std::fmt; use std::iter::Peekable; -use std::str::FromStr; -use strum_macros::{EnumString, IntoStaticStr}; -use syn::{Ident, PathSegment, Type, TypePath}; -use tracing::debug; - -#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString)] -#[strum(serialize_all = "lowercase")] -enum PrimitiveIdent { - Bool, - Char, - F16, - F32, - F64, - F128, - I8, - I16, - I32, - I64, - I128, - Isize, - Str, - U8, - U16, - U32, - U64, - U128, - Usize, -} +use syn::{PathSegment, QSelf, TypePath}; +use tracing::{debug, debug_span}; + +mod type_resolution; macro_rules! validate_kind { ($tcx:ident, $id:ident, $expected:literal, $kind:pat) => {{ @@ -61,6 +39,7 @@ macro_rules! validate_kind { } }}; } +pub(crate) use validate_kind; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FnResolution { @@ -76,20 +55,34 @@ pub fn resolve_fn_path<'tcx>( current_module: LocalDefId, path: &TypePath, ) -> Result> { - match (&path.qself, &path.path.leading_colon) { - (Some(qself), Some(_)) => { - // Qualified path that does not define a trait. - resolve_ty(tcx, current_module, &qself.ty)?; - Err(ResolveError::UnsupportedPath { kind: "qualified bare function paths" }) - } - (Some(qself), None) => { - let ty = resolve_ty(tcx, current_module, &qself.ty)?; + let _span = debug_span!("resolve_fn_path", ?path).entered(); + match &path.qself { + // Qualified path for a trait method implementation, like `::bar`. + Some(QSelf { ty: syn_ty, position, .. }) if *position > 0 => { + let ty = type_resolution::resolve_ty(tcx, current_module, &syn_ty)?; let def_id = resolve_path(tcx, current_module, &path.path)?; validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; Ok(FnResolution::FnImpl { def: stable_fn_def(tcx, def_id).unwrap(), ty }) } - (None, _) => { - // Simple path + // Qualified path for a primitive type, such as `<[u8]::sort>`. + Some(QSelf { ty: syn_ty, .. }) if type_resolution::is_type_primitive(syn_ty) => { + let ty = type_resolution::resolve_ty(tcx, current_module, &syn_ty)?; + let resolved = resolve_in_primitive(tcx, ty, path.path.segments.iter())?; + if resolved.segments.is_empty() { + Ok(FnResolution::Fn(stable_fn_def(tcx, resolved.base).unwrap())) + } else { + Err(ResolveError::UnexpectedType { tcx, item: resolved.base, expected: "module" }) + } + } + // Qualified path for a non-primitive type, such as `::foo>`. + Some(QSelf { ty: syn_ty, .. }) => { + let ty = type_resolution::resolve_ty(tcx, current_module, &syn_ty)?; + let def_id = resolve_in_user_type(tcx, ty, path.path.segments.iter())?; + validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; + Ok(FnResolution::Fn(stable_fn_def(tcx, def_id).unwrap())) + } + // Simple path + None => { let def_id = resolve_path(tcx, current_module, &path.path)?; validate_kind!(tcx, def_id, "function / method", DefKind::Fn | DefKind::AssocFn)?; Ok(FnResolution::Fn(stable_fn_def(tcx, def_id).unwrap())) @@ -97,14 +90,15 @@ pub fn resolve_fn_path<'tcx>( } } -/// Attempts to resolve a simple path (in the form of a string) to a function / method `DefId`. +/// Attempts to resolve a *simple path* (in the form of a string) to a function / method `DefId`. /// -/// Use `[resolve_fn_path]` if you want to handle qualified paths and simple paths +/// Use `[resolve_fn_path]` if you want to handle qualified paths and simple paths. pub fn resolve_fn<'tcx>( tcx: TyCtxt<'tcx>, current_module: LocalDefId, path_str: &str, ) -> Result> { + let _span = debug_span!("resolve_fn", ?path_str, ?current_module).entered(); let path = syn::parse_str(path_str).map_err(|err| ResolveError::InvalidPath { msg: format!("Expected a path, but found `{path_str}`. {err}"), })?; @@ -143,74 +137,6 @@ pub fn expect_resolve_fn( } } -/// Attempts to resolve a type. -pub fn resolve_ty<'tcx>( - tcx: TyCtxt<'tcx>, - current_module: LocalDefId, - typ: &syn::Type, -) -> Result> { - debug!(?typ, ?current_module, "resolve_ty"); - let unsupported = |kind: &'static str| Err(ResolveError::UnsupportedPath { kind }); - let invalid = |kind: &'static str| { - Err(ResolveError::InvalidPath { - msg: format!("Expected a type, but found {kind} `{}`", typ.to_token_stream()), - }) - }; - #[warn(non_exhaustive_omitted_patterns)] - match typ { - Type::Path(path) if path.qself.is_none() => { - let def_id = resolve_path(tcx, current_module, &path.path)?; - validate_kind!(tcx, def_id, "type", DefKind::Struct | DefKind::Union | DefKind::Enum)?; - Ok(rustc_internal::stable(tcx.type_of(def_id)).value) - } - Type::Path(_) => unsupported("qualified paths"), - Type::Array(_) - | Type::BareFn(_) - | Type::Macro(_) - | Type::Never(_) - | Type::Paren(_) - | Type::Ptr(_) - | Type::Reference(_) - | Type::Slice(_) - | Type::Tuple(_) => unsupported("path including primitive types"), - Type::Verbatim(_) => unsupported("unknown paths"), - Type::Group(_) => invalid("group paths"), - Type::ImplTrait(_) => invalid("trait impl paths"), - Type::Infer(_) => invalid("inferred paths"), - Type::TraitObject(_) => invalid("trait object paths"), - _ => { - unreachable!() - } - } -} - -/// Checks if a Path segment represents a primitive -fn is_primitive(ident: &Ident) -> bool { - let token = ident.to_string(); - let Ok(typ) = syn::parse_str(&token) else { return false }; - #[warn(non_exhaustive_omitted_patterns)] - match typ { - Type::Array(_) - | Type::Ptr(_) - | Type::Reference(_) - | Type::Slice(_) - | Type::Never(_) - | Type::Tuple(_) => true, - Type::Path(_) => PrimitiveIdent::from_str(&token).is_ok(), - Type::BareFn(_) - | Type::Group(_) - | Type::ImplTrait(_) - | Type::Infer(_) - | Type::Macro(_) - | Type::Paren(_) - | Type::TraitObject(_) - | Type::Verbatim(_) => false, - _ => { - unreachable!() - } - } -} - /// 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. @@ -220,15 +146,15 @@ fn resolve_path<'tcx>( path: &syn::Path, ) -> Result> { debug!(?path, "resolve_path"); - let _span = tracing::span!(tracing::Level::DEBUG, "path_resolution").entered(); - let path = resolve_prefix(tcx, current_module, path)?; path.segments.into_iter().try_fold(path.base, |base, segment| { let name = segment.ident.to_string(); let def_kind = tcx.def_kind(base); let next_item = match def_kind { DefKind::ForeignMod | DefKind::Mod => resolve_in_module(tcx, base, &name), - DefKind::Struct | DefKind::Enum | DefKind::Union => resolve_in_type(tcx, base, &name), + DefKind::Struct | DefKind::Enum | DefKind::Union => { + resolve_in_type_def(tcx, base, &name) + } DefKind::Trait => resolve_in_trait(tcx, base, &name), kind => { debug!(?base, ?kind, "resolve_path: unexpected item"); @@ -250,6 +176,8 @@ pub enum ResolveError<'tcx> { InvalidPath { msg: String }, /// Unable to find an item. MissingItem { tcx: TyCtxt<'tcx>, base: DefId, unresolved: String }, + /// Unable to find an item in a primitive type. + MissingPrimitiveItem { base: Ty, unresolved: String }, /// Error triggered when the identifier points to an item with unexpected type. UnexpectedType { tcx: TyCtxt<'tcx>, item: DefId, expected: &'static str }, /// Error triggered when the identifier is not currently supported. @@ -291,6 +219,9 @@ impl<'tcx> fmt::Display for ResolveError<'tcx> { let def_desc = description(*tcx, *base); write!(f, "unable to find `{unresolved}` inside {def_desc}") } + ResolveError::MissingPrimitiveItem { base, unresolved } => { + write!(f, "unable to find `{unresolved}` inside `{base}`") + } ResolveError::UnsupportedPath { kind } => { write!(f, "Kani currently cannot resolve {kind}") } @@ -334,6 +265,7 @@ fn resolve_prefix<'tcx>( // Resolve qualifiers `crate`, initial `::`, and `self`. The qualifier // `self` may be followed be `super` (handled below). match (path.leading_colon, segments.next()) { + // Leading `::` indicates that the path points to an item inside an external crate. (Some(_), Some(segment)) => { // Skip root and get the external crate from the name that follows `::`. let next_name = segment.ident.to_string(); @@ -348,9 +280,11 @@ fn resolve_prefix<'tcx>( }) } } + // Path with `::` alone is invalid. (Some(_), None) => { Err(ResolveError::InvalidPath { msg: "expected identifier after `::`".to_string() }) } + // Path starting with `crate::`. (None, Some(segment)) if segment.ident == CRATE => { // Find the module at the root of the crate. let current_module_hir_id = tcx.local_def_id_to_hir_id(current_module); @@ -360,14 +294,19 @@ fn resolve_prefix<'tcx>( }; Ok(Path { base: crate_root.to_def_id(), segments: segments.cloned().collect() }) } + // Path starting with "self::" (None, Some(segment)) if segment.ident == SELF => { resolve_super(tcx, current_module, segments.peekable()) } + // Path starting with "super::" (None, Some(segment)) if segment.ident == SUPER => { resolve_super(tcx, current_module, path.segments.iter().peekable()) } - (None, Some(segment)) if is_primitive(&segment.ident) => { - Err(ResolveError::UnsupportedPath { kind: "path including primitive types" }) + // Path starting with a primitive, such as "u8::" + (None, Some(segment)) if type_resolution::is_primitive(&segment) => { + let syn_ty = syn::parse2(segment.to_token_stream()).unwrap(); + let ty = type_resolution::resolve_ty(tcx, current_module, &syn_ty)?; + resolve_in_primitive(tcx, ty, segments) } (None, Some(segment)) => { // No special key word was used. Try local first otherwise try external name. @@ -561,8 +500,41 @@ fn resolve_in_glob_use(tcx: TyCtxt, res: &Res, name: &str) -> RelativeResolution } } -/// Resolves a function in a type. -fn resolve_in_type<'tcx>( +/// Resolves a function in a user type (non-primitive). +fn resolve_in_user_type<'tcx, 'a, I>( + tcx: TyCtxt<'tcx>, + ty: Ty, + mut segments: I, +) -> Result> +where + I: Iterator, +{ + let def_id = match ty.kind() { + TyKind::RigidTy(rigid_ty) => match rigid_ty { + RigidTy::Adt(def, _) => rustc_internal::internal(tcx, def.def_id()), + RigidTy::Foreign(_) => { + return Err(ResolveError::UnsupportedPath { kind: "foreign type" }); + } + _ => { + unreachable!("Unexpected type {ty}") + } + }, + TyKind::Alias(_, _) => return Err(ResolveError::UnsupportedPath { kind: "alias" }), + TyKind::Param(_) | TyKind::Bound(_, _) => { + // Name resolution can not resolve in a parameter or bound. + unreachable!() + } + }; + let Some(name) = segments.next() else { unreachable!() }; + if segments.next().is_some() { + Err(ResolveError::UnexpectedType { tcx, item: def_id, expected: "module" }) + } else { + resolve_in_type_def(tcx, def_id, &name.ident.to_string()) + } +} + +/// Resolves a function in a type given its `def_id`. +fn resolve_in_type_def<'tcx>( tcx: TyCtxt<'tcx>, type_id: DefId, name: &str, @@ -576,11 +548,7 @@ fn resolve_in_type<'tcx>( .iter() .flat_map(|impl_id| tcx.associated_item_def_ids(impl_id)) .cloned() - .find(|item| { - let item_path = tcx.def_path_str(*item); - let last = item_path.split("::").last().unwrap(); - last == name - }) + .find(|item| is_item_name(tcx, *item, name)) .ok_or_else(missing_item_err) } @@ -594,14 +562,53 @@ fn resolve_in_trait<'tcx>( let missing_item_err = || ResolveError::MissingItem { tcx, base: trait_id, unresolved: name.to_string() }; let trait_def = tcx.trait_def(trait_id); - // Try the inherent `impl` blocks (i.e., non-trait `impl`s). + // Look for the given name in the list of associated items for the trait definition. tcx.associated_item_def_ids(trait_def.def_id) .iter() .copied() - .find(|item| { - let item_path = tcx.def_path_str(*item); - let last = item_path.split("::").last().unwrap(); - last == name - }) + .find(|item| is_item_name(tcx, *item, name)) .ok_or_else(missing_item_err) } + +/// Resolves a primitive type function. +/// +/// This function assumes that `ty` is a primitive. +fn resolve_in_primitive<'tcx, 'a, I>( + tcx: TyCtxt<'tcx>, + ty: Ty, + mut segments: I, +) -> Result> +where + I: Iterator, +{ + if let Some(next) = segments.next() { + let name = next.ident.to_string(); + debug!(?name, ?ty, "resolve_in_primitive"); + let internal_ty = rustc_internal::internal(tcx, ty); + let simple_ty = + fast_reject::simplify_type(tcx, internal_ty, TreatParams::AsCandidateKey).unwrap(); + let impls = tcx.incoherent_impls(simple_ty).unwrap(); + // Find the primitive impl. + let item = impls + .iter() + .find_map(|item_impl| { + tcx.associated_item_def_ids(item_impl) + .iter() + .copied() + .find(|item| is_item_name(tcx, *item, &name)) + }) + .ok_or_else(|| ResolveError::MissingPrimitiveItem { + base: ty, + unresolved: name.to_string(), + })?; + Ok(Path { base: item, segments: segments.cloned().collect() }) + } else { + Err(ResolveError::InvalidPath { msg: format!("Unexpected primitive type `{ty}`") }) + } +} + +fn is_item_name(tcx: TyCtxt, item: DefId, name: &str) -> bool { + let item_path = tcx.def_path_str(item); + let last = item_path.split("::").last().unwrap(); + last == name +} diff --git a/kani-compiler/src/kani_middle/resolve/type_resolution.rs b/kani-compiler/src/kani_middle/resolve/type_resolution.rs new file mode 100644 index 000000000000..790019ab2998 --- /dev/null +++ b/kani-compiler/src/kani_middle/resolve/type_resolution.rs @@ -0,0 +1,202 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! This module contains code used for resolve type / trait names + +use crate::kani_middle::resolve::{resolve_path, validate_kind, ResolveError}; +use quote::ToTokens; +use rustc_hir::def::DefKind; +use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; +use rustc_span::def_id::LocalDefId; +use stable_mir::mir::Mutability; +use stable_mir::ty::{FloatTy, IntTy, Region, RegionKind, RigidTy, Ty, UintTy}; +use std::str::FromStr; +use strum_macros::{EnumString, IntoStaticStr}; +use syn::{Expr, ExprLit, Lit, Type, TypePath}; +use tracing::{debug, debug_span}; + +/// Attempts to resolve a type from a type expression. +pub fn resolve_ty<'tcx>( + tcx: TyCtxt<'tcx>, + current_module: LocalDefId, + typ: &syn::Type, +) -> Result> { + let _span = debug_span!("resolve_ty", ?typ).entered(); + debug!(?typ, ?current_module, "resolve_ty"); + let unsupported = |kind: &'static str| Err(ResolveError::UnsupportedPath { kind }); + let invalid = |kind: &'static str| { + Err(ResolveError::InvalidPath { + msg: format!("Expected a type, but found {kind} `{}`", typ.to_token_stream()), + }) + }; + #[warn(non_exhaustive_omitted_patterns)] + match typ { + Type::Path(TypePath { qself, path }) => { + assert_eq!(*qself, None, "Unexpected qualified path"); + if let Some(primitive) = + path.get_ident().and_then(|ident| PrimitiveIdent::from_str(&ident.to_string()).ok()) + { + Ok(primitive.into()) + } else { + let def_id = resolve_path(tcx, current_module, path)?; + validate_kind!( + tcx, + def_id, + "type", + DefKind::Struct | DefKind::Union | DefKind::Enum + )?; + Ok(rustc_internal::stable(tcx.type_of(def_id)).value) + } + } + Type::Array(array) => { + let elem_ty = resolve_ty(tcx, current_module, &array.elem)?; + let len = parse_len(&array.len).map_err(|msg| ResolveError::InvalidPath { msg })?; + Ty::try_new_array(elem_ty, len.try_into().unwrap()).map_err(|err| { + ResolveError::InvalidPath { msg: format!("Cannot instantiate array. {err}") } + }) + } + Type::Paren(inner) => resolve_ty(tcx, current_module, &inner.elem), + Type::Ptr(ptr) => { + let elem_ty = resolve_ty(tcx, current_module, &ptr.elem)?; + let mutability = + if ptr.mutability.is_some() { Mutability::Mut } else { Mutability::Not }; + Ok(Ty::new_ptr(elem_ty, mutability)) + } + Type::Reference(reference) => { + let elem_ty = resolve_ty(tcx, current_module, &reference.elem)?; + let mutability = + if reference.mutability.is_some() { Mutability::Mut } else { Mutability::Not }; + Ok(Ty::new_ref(Region { kind: RegionKind::ReErased }, elem_ty, mutability)) + } + Type::Slice(slice) => { + let elem_ty = resolve_ty(tcx, current_module, &slice.elem)?; + Ok(Ty::from_rigid_kind(RigidTy::Slice(elem_ty))) + } + Type::Tuple(tuple) => { + let elems = tuple + .elems + .iter() + .map(|elem| resolve_ty(tcx, current_module, &elem)) + .collect::, _>>()?; + Ok(Ty::new_tuple(&elems)) + } + Type::Never(_) => Ok(Ty::from_rigid_kind(RigidTy::Never)), + Type::BareFn(_) => unsupported("bare function"), + Type::Macro(_) => invalid("macro"), + Type::Group(_) => invalid("group paths"), + Type::ImplTrait(_) => invalid("trait impl paths"), + Type::Infer(_) => invalid("inferred paths"), + Type::TraitObject(_) => invalid("trait object paths"), + Type::Verbatim(_) => unsupported("unknown paths"), + _ => { + unreachable!() + } + } +} + +/// Enumeration of existing primitive types that are not parametric. +#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString)] +#[strum(serialize_all = "lowercase")] +pub(super) enum PrimitiveIdent { + Bool, + Char, + F16, + F32, + F64, + F128, + I8, + I16, + I32, + I64, + I128, + Isize, + Str, + U8, + U16, + U32, + U64, + U128, + Usize, +} + +/// Convert a primitive ident into a primitive `Ty`. +impl From for Ty { + fn from(value: PrimitiveIdent) -> Self { + match value { + PrimitiveIdent::Bool => Ty::bool_ty(), + PrimitiveIdent::Char => Ty::from_rigid_kind(RigidTy::Char), + PrimitiveIdent::F16 => Ty::from_rigid_kind(RigidTy::Float(FloatTy::F16)), + PrimitiveIdent::F32 => Ty::from_rigid_kind(RigidTy::Float(FloatTy::F32)), + PrimitiveIdent::F64 => Ty::from_rigid_kind(RigidTy::Float(FloatTy::F64)), + PrimitiveIdent::F128 => Ty::from_rigid_kind(RigidTy::Float(FloatTy::F128)), + PrimitiveIdent::I8 => Ty::signed_ty(IntTy::I8), + PrimitiveIdent::I16 => Ty::signed_ty(IntTy::I16), + PrimitiveIdent::I32 => Ty::signed_ty(IntTy::I32), + PrimitiveIdent::I64 => Ty::signed_ty(IntTy::I64), + PrimitiveIdent::I128 => Ty::signed_ty(IntTy::I128), + PrimitiveIdent::Isize => Ty::signed_ty(IntTy::Isize), + PrimitiveIdent::Str => Ty::from_rigid_kind(RigidTy::Str), + PrimitiveIdent::U8 => Ty::unsigned_ty(UintTy::U8), + PrimitiveIdent::U16 => Ty::unsigned_ty(UintTy::U16), + PrimitiveIdent::U32 => Ty::unsigned_ty(UintTy::U32), + PrimitiveIdent::U64 => Ty::unsigned_ty(UintTy::U64), + PrimitiveIdent::U128 => Ty::unsigned_ty(UintTy::U128), + PrimitiveIdent::Usize => Ty::unsigned_ty(UintTy::Usize), + } + } +} + +/// Checks if a Path segment represents a primitive. +/// +/// Note that this function will return false for expressions that cannot be parsed as a type. +pub(super) fn is_primitive(path: &T) -> bool +where + T: ToTokens, +{ + let token = path.to_token_stream(); + let Ok(typ) = syn::parse2(token) else { return false }; + is_type_primitive(&typ) +} + +/// Checks if a type is a primitive including composite ones. +pub(super) fn is_type_primitive(typ: &syn::Type) -> bool { + #[warn(non_exhaustive_omitted_patterns)] + match typ { + Type::Array(_) + | Type::Ptr(_) + | Type::Reference(_) + | Type::Slice(_) + | Type::Never(_) + | Type::Tuple(_) => true, + Type::Path(TypePath { qself: Some(qself), path }) => { + path.segments.is_empty() && is_type_primitive(&qself.ty) + } + Type::Path(TypePath { qself: None, path }) => path + .get_ident() + .map_or(false, |ident| PrimitiveIdent::from_str(&ident.to_string()).is_ok()), + Type::BareFn(_) + | Type::Group(_) + | Type::ImplTrait(_) + | Type::Infer(_) + | Type::Macro(_) + | Type::Paren(_) + | Type::TraitObject(_) + | Type::Verbatim(_) => false, + _ => { + unreachable!() + } + } +} + +/// Parse the length of the array. +/// We currently only support a constant length. +fn parse_len(len: &Expr) -> Result { + if let Expr::Lit(ExprLit { lit: Lit::Int(lit), .. }) = len { + if matches!(lit.suffix(), "" | "usize") + && let Ok(val) = usize::from_str(lit.base10_digits()) + { + return Ok(val); + } + } + Err(format!("Expected a `usize` constant, but found `{}`", len.to_token_stream())) +} diff --git a/tests/kani/Stubbing/StubPrimitives/fixme_stub_lowered_methods.rs b/tests/kani/Stubbing/StubPrimitives/fixme_stub_lowered_methods.rs new file mode 100644 index 000000000000..130a062071ba --- /dev/null +++ b/tests/kani/Stubbing/StubPrimitives/fixme_stub_lowered_methods.rs @@ -0,0 +1,26 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing + +//! Kani supports stubbing of most primitive methods, however, some methods, such as len, may be +//! lowered to an Rvalue. + +/// Check that we can stub slices methods +pub mod slices_check { + #[derive(kani::Arbitrary)] + pub struct MyStruct(u8, i32); + + pub fn stub_len_is_10(_: &[T]) -> usize { + 10 + } + + // This fails since `<[T]>::len` is lowered to `Rvalue::Len`. + #[kani::proof] + #[kani::stub(<[MyStruct]>::len, stub_len_is_10)] + pub fn check_stub_len_is_10() { + let input: [MyStruct; 5] = kani::any(); + let slice = kani::slice::any_slice_of_array(&input); + assert_eq!(slice.len(), 10); + } +} diff --git a/tests/kani/Stubbing/StubPrimitives/stub_bool_methods.rs b/tests/kani/Stubbing/StubPrimitives/stub_bool_methods.rs new file mode 100644 index 000000000000..44f926552c74 --- /dev/null +++ b/tests/kani/Stubbing/StubPrimitives/stub_bool_methods.rs @@ -0,0 +1,35 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we can correctly stub bool functions. + +pub fn stub_then_some_is_none(_: bool, _: T) -> Option { + None +} + +/// Check that we can stub `then_some`. +#[kani::proof] +#[kani::stub(bool::then_some, stub_then_some_is_none)] +pub fn check_stub_then_some() { + let input: bool = kani::any(); + assert_eq!(input.then_some("h"), None); +} + +pub fn stub_then_panic(_: bool, _: F) -> Option +where + F: FnOnce() -> T, +{ + panic!() +} + +/// Check that we can stub `then`. +#[kani::proof] +#[kani::should_panic] +#[kani::stub(bool::then, stub_then_panic)] +pub fn check_stub_then() { + let input: bool = kani::any(); + let output: char = kani::any(); + assert_eq!(input.then(|| output).unwrap_or(output), output); +} diff --git a/tests/kani/Stubbing/StubPrimitives/stub_char_methods.rs b/tests/kani/Stubbing/StubPrimitives/stub_char_methods.rs new file mode 100644 index 000000000000..a012208c7761 --- /dev/null +++ b/tests/kani/Stubbing/StubPrimitives/stub_char_methods.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we can correctly stub char functions. +/// Check that we can stub is_ascii from `char`. +pub fn stub_is_ascii_true(_: &char) -> bool { + true +} + +/// Check stubbing by directly calling `str::is_ascii` +#[kani::proof] +#[kani::stub(char::is_ascii, stub_is_ascii_true)] +pub fn check_stub_is_ascii() { + let input: char = kani::any(); + assert!(input.is_ascii()); +} diff --git a/tests/kani/Stubbing/StubPrimitives/stub_float_methods.rs b/tests/kani/Stubbing/StubPrimitives/stub_float_methods.rs new file mode 100644 index 000000000000..196c1be3053e --- /dev/null +++ b/tests/kani/Stubbing/StubPrimitives/stub_float_methods.rs @@ -0,0 +1,32 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we can correctly stub float functions. +#![feature(f128)] +#![feature(f16)] + +/// Generate stub and harness for floor method on floats. +macro_rules! stub_floor { + ($ty:ty, $harness:ident, $stub:ident) => { + // Stub that always returns 0. + pub fn $stub(_: $ty) -> $ty { + 0.0 + } + + // Harness + #[kani::proof] + #[kani::stub($ty::floor, $stub)] + pub fn $harness() { + let input = kani::any(); + let floor = <$ty>::floor(input); + assert_eq!(floor, 0.0); + } + }; +} + +stub_floor!(f16, f16_floor, stub_f16_floor); +stub_floor!(f32, f32_floor, stub_f32_floor); +stub_floor!(f64, f64_floor, stub_f64_floor); +stub_floor!(f128, f128_floor, stub_f128_floor); diff --git a/tests/kani/Stubbing/StubPrimitives/stub_int_methods.rs b/tests/kani/Stubbing/StubPrimitives/stub_int_methods.rs new file mode 100644 index 000000000000..bebbc47df245 --- /dev/null +++ b/tests/kani/Stubbing/StubPrimitives/stub_int_methods.rs @@ -0,0 +1,39 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we can correctly stub integer types functions. + +/// Generate stub and harness for count_ones method on integers. +macro_rules! stub_count_ones { + ($ty:ty, $harness:ident, $stub:ident) => { + // Stub that always returns 0. + pub fn $stub(_: $ty) -> u32 { + 0 + } + + // Harness + #[kani::proof] + #[kani::stub($ty::count_ones, $stub)] + pub fn $harness() { + let input = kani::any(); + let ones = <$ty>::count_ones(input); + assert_eq!(ones, 0); + } + }; +} + +stub_count_ones!(u8, u8_count_ones, stub_u8_count_ones); +stub_count_ones!(u16, u16_count_ones, stub_u16_count_ones); +stub_count_ones!(u32, u32_count_ones, stub_u32_count_ones); +stub_count_ones!(u64, u64_count_ones, stub_u64_count_ones); +stub_count_ones!(u128, u128_count_ones, stub_u128_count_ones); +stub_count_ones!(usize, usize_count_ones, stub_usize_count_ones); + +stub_count_ones!(i8, i8_count_ones, stub_i8_count_ones); +stub_count_ones!(i16, i16_count_ones, stub_i16_count_ones); +stub_count_ones!(i32, i32_count_ones, stub_i32_count_ones); +stub_count_ones!(i64, i64_count_ones, stub_i64_count_ones); +stub_count_ones!(i128, i128_count_ones, stub_i128_count_ones); +stub_count_ones!(isize, isize_count_ones, stub_isize_count_ones); diff --git a/tests/kani/Stubbing/StubPrimitives/stub_ptr_methods.rs b/tests/kani/Stubbing/StubPrimitives/stub_ptr_methods.rs new file mode 100644 index 000000000000..1bce08059d4d --- /dev/null +++ b/tests/kani/Stubbing/StubPrimitives/stub_ptr_methods.rs @@ -0,0 +1,38 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we can correctly stub methods from raw pointer types. + +pub fn stub_len_is_10(_: *const [T]) -> usize { + 10 +} + +pub fn stub_mut_len_is_0(_: *mut [T]) -> usize { + 0 +} + +#[kani::proof] +#[kani::stub(<*const [u8]>::len, stub_len_is_10)] +#[kani::stub(<*mut [u8]>::len, stub_mut_len_is_0)] +pub fn check_stub_len_raw_ptr() { + let mut input: [u8; 5] = kani::any(); + let mut_ptr = &mut input as *mut [u8]; + let ptr = &input as *const [u8]; + assert_eq!(mut_ptr.len(), 0); + assert_eq!(ptr.len(), 10); +} + +pub fn stub_is_always_null(_: *const T) -> bool { + true +} + +// Fix-me: Option doesn't seem to work without the fully qualified path. +#[kani::proof] +#[kani::stub(<*const std::option::Option>::is_null, stub_is_always_null)] +pub fn check_stub_is_null() { + let input: Option = kani::any(); + let ptr = &input as *const Option; + assert!(unsafe { ptr.is_null() }); +} diff --git a/tests/kani/Stubbing/StubPrimitives/stub_slice_methods.rs b/tests/kani/Stubbing/StubPrimitives/stub_slice_methods.rs new file mode 100644 index 000000000000..b7e4fafaa638 --- /dev/null +++ b/tests/kani/Stubbing/StubPrimitives/stub_slice_methods.rs @@ -0,0 +1,37 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we can correctly stub slices and string slices functions. + +/// Check that we can stub str::is_ascii +pub mod str_check { + pub fn stub_is_ascii_false(_: &str) -> bool { + false + } + + #[kani::proof] + #[kani::stub(str::is_ascii, stub_is_ascii_false)] + pub fn check_stub_is_ascii() { + let input = "is_ascii"; + assert!(!input.is_ascii()); + } +} + +/// Check that we can stub slices +pub mod slices_check { + #[derive(kani::Arbitrary, Ord, PartialOrd, Copy, Clone, PartialEq, Eq)] + pub struct MyStruct(u8, i32); + + pub fn stub_sort_noop(_: &mut [T]) {} + + #[kani::proof] + #[kani::stub(<[MyStruct]>::sort, stub_sort_noop)] + pub fn check_stub_sort_noop() { + let mut input: [MyStruct; 5] = kani::any(); + let copy = input.clone(); + input.sort(); + assert_eq!(input, copy); + } +} diff --git a/tests/ui/function-stubbing-error/resolution_errors.expected b/tests/ui/function-stubbing-error/resolution_errors.expected new file mode 100644 index 000000000000..d722f13bd691 --- /dev/null +++ b/tests/ui/function-stubbing-error/resolution_errors.expected @@ -0,0 +1,36 @@ +error: failed to resolve `<[char ; 10]>::foo`: unable to find `foo` inside `[char; 10]` +resolution_errors.rs +| +| #[kani::stub(<[char; 10]>::foo, stub_foo)] +| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: failed to resolve `str::foo`: unable to find `foo` inside `str` +resolution_errors.rs +| +| #[kani::stub(str::foo, stub_foo)] +| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: failed to resolve `<[u32]>::foo`: unable to find `foo` inside `[u32]` +resolution_errors.rs +| +| #[kani::stub(<[u32]>::foo, stub_foo)] +| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: failed to resolve `<(i32 , i32)>::foo`: unable to find `foo` inside `(i32, i32)` +resolution_errors.rs +| +| #[kani::stub(<(i32, i32)>::foo, stub_foo)] +| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: failed to resolve `u8::foo`: unable to find `foo` inside `u8` +resolution_errors.rs +| +| #[kani::stub(u8::foo, stub_foo)] +| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: failed to resolve `::foo`: unable to find `foo` inside struct `Bar` +resolution_errors.rs +| +| #[kani::stub(::foo, stub_foo)] +| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + diff --git a/tests/ui/function-stubbing-error/resolution_errors.rs b/tests/ui/function-stubbing-error/resolution_errors.rs new file mode 100644 index 000000000000..efb87efad08e --- /dev/null +++ b/tests/ui/function-stubbing-error/resolution_errors.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z stubbing +// +//! This tests that we emit a nice error message for resolution failures. + +/// Dummy structure +pub struct Bar; + +/// Dummy stub +pub fn stub_foo() -> bool { + true +} + +#[kani::proof] +#[kani::stub(::foo, stub_foo)] +#[kani::stub(u8::foo, stub_foo)] +#[kani::stub(<(i32, i32)>::foo, stub_foo)] +#[kani::stub(<[u32]>::foo, stub_foo)] +#[kani::stub(str::foo, stub_foo)] +#[kani::stub(<[char; 10]>::foo, stub_foo)] +fn invalid_methods() {} diff --git a/tests/ui/function-stubbing-error/unsupported_resolutions.expected b/tests/ui/function-stubbing-error/unsupported_resolutions.expected index 1b4c00d22f4f..e723ac97813d 100644 --- a/tests/ui/function-stubbing-error/unsupported_resolutions.expected +++ b/tests/ui/function-stubbing-error/unsupported_resolutions.expected @@ -1,8 +1,4 @@ -error: failed to resolve `<[char ; 10]>::foo`: Kani currently cannot resolve path including primitive types -error: failed to resolve `<& [u32]>::foo`: Kani currently cannot resolve path including primitive types -error: failed to resolve `<& [u32] as Foo>::foo`: Kani currently cannot resolve path including primitive types -error: failed to resolve `<(i32 , i32) as Foo>::foo`: Kani currently cannot resolve path including primitive types -error: failed to resolve `u8::foo`: Kani currently cannot resolve path including primitive types +error: Kani currently does not support stubbing trait implementations. error: failed to resolve `::bar`: unable to find `bar` inside trait `Foo` -error: Kani currently does not support stubbing trait implementations -error: failed to resolve `::foo`: Kani currently cannot resolve qualified bare function paths + +error: aborting due to 4 previous errors diff --git a/tests/ui/function-stubbing-error/unsupported_resolutions.rs b/tests/ui/function-stubbing-error/unsupported_resolutions.rs index 2fc74a0fd44f..0fcf102741eb 100644 --- a/tests/ui/function-stubbing-error/unsupported_resolutions.rs +++ b/tests/ui/function-stubbing-error/unsupported_resolutions.rs @@ -19,7 +19,7 @@ impl Foo for Bar {} impl Foo for u8 {} -impl Foo for &[T] {} +impl Foo for [T] {} impl Foo for [char; 10] {} @@ -30,13 +30,11 @@ pub fn stub_foo() -> bool { true } +/// We still do not support stubbing for trait methods. +/// #[kani::proof] -#[kani::stub(::foo, stub_foo)] #[kani::stub(::foo, stub_foo)] #[kani::stub(::bar, stub_foo)] -#[kani::stub(u8::foo, stub_foo)] #[kani::stub(<(i32, i32) as Foo>::foo, stub_foo)] -#[kani::stub(<&[u32] as Foo>::foo, stub_foo)] -#[kani::stub(<&[u32]>::foo, stub_foo)] -#[kani::stub(<[char; 10]>::foo, stub_foo)] +#[kani::stub(<[u32] as Foo>::foo, stub_foo)] fn unsupported_args() {} From fde5fbbf3cb981cb4e5e18e5c167a6518ca61415 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 23:42:12 -0700 Subject: [PATCH 068/159] Automatic toolchain upgrade to nightly-2024-09-06 (#3502) Update Rust toolchain from nightly-2024-09-05 to nightly-2024-09-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 9880980d2d48..c01df140e897 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-09-05" +channel = "nightly-2024-09-06" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 20eb00c750837bce7a11e1154a6532e283bd9706 Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Sat, 7 Sep 2024 09:14:52 -0700 Subject: [PATCH 069/159] Fix iss3495 (#3501) As suggested in https://github.com/GnomedDev/proc-macro-error-2/issues/1#issuecomment-2333926796, turning on the `nightly` feature for `proc-macro-error2` to restore the previous error message for `derive(Arbitrary)`. Resolves #3495 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- library/kani_macros/Cargo.toml | 2 +- tests/ui/derive-arbitrary/union/expected | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index a1aeb743952b..6930366b84fc 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -13,7 +13,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" -proc-macro-error2 = "2.0.0" +proc-macro-error2 = { version = "2.0.0", features = ["nightly"] } quote = "1.0.20" syn = { version = "2.0.18", features = ["full", "visit-mut", "visit", "extra-traits"] } diff --git a/tests/ui/derive-arbitrary/union/expected b/tests/ui/derive-arbitrary/union/expected index e54592a47ca6..3acea286d7ca 100644 --- a/tests/ui/derive-arbitrary/union/expected +++ b/tests/ui/derive-arbitrary/union/expected @@ -3,7 +3,10 @@ error: Cannot derive `Arbitrary` for `Wrapper` union |\ | #[derive(kani::Arbitrary)]\ | ^^^^^^^^^^^^^^^\ -| +|\ note: `#[derive(Arbitrary)]` cannot be used for unions such as `Wrapper` +|\ +| union Wrapper {\ +| ^^^^^^^\ = note: this error originates in the derive macro `kani::Arbitrary` From 7896cff0922cb051e261e0b7cacaf879cd612d96 Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:00:05 -0700 Subject: [PATCH 070/159] Upgrade toolchain to 2024-09-07 (#3504) Relevant upstream PR: https://github.com/rust-lang/rust/pull/128776: The `TreatParams` enum variants were renamed. Resolves #3503 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- kani-compiler/src/kani_middle/resolve.rs | 3 ++- rust-toolchain.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index b2b00c1e4a40..d2b1fcd06471 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -586,7 +586,8 @@ where debug!(?name, ?ty, "resolve_in_primitive"); let internal_ty = rustc_internal::internal(tcx, ty); let simple_ty = - fast_reject::simplify_type(tcx, internal_ty, TreatParams::AsCandidateKey).unwrap(); + fast_reject::simplify_type(tcx, internal_ty, TreatParams::InstantiateWithInfer) + .unwrap(); let impls = tcx.incoherent_impls(simple_ty).unwrap(); // Find the primitive impl. let item = impls diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c01df140e897..871f98b09046 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-09-06" +channel = "nightly-2024-09-07" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 92c25e67af50ad24ee205ec956c942821df5f1d8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:53:48 -0700 Subject: [PATCH 071/159] Automatic cargo update to 2024-09-09 (#3506) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 560ad32c310c..2e179767122f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" [[package]] name = "autocfg" @@ -147,9 +147,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -812,9 +812,9 @@ dependencies = [ [[package]] name = "proc-macro-error2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74cdd32837fa2e86ec09c8266e5aad92400ac934c6dbca83d54673b298db3e45" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", "proc-macro2", @@ -951,9 +951,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ "bitflags", "errno", @@ -1012,18 +1012,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -1032,9 +1032,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", From cda1b30c935c327a8d4ccba0add9f30b9612863f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:54:20 -0700 Subject: [PATCH 072/159] Automatic toolchain upgrade to nightly-2024-09-08 (#3505) Update Rust toolchain from nightly-2024-09-07 to nightly-2024-09-08 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 871f98b09046..a3febf5ae7db 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-09-07" +channel = "nightly-2024-09-08" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 80a4e51833a6fd8f3398adf109129f45f4431c15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:51:14 -0700 Subject: [PATCH 073/159] Bump tests/perf/s2n-quic from `1ff3a9c` to `d8be30e` (#3509) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `1ff3a9c` to `d8be30e`.
Commits

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 1ff3a9c8adee..d8be30efa9b9 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 1ff3a9c8adee30346537698c7b2a12279facd2d3 +Subproject commit d8be30efa9b9f0d639a08cd59b51c4e52a2a7b9b From 5f6de6ee3b42ffdc91e9414beb4c119edb81acf1 Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:01:24 -0700 Subject: [PATCH 074/159] Reduce object-bits for test to avoid OOM (#3511) Kani often runs out of memory on `tests/expected/function-contract/history/stub.rs` when running the regressions. In particular, `quadruple_harness` consumes over 9 GB of memory. This PR reduces the object bits for this test to 8 to avoid OOM issues. This brings down memory usage to ~125 MB. Before: ```bash $ /usr/bin/time -v kani -Zfunction-contracts stub.rs --harness quadruple_harness Maximum resident set size (kbytes): 9136036 ``` After: ```bash $ /usr/bin/time -v kani -Zfunction-contracts stub.rs --harness quadruple_harness --enable-unstable --cbmc-args --object-bits 8 ... Maximum resident set size (kbytes): 125172 ``` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- tests/expected/function-contract/history/stub.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/expected/function-contract/history/stub.rs b/tests/expected/function-contract/history/stub.rs index 77c590134f41..ce795bea6990 100644 --- a/tests/expected/function-contract/history/stub.rs +++ b/tests/expected/function-contract/history/stub.rs @@ -1,6 +1,8 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: -Zfunction-contracts +// This test consumes > 9 GB of memory with 16 object bits. Reducing the number +// of object bits to 8 to avoid running out of memory. +// kani-flags: -Zfunction-contracts --enable-unstable --cbmc-args --object-bits 8 #[kani::ensures(|result| old(*ptr + *ptr) == *ptr)] #[kani::requires(*ptr < 100)] From 76ad70111300bfca9abeff178732daa7841d6317 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:31:39 -0700 Subject: [PATCH 075/159] Bump peter-evans/create-pull-request from 6 to 7 (#3510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7.
Release notes

Sourced from peter-evans/create-pull-request's releases.

Create Pull Request v7.0.0

:sparkles: Now supports commit signing with bot-generated tokens! See "What's new" below. :writing_hand::robot:

Behaviour changes

  • Action input git-token has been renamed branch-token, to be more clear about its purpose. The branch-token is the token that the action will use to create and update the branch.
  • The action now handles requests that have been rate-limited by GitHub. Requests hitting a primary rate limit will retry twice, for a total of three attempts. Requests hitting a secondary rate limit will not be retried.
  • The pull-request-operation output now returns none when no operation was executed.
  • Removed deprecated output environment variable PULL_REQUEST_NUMBER. Please use the pull-request-number action output instead.

What's new

  • The action can now sign commits as github-actions[bot] when using GITHUB_TOKEN, or your own bot when using GitHub App tokens. See commit signing for details.
  • Action input draft now accepts a new value always-true. This will set the pull request to draft status when the pull request is updated, as well as on creation.
  • A new action input maintainer-can-modify indicates whether maintainers can modify the pull request. The default is true, which retains the existing behaviour of the action.
  • A new output pull-request-commits-verified returns true or false, indicating whether GitHub considers the signature of the branch's commits to be verified.

What's Changed

New Contributors

Full Changelog: https://github.com/peter-evans/create-pull-request/compare/v6.1.0...v7.0.0

Create Pull Request v6.1.0

✨ Adds pull-request-branch as an action output.

What's Changed

... (truncated)

Commits
  • 8867c4a fix: handle ambiguous argument failure on diff stat (#3312)
  • 6073f54 build(deps-dev): bump @​typescript-eslint/eslint-plugin (#3291)
  • 6d01b56 build(deps-dev): bump eslint-plugin-import from 2.29.1 to 2.30.0 (#3290)
  • 25cf845 build(deps-dev): bump @​typescript-eslint/parser from 7.17.0 to 7.18.0 (#3289)
  • d87b980 build(deps-dev): bump @​types/node from 18.19.46 to 18.19.48 (#3288)
  • 119d131 build(deps): bump peter-evans/create-pull-request from 6 to 7 (#3283)
  • 73e6230 docs: update readme
  • c0348e8 ci: add v7 to workflow
  • 4320041 feat: signed commits (v7) (#3057)
  • 0c2a66f build(deps-dev): bump ts-jest from 29.2.4 to 29.2.5 (#3256)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=peter-evans/create-pull-request&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) 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> --- .github/workflows/cargo-update.yml | 2 +- .github/workflows/cbmc-update.yml | 2 +- .github/workflows/toolchain-upgrade.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cargo-update.yml b/.github/workflows/cargo-update.yml index d8bd170d1821..402b8ce634e7 100644 --- a/.github/workflows/cargo-update.yml +++ b/.github/workflows/cargo-update.yml @@ -37,7 +37,7 @@ jobs: git diff fi - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: commit-message: Upgrade cargo dependencies to ${{ env.today }} branch: cargo-update-${{ env.today }} diff --git a/.github/workflows/cbmc-update.yml b/.github/workflows/cbmc-update.yml index 5fe8a866c0e4..7b63c0dae7de 100644 --- a/.github/workflows/cbmc-update.yml +++ b/.github/workflows/cbmc-update.yml @@ -63,7 +63,7 @@ jobs: - name: Create Pull Request if: ${{ env.next_step == 'create_pr' }} - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: commit-message: Upgrade CBMC from ${{ env.CBMC_VERSION }} to ${{ env.CBMC_LATEST }} branch: cbmc-${{ env.CBMC_LATEST }} diff --git a/.github/workflows/toolchain-upgrade.yml b/.github/workflows/toolchain-upgrade.yml index 76c3a5484414..8a7f448ca84e 100644 --- a/.github/workflows/toolchain-upgrade.yml +++ b/.github/workflows/toolchain-upgrade.yml @@ -38,7 +38,7 @@ jobs: - name: Create Pull Request id: create_pr if: ${{ env.next_step == 'create_pr' }} - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: commit-message: Upgrade Rust toolchain to nightly-${{ env.next_toolchain_date }} branch: toolchain-${{ env.next_toolchain_date }} From 31e1679a2111d021db96bf1bae34a33e867f827c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:30:12 +0200 Subject: [PATCH 076/159] Automatic cargo update to 2024-09-16 (#3515) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e179767122f..79f86f28e7a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "autocfg" @@ -720,9 +720,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" [[package]] name = "os_info" @@ -892,9 +892,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags", ] @@ -951,9 +951,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", @@ -1345,9 +1345,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-width" From 21cb34adb14cc8a8661bf26f051817cf4f142130 Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:49:10 -0700 Subject: [PATCH 077/159] Downgrade once_cell (#3517) Version 1.20.0 of the `once_cell` crate was yanked: https://crates.io/crates/once_cell/1.20.0 which is causing cargo deny in our CI to fail, e.g.: https://github.com/model-checking/kani/actions/runs/10888030526/job/30211573712?pr=3516 Downgrading the version to 1.19.0. 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79f86f28e7a9..63f86490e284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -720,9 +720,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "os_info" From d98ba4df57eb423b2073d1f34564724d4bcb9424 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:24:54 +0000 Subject: [PATCH 078/159] Bump tests/perf/s2n-quic from `d8be30e` to `132ba54` (#3516) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `d8be30e` to `132ba54`.
Commits

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> Co-authored-by: Michael Tautschnig --- 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 d8be30efa9b9..132ba54b6ff7 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit d8be30efa9b9f0d639a08cd59b51c4e52a2a7b9b +Subproject commit 132ba54b6ff7406204b866eb644594201d6be8d7 From 44b585e01c300c45acf102cfb2027ad855bb14e6 Mon Sep 17 00:00:00 2001 From: Qinheping Hu Date: Tue, 17 Sep 2024 02:22:57 -0500 Subject: [PATCH 079/159] Upgrade toolchain to 2024-09-09 (#3518) Relevant upstream PR: https://github.com/rust-lang/rust/pull/129313: Supress niches in coroutines to avoid aliasing violations #129313 Resolves https://github.com/model-checking/kani/issues/3512 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rust-toolchain.toml | 2 +- .../rustc-coroutine-tests/niche-in-generator-size.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a3febf5ae7db..9932620638ce 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-09-08" +channel = "nightly-2024-09-09" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] diff --git a/tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator-size.rs b/tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator-size.rs index 68b0a8589d3c..71a3eb9553c0 100644 --- a/tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator-size.rs +++ b/tests/kani/Coroutines/rustc-coroutine-tests/niche-in-generator-size.rs @@ -26,6 +26,7 @@ fn main() { take(x); }; - // FIXME: size of coroutines does not work reliably (https://github.com/model-checking/kani/issues/1395) - assert_eq!(size_of_val(&gen1), size_of_val(&Some(gen1))); + // FIXME(https://github.com/rust-lang/rust/issues/63818#issuecomment-2264915918): + // niches in coroutines are disabled. Should be `assert_eq`. + assert_ne!(size_of_val(&gen1), size_of_val(&Some(gen1))); } From 463f192db1b661b62230f7be42d8349756567220 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:08:14 +0200 Subject: [PATCH 080/159] Automatic toolchain upgrade to nightly-2024-09-10 (#3519) Update Rust toolchain from nightly-2024-09-09 to nightly-2024-09-10 without any other source changes. Co-authored-by: tautschnig <1144736+tautschnig@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 9932620638ce..c3c1d4e6125d 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-09-09" +channel = "nightly-2024-09-10" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From dba8f3926a61025f5078de787ebd8d21278333ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:14:33 -0700 Subject: [PATCH 081/159] Automatic toolchain upgrade to nightly-2024-09-11 (#3520) Update Rust toolchain from nightly-2024-09-10 to nightly-2024-09-11 without any other source changes. Co-authored-by: tautschnig <1144736+tautschnig@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 c3c1d4e6125d..1177a1ec6809 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-09-10" +channel = "nightly-2024-09-11" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From f8889139005e9463b52d4979ae59f05e7f36aadf Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:50:43 -0700 Subject: [PATCH 082/159] Instrument validity checks for pointer to reference casts for slices and str's (#3513) As pointed out in #3498, validity checks for pointer to reference casts (added in #3221) were not instrumented in the case of fat pointers (e.g. array and string slices). This PR extends the instrumentation of validity checks to handle those cases. Resolves #3498 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 --- .../codegen_cprover_gotoc/codegen/assert.rs | 73 ++++++++++--------- .../codegen/intrinsic.rs | 8 +- .../codegen_cprover_gotoc/codegen/rvalue.rs | 23 ++++-- .../ptr_to_ref_cast/alignment/expected | 4 + .../ptr_to_ref_cast/alignment/test.rs | 20 +++++ .../expected/ptr_to_ref_cast/invalid/expected | 8 ++ .../expected/ptr_to_ref_cast/invalid/test.rs | 58 +++++++++++++++ tests/expected/ptr_to_ref_cast/slice/expected | 6 ++ tests/expected/ptr_to_ref_cast/slice/test.rs | 36 +++++++++ tests/expected/ptr_to_ref_cast/str/expected | 5 ++ tests/expected/ptr_to_ref_cast/str/test.rs | 16 ++++ .../kani/Projection/slice_slice_projection.rs | 6 +- 12 files changed, 216 insertions(+), 47 deletions(-) create mode 100644 tests/expected/ptr_to_ref_cast/alignment/expected create mode 100644 tests/expected/ptr_to_ref_cast/alignment/test.rs create mode 100644 tests/expected/ptr_to_ref_cast/invalid/expected create mode 100644 tests/expected/ptr_to_ref_cast/invalid/test.rs create mode 100644 tests/expected/ptr_to_ref_cast/slice/expected create mode 100644 tests/expected/ptr_to_ref_cast/slice/test.rs create mode 100644 tests/expected/ptr_to_ref_cast/str/expected create mode 100644 tests/expected/ptr_to_ref_cast/str/test.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs index 72ccb95cf97b..c81d60e40d42 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs @@ -23,10 +23,12 @@ use cbmc::goto_program::{Expr, Location, Stmt, Type}; use cbmc::InternedString; use rustc_middle::mir::coverage::SourceRegion; use stable_mir::mir::{Place, ProjectionElem}; -use stable_mir::ty::{Span as SpanStable, TypeAndMut}; +use stable_mir::ty::{Span as SpanStable, Ty}; use strum_macros::{AsRefStr, EnumString}; use tracing::debug; +use super::intrinsic::SizeAlign; + /// Classifies the type of CBMC `assert`, as different assertions can have different semantics (e.g. cover) /// /// Each property class should justify its existence with a note about the special handling it recieves. @@ -333,8 +335,10 @@ impl<'tcx> GotocCtx<'tcx> { pub fn codegen_raw_ptr_deref_validity_check( &mut self, place: &Place, + place_ref: Expr, + place_ref_ty: Ty, loc: &Location, - ) -> Option { + ) -> Option<(Stmt, Stmt)> { if let Some(ProjectionElem::Deref) = place.projection.last() { // Create a place without the topmost dereference projection.ß let ptr_place = { @@ -346,41 +350,42 @@ impl<'tcx> GotocCtx<'tcx> { let ptr_place_ty = self.place_ty_stable(&ptr_place); if ptr_place_ty.kind().is_raw_ptr() { // Extract the size of the pointee. - let pointee_size = { - let TypeAndMut { ty: pointee_ty, .. } = - ptr_place_ty.kind().builtin_deref(true).unwrap(); - let pointee_ty_layout = pointee_ty.layout().unwrap(); - pointee_ty_layout.shape().size.bytes() + let SizeAlign { size: sz, align } = + self.size_and_align_of_dst(place_ref_ty, place_ref); + + // Encode __CPROVER_r_ok(ptr, size). + // First, generate a CBMC expression representing the pointer. + let ptr = { + let ptr_projection = self.codegen_place_stable(&ptr_place, *loc).unwrap(); + let place_ty = self.place_ty_stable(place); + if self.use_thin_pointer_stable(place_ty) { + ptr_projection.goto_expr().clone() + } else { + ptr_projection.goto_expr().clone().member("data", &self.symbol_table) + } }; + // Then generate an alignment check + let align_ok = + ptr.clone().cast_to(Type::size_t()).rem(align).eq(Type::size_t().zero()); + let align_check = self.codegen_assert_assume(align_ok, PropertyClass::SafetyCheck, + "misaligned pointer to reference cast: address must be a multiple of its type's \ + alignment", *loc); + // Then, generate a __CPROVER_r_ok check. + let raw_ptr_read_ok_expr = + Expr::read_ok(ptr.cast_to(Type::void_pointer()), sz.clone()) + .cast_to(Type::Bool); // __CPROVER_r_ok fails if size == 0, so need to explicitly avoid the check. - if pointee_size != 0 { - // Encode __CPROVER_r_ok(ptr, size). - // First, generate a CBMC expression representing the pointer. - let ptr = { - let ptr_projection = self.codegen_place_stable(&ptr_place, *loc).unwrap(); - let place_ty = self.place_ty_stable(place); - if self.use_thin_pointer_stable(place_ty) { - ptr_projection.goto_expr().clone() - } else { - ptr_projection.goto_expr().clone().member("data", &self.symbol_table) - } - }; - // Then, generate a __CPROVER_r_ok check. - let raw_ptr_read_ok_expr = Expr::read_ok( - ptr.cast_to(Type::void_pointer()), - Expr::int_constant(pointee_size, Type::size_t()), - ) - .cast_to(Type::Bool); - // Finally, assert that the pointer points to a valid memory location. - let raw_ptr_read_ok = self.codegen_assert( - raw_ptr_read_ok_expr, - PropertyClass::SafetyCheck, - "dereference failure: pointer invalid", - *loc, - ); - return Some(raw_ptr_read_ok); - } + let sz_typ = sz.typ().clone(); + let raw_ptr_read_ok_expr = sz.eq(sz_typ.zero()).or(raw_ptr_read_ok_expr); + // Finally, assert that the pointer points to a valid memory location. + let raw_ptr_read_ok = self.codegen_assert( + raw_ptr_read_ok_expr, + PropertyClass::SafetyCheck, + "dereference failure: pointer invalid", + *loc, + ); + return Some((align_check, raw_ptr_read_ok)); } } None diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 421d79e943c4..1b4c0174d5ed 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -18,9 +18,9 @@ use stable_mir::mir::{BasicBlockIdx, Operand, Place}; use stable_mir::ty::{GenericArgs, RigidTy, Span, Ty, TyKind, UintTy}; use tracing::debug; -struct SizeAlign { - size: Expr, - align: Expr, +pub struct SizeAlign { + pub size: Expr, + pub align: Expr, } enum VTableInfo { @@ -1291,7 +1291,7 @@ impl<'tcx> GotocCtx<'tcx> { /// This function computes the size and alignment of a dynamically-sized type. /// The implementations follows closely the SSA implementation found in /// `rustc_codegen_ssa::glue::size_and_align_of_dst`. - fn size_and_align_of_dst(&mut self, ty: Ty, arg: Expr) -> SizeAlign { + pub fn size_and_align_of_dst(&mut self, ty: Ty, arg: Expr) -> SizeAlign { let layout = self.layout_of_stable(ty); let usizet = Type::size_t(); if !layout.is_unsized() { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index e8f1424f09a3..7abd949357ff 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -738,12 +738,23 @@ impl<'tcx> GotocCtx<'tcx> { Rvalue::Ref(_, _, p) | Rvalue::AddressOf(_, p) => { let place_ref = self.codegen_place_ref_stable(&p, loc); 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, - ), + match self.codegen_raw_ptr_deref_validity_check( + &p, + place_ref.clone(), + self.place_ty_stable(p), + &loc, + ) { + Some((ptr_alignment_check_expr, ptr_validity_check_expr)) => { + Expr::statement_expression( + vec![ + ptr_alignment_check_expr, + ptr_validity_check_expr, + place_ref.as_stmt(loc), + ], + place_ref_type, + loc, + ) + } None => place_ref, } } diff --git a/tests/expected/ptr_to_ref_cast/alignment/expected b/tests/expected/ptr_to_ref_cast/alignment/expected new file mode 100644 index 000000000000..c7d8f5109e21 --- /dev/null +++ b/tests/expected/ptr_to_ref_cast/alignment/expected @@ -0,0 +1,4 @@ +check_misaligned_ptr_cast_fail.safety_check\ +Status: FAILURE\ +Description: "misaligned pointer to reference cast: address must be a multiple of its type's alignment"\ +in function check_misaligned_ptr_cast_fail diff --git a/tests/expected/ptr_to_ref_cast/alignment/test.rs b/tests/expected/ptr_to_ref_cast/alignment/test.rs new file mode 100644 index 000000000000..5e0a18a79da7 --- /dev/null +++ b/tests/expected/ptr_to_ref_cast/alignment/test.rs @@ -0,0 +1,20 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test checks that Kani detects UB resulting from converting a raw +//! pointer to a reference when the pointer is not properly aligned. + +#[repr(align(4))] +#[derive(Clone, Copy)] +struct AlignedI32(i32); + +#[kani::proof] +fn check_misaligned_ptr_cast_fail() { + let data = AlignedI32(42); + let ptr = &data as *const AlignedI32; + + unsafe { + let misaligned = ptr.byte_add(1); + let x = unsafe { &*misaligned }; + } +} diff --git a/tests/expected/ptr_to_ref_cast/invalid/expected b/tests/expected/ptr_to_ref_cast/invalid/expected new file mode 100644 index 000000000000..df4b8143d2aa --- /dev/null +++ b/tests/expected/ptr_to_ref_cast/invalid/expected @@ -0,0 +1,8 @@ +MyStr::new.safety_check\ +Status: FAILURE\ +Description: "dereference failure: pointer invalid"\ +in function MyStr::new + +Verification failed for - check_size_of_val +Verification failed for - check_slice_my_str +Complete - 1 successfully verified harnesses, 2 failures, 3 total. diff --git a/tests/expected/ptr_to_ref_cast/invalid/test.rs b/tests/expected/ptr_to_ref_cast/invalid/test.rs new file mode 100644 index 000000000000..50d2c8dd05e8 --- /dev/null +++ b/tests/expected/ptr_to_ref_cast/invalid/test.rs @@ -0,0 +1,58 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test case checks the usage of slices of slices (&[&[T]]). +use std::mem::size_of_val; + +/// Structure with a raw string (i.e.: [char]). +struct MyStr { + header: u16, + data: str, +} + +impl MyStr { + /// This creates a MyStr from a byte slice. + fn new(original: &mut String) -> &mut Self { + let buf = original.get_mut(..).unwrap(); + assert!(size_of_val(buf) > 2, "This requires at least 2 bytes"); + let unsized_len = buf.len() - 2; + let ptr = std::ptr::slice_from_raw_parts_mut(buf.as_mut_ptr(), unsized_len); + unsafe { &mut *(ptr as *mut Self) } + } +} + +#[kani::proof] +fn sanity_check_my_str() { + let mut buf = String::from("123456"); + let my_str = MyStr::new(&mut buf); + my_str.header = 0; + + assert_eq!(size_of_val(my_str), 6); + assert_eq!(my_str.data.len(), 4); + assert_eq!(my_str.data.chars().nth(0), Some('3')); + assert_eq!(my_str.data.chars().nth(3), Some('6')); +} + +#[kani::proof] +fn check_slice_my_str() { + let mut buf_0 = String::from("000"); + let mut buf_1 = String::from("001"); + let my_slice = &[MyStr::new(&mut buf_0), MyStr::new(&mut buf_1)]; + assert_eq!(my_slice.len(), 2); + + assert_eq!(my_slice[0].data.len(), 1); + assert_eq!(my_slice[1].data.len(), 1); + + assert_eq!(my_slice[0].data.chars().nth(0), Some('0')); + assert_eq!(my_slice[1].data.chars().nth(0), Some('1')); +} + +#[kani::proof] +fn check_size_of_val() { + let mut buf_0 = String::from("000"); + let mut buf_1 = String::from("001"); + let my_slice = &[MyStr::new(&mut buf_0), MyStr::new(&mut buf_1)]; + assert_eq!(size_of_val(my_slice), 32); // Slice of 2 fat pointers. + assert_eq!(size_of_val(my_slice[0]), 4); // Size of a fat pointer. + assert_eq!(size_of_val(&my_slice[0].data), 1); // Size of str. +} diff --git a/tests/expected/ptr_to_ref_cast/slice/expected b/tests/expected/ptr_to_ref_cast/slice/expected new file mode 100644 index 000000000000..8a8b39f8594e --- /dev/null +++ b/tests/expected/ptr_to_ref_cast/slice/expected @@ -0,0 +1,6 @@ +Status: FAILURE\ +Description: "dereference failure: pointer invalid"\ + +Verification failed for - check_with_byte_add_fail +Verification failed for - check_with_metadata_fail +Complete - 1 successfully verified harnesses, 2 failures, 3 total. diff --git a/tests/expected/ptr_to_ref_cast/slice/test.rs b/tests/expected/ptr_to_ref_cast/slice/test.rs new file mode 100644 index 000000000000..8526d0bf1c7f --- /dev/null +++ b/tests/expected/ptr_to_ref_cast/slice/test.rs @@ -0,0 +1,36 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +#![feature(set_ptr_value)] + +//! This test checks that Kani detects UB resulting from converting a raw +//! pointer to a reference when the metadata is not valid. + +// Generate invalid fat pointer by combining the metadata. +#[kani::proof] +fn check_with_metadata_fail() { + let short = [0u32; 2]; + let long = [0u32; 10]; + let ptr = &short as *const [u32]; + // This should trigger UB since the slice is not valid for the new length. + let fake_long = unsafe { &*ptr.with_metadata_of(&long) }; + assert_eq!(fake_long.len(), long.len()); +} + +#[kani::proof] +fn check_with_byte_add_fail() { + let data = [5u8; 5]; + let ptr = &data as *const [u8]; + // This should trigger UB since the metadata does not get adjusted. + let val = unsafe { &*ptr.byte_add(1) }; + assert_eq!(val.len(), data.len()); +} + +#[kani::proof] +fn check_with_byte_add_sub_pass() { + let data = [5u8; 5]; + let ptr = &data as *const [u8]; + let offset = kani::any_where(|i| *i < 100); + // This should pass since the resulting metadata is valid + let val = unsafe { &*ptr.byte_add(offset).byte_sub(offset) }; + assert_eq!(val.len(), data.len()); +} diff --git a/tests/expected/ptr_to_ref_cast/str/expected b/tests/expected/ptr_to_ref_cast/str/expected new file mode 100644 index 000000000000..e2de069eb399 --- /dev/null +++ b/tests/expected/ptr_to_ref_cast/str/expected @@ -0,0 +1,5 @@ +Status: FAILURE\ +Description: "dereference failure: pointer invalid"\ + +VERIFICATION:- FAILED +Verification failed for - check_with_metadata_fail diff --git a/tests/expected/ptr_to_ref_cast/str/test.rs b/tests/expected/ptr_to_ref_cast/str/test.rs new file mode 100644 index 000000000000..137034e34327 --- /dev/null +++ b/tests/expected/ptr_to_ref_cast/str/test.rs @@ -0,0 +1,16 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +#![feature(set_ptr_value)] + +//! This test checks that Kani detects UB resulting from converting a raw +//! pointer to a reference when the metadata is not valid. + +#[kani::proof] +fn check_with_metadata_fail() { + let short = "sh"; + let long = "longer"; + let ptr = short as *const str; + // This should trigger UB since the slice is not valid for the new length. + let fake_long = unsafe { &*ptr.with_metadata_of(long) }; + assert_eq!(fake_long.len(), long.len()); +} diff --git a/tests/kani/Projection/slice_slice_projection.rs b/tests/kani/Projection/slice_slice_projection.rs index 6e1cf42af4e7..6bfa62c2f6df 100644 --- a/tests/kani/Projection/slice_slice_projection.rs +++ b/tests/kani/Projection/slice_slice_projection.rs @@ -7,7 +7,8 @@ use std::mem::size_of_val; /// Structure with a raw string (i.e.: [char]). struct MyStr { - header: u16, + header_0: u8, + header_1: u8, data: str, } @@ -26,7 +27,6 @@ impl MyStr { fn sanity_check_my_str() { let mut buf = String::from("123456"); let my_str = MyStr::new(&mut buf); - my_str.header = 0; assert_eq!(size_of_val(my_str), 6); assert_eq!(my_str.data.len(), 4); @@ -54,6 +54,6 @@ fn check_size_of_val() { let mut buf_1 = String::from("001"); let my_slice = &[MyStr::new(&mut buf_0), MyStr::new(&mut buf_1)]; assert_eq!(size_of_val(my_slice), 32); // Slice of 2 fat pointers. - assert_eq!(size_of_val(my_slice[0]), 4); // Size of a fat pointer. + assert_eq!(size_of_val(my_slice[0]), 3); // Size of a fat pointer. assert_eq!(size_of_val(&my_slice[0].data), 1); // Size of str. } From d2051b77437a0032120f0513e0e9c3c4766d8562 Mon Sep 17 00:00:00 2001 From: Qinheping Hu Date: Wed, 18 Sep 2024 02:54:13 -0500 Subject: [PATCH 083/159] Upgrade toolchain to 2024-09-12 (#3524) Relevant upstream PR: https://github.com/rust-lang/rust/commit/d2309c2a9d Ban non-array SIMD Resolves https://github.com/model-checking/kani/issues/3521 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rust-toolchain.toml | 2 +- .../intrinsics/simd-arith-overflows/main.rs | 6 ++-- .../simd-cmp-result-type-is-diff-size/main.rs | 12 +++---- .../intrinsics/simd-div-div-zero/main.rs | 6 ++-- .../intrinsics/simd-div-rem-overflow/expected | 4 +-- .../intrinsics/simd-div-rem-overflow/main.rs | 14 ++++---- .../simd-extract-wrong-type/main.rs | 4 +-- .../intrinsics/simd-insert-wrong-type/main.rs | 4 +-- .../intrinsics/simd-rem-div-zero/main.rs | 6 ++-- .../simd-result-type-is-float/main.rs | 14 ++++---- .../simd-shl-shift-negative/main.rs | 6 ++-- .../simd-shl-shift-too-large/main.rs | 6 ++-- .../simd-shr-shift-negative/main.rs | 6 ++-- .../simd-shr-shift-too-large/main.rs | 6 ++-- .../simd-shuffle-indexes-out/main.rs | 6 ++-- .../main.rs | 8 ++--- .../main.rs | 8 ++--- tests/kani/Intrinsics/SIMD/Compare/float.rs | 34 +++++++++---------- .../SIMD/Compare/result_type_is_same_size.rs | 14 ++++---- tests/kani/Intrinsics/SIMD/Compare/signed.rs | 30 ++++++++-------- .../kani/Intrinsics/SIMD/Compare/unsigned.rs | 30 ++++++++-------- .../kani/Intrinsics/SIMD/Construction/main.rs | 16 ++++----- tests/kani/Intrinsics/SIMD/Operators/arith.rs | 10 +++--- .../Intrinsics/SIMD/Operators/bitshift.rs | 22 ++++++------ .../kani/Intrinsics/SIMD/Operators/bitwise.rs | 24 ++++++------- .../Intrinsics/SIMD/Operators/division.rs | 14 ++++---- .../SIMD/Operators/division_float.rs | 6 ++-- tests/kani/Intrinsics/SIMD/Shuffle/main.rs | 30 ++++++++-------- tests/kani/SIMD/generic_access.rs | 8 ++--- tests/kani/SIMD/multi_field_simd.rs | 12 +++---- 30 files changed, 184 insertions(+), 184 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 1177a1ec6809..934515867187 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-09-11" +channel = "nightly-2024-09-12" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] diff --git a/tests/expected/intrinsics/simd-arith-overflows/main.rs b/tests/expected/intrinsics/simd-arith-overflows/main.rs index fc710645fd66..28be8e309774 100644 --- a/tests/expected/intrinsics/simd-arith-overflows/main.rs +++ b/tests/expected/intrinsics/simd-arith-overflows/main.rs @@ -8,14 +8,14 @@ use std::intrinsics::simd::{simd_add, simd_mul, simd_sub}; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct i8x2(i8, i8); +pub struct i8x2([i8; 2]); #[kani::proof] fn main() { let a = kani::any(); let b = kani::any(); - let simd_a = i8x2(a, a); - let simd_b = i8x2(b, b); + let simd_a = i8x2([a, a]); + let simd_b = i8x2([b, b]); unsafe { let _ = simd_add(simd_a, simd_b); diff --git a/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/main.rs b/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/main.rs index f84cf1d8b9aa..fd4dd40b17b8 100644 --- a/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/main.rs +++ b/tests/expected/intrinsics/simd-cmp-result-type-is-diff-size/main.rs @@ -9,26 +9,26 @@ use std::intrinsics::simd::simd_eq; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u64x2(u64, u64); +pub struct u64x2([u64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u32x4(u32, u32, u32, u32); +pub struct u32x4([u32; 4]); #[kani::proof] fn main() { - let x = u64x2(0, 0); - let y = u64x2(0, 1); + let x = u64x2([0, 0]); + let y = u64x2([0, 1]); unsafe { let invalid_simd: u32x4 = simd_eq(x, y); - assert!(invalid_simd == u32x4(u32::MAX, u32::MAX, 0, 0)); + assert!(invalid_simd == u32x4([u32::MAX, u32::MAX, 0, 0])); // ^^^^ The code above fails to type-check in Rust with the error: // ``` // error[E0511]: invalid monomorphization of `simd_eq` intrinsic: expected diff --git a/tests/expected/intrinsics/simd-div-div-zero/main.rs b/tests/expected/intrinsics/simd-div-div-zero/main.rs index 148ae62a252c..0765e0fd0755 100644 --- a/tests/expected/intrinsics/simd-div-div-zero/main.rs +++ b/tests/expected/intrinsics/simd-div-div-zero/main.rs @@ -8,13 +8,13 @@ use std::intrinsics::simd::simd_div; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[kani::proof] fn test_simd_div() { let dividend = kani::any(); - let dividends = i32x2(dividend, dividend); + let dividends = i32x2([dividend, dividend]); let divisor = 0; - let divisors = i32x2(divisor, divisor); + let divisors = i32x2([divisor, divisor]); let _ = unsafe { simd_div(dividends, divisors) }; } diff --git a/tests/expected/intrinsics/simd-div-rem-overflow/expected b/tests/expected/intrinsics/simd-div-rem-overflow/expected index 156854c7dcdc..9c1a0aa96774 100644 --- a/tests/expected/intrinsics/simd-div-rem-overflow/expected +++ b/tests/expected/intrinsics/simd-div-rem-overflow/expected @@ -1,8 +1,8 @@ FAILURE\ attempt to compute simd_div which would overflow UNREACHABLE\ -assertion failed: quotients.0 == quotients.1 +assertion failed: quotients.0[0] == quotients.0[1] FAILURE\ attempt to compute simd_rem which would overflow UNREACHABLE\ -assertion failed: remainders.0 == remainders.1 \ No newline at end of file +assertion failed: remainders.0[0] == remainders.0[1] \ No newline at end of file diff --git a/tests/expected/intrinsics/simd-div-rem-overflow/main.rs b/tests/expected/intrinsics/simd-div-rem-overflow/main.rs index 5f49e7db6154..117fd6bffe95 100644 --- a/tests/expected/intrinsics/simd-div-rem-overflow/main.rs +++ b/tests/expected/intrinsics/simd-div-rem-overflow/main.rs @@ -8,7 +8,7 @@ use std::intrinsics::simd::{simd_div, simd_rem}; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); unsafe fn do_simd_div(dividends: i32x2, divisors: i32x2) -> i32x2 { simd_div(dividends, divisors) @@ -21,19 +21,19 @@ unsafe fn do_simd_rem(dividends: i32x2, divisors: i32x2) -> i32x2 { #[kani::proof] fn test_simd_div_overflow() { let dividend = i32::MIN; - let dividends = i32x2(dividend, dividend); + let dividends = i32x2([dividend, dividend]); let divisor = -1; - let divisors = i32x2(divisor, divisor); + let divisors = i32x2([divisor, divisor]); let quotients = unsafe { do_simd_div(dividends, divisors) }; - assert_eq!(quotients.0, quotients.1); + assert_eq!(quotients.0[0], quotients.0[1]); } #[kani::proof] fn test_simd_rem_overflow() { let dividend = i32::MIN; - let dividends = i32x2(dividend, dividend); + let dividends = i32x2([dividend, dividend]); let divisor = -1; - let divisors = i32x2(divisor, divisor); + let divisors = i32x2([divisor, divisor]); let remainders = unsafe { do_simd_rem(dividends, divisors) }; - assert_eq!(remainders.0, remainders.1); + assert_eq!(remainders.0[0], remainders.0[1]); } diff --git a/tests/expected/intrinsics/simd-extract-wrong-type/main.rs b/tests/expected/intrinsics/simd-extract-wrong-type/main.rs index b8fb5a3ffc6f..9af6b5279ec6 100644 --- a/tests/expected/intrinsics/simd-extract-wrong-type/main.rs +++ b/tests/expected/intrinsics/simd-extract-wrong-type/main.rs @@ -10,11 +10,11 @@ use std::intrinsics::simd::simd_extract; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[kani::proof] fn main() { - let y = i64x2(0, 1); + let y = i64x2([0, 1]); let res: i32 = unsafe { simd_extract(y, 1) }; // ^^^^ The code above fails to type-check in Rust with the error: // ``` diff --git a/tests/expected/intrinsics/simd-insert-wrong-type/main.rs b/tests/expected/intrinsics/simd-insert-wrong-type/main.rs index 6c4946a051b6..8a0853cc7945 100644 --- a/tests/expected/intrinsics/simd-insert-wrong-type/main.rs +++ b/tests/expected/intrinsics/simd-insert-wrong-type/main.rs @@ -10,11 +10,11 @@ use std::intrinsics::simd::simd_insert; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[kani::proof] fn main() { - let y = i64x2(0, 1); + let y = i64x2([0, 1]); let _ = unsafe { simd_insert(y, 0, 1) }; // ^^^^ The code above fails to type-check in Rust with the error: // ``` diff --git a/tests/expected/intrinsics/simd-rem-div-zero/main.rs b/tests/expected/intrinsics/simd-rem-div-zero/main.rs index 4393808ac039..7ed9bd2968cd 100644 --- a/tests/expected/intrinsics/simd-rem-div-zero/main.rs +++ b/tests/expected/intrinsics/simd-rem-div-zero/main.rs @@ -8,13 +8,13 @@ use std::intrinsics::simd::simd_rem; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[kani::proof] fn test_simd_rem() { let dividend = kani::any(); - let dividends = i32x2(dividend, dividend); + let dividends = i32x2([dividend, dividend]); let divisor = 0; - let divisors = i32x2(divisor, divisor); + let divisors = i32x2([divisor, divisor]); let _ = unsafe { simd_rem(dividends, divisors) }; } diff --git a/tests/expected/intrinsics/simd-result-type-is-float/main.rs b/tests/expected/intrinsics/simd-result-type-is-float/main.rs index 01286de9cdd8..2d7892a1a537 100644 --- a/tests/expected/intrinsics/simd-result-type-is-float/main.rs +++ b/tests/expected/intrinsics/simd-result-type-is-float/main.rs @@ -9,31 +9,31 @@ use std::intrinsics::simd::simd_eq; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u64x2(u64, u64); +pub struct u64x2([u64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u32x4(u32, u32, u32, u32); +pub struct u32x4([u32; 4]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq)] -pub struct f32x2(f32, f32); +pub struct f32x2([f32; 2]); #[kani::proof] fn main() { - let x = u64x2(0, 0); - let y = u64x2(0, 1); + let x = u64x2([0, 0]); + let y = u64x2([0, 1]); unsafe { let invalid_simd: f32x2 = simd_eq(x, y); - assert!(invalid_simd == f32x2(0.0, -1.0)); + assert!(invalid_simd == f32x2([0.0, -1.0])); // ^^^^ The code above fails to type-check in Rust with the error: // ``` // error[E0511]: invalid monomorphization of `simd_eq` intrinsic: expected return type with integer elements, found `f32x2` with non-integer `f32` diff --git a/tests/expected/intrinsics/simd-shl-shift-negative/main.rs b/tests/expected/intrinsics/simd-shl-shift-negative/main.rs index 2b7b2a418e19..f18a9ba14190 100644 --- a/tests/expected/intrinsics/simd-shl-shift-negative/main.rs +++ b/tests/expected/intrinsics/simd-shl-shift-negative/main.rs @@ -8,14 +8,14 @@ use std::intrinsics::simd::simd_shl; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[kani::proof] fn test_simd_shl() { let value = kani::any(); - let values = i32x2(value, value); + let values = i32x2([value, value]); let shift = kani::any(); kani::assume(shift < 32); - let shifts = i32x2(shift, shift); + let shifts = i32x2([shift, shift]); let _result = unsafe { simd_shl(values, shifts) }; } diff --git a/tests/expected/intrinsics/simd-shl-shift-too-large/main.rs b/tests/expected/intrinsics/simd-shl-shift-too-large/main.rs index dada7a5a8b84..75b73a5b81f1 100644 --- a/tests/expected/intrinsics/simd-shl-shift-too-large/main.rs +++ b/tests/expected/intrinsics/simd-shl-shift-too-large/main.rs @@ -8,14 +8,14 @@ use std::intrinsics::simd::simd_shl; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[kani::proof] fn test_simd_shl() { let value = kani::any(); - let values = i32x2(value, value); + let values = i32x2([value, value]); let shift = kani::any(); kani::assume(shift >= 0); - let shifts = i32x2(shift, shift); + let shifts = i32x2([shift, shift]); let _result = unsafe { simd_shl(values, shifts) }; } diff --git a/tests/expected/intrinsics/simd-shr-shift-negative/main.rs b/tests/expected/intrinsics/simd-shr-shift-negative/main.rs index dc38955099a2..f46a91b37c21 100644 --- a/tests/expected/intrinsics/simd-shr-shift-negative/main.rs +++ b/tests/expected/intrinsics/simd-shr-shift-negative/main.rs @@ -8,14 +8,14 @@ use std::intrinsics::simd::simd_shr; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[kani::proof] fn test_simd_shr() { let value = kani::any(); - let values = i32x2(value, value); + let values = i32x2([value, value]); let shift = kani::any(); kani::assume(shift < 32); - let shifts = i32x2(shift, shift); + let shifts = i32x2([shift, shift]); let _result = unsafe { simd_shr(values, shifts) }; } diff --git a/tests/expected/intrinsics/simd-shr-shift-too-large/main.rs b/tests/expected/intrinsics/simd-shr-shift-too-large/main.rs index 70ae0ad0da45..cdcfce0cf755 100644 --- a/tests/expected/intrinsics/simd-shr-shift-too-large/main.rs +++ b/tests/expected/intrinsics/simd-shr-shift-too-large/main.rs @@ -8,14 +8,14 @@ use std::intrinsics::simd::simd_shr; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[kani::proof] fn test_simd_shr() { let value = kani::any(); - let values = i32x2(value, value); + let values = i32x2([value, value]); let shift = kani::any(); kani::assume(shift >= 0); - let shifts = i32x2(shift, shift); + let shifts = i32x2([shift, shift]); let _result = unsafe { simd_shr(values, shifts) }; } diff --git a/tests/expected/intrinsics/simd-shuffle-indexes-out/main.rs b/tests/expected/intrinsics/simd-shuffle-indexes-out/main.rs index 0f7c42d2d46c..b89f40369853 100644 --- a/tests/expected/intrinsics/simd-shuffle-indexes-out/main.rs +++ b/tests/expected/intrinsics/simd-shuffle-indexes-out/main.rs @@ -9,12 +9,12 @@ use std::intrinsics::simd::simd_shuffle; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[kani::proof] fn main() { - let y = i64x2(0, 1); - let z = i64x2(1, 2); + let y = i64x2([0, 1]); + let z = i64x2([1, 2]); // Only [0, 3] are valid indexes, 4 is out of bounds const I: [u32; 2] = [1, 4]; let _: i64x2 = unsafe { simd_shuffle(y, z, I) }; diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs index 6345f5516101..3fe534f3bc08 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs @@ -9,17 +9,17 @@ use std::intrinsics::simd::simd_shuffle; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x4(i64, i64, i64, i64); +pub struct i64x4([i64; 4]); #[kani::proof] fn main() { - let y = i64x2(0, 1); - let z = i64x2(1, 2); + let y = i64x2([0, 1]); + let z = i64x2([1, 2]); const I: [u32; 4] = [1, 2, 1, 2]; let x: i64x2 = unsafe { simd_shuffle(y, z, I) }; // ^^^^ The code above fails to type-check in Rust with the error: diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs index 81f176700152..8c7cd5245cbe 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs @@ -9,17 +9,17 @@ use std::intrinsics::simd::simd_shuffle; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq)] -pub struct f64x2(f64, f64); +pub struct f64x2([f64; 2]); #[kani::proof] fn main() { - let y = i64x2(0, 1); - let z = i64x2(1, 2); + let y = i64x2([0, 1]); + let z = i64x2([1, 2]); const I: [u32; 2] = [1, 2]; let x: f64x2 = unsafe { simd_shuffle(y, z, I) }; // ^^^^ The code above fails to type-check in Rust with the error: diff --git a/tests/kani/Intrinsics/SIMD/Compare/float.rs b/tests/kani/Intrinsics/SIMD/Compare/float.rs index cc5765ef226b..6c69d142ff7d 100644 --- a/tests/kani/Intrinsics/SIMD/Compare/float.rs +++ b/tests/kani/Intrinsics/SIMD/Compare/float.rs @@ -8,17 +8,17 @@ use std::intrinsics::simd::*; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq)] -pub struct f64x2(f64, f64); +pub struct f64x2([f64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); macro_rules! assert_cmp { ($res_cmp: ident, $simd_cmp: ident, $x: expr, $y: expr, $($res: expr),+) => { @@ -29,21 +29,21 @@ macro_rules! assert_cmp { #[kani::proof] fn main() { - let x = f64x2(0.0, 0.0); - let y = f64x2(0.0, 1.0); + let x = f64x2([0.0, 0.0]); + let y = f64x2([0.0, 1.0]); unsafe { - assert_cmp!(res_eq, simd_eq, x, x, -1, -1); - assert_cmp!(res_eq, simd_eq, x, y, -1, 0); - assert_cmp!(res_ne, simd_ne, x, x, 0, 0); - assert_cmp!(res_ne, simd_ne, x, y, 0, -1); - assert_cmp!(res_lt, simd_lt, x, x, 0, 0); - assert_cmp!(res_lt, simd_lt, x, y, 0, -1); - assert_cmp!(res_le, simd_le, x, x, -1, -1); - assert_cmp!(res_le, simd_le, x, y, -1, -1); - assert_cmp!(res_gt, simd_gt, x, x, 0, 0); - assert_cmp!(res_gt, simd_gt, x, y, 0, 0); - assert_cmp!(res_ge, simd_ge, x, x, -1, -1); - assert_cmp!(res_ge, simd_ge, x, y, -1, 0); + assert_cmp!(res_eq, simd_eq, x, x, [-1, -1]); + assert_cmp!(res_eq, simd_eq, x, y, [-1, 0]); + assert_cmp!(res_ne, simd_ne, x, x, [0, 0]); + assert_cmp!(res_ne, simd_ne, x, y, [0, -1]); + assert_cmp!(res_lt, simd_lt, x, x, [0, 0]); + assert_cmp!(res_lt, simd_lt, x, y, [0, -1]); + assert_cmp!(res_le, simd_le, x, x, [-1, -1]); + assert_cmp!(res_le, simd_le, x, y, [-1, -1]); + assert_cmp!(res_gt, simd_gt, x, x, [0, 0]); + assert_cmp!(res_gt, simd_gt, x, y, [0, 0]); + assert_cmp!(res_ge, simd_ge, x, x, [-1, -1]); + assert_cmp!(res_ge, simd_ge, x, y, [-1, 0]); } } diff --git a/tests/kani/Intrinsics/SIMD/Compare/result_type_is_same_size.rs b/tests/kani/Intrinsics/SIMD/Compare/result_type_is_same_size.rs index d3582057fd00..584a5e07876a 100644 --- a/tests/kani/Intrinsics/SIMD/Compare/result_type_is_same_size.rs +++ b/tests/kani/Intrinsics/SIMD/Compare/result_type_is_same_size.rs @@ -9,28 +9,28 @@ use std::intrinsics::simd::simd_eq; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u64x2(u64, u64); +pub struct u64x2([u64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u32x2(u32, u32); +pub struct u32x2([u32; 2]); #[kani::proof] fn main() { - let x = u64x2(0, 0); - let y = u64x2(0, 1); + let x = u64x2([0, 0]); + let y = u64x2([0, 1]); unsafe { let w: i64x2 = simd_eq(x, y); - assert!(w == i64x2(-1, 0)); + assert!(w == i64x2([-1, 0])); let z: u32x2 = simd_eq(x, y); - assert!(z == u32x2(u32::MAX, 0)); + assert!(z == u32x2([u32::MAX, 0])); } } diff --git a/tests/kani/Intrinsics/SIMD/Compare/signed.rs b/tests/kani/Intrinsics/SIMD/Compare/signed.rs index cfd781fa64c7..671078bad2d4 100644 --- a/tests/kani/Intrinsics/SIMD/Compare/signed.rs +++ b/tests/kani/Intrinsics/SIMD/Compare/signed.rs @@ -8,7 +8,7 @@ use std::intrinsics::simd::*; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); macro_rules! assert_cmp { ($res_cmp: ident, $simd_cmp: ident, $x: expr, $y: expr, $($res: expr),+) => { @@ -23,21 +23,21 @@ macro_rules! assert_cmp { // * No bits set (e.g., 0 in signed integers) if the result is true #[kani::proof] fn main() { - let x = i64x2(0, 0); - let y = i64x2(0, 1); + let x = i64x2([0, 0]); + let y = i64x2([0, 1]); unsafe { - assert_cmp!(res_eq, simd_eq, x, x, -1, -1); - assert_cmp!(res_eq, simd_eq, x, y, -1, 0); - assert_cmp!(res_ne, simd_ne, x, x, 0, 0); - assert_cmp!(res_ne, simd_ne, x, y, 0, -1); - assert_cmp!(res_lt, simd_lt, x, x, 0, 0); - assert_cmp!(res_lt, simd_lt, x, y, 0, -1); - assert_cmp!(res_le, simd_le, x, x, -1, -1); - assert_cmp!(res_le, simd_le, x, y, -1, -1); - assert_cmp!(res_gt, simd_gt, x, x, 0, 0); - assert_cmp!(res_gt, simd_gt, x, y, 0, 0); - assert_cmp!(res_ge, simd_ge, x, x, -1, -1); - assert_cmp!(res_ge, simd_ge, x, y, -1, 0); + assert_cmp!(res_eq, simd_eq, x, x, [-1, -1]); + assert_cmp!(res_eq, simd_eq, x, y, [-1, 0]); + assert_cmp!(res_ne, simd_ne, x, x, [0, 0]); + assert_cmp!(res_ne, simd_ne, x, y, [0, -1]); + assert_cmp!(res_lt, simd_lt, x, x, [0, 0]); + assert_cmp!(res_lt, simd_lt, x, y, [0, -1]); + assert_cmp!(res_le, simd_le, x, x, [-1, -1]); + assert_cmp!(res_le, simd_le, x, y, [-1, -1]); + assert_cmp!(res_gt, simd_gt, x, x, [0, 0]); + assert_cmp!(res_gt, simd_gt, x, y, [0, 0]); + assert_cmp!(res_ge, simd_ge, x, x, [-1, -1]); + assert_cmp!(res_ge, simd_ge, x, y, [-1, 0]); } } diff --git a/tests/kani/Intrinsics/SIMD/Compare/unsigned.rs b/tests/kani/Intrinsics/SIMD/Compare/unsigned.rs index ee39f750c8a2..b2943b36ab71 100644 --- a/tests/kani/Intrinsics/SIMD/Compare/unsigned.rs +++ b/tests/kani/Intrinsics/SIMD/Compare/unsigned.rs @@ -8,7 +8,7 @@ use std::intrinsics::simd::*; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u64x2(u64, u64); +pub struct u64x2([u64; 2]); macro_rules! assert_cmp { ($res_cmp: ident, $simd_cmp: ident, $x: expr, $y: expr, $($res: expr),+) => { @@ -23,21 +23,21 @@ macro_rules! assert_cmp { // * No bits set (e.g., 0 in signed integers) if the result is true #[kani::proof] fn main() { - let x = u64x2(0, 0); - let y = u64x2(0, 1); + let x = u64x2([0, 0]); + let y = u64x2([0, 1]); unsafe { - assert_cmp!(res_eq, simd_eq, x, x, u64::MAX, u64::MAX); - assert_cmp!(res_eq, simd_eq, x, y, u64::MAX, 0); - assert_cmp!(res_ne, simd_ne, x, x, 0, 0); - assert_cmp!(res_ne, simd_ne, x, y, 0, u64::MAX); - assert_cmp!(res_lt, simd_lt, x, x, 0, 0); - assert_cmp!(res_lt, simd_lt, x, y, 0, u64::MAX); - assert_cmp!(res_le, simd_le, x, x, u64::MAX, u64::MAX); - assert_cmp!(res_le, simd_le, x, y, u64::MAX, u64::MAX); - assert_cmp!(res_gt, simd_gt, x, x, 0, 0); - assert_cmp!(res_gt, simd_gt, x, y, 0, 0); - assert_cmp!(res_ge, simd_ge, x, x, u64::MAX, u64::MAX); - assert_cmp!(res_ge, simd_ge, x, y, u64::MAX, 0); + assert_cmp!(res_eq, simd_eq, x, x, [u64::MAX, u64::MAX]); + assert_cmp!(res_eq, simd_eq, x, y, [u64::MAX, 0]); + assert_cmp!(res_ne, simd_ne, x, x, [0, 0]); + assert_cmp!(res_ne, simd_ne, x, y, [0, u64::MAX]); + assert_cmp!(res_lt, simd_lt, x, x, [0, 0]); + assert_cmp!(res_lt, simd_lt, x, y, [0, u64::MAX]); + assert_cmp!(res_le, simd_le, x, x, [u64::MAX, u64::MAX]); + assert_cmp!(res_le, simd_le, x, y, [u64::MAX, u64::MAX]); + assert_cmp!(res_gt, simd_gt, x, x, [0, 0]); + assert_cmp!(res_gt, simd_gt, x, y, [0, 0]); + assert_cmp!(res_ge, simd_ge, x, x, [u64::MAX, u64::MAX]); + assert_cmp!(res_ge, simd_ge, x, y, [u64::MAX, 0]); } } diff --git a/tests/kani/Intrinsics/SIMD/Construction/main.rs b/tests/kani/Intrinsics/SIMD/Construction/main.rs index 5de3ae20d868..92c5a167dc15 100644 --- a/tests/kani/Intrinsics/SIMD/Construction/main.rs +++ b/tests/kani/Intrinsics/SIMD/Construction/main.rs @@ -9,16 +9,16 @@ use std::intrinsics::simd::{simd_extract, simd_insert}; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[kani::proof] fn main() { - let y = i64x2(0, 1); - let z = i64x2(1, 2); + let y = i64x2([0, 1]); + let z = i64x2([1, 2]); // Indexing into the vectors - assert!(z.0 == 1); - assert!(z.1 == 2); + assert!(z.0[0] == 1); + assert!(z.0[1] == 2); { // Intrinsic indexing @@ -31,9 +31,9 @@ fn main() { // Intrinsic updating let m = unsafe { simd_insert(y, 0, 1_i64) }; let n = unsafe { simd_insert(y, 1, 5_i64) }; - assert!(m.0 == 1 && m.1 == 1); - assert!(n.0 == 0 && n.1 == 5); + assert!(m.0[0] == 1 && m.0[1] == 1); + assert!(n.0[0] == 0 && n.0[1] == 5); // Original unchanged - assert!(y.0 == 0 && y.1 == 1); + assert!(y.0[0] == 0 && y.0[1] == 1); } } diff --git a/tests/kani/Intrinsics/SIMD/Operators/arith.rs b/tests/kani/Intrinsics/SIMD/Operators/arith.rs index d9f442a659ba..33a8ba65b538 100644 --- a/tests/kani/Intrinsics/SIMD/Operators/arith.rs +++ b/tests/kani/Intrinsics/SIMD/Operators/arith.rs @@ -9,7 +9,7 @@ use std::intrinsics::simd::{simd_add, simd_mul, simd_sub}; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i8x2(i8, i8); +pub struct i8x2([i8; 2]); macro_rules! verify_no_overflow { ($cf: ident, $uf: ident) => {{ @@ -17,11 +17,11 @@ macro_rules! verify_no_overflow { let b: i8 = kani::any(); let checked = a.$cf(b); kani::assume(checked.is_some()); - let simd_a = i8x2(a, a); - let simd_b = i8x2(b, b); + let simd_a = i8x2([a, a]); + let simd_b = i8x2([b, b]); let unchecked: i8x2 = unsafe { $uf(simd_a, simd_b) }; - assert!(checked.unwrap() == unchecked.0); - assert!(checked.unwrap() == unchecked.1); + assert!(checked.unwrap() == unchecked.0[0]); + assert!(checked.unwrap() == unchecked.0[1]); }}; } diff --git a/tests/kani/Intrinsics/SIMD/Operators/bitshift.rs b/tests/kani/Intrinsics/SIMD/Operators/bitshift.rs index fa2cd3c52cd6..09a19fa7b29a 100644 --- a/tests/kani/Intrinsics/SIMD/Operators/bitshift.rs +++ b/tests/kani/Intrinsics/SIMD/Operators/bitshift.rs @@ -9,47 +9,47 @@ use std::intrinsics::simd::{simd_shl, simd_shr}; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u32x2(u32, u32); +pub struct u32x2([u32; 2]); #[kani::proof] fn test_simd_shl() { let value = kani::any(); - let values = i32x2(value, value); + let values = i32x2([value, value]); let shift = kani::any(); kani::assume(shift >= 0); kani::assume(shift < 32); - let shifts = i32x2(shift, shift); + let shifts = i32x2([shift, shift]); let normal_result = value << shift; let simd_result = unsafe { simd_shl(values, shifts) }; - assert_eq!(normal_result, simd_result.0); + assert_eq!(normal_result, simd_result.0[0]); } #[kani::proof] fn test_simd_shr_signed() { let value = kani::any(); - let values = i32x2(value, value); + let values = i32x2([value, value]); let shift = kani::any(); kani::assume(shift >= 0); kani::assume(shift < 32); - let shifts = i32x2(shift, shift); + let shifts = i32x2([shift, shift]); let normal_result = value >> shift; let simd_result = unsafe { simd_shr(values, shifts) }; - assert_eq!(normal_result, simd_result.0); + assert_eq!(normal_result, simd_result.0[0]); } #[kani::proof] fn test_simd_shr_unsigned() { let value = kani::any(); - let values = u32x2(value, value); + let values = u32x2([value, value]); let shift = kani::any(); kani::assume(shift < 32); - let shifts = u32x2(shift, shift); + let shifts = u32x2([shift, shift]); let normal_result = value >> shift; let simd_result = unsafe { simd_shr(values, shifts) }; - assert_eq!(normal_result, simd_result.0); + assert_eq!(normal_result, simd_result.0[0]); } diff --git a/tests/kani/Intrinsics/SIMD/Operators/bitwise.rs b/tests/kani/Intrinsics/SIMD/Operators/bitwise.rs index b18410088f18..a9aaa96f75e5 100644 --- a/tests/kani/Intrinsics/SIMD/Operators/bitwise.rs +++ b/tests/kani/Intrinsics/SIMD/Operators/bitwise.rs @@ -14,52 +14,52 @@ use std::intrinsics::simd::{simd_and, simd_or, simd_xor}; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i8x2(i8, i8); +pub struct i8x2([i8; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i16x2(i16, i16); +pub struct i16x2([i16; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u8x2(u8, u8); +pub struct u8x2([u8; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u16x2(u16, u16); +pub struct u16x2([u16; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u32x2(u32, u32); +pub struct u32x2([u32; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct u64x2(u64, u64); +pub struct u64x2([u64; 2]); macro_rules! compare_simd_op_with_normal_op { ($simd_op: ident, $normal_op: tt, $simd_type: ident) => { let tup_x: (_,_) = kani::any(); let tup_y: (_,_) = kani::any(); - let x = $simd_type(tup_x.0, tup_x.1); - let y = $simd_type(tup_y.0, tup_y.1); + let x = $simd_type([tup_x.0, tup_x.1]); + let y = $simd_type([tup_y.0, tup_y.1]); let res_and = unsafe { $simd_op(x, y) }; - assert_eq!(tup_x.0 $normal_op tup_y.0, res_and.0); - assert_eq!(tup_x.1 $normal_op tup_y.1, res_and.1); + assert_eq!(tup_x.0 $normal_op tup_y.0, res_and.0[0]); + assert_eq!(tup_x.1 $normal_op tup_y.1, res_and.0[1]); }; } diff --git a/tests/kani/Intrinsics/SIMD/Operators/division.rs b/tests/kani/Intrinsics/SIMD/Operators/division.rs index e1e8dab39cca..d48968342ca4 100644 --- a/tests/kani/Intrinsics/SIMD/Operators/division.rs +++ b/tests/kani/Intrinsics/SIMD/Operators/division.rs @@ -9,32 +9,32 @@ use std::intrinsics::simd::{simd_div, simd_rem}; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i32x2(i32, i32); +pub struct i32x2([i32; 2]); #[kani::proof] fn test_simd_div() { let dividend = kani::any(); - let dividends = i32x2(dividend, dividend); + let dividends = i32x2([dividend, dividend]); let divisor = kani::any(); // Narrow down the divisor interval so the operation doesn't overflow and // the test finishes in a short time kani::assume(divisor > 0 && divisor < 5); - let divisors = i32x2(divisor, divisor); + let divisors = i32x2([divisor, divisor]); let normal_result = dividend / divisor; let simd_result = unsafe { simd_div(dividends, divisors) }; - assert_eq!(normal_result, simd_result.0); + assert_eq!(normal_result, simd_result.0[0]); } #[kani::proof] fn test_simd_rem() { let dividend = kani::any(); - let dividends = i32x2(dividend, dividend); + let dividends = i32x2([dividend, dividend]); let divisor = kani::any(); // Narrow down the divisor interval so the operation doesn't overflow and // the test finishes in a short time kani::assume(divisor > 0 && divisor < 5); - let divisors = i32x2(divisor, divisor); + let divisors = i32x2([divisor, divisor]); let normal_result = dividend % divisor; let simd_result = unsafe { simd_rem(dividends, divisors) }; - assert_eq!(normal_result, simd_result.0); + assert_eq!(normal_result, simd_result.0[0]); } diff --git a/tests/kani/Intrinsics/SIMD/Operators/division_float.rs b/tests/kani/Intrinsics/SIMD/Operators/division_float.rs index 711b67b87116..d61bd81f1f87 100644 --- a/tests/kani/Intrinsics/SIMD/Operators/division_float.rs +++ b/tests/kani/Intrinsics/SIMD/Operators/division_float.rs @@ -8,15 +8,15 @@ use std::intrinsics::simd::simd_div; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, kani::Arbitrary)] -pub struct f32x2(f32, f32); +pub struct f32x2([f32; 2]); impl f32x2 { fn new_with(f: impl Fn() -> f32) -> Self { - f32x2(f(), f()) + f32x2([f(), f()]) } fn non_simd_div(self, divisors: Self) -> Self { - f32x2(self.0 / divisors.0, self.1 / divisors.1) + f32x2([self.0[0] / divisors.0[0], self.0[1] / divisors.0[1]]) } } diff --git a/tests/kani/Intrinsics/SIMD/Shuffle/main.rs b/tests/kani/Intrinsics/SIMD/Shuffle/main.rs index 9b6870e40004..a105a3b1d81e 100644 --- a/tests/kani/Intrinsics/SIMD/Shuffle/main.rs +++ b/tests/kani/Intrinsics/SIMD/Shuffle/main.rs @@ -9,45 +9,45 @@ use std::intrinsics::simd::simd_shuffle; #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[repr(simd)] #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq, Eq)] -pub struct i64x4(i64, i64, i64, i64); +pub struct i64x4([i64; 4]); #[kani::proof] fn main() { { - let y = i64x2(0, 1); - let z = i64x2(1, 2); + let y = i64x2([0, 1]); + let z = i64x2([1, 2]); const I: [u32; 2] = [1, 2]; let x: i64x2 = unsafe { simd_shuffle(y, z, I) }; - assert!(x.0 == 1); - assert!(x.1 == 1); + assert!(x.0[0] == 1); + assert!(x.0[1] == 1); } { - let y = i64x2(0, 1); - let z = i64x2(1, 2); + let y = i64x2([0, 1]); + let z = i64x2([1, 2]); const I: [u32; 2] = [1, 2]; let x: i64x2 = unsafe { simd_shuffle(y, z, I) }; - assert!(x.0 == 1); - assert!(x.1 == 1); + assert!(x.0[0] == 1); + assert!(x.0[1] == 1); } { - let a = i64x4(1, 2, 3, 4); - let b = i64x4(5, 6, 7, 8); + let a = i64x4([1, 2, 3, 4]); + let b = i64x4([5, 6, 7, 8]); const I: [u32; 4] = [1, 3, 5, 7]; let c: i64x4 = unsafe { simd_shuffle(a, b, I) }; - assert!(c == i64x4(2, 4, 6, 8)); + assert!(c == i64x4([2, 4, 6, 8])); } } #[kani::proof] fn check_shuffle() { { - let y = i64x2(0, 1); - let z = i64x2(1, 2); + let y = i64x2([0, 1]); + let z = i64x2([1, 2]); const I: [u32; 4] = [1, 2, 0, 3]; let _x: i64x4 = unsafe { simd_shuffle(y, z, I) }; } diff --git a/tests/kani/SIMD/generic_access.rs b/tests/kani/SIMD/generic_access.rs index 280175a781c6..805eeaa403ea 100644 --- a/tests/kani/SIMD/generic_access.rs +++ b/tests/kani/SIMD/generic_access.rs @@ -31,20 +31,20 @@ mod fields_based { use super::*; #[repr(simd)] - struct CustomSimd(T, T); + struct CustomSimd([T; 2]); fn check_fields( simd: CustomSimd, expected: [T; LANES], ) { - assert_eq!(simd.0, expected[0]); - assert_eq!(simd.1, expected[1]) + assert_eq!(simd.0[0], expected[0]); + assert_eq!(simd.0[1], expected[1]) } #[kani::proof] fn check_field_access() { let data: [u8; 16] = kani::any(); - let vec = CustomSimd(data[0], data[1]); + let vec = CustomSimd([data[0], data[1]]); check_fields(vec, data); } } diff --git a/tests/kani/SIMD/multi_field_simd.rs b/tests/kani/SIMD/multi_field_simd.rs index d54cf1a07bdb..c8cdd848c69e 100644 --- a/tests/kani/SIMD/multi_field_simd.rs +++ b/tests/kani/SIMD/multi_field_simd.rs @@ -10,19 +10,19 @@ #[repr(simd)] #[derive(PartialEq, Eq, PartialOrd, kani::Arbitrary)] -pub struct i64x2(i64, i64); +pub struct i64x2([i64; 2]); #[kani::proof] fn check_diff() { - let x = i64x2(1, 2); - let y = i64x2(3, 4); + let x = i64x2([1, 2]); + let y = i64x2([3, 4]); assert!(x != y); } #[kani::proof] fn check_ge() { let x: i64x2 = kani::any(); - kani::assume(x.0 > 0); - kani::assume(x.1 > 0); - assert!(x > i64x2(0, 0)); + kani::assume(x.0[0] > 0); + kani::assume(x.0[1] > 0); + assert!(x > i64x2([0, 0])); } From dd26362479ca9f57445f66187ea33d6a70ced534 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 18 Sep 2024 15:08:13 -0400 Subject: [PATCH 084/159] Call `check_proof_attribute` for contract harnesses (#3522) Kani enforces that `[kani::proof]` attribute is not applied to generic functions. We do not currently enforce this restriction on contract harnesses. When the compiler [searches for harnesses to verify](https://github.com/model-checking/kani/blob/dba8f3926a61025f5078de787ebd8d21278333ca/kani-compiler/src/kani_middle/codegen_units.rs#L63), it only looks at monomorphized functions. Thus, currently a user can write this code: ```rust #[kani::requires(true)] fn foo() {} #[kani::proof_for_contract(foo)] fn check_foo() { foo() } ``` and get "No proof harnesses (functions with #[kani::proof]) were found to verify." In the case where a user is running many harnesses, they may not notice that Kani skipped the harness. For example, we currently have [this harness](https://github.com/model-checking/verify-rust-std/blob/149f6dd5409fac01a983d7b98c51d51666c74e45/library/core/src/ptr/unique.rs#L288) in the standard library, which doesn't actually run. (PR to fix is [here](https://github.com/model-checking/verify-rust-std/pull/86)). After this PR merges, the code snippet above would instead error with: ```rust error: the '#[kani::proof_for_contract]' attribute cannot be applied to generic functions --> src/lib.rs:4:1 | 4 | #[kani::proof_for_contract(foo)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `kani::proof_for_contract` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error ``` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- kani-compiler/src/kani_middle/attributes.rs | 27 +++++++++++++++----- tests/ui/invalid-contract-harness/expected | 17 ++++++++++++ tests/ui/invalid-contract-harness/invalid.rs | 25 ++++++++++++++++++ tests/ui/invalid-harnesses/expected | 2 +- tests/ui/mir-linker/generic-harness/expected | 2 +- 5 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 tests/ui/invalid-contract-harness/expected create mode 100644 tests/ui/invalid-contract-harness/invalid.rs diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 9a3ff7c1d6a6..86e97f633dfb 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -358,7 +358,7 @@ impl<'tcx> KaniAttributes<'tcx> { ); } expect_single(self.tcx, kind, &attrs); - attrs.iter().for_each(|attr| self.check_proof_attribute(attr)) + attrs.iter().for_each(|attr| self.check_proof_attribute(kind, attr)) } KaniAttributeKind::Unstable => attrs.iter().for_each(|attr| { let _ = UnstableAttribute::try_from(*attr).map_err(|err| err.report(self.tcx)); @@ -370,6 +370,7 @@ impl<'tcx> KaniAttributes<'tcx> { ); } expect_single(self.tcx, kind, &attrs); + attrs.iter().for_each(|attr| self.check_proof_attribute(kind, attr)) } KaniAttributeKind::StubVerified => { expect_single(self.tcx, kind, &attrs); @@ -583,15 +584,29 @@ impl<'tcx> KaniAttributes<'tcx> { } /// Check that if this item is tagged with a proof_attribute, it is a valid harness. - fn check_proof_attribute(&self, proof_attribute: &Attribute) { + fn check_proof_attribute(&self, kind: KaniAttributeKind, proof_attribute: &Attribute) { let span = proof_attribute.span; let tcx = self.tcx; - expect_no_args(tcx, KaniAttributeKind::Proof, proof_attribute); + if let KaniAttributeKind::Proof = kind { + expect_no_args(tcx, kind, proof_attribute); + } + if tcx.def_kind(self.item) != DefKind::Fn { - tcx.dcx().span_err(span, "the `proof` attribute can only be applied to functions"); + tcx.dcx().span_err( + span, + format!( + "the '#[kani::{}]' attribute can only be applied to functions", + kind.as_ref() + ), + ); } else if tcx.generics_of(self.item).requires_monomorphization(tcx) { - tcx.dcx() - .span_err(span, "the `proof` attribute cannot be applied to generic functions"); + tcx.dcx().span_err( + span, + format!( + "the '#[kani::{}]' attribute cannot be applied to generic functions", + kind.as_ref() + ), + ); } else { let instance = Instance::mono(tcx, self.item); if !super::fn_abi(tcx, instance).args.is_empty() { diff --git a/tests/ui/invalid-contract-harness/expected b/tests/ui/invalid-contract-harness/expected new file mode 100644 index 000000000000..1e07c4ca4b16 --- /dev/null +++ b/tests/ui/invalid-contract-harness/expected @@ -0,0 +1,17 @@ +error: only one '#[kani::proof_for_contract]' attribute is allowed per harness\ +invalid.rs:\ +|\ +| #[kani::proof_for_contract(foo)]\ +| ^^^^^^^^^^^^^^ + +error: functions used as harnesses cannot have any arguments\ +invalid.rs:\ +|\ +| #[kani::proof_for_contract(foo)] +| ^^^^^^^^^^^^^^ + +error: the '#[kani::proof_for_contract]' attribute cannot be applied to generic functions\ +invalid.rs:\ +|\ +| #[kani::proof_for_contract(foo)]\ +| ^^^^^^^^^^^^^^ diff --git a/tests/ui/invalid-contract-harness/invalid.rs b/tests/ui/invalid-contract-harness/invalid.rs new file mode 100644 index 000000000000..e30382e3c602 --- /dev/null +++ b/tests/ui/invalid-contract-harness/invalid.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// This test is to check Kani's error handling of invalid usages of the `proof_for_contract` harness. +// We also ensure that all errors and warnings are printed in one compilation. + +#[kani::requires(true)] +fn foo() {} + +#[kani::proof_for_contract(foo)] +#[kani::proof_for_contract(foo)] +fn multiple_proof_annotations() { + foo(); +} + +#[kani::proof_for_contract(foo)] +fn proof_with_arg(arg: bool) { + foo(); +} + +#[kani::proof_for_contract(foo)] +fn generic_harness() { + foo(); +} diff --git a/tests/ui/invalid-harnesses/expected b/tests/ui/invalid-harnesses/expected index 7def51a0d85a..265af44a1685 100644 --- a/tests/ui/invalid-harnesses/expected +++ b/tests/ui/invalid-harnesses/expected @@ -10,7 +10,7 @@ invalid.rs:\ | #[kani::proof] | ^^^^^^^^^^^^^^ -error: the `proof` attribute cannot be applied to generic functions\ +error: the '#[kani::proof]' attribute cannot be applied to generic functions\ invalid.rs:\ |\ | #[kani::proof]\ diff --git a/tests/ui/mir-linker/generic-harness/expected b/tests/ui/mir-linker/generic-harness/expected index 0798bb9e99a3..176ca1a5b7db 100644 --- a/tests/ui/mir-linker/generic-harness/expected +++ b/tests/ui/mir-linker/generic-harness/expected @@ -1 +1 @@ -error: the `proof` attribute cannot be applied to generic functions +error: the '#[kani::proof]' attribute cannot be applied to generic functions From 27cee8b71d8e82d86e04d315534d1677d2744716 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 18 Sep 2024 16:11:23 -0400 Subject: [PATCH 085/159] Add tests for issue 3009 (#3526) Kani v0.55 no longer has the overly broad span issue reported in #3009. I suspect that our shift (#3363) from functions to closures for contracts allows rustc to produce better error messages. Add tests to prevent regression in the future. Resolves #3009 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../function-contracts/non_bool_contracts.expected | 11 +++++++++++ tests/ui/function-contracts/non_bool_contracts.rs | 13 +++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/ui/function-contracts/non_bool_contracts.expected create mode 100644 tests/ui/function-contracts/non_bool_contracts.rs diff --git a/tests/ui/function-contracts/non_bool_contracts.expected b/tests/ui/function-contracts/non_bool_contracts.expected new file mode 100644 index 000000000000..a6d7704330db --- /dev/null +++ b/tests/ui/function-contracts/non_bool_contracts.expected @@ -0,0 +1,11 @@ +| +| #[kani::requires(a + b)] +| -----------------^^^^^-- +| | | +| | expected `bool`, found `u64` +| arguments to this function are incorrect +| + +| +| #[kani::ensures(|result| a % *result && b % *result == 0 && *result != 0)] +| ^^^^^^^^^^^ expected `bool`, found `u64` \ No newline at end of file diff --git a/tests/ui/function-contracts/non_bool_contracts.rs b/tests/ui/function-contracts/non_bool_contracts.rs new file mode 100644 index 000000000000..e192e625cac3 --- /dev/null +++ b/tests/ui/function-contracts/non_bool_contracts.rs @@ -0,0 +1,13 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// This tests that Kani reports the "ideal" error message when contracts are non-boolean expressions +// By "ideal," we mean that the error spans are as narrow as possible +// (c.f. https://github.com/model-checking/kani/issues/3009) + +#[kani::requires(a + b)] +#[kani::ensures(|result| a % *result && b % *result == 0 && *result != 0)] +fn gcd(a: u64, b: u64) -> u64 { + 0 +} From f1221b1b25e9e2896a9dfef48768cabc677b0ef6 Mon Sep 17 00:00:00 2001 From: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:40:16 -0400 Subject: [PATCH 086/159] Fix storing coverage data in cargo projects (#3527) This PR changes the condition we use to decide where to store the coverage data resulting from a coverage-enabled verification run. The previous condition would otherwise cause Kani to crash at the end of the run in some cases: ``` Source-based code coverage results: src/pair.rs (pair::Pair::new) * 6:5 - 8:6 COVERED src/pair.rs (pair::Pair::sum) * 9:5 - 11:6 COVERED src/pair.rs (pair::kani_tests::test_one_plus_two) * 29:5 - 32:6 COVERED Verification Time: 0.083455205s thread 'main' panicked at kani-driver/src/coverage/cov_session.rs:70:43: called `Option::unwrap()` on a `None` value note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` The PR also adds a new `cargo-coverage` mode to `compiletest` which runs `cargo kani` with the source-based coverage feature enabled. This ensures that coverage-enabled `cargo kani` runs are also being tested, and will also be helpful for testing the upcoming coverage tool (#3121) with cargo packages. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- kani-driver/src/coverage/cov_session.rs | 4 +-- scripts/kani-regression.sh | 1 + tests/cargo-coverage/simple-lib/Cargo.toml | 10 ++++++ tests/cargo-coverage/simple-lib/src/lib.rs | 25 ++++++++++++++ tests/cargo-coverage/simple-lib/src/pair.rs | 33 +++++++++++++++++++ .../simple-lib/test_one_plus_two.expected | 10 ++++++ .../simple-lib/test_sum.expected | 10 ++++++ tools/compiletest/src/common.rs | 3 ++ tools/compiletest/src/main.rs | 8 +++-- tools/compiletest/src/runtest.rs | 33 +++++++++++++++++-- 10 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 tests/cargo-coverage/simple-lib/Cargo.toml create mode 100644 tests/cargo-coverage/simple-lib/src/lib.rs create mode 100644 tests/cargo-coverage/simple-lib/src/pair.rs create mode 100644 tests/cargo-coverage/simple-lib/test_one_plus_two.expected create mode 100644 tests/cargo-coverage/simple-lib/test_sum.expected diff --git a/kani-driver/src/coverage/cov_session.rs b/kani-driver/src/coverage/cov_session.rs index df82d982bd72..bf6b34d3cd0b 100644 --- a/kani-driver/src/coverage/cov_session.rs +++ b/kani-driver/src/coverage/cov_session.rs @@ -18,7 +18,7 @@ impl KaniSession { /// Note: Currently, coverage mappings are not included due to technical /// limitations. But this is where we should save them. pub fn save_coverage_metadata(&self, project: &Project, stamp: &String) -> Result<()> { - if self.args.target_dir.is_some() { + if project.input.is_none() { self.save_coverage_metadata_cargo(project, stamp) } else { self.save_coverage_metadata_standalone(project, stamp) @@ -97,7 +97,7 @@ impl KaniSession { results: &Vec, stamp: &String, ) -> Result<()> { - if self.args.target_dir.is_some() { + if project.input.is_none() { self.save_coverage_results_cargo(results, stamp) } else { self.save_coverage_results_standalone(project, results, stamp) diff --git a/scripts/kani-regression.sh b/scripts/kani-regression.sh index b1de293d533c..bd6b04d7386e 100755 --- a/scripts/kani-regression.sh +++ b/scripts/kani-regression.sh @@ -60,6 +60,7 @@ TESTS=( "cargo-ui cargo-kani" "script-based-pre exec" "coverage coverage-based" + "cargo-coverage cargo-coverage" "kani-docs cargo-kani" "kani-fixme kani-fixme" ) diff --git a/tests/cargo-coverage/simple-lib/Cargo.toml b/tests/cargo-coverage/simple-lib/Cargo.toml new file mode 100644 index 000000000000..23f843337dc7 --- /dev/null +++ b/tests/cargo-coverage/simple-lib/Cargo.toml @@ -0,0 +1,10 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +[package] +name = "simple-lib" +version = "0.1.0" +edition = "2018" + +[dependencies] + +[workspace] diff --git a/tests/cargo-coverage/simple-lib/src/lib.rs b/tests/cargo-coverage/simple-lib/src/lib.rs new file mode 100644 index 000000000000..f5f23c6a2acb --- /dev/null +++ b/tests/cargo-coverage/simple-lib/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +pub mod pair; +pub use pair::Pair; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} + +#[cfg(kani)] +mod kani_tests { + use super::*; + + #[kani::proof] + fn test_sum() { + let a: u64 = kani::any(); + let b: u64 = kani::any(); + let p = Pair::new(a, b); + assert!(p.sum() == a.wrapping_add(b)); + } +} diff --git a/tests/cargo-coverage/simple-lib/src/pair.rs b/tests/cargo-coverage/simple-lib/src/pair.rs new file mode 100644 index 000000000000..0201156c4225 --- /dev/null +++ b/tests/cargo-coverage/simple-lib/src/pair.rs @@ -0,0 +1,33 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +pub struct Pair(pub u64, pub u64); + +impl Pair { + pub fn new(a: u64, b: u64) -> Self { + Pair(a, b) + } + pub fn sum(&self) -> u64 { + self.0.wrapping_add(self.1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn one_plus_two() { + let p = Pair::new(1, 2); + assert_eq!(p.sum(), 3); + } +} + +#[cfg(kani)] +mod kani_tests { + use super::*; + + #[kani::proof] + fn test_one_plus_two() { + let p = Pair::new(1, 2); + assert!(p.sum() == 3); + } +} diff --git a/tests/cargo-coverage/simple-lib/test_one_plus_two.expected b/tests/cargo-coverage/simple-lib/test_one_plus_two.expected new file mode 100644 index 000000000000..6010f6fe7b3d --- /dev/null +++ b/tests/cargo-coverage/simple-lib/test_one_plus_two.expected @@ -0,0 +1,10 @@ +Source-based code coverage results: + +src/pair.rs (pair::Pair::new)\ + * 6:5 - 8:6 COVERED + +src/pair.rs (pair::Pair::sum)\ + * 9:5 - 11:6 COVERED + +src/pair.rs (pair::kani_tests::test_one_plus_two)\ + * 29:5 - 32:6 COVERED diff --git a/tests/cargo-coverage/simple-lib/test_sum.expected b/tests/cargo-coverage/simple-lib/test_sum.expected new file mode 100644 index 000000000000..ea1bd5ad3a62 --- /dev/null +++ b/tests/cargo-coverage/simple-lib/test_sum.expected @@ -0,0 +1,10 @@ +Source-based code coverage results: + +src/lib.rs (kani_tests::test_sum)\ + * 19:5 - 24:6 COVERED + +src/pair.rs (pair::Pair::new)\ + * 6:5 - 8:6 COVERED + +src/pair.rs (pair::Pair::sum)\ + * 9:5 - 11:6 COVERED diff --git a/tools/compiletest/src/common.rs b/tools/compiletest/src/common.rs index 99ec78bef693..d5c4ccf3c116 100644 --- a/tools/compiletest/src/common.rs +++ b/tools/compiletest/src/common.rs @@ -17,6 +17,7 @@ use test::ColorConfig; pub enum Mode { Kani, KaniFixme, + CargoCoverage, CargoKani, CargoKaniTest, // `cargo kani --tests`. This is temporary and should be removed when s2n-quic moves --tests to `Cargo.toml`. CoverageBased, @@ -34,6 +35,7 @@ impl FromStr for Mode { "cargo-kani" => Ok(CargoKani), "cargo-kani-test" => Ok(CargoKaniTest), "coverage-based" => Ok(CoverageBased), + "cargo-coverage" => Ok(CargoCoverage), "exec" => Ok(Exec), "expected" => Ok(Expected), "stub-tests" => Ok(Stub), @@ -47,6 +49,7 @@ impl fmt::Display for Mode { let s = match *self { Kani => "kani", KaniFixme => "kani-fixme", + CargoCoverage => "cargo-coverage", CargoKani => "cargo-kani", CargoKaniTest => "cargo-kani-test", CoverageBased => "coverage-based", diff --git a/tools/compiletest/src/main.rs b/tools/compiletest/src/main.rs index 94cbd561aa51..dd1284d7a3c9 100644 --- a/tools/compiletest/src/main.rs +++ b/tools/compiletest/src/main.rs @@ -349,7 +349,7 @@ fn collect_tests_from_dir( tests: &mut Vec, ) -> io::Result<()> { match config.mode { - Mode::CargoKani | Mode::CargoKaniTest => { + Mode::CargoCoverage | Mode::CargoKani | Mode::CargoKaniTest => { collect_expected_tests_from_dir(config, dir, relative_dir_path, inputs, tests) } Mode::Exec => collect_exec_tests_from_dir(config, dir, relative_dir_path, inputs, tests), @@ -378,7 +378,11 @@ fn collect_expected_tests_from_dir( // output directory corresponding to each to avoid race conditions during // the testing phase. We immediately return after adding the tests to avoid // treating `*.rs` files as tests. - assert!(config.mode == Mode::CargoKani || config.mode == Mode::CargoKaniTest); + assert!( + config.mode == Mode::CargoCoverage + || config.mode == Mode::CargoKani + || config.mode == Mode::CargoKaniTest + ); let has_cargo_toml = dir.join("Cargo.toml").exists(); for file in fs::read_dir(dir)? { diff --git a/tools/compiletest/src/runtest.rs b/tools/compiletest/src/runtest.rs index c8387c691296..c6575db17819 100644 --- a/tools/compiletest/src/runtest.rs +++ b/tools/compiletest/src/runtest.rs @@ -8,7 +8,7 @@ use crate::common::KaniFailStep; use crate::common::{output_base_dir, output_base_name}; use crate::common::{ - CargoKani, CargoKaniTest, CoverageBased, Exec, Expected, Kani, KaniFixme, Stub, + CargoCoverage, CargoKani, CargoKaniTest, CoverageBased, Exec, Expected, Kani, KaniFixme, Stub, }; use crate::common::{Config, TestPaths}; use crate::header::TestProps; @@ -74,6 +74,7 @@ impl<'test> TestCx<'test> { match self.config.mode { Kani => self.run_kani_test(), KaniFixme => self.run_kani_test(), + CargoCoverage => self.run_cargo_coverage_test(), CargoKani => self.run_cargo_kani_test(false), CargoKaniTest => self.run_cargo_kani_test(true), CoverageBased => self.run_expected_coverage_test(), @@ -313,7 +314,7 @@ impl<'test> TestCx<'test> { self.compose_and_run(kani) } - /// Run coverage based output for kani on a single file + /// Run Kani with coverage enabled on a single source file fn run_kani_with_coverage(&self) -> ProcRes { let mut kani = Command::new("kani"); if !self.props.compile_flags.is_empty() { @@ -329,6 +330,34 @@ impl<'test> TestCx<'test> { self.compose_and_run(kani) } + /// Run Kani with coverage enabled on a cargo package + fn run_cargo_coverage_test(&self) { + // We create our own command for the same reasons listed in `run_kani_test` method. + let mut cargo = Command::new("cargo"); + // We run `cargo` on the directory where we found the `*.expected` file + let parent_dir = self.testpaths.file.parent().unwrap(); + // The name of the function to test is the same as the stem of `*.expected` file + let function_name = self.testpaths.file.file_stem().unwrap().to_str().unwrap(); + cargo + .arg("kani") + .arg("--coverage") + .arg("-Zsource-coverage") + .arg("--target-dir") + .arg(self.output_base_dir().join("target")) + .current_dir(parent_dir); + + if "expected" != self.testpaths.file.file_name().unwrap() { + cargo.args(["--harness", function_name]); + } + cargo.args(&self.config.extra_args); + + let proc_res = self.compose_and_run(cargo); + self.verify_output(&proc_res, &self.testpaths.file); + + // TODO: We should probably be checking the exit status somehow + // See https://github.com/model-checking/kani/issues/1895 + } + /// Runs an executable file and: /// * Checks the expected output if an expected file is specified /// * Checks the exit code (assumed to be 0 by default) From d05d6041551b234f24a25f509dcfad68ecf59a83 Mon Sep 17 00:00:00 2001 From: Qinheping Hu Date: Sat, 21 Sep 2024 03:10:35 -0500 Subject: [PATCH 087/159] Upgrade toolchain to 2024-09-14 (#3529) Resolves #3525 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 934515867187..668713f66b4a 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-09-12" +channel = "nightly-2024-09-14" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 7be4c2226e83e208ba00d740bca761615aa1ae4d Mon Sep 17 00:00:00 2001 From: Qinheping Hu Date: Sun, 22 Sep 2024 04:14:40 -0500 Subject: [PATCH 088/159] Upgrade toolchain to 2024-09-15 (#3531) Relevant upstream PR: https://github.com/rust-lang/rust/commit/60ee1b7ac6 simd_shuffle: require index argument to be a vector Resolves https://github.com/model-checking/kani/issues/3530 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../codegen/intrinsic.rs | 42 ++++++++----------- rust-toolchain.toml | 2 +- .../simd-shuffle-indexes-out/main.rs | 5 ++- .../main.rs | 5 ++- .../main.rs | 5 ++- tests/kani/Intrinsics/SIMD/Shuffle/main.rs | 11 +++-- 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 1b4c0174d5ed..4b66d60e735b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -1243,35 +1243,27 @@ impl<'tcx> GotocCtx<'tcx> { /// 1. `simd_shuffleN`, where `N` is a number which is part of the name /// (e.g., `simd_shuffle4`). /// 2. `simd_shuffle`, where `N` isn't specified and must be computed from - /// the length of the indexes array (the third argument). + /// the length of the indexes SIMD vector (the third argument). fn simd_shuffle_length(&mut self, stripped: &str, farg_types: &[Ty], span: Span) -> u64 { let n = if stripped.is_empty() { - // Make sure that this is an array, since only the + // Make sure that this is an SIMD vector, since only the // length-suffixed version of `simd_shuffle` (e.g., // `simd_shuffle4`) is type-checked - match farg_types[2].kind() { - TyKind::RigidTy(RigidTy::Array(ty, len)) - if matches!(ty.kind(), TyKind::RigidTy(RigidTy::Uint(UintTy::U32))) => - { - len.eval_target_usize().unwrap_or_else(|err| { - utils::span_err( - self.tcx, - span, - format!("could not evaluate shuffle index array length: {err}"), - ); - // Return a dummy value - u64::MIN - }) - } - _ => { - let err_msg = format!( - "simd_shuffle index must be an array of `u32`, got `{}`", - self.pretty_ty(farg_types[2]) - ); - utils::span_err(self.tcx, span, err_msg); - // Return a dummy value - u64::MIN - } + if farg_types[2].kind().is_simd() + && matches!( + self.simd_size_and_type(farg_types[2]).1.kind(), + TyKind::RigidTy(RigidTy::Uint(UintTy::U32)) + ) + { + self.simd_size_and_type(farg_types[2]).0 + } else { + let err_msg = format!( + "simd_shuffle index must be a SIMD vector of `u32`, got `{}`", + self.pretty_ty(farg_types[2]) + ); + utils::span_err(self.tcx, span, err_msg); + // Return a dummy value + u64::MIN } } else { stripped.parse().unwrap_or_else(|_| { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 668713f66b4a..d87b063bfd23 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-09-14" +channel = "nightly-2024-09-15" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] diff --git a/tests/expected/intrinsics/simd-shuffle-indexes-out/main.rs b/tests/expected/intrinsics/simd-shuffle-indexes-out/main.rs index b89f40369853..a9da5c773743 100644 --- a/tests/expected/intrinsics/simd-shuffle-indexes-out/main.rs +++ b/tests/expected/intrinsics/simd-shuffle-indexes-out/main.rs @@ -11,11 +11,14 @@ use std::intrinsics::simd::simd_shuffle; #[derive(Clone, Copy, PartialEq, Eq)] pub struct i64x2([i64; 2]); +#[repr(simd)] +struct SimdShuffleIdx([u32; LEN]); + #[kani::proof] fn main() { let y = i64x2([0, 1]); let z = i64x2([1, 2]); // Only [0, 3] are valid indexes, 4 is out of bounds - const I: [u32; 2] = [1, 4]; + const I: SimdShuffleIdx<2> = SimdShuffleIdx([1, 4]); let _: i64x2 = unsafe { simd_shuffle(y, z, I) }; } diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs index 3fe534f3bc08..3936f39ed318 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-size/main.rs @@ -16,11 +16,14 @@ pub struct i64x2([i64; 2]); #[derive(Clone, Copy, PartialEq, Eq)] pub struct i64x4([i64; 4]); +#[repr(simd)] +struct SimdShuffleIdx([u32; LEN]); + #[kani::proof] fn main() { let y = i64x2([0, 1]); let z = i64x2([1, 2]); - const I: [u32; 4] = [1, 2, 1, 2]; + const I: SimdShuffleIdx<4> = SimdShuffleIdx([1, 2, 1, 2]); let x: i64x2 = unsafe { simd_shuffle(y, z, I) }; // ^^^^ The code above fails to type-check in Rust with the error: // ``` diff --git a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs index 8c7cd5245cbe..7f8c511a96e5 100644 --- a/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs +++ b/tests/expected/intrinsics/simd-shuffle-result-type-is-diff-type/main.rs @@ -16,11 +16,14 @@ pub struct i64x2([i64; 2]); #[derive(Clone, Copy, PartialEq)] pub struct f64x2([f64; 2]); +#[repr(simd)] +struct SimdShuffleIdx([u32; LEN]); + #[kani::proof] fn main() { let y = i64x2([0, 1]); let z = i64x2([1, 2]); - const I: [u32; 2] = [1, 2]; + const I: SimdShuffleIdx<2> = SimdShuffleIdx([1, 2]); let x: f64x2 = unsafe { simd_shuffle(y, z, I) }; // ^^^^ The code above fails to type-check in Rust with the error: // ``` diff --git a/tests/kani/Intrinsics/SIMD/Shuffle/main.rs b/tests/kani/Intrinsics/SIMD/Shuffle/main.rs index a105a3b1d81e..b20c16be5877 100644 --- a/tests/kani/Intrinsics/SIMD/Shuffle/main.rs +++ b/tests/kani/Intrinsics/SIMD/Shuffle/main.rs @@ -16,12 +16,15 @@ pub struct i64x2([i64; 2]); #[derive(Clone, Copy, PartialEq, Eq)] pub struct i64x4([i64; 4]); +#[repr(simd)] +struct SimdShuffleIdx([u32; LEN]); + #[kani::proof] fn main() { { let y = i64x2([0, 1]); let z = i64x2([1, 2]); - const I: [u32; 2] = [1, 2]; + const I: SimdShuffleIdx<2> = SimdShuffleIdx([1, 2]); let x: i64x2 = unsafe { simd_shuffle(y, z, I) }; assert!(x.0[0] == 1); assert!(x.0[1] == 1); @@ -29,7 +32,7 @@ fn main() { { let y = i64x2([0, 1]); let z = i64x2([1, 2]); - const I: [u32; 2] = [1, 2]; + const I: SimdShuffleIdx<2> = SimdShuffleIdx([1, 2]); let x: i64x2 = unsafe { simd_shuffle(y, z, I) }; assert!(x.0[0] == 1); assert!(x.0[1] == 1); @@ -37,7 +40,7 @@ fn main() { { let a = i64x4([1, 2, 3, 4]); let b = i64x4([5, 6, 7, 8]); - const I: [u32; 4] = [1, 3, 5, 7]; + const I: SimdShuffleIdx<4> = SimdShuffleIdx([1, 3, 5, 7]); let c: i64x4 = unsafe { simd_shuffle(a, b, I) }; assert!(c == i64x4([2, 4, 6, 8])); } @@ -48,7 +51,7 @@ fn check_shuffle() { { let y = i64x2([0, 1]); let z = i64x2([1, 2]); - const I: [u32; 4] = [1, 2, 0, 3]; + const I: SimdShuffleIdx<4> = SimdShuffleIdx([1, 2, 0, 3]); let _x: i64x4 = unsafe { simd_shuffle(y, z, I) }; } } From 118e279655bdb23c57d1548eab028da45688cf6c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 22 Sep 2024 11:39:13 -0500 Subject: [PATCH 089/159] Automatic toolchain upgrade to nightly-2024-09-16 (#3532) Update Rust toolchain from nightly-2024-09-15 to nightly-2024-09-16 without any other source changes. Co-authored-by: qinheping <16714939+qinheping@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 d87b063bfd23..6739a918d59b 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-09-15" +channel = "nightly-2024-09-16" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 858410bcc94b7c30151e8ce6e760c99ab77d0c84 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 22 Sep 2024 17:52:03 -0500 Subject: [PATCH 090/159] Automatic toolchain upgrade to nightly-2024-09-17 (#3533) Update Rust toolchain from nightly-2024-09-16 to nightly-2024-09-17 without any other source changes. Co-authored-by: qinheping <16714939+qinheping@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 6739a918d59b..7cd260ede123 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-09-16" +channel = "nightly-2024-09-17" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From bb241de7c411fb0d6fcfebab1461476db8ec6632 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:59:55 -0400 Subject: [PATCH 091/159] Automatic cargo update to 2024-09-23 (#3535) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63f86490e284..c5fb50872a44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,9 +147,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.17" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ "clap_builder", "clap_derive", @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ "anstream", "anstyle", @@ -169,9 +169,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -1163,18 +1163,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -1245,9 +1245,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap", "serde", @@ -1351,9 +1351,9 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unsafe-libyaml" From a422cca0a953d458748038ed66a3fdc9a6237378 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Mon, 23 Sep 2024 05:15:59 -0700 Subject: [PATCH 092/159] Append harness name to the graph file (debug feature only) (#3528) Our debug feature that dumps the call graph into a file was using the crate name, which meant that the call graph kept getting overriden. Instead, append the name of the harness to the file. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- kani-compiler/src/kani_middle/reachability.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index d2c9d50515c4..b808a5c5b8ee 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -60,7 +60,7 @@ pub fn collect_reachable_items( #[cfg(debug_assertions)] collector .call_graph - .dump_dot(tcx) + .dump_dot(tcx, starting_points.first().cloned()) .unwrap_or_else(|e| tracing::error!("Failed to dump call graph: {e}")); tcx.dcx().abort_if_errors(); @@ -579,12 +579,18 @@ impl CallGraph { /// Print the graph in DOT format to a file. /// See for more information. - fn dump_dot(&self, tcx: TyCtxt) -> std::io::Result<()> { + fn dump_dot(&self, tcx: TyCtxt, initial: Option) -> std::io::Result<()> { if let Ok(target) = std::env::var("KANI_REACH_DEBUG") { debug!(?target, "dump_dot"); + let name = initial.map(|item| Node(item).to_string()).unwrap_or_default(); let outputs = tcx.output_filenames(()); let base_path = outputs.path(OutputType::Metadata); - let path = base_path.as_path().with_extension("dot"); + let file_stem = format!( + "{}_{}.dot", + base_path.as_path().file_stem().unwrap().to_string_lossy(), + name + ); + let path = base_path.as_path().parent().unwrap().join(file_stem); let out_file = File::create(path)?; let mut writer = BufWriter::new(out_file); writeln!(writer, "digraph ReachabilityGraph {{")?; From 06822579041c1aab7527eb3085d3520f7d805c43 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Mon, 23 Sep 2024 16:23:21 +0200 Subject: [PATCH 093/159] Update to CBMC 6.3.1 and fix auto-update script (#3537) Update to CBMC release 6.3.1. This release includes a full fix for the build problem that we carried a patch for (in #3431 and #3436), thus dropping the patching step. The automatic update of CBMC requires actually installing that newer version of CBMC, else regression tests fail as seen in https://github.com/model-checking/kani/actions/runs/10987766383/job/30503118786. Resolves: #3507, #3536 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/cbmc-update.yml | 2 + kani-dependencies | 4 +- scripts/setup/al2/install_cbmc.sh | 74 ------------------------------- 3 files changed, 4 insertions(+), 76 deletions(-) diff --git a/.github/workflows/cbmc-update.yml b/.github/workflows/cbmc-update.yml index 7b63c0dae7de..8d0bc057da2f 100644 --- a/.github/workflows/cbmc-update.yml +++ b/.github/workflows/cbmc-update.yml @@ -51,6 +51,8 @@ jobs: sed -i "s/^CBMC_MINOR=.*/CBMC_MINOR=\"$CBMC_LATEST_MINOR\"/" kani-dependencies sed -i "s/^CBMC_VERSION=.*/CBMC_VERSION=\"$CBMC_LATEST\"/" kani-dependencies git diff + # install the newer CBMC version + ./scripts/setup/ubuntu/install_cbmc.sh if ! ./scripts/kani-regression.sh ; then echo "next_step=create_issue" >> $GITHUB_ENV else diff --git a/kani-dependencies b/kani-dependencies index 421188a08762..6f844839086c 100644 --- a/kani-dependencies +++ b/kani-dependencies @@ -1,6 +1,6 @@ CBMC_MAJOR="6" -CBMC_MINOR="1" -CBMC_VERSION="6.1.1" +CBMC_MINOR="3" +CBMC_VERSION="6.3.1" # If you update this version number, remember to bump it in `src/setup.rs` too CBMC_VIEWER_MAJOR="3" diff --git a/scripts/setup/al2/install_cbmc.sh b/scripts/setup/al2/install_cbmc.sh index 3bac22ace3db..81c476dab440 100755 --- a/scripts/setup/al2/install_cbmc.sh +++ b/scripts/setup/al2/install_cbmc.sh @@ -21,80 +21,6 @@ git clone \ pushd "${WORK_DIR}" -# apply workaround for https://github.com/diffblue/cbmc/issues/8357 until it is -# properly fixed in CBMC -cat > varargs.patch << "EOF" ---- a/src/ansi-c/library/stdio.c -+++ b/src/ansi-c/library/stdio.c -@@ -1135,7 +1135,7 @@ int vfscanf(FILE *restrict stream, const char *restrict format, va_list arg) - - (void)*format; - while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&arg) < -- __CPROVER_OBJECT_SIZE(arg)) -+ __CPROVER_OBJECT_SIZE(*(void **)&arg)) - { - void *a = va_arg(arg, void *); - __CPROVER_havoc_object(a); -@@ -1233,7 +1233,7 @@ int __stdio_common_vfscanf( - - (void)*format; - while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&args) < -- __CPROVER_OBJECT_SIZE(args)) -+ __CPROVER_OBJECT_SIZE(*(void **)&args)) - { - void *a = va_arg(args, void *); - __CPROVER_havoc_object(a); -@@ -1312,7 +1312,7 @@ __CPROVER_HIDE:; - (void)*s; - (void)*format; - while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&arg) < -- __CPROVER_OBJECT_SIZE(arg)) -+ __CPROVER_OBJECT_SIZE(*(void **)&arg)) - { - void *a = va_arg(arg, void *); - __CPROVER_havoc_object(a); -@@ -1388,7 +1388,7 @@ int __stdio_common_vsscanf( - (void)*s; - (void)*format; - while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&args) < -- __CPROVER_OBJECT_SIZE(args)) -+ __CPROVER_OBJECT_SIZE(*(void **)&args)) - { - void *a = va_arg(args, void *); - __CPROVER_havoc_object(a); -@@ -1774,12 +1774,12 @@ int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) - (void)*fmt; - - while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&ap) < -- __CPROVER_OBJECT_SIZE(ap)) -+ __CPROVER_OBJECT_SIZE(*(void **)&ap)) - - { - (void)va_arg(ap, int); - __CPROVER_precondition( -- __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(ap), -+ __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(*(void **)&ap), - "vsnprintf object overlap"); - } - -@@ -1822,12 +1822,12 @@ int __builtin___vsnprintf_chk( - (void)*fmt; - - while((__CPROVER_size_t)__CPROVER_POINTER_OFFSET(*(void **)&ap) < -- __CPROVER_OBJECT_SIZE(ap)) -+ __CPROVER_OBJECT_SIZE(*(void **)&ap)) - - { - (void)va_arg(ap, int); - __CPROVER_precondition( -- __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(ap), -+ __CPROVER_POINTER_OBJECT(str) != __CPROVER_POINTER_OBJECT(*(void **)&ap), - "vsnprintf object overlap"); - } - -EOF -patch -p1 < varargs.patch - cmake3 -S . -Bbuild -DWITH_JBMC=OFF -Dsat_impl="minisat2;cadical" \ -DCMAKE_C_COMPILER=gcc10-cc -DCMAKE_CXX_COMPILER=gcc10-c++ \ -DCMAKE_CXX_STANDARD_LIBRARIES=-lstdc++fs \ From 0ddeb2104a0c439675b617e9cf9ad4918c55f69d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:30:10 +0000 Subject: [PATCH 094/159] Bump tests/perf/s2n-quic from `132ba54` to `a88ae41` (#3540) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `132ba54` to `a88ae41`.
Commits
  • a88ae41 feat(s2n-quic-dc): Make stream::recv::error::Kind pub (#2326)
  • 31d5474 feat(s2n-quic-events): add search_completed boolean to mtu updated event (#2322)
  • 29bd9b7 refactor(s2n-quic-h3): remove s2n-quic-core dependency (#2325)
  • 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 132ba54b6ff7..a88ae4191af1 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 132ba54b6ff7406204b866eb644594201d6be8d7 +Subproject commit a88ae4191af100747404db207976c7da1a3b8370 From 2755592ab80869408fc7a6d7a7a83a1066ce7abb Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Tue, 24 Sep 2024 11:18:16 +0200 Subject: [PATCH 095/159] Update toolchain to 2024-09-20 (#3539) Changes required due to: - rust-lang/rust@bdacdfe95f Minimize visibilities. Resolves: #3534 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 | 32 ++ kani-compiler/Cargo.toml | 5 + .../compiler_interface.rs | 301 +++++++++++++++++- rust-toolchain.toml | 2 +- 4 files changed, 332 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5fb50872a44..390336c8fde8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,6 +248,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -476,6 +485,7 @@ dependencies = [ "kani_metadata", "lazy_static", "num", + "object", "quote", "regex", "serde", @@ -718,6 +728,19 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "crc32fast", + "hashbrown", + "indexmap", + "memchr", + "wasmparser", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1404,6 +1427,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasmparser" +version = "0.216.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcdee6bea3619d311fb4b299721e89a986c3470f804b6d534340e412589028e3" +dependencies = [ + "bitflags", +] + [[package]] name = "which" version = "6.0.3" diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 9ca8d10f5275..9a4a0de43a34 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -28,6 +28,11 @@ tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_de tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} tracing-tree = "0.4.0" +[dependencies.object] +version = "0.36.2" +default-features = false +features = ["elf", "macho", "pe", "xcoff", "write", "wasm"] + # Future proofing: enable backend dependencies using feature. [features] default = ['cprover'] diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 9f700192f2f2..9ef8bdf314c2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -24,15 +24,19 @@ use kani_metadata::artifact::convert_type; use kani_metadata::UnsupportedFeature; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use kani_metadata::{AssignsContract, CompilerArtifactStub}; +use object::write::{self, StandardSegment, Symbol, SymbolSection}; +use object::{ + elf, pe, xcoff, Architecture, BinaryFormat, Endianness, FileFlags, SectionFlags, SectionKind, + SubArchitecture, SymbolFlags, SymbolKind, SymbolScope, +}; use rustc_codegen_ssa::back::archive::{ArArchiveBuilder, ArchiveBuilder, DEFAULT_OBJECT_READER}; -use rustc_codegen_ssa::back::metadata::create_wrapper_file; +use rustc_codegen_ssa::back::metadata::create_metadata_file_for_wasm; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CodegenResults, CrateInfo}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{ErrorGuaranteed, DEFAULT_LOCALE_RESOURCE}; use rustc_hir::def_id::{DefId as InternalDefId, LOCAL_CRATE}; -use rustc_metadata::creader::MetadataLoaderDyn; use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; @@ -42,8 +46,9 @@ use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::output::out_filename; use rustc_session::Session; use rustc_smir::rustc_internal; +use rustc_span::sym; use rustc_target::abi::Endian; -use rustc_target::spec::PanicStrategy; +use rustc_target::spec::{ef_avr_arch, PanicStrategy, RelocModel, Target}; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::{CrateDef, DefId}; use std::any::Any; @@ -224,11 +229,293 @@ impl GotocCodegenBackend { } } -impl CodegenBackend for GotocCodegenBackend { - fn metadata_loader(&self) -> Box { - Box::new(rustc_codegen_ssa::back::metadata::DefaultMetadataLoader) +// Copy of macho_object_build_version_for_target from +// rust/compiler/rustc_codegen_ssa/src/back/metadata.rs +fn macho_object_build_version_for_target(target: &Target) -> object::write::MachOBuildVersion { + /// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz" + /// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200 + fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 { + let (major, minor, patch) = (major as u32, minor as u32, patch as u32); + (major << 16) | (minor << 8) | patch + } + + let platform = + rustc_target::spec::current_apple_platform(target).expect("unknown Apple target OS"); + let min_os = rustc_target::spec::current_apple_deployment_target(target); + let (sdk_major, sdk_minor) = + rustc_target::spec::current_apple_sdk_version(platform).expect("unknown Apple target OS"); + + let mut build_version = object::write::MachOBuildVersion::default(); + build_version.platform = platform; + build_version.minos = pack_version(min_os); + build_version.sdk = pack_version((sdk_major, sdk_minor, 0)); + build_version +} + +// Copy of create_object_file from +// rust/compiler/rustc_codegen_ssa/src/back/metadata.rs +fn create_object_file(sess: &Session) -> Option> { + let endianness = match sess.target.options.endian { + Endian::Little => Endianness::Little, + Endian::Big => Endianness::Big, + }; + let (architecture, sub_architecture) = match &sess.target.arch[..] { + "arm" => (Architecture::Arm, None), + "aarch64" => ( + if sess.target.pointer_width == 32 { + Architecture::Aarch64_Ilp32 + } else { + Architecture::Aarch64 + }, + None, + ), + "x86" => (Architecture::I386, None), + "s390x" => (Architecture::S390x, None), + "mips" | "mips32r6" => (Architecture::Mips, None), + "mips64" | "mips64r6" => (Architecture::Mips64, None), + "x86_64" => ( + if sess.target.pointer_width == 32 { + Architecture::X86_64_X32 + } else { + Architecture::X86_64 + }, + None, + ), + "powerpc" => (Architecture::PowerPc, None), + "powerpc64" => (Architecture::PowerPc64, None), + "riscv32" => (Architecture::Riscv32, None), + "riscv64" => (Architecture::Riscv64, None), + "sparc" => (Architecture::Sparc32Plus, None), + "sparc64" => (Architecture::Sparc64, None), + "avr" => (Architecture::Avr, None), + "msp430" => (Architecture::Msp430, None), + "hexagon" => (Architecture::Hexagon, None), + "bpf" => (Architecture::Bpf, None), + "loongarch64" => (Architecture::LoongArch64, None), + "csky" => (Architecture::Csky, None), + "arm64ec" => (Architecture::Aarch64, Some(SubArchitecture::Arm64EC)), + // Unsupported architecture. + _ => return None, + }; + let binary_format = if sess.target.is_like_osx { + BinaryFormat::MachO + } else if sess.target.is_like_windows { + BinaryFormat::Coff + } else if sess.target.is_like_aix { + BinaryFormat::Xcoff + } else { + BinaryFormat::Elf + }; + + let mut file = write::Object::new(binary_format, architecture, endianness); + file.set_sub_architecture(sub_architecture); + if sess.target.is_like_osx { + if sess.target.llvm_target.starts_with("arm64e") { + file.set_macho_cpu_subtype(object::macho::CPU_SUBTYPE_ARM64E); + } + + file.set_macho_build_version(macho_object_build_version_for_target(&sess.target)) + } + if binary_format == BinaryFormat::Coff { + // Disable the default mangler to avoid mangling the special "@feat.00" symbol name. + let original_mangling = file.mangling(); + file.set_mangling(object::write::Mangling::None); + + let mut feature = 0; + + if file.architecture() == object::Architecture::I386 { + // When linking with /SAFESEH on x86, lld requires that all linker inputs be marked as + // safe exception handling compatible. Metadata files masquerade as regular COFF + // objects and are treated as linker inputs, despite containing no actual code. Thus, + // they still need to be marked as safe exception handling compatible. See #96498. + // Reference: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format + feature |= 1; + } + + file.add_symbol(object::write::Symbol { + name: "@feat.00".into(), + value: feature, + size: 0, + kind: object::SymbolKind::Data, + scope: object::SymbolScope::Compilation, + weak: false, + section: object::write::SymbolSection::Absolute, + flags: object::SymbolFlags::None, + }); + + file.set_mangling(original_mangling); } + let e_flags = match architecture { + Architecture::Mips => { + let arch = match sess.target.options.cpu.as_ref() { + "mips1" => elf::EF_MIPS_ARCH_1, + "mips2" => elf::EF_MIPS_ARCH_2, + "mips3" => elf::EF_MIPS_ARCH_3, + "mips4" => elf::EF_MIPS_ARCH_4, + "mips5" => elf::EF_MIPS_ARCH_5, + s if s.contains("r6") => elf::EF_MIPS_ARCH_32R6, + _ => elf::EF_MIPS_ARCH_32R2, + }; + + let mut e_flags = elf::EF_MIPS_CPIC | arch; + + // If the ABI is explicitly given, use it or default to O32. + match sess.target.options.llvm_abiname.to_lowercase().as_str() { + "n32" => e_flags |= elf::EF_MIPS_ABI2, + "o32" => e_flags |= elf::EF_MIPS_ABI_O32, + _ => e_flags |= elf::EF_MIPS_ABI_O32, + }; + + if sess.target.options.relocation_model != RelocModel::Static { + e_flags |= elf::EF_MIPS_PIC; + } + if sess.target.options.cpu.contains("r6") { + e_flags |= elf::EF_MIPS_NAN2008; + } + e_flags + } + Architecture::Mips64 => { + // copied from `mips64el-linux-gnuabi64-gcc foo.c -c` + elf::EF_MIPS_CPIC + | elf::EF_MIPS_PIC + | if sess.target.options.cpu.contains("r6") { + elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008 + } else { + elf::EF_MIPS_ARCH_64R2 + } + } + Architecture::Riscv32 | Architecture::Riscv64 => { + // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc + let mut e_flags: u32 = 0x0; + + // Check if compressed is enabled + // `unstable_target_features` is used here because "c" is gated behind riscv_target_feature. + if sess.unstable_target_features.contains(&sym::c) { + e_flags |= elf::EF_RISCV_RVC; + } + + // Set the appropriate flag based on ABI + // This needs to match LLVM `RISCVELFStreamer.cpp` + match &*sess.target.llvm_abiname { + "" | "ilp32" | "lp64" => (), + "ilp32f" | "lp64f" => e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE, + "ilp32d" | "lp64d" => e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE, + "ilp32e" => e_flags |= elf::EF_RISCV_RVE, + _ => panic!("unknown RISC-V ABI name"), + } + + e_flags + } + Architecture::LoongArch64 => { + // Source: https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc#e_flags-identifies-abi-type-and-version + let mut e_flags: u32 = elf::EF_LARCH_OBJABI_V1; + + // Set the appropriate flag based on ABI + // This needs to match LLVM `LoongArchELFStreamer.cpp` + match &*sess.target.llvm_abiname { + "ilp32s" | "lp64s" => e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT, + "ilp32f" | "lp64f" => e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT, + "ilp32d" | "lp64d" => e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT, + _ => panic!("unknown LoongArch ABI name"), + } + + e_flags + } + Architecture::Avr => { + // Resolve the ISA revision and set + // the appropriate EF_AVR_ARCH flag. + ef_avr_arch(&sess.target.options.cpu) + } + Architecture::Csky => { + let e_flags = match sess.target.options.abi.as_ref() { + "abiv2" => elf::EF_CSKY_ABIV2, + _ => elf::EF_CSKY_ABIV1, + }; + e_flags + } + _ => 0, + }; + // adapted from LLVM's `MCELFObjectTargetWriter::getOSABI` + let os_abi = match sess.target.options.os.as_ref() { + "hermit" => elf::ELFOSABI_STANDALONE, + "freebsd" => elf::ELFOSABI_FREEBSD, + "solaris" => elf::ELFOSABI_SOLARIS, + _ => elf::ELFOSABI_NONE, + }; + let abi_version = 0; + // rustc implementation also does: + // add_gnu_property_note(&mut file, architecture, binary_format, endianness); + file.flags = FileFlags::Elf { os_abi, abi_version, e_flags }; + Some(file) +} + +// copy of create_wrapper_file from +// rust/compiler/rustc_codegen_ssa/src/back/metadata.rs +// without the MetadataPosition return value, which we don't need +fn create_wrapper_file(sess: &Session, section_name: String, data: &[u8]) -> Vec { + let Some(mut file) = create_object_file(sess) else { + if sess.target.is_like_wasm { + return create_metadata_file_for_wasm(sess, data, §ion_name); + } + + // Targets using this branch don't have support implemented here yet or + // they're not yet implemented in the `object` crate and will likely + // fill out this module over time. + return data.to_vec(); + }; + let section = if file.format() == BinaryFormat::Xcoff { + file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug) + } else { + file.add_section( + file.segment_name(StandardSegment::Debug).to_vec(), + section_name.into_bytes(), + SectionKind::Debug, + ) + }; + match file.format() { + BinaryFormat::Coff => { + file.section_mut(section).flags = + SectionFlags::Coff { characteristics: pe::IMAGE_SCN_LNK_REMOVE }; + } + BinaryFormat::Elf => { + file.section_mut(section).flags = + SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 }; + } + BinaryFormat::Xcoff => { + // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss. + file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text); + file.section_mut(section).flags = + SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 }; + // Encode string stored in .info section of XCOFF. + // FIXME: The length of data here is not guaranteed to fit in a u32. + // We may have to split the data into multiple pieces in order to + // store in .info section. + let len: u32 = data.len().try_into().unwrap(); + let offset = file.append_section_data(section, &len.to_be_bytes(), 1); + // Add a symbol referring to the data in .info section. + file.add_symbol(Symbol { + name: "__aix_rust_metadata".into(), + value: offset + 4, + size: 0, + kind: SymbolKind::Unknown, + scope: SymbolScope::Compilation, + weak: false, + section: SymbolSection::Section(section), + flags: SymbolFlags::Xcoff { + n_sclass: xcoff::C_INFO, + x_smtyp: xcoff::C_HIDEXT, + x_smclas: xcoff::C_HIDEXT, + containing_csect: None, + }, + }); + } + _ => {} + }; + file.append_section_data(section, data, 1); + file.write().unwrap() +} +impl CodegenBackend for GotocCodegenBackend { fn provide(&self, providers: &mut Providers) { provide::provide(providers, &self.queries.lock().unwrap()); } @@ -439,7 +726,7 @@ impl CodegenBackend for GotocCodegenBackend { let mut builder = Box::new(ArArchiveBuilder::new(sess, &DEFAULT_OBJECT_READER)); let tmp_dir = TempFileBuilder::new().prefix("kani").tempdir().unwrap(); let path = MaybeTempDir::new(tmp_dir, sess.opts.cg.save_temps); - let (metadata, _metadata_position) = create_wrapper_file( + let metadata = create_wrapper_file( sess, ".rmeta".to_string(), codegen_results.metadata.raw_data(), diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7cd260ede123..8280b9442a9b 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-09-17" +channel = "nightly-2024-09-20" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 9dc09e7c9dce292d619e72ef219944c0e6cf3376 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Tue, 24 Sep 2024 16:02:28 +0200 Subject: [PATCH 096/159] Update toolchain to 2024-09-23 (#3544) Changes required due to: - https://github.com/rust-lang/rust/pull/130593 Sync from rustfmt - https://github.com/rust-lang/rust/pull/124895 Disallow hidden references to mutable static With the exception of changes to `rust-toolchain.toml`, `rustfmt.toml`, and `library/kani/src/futures.rs` all changes were automatically created by running `scripts/kani-fmt.sh`. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- cprover_bindings/src/cbmc_string.rs | 2 +- cprover_bindings/src/env.rs | 2 +- cprover_bindings/src/goto_program/expr.rs | 11 +- cprover_bindings/src/goto_program/mod.rs | 4 +- .../src/goto_program/symbol_table.rs | 2 +- cprover_bindings/src/goto_program/typ.rs | 10 +- .../src/irep/goto_binary_serde.rs | 4 +- cprover_bindings/src/irep/irep.rs | 2 +- cprover_bindings/src/irep/serialize.rs | 230 +++++++++--------- cprover_bindings/src/irep/to_irep.rs | 71 +++--- cprover_bindings/src/utils.rs | 2 +- .../codegen_cprover_gotoc/codegen/assert.rs | 2 +- .../codegen_cprover_gotoc/codegen/contract.rs | 4 +- .../codegen/foreign_function.rs | 6 +- .../codegen_cprover_gotoc/codegen/function.rs | 8 +- .../codegen/intrinsic.rs | 6 +- .../codegen_cprover_gotoc/codegen/operand.rs | 2 +- .../codegen_cprover_gotoc/codegen/place.rs | 2 +- .../codegen_cprover_gotoc/codegen/rvalue.rs | 20 +- .../codegen/statement.rs | 6 +- .../codegen/static_var.rs | 2 +- .../src/codegen_cprover_gotoc/codegen/typ.rs | 6 +- .../compiler_interface.rs | 18 +- .../context/current_fn.rs | 6 +- .../codegen_cprover_gotoc/context/goto_ctx.rs | 8 +- .../context/vtable_ctx.rs | 4 +- .../codegen_cprover_gotoc/overrides/hooks.rs | 4 +- .../codegen_cprover_gotoc/overrides/mod.rs | 2 +- .../src/codegen_cprover_gotoc/utils/debug.rs | 4 +- .../src/codegen_cprover_gotoc/utils/utils.rs | 2 +- kani-compiler/src/intrinsics.rs | 2 +- kani-compiler/src/kani_middle/analysis.rs | 2 +- kani-compiler/src/kani_middle/attributes.rs | 4 +- .../src/kani_middle/codegen_units.rs | 2 +- kani-compiler/src/kani_middle/coercion.rs | 4 +- kani-compiler/src/kani_middle/intrinsics.rs | 2 +- kani-compiler/src/kani_middle/metadata.rs | 4 +- kani-compiler/src/kani_middle/mod.rs | 4 +- .../points_to/points_to_analysis.rs | 42 ++-- .../kani_middle/points_to/points_to_graph.rs | 4 +- kani-compiler/src/kani_middle/reachability.rs | 6 +- kani-compiler/src/kani_middle/resolve.rs | 6 +- .../kani_middle/resolve/type_resolution.rs | 2 +- kani-compiler/src/kani_middle/stubbing/mod.rs | 2 +- .../delayed_ub/initial_target_visitor.rs | 4 +- .../delayed_ub/instrumentation_visitor.rs | 4 +- .../transform/check_uninit/delayed_ub/mod.rs | 8 +- .../kani_middle/transform/check_uninit/mod.rs | 6 +- .../transform/check_uninit/ptr_uninit/mod.rs | 8 +- .../check_uninit/ptr_uninit/uninit_visitor.rs | 10 +- .../src/kani_middle/transform/check_values.rs | 2 +- .../src/kani_middle/transform/contracts.rs | 10 +- .../kani_middle/transform/kani_intrinsics.rs | 6 +- .../src/kani_middle/transform/mod.rs | 28 +-- .../src/kani_middle/transform/stubs.rs | 2 +- kani-compiler/src/session.rs | 8 +- kani-driver/src/args/mod.rs | 2 +- kani-driver/src/args_toml.rs | 4 +- kani-driver/src/assess/metadata.rs | 2 +- kani-driver/src/assess/mod.rs | 4 +- kani-driver/src/assess/scan.rs | 2 +- kani-driver/src/call_cargo.rs | 6 +- kani-driver/src/call_cbmc.rs | 6 +- kani-driver/src/call_single_file.rs | 2 +- kani-driver/src/cbmc_property_renderer.rs | 208 +++++++--------- kani-driver/src/concrete_playback/playback.rs | 4 +- .../src/concrete_playback/test_generator.rs | 22 +- kani-driver/src/coverage/cov_session.rs | 4 +- kani-driver/src/harness_runner.rs | 2 +- kani-driver/src/main.rs | 4 +- kani-driver/src/metadata.rs | 2 +- kani-driver/src/project.rs | 2 +- kani-driver/src/session.rs | 6 +- kani_metadata/src/artifact.rs | 2 +- library/kani/src/futures.rs | 3 + library/kani/src/lib.rs | 2 +- library/kani/src/vec.rs | 2 +- library/kani_macros/src/derive.rs | 6 +- library/kani_macros/src/lib.rs | 2 +- .../src/sysroot/contracts/bootstrap.rs | 2 +- .../src/sysroot/contracts/check.rs | 6 +- .../src/sysroot/contracts/helpers.rs | 4 +- .../src/sysroot/contracts/initialize.rs | 2 +- .../kani_macros/src/sysroot/contracts/mod.rs | 2 +- .../src/sysroot/contracts/replace.rs | 2 +- .../src/sysroot/contracts/shared.rs | 2 +- rust-toolchain.toml | 2 +- rustfmt.toml | 2 +- src/cmd.rs | 2 +- src/lib.rs | 2 +- src/os_hacks.rs | 2 +- src/setup.rs | 2 +- tests/cargo-kani/percent-encoding/src/lib.rs | 2 +- tests/cargo-kani/small-vec/src/lib.rs | 2 +- .../stubbing-use-as-foreign/src/lib.rs | 2 +- .../stubbing-use-foreign/src/lib.rs | 2 +- tests/cargo-kani/vecdeque-cve/src/harness.rs | 4 +- tests/expected/dealloc/stack/test.rs | 2 +- tests/expected/realloc/null/main.rs | 2 +- tests/expected/realloc/shrink/main.rs | 2 +- tests/expected/realloc/zero_size/main.rs | 2 +- tests/expected/shadow/uninit_array/test.rs | 2 +- .../uninit/alloc-to-slice/alloc-to-slice.rs | 2 +- tests/expected/uninit/atomic/atomic.rs | 2 +- .../expected/uninit/intrinsics/intrinsics.rs | 2 +- tests/kani/AsyncAwait/spawn.rs | 2 +- tests/kani/Coroutines/issue-1593.rs | 2 +- tests/kani/FatPointers/boxmuttrait.rs | 2 +- tests/kani/FatPointers/boxmuttrait_fail.rs | 2 +- .../Intrinsics/Atomic/Stable/Fence/main.rs | 2 +- tests/kani/Intrinsics/RawEq/uninit_eq.rs | 2 +- tests/kani/Intrinsics/RawEq/uninit_ne.rs | 2 +- tests/kani/Realloc/two_reallocs.rs | 2 +- tests/kani/Stubbing/resolve_use.rs | 2 +- tests/kani/Stubbing/resolve_use_as.rs | 2 +- tests/kani/Uninit/alloc-to-slice.rs | 2 +- tests/kani/Uninit/alloc-zeroed-to-slice.rs | 2 +- tests/perf/hashset/src/lib.rs | 2 +- .../alloc-zeroed.rs | 2 +- .../tokio-proofs/src/tokio/io_mem_stream.rs | 2 +- .../tokio-proofs/src/tokio/support/panic.rs | 2 +- .../src/tokio_stream/stream_stream_map.rs | 2 +- .../tokio-proofs/src/tokio_test/block_on.rs | 2 +- tests/std-checks/std/src/sync/atomic.rs | 2 +- tools/build-kani/src/main.rs | 2 +- tools/build-kani/src/sysroot.rs | 4 +- tools/compiletest/src/common.rs | 2 +- tools/compiletest/src/header.rs | 2 +- tools/compiletest/src/main.rs | 4 +- tools/compiletest/src/runtest.rs | 2 +- tools/scanner/src/analysis.rs | 2 +- 131 files changed, 503 insertions(+), 546 deletions(-) diff --git a/cprover_bindings/src/cbmc_string.rs b/cprover_bindings/src/cbmc_string.rs index 4c392f647759..d869b93a8570 100644 --- a/cprover_bindings/src/cbmc_string.rs +++ b/cprover_bindings/src/cbmc_string.rs @@ -3,9 +3,9 @@ use lazy_static::lazy_static; use std::sync::Mutex; +use string_interner::StringInterner; use string_interner::backend::StringBackend; use string_interner::symbol::SymbolU32; -use string_interner::StringInterner; /// This class implements an interner for Strings. /// CBMC objects to have a large number of strings which refer to names: symbols, files, etc. diff --git a/cprover_bindings/src/env.rs b/cprover_bindings/src/env.rs index d31d213aa7d2..e4730c98d34c 100644 --- a/cprover_bindings/src/env.rs +++ b/cprover_bindings/src/env.rs @@ -8,8 +8,8 @@ //! c.f. CBMC code [src/ansi-c/ansi_c_internal_additions.cpp]. //! One possible invocation of this insertion in CBMC can be found in \[ansi_c_languaget::parse\]. -use super::goto_program::{Expr, Location, Symbol, SymbolTable, Type}; use super::MachineModel; +use super::goto_program::{Expr, Location, Symbol, SymbolTable, Type}; use num::bigint::BigInt; fn int_constant(name: &str, value: T) -> Symbol where diff --git a/cprover_bindings/src/goto_program/expr.rs b/cprover_bindings/src/goto_program/expr.rs index 16f33115e0ff..dad3b06e7753 100644 --- a/cprover_bindings/src/goto_program/expr.rs +++ b/cprover_bindings/src/goto_program/expr.rs @@ -285,13 +285,10 @@ pub fn arithmetic_overflow_result_type(operand_type: Type) -> Type { // give the struct the name "overflow_result_", e.g. // "overflow_result_Unsignedbv" let name: InternedString = format!("overflow_result_{operand_type:?}").into(); - Type::struct_type( - name, - vec![ - DatatypeComponent::field(ARITH_OVERFLOW_RESULT_FIELD, operand_type), - DatatypeComponent::field(ARITH_OVERFLOW_OVERFLOWED_FIELD, Type::bool()), - ], - ) + Type::struct_type(name, vec![ + DatatypeComponent::field(ARITH_OVERFLOW_RESULT_FIELD, operand_type), + DatatypeComponent::field(ARITH_OVERFLOW_OVERFLOWED_FIELD, Type::bool()), + ]) } /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/cprover_bindings/src/goto_program/mod.rs b/cprover_bindings/src/goto_program/mod.rs index 8b61722344fb..4d17be37f081 100644 --- a/cprover_bindings/src/goto_program/mod.rs +++ b/cprover_bindings/src/goto_program/mod.rs @@ -17,8 +17,8 @@ mod typ; pub use builtin::BuiltinFn; pub use expr::{ - arithmetic_overflow_result_type, ArithmeticOverflowResult, BinaryOperator, Expr, ExprValue, - SelfOperator, UnaryOperator, ARITH_OVERFLOW_OVERFLOWED_FIELD, ARITH_OVERFLOW_RESULT_FIELD, + ARITH_OVERFLOW_OVERFLOWED_FIELD, ARITH_OVERFLOW_RESULT_FIELD, ArithmeticOverflowResult, + BinaryOperator, Expr, ExprValue, SelfOperator, UnaryOperator, arithmetic_overflow_result_type, }; pub use location::Location; pub use stmt::{Stmt, StmtBody, SwitchCase}; diff --git a/cprover_bindings/src/goto_program/symbol_table.rs b/cprover_bindings/src/goto_program/symbol_table.rs index cd5bd8a6d967..97567670dee0 100644 --- a/cprover_bindings/src/goto_program/symbol_table.rs +++ b/cprover_bindings/src/goto_program/symbol_table.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::super::{env, MachineModel}; +use super::super::{MachineModel, env}; use super::{BuiltinFn, FunctionContract, Stmt, Symbol}; use crate::InternedString; use std::collections::BTreeMap; diff --git a/cprover_bindings/src/goto_program/typ.rs b/cprover_bindings/src/goto_program/typ.rs index d0cc8821a447..3210dd46e43f 100644 --- a/cprover_bindings/src/goto_program/typ.rs +++ b/cprover_bindings/src/goto_program/typ.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use self::DatatypeComponent::*; use self::Type::*; -use super::super::utils::{aggr_tag, max_int, min_int}; use super::super::MachineModel; +use super::super::utils::{aggr_tag, max_int, min_int}; use super::{Expr, SymbolTable}; use crate::cbmc_string::InternedString; use std::collections::BTreeMap; @@ -1598,10 +1598,10 @@ mod type_tests { fn check_typedef_struct_properties() { // Create a struct with a random field. let struct_name: InternedString = "MyStruct".into(); - let struct_type = Type::struct_type( - struct_name, - vec![DatatypeComponent::Field { name: "field".into(), typ: Double }], - ); + let struct_type = Type::struct_type(struct_name, vec![DatatypeComponent::Field { + name: "field".into(), + typ: Double, + }]); // Insert a field to the sym table to represent the struct field. let mut sym_table = SymbolTable::new(machine_model_test_stub()); sym_table.ensure(struct_type.type_name().unwrap(), |_, name| { diff --git a/cprover_bindings/src/irep/goto_binary_serde.rs b/cprover_bindings/src/irep/goto_binary_serde.rs index 6f821768f996..bb725de638cf 100644 --- a/cprover_bindings/src/irep/goto_binary_serde.rs +++ b/cprover_bindings/src/irep/goto_binary_serde.rs @@ -1205,12 +1205,12 @@ mod sharing_stats { mod tests { use super::GotoBinarySerializer; use super::IrepNumbering; + use crate::InternedString; use crate::cbmc_string::InternString; - use crate::irep::goto_binary_serde::GotoBinaryDeserializer; use crate::irep::Irep; use crate::irep::IrepId; + use crate::irep::goto_binary_serde::GotoBinaryDeserializer; use crate::linear_map; - use crate::InternedString; use linear_map::LinearMap; use std::io::BufWriter; /// Utility function : creates a Irep representing a single symbol. diff --git a/cprover_bindings/src/irep/irep.rs b/cprover_bindings/src/irep/irep.rs index 0d0c6dc4ace7..89da8e3446f5 100644 --- a/cprover_bindings/src/irep/irep.rs +++ b/cprover_bindings/src/irep/irep.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! The actual `Irep` structure, and associated constructors, getters, and setters. -use super::super::goto_program::{Location, Type}; use super::super::MachineModel; +use super::super::goto_program::{Location, Type}; use super::{IrepId, ToIrep}; use crate::cbmc_string::InternedString; use crate::linear_map; diff --git a/cprover_bindings/src/irep/serialize.rs b/cprover_bindings/src/irep/serialize.rs index bf3f0b807f61..06cc7bda17b9 100644 --- a/cprover_bindings/src/irep/serialize.rs +++ b/cprover_bindings/src/irep/serialize.rs @@ -1,10 +1,10 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT //! This crate implements irep serialization using serde Serializer. -use crate::irep::{Irep, IrepId, Symbol, SymbolTable}; use crate::InternedString; -use serde::ser::{SerializeMap, Serializer}; +use crate::irep::{Irep, IrepId, Symbol, SymbolTable}; use serde::Serialize; +use serde::ser::{SerializeMap, Serializer}; impl Serialize for Irep { fn serialize(&self, serializer: S) -> Result @@ -145,14 +145,16 @@ impl Serialize for Symbol { #[cfg(test)] mod test { use super::*; - use serde_test::{assert_ser_tokens, Token}; + use serde_test::{Token, assert_ser_tokens}; #[test] fn serialize_irep() { let irep = Irep::empty(); - assert_ser_tokens( - &irep, - &[Token::Map { len: None }, Token::String("id"), Token::String("empty"), Token::MapEnd], - ); + assert_ser_tokens(&irep, &[ + Token::Map { len: None }, + Token::String("id"), + Token::String("empty"), + Token::MapEnd, + ]); } #[test] @@ -187,80 +189,77 @@ mod test { is_weak: false, }; sym_table.insert(symbol); - assert_ser_tokens( - &sym_table, - &[ - Token::Map { len: None }, - Token::String("symbolTable"), - Token::Map { len: Some(1) }, - Token::String("my_name"), - // symbol start - Token::Map { len: None }, - // type irep - Token::String("type"), - Token::Map { len: None }, - Token::String("id"), - Token::String("empty"), - Token::MapEnd, - // value irep - Token::String("value"), - Token::Map { len: None }, - Token::String("id"), - Token::String("empty"), - Token::MapEnd, - // value locaton - Token::String("location"), - Token::Map { len: None }, - Token::String("id"), - Token::String("empty"), - Token::MapEnd, - Token::String("name"), - Token::String("my_name"), - Token::String("module"), - Token::String(""), - Token::String("baseName"), - Token::String(""), - Token::String("prettyName"), - Token::String(""), - Token::String("mode"), - Token::String(""), - Token::String("isType"), - Token::Bool(false), - Token::String("isMacro"), - Token::Bool(false), - Token::String("isExported"), - Token::Bool(false), - Token::String("isInput"), - Token::Bool(false), - Token::String("isOutput"), - Token::Bool(false), - Token::String("isStateVar"), - Token::Bool(false), - Token::String("isProperty"), - Token::Bool(false), - Token::String("isStaticLifetime"), - Token::Bool(false), - Token::String("isThreadLocal"), - Token::Bool(false), - Token::String("isLvalue"), - Token::Bool(false), - Token::String("isFileLocal"), - Token::Bool(false), - Token::String("isExtern"), - Token::Bool(false), - Token::String("isVolatile"), - Token::Bool(false), - Token::String("isParameter"), - Token::Bool(false), - Token::String("isAuxiliary"), - Token::Bool(false), - Token::String("isWeak"), - Token::Bool(false), - Token::MapEnd, - Token::MapEnd, - Token::MapEnd, - ], - ); + assert_ser_tokens(&sym_table, &[ + Token::Map { len: None }, + Token::String("symbolTable"), + Token::Map { len: Some(1) }, + Token::String("my_name"), + // symbol start + Token::Map { len: None }, + // type irep + Token::String("type"), + Token::Map { len: None }, + Token::String("id"), + Token::String("empty"), + Token::MapEnd, + // value irep + Token::String("value"), + Token::Map { len: None }, + Token::String("id"), + Token::String("empty"), + Token::MapEnd, + // value locaton + Token::String("location"), + Token::Map { len: None }, + Token::String("id"), + Token::String("empty"), + Token::MapEnd, + Token::String("name"), + Token::String("my_name"), + Token::String("module"), + Token::String(""), + Token::String("baseName"), + Token::String(""), + Token::String("prettyName"), + Token::String(""), + Token::String("mode"), + Token::String(""), + Token::String("isType"), + Token::Bool(false), + Token::String("isMacro"), + Token::Bool(false), + Token::String("isExported"), + Token::Bool(false), + Token::String("isInput"), + Token::Bool(false), + Token::String("isOutput"), + Token::Bool(false), + Token::String("isStateVar"), + Token::Bool(false), + Token::String("isProperty"), + Token::Bool(false), + Token::String("isStaticLifetime"), + Token::Bool(false), + Token::String("isThreadLocal"), + Token::Bool(false), + Token::String("isLvalue"), + Token::Bool(false), + Token::String("isFileLocal"), + Token::Bool(false), + Token::String("isExtern"), + Token::Bool(false), + Token::String("isVolatile"), + Token::Bool(false), + Token::String("isParameter"), + Token::Bool(false), + Token::String("isAuxiliary"), + Token::Bool(false), + Token::String("isWeak"), + Token::Bool(false), + Token::MapEnd, + Token::MapEnd, + Token::MapEnd, + ]); } #[test] @@ -269,41 +268,38 @@ mod test { let one_irep = Irep::one(); let sub_irep = Irep::just_sub(vec![empty_irep.clone(), one_irep]); let top_irep = Irep::just_sub(vec![sub_irep, empty_irep]); - assert_ser_tokens( - &top_irep, - &[ - // top_irep - Token::Map { len: None }, - Token::String("id"), - Token::String(""), - Token::String("sub"), - Token::Seq { len: Some(2) }, - // sub_irep - Token::Map { len: None }, - Token::String("id"), - Token::String(""), - Token::String("sub"), - Token::Seq { len: Some(2) }, - // empty_irep - Token::Map { len: None }, - Token::String("id"), - Token::String("empty"), - Token::MapEnd, - // one_irep - Token::Map { len: None }, - Token::String("id"), - Token::String("1"), - Token::MapEnd, - Token::SeqEnd, - Token::MapEnd, - // empty_irep - Token::Map { len: None }, - Token::String("id"), - Token::String("empty"), - Token::MapEnd, - Token::SeqEnd, - Token::MapEnd, - ], - ); + assert_ser_tokens(&top_irep, &[ + // top_irep + Token::Map { len: None }, + Token::String("id"), + Token::String(""), + Token::String("sub"), + Token::Seq { len: Some(2) }, + // sub_irep + Token::Map { len: None }, + Token::String("id"), + Token::String(""), + Token::String("sub"), + Token::Seq { len: Some(2) }, + // empty_irep + Token::Map { len: None }, + Token::String("id"), + Token::String("empty"), + Token::MapEnd, + // one_irep + Token::Map { len: None }, + Token::String("id"), + Token::String("1"), + Token::MapEnd, + Token::SeqEnd, + Token::MapEnd, + // empty_irep + Token::Map { len: None }, + Token::String("id"), + Token::String("empty"), + Token::MapEnd, + Token::SeqEnd, + Token::MapEnd, + ]); } } diff --git a/cprover_bindings/src/irep/to_irep.rs b/cprover_bindings/src/irep/to_irep.rs index bf0b8ce862dd..788c9f1e79d4 100644 --- a/cprover_bindings/src/irep/to_irep.rs +++ b/cprover_bindings/src/irep/to_irep.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Converts a typed goto-program into the `Irep` serilization format of CBMC // TODO: consider making a macro to replace `linear_map![])` for initilizing btrees. -use super::super::goto_program; use super::super::MachineModel; +use super::super::goto_program; use super::{Irep, IrepId}; -use crate::linear_map; use crate::InternedString; +use crate::linear_map; use goto_program::{ BinaryOperator, CIntType, DatatypeComponent, Expr, ExprValue, Lambda, Location, Parameter, SelfOperator, Stmt, StmtBody, SwitchCase, SymbolValues, Type, UnaryOperator, @@ -273,10 +273,12 @@ impl ToIrep for ExprValue { )], } } - ExprValue::FunctionCall { function, arguments } => side_effect_irep( - IrepId::FunctionCall, - vec![function.to_irep(mm), arguments_irep(arguments.iter(), mm)], - ), + ExprValue::FunctionCall { function, arguments } => { + side_effect_irep(IrepId::FunctionCall, vec![ + function.to_irep(mm), + arguments_irep(arguments.iter(), mm), + ]) + } ExprValue::If { c, t, e } => Irep { id: IrepId::If, sub: vec![c.to_irep(mm), t.to_irep(mm), e.to_irep(mm)], @@ -318,10 +320,11 @@ impl ToIrep for ExprValue { named_sub: linear_map![], }, ExprValue::SelfOp { op, e } => side_effect_irep(op.to_irep_id(), vec![e.to_irep(mm)]), - ExprValue::StatementExpression { statements: ops, location: loc } => side_effect_irep( - IrepId::StatementExpression, - vec![Stmt::block(ops.to_vec(), *loc).to_irep(mm)], - ), + ExprValue::StatementExpression { statements: ops, location: loc } => { + side_effect_irep(IrepId::StatementExpression, vec![ + Stmt::block(ops.to_vec(), *loc).to_irep(mm), + ]) + } ExprValue::StringConstant { s } => Irep { id: IrepId::StringConstant, sub: vec![], @@ -488,10 +491,10 @@ impl ToIrep for StmtBody { StmtBody::Dead(symbol) => code_irep(IrepId::Dead, vec![symbol.to_irep(mm)]), StmtBody::Decl { lhs, value } => { if value.is_some() { - code_irep( - IrepId::Decl, - vec![lhs.to_irep(mm), value.as_ref().unwrap().to_irep(mm)], - ) + code_irep(IrepId::Decl, vec![ + lhs.to_irep(mm), + value.as_ref().unwrap().to_irep(mm), + ]) } else { code_irep(IrepId::Decl, vec![lhs.to_irep(mm)]) } @@ -504,28 +507,26 @@ impl ToIrep for StmtBody { .with_comment("deinit") } StmtBody::Expression(e) => code_irep(IrepId::Expression, vec![e.to_irep(mm)]), - StmtBody::For { init, cond, update, body } => code_irep( - IrepId::For, - vec![init.to_irep(mm), cond.to_irep(mm), update.to_irep(mm), body.to_irep(mm)], - ), - StmtBody::FunctionCall { lhs, function, arguments } => code_irep( - IrepId::FunctionCall, - vec![ + StmtBody::For { init, cond, update, body } => code_irep(IrepId::For, vec![ + init.to_irep(mm), + cond.to_irep(mm), + update.to_irep(mm), + body.to_irep(mm), + ]), + StmtBody::FunctionCall { lhs, function, arguments } => { + code_irep(IrepId::FunctionCall, vec![ lhs.as_ref().map_or(Irep::nil(), |x| x.to_irep(mm)), function.to_irep(mm), arguments_irep(arguments.iter(), mm), - ], - ), + ]) + } StmtBody::Goto(dest) => code_irep(IrepId::Goto, vec![]) .with_named_sub(IrepId::Destination, Irep::just_string_id(dest.to_string())), - StmtBody::Ifthenelse { i, t, e } => code_irep( - IrepId::Ifthenelse, - vec![ - i.to_irep(mm), - t.to_irep(mm), - e.as_ref().map_or(Irep::nil(), |x| x.to_irep(mm)), - ], - ), + StmtBody::Ifthenelse { i, t, e } => code_irep(IrepId::Ifthenelse, vec![ + i.to_irep(mm), + t.to_irep(mm), + e.as_ref().map_or(Irep::nil(), |x| x.to_irep(mm)), + ]), StmtBody::Label { label, body } => code_irep(IrepId::Label, vec![body.to_irep(mm)]) .with_named_sub(IrepId::Label, Irep::just_string_id(label.to_string())), StmtBody::Return(e) => { @@ -537,10 +538,10 @@ impl ToIrep for StmtBody { if default.is_some() { switch_arms.push(switch_default_irep(default.as_ref().unwrap(), mm)); } - code_irep( - IrepId::Switch, - vec![control.to_irep(mm), code_irep(IrepId::Block, switch_arms)], - ) + code_irep(IrepId::Switch, vec![ + control.to_irep(mm), + code_irep(IrepId::Block, switch_arms), + ]) } StmtBody::While { cond, body } => { code_irep(IrepId::While, vec![cond.to_irep(mm), body.to_irep(mm)]) diff --git a/cprover_bindings/src/utils.rs b/cprover_bindings/src/utils.rs index dd1d39ee53d1..4a323277ec9e 100644 --- a/cprover_bindings/src/utils.rs +++ b/cprover_bindings/src/utils.rs @@ -3,8 +3,8 @@ //! Useful utilities for CBMC use crate::InternedString; -use num::bigint::{BigInt, Sign}; use num::Signed; +use num::bigint::{BigInt, Sign}; use num_traits::Zero; /// The aggregate name used in CBMC for aggregates of type `n`. diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs index c81d60e40d42..66d6e2eb1ab4 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs @@ -19,8 +19,8 @@ //! use crate::codegen_cprover_gotoc::GotocCtx; -use cbmc::goto_program::{Expr, Location, Stmt, Type}; use cbmc::InternedString; +use cbmc::goto_program::{Expr, Location, Stmt, Type}; use rustc_middle::mir::coverage::SourceRegion; use stable_mir::mir::{Place, ProjectionElem}; use stable_mir::ty::{Span as SpanStable, Ty}; diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index a871cd58f94f..26caf29bea1d 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -1,16 +1,16 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::codegen_cprover_gotoc::{codegen::ty_stable::pointee_type_stable, GotocCtx}; +use crate::codegen_cprover_gotoc::{GotocCtx, codegen::ty_stable::pointee_type_stable}; use crate::kani_middle::attributes::KaniAttributes; use cbmc::goto_program::FunctionContract; use cbmc::goto_program::{Expr, Lambda, Location, Type}; use kani_metadata::AssignsContract; use rustc_hir::def_id::DefId as InternalDefId; use rustc_smir::rustc_internal; +use stable_mir::CrateDef; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::mir::{Local, VarDebugInfoContents}; use stable_mir::ty::{FnDef, RigidTy, TyKind}; -use stable_mir::CrateDef; impl<'tcx> GotocCtx<'tcx> { /// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`, diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs index 386a50bc7ebc..1a68533d3dce 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/foreign_function.rs @@ -9,17 +9,17 @@ //! linking and usability unless unstable C-FFI support is enabled. use std::collections::HashSet; -use crate::codegen_cprover_gotoc::codegen::PropertyClass; use crate::codegen_cprover_gotoc::GotocCtx; +use crate::codegen_cprover_gotoc::codegen::PropertyClass; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{Expr, Location, Stmt, Symbol, Type}; use cbmc::{InternString, InternedString}; use lazy_static::lazy_static; +use stable_mir::CrateDef; use stable_mir::abi::{CallConvention, PassMode}; -use stable_mir::mir::mono::Instance; use stable_mir::mir::Place; +use stable_mir::mir::mono::Instance; use stable_mir::ty::{RigidTy, TyKind}; -use stable_mir::CrateDef; use tracing::{debug, trace}; lazy_static! { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 34f8363f4948..eae547d58a4f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -3,14 +3,14 @@ //! This file contains functions related to codegenning MIR functions into gotoc -use crate::codegen_cprover_gotoc::codegen::block::reverse_postorder; use crate::codegen_cprover_gotoc::GotocCtx; -use cbmc::goto_program::{Expr, Stmt, Symbol}; +use crate::codegen_cprover_gotoc::codegen::block::reverse_postorder; use cbmc::InternString; +use cbmc::goto_program::{Expr, Stmt, Symbol}; +use stable_mir::CrateDef; use stable_mir::mir::mono::Instance; use stable_mir::mir::{Body, Local}; use stable_mir::ty::{RigidTy, TyKind}; -use stable_mir::CrateDef; use std::collections::BTreeMap; use tracing::{debug, debug_span}; @@ -223,8 +223,8 @@ pub mod rustc_smir { use rustc_middle::mir::coverage::MappingKind::Code; use rustc_middle::mir::coverage::SourceRegion; use rustc_middle::ty::TyCtxt; - use stable_mir::mir::mono::Instance; use stable_mir::Opaque; + use stable_mir::mir::mono::Instance; type CoverageOpaque = stable_mir::Opaque; diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 4b66d60e735b..79afe39fd89e 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! this module handles intrinsics use super::typ; -use super::{bb_label, PropertyClass}; +use super::{PropertyClass, bb_label}; use crate::codegen_cprover_gotoc::codegen::ty_stable::pointee_type_stable; -use crate::codegen_cprover_gotoc::{utils, GotocCtx}; +use crate::codegen_cprover_gotoc::{GotocCtx, utils}; use crate::intrinsics::Intrinsic; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{ ArithmeticOverflowResult, BinaryOperator, BuiltinFn, Expr, Location, Stmt, Type, }; -use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::ParamEnv; +use rustc_middle::ty::layout::ValidityRequirement; use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; use stable_mir::mir::{BasicBlockIdx, Operand, Place}; diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs index 85bb8292ec67..3c67741667c2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::codegen_cprover_gotoc::utils::slice_fat_ptr; use crate::codegen_cprover_gotoc::GotocCtx; +use crate::codegen_cprover_gotoc::utils::slice_fat_ptr; use crate::unwrap_or_return_codegen_unimplemented; use cbmc::goto_program::{DatatypeComponent, Expr, ExprValue, Location, Symbol, Type}; use rustc_middle::ty::Const as ConstInternal; diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs index d0e3fc3f442f..fb1ddb76e3a2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs @@ -6,10 +6,10 @@ //! in [GotocCtx::codegen_place] below. use super::typ::TypeExt; +use crate::codegen_cprover_gotoc::GotocCtx; use crate::codegen_cprover_gotoc::codegen::ty_stable::pointee_type; use crate::codegen_cprover_gotoc::codegen::typ::std_pointee_type; use crate::codegen_cprover_gotoc::utils::{dynamic_fat_ptr, slice_fat_ptr}; -use crate::codegen_cprover_gotoc::GotocCtx; use crate::unwrap_or_return_codegen_unimplemented; use cbmc::goto_program::{Expr, ExprValue, Location, Stmt, Type}; use rustc_middle::ty::layout::LayoutOf; diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index 7abd949357ff..7a4594a181e3 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -1,21 +1,21 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::codegen_cprover_gotoc::codegen::PropertyClass; 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; use crate::codegen_cprover_gotoc::utils::{dynamic_fat_ptr, slice_fat_ptr}; use crate::codegen_cprover_gotoc::{GotocCtx, VtableCtx}; use crate::kani_middle::coercion::{ - extract_unsize_casting_stable, CoerceUnsizedInfo, CoerceUnsizedIterator, CoercionBaseStable, + CoerceUnsizedInfo, CoerceUnsizedIterator, CoercionBaseStable, extract_unsize_casting_stable, }; use crate::unwrap_or_return_codegen_unimplemented; +use cbmc::MachineModel; use cbmc::goto_program::{ - arithmetic_overflow_result_type, BinaryOperator, Expr, Location, Stmt, Type, - ARITH_OVERFLOW_OVERFLOWED_FIELD, ARITH_OVERFLOW_RESULT_FIELD, + ARITH_OVERFLOW_OVERFLOWED_FIELD, ARITH_OVERFLOW_RESULT_FIELD, BinaryOperator, Expr, Location, + Stmt, Type, arithmetic_overflow_result_type, }; -use cbmc::MachineModel; -use cbmc::{btree_string_map, InternString, InternedString}; +use cbmc::{InternString, InternedString, btree_string_map}; use num::bigint::BigInt; use rustc_middle::ty::{ParamEnv, TyCtxt, VtblEntry}; use rustc_smir::rustc_internal; @@ -927,10 +927,10 @@ impl<'tcx> GotocCtx<'tcx> { pub fn codegen_discriminant_field(&self, place: Expr, ty: Ty) -> Expr { let layout = self.layout_of_stable(ty); assert!( - matches!( - &layout.variants, - Variants::Multiple { tag_encoding: TagEncoding::Direct, .. } - ), + matches!(&layout.variants, Variants::Multiple { + tag_encoding: TagEncoding::Direct, + .. + }), "discriminant field (`case`) only exists for multiple variants and direct encoding" ); let expr = if ty.kind().is_coroutine() { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs index ed0178511126..e54f962336d0 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs @@ -1,8 +1,8 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::typ::TypeExt; use super::typ::FN_RETURN_VOID_VAR_NAME; -use super::{bb_label, PropertyClass}; +use super::typ::TypeExt; +use super::{PropertyClass, bb_label}; use crate::codegen_cprover_gotoc::codegen::function::rustc_smir::region_from_coverage_opaque; use crate::codegen_cprover_gotoc::{GotocCtx, VtableCtx}; use crate::unwrap_or_return_codegen_unimplemented_stmt; @@ -15,7 +15,7 @@ use stable_mir::abi::{ArgAbi, FnAbi, PassMode}; use stable_mir::mir::mono::{Instance, InstanceKind}; use stable_mir::mir::{ AssertMessage, BasicBlockIdx, CopyNonOverlapping, NonDivergingIntrinsic, Operand, Place, - Statement, StatementKind, SwitchTargets, Terminator, TerminatorKind, RETURN_LOCAL, + RETURN_LOCAL, Statement, StatementKind, SwitchTargets, Terminator, TerminatorKind, }; use stable_mir::ty::{Abi, RigidTy, Span, Ty, TyKind, VariantIdx}; use tracing::{debug, debug_span, trace}; diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs index 537fbcb63fbb..a45c3a8d69a0 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/static_var.rs @@ -5,8 +5,8 @@ use crate::codegen_cprover_gotoc::GotocCtx; use crate::kani_middle::is_interior_mut; -use stable_mir::mir::mono::{Instance, StaticDef}; use stable_mir::CrateDef; +use stable_mir::mir::mono::{Instance, StaticDef}; use tracing::debug; impl<'tcx> GotocCtx<'tcx> { diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs index 016bf0d3a261..ae45a2456794 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs @@ -6,10 +6,10 @@ use cbmc::utils::aggr_tag; use cbmc::{InternString, InternedString}; use rustc_ast::ast::Mutability; use rustc_index::IndexVec; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::print::FmtPrinter; -use rustc_middle::ty::GenericArgsRef; +use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{ self, AdtDef, Const, CoroutineArgs, CoroutineArgsExt, FloatTy, Instance, IntTy, PolyFnSig, Ty, TyCtxt, TyKind, UintTy, VariantDef, VtblEntry, @@ -22,8 +22,8 @@ use rustc_target::abi::{ TyAndLayout, VariantIdx, Variants, }; use stable_mir::abi::{ArgAbi, FnAbi, PassMode}; -use stable_mir::mir::mono::Instance as InstanceStable; use stable_mir::mir::Body; +use stable_mir::mir::mono::Instance as InstanceStable; use tracing::{debug, trace, warn}; /// Map the unit type to an empty struct diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 9ef8bdf314c2..1efc41785351 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -6,7 +6,7 @@ use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; use crate::kani_middle::analysis; -use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; +use crate::kani_middle::attributes::{KaniAttributes, is_test_harness_description}; use crate::kani_middle::check_reachable_items; use crate::kani_middle::codegen_units::{CodegenUnit, CodegenUnits}; use crate::kani_middle::metadata::gen_test_metadata; @@ -16,18 +16,18 @@ use crate::kani_middle::reachability::{ }; use crate::kani_middle::transform::{BodyTransformation, GlobalPasses}; use crate::kani_queries::QueryDb; +use cbmc::RoundingMode; use cbmc::goto_program::Location; use cbmc::irep::goto_binary_serde::write_goto_binary_file; -use cbmc::RoundingMode; use cbmc::{InternedString, MachineModel}; -use kani_metadata::artifact::convert_type; use kani_metadata::UnsupportedFeature; +use kani_metadata::artifact::convert_type; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use kani_metadata::{AssignsContract, CompilerArtifactStub}; use object::write::{self, StandardSegment, Symbol, SymbolSection}; use object::{ - elf, pe, xcoff, Architecture, BinaryFormat, Endianness, FileFlags, SectionFlags, SectionKind, - SubArchitecture, SymbolFlags, SymbolKind, SymbolScope, + Architecture, BinaryFormat, Endianness, FileFlags, SectionFlags, SectionKind, SubArchitecture, + SymbolFlags, SymbolKind, SymbolScope, elf, pe, xcoff, }; use rustc_codegen_ssa::back::archive::{ArArchiveBuilder, ArchiveBuilder, DEFAULT_OBJECT_READER}; use rustc_codegen_ssa::back::metadata::create_metadata_file_for_wasm; @@ -35,20 +35,20 @@ use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CodegenResults, CrateInfo}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::temp_dir::MaybeTempDir; -use rustc_errors::{ErrorGuaranteed, DEFAULT_LOCALE_RESOURCE}; +use rustc_errors::{DEFAULT_LOCALE_RESOURCE, ErrorGuaranteed}; use rustc_hir::def_id::{DefId as InternalDefId, LOCAL_CRATE}; -use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; use rustc_metadata::EncodedMetadata; +use rustc_metadata::fs::{METADATA_FILENAME, emit_wrapper_file}; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; +use rustc_session::Session; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::output::out_filename; -use rustc_session::Session; use rustc_smir::rustc_internal; use rustc_span::sym; use rustc_target::abi::Endian; -use rustc_target::spec::{ef_avr_arch, PanicStrategy, RelocModel, Target}; +use rustc_target::spec::{PanicStrategy, RelocModel, Target, ef_avr_arch}; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::{CrateDef, DefId}; use std::any::Any; diff --git a/kani-compiler/src/codegen_cprover_gotoc/context/current_fn.rs b/kani-compiler/src/codegen_cprover_gotoc/context/current_fn.rs index ea3c9e909eb6..246b6cd94b47 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/current_fn.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/current_fn.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::codegen_cprover_gotoc::GotocCtx; -use cbmc::goto_program::Stmt; use cbmc::InternedString; +use cbmc::goto_program::Stmt; use rustc_middle::ty::Instance as InstanceInternal; use rustc_smir::rustc_internal; -use stable_mir::mir::mono::Instance; -use stable_mir::mir::{visit::Location, visit::MirVisitor, Body, Local, LocalDecl, Rvalue}; use stable_mir::CrateDef; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::{Body, Local, LocalDecl, Rvalue, visit::Location, visit::MirVisitor}; use std::collections::{HashMap, HashSet}; /// This structure represents useful data about the function we are currently compiling. 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 3f17f4b87233..d88506464cf7 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs @@ -16,9 +16,9 @@ //! this structure as input. use super::current_fn::CurrentFnCtx; use super::vtable_ctx::VtableCtx; -use crate::codegen_cprover_gotoc::overrides::{fn_hooks, GotocHooks}; -use crate::codegen_cprover_gotoc::utils::full_crate_name; use crate::codegen_cprover_gotoc::UnsupportedConstructs; +use crate::codegen_cprover_gotoc::overrides::{GotocHooks, fn_hooks}; +use crate::codegen_cprover_gotoc::utils::full_crate_name; use crate::kani_middle::transform::BodyTransformation; use crate::kani_queries::QueryDb; use cbmc::goto_program::{ @@ -33,12 +33,12 @@ use rustc_middle::ty::layout::{ TyAndLayout, }; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_span::source_map::respan; use rustc_span::Span; +use rustc_span::source_map::respan; use rustc_target::abi::call::FnAbi; use rustc_target::abi::{HasDataLayout, TargetDataLayout}; -use stable_mir::mir::mono::Instance; use stable_mir::mir::Body; +use stable_mir::mir::mono::Instance; use stable_mir::ty::Allocation; use std::fmt::Debug; diff --git a/kani-compiler/src/codegen_cprover_gotoc/context/vtable_ctx.rs b/kani-compiler/src/codegen_cprover_gotoc/context/vtable_ctx.rs index 98e78ed2b5fd..854ddf6baa57 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/vtable_ctx.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/vtable_ctx.rs @@ -1,6 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::codegen_cprover_gotoc::GotocCtx; /// We can improve verification performance by conveying semantic information /// about function pointer calls down to the underlying solver. In particular, /// method calls through Rust dynamic trait objects use a vtable (virtual method @@ -16,9 +17,8 @@ /// For the current CBMC implementation of function restrictions, see: /// http://cprover.diffblue.com/md__home_travis_build_diffblue_cbmc_doc_architectural_restrict-function-pointer.html use crate::codegen_cprover_gotoc::codegen::typ::pointee_type; -use crate::codegen_cprover_gotoc::GotocCtx; -use cbmc::goto_program::{Stmt, Type}; use cbmc::InternedString; +use cbmc::goto_program::{Stmt, Type}; use kani_metadata::{CallSite, PossibleMethodEntry, TraitDefinedMethod, VtableCtxResults}; use rustc_data_structures::fx::FxHashMap; use tracing::debug; diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs index c0df279932f6..da3cef9e3564 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs @@ -8,8 +8,8 @@ //! It would be too nasty if we spread around these sort of undocumented hooks in place, so //! this module addresses this issue. -use crate::codegen_cprover_gotoc::codegen::{bb_label, PropertyClass}; use crate::codegen_cprover_gotoc::GotocCtx; +use crate::codegen_cprover_gotoc::codegen::{PropertyClass, bb_label}; use crate::kani_middle::attributes::matches_diagnostic as matches_function; use crate::unwrap_or_return_codegen_unimplemented_stmt; use cbmc::goto_program::{BuiltinFn, Expr, Stmt, Type}; @@ -17,7 +17,7 @@ use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; use stable_mir::mir::{BasicBlockIdx, Place}; -use stable_mir::{ty::Span, CrateDef}; +use stable_mir::{CrateDef, ty::Span}; use std::rc::Rc; use tracing::debug; diff --git a/kani-compiler/src/codegen_cprover_gotoc/overrides/mod.rs b/kani-compiler/src/codegen_cprover_gotoc/overrides/mod.rs index 3dda5076fb89..1ba563527626 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/overrides/mod.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/overrides/mod.rs @@ -8,4 +8,4 @@ mod hooks; -pub use hooks::{fn_hooks, GotocHooks}; +pub use hooks::{GotocHooks, fn_hooks}; diff --git a/kani-compiler/src/codegen_cprover_gotoc/utils/debug.rs b/kani-compiler/src/codegen_cprover_gotoc/utils/debug.rs index 0c69cb30b37f..fe19c854c2ac 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/utils/debug.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/utils/debug.rs @@ -5,9 +5,9 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::Location; -use stable_mir::mir::mono::Instance; -use stable_mir::mir::Body; use stable_mir::CrateDef; +use stable_mir::mir::Body; +use stable_mir::mir::mono::Instance; use std::cell::RefCell; use std::panic; use std::sync::LazyLock; diff --git a/kani-compiler/src/codegen_cprover_gotoc/utils/utils.rs b/kani-compiler/src/codegen_cprover_gotoc/utils/utils.rs index 16edca2a826c..faf1ed61dc2f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/utils/utils.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/utils/utils.rs @@ -3,7 +3,7 @@ use super::super::codegen::TypeExt; use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::{Expr, ExprValue, Location, SymbolTable, Type}; -use cbmc::{btree_string_map, InternedString}; +use cbmc::{InternedString, btree_string_map}; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::ty::Span; diff --git a/kani-compiler/src/intrinsics.rs b/kani-compiler/src/intrinsics.rs index 9485d8525410..13a12751bd07 100644 --- a/kani-compiler/src/intrinsics.rs +++ b/kani-compiler/src/intrinsics.rs @@ -4,7 +4,7 @@ //! Single source of truth about which intrinsics we support. use stable_mir::{ - mir::{mono::Instance, Mutability}, + mir::{Mutability, mono::Instance}, ty::{FloatTy, IntTy, RigidTy, TyKind, UintTy}, }; diff --git a/kani-compiler/src/kani_middle/analysis.rs b/kani-compiler/src/kani_middle/analysis.rs index 0adee0e52cca..b232db8feecb 100644 --- a/kani-compiler/src/kani_middle/analysis.rs +++ b/kani-compiler/src/kani_middle/analysis.rs @@ -9,7 +9,7 @@ use stable_mir::mir::mono::MonoItem; use stable_mir::mir::{ - visit::Location, MirVisitor, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + MirVisitor, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, visit::Location, }; use std::collections::HashMap; use std::fmt::Display; diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 86e97f633dfb..630f818231c9 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -7,7 +7,7 @@ use std::collections::BTreeMap; use kani_metadata::{CbmcSolver, HarnessAttributes, HarnessKind, Stub}; use quote::ToTokens; use rustc_ast::{ - attr, AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, + AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, attr, }; use rustc_errors::ErrorGuaranteed; use rustc_hir::{def::DefKind, def_id::DefId}; @@ -25,7 +25,7 @@ use syn::{PathSegment, TypePath}; use tracing::{debug, trace}; -use super::resolve::{resolve_fn, resolve_fn_path, FnResolution, ResolveError}; +use super::resolve::{FnResolution, ResolveError, resolve_fn, resolve_fn_path}; #[derive(Debug, Clone, Copy, AsRefStr, EnumString, PartialEq, Eq, PartialOrd, Ord)] #[strum(serialize_all = "snake_case")] diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index d16e4d2b93d1..071e880ffd9f 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -19,9 +19,9 @@ use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; use rustc_session::config::OutputType; use rustc_smir::rustc_internal; +use stable_mir::CrateDef; use stable_mir::mir::mono::Instance; use stable_mir::ty::{FnDef, IndexedVal, RigidTy, TyKind}; -use stable_mir::CrateDef; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fs::File; use std::io::BufWriter; diff --git a/kani-compiler/src/kani_middle/coercion.rs b/kani-compiler/src/kani_middle/coercion.rs index 38760fca300d..224abef1b02b 100644 --- a/kani-compiler/src/kani_middle/coercion.rs +++ b/kani-compiler/src/kani_middle/coercion.rs @@ -15,12 +15,12 @@ use rustc_hir::lang_items::LangItem; use rustc_middle::traits::{ImplSource, ImplSourceUserDefinedData}; -use rustc_middle::ty::adjustment::CustomCoerceUnsized; use rustc_middle::ty::TraitRef; +use rustc_middle::ty::adjustment::CustomCoerceUnsized; use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; use rustc_smir::rustc_internal; -use stable_mir::ty::{RigidTy, Ty as TyStable, TyKind}; use stable_mir::Symbol; +use stable_mir::ty::{RigidTy, Ty as TyStable, TyKind}; use tracing::trace; /// Given an unsized coercion (e.g. from `&u8` to `&dyn Debug`), extract the pair of diff --git a/kani-compiler/src/kani_middle/intrinsics.rs b/kani-compiler/src/kani_middle/intrinsics.rs index 73e42be00e7b..c32ec38a3696 100644 --- a/kani-compiler/src/kani_middle/intrinsics.rs +++ b/kani-compiler/src/kani_middle/intrinsics.rs @@ -8,7 +8,7 @@ use rustc_middle::mir::{Local, LocalDecl}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::ty::{Const, GenericArgsRef, IntrinsicDef}; use rustc_span::source_map::Spanned; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::{Symbol, sym}; use tracing::{debug, trace}; pub struct ModelIntrinsics<'tcx> { diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 2f0f22d49e1c..db6b6b06149a 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -8,10 +8,10 @@ use std::path::Path; use crate::kani_middle::attributes::test_harness_name; use kani_metadata::{ArtifactType, HarnessAttributes, HarnessKind, HarnessMetadata}; use rustc_middle::ty::TyCtxt; -use stable_mir::mir::mono::Instance; use stable_mir::CrateDef; +use stable_mir::mir::mono::Instance; -use super::{attributes::KaniAttributes, SourceLocation}; +use super::{SourceLocation, attributes::KaniAttributes}; /// Create the harness metadata for a proof harness for a given function. pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> HarnessMetadata { diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index f281e5de5e88..5f7f4c28068f 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -14,14 +14,14 @@ use rustc_middle::ty::layout::{ }; use rustc_middle::ty::{self, Instance as InstanceInternal, Ty as TyInternal, TyCtxt}; use rustc_smir::rustc_internal; -use rustc_span::source_map::respan; use rustc_span::Span; +use rustc_span::source_map::respan; use rustc_target::abi::call::FnAbi; use rustc_target::abi::{HasDataLayout, TargetDataLayout}; +use stable_mir::CrateDef; use stable_mir::mir::mono::MonoItem; use stable_mir::ty::{FnDef, RigidTy, Span as SpanStable, Ty, TyKind}; use stable_mir::visitor::{Visitable, Visitor as TyVisitor}; -use stable_mir::CrateDef; use std::ops::ControlFlow; use self::attributes::KaniAttributes; 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 index 68343ccaad37..040a925e140f 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_analysis.rs @@ -42,8 +42,8 @@ use rustc_middle::{ }; 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 rustc_span::{DUMMY_SP, source_map::Spanned}; +use stable_mir::mir::{Body as StableBody, mono::Instance as StableInstance}; use std::collections::HashSet; /// Main points-to analysis object. @@ -458,22 +458,19 @@ impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { // 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 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(), - }, - )]); + 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); @@ -481,13 +478,10 @@ impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { } 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 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); } @@ -501,10 +495,10 @@ impl<'a, 'tcx> PointsToAnalysis<'a, 'tcx> { // 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() }, - )]); + 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)); } 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 index 61804d0ff71a..4dd2fba62fb0 100644 --- a/kani-compiler/src/kani_middle/points_to/points_to_graph.rs +++ b/kani-compiler/src/kani_middle/points_to/points_to_graph.rs @@ -8,11 +8,11 @@ use rustc_middle::{ mir::{Location, Place, ProjectionElem}, ty::{Instance, List, TyCtxt}, }; -use rustc_mir_dataflow::{fmt::DebugWithContext, JoinSemiLattice}; +use rustc_mir_dataflow::{JoinSemiLattice, fmt::DebugWithContext}; use rustc_smir::rustc_internal; use stable_mir::mir::{ - mono::{Instance as StableInstance, StaticDef}, Place as StablePlace, + mono::{Instance as StableInstance, StaticDef}, }; use std::collections::{HashMap, HashSet, VecDeque}; diff --git a/kani-compiler/src/kani_middle/reachability.rs b/kani-compiler/src/kani_middle/reachability.rs index b808a5c5b8ee..6f2e779320f3 100644 --- a/kani-compiler/src/kani_middle/reachability.rs +++ b/kani-compiler/src/kani_middle/reachability.rs @@ -24,14 +24,14 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_middle::ty::{TyCtxt, VtblEntry}; use rustc_session::config::OutputType; use rustc_smir::rustc_internal; +use stable_mir::CrateItem; use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; use stable_mir::mir::mono::{Instance, InstanceKind, MonoItem, StaticDef}; use stable_mir::mir::{ - visit::Location, Body, CastKind, ConstOperand, MirVisitor, PointerCoercion, Rvalue, Terminator, - TerminatorKind, + Body, CastKind, ConstOperand, MirVisitor, PointerCoercion, Rvalue, Terminator, TerminatorKind, + visit::Location, }; use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind}; -use stable_mir::CrateItem; use stable_mir::{CrateDef, ItemKind}; use std::fmt::{Display, Formatter}; use std::{ diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index d2b1fcd06471..d71f9710d404 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -14,13 +14,13 @@ use crate::kani_middle::stable_fn_def; use quote::ToTokens; 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::def_id::{CRATE_DEF_INDEX, DefId, LOCAL_CRATE, LocalDefId, LocalModDefId}; use rustc_hir::{ItemKind, UseKind}; -use rustc_middle::ty::fast_reject::{self, TreatParams}; use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::fast_reject::{self, TreatParams}; use rustc_smir::rustc_internal; -use stable_mir::ty::{FnDef, RigidTy, Ty, TyKind}; use stable_mir::CrateDef; +use stable_mir::ty::{FnDef, RigidTy, Ty, TyKind}; use std::collections::HashSet; use std::fmt; use std::iter::Peekable; diff --git a/kani-compiler/src/kani_middle/resolve/type_resolution.rs b/kani-compiler/src/kani_middle/resolve/type_resolution.rs index 790019ab2998..9e594785a122 100644 --- a/kani-compiler/src/kani_middle/resolve/type_resolution.rs +++ b/kani-compiler/src/kani_middle/resolve/type_resolution.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! This module contains code used for resolve type / trait names -use crate::kani_middle::resolve::{resolve_path, validate_kind, ResolveError}; +use crate::kani_middle::resolve::{ResolveError, resolve_path, validate_kind}; use quote::ToTokens; use rustc_hir::def::DefKind; use rustc_middle::ty::TyCtxt; diff --git a/kani-compiler/src/kani_middle/stubbing/mod.rs b/kani-compiler/src/kani_middle/stubbing/mod.rs index f145a4d105e2..4a48c649983c 100644 --- a/kani-compiler/src/kani_middle/stubbing/mod.rs +++ b/kani-compiler/src/kani_middle/stubbing/mod.rs @@ -14,9 +14,9 @@ use rustc_hir::def_id::DefId; use rustc_middle::mir::Const; use rustc_middle::ty::{self, EarlyBinder, ParamEnv, TyCtxt, TypeFoldable}; use rustc_smir::rustc_internal; +use stable_mir::mir::ConstOperand; use stable_mir::mir::mono::Instance; use stable_mir::mir::visit::{Location, MirVisitor}; -use stable_mir::mir::ConstOperand; use stable_mir::ty::{FnDef, RigidTy, TyKind}; use stable_mir::{CrateDef, CrateItem}; 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 index 35d0e7041704..dfd16594146f 100644 --- 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 @@ -10,11 +10,11 @@ use crate::{ }; use stable_mir::{ mir::{ + Body, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, Rvalue, + Statement, StatementKind, Terminator, TerminatorKind, alloc::GlobalAlloc, mono::{Instance, InstanceKind, StaticDef}, visit::Location, - Body, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, Rvalue, - Statement, StatementKind, Terminator, TerminatorKind, }, ty::{ConstantKind, RigidTy, TyKind}, }; 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 index 41a2b69d1fdf..877ff41ea6d5 100644 --- 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 @@ -9,16 +9,16 @@ use crate::kani_middle::{ transform::{ body::{InsertPosition, MutableBody, SourceInstruction}, check_uninit::{ - relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, TargetFinder, + relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, }, }, }; use rustc_middle::ty::TyCtxt; use stable_mir::mir::{ + MirVisitor, Operand, Place, Rvalue, Statement, Terminator, mono::Instance, visit::{Location, PlaceContext}, - MirVisitor, Operand, Place, Rvalue, Statement, Terminator, }; use std::collections::HashSet; 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 index c31f72c9b5c3..b2a7ffd13bc1 100644 --- 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 @@ -8,11 +8,11 @@ use std::collections::HashSet; use crate::args::ExtraChecks; use crate::kani_middle::{ - points_to::{run_points_to_analysis, MemLoc, PointsToGraph}, + points_to::{MemLoc, PointsToGraph, run_points_to_analysis}, reachability::CallGraph, transform::{ - body::CheckType, check_uninit::UninitInstrumenter, BodyTransformation, GlobalPass, - TransformationResult, + BodyTransformation, GlobalPass, TransformationResult, body::CheckType, + check_uninit::UninitInstrumenter, }, }; use crate::kani_queries::QueryDb; @@ -22,8 +22,8 @@ use rustc_middle::ty::TyCtxt; use rustc_mir_dataflow::JoinSemiLattice; use rustc_session::config::OutputType; use stable_mir::{ - mir::mono::{Instance, MonoItem}, mir::MirVisitor, + mir::mono::{Instance, MonoItem}, ty::FnDef, }; 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 726dc1965770..1ce21ef50669 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/mod.rs @@ -12,12 +12,12 @@ use relevant_instruction::{InitRelevantInstruction, MemoryInitOp}; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::{ + CrateDef, mir::{ - mono::Instance, AggregateKind, BasicBlock, Body, ConstOperand, Mutability, Operand, Place, - Rvalue, Statement, StatementKind, Terminator, TerminatorKind, UnwindAction, + AggregateKind, BasicBlock, Body, ConstOperand, Mutability, Operand, Place, Rvalue, + Statement, StatementKind, Terminator, TerminatorKind, UnwindAction, mono::Instance, }, ty::{FnDef, GenericArgKind, GenericArgs, MirConst, RigidTy, Ty, TyConst, TyKind, UintTy}, - CrateDef, }; use std::collections::HashMap; 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 index 37f1d94ed744..2408a3b50a6c 100644 --- 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 @@ -6,17 +6,17 @@ use crate::args::ExtraChecks; use crate::kani_middle::transform::{ - body::{CheckType, InsertPosition, MutableBody, SourceInstruction}, - check_uninit::{get_mem_init_fn_def, UninitInstrumenter}, TransformPass, TransformationType, + body::{CheckType, InsertPosition, MutableBody, SourceInstruction}, + check_uninit::{UninitInstrumenter, get_mem_init_fn_def}, }; 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, + mir::{Body, Mutability, Place, mono::Instance}, + ty::{FnDef, GenericArgs, Ty}, }; use std::collections::HashMap; use std::fmt::Debug; diff --git a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs index bd4356017b6b..20f4538453b5 100644 --- a/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs +++ b/kani-compiler/src/kani_middle/transform/check_uninit/ptr_uninit/uninit_visitor.rs @@ -8,20 +8,20 @@ use crate::{ kani_middle::transform::{ body::{InsertPosition, MutableBody, SourceInstruction}, check_uninit::{ - relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, - ty_layout::{tys_layout_compatible_to_size, LayoutComputationError}, PointeeInfo, TargetFinder, + relevant_instruction::{InitRelevantInstruction, MemoryInitOp}, + ty_layout::{LayoutComputationError, tys_layout_compatible_to_size}, }, }, }; use stable_mir::{ mir::{ - alloc::GlobalAlloc, - mono::{Instance, InstanceKind}, - visit::{Location, PlaceContext}, AggregateKind, CastKind, LocalDecl, MirVisitor, NonDivergingIntrinsic, Operand, Place, PointerCoercion, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + alloc::GlobalAlloc, + mono::{Instance, InstanceKind}, + visit::{Location, PlaceContext}, }, ty::{AdtKind, ConstantKind, RigidTy, TyKind}, }; diff --git a/kani-compiler/src/kani_middle/transform/check_values.rs b/kani-compiler/src/kani_middle/transform/check_values.rs index a81f76d25393..d0fd553ef5cf 100644 --- a/kani-compiler/src/kani_middle/transform/check_values.rs +++ b/kani-compiler/src/kani_middle/transform/check_values.rs @@ -22,6 +22,7 @@ use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; use rustc_middle::ty::{Const, TyCtxt}; use rustc_smir::rustc_internal; +use stable_mir::CrateDef; use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape, WrappingRange}; use stable_mir::mir::mono::{Instance, InstanceKind}; use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef}; @@ -32,7 +33,6 @@ use stable_mir::mir::{ }; use stable_mir::target::{MachineInfo, MachineSize}; use stable_mir::ty::{AdtKind, IndexedVal, MirConst, RigidTy, Ty, TyKind, UintTy}; -use stable_mir::CrateDef; use std::fmt::Debug; use strum_macros::AsRefStr; use tracing::{debug, trace}; diff --git a/kani-compiler/src/kani_middle/transform/contracts.rs b/kani-compiler/src/kani_middle/transform/contracts.rs index 71e06f5c49cd..480c480a0aea 100644 --- a/kani-compiler/src/kani_middle/transform/contracts.rs +++ b/kani-compiler/src/kani_middle/transform/contracts.rs @@ -12,12 +12,12 @@ 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::CrateDef; use stable_mir::mir::mono::Instance; 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}; @@ -420,10 +420,10 @@ impl FunctionWithContractPass { &mut mode_call, InsertPosition::Before, ); - new_body.replace_terminator( - &mode_call, - Terminator { kind: TerminatorKind::Goto { target }, span }, - ); + new_body.replace_terminator(&mode_call, Terminator { + kind: TerminatorKind::Goto { target }, + span, + }); new_body.into() } diff --git a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs index 5a026db525a7..845916af5181 100644 --- a/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs +++ b/kani-compiler/src/kani_middle/transform/kani_intrinsics.rs @@ -14,7 +14,7 @@ use crate::kani_middle::transform::body::{ }; 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, + PointeeLayout, get_mem_init_fn_def, mk_layout_operand, resolve_mem_init_fn, }; use crate::kani_middle::transform::check_values::{build_limits, ty_validity_per_offset}; use crate::kani_middle::transform::{TransformPass, TransformationType}; @@ -22,8 +22,8 @@ use crate::kani_queries::QueryDb; use rustc_middle::ty::TyCtxt; use stable_mir::mir::mono::Instance; use stable_mir::mir::{ - BasicBlock, BinOp, Body, ConstOperand, Mutability, Operand, Place, Rvalue, Statement, - StatementKind, Terminator, TerminatorKind, UnwindAction, RETURN_LOCAL, + BasicBlock, BinOp, Body, ConstOperand, Mutability, Operand, Place, RETURN_LOCAL, Rvalue, + Statement, StatementKind, Terminator, TerminatorKind, UnwindAction, }; use stable_mir::target::MachineInfo; use stable_mir::ty::{FnDef, MirConst, RigidTy, Ty, TyKind, UintTy}; diff --git a/kani-compiler/src/kani_middle/transform/mod.rs b/kani-compiler/src/kani_middle/transform/mod.rs index 2d963cd1d6eb..7d97a49f287a 100644 --- a/kani-compiler/src/kani_middle/transform/mod.rs +++ b/kani-compiler/src/kani_middle/transform/mod.rs @@ -27,8 +27,8 @@ use crate::kani_middle::transform::stubs::{ExternFnStubPass, FnStubPass}; use crate::kani_queries::QueryDb; use dump_mir_pass::DumpMirPass; use rustc_middle::ty::TyCtxt; -use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::mir::Body; +use stable_mir::mir::mono::{Instance, MonoItem}; use std::collections::HashMap; use std::fmt::Debug; @@ -79,22 +79,16 @@ impl BodyTransformation { // would also make sense to check that the values are initialized before checking their // validity. In the future, it would be nice to have a mechanism to skip automatically // generated code for future instrumentation passes. - transformer.add_pass( - queries, - UninitPass { - // Since this uses demonic non-determinism under the hood, should not assume the assertion. - check_type: CheckType::new_assert(tcx), - mem_init_fn_cache: HashMap::new(), - }, - ); - transformer.add_pass( - queries, - IntrinsicGeneratorPass { - check_type, - mem_init_fn_cache: HashMap::new(), - arguments: queries.args().clone(), - }, - ); + transformer.add_pass(queries, UninitPass { + // Since this uses demonic non-determinism under the hood, should not assume the assertion. + check_type: CheckType::new_assert(tcx), + mem_init_fn_cache: HashMap::new(), + }); + transformer.add_pass(queries, IntrinsicGeneratorPass { + check_type, + mem_init_fn_cache: HashMap::new(), + arguments: queries.args().clone(), + }); transformer } diff --git a/kani-compiler/src/kani_middle/transform/stubs.rs b/kani-compiler/src/kani_middle/transform/stubs.rs index 3dbd667c3943..09d6e1fe8520 100644 --- a/kani-compiler/src/kani_middle/transform/stubs.rs +++ b/kani-compiler/src/kani_middle/transform/stubs.rs @@ -9,11 +9,11 @@ use crate::kani_middle::transform::{TransformPass, TransformationType}; use crate::kani_queries::QueryDb; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; +use stable_mir::CrateDef; use stable_mir::mir::mono::Instance; use stable_mir::mir::visit::{Location, MirVisitor}; use stable_mir::mir::{Body, ConstOperand, LocalDecl, Operand, Terminator, TerminatorKind}; use stable_mir::ty::{FnDef, MirConst, RigidTy, TyKind}; -use stable_mir::CrateDef; use std::collections::HashMap; use std::fmt::Debug; use tracing::{debug, trace}; diff --git a/kani-compiler/src/session.rs b/kani-compiler/src/session.rs index f5dd78ff86b0..d56877d11805 100644 --- a/kani-compiler/src/session.rs +++ b/kani-compiler/src/session.rs @@ -7,18 +7,18 @@ use crate::args::Arguments; use rustc_data_structures::sync::Lrc; use rustc_errors::emitter::Emitter; use rustc_errors::{ - emitter::HumanReadableErrorType, fallback_fluent_bundle, json::JsonEmitter, ColorConfig, - DiagInner, + ColorConfig, DiagInner, emitter::HumanReadableErrorType, fallback_fluent_bundle, + json::JsonEmitter, }; -use rustc_session::config::ErrorOutputType; use rustc_session::EarlyDiagCtxt; +use rustc_session::config::ErrorOutputType; use rustc_span::source_map::FilePathMapping; use rustc_span::source_map::SourceMap; use std::io; use std::io::IsTerminal; use std::panic; use std::sync::LazyLock; -use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; +use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt}; use tracing_tree::HierarchicalLayer; /// Environment variable used to control this session log tracing. diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index c3cfc113af64..72de44e36014 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -15,7 +15,7 @@ use crate::args::cargo::CargoTargetArgs; use crate::util::warning; use cargo::CargoCommonArgs; use clap::builder::{PossibleValue, TypedValueParser}; -use clap::{error::ContextKind, error::ContextValue, error::Error, error::ErrorKind, ValueEnum}; +use clap::{ValueEnum, error::ContextKind, error::ContextValue, error::Error, error::ErrorKind}; use kani_metadata::CbmcSolver; use std::ffi::OsString; use std::path::PathBuf; diff --git a/kani-driver/src/args_toml.rs b/kani-driver/src/args_toml.rs index 37b38a425597..2fff01c4f5f9 100644 --- a/kani-driver/src/args_toml.rs +++ b/kani-driver/src/args_toml.rs @@ -1,14 +1,14 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use clap::Parser; use std::collections::BTreeMap; use std::ffi::OsString; use std::path::PathBuf; use std::process::Command; -use toml::value::Table; use toml::Value; +use toml::value::Table; /// Produce the list of arguments to pass to ourself (cargo-kani). /// diff --git a/kani-driver/src/assess/metadata.rs b/kani-driver/src/assess/metadata.rs index d555f1e4d309..14018541d0e5 100644 --- a/kani-driver/src/assess/metadata.rs +++ b/kani-driver/src/assess/metadata.rs @@ -15,11 +15,11 @@ use std::path::Path; use anyhow::Result; use serde::{Deserialize, Serialize}; +use super::AssessArgs; use super::table_builder::TableBuilder; use super::table_failure_reasons::FailureReasonsTableRow; use super::table_promising_tests::PromisingTestsTableRow; use super::table_unsupported_features::UnsupportedFeaturesTableRow; -use super::AssessArgs; /// The structure of `.kani-assess-metadata.json` files. This is a the structure for both /// assess (standard) and scan. It it meant to hold results for one or more packages. diff --git a/kani-driver/src/assess/mod.rs b/kani-driver/src/assess/mod.rs index f5b2ccb63035..4ee82e48389c 100644 --- a/kani-driver/src/assess/mod.rs +++ b/kani-driver/src/assess/mod.rs @@ -1,8 +1,8 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use self::metadata::{write_metadata, AssessMetadata}; -use anyhow::{bail, Result}; +use self::metadata::{AssessMetadata, write_metadata}; +use anyhow::{Result, bail}; use kani_metadata::KaniMetadata; use crate::assess::table_builder::TableBuilder; diff --git a/kani-driver/src/assess/scan.rs b/kani-driver/src/assess/scan.rs index ef33f91eb522..7ce29bb64b33 100644 --- a/kani-driver/src/assess/scan.rs +++ b/kani-driver/src/assess/scan.rs @@ -9,8 +9,8 @@ use std::time::Instant; use anyhow::Result; use cargo_metadata::Package; -use crate::session::setup_cargo_command; use crate::session::KaniSession; +use crate::session::setup_cargo_command; use super::metadata::AssessMetadata; use super::metadata::{aggregate_metadata, read_metadata}; diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index ef289cead4c2..191b104a8c1c 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::args::VerificationArgs; -use crate::call_single_file::{to_rustc_arg, LibConfig}; +use crate::call_single_file::{LibConfig, to_rustc_arg}; use crate::project::Artifact; -use crate::session::{lib_folder, lib_no_core_folder, setup_cargo_command, KaniSession}; +use crate::session::{KaniSession, lib_folder, lib_no_core_folder, setup_cargo_command}; use crate::util; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel}; use cargo_metadata::{ Artifact as RustcArtifact, Message, Metadata, MetadataCommand, Package, Target, diff --git a/kani-driver/src/call_cbmc.rs b/kani-driver/src/call_cbmc.rs index bc4424aeb231..17e99bf17d0f 100644 --- a/kani-driver/src/call_cbmc.rs +++ b/kani-driver/src/call_cbmc.rs @@ -1,12 +1,12 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use kani_metadata::{CbmcSolver, HarnessMetadata}; use regex::Regex; use rustc_demangle::demangle; -use std::collections::btree_map::Entry; use std::collections::BTreeMap; +use std::collections::btree_map::Entry; use std::ffi::OsString; use std::fmt::Write; use std::path::Path; @@ -16,7 +16,7 @@ use std::time::{Duration, Instant}; use crate::args::{OutputFormat, VerificationArgs}; use crate::cbmc_output_parser::{ - extract_results, process_cbmc_output, CheckStatus, Property, VerificationOutput, + CheckStatus, Property, VerificationOutput, extract_results, process_cbmc_output, }; use crate::cbmc_property_renderer::{format_coverage, format_result, kani_cbmc_output_filter}; use crate::coverage::cov_results::{CoverageCheck, CoverageResults}; diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index 868d1adce636..63ca71d5b8d1 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -7,7 +7,7 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; -use crate::session::{lib_folder, KaniSession}; +use crate::session::{KaniSession, lib_folder}; pub struct LibConfig { args: Vec, diff --git a/kani-driver/src/cbmc_property_renderer.rs b/kani-driver/src/cbmc_property_renderer.rs index a0202169863c..145e0c9c63aa 100644 --- a/kani-driver/src/cbmc_property_renderer.rs +++ b/kani-driver/src/cbmc_property_renderer.rs @@ -22,127 +22,97 @@ static CBMC_ALT_DESCRIPTIONS: Lazy = Lazy::new(|| { map.insert("error_label", vec![]); map.insert("division-by-zero", vec![("division by zero", None)]); map.insert("enum-range-check", vec![("enum range check", None)]); - map.insert( - "undefined-shift", - vec![ - ("shift distance is negative", None), - ("shift distance too large", None), - ("shift operand is negative", None), - ("shift of non-integer type", None), - ], - ); - map.insert( - "overflow", - vec![ - ("result of signed mod is not representable", None), - ("arithmetic overflow on signed type conversion", None), - ("arithmetic overflow on signed division", None), - ("arithmetic overflow on signed unary minus", None), - ("arithmetic overflow on signed shl", None), - ("arithmetic overflow on unsigned unary minus", None), - ("arithmetic overflow on signed +", Some("arithmetic overflow on signed addition")), - ("arithmetic overflow on signed -", Some("arithmetic overflow on signed subtraction")), - ( - "arithmetic overflow on signed *", - Some("arithmetic overflow on signed multiplication"), - ), - ("arithmetic overflow on unsigned +", Some("arithmetic overflow on unsigned addition")), - ( - "arithmetic overflow on unsigned -", - Some("arithmetic overflow on unsigned subtraction"), - ), - ( - "arithmetic overflow on unsigned *", - Some("arithmetic overflow on unsigned multiplication"), - ), - ("arithmetic overflow on floating-point typecast", None), - ("arithmetic overflow on floating-point division", None), - ("arithmetic overflow on floating-point addition", None), - ("arithmetic overflow on floating-point subtraction", None), - ("arithmetic overflow on floating-point multiplication", None), - ("arithmetic overflow on unsigned to signed type conversion", None), - ("arithmetic overflow on float to signed integer type conversion", None), - ("arithmetic overflow on signed to unsigned type conversion", None), - ("arithmetic overflow on unsigned to unsigned type conversion", None), - ("arithmetic overflow on float to unsigned integer type conversion", None), - ], - ); - map.insert( - "NaN", - vec![ - ("NaN on +", Some("NaN on addition")), - ("NaN on -", Some("NaN on subtraction")), - ("NaN on /", Some("NaN on division")), - ("NaN on *", Some("NaN on multiplication")), - ], - ); + map.insert("undefined-shift", vec![ + ("shift distance is negative", None), + ("shift distance too large", None), + ("shift operand is negative", None), + ("shift of non-integer type", None), + ]); + map.insert("overflow", vec![ + ("result of signed mod is not representable", None), + ("arithmetic overflow on signed type conversion", None), + ("arithmetic overflow on signed division", None), + ("arithmetic overflow on signed unary minus", None), + ("arithmetic overflow on signed shl", None), + ("arithmetic overflow on unsigned unary minus", None), + ("arithmetic overflow on signed +", Some("arithmetic overflow on signed addition")), + ("arithmetic overflow on signed -", Some("arithmetic overflow on signed subtraction")), + ("arithmetic overflow on signed *", Some("arithmetic overflow on signed multiplication")), + ("arithmetic overflow on unsigned +", Some("arithmetic overflow on unsigned addition")), + ("arithmetic overflow on unsigned -", Some("arithmetic overflow on unsigned subtraction")), + ( + "arithmetic overflow on unsigned *", + Some("arithmetic overflow on unsigned multiplication"), + ), + ("arithmetic overflow on floating-point typecast", None), + ("arithmetic overflow on floating-point division", None), + ("arithmetic overflow on floating-point addition", None), + ("arithmetic overflow on floating-point subtraction", None), + ("arithmetic overflow on floating-point multiplication", None), + ("arithmetic overflow on unsigned to signed type conversion", None), + ("arithmetic overflow on float to signed integer type conversion", None), + ("arithmetic overflow on signed to unsigned type conversion", None), + ("arithmetic overflow on unsigned to unsigned type conversion", None), + ("arithmetic overflow on float to unsigned integer type conversion", None), + ]); + map.insert("NaN", vec![ + ("NaN on +", Some("NaN on addition")), + ("NaN on -", Some("NaN on subtraction")), + ("NaN on /", Some("NaN on division")), + ("NaN on *", Some("NaN on multiplication")), + ]); map.insert("pointer", vec![("same object violation", None)]); - map.insert( - "pointer_arithmetic", - vec![ - ("pointer relation: deallocated dynamic object", None), - ("pointer relation: dead object", None), - ("pointer relation: pointer NULL", None), - ("pointer relation: pointer invalid", None), - ("pointer relation: pointer outside dynamic object bounds", None), - ("pointer relation: pointer outside object bounds", None), - ("pointer relation: invalid integer address", None), - ("pointer arithmetic: deallocated dynamic object", None), - ("pointer arithmetic: dead object", None), - ("pointer arithmetic: pointer NULL", None), - ("pointer arithmetic: pointer invalid", None), - ("pointer arithmetic: pointer outside dynamic object bounds", None), - ("pointer arithmetic: pointer outside object bounds", None), - ("pointer arithmetic: invalid integer address", None), - ], - ); - map.insert( - "pointer_dereference", - vec![ - ( - "dereferenced function pointer must be", - Some("dereference failure: invalid function pointer"), - ), - ("dereference failure: pointer NULL", None), - ("dereference failure: pointer invalid", None), - ("dereference failure: deallocated dynamic object", None), - ("dereference failure: dead object", None), - ("dereference failure: pointer outside dynamic object bounds", None), - ("dereference failure: pointer outside object bounds", None), - ("dereference failure: invalid integer address", None), - ], - ); + map.insert("pointer_arithmetic", vec![ + ("pointer relation: deallocated dynamic object", None), + ("pointer relation: dead object", None), + ("pointer relation: pointer NULL", None), + ("pointer relation: pointer invalid", None), + ("pointer relation: pointer outside dynamic object bounds", None), + ("pointer relation: pointer outside object bounds", None), + ("pointer relation: invalid integer address", None), + ("pointer arithmetic: deallocated dynamic object", None), + ("pointer arithmetic: dead object", None), + ("pointer arithmetic: pointer NULL", None), + ("pointer arithmetic: pointer invalid", None), + ("pointer arithmetic: pointer outside dynamic object bounds", None), + ("pointer arithmetic: pointer outside object bounds", None), + ("pointer arithmetic: invalid integer address", None), + ]); + map.insert("pointer_dereference", vec![ + ( + "dereferenced function pointer must be", + Some("dereference failure: invalid function pointer"), + ), + ("dereference failure: pointer NULL", None), + ("dereference failure: pointer invalid", None), + ("dereference failure: deallocated dynamic object", None), + ("dereference failure: dead object", None), + ("dereference failure: pointer outside dynamic object bounds", None), + ("dereference failure: pointer outside object bounds", None), + ("dereference failure: invalid integer address", None), + ]); // These are very hard to understand without more context. - map.insert( - "pointer_primitives", - vec![ - ("pointer invalid", None), - ("deallocated dynamic object", Some("pointer to deallocated dynamic object")), - ("dead object", Some("pointer to dead object")), - ("pointer outside dynamic object bounds", None), - ("pointer outside object bounds", None), - ("invalid integer address", None), - ], - ); - map.insert( - "array_bounds", - vec![ - ("lower bound", Some("index out of bounds")), - // This one is redundant: - // ("dynamic object upper bound", Some("access out of bounds")), - ( - "upper bound", - Some("index out of bounds: the length is less than or equal to the given index"), - ), - ], - ); - map.insert( - "bit_count", - vec![ - ("count trailing zeros is undefined for value zero", None), - ("count leading zeros is undefined for value zero", None), - ], - ); + map.insert("pointer_primitives", vec![ + ("pointer invalid", None), + ("deallocated dynamic object", Some("pointer to deallocated dynamic object")), + ("dead object", Some("pointer to dead object")), + ("pointer outside dynamic object bounds", None), + ("pointer outside object bounds", None), + ("invalid integer address", None), + ]); + map.insert("array_bounds", vec![ + ("lower bound", Some("index out of bounds")), + // This one is redundant: + // ("dynamic object upper bound", Some("access out of bounds")), + ( + "upper bound", + Some("index out of bounds: the length is less than or equal to the given index"), + ), + ]); + map.insert("bit_count", vec![ + ("count trailing zeros is undefined for value zero", None), + ("count leading zeros is undefined for value zero", None), + ]); map.insert("memory-leak", vec![("dynamically allocated memory never freed", None)]); // These pre-conditions should not print temporary variables since they are embedded in the libc implementation. // They are added via `__CPROVER_precondition`. diff --git a/kani-driver/src/concrete_playback/playback.rs b/kani-driver/src/concrete_playback/playback.rs index c56694ad32fd..833fdbc02384 100644 --- a/kani-driver/src/concrete_playback/playback.rs +++ b/kani-driver/src/concrete_playback/playback.rs @@ -6,8 +6,8 @@ use crate::args::common::Verbosity; use crate::args::playback_args::{CargoPlaybackArgs, KaniPlaybackArgs, MessageFormat}; use crate::call_cargo::cargo_config_args; -use crate::call_single_file::{base_rustc_flags, LibConfig}; -use crate::session::{lib_playback_folder, setup_cargo_command, InstallType}; +use crate::call_single_file::{LibConfig, base_rustc_flags}; +use crate::session::{InstallType, lib_playback_folder, setup_cargo_command}; use crate::{session, util}; use anyhow::Result; use std::ffi::OsString; diff --git a/kani-driver/src/concrete_playback/test_generator.rs b/kani-driver/src/concrete_playback/test_generator.rs index 5faa6299a5d3..5c4076f13591 100644 --- a/kani-driver/src/concrete_playback/test_generator.rs +++ b/kani-driver/src/concrete_playback/test_generator.rs @@ -9,11 +9,11 @@ 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 concrete_vals_extractor::{ConcreteVal, extract_harness_values}; use kani_metadata::{HarnessKind, HarnessMetadata}; use std::collections::hash_map::DefaultHasher; use std::ffi::OsString; -use std::fs::{read_to_string, File}; +use std::fs::{File, read_to_string}; use std::hash::{Hash, Hasher}; use std::io::{BufRead, BufReader, Write}; use std::path::Path; @@ -500,10 +500,11 @@ mod tests { /// Check that the generated unit tests have the right formatting and indentation #[test] fn format_two_concrete_vals() { - let concrete_vals = [ - ConcreteVal { byte_arr: vec![0, 0], interp_val: "0".to_string() }, - ConcreteVal { byte_arr: vec![0, 0, 0, 0, 0, 0, 0, 0], interp_val: "0l".to_string() }, - ]; + let concrete_vals = + [ConcreteVal { byte_arr: vec![0, 0], interp_val: "0".to_string() }, ConcreteVal { + byte_arr: vec![0, 0, 0, 0, 0, 0, 0, 0], + interp_val: "0l".to_string(), + }]; let actual: Vec<_> = format_concrete_vals(&concrete_vals).collect(); let expected = vec![ format!("{:<8}// 0", " "), @@ -597,10 +598,11 @@ mod tests { #[test] fn check_rustfmt_args_some_line_ranges() { - let file_line_ranges = [ - FileLineRange { file: "file1".to_string(), line_range: None }, - FileLineRange { file: "path/to/file2".to_string(), line_range: Some((1, 3)) }, - ]; + let file_line_ranges = + [FileLineRange { file: "file1".to_string(), line_range: None }, FileLineRange { + file: "path/to/file2".to_string(), + line_range: Some((1, 3)), + }]; let args = rustfmt_args(&file_line_ranges); let expected: Vec = [ "--unstable-features", diff --git a/kani-driver/src/coverage/cov_session.rs b/kani-driver/src/coverage/cov_session.rs index bf6b34d3cd0b..60fa2f1c242f 100644 --- a/kani-driver/src/coverage/cov_session.rs +++ b/kani-driver/src/coverage/cov_session.rs @@ -5,10 +5,10 @@ use std::fs; use std::fs::File; use std::io::Write; +use crate::KaniSession; use crate::harness_runner::HarnessResult; use crate::project::Project; -use crate::KaniSession; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; impl KaniSession { /// Saves metadata required for coverage-related features. diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index 7e432932ab29..5a77a6a125a3 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use kani_metadata::{ArtifactType, HarnessMetadata}; use rayon::prelude::*; use std::path::Path; diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index d3bd697d2ea4..e8ede555f180 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -5,9 +5,9 @@ use std::ffi::OsString; use std::process::ExitCode; use anyhow::Result; -use time::{format_description, OffsetDateTime}; +use time::{OffsetDateTime, format_description}; -use args::{check_is_valid, CargoKaniSubcommand}; +use args::{CargoKaniSubcommand, check_is_valid}; use args_toml::join_args; use crate::args::StandaloneSubcommand; diff --git a/kani-driver/src/metadata.rs b/kani-driver/src/metadata.rs index 3f9cd8f2bf84..625d8b4bbcaa 100644 --- a/kani-driver/src/metadata.rs +++ b/kani-driver/src/metadata.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use std::path::Path; use tracing::{debug, trace}; diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index e0d2d2edd139..363050958d61 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -9,7 +9,7 @@ use crate::session::KaniSession; use crate::util::crate_name; use anyhow::{Context, Result}; use kani_metadata::{ - artifact::convert_type, ArtifactType, ArtifactType::*, HarnessMetadata, KaniMetadata, + ArtifactType, ArtifactType::*, HarnessMetadata, KaniMetadata, artifact::convert_type, }; use std::env::current_dir; use std::fs; diff --git a/kani-driver/src/session.rs b/kani-driver/src/session.rs index 03cfb108e324..2df431eb4587 100644 --- a/kani-driver/src/session.rs +++ b/kani-driver/src/session.rs @@ -1,10 +1,10 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::args::common::Verbosity; use crate::args::VerificationArgs; +use crate::args::common::Verbosity; use crate::util::render_command; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use std::io::IsTerminal; use std::io::Write; use std::path::{Path, PathBuf}; @@ -13,7 +13,7 @@ use std::sync::Mutex; use std::time::Instant; use strum_macros::Display; use tracing::level_filters::LevelFilter; -use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; +use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt}; /// Environment variable used to control this session log tracing. /// This is the same variable used to control `kani-compiler` logs. Note that you can still control diff --git a/kani_metadata/src/artifact.rs b/kani_metadata/src/artifact.rs index 078cad14f679..12f8f9a49566 100644 --- a/kani_metadata/src/artifact.rs +++ b/kani_metadata/src/artifact.rs @@ -94,7 +94,7 @@ impl Deref for ArtifactType { #[cfg(test)] mod test { - use super::{convert_type, ArtifactType::*}; + use super::{ArtifactType::*, convert_type}; use std::path::PathBuf; #[test] diff --git a/library/kani/src/futures.rs b/library/kani/src/futures.rs index 637f24a80f6a..37cab17b0a5b 100644 --- a/library/kani/src/futures.rs +++ b/library/kani/src/futures.rs @@ -163,6 +163,7 @@ pub struct JoinHandle { index: usize, } +#[allow(static_mut_refs)] impl Future for JoinHandle { type Output = (); @@ -180,6 +181,7 @@ impl Future for JoinHandle { /// /// This function can only be called inside a future passed to [`block_on_with_spawn`]. #[crate::unstable(feature = "async-lib", issue = 2559, reason = "experimental async support")] +#[allow(static_mut_refs)] pub fn spawn + Sync + 'static>(fut: F) -> JoinHandle { unsafe { if let Some(executor) = GLOBAL_EXECUTOR.as_mut() { @@ -195,6 +197,7 @@ pub fn spawn + Sync + 'static>(fut: F) -> JoinHandle { /// /// Contrary to [`block_on`], this allows `spawn`ing other futures #[crate::unstable(feature = "async-lib", issue = 2559, reason = "experimental async support")] +#[allow(static_mut_refs)] pub fn block_on_with_spawn + Sync + 'static>( fut: F, scheduling_plan: impl SchedulingStrategy, diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index c3e9ae5497cc..053af760067b 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -44,7 +44,7 @@ pub fn concrete_playback_run(_: Vec>, _: F) { unreachable!("Concrete playback does not work during verification") } -pub use futures::{block_on, block_on_with_spawn, spawn, yield_now, RoundRobin}; +pub use futures::{RoundRobin, block_on, block_on_with_spawn, spawn, yield_now}; // Kani proc macros must be in a separate crate pub use kani_macros::*; diff --git a/library/kani/src/vec.rs b/library/kani/src/vec.rs index a3ec05a9c953..d9da65aa4537 100644 --- a/library/kani/src/vec.rs +++ b/library/kani/src/vec.rs @@ -1,6 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::{any, any_where, Arbitrary}; +use crate::{Arbitrary, any, any_where}; /// Generates an arbitrary vector whose length is at most MAX_LENGTH. pub fn any_vec() -> Vec diff --git a/library/kani_macros/src/derive.rs b/library/kani_macros/src/derive.rs index e89f6f089795..04b288069387 100644 --- a/library/kani_macros/src/derive.rs +++ b/library/kani_macros/src/derive.rs @@ -9,13 +9,13 @@ //! struct S; //! //! ``` -use proc_macro2::{Ident, Span, TokenStream}; use proc_macro_error2::abort; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::{ - parse_macro_input, parse_quote, Data, DataEnum, DeriveInput, Fields, GenericParam, Generics, - Index, + Data, DataEnum, DeriveInput, Fields, GenericParam, Generics, Index, parse_macro_input, + parse_quote, }; pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::TokenStream { diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 8a1d2d80f864..3e6c9d018422 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -409,7 +409,7 @@ mod sysroot { use { quote::{format_ident, quote}, syn::parse::{Parse, ParseStream}, - syn::{parse_macro_input, ItemFn}, + syn::{ItemFn, parse_macro_input}, }; /// Annotate the harness with a #[kanitool::] with optional arguments. diff --git a/library/kani_macros/src/sysroot/contracts/bootstrap.rs b/library/kani_macros/src/sysroot/contracts/bootstrap.rs index 8ad21e8a9c6b..5a9e12a62ae2 100644 --- a/library/kani_macros/src/sysroot/contracts/bootstrap.rs +++ b/library/kani_macros/src/sysroot/contracts/bootstrap.rs @@ -8,7 +8,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{Expr, ItemFn, Stmt}; -use super::{helpers::*, ContractConditionsHandler, INTERNAL_RESULT_IDENT}; +use super::{ContractConditionsHandler, INTERNAL_RESULT_IDENT, helpers::*}; impl<'a> ContractConditionsHandler<'a> { /// Generate initial contract. diff --git a/library/kani_macros/src/sysroot/contracts/check.rs b/library/kani_macros/src/sysroot/contracts/check.rs index 314a5e74bc98..863cf882f063 100644 --- a/library/kani_macros/src/sysroot/contracts/check.rs +++ b/library/kani_macros/src/sysroot/contracts/check.rs @@ -6,11 +6,11 @@ use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::quote; use std::mem; -use syn::{parse_quote, Block, Expr, FnArg, Local, LocalInit, Pat, PatIdent, ReturnType, Stmt}; +use syn::{Block, Expr, FnArg, Local, LocalInit, Pat, PatIdent, ReturnType, Stmt, parse_quote}; use super::{ - helpers::*, shared::build_ensures, ClosureType, ContractConditionsData, - ContractConditionsHandler, INTERNAL_RESULT_IDENT, + ClosureType, ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT, + helpers::*, shared::build_ensures, }; const WRAPPER_ARG: &str = "_wrapper_arg"; diff --git a/library/kani_macros/src/sysroot/contracts/helpers.rs b/library/kani_macros/src/sysroot/contracts/helpers.rs index 8db6218e2693..49d101dad32a 100644 --- a/library/kani_macros/src/sysroot/contracts/helpers.rs +++ b/library/kani_macros/src/sysroot/contracts/helpers.rs @@ -9,8 +9,8 @@ use proc_macro2::{Ident, Span}; use std::borrow::Cow; use syn::spanned::Spanned; use syn::{ - parse_quote, Attribute, Expr, ExprBlock, ExprCall, ExprPath, Local, LocalInit, PatIdent, Path, - Stmt, + Attribute, Expr, ExprBlock, ExprCall, ExprPath, Local, LocalInit, PatIdent, Path, Stmt, + parse_quote, }; /// If an explicit return type was provided it is returned, otherwise `()`. diff --git a/library/kani_macros/src/sysroot/contracts/initialize.rs b/library/kani_macros/src/sysroot/contracts/initialize.rs index 50c63173b601..bd06139b27f5 100644 --- a/library/kani_macros/src/sysroot/contracts/initialize.rs +++ b/library/kani_macros/src/sysroot/contracts/initialize.rs @@ -8,9 +8,9 @@ use proc_macro2::TokenStream as TokenStream2; use syn::ItemFn; use super::{ - helpers::{chunks_by, is_token_stream_2_comma, matches_path}, ContractConditionsData, ContractConditionsHandler, ContractConditionsType, ContractFunctionState, + helpers::{chunks_by, is_token_stream_2_comma, matches_path}, }; impl<'a> TryFrom<&'a syn::Attribute> for ContractFunctionState { diff --git a/library/kani_macros/src/sysroot/contracts/mod.rs b/library/kani_macros/src/sysroot/contracts/mod.rs index 281960bf041b..41be536ffb6d 100644 --- a/library/kani_macros/src/sysroot/contracts/mod.rs +++ b/library/kani_macros/src/sysroot/contracts/mod.rs @@ -395,7 +395,7 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; -use syn::{parse_macro_input, parse_quote, Expr, ExprClosure, ItemFn}; +use syn::{Expr, ExprClosure, ItemFn, parse_macro_input, parse_quote}; mod bootstrap; mod check; diff --git a/library/kani_macros/src/sysroot/contracts/replace.rs b/library/kani_macros/src/sysroot/contracts/replace.rs index 71913e630622..b28e178bea6d 100644 --- a/library/kani_macros/src/sysroot/contracts/replace.rs +++ b/library/kani_macros/src/sysroot/contracts/replace.rs @@ -9,9 +9,9 @@ use std::mem; use syn::Stmt; use super::{ + ClosureType, ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT, helpers::*, shared::{build_ensures, try_as_result_assign}, - ClosureType, ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT, }; impl<'a> ContractConditionsHandler<'a> { diff --git a/library/kani_macros/src/sysroot/contracts/shared.rs b/library/kani_macros/src/sysroot/contracts/shared.rs index f189a9f98b0c..d7e77ee2a8d4 100644 --- a/library/kani_macros/src/sysroot/contracts/shared.rs +++ b/library/kani_macros/src/sysroot/contracts/shared.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::quote; use std::hash::{DefaultHasher, Hash, Hasher}; -use syn::{spanned::Spanned, visit_mut::VisitMut, Expr, ExprCall, ExprClosure, ExprPath, Path}; +use syn::{Expr, ExprCall, ExprClosure, ExprPath, Path, spanned::Spanned, visit_mut::VisitMut}; use super::INTERNAL_RESULT_IDENT; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8280b9442a9b..28cedff1d1de 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-09-20" +channel = "nightly-2024-09-23" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] diff --git a/rustfmt.toml b/rustfmt.toml index 95988ef7f52a..44cbfe4a3dc9 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -3,7 +3,7 @@ # Run rustfmt with this config (it should be picked up automatically). edition = "2021" -version = "Two" +style_edition = "2024" use_small_heuristics = "Max" merge_derives = false diff --git a/src/cmd.rs b/src/cmd.rs index 77204f092e74..1ab9216a4d46 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -7,7 +7,7 @@ use std::ffi::OsString; use std::process::Command; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; /// Helper trait to fallibly run commands pub trait AutoRun { diff --git a/src/lib.rs b/src/lib.rs index 0015829f61d3..f24163d3213c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::{env, fs}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; /// Effectively the entry point (i.e. `main` function) for both our proxy binaries. /// `bin` should be either `kani` or `cargo-kani` diff --git a/src/os_hacks.rs b/src/os_hacks.rs index ca064a662901..8d3a20575d7d 100644 --- a/src/os_hacks.rs +++ b/src/os_hacks.rs @@ -8,7 +8,7 @@ use std::path::Path; use std::process::Command; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use os_info::Info; use crate::cmd::AutoRun; diff --git a/src/setup.rs b/src/setup.rs index 58387f7031b7..eb1227269ccc 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -8,7 +8,7 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use crate::cmd::AutoRun; use crate::os_hacks; diff --git a/tests/cargo-kani/percent-encoding/src/lib.rs b/tests/cargo-kani/percent-encoding/src/lib.rs index 8da898139aa8..e9667b7cb375 100644 --- a/tests/cargo-kani/percent-encoding/src/lib.rs +++ b/tests/cargo-kani/percent-encoding/src/lib.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; +use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode}; #[kani::proof] pub fn check_encoding() { diff --git a/tests/cargo-kani/small-vec/src/lib.rs b/tests/cargo-kani/small-vec/src/lib.rs index 45b07a345285..dbf9d7f9065c 100644 --- a/tests/cargo-kani/small-vec/src/lib.rs +++ b/tests/cargo-kani/small-vec/src/lib.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use smallvec::{smallvec, SmallVec}; +use smallvec::{SmallVec, smallvec}; #[kani::proof] #[kani::unwind(4)] diff --git a/tests/cargo-kani/stubbing-use-as-foreign/src/lib.rs b/tests/cargo-kani/stubbing-use-as-foreign/src/lib.rs index 34d29bb3423d..bac3b9b1390c 100644 --- a/tests/cargo-kani/stubbing-use-as-foreign/src/lib.rs +++ b/tests/cargo-kani/stubbing-use-as-foreign/src/lib.rs @@ -5,9 +5,9 @@ //! YYY;`) referring to external code when resolving paths in `kani::stub` //! attributes. +use other_crate::MyType as MyFavoriteType; use other_crate::inner_mod::magic_number42 as forty_two; use other_crate::magic_number13 as thirteen; -use other_crate::MyType as MyFavoriteType; #[kani::proof] #[kani::stub(zero, thirteen)] diff --git a/tests/cargo-kani/stubbing-use-foreign/src/lib.rs b/tests/cargo-kani/stubbing-use-foreign/src/lib.rs index 07e86207a944..def0ce5a73eb 100644 --- a/tests/cargo-kani/stubbing-use-foreign/src/lib.rs +++ b/tests/cargo-kani/stubbing-use-foreign/src/lib.rs @@ -4,9 +4,9 @@ //! This tests whether we take into account use statements (`use XXX;`) //! referring to external code when resolving paths in `kani::stub` attributes. +use other_crate::MyType; use other_crate::inner_mod::magic_number42; use other_crate::magic_number13; -use other_crate::MyType; #[kani::proof] #[kani::stub(zero, magic_number13)] diff --git a/tests/cargo-kani/vecdeque-cve/src/harness.rs b/tests/cargo-kani/vecdeque-cve/src/harness.rs index 5c36450ee36a..f8ec8f5fdbde 100644 --- a/tests/cargo-kani/vecdeque-cve/src/harness.rs +++ b/tests/cargo-kani/vecdeque-cve/src/harness.rs @@ -27,8 +27,8 @@ const MAX_CAPACITY: usize = usize::MAX >> 2; /// This module uses a version of VecDeque that includes the CVE fix. mod fixed_proofs { - use crate::fixed::VecDeque; use crate::MAX_CAPACITY; + use crate::fixed::VecDeque; /// Minimal example that we no longer expect to fail #[kani::proof] @@ -98,8 +98,8 @@ mod fixed_proofs { mod cve_proofs { // Modified version of vec_deque with reserve issue. - use crate::cve::VecDeque; use crate::MAX_CAPACITY; + use crate::cve::VecDeque; /// Minimal example that we expect to fail #[kani::proof] diff --git a/tests/expected/dealloc/stack/test.rs b/tests/expected/dealloc/stack/test.rs index 87da636d8916..a4b1e3f481e3 100644 --- a/tests/expected/dealloc/stack/test.rs +++ b/tests/expected/dealloc/stack/test.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use std::alloc::{dealloc, Layout}; +use std::alloc::{Layout, dealloc}; // This test checks that Kani flags the deallocation of a stack-allocated // variable diff --git a/tests/expected/realloc/null/main.rs b/tests/expected/realloc/null/main.rs index 668ad4f84c2f..6f5a3b8bd2ab 100644 --- a/tests/expected/realloc/null/main.rs +++ b/tests/expected/realloc/null/main.rs @@ -3,7 +3,7 @@ // Calling realloc with a null pointer fails -use std::alloc::{realloc, Layout}; +use std::alloc::{Layout, realloc}; #[kani::proof] fn main() { diff --git a/tests/expected/realloc/shrink/main.rs b/tests/expected/realloc/shrink/main.rs index 7c4bd377fea7..d7b31244a9f7 100644 --- a/tests/expected/realloc/shrink/main.rs +++ b/tests/expected/realloc/shrink/main.rs @@ -4,7 +4,7 @@ // Use realloc to shrink the size of the allocated block and make sure // out-of-bound accesses result in verification failure -use std::alloc::{alloc, dealloc, realloc, Layout}; +use std::alloc::{Layout, alloc, dealloc, realloc}; #[kani::proof] fn main() { diff --git a/tests/expected/realloc/zero_size/main.rs b/tests/expected/realloc/zero_size/main.rs index 3e347af7512f..620ce17fdd2d 100644 --- a/tests/expected/realloc/zero_size/main.rs +++ b/tests/expected/realloc/zero_size/main.rs @@ -3,7 +3,7 @@ // Calling realloc with a size of zero fails -use std::alloc::{alloc, realloc, Layout}; +use std::alloc::{Layout, alloc, realloc}; #[kani::proof] fn main() { diff --git a/tests/expected/shadow/uninit_array/test.rs b/tests/expected/shadow/uninit_array/test.rs index 8a9536e5a8e8..046d0f9f551f 100644 --- a/tests/expected/shadow/uninit_array/test.rs +++ b/tests/expected/shadow/uninit_array/test.rs @@ -6,7 +6,7 @@ // It checks that shadow memory can be used to track whether a memory location // is initialized. -use std::alloc::{alloc, dealloc, Layout}; +use std::alloc::{Layout, alloc, dealloc}; static mut SM: kani::shadow::ShadowMem = kani::shadow::ShadowMem::new(false); diff --git a/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs b/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs index 318f234c31be..d4e4c4b758da 100644 --- a/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs +++ b/tests/expected/uninit/alloc-to-slice/alloc-to-slice.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Z uninit-checks -use std::alloc::{alloc, Layout}; +use std::alloc::{Layout, alloc}; use std::slice::from_raw_parts; /// Checks that Kani catches an attempt to form a slice from uninitialized memory. diff --git a/tests/expected/uninit/atomic/atomic.rs b/tests/expected/uninit/atomic/atomic.rs index 63c85af41a3f..78b9dd2fdea0 100644 --- a/tests/expected/uninit/atomic/atomic.rs +++ b/tests/expected/uninit/atomic/atomic.rs @@ -4,7 +4,7 @@ #![feature(core_intrinsics)] -use std::alloc::{alloc, Layout}; +use std::alloc::{Layout, alloc}; use std::sync::atomic::{AtomicU8, Ordering}; // Checks if memory initialization checks correctly fail when uninitialized memory is passed to diff --git a/tests/expected/uninit/intrinsics/intrinsics.rs b/tests/expected/uninit/intrinsics/intrinsics.rs index b023853b2fbc..33d5e72b1da9 100644 --- a/tests/expected/uninit/intrinsics/intrinsics.rs +++ b/tests/expected/uninit/intrinsics/intrinsics.rs @@ -5,7 +5,7 @@ #![feature(core_intrinsics)] -use std::alloc::{alloc, alloc_zeroed, Layout}; +use std::alloc::{Layout, alloc, alloc_zeroed}; use std::intrinsics::*; #[kani::proof] diff --git a/tests/kani/AsyncAwait/spawn.rs b/tests/kani/AsyncAwait/spawn.rs index 76583f10d615..fa203b2a5c12 100644 --- a/tests/kani/AsyncAwait/spawn.rs +++ b/tests/kani/AsyncAwait/spawn.rs @@ -7,8 +7,8 @@ //! This file tests the executor and spawn infrastructure from the Kani library. use std::sync::{ - atomic::{AtomicI64, Ordering}, Arc, + atomic::{AtomicI64, Ordering}, }; #[kani::proof(schedule = kani::RoundRobin::default())] diff --git a/tests/kani/Coroutines/issue-1593.rs b/tests/kani/Coroutines/issue-1593.rs index 9028dadad3d3..b85aefba9378 100644 --- a/tests/kani/Coroutines/issue-1593.rs +++ b/tests/kani/Coroutines/issue-1593.rs @@ -8,8 +8,8 @@ // in the context of vtables. use std::sync::{ - atomic::{AtomicI64, Ordering}, Arc, + atomic::{AtomicI64, Ordering}, }; #[kani::proof] diff --git a/tests/kani/FatPointers/boxmuttrait.rs b/tests/kani/FatPointers/boxmuttrait.rs index aa965f56c71e..a9469d7273f1 100644 --- a/tests/kani/FatPointers/boxmuttrait.rs +++ b/tests/kani/FatPointers/boxmuttrait.rs @@ -9,7 +9,7 @@ #![feature(ptr_metadata)] use std::any::Any; -use std::io::{sink, Write}; +use std::io::{Write, sink}; use std::ptr::DynMetadata; include!("../Helpers/vtable_utils_ignore.rs"); diff --git a/tests/kani/FatPointers/boxmuttrait_fail.rs b/tests/kani/FatPointers/boxmuttrait_fail.rs index 4d4dc6fc8384..35d05a58c6f8 100644 --- a/tests/kani/FatPointers/boxmuttrait_fail.rs +++ b/tests/kani/FatPointers/boxmuttrait_fail.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-verify-fail -use std::io::{sink, Write}; +use std::io::{Write, sink}; #[kani::proof] fn main() { diff --git a/tests/kani/Intrinsics/Atomic/Stable/Fence/main.rs b/tests/kani/Intrinsics/Atomic/Stable/Fence/main.rs index a119364f9b42..6ea70ab3b830 100644 --- a/tests/kani/Intrinsics/Atomic/Stable/Fence/main.rs +++ b/tests/kani/Intrinsics/Atomic/Stable/Fence/main.rs @@ -4,7 +4,7 @@ // Check that `atomic_fence` and other variants (stable version) can be // processed. -use std::sync::atomic::{fence, Ordering}; +use std::sync::atomic::{Ordering, fence}; #[kani::proof] fn main() { diff --git a/tests/kani/Intrinsics/RawEq/uninit_eq.rs b/tests/kani/Intrinsics/RawEq/uninit_eq.rs index 9e7f81dfaef1..390e0f774a4a 100644 --- a/tests/kani/Intrinsics/RawEq/uninit_eq.rs +++ b/tests/kani/Intrinsics/RawEq/uninit_eq.rs @@ -6,7 +6,7 @@ // uninitialized #![feature(core_intrinsics)] use std::intrinsics::raw_eq; -use std::mem::{zeroed, MaybeUninit}; +use std::mem::{MaybeUninit, zeroed}; #[kani::proof] fn main() { diff --git a/tests/kani/Intrinsics/RawEq/uninit_ne.rs b/tests/kani/Intrinsics/RawEq/uninit_ne.rs index 0f0642976671..73b5715b4846 100644 --- a/tests/kani/Intrinsics/RawEq/uninit_ne.rs +++ b/tests/kani/Intrinsics/RawEq/uninit_ne.rs @@ -6,7 +6,7 @@ // is uninitialized #![feature(core_intrinsics)] use std::intrinsics::raw_eq; -use std::mem::{zeroed, MaybeUninit}; +use std::mem::{MaybeUninit, zeroed}; #[kani::proof] fn main() { diff --git a/tests/kani/Realloc/two_reallocs.rs b/tests/kani/Realloc/two_reallocs.rs index 449e00cce56d..dc7b5ec3cd87 100644 --- a/tests/kani/Realloc/two_reallocs.rs +++ b/tests/kani/Realloc/two_reallocs.rs @@ -3,7 +3,7 @@ // Perform two reallocs in a row and make sure the data is properly copied -use std::alloc::{alloc, dealloc, realloc, Layout}; +use std::alloc::{Layout, alloc, dealloc, realloc}; #[kani::proof] fn main() { diff --git a/tests/kani/Stubbing/resolve_use.rs b/tests/kani/Stubbing/resolve_use.rs index ac1f6f7325f8..20c5faddfdc6 100644 --- a/tests/kani/Stubbing/resolve_use.rs +++ b/tests/kani/Stubbing/resolve_use.rs @@ -20,8 +20,8 @@ impl MyType { mod my_mod { use self::inner_mod::magic_number42; - use super::magic_number13; use super::MyType; + use super::magic_number13; mod inner_mod { pub fn magic_number42() -> u32 { diff --git a/tests/kani/Stubbing/resolve_use_as.rs b/tests/kani/Stubbing/resolve_use_as.rs index 790e26e575e9..5f55b8f7ac59 100644 --- a/tests/kani/Stubbing/resolve_use_as.rs +++ b/tests/kani/Stubbing/resolve_use_as.rs @@ -20,8 +20,8 @@ impl MyType { mod my_mod { use self::inner_mod::magic_number42 as forty_two; - use super::magic_number13 as thirteen; use super::MyType as MyFavoriteType; + use super::magic_number13 as thirteen; mod inner_mod { pub fn magic_number42() -> u32 { diff --git a/tests/kani/Uninit/alloc-to-slice.rs b/tests/kani/Uninit/alloc-to-slice.rs index 863d3b40b390..6f235fa41b51 100644 --- a/tests/kani/Uninit/alloc-to-slice.rs +++ b/tests/kani/Uninit/alloc-to-slice.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Z uninit-checks -use std::alloc::{alloc, Layout}; +use std::alloc::{Layout, alloc}; #[kani::proof] fn alloc_to_slice() { diff --git a/tests/kani/Uninit/alloc-zeroed-to-slice.rs b/tests/kani/Uninit/alloc-zeroed-to-slice.rs index d00ca4c6abff..1e463b3227a0 100644 --- a/tests/kani/Uninit/alloc-zeroed-to-slice.rs +++ b/tests/kani/Uninit/alloc-zeroed-to-slice.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Z uninit-checks -use std::alloc::{alloc_zeroed, Layout}; +use std::alloc::{Layout, alloc_zeroed}; use std::slice::from_raw_parts; #[kani::proof] diff --git a/tests/perf/hashset/src/lib.rs b/tests/perf/hashset/src/lib.rs index e2f1fc16666a..c9a3c01f93de 100644 --- a/tests/perf/hashset/src/lib.rs +++ b/tests/perf/hashset/src/lib.rs @@ -3,7 +3,7 @@ // kani-flags: -Z stubbing //! Try to verify HashSet basic behavior. -use std::collections::{hash_map::RandomState, HashSet}; +use std::collections::{HashSet, hash_map::RandomState}; use std::mem::{size_of, size_of_val, transmute}; #[allow(dead_code)] diff --git a/tests/script-based-pre/mem-init-reinstrumentation/alloc-zeroed.rs b/tests/script-based-pre/mem-init-reinstrumentation/alloc-zeroed.rs index 891a97fd8c22..008e7d132f10 100644 --- a/tests/script-based-pre/mem-init-reinstrumentation/alloc-zeroed.rs +++ b/tests/script-based-pre/mem-init-reinstrumentation/alloc-zeroed.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Z uninit-checks -use std::alloc::{alloc_zeroed, Layout}; +use std::alloc::{Layout, alloc_zeroed}; use std::slice::from_raw_parts; #[kani::proof] diff --git a/tests/slow/tokio-proofs/src/tokio/io_mem_stream.rs b/tests/slow/tokio-proofs/src/tokio/io_mem_stream.rs index 16b87cfadf5a..d013af942b9b 100644 --- a/tests/slow/tokio-proofs/src/tokio/io_mem_stream.rs +++ b/tests/slow/tokio-proofs/src/tokio/io_mem_stream.rs @@ -9,7 +9,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "full")] -use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt}; +use tokio::io::{AsyncReadExt, AsyncWriteExt, duplex}; #[cfg(disabled)] // because it timed out after 2h #[kani::proof] diff --git a/tests/slow/tokio-proofs/src/tokio/support/panic.rs b/tests/slow/tokio-proofs/src/tokio/support/panic.rs index 12ae92a521e4..b35fde0ecdd7 100644 --- a/tests/slow/tokio-proofs/src/tokio/support/panic.rs +++ b/tests/slow/tokio-proofs/src/tokio/support/panic.rs @@ -6,7 +6,7 @@ // Original copyright tokio contributors. // origin: tokio/tests/support -use parking_lot::{const_mutex, Mutex}; +use parking_lot::{Mutex, const_mutex}; use std::panic; use std::sync::Arc; diff --git a/tests/slow/tokio-proofs/src/tokio_stream/stream_stream_map.rs b/tests/slow/tokio-proofs/src/tokio_stream/stream_stream_map.rs index 39cb46077626..72c4d486daf8 100644 --- a/tests/slow/tokio-proofs/src/tokio_stream/stream_stream_map.rs +++ b/tests/slow/tokio-proofs/src/tokio_stream/stream_stream_map.rs @@ -7,7 +7,7 @@ // origin: tokio-stream/tests/ at commit b2ada60e701d5c9e6644cf8fc42a100774f8e23f use crate::tokio_stream::support::mpsc; -use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap}; +use tokio_stream::{self as stream, Stream, StreamExt, StreamMap, pending}; use tokio_test::{assert_ok, assert_pending, assert_ready, task}; use std::pin::Pin; diff --git a/tests/slow/tokio-proofs/src/tokio_test/block_on.rs b/tests/slow/tokio-proofs/src/tokio_test/block_on.rs index 62b5c0a514a2..3bf00ac0a5a7 100644 --- a/tests/slow/tokio-proofs/src/tokio_test/block_on.rs +++ b/tests/slow/tokio-proofs/src/tokio_test/block_on.rs @@ -8,7 +8,7 @@ #![warn(rust_2018_idioms)] -use tokio::time::{sleep_until, Duration, Instant}; +use tokio::time::{Duration, Instant, sleep_until}; use tokio_test::block_on; #[cfg(disabled)] // because epoll is missing diff --git a/tests/std-checks/std/src/sync/atomic.rs b/tests/std-checks/std/src/sync/atomic.rs index 85c9a3380775..f6fc822bf40a 100644 --- a/tests/std-checks/std/src/sync/atomic.rs +++ b/tests/std-checks/std/src/sync/atomic.rs @@ -3,7 +3,7 @@ extern crate kani; -use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8, AtomicUsize}; +use std::sync::atomic::{AtomicU8, AtomicU16, AtomicU32, AtomicU64, AtomicUsize}; /// Create wrapper functions to standard library functions that contains their contract. pub mod contracts { diff --git a/tools/build-kani/src/main.rs b/tools/build-kani/src/main.rs index 2c181d69d6e5..7000ddb045a7 100644 --- a/tools/build-kani/src/main.rs +++ b/tools/build-kani/src/main.rs @@ -11,7 +11,7 @@ mod parser; mod sysroot; use crate::sysroot::{build_bin, build_lib, kani_no_core_lib, kani_playback_lib, kani_sysroot_lib}; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use clap::Parser; use std::{ffi::OsString, path::Path, process::Command}; diff --git a/tools/build-kani/src/sysroot.rs b/tools/build-kani/src/sysroot.rs index ca42fea3f441..edd2a0973a83 100644 --- a/tools/build-kani/src/sysroot.rs +++ b/tools/build-kani/src/sysroot.rs @@ -11,8 +11,8 @@ //! //! Note: We don't cross-compile. Target is the same as the host. -use crate::{cp, AutoRun}; -use anyhow::{bail, format_err, Result}; +use crate::{AutoRun, cp}; +use anyhow::{Result, bail, format_err}; use cargo_metadata::{Artifact, Message}; use std::ffi::OsStr; use std::fs; diff --git a/tools/compiletest/src/common.rs b/tools/compiletest/src/common.rs index d5c4ccf3c116..0ff106c92de3 100644 --- a/tools/compiletest/src/common.rs +++ b/tools/compiletest/src/common.rs @@ -10,8 +10,8 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; -use test::test::TestTimeOptions; use test::ColorConfig; +use test::test::TestTimeOptions; #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub enum Mode { diff --git a/tools/compiletest/src/header.rs b/tools/compiletest/src/header.rs index afbee19e6ade..f49d4f64ce47 100644 --- a/tools/compiletest/src/header.rs +++ b/tools/compiletest/src/header.rs @@ -4,8 +4,8 @@ // See GitHub history for details. use std::fs::File; -use std::io::prelude::*; use std::io::BufReader; +use std::io::prelude::*; use std::path::Path; use tracing::*; diff --git a/tools/compiletest/src/main.rs b/tools/compiletest/src/main.rs index dd1284d7a3c9..9027e30121f8 100644 --- a/tools/compiletest/src/main.rs +++ b/tools/compiletest/src/main.rs @@ -10,8 +10,8 @@ extern crate test; -use crate::common::{output_base_dir, output_relative_path}; use crate::common::{Config, Mode, TestPaths}; +use crate::common::{output_base_dir, output_relative_path}; use crate::util::{logv, print_msg, top_level}; use getopts::Options; use std::env; @@ -21,8 +21,8 @@ use std::io::{self}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::{Duration, SystemTime}; -use test::test::TestTimeOptions; use test::ColorConfig; +use test::test::TestTimeOptions; use tracing::*; use walkdir::WalkDir; diff --git a/tools/compiletest/src/runtest.rs b/tools/compiletest/src/runtest.rs index c6575db17819..6e6425d086c3 100644 --- a/tools/compiletest/src/runtest.rs +++ b/tools/compiletest/src/runtest.rs @@ -6,11 +6,11 @@ // ignore-tidy-filelength use crate::common::KaniFailStep; -use crate::common::{output_base_dir, output_base_name}; use crate::common::{ CargoCoverage, CargoKani, CargoKaniTest, CoverageBased, Exec, Expected, Kani, KaniFixme, Stub, }; use crate::common::{Config, TestPaths}; +use crate::common::{output_base_dir, output_base_name}; use crate::header::TestProps; use crate::read2::read2; use crate::util::logv; diff --git a/tools/scanner/src/analysis.rs b/tools/scanner/src/analysis.rs index b32627939bf5..e0f65929ffdd 100644 --- a/tools/scanner/src/analysis.rs +++ b/tools/scanner/src/analysis.rs @@ -7,7 +7,7 @@ use crate::info; use csv::WriterBuilder; use graph_cycles::Cycles; use petgraph::graph::Graph; -use serde::{ser::SerializeStruct, Serialize, Serializer}; +use serde::{Serialize, Serializer, ser::SerializeStruct}; use stable_mir::mir::mono::Instance; use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef}; use stable_mir::mir::{ From 4f75199d476c10316e43171b662bf97a35cfdb29 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 24 Sep 2024 11:52:53 -0400 Subject: [PATCH 097/159] Revert #3539 and reuse rust backend infrastructure instead (#3545) Replace the body by a call to link_binary as other backend implementations do, e.g. [rustc_codegen_gcc](https://github.com/rust-lang/rust/blob/648d024a7859e1ab7fdffe5e419b6e35ccb16a4a/compiler/rustc_codegen_gcc/src/lib.rs#L269-L271). Also remove the implementation of `metadata_loader` which has a [default body](https://github.com/rust-lang/rust/blob/11e760b7f4e4aaa11bf51a64d4bb7f1171f6e466/compiler/rustc_codegen_ssa/src/traits/backend.rs#L58-L60). 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: Michael Tautschnig --- Cargo.lock | 32 -- kani-compiler/Cargo.toml | 5 - .../compiler_interface.rs | 321 +----------------- 3 files changed, 13 insertions(+), 345 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 390336c8fde8..c5fb50872a44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,15 +248,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -485,7 +476,6 @@ dependencies = [ "kani_metadata", "lazy_static", "num", - "object", "quote", "regex", "serde", @@ -728,19 +718,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" -dependencies = [ - "crc32fast", - "hashbrown", - "indexmap", - "memchr", - "wasmparser", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -1427,15 +1404,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasmparser" -version = "0.216.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdee6bea3619d311fb4b299721e89a986c3470f804b6d534340e412589028e3" -dependencies = [ - "bitflags", -] - [[package]] name = "which" version = "6.0.3" diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 9a4a0de43a34..9ca8d10f5275 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -28,11 +28,6 @@ tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_de tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]} tracing-tree = "0.4.0" -[dependencies.object] -version = "0.36.2" -default-features = false -features = ["elf", "macho", "pe", "xcoff", "write", "wasm"] - # Future proofing: enable backend dependencies using feature. [features] default = ['cprover'] diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 1efc41785351..85117b6a2974 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -24,21 +24,16 @@ use kani_metadata::UnsupportedFeature; use kani_metadata::artifact::convert_type; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use kani_metadata::{AssignsContract, CompilerArtifactStub}; -use object::write::{self, StandardSegment, Symbol, SymbolSection}; -use object::{ - Architecture, BinaryFormat, Endianness, FileFlags, SectionFlags, SectionKind, SubArchitecture, - SymbolFlags, SymbolKind, SymbolScope, elf, pe, xcoff, +use rustc_codegen_ssa::back::archive::{ + ArArchiveBuilder, ArchiveBuilder, ArchiveBuilderBuilder, DEFAULT_OBJECT_READER, }; -use rustc_codegen_ssa::back::archive::{ArArchiveBuilder, ArchiveBuilder, DEFAULT_OBJECT_READER}; -use rustc_codegen_ssa::back::metadata::create_metadata_file_for_wasm; +use rustc_codegen_ssa::back::link::link_binary; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CodegenResults, CrateInfo}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; -use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{DEFAULT_LOCALE_RESOURCE, ErrorGuaranteed}; use rustc_hir::def_id::{DefId as InternalDefId, LOCAL_CRATE}; use rustc_metadata::EncodedMetadata; -use rustc_metadata::fs::{METADATA_FILENAME, emit_wrapper_file}; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; @@ -46,9 +41,8 @@ use rustc_session::Session; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::output::out_filename; use rustc_smir::rustc_internal; -use rustc_span::sym; use rustc_target::abi::Endian; -use rustc_target::spec::{PanicStrategy, RelocModel, Target, ef_avr_arch}; +use rustc_target::spec::PanicStrategy; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::{CrateDef, DefId}; use std::any::Any; @@ -61,7 +55,6 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::{Arc, Mutex}; use std::time::Instant; -use tempfile::Builder as TempFileBuilder; use tracing::{debug, error, info}; pub type UnsupportedConstructs = FxHashMap>; @@ -229,292 +222,6 @@ impl GotocCodegenBackend { } } -// Copy of macho_object_build_version_for_target from -// rust/compiler/rustc_codegen_ssa/src/back/metadata.rs -fn macho_object_build_version_for_target(target: &Target) -> object::write::MachOBuildVersion { - /// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz" - /// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200 - fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 { - let (major, minor, patch) = (major as u32, minor as u32, patch as u32); - (major << 16) | (minor << 8) | patch - } - - let platform = - rustc_target::spec::current_apple_platform(target).expect("unknown Apple target OS"); - let min_os = rustc_target::spec::current_apple_deployment_target(target); - let (sdk_major, sdk_minor) = - rustc_target::spec::current_apple_sdk_version(platform).expect("unknown Apple target OS"); - - let mut build_version = object::write::MachOBuildVersion::default(); - build_version.platform = platform; - build_version.minos = pack_version(min_os); - build_version.sdk = pack_version((sdk_major, sdk_minor, 0)); - build_version -} - -// Copy of create_object_file from -// rust/compiler/rustc_codegen_ssa/src/back/metadata.rs -fn create_object_file(sess: &Session) -> Option> { - let endianness = match sess.target.options.endian { - Endian::Little => Endianness::Little, - Endian::Big => Endianness::Big, - }; - let (architecture, sub_architecture) = match &sess.target.arch[..] { - "arm" => (Architecture::Arm, None), - "aarch64" => ( - if sess.target.pointer_width == 32 { - Architecture::Aarch64_Ilp32 - } else { - Architecture::Aarch64 - }, - None, - ), - "x86" => (Architecture::I386, None), - "s390x" => (Architecture::S390x, None), - "mips" | "mips32r6" => (Architecture::Mips, None), - "mips64" | "mips64r6" => (Architecture::Mips64, None), - "x86_64" => ( - if sess.target.pointer_width == 32 { - Architecture::X86_64_X32 - } else { - Architecture::X86_64 - }, - None, - ), - "powerpc" => (Architecture::PowerPc, None), - "powerpc64" => (Architecture::PowerPc64, None), - "riscv32" => (Architecture::Riscv32, None), - "riscv64" => (Architecture::Riscv64, None), - "sparc" => (Architecture::Sparc32Plus, None), - "sparc64" => (Architecture::Sparc64, None), - "avr" => (Architecture::Avr, None), - "msp430" => (Architecture::Msp430, None), - "hexagon" => (Architecture::Hexagon, None), - "bpf" => (Architecture::Bpf, None), - "loongarch64" => (Architecture::LoongArch64, None), - "csky" => (Architecture::Csky, None), - "arm64ec" => (Architecture::Aarch64, Some(SubArchitecture::Arm64EC)), - // Unsupported architecture. - _ => return None, - }; - let binary_format = if sess.target.is_like_osx { - BinaryFormat::MachO - } else if sess.target.is_like_windows { - BinaryFormat::Coff - } else if sess.target.is_like_aix { - BinaryFormat::Xcoff - } else { - BinaryFormat::Elf - }; - - let mut file = write::Object::new(binary_format, architecture, endianness); - file.set_sub_architecture(sub_architecture); - if sess.target.is_like_osx { - if sess.target.llvm_target.starts_with("arm64e") { - file.set_macho_cpu_subtype(object::macho::CPU_SUBTYPE_ARM64E); - } - - file.set_macho_build_version(macho_object_build_version_for_target(&sess.target)) - } - if binary_format == BinaryFormat::Coff { - // Disable the default mangler to avoid mangling the special "@feat.00" symbol name. - let original_mangling = file.mangling(); - file.set_mangling(object::write::Mangling::None); - - let mut feature = 0; - - if file.architecture() == object::Architecture::I386 { - // When linking with /SAFESEH on x86, lld requires that all linker inputs be marked as - // safe exception handling compatible. Metadata files masquerade as regular COFF - // objects and are treated as linker inputs, despite containing no actual code. Thus, - // they still need to be marked as safe exception handling compatible. See #96498. - // Reference: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format - feature |= 1; - } - - file.add_symbol(object::write::Symbol { - name: "@feat.00".into(), - value: feature, - size: 0, - kind: object::SymbolKind::Data, - scope: object::SymbolScope::Compilation, - weak: false, - section: object::write::SymbolSection::Absolute, - flags: object::SymbolFlags::None, - }); - - file.set_mangling(original_mangling); - } - let e_flags = match architecture { - Architecture::Mips => { - let arch = match sess.target.options.cpu.as_ref() { - "mips1" => elf::EF_MIPS_ARCH_1, - "mips2" => elf::EF_MIPS_ARCH_2, - "mips3" => elf::EF_MIPS_ARCH_3, - "mips4" => elf::EF_MIPS_ARCH_4, - "mips5" => elf::EF_MIPS_ARCH_5, - s if s.contains("r6") => elf::EF_MIPS_ARCH_32R6, - _ => elf::EF_MIPS_ARCH_32R2, - }; - - let mut e_flags = elf::EF_MIPS_CPIC | arch; - - // If the ABI is explicitly given, use it or default to O32. - match sess.target.options.llvm_abiname.to_lowercase().as_str() { - "n32" => e_flags |= elf::EF_MIPS_ABI2, - "o32" => e_flags |= elf::EF_MIPS_ABI_O32, - _ => e_flags |= elf::EF_MIPS_ABI_O32, - }; - - if sess.target.options.relocation_model != RelocModel::Static { - e_flags |= elf::EF_MIPS_PIC; - } - if sess.target.options.cpu.contains("r6") { - e_flags |= elf::EF_MIPS_NAN2008; - } - e_flags - } - Architecture::Mips64 => { - // copied from `mips64el-linux-gnuabi64-gcc foo.c -c` - elf::EF_MIPS_CPIC - | elf::EF_MIPS_PIC - | if sess.target.options.cpu.contains("r6") { - elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008 - } else { - elf::EF_MIPS_ARCH_64R2 - } - } - Architecture::Riscv32 | Architecture::Riscv64 => { - // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc - let mut e_flags: u32 = 0x0; - - // Check if compressed is enabled - // `unstable_target_features` is used here because "c" is gated behind riscv_target_feature. - if sess.unstable_target_features.contains(&sym::c) { - e_flags |= elf::EF_RISCV_RVC; - } - - // Set the appropriate flag based on ABI - // This needs to match LLVM `RISCVELFStreamer.cpp` - match &*sess.target.llvm_abiname { - "" | "ilp32" | "lp64" => (), - "ilp32f" | "lp64f" => e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE, - "ilp32d" | "lp64d" => e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE, - "ilp32e" => e_flags |= elf::EF_RISCV_RVE, - _ => panic!("unknown RISC-V ABI name"), - } - - e_flags - } - Architecture::LoongArch64 => { - // Source: https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc#e_flags-identifies-abi-type-and-version - let mut e_flags: u32 = elf::EF_LARCH_OBJABI_V1; - - // Set the appropriate flag based on ABI - // This needs to match LLVM `LoongArchELFStreamer.cpp` - match &*sess.target.llvm_abiname { - "ilp32s" | "lp64s" => e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT, - "ilp32f" | "lp64f" => e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT, - "ilp32d" | "lp64d" => e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT, - _ => panic!("unknown LoongArch ABI name"), - } - - e_flags - } - Architecture::Avr => { - // Resolve the ISA revision and set - // the appropriate EF_AVR_ARCH flag. - ef_avr_arch(&sess.target.options.cpu) - } - Architecture::Csky => { - let e_flags = match sess.target.options.abi.as_ref() { - "abiv2" => elf::EF_CSKY_ABIV2, - _ => elf::EF_CSKY_ABIV1, - }; - e_flags - } - _ => 0, - }; - // adapted from LLVM's `MCELFObjectTargetWriter::getOSABI` - let os_abi = match sess.target.options.os.as_ref() { - "hermit" => elf::ELFOSABI_STANDALONE, - "freebsd" => elf::ELFOSABI_FREEBSD, - "solaris" => elf::ELFOSABI_SOLARIS, - _ => elf::ELFOSABI_NONE, - }; - let abi_version = 0; - // rustc implementation also does: - // add_gnu_property_note(&mut file, architecture, binary_format, endianness); - file.flags = FileFlags::Elf { os_abi, abi_version, e_flags }; - Some(file) -} - -// copy of create_wrapper_file from -// rust/compiler/rustc_codegen_ssa/src/back/metadata.rs -// without the MetadataPosition return value, which we don't need -fn create_wrapper_file(sess: &Session, section_name: String, data: &[u8]) -> Vec { - let Some(mut file) = create_object_file(sess) else { - if sess.target.is_like_wasm { - return create_metadata_file_for_wasm(sess, data, §ion_name); - } - - // Targets using this branch don't have support implemented here yet or - // they're not yet implemented in the `object` crate and will likely - // fill out this module over time. - return data.to_vec(); - }; - let section = if file.format() == BinaryFormat::Xcoff { - file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug) - } else { - file.add_section( - file.segment_name(StandardSegment::Debug).to_vec(), - section_name.into_bytes(), - SectionKind::Debug, - ) - }; - match file.format() { - BinaryFormat::Coff => { - file.section_mut(section).flags = - SectionFlags::Coff { characteristics: pe::IMAGE_SCN_LNK_REMOVE }; - } - BinaryFormat::Elf => { - file.section_mut(section).flags = - SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 }; - } - BinaryFormat::Xcoff => { - // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss. - file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text); - file.section_mut(section).flags = - SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 }; - // Encode string stored in .info section of XCOFF. - // FIXME: The length of data here is not guaranteed to fit in a u32. - // We may have to split the data into multiple pieces in order to - // store in .info section. - let len: u32 = data.len().try_into().unwrap(); - let offset = file.append_section_data(section, &len.to_be_bytes(), 1); - // Add a symbol referring to the data in .info section. - file.add_symbol(Symbol { - name: "__aix_rust_metadata".into(), - value: offset + 4, - size: 0, - kind: SymbolKind::Unknown, - scope: SymbolScope::Compilation, - weak: false, - section: SymbolSection::Section(section), - flags: SymbolFlags::Xcoff { - n_sclass: xcoff::C_INFO, - x_smtyp: xcoff::C_HIDEXT, - x_smclas: xcoff::C_HIDEXT, - containing_csect: None, - }, - }); - } - _ => {} - }; - file.append_section_data(section, data, 1); - file.write().unwrap() -} - impl CodegenBackend for GotocCodegenBackend { fn provide(&self, providers: &mut Providers) { provide::provide(providers, &self.queries.lock().unwrap()); @@ -723,17 +430,7 @@ impl CodegenBackend for GotocCodegenBackend { debug!(?crate_type, ?out_path, "link"); if *crate_type == CrateType::Rlib { // Emit the `rlib` that contains just one file: `.rmeta` - let mut builder = Box::new(ArArchiveBuilder::new(sess, &DEFAULT_OBJECT_READER)); - let tmp_dir = TempFileBuilder::new().prefix("kani").tempdir().unwrap(); - let path = MaybeTempDir::new(tmp_dir, sess.opts.cg.save_temps); - let metadata = create_wrapper_file( - sess, - ".rmeta".to_string(), - codegen_results.metadata.raw_data(), - ); - let metadata = emit_wrapper_file(sess, &metadata, &path, METADATA_FILENAME); - builder.add_file(&metadata); - builder.build(&out_path); + link_binary(sess, &ArArchiveBuilderBuilder, &codegen_results, outputs)? } else { // Write the location of the kani metadata file in the requested compiler output file. let base_filepath = outputs.path(OutputType::Object); @@ -749,6 +446,14 @@ impl CodegenBackend for GotocCodegenBackend { } } +struct ArArchiveBuilderBuilder; + +impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder { + fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box { + Box::new(ArArchiveBuilder::new(sess, &DEFAULT_OBJECT_READER)) + } +} + fn contract_metadata_for_harness(tcx: TyCtxt, def_id: DefId) -> Option { let attrs = KaniAttributes::for_def_id(tcx, def_id); attrs.interpret_for_contract_attribute().map(|(_, id, _)| id) From 61aca4cae069bdd986ea38decc4d721100fd3b78 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:43:38 +0200 Subject: [PATCH 098/159] Automatic toolchain upgrade to nightly-2024-09-25 (#3547) Update Rust toolchain from nightly-2024-09-23 to nightly-2024-09-25 without any other source changes. We skip over 2024-09-24 as that version ICEs when trying to build http-body (fixed by https://github.com/rust-lang/rust/pull/130775). --------- Co-authored-by: celinval <35149715+celinval@users.noreply.github.com> Co-authored-by: Michael Tautschnig --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 28cedff1d1de..3cd835927ee1 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-09-23" +channel = "nightly-2024-09-25" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 7c9b710523a637501d52cda823bae473b7b63ade Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 27 Sep 2024 10:10:54 +0200 Subject: [PATCH 099/159] Update toolchain to 2024-09-26 (#3549) Changes required due to: - https://github.com/rust-lang/rust/pull/130234 improve compile errors for invalid ptr-to-ptr casts with trait objects - rust-lang/rust@cfb8419900 Separate collection of crate-local inherent impls from error reporting - rust-lang/rust@40fca8f7a8 Bump Clippy version -> 0.1.83 Resolves: #3548 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- cprover_bindings/src/goto_program/expr.rs | 4 +- cprover_bindings/src/goto_program/stmt.rs | 4 +- cprover_bindings/src/goto_program/symbol.rs | 2 - cprover_bindings/src/goto_program/typ.rs | 4 +- .../src/irep/goto_binary_serde.rs | 152 +++++++++--------- .../codegen_cprover_gotoc/context/goto_ctx.rs | 1 - kani-compiler/src/kani_middle/resolve.rs | 3 +- .../src/kani_middle/transform/internal_mir.rs | 12 +- rust-toolchain.toml | 2 +- 9 files changed, 94 insertions(+), 90 deletions(-) diff --git a/cprover_bindings/src/goto_program/expr.rs b/cprover_bindings/src/goto_program/expr.rs index dad3b06e7753..2a29985cb229 100644 --- a/cprover_bindings/src/goto_program/expr.rs +++ b/cprover_bindings/src/goto_program/expr.rs @@ -15,7 +15,7 @@ use std::collections::BTreeMap; use std::fmt::Debug; /////////////////////////////////////////////////////////////////////////////////////////////// -/// Datatypes +// Datatypes /////////////////////////////////////////////////////////////////////////////////////////////// /// An `Expr` represents an expression type: i.e. a computation that returns a value. @@ -292,7 +292,7 @@ pub fn arithmetic_overflow_result_type(operand_type: Type) -> Type { } /////////////////////////////////////////////////////////////////////////////////////////////// -/// Implementations +// Implementations /////////////////////////////////////////////////////////////////////////////////////////////// /// Getters diff --git a/cprover_bindings/src/goto_program/stmt.rs b/cprover_bindings/src/goto_program/stmt.rs index 951e58f9a954..2afbdafc95a3 100644 --- a/cprover_bindings/src/goto_program/stmt.rs +++ b/cprover_bindings/src/goto_program/stmt.rs @@ -6,7 +6,7 @@ use crate::{InternString, InternedString}; use std::fmt::Debug; /////////////////////////////////////////////////////////////////////////////////////////////// -/// Datatypes +// Datatypes /////////////////////////////////////////////////////////////////////////////////////////////// /// An `Stmt` represents a statement type: i.e. a computation that does not return a value. @@ -118,7 +118,7 @@ pub struct SwitchCase { } /////////////////////////////////////////////////////////////////////////////////////////////// -/// Implementations +// Implementations /////////////////////////////////////////////////////////////////////////////////////////////// /// Getters diff --git a/cprover_bindings/src/goto_program/symbol.rs b/cprover_bindings/src/goto_program/symbol.rs index 457be1163a3a..5c86dd04909a 100644 --- a/cprover_bindings/src/goto_program/symbol.rs +++ b/cprover_bindings/src/goto_program/symbol.rs @@ -20,8 +20,6 @@ pub struct Symbol { /// Contracts to be enforced (only supported for functions) pub contract: Option>, - /// Optional debugging information - /// Local name `x` pub base_name: Option, /// Fully qualifier name `foo::bar::x` diff --git a/cprover_bindings/src/goto_program/typ.rs b/cprover_bindings/src/goto_program/typ.rs index 3210dd46e43f..6e087a6b7f58 100644 --- a/cprover_bindings/src/goto_program/typ.rs +++ b/cprover_bindings/src/goto_program/typ.rs @@ -10,7 +10,7 @@ use std::collections::BTreeMap; use std::fmt::Debug; /////////////////////////////////////////////////////////////////////////////////////////////// -/// Datatypes +// Datatypes /////////////////////////////////////////////////////////////////////////////////////////////// /// Represents the different types that can be used in a goto-program. @@ -112,7 +112,7 @@ pub struct Parameter { } /////////////////////////////////////////////////////////////////////////////////////////////// -/// Implementations +// Implementations /////////////////////////////////////////////////////////////////////////////////////////////// /// Getters diff --git a/cprover_bindings/src/irep/goto_binary_serde.rs b/cprover_bindings/src/irep/goto_binary_serde.rs index bb725de638cf..1e2dc37eb589 100644 --- a/cprover_bindings/src/irep/goto_binary_serde.rs +++ b/cprover_bindings/src/irep/goto_binary_serde.rs @@ -1,6 +1,82 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT //! GOTO binary serializer. +//! +//! # Design overview +//! +//! When saving a [SymbolTable] to a binary file, the [Irep] describing each +//! symbol's type, value and source location are structurally hashed and +//! uniquely numbered so that structurally identical [Irep] only get written +//! in full to the file the first time they are encountered and that ulterior +//! occurrences are referenced by their unique number instead. +//! The key concept at play is that of a numbering, ie a function that assigns +//! numbers to values of a given type. +//! +//! The [IrepNumbering] struct offers high-level methods to number +//! [InternedString], [IrepId] and [Irep] values: +//! - [InternedString] objects get mapped to [NumberedString] objects based on +//! the characters they contain. +//! - [IrepId] objects get mapped to [NumberedString] objects based on the +//! characters of their string representation, in the same number space +//! as [InternedString]. +//! - [Irep] objects get mapped to [NumberedIrep] based on: +//! + the unique numbers assigned to their [Irep::id] attribute, +//! + the unique numbers of [Irep] found in their [Irep::sub] attribute, +//! + the pairs of unique numbers assigned to the ([IrepId],[Irep]) pairs +//! found in their [Ipre::named_sub] attribute. +//! +//! In order to assign the same number to structurally identical [Irep] objects, +//! [IrepNumbering] essentially implements a cache where each [NumberedIrep] is +//! keyed under an [IrepKey] that describes its internal structure. +//! +//! An [IrepKey] is simply the vector of unique numbers describing the +//! `id`, `sub` and `named_sub` attributes of a [Irep]. +//! +//! A [NumberedIrep] is conceptually a pair made of the [IrepKey] itself and the +//! unique number assigned to that key. +//! +//! The cache implemented by [IrepNumbering] is bidirectional. It lets you +//! compute the [NumberedIrep] of an [Irep], and lets you fetch a numbered +//! [NumberedIrep] from its unique number. +//! +//! In practice: +//! - the forward directon from [IrepKey] to unique numbers is implemented using a `HashMap` +//! - the inverse direction from unique numbers to [NumberedIrep] is implemented usign a `Vec` +//! called the `index` that stores [NumberedIrep] under their unique number. +//! +//! Earlier we said that an [NumberedIrep] is conceptually a pair formed of +//! an [IrepKey] and its unique number. It is represented using only +//! a pair formed of a `usize` representing the unique number, and a `usize` +//! representing the index at which the key can be found inside a single vector +//! of type `Vec` called `keys` where all [IrepKey] are concatenated when +//! they first get numbered. The inverse map of keys is represented this way +//! because the Rust hash map that implements the forward cache owns +//! its keys which would make it difficult to store keys references in inverse +//! cache, which would introduce circular dependencies and require `Rc` and +//! liftetimes annotations everywhere. +//! +//! Numberig an [Irep] consists in recursively traversing it and numbering its +//! contents in a bottom-up fashion, then assembling its [IrepKey] and querying +//! the cache to either return an existing [NumberedIrep] or creating a new one +//! (in case that key has never been seen before). +//! +//! The [GotoBinarySerializer] internally uses a [IrepNumbering] and a cache +//! of [NumberedIrep] and [NumberedString] it has already written to file. +//! +//! When given an [InternedString], [IrepId] or [Irep] to serialize, +//! the [GotoBinarySerializer] first numbers that object using its internal +//! [IrepNumbering] instance. Then it looks up that unique number in its cache +//! of already written objects. If the object was seen before, only the unique +//! number of that object is written to file. If the object was never seen +//! before, then the unique number of that object is written to file, followed +//! by the objects describing its contents (themselves only being written fully +//! if they have never been seen before, or only referenced if they have been +//! seen before, etc.) +//! +//! The [GotoBinaryDeserializer] also uses an [IrepNumbering] and a cache +//! of [NumberedIrep] and [NumberedString] it has already read from file. +//! Dually to the serializer, it will only attempt to decode the contents of an +//! object from the byte stream on the first occurrence. use crate::irep::{Irep, IrepId, Symbol, SymbolTable}; use crate::{InternString, InternedString}; @@ -40,82 +116,6 @@ pub fn read_goto_binary_file(filename: &Path) -> io::Result<()> { deserializer.read_file() } -/// # Design overview -/// -/// When saving a [SymbolTable] to a binary file, the [Irep] describing each -/// symbol's type, value and source location are structurally hashed and -/// uniquely numbered so that structurally identical [Irep] only get written -/// in full to the file the first time they are encountered and that ulterior -/// occurrences are referenced by their unique number instead. -/// The key concept at play is that of a numbering, ie a function that assigns -/// numbers to values of a given type. -/// -/// The [IrepNumbering] struct offers high-level methods to number -/// [InternedString], [IrepId] and [Irep] values: -/// - [InternedString] objects get mapped to [NumberedString] objects based on -/// the characters they contain. -/// - [IrepId] objects get mapped to [NumberedString] objects based on the -/// characters of their string representation, in the same number space -/// as [InternedString]. -/// - [Irep] objects get mapped to [NumberedIrep] based on: -/// + the unique numbers assigned to their [Irep::id] attribute, -/// + the unique numbers of [Irep] found in their [Irep::sub] attribute, -/// + the pairs of unique numbers assigned to the ([IrepId],[Irep]) pairs -/// found in their [Ipre::named_sub] attribute. -/// -/// In order to assign the same number to structurally identical [Irep] objects, -/// [IrepNumbering] essentially implements a cache where each [NumberedIrep] is -/// keyed under an [IrepKey] that describes its internal structure. -/// -/// An [IrepKey] is simply the vector of unique numbers describing the -/// `id`, `sub` and `named_sub` attributes of a [Irep]. -/// -/// A [NumberedIrep] is conceptually a pair made of the [IrepKey] itself and the -/// unique number assigned to that key. -/// -/// The cache implemented by [IrepNumbering] is bidirectional. It lets you -/// compute the [NumberedIrep] of an [Irep], and lets you fetch a numbered -/// [NumberedIrep] from its unique number. -/// -/// In practice: -/// - the forward directon from [IrepKey] to unique numbers is implemented using a `HashMap` -/// - the inverse direction from unique numbers to [NumberedIrep] is implemented usign a `Vec` -/// called the `index` that stores [NumberedIrep] under their unique number. -/// -/// Earlier we said that an [NumberedIrep] is conceptually a pair formed of -/// an [IrepKey] and its unique number. It is represented using only -/// a pair formed of a `usize` representing the unique number, and a `usize` -/// representing the index at which the key can be found inside a single vector -/// of type `Vec` called `keys` where all [IrepKey] are concatenated when -/// they first get numbered. The inverse map of keys is represented this way -/// because the Rust hash map that implements the forward cache owns -/// its keys which would make it difficult to store keys references in inverse -/// cache, which would introduce circular dependencies and require `Rc` and -/// liftetimes annotations everywhere. -/// -/// Numberig an [Irep] consists in recursively traversing it and numbering its -/// contents in a bottom-up fashion, then assembling its [IrepKey] and querying -/// the cache to either return an existing [NumberedIrep] or creating a new one -/// (in case that key has never been seen before). -/// -/// The [GotoBinarySerializer] internally uses a [IrepNumbering] and a cache -/// of [NumberedIrep] and [NumberedString] it has already written to file. -/// -/// When given an [InternedString], [IrepId] or [Irep] to serialize, -/// the [GotoBinarySerializer] first numbers that object using its internal -/// [IrepNumbering] instance. Then it looks up that unique number in its cache -/// of already written objects. If the object was seen before, only the unique -/// number of that object is written to file. If the object was never seen -/// before, then the unique number of that object is written to file, followed -/// by the objects describing its contents (themselves only being written fully -/// if they have never been seen before, or only referenced if they have been -/// seen before, etc.) -/// -/// The [GotoBinaryDeserializer] also uses an [IrepNumbering] and a cache -/// of [NumberedIrep] and [NumberedString] it has already read from file. -/// Dually to the serializer, it will only attempt to decode the contents of an -/// object from the byte stream on the first occurrence. - /// A numbered [InternedString]. The number is guaranteed to be in [0,N]. /// Had to introduce this indirection because [InternedString] does not let you /// access its unique id, so we have to build one ourselves. 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 d88506464cf7..3a47f7b9c09c 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs @@ -249,7 +249,6 @@ impl<'tcx> GotocCtx<'tcx> { /// Ensures that a struct with name `struct_name` appears in the symbol table. /// If it doesn't, inserts it using `f`. /// Returns: a struct-tag referencing the inserted struct. - pub fn ensure_struct< T: Into, U: Into, diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index d71f9710d404..31cf55650559 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -544,7 +544,6 @@ fn resolve_in_type_def<'tcx>( || ResolveError::MissingItem { tcx, base: type_id, unresolved: name.to_string() }; // Try the inherent `impl` blocks (i.e., non-trait `impl`s). tcx.inherent_impls(type_id) - .map_err(|_| missing_item_err())? .iter() .flat_map(|impl_id| tcx.associated_item_def_ids(impl_id)) .cloned() @@ -588,7 +587,7 @@ where let simple_ty = fast_reject::simplify_type(tcx, internal_ty, TreatParams::InstantiateWithInfer) .unwrap(); - let impls = tcx.incoherent_impls(simple_ty).unwrap(); + let impls = tcx.incoherent_impls(simple_ty); // Find the primitive impl. let item = impls .iter() diff --git a/kani-compiler/src/kani_middle/transform/internal_mir.rs b/kani-compiler/src/kani_middle/transform/internal_mir.rs index ba23fbf2dddf..d29a5e688d2c 100644 --- a/kani-compiler/src/kani_middle/transform/internal_mir.rs +++ b/kani-compiler/src/kani_middle/transform/internal_mir.rs @@ -7,6 +7,7 @@ //! other maintainers wanted to keep the conversions minimal. For more information, see //! https://github.com/rust-lang/rust/pull/127782 +use rustc_middle::mir::CoercionSource; use rustc_middle::ty::{self as rustc_ty, TyCtxt}; use rustc_smir::rustc_internal::internal; use stable_mir::mir::{ @@ -125,10 +126,17 @@ impl RustcInternalMir for CastKind { CastKind::PointerWithExposedProvenance => { rustc_middle::mir::CastKind::PointerWithExposedProvenance } + // smir doesn't yet have CoercionSource information, so we just choose "Implicit" CastKind::PointerCoercion(ptr_coercion) => { - rustc_middle::mir::CastKind::PointerCoercion(ptr_coercion.internal_mir(tcx)) + rustc_middle::mir::CastKind::PointerCoercion( + ptr_coercion.internal_mir(tcx), + CoercionSource::Implicit, + ) } - CastKind::DynStar => rustc_middle::mir::CastKind::DynStar, + CastKind::DynStar => rustc_middle::mir::CastKind::PointerCoercion( + rustc_ty::adjustment::PointerCoercion::DynStar, + CoercionSource::Implicit, + ), CastKind::IntToInt => rustc_middle::mir::CastKind::IntToInt, CastKind::FloatToInt => rustc_middle::mir::CastKind::FloatToInt, CastKind::FloatToFloat => rustc_middle::mir::CastKind::FloatToFloat, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 3cd835927ee1..9b6300f9fa74 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-09-25" +channel = "nightly-2024-09-26" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 8371afb4566904df5604bcbbc5c198224f05beac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:04:41 -0700 Subject: [PATCH 100/159] Automatic toolchain upgrade to nightly-2024-09-27 (#3550) Update Rust toolchain from nightly-2024-09-26 to nightly-2024-09-27 without any other source changes. Co-authored-by: tautschnig <1144736+tautschnig@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 9b6300f9fa74..90d2d4f3d558 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-09-26" +channel = "nightly-2024-09-27" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 25c726b94564c6b1e95a8a4a4a1103068cd7ccd3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 12:27:24 +0200 Subject: [PATCH 101/159] Automatic toolchain upgrade to nightly-2024-09-28 (#3552) Update Rust toolchain from nightly-2024-09-27 to nightly-2024-09-28 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 90d2d4f3d558..5e957c520955 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-09-27" +channel = "nightly-2024-09-28" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 799c855c92760748de23c148ddd80fda5729cfc8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:00:48 +0200 Subject: [PATCH 102/159] Automatic toolchain upgrade to nightly-2024-09-29 (#3553) Update Rust toolchain from nightly-2024-09-28 to nightly-2024-09-29 without any other source changes. Co-authored-by: tautschnig <1144736+tautschnig@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 5e957c520955..0e6479a68958 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-09-28" +channel = "nightly-2024-09-29" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From eaab7299b2b66870faea137d015ead40a93fbc86 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:24:25 +0200 Subject: [PATCH 103/159] Automatic cargo update to 2024-09-30 (#3555) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 65 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5fb50872a44..acc0cc485bdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ahash" @@ -81,9 +81,9 @@ checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" @@ -563,9 +563,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "linear-map" @@ -720,9 +720,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "os_info" @@ -785,6 +788,12 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -892,23 +901,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -922,13 +931,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -939,9 +948,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" @@ -1044,9 +1053,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -1139,9 +1148,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -1150,9 +1159,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -1245,9 +1254,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", @@ -1531,9 +1540,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] From d712846980230d5aeec7efc93c310dc8bee4449e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:25:16 +0200 Subject: [PATCH 104/159] Automatic toolchain upgrade to nightly-2024-09-30 (#3554) Update Rust toolchain from nightly-2024-09-29 to nightly-2024-09-30 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 0e6479a68958..675b67ae7a85 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-09-29" +channel = "nightly-2024-09-30" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 5d8a71405444928be61423f3fd0c87e3667fcc39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:58:05 -0400 Subject: [PATCH 105/159] Bump tests/perf/s2n-quic from `a88ae41` to `2a735a9` (#3556) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `a88ae41` to `2a735a9`.
Commits

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 a88ae4191af1..2a735a983a54 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit a88ae4191af100747404db207976c7da1a3b8370 +Subproject commit 2a735a983a5426b13c926d19090d32ea0a99ea36 From 10b8a9daa96c696ed295c4fa6fda70cab955156f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:57:30 +0200 Subject: [PATCH 106/159] Automatic toolchain upgrade to nightly-2024-10-01 (#3558) Update Rust toolchain from nightly-2024-09-30 to nightly-2024-10-01 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 675b67ae7a85..c0fba0261fa7 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-09-30" +channel = "nightly-2024-10-01" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From fb8003146fb11d12ae14ff6aeafc6afc16d9f115 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 03:58:02 +0000 Subject: [PATCH 107/159] Automatic toolchain upgrade to nightly-2024-10-02 (#3562) Update Rust toolchain from nightly-2024-10-01 to nightly-2024-10-02 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 c0fba0261fa7..bd0b55ef531f 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-10-01" +channel = "nightly-2024-10-02" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From 14ebca99fbe9c7bb0f58b937bfb01d079687c51c Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:46:34 -0700 Subject: [PATCH 108/159] Remove obsolete linker options (#3559) These were made obsolete a while back. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- kani-driver/src/args/mod.rs | 17 +---------------- tests/cargo-kani/asm/global/Cargo.toml | 2 +- tests/cargo-kani/mir-linker/Cargo.toml | 3 --- tests/cargo-ui/ws-integ-tests/Cargo.toml | 2 -- tests/kani/UnsizedCoercion/basic_coercion.rs | 1 - .../UnsizedCoercion/basic_inner_coercion.rs | 1 - .../UnsizedCoercion/basic_outer_coercion.rs | 1 - tests/kani/UnsizedCoercion/box_coercion.rs | 1 - .../kani/UnsizedCoercion/box_inner_coercion.rs | 1 - .../kani/UnsizedCoercion/box_outer_coercion.rs | 1 - .../UnsizedCoercion/custom_outer_coercion.rs | 1 - tests/kani/UnsizedCoercion/double_coercion.rs | 1 - tests/kani/UnsizedCoercion/rc_outer_coercion.rs | 1 - .../UnsizedCoercion/trait_to_trait_coercion.rs | 1 - tests/perf/string/src/any_str.rs | 5 ----- .../Strings/copy_empty_string_by_intrinsic.rs | 1 - .../ui/mir-linker/generic-harness/incorrect.rs | 2 -- 17 files changed, 2 insertions(+), 40 deletions(-) diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 72de44e36014..573ad21ab31a 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -40,6 +40,7 @@ where .unwrap() } +#[allow(dead_code)] pub fn print_obsolete(verbosity: &CommonArgs, option: &str) { if !verbosity.quiet { warning(&format!( @@ -189,14 +190,6 @@ pub struct VerificationArgs { #[arg(long, hide_short_help = true)] pub only_codegen: bool, - /// Deprecated flag. This is a no-op since we no longer support the legacy linker and - /// it will be removed in a future Kani release. - #[arg(long, hide = true, conflicts_with("mir_linker"))] - pub legacy_linker: bool, - /// Deprecated flag. This is a no-op since we no longer support any other linker. - #[arg(long, hide = true)] - pub mir_linker: bool, - /// Specify the value used for loop unwinding in CBMC #[arg(long)] pub default_unwind: Option, @@ -524,14 +517,6 @@ impl ValidateArgs for VerificationArgs { } } - if self.mir_linker { - print_obsolete(&self.common_args, "--mir-linker"); - } - - if self.legacy_linker { - print_obsolete(&self.common_args, "--legacy-linker"); - } - // TODO: these conflicting flags reflect what's necessary to pass current tests unmodified. // We should consider improving the error messages slightly in a later pull request. if natives_unwind && extra_unwind { diff --git a/tests/cargo-kani/asm/global/Cargo.toml b/tests/cargo-kani/asm/global/Cargo.toml index 21ff904a2f99..79ffab9377c1 100644 --- a/tests/cargo-kani/asm/global/Cargo.toml +++ b/tests/cargo-kani/asm/global/Cargo.toml @@ -13,4 +13,4 @@ crate_with_global_asm = { path = "crate_with_global_asm" } [package.metadata.kani] # Issue with MIR Linker on static constant. -flags = { enable-unstable = true, ignore-global-asm = true, mir-linker = true } +flags = { enable-unstable = true, ignore-global-asm = true } diff --git a/tests/cargo-kani/mir-linker/Cargo.toml b/tests/cargo-kani/mir-linker/Cargo.toml index 39d5016a407b..f00d4c687864 100644 --- a/tests/cargo-kani/mir-linker/Cargo.toml +++ b/tests/cargo-kani/mir-linker/Cargo.toml @@ -8,6 +8,3 @@ description = "Ensures that the mir-linker works accross crates" [dependencies] semver = "1.0" - -[package.metadata.kani] -flags = { mir-linker=true, enable-unstable=true } diff --git a/tests/cargo-ui/ws-integ-tests/Cargo.toml b/tests/cargo-ui/ws-integ-tests/Cargo.toml index b09d4c9a5f19..93c4baebe120 100644 --- a/tests/cargo-ui/ws-integ-tests/Cargo.toml +++ b/tests/cargo-ui/ws-integ-tests/Cargo.toml @@ -15,5 +15,3 @@ members = [ [workspace.metadata.kani.flags] workspace = true tests = true -enable-unstable=true -mir-linker=true diff --git a/tests/kani/UnsizedCoercion/basic_coercion.rs b/tests/kani/UnsizedCoercion/basic_coercion.rs index a60a76aec3be..ee8c80e87011 100644 --- a/tests/kani/UnsizedCoercion/basic_coercion.rs +++ b/tests/kani/UnsizedCoercion/basic_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion from using built-in references and pointers. //! Tests are broken down into different crates to ensure that the reachability works for each case. diff --git a/tests/kani/UnsizedCoercion/basic_inner_coercion.rs b/tests/kani/UnsizedCoercion/basic_inner_coercion.rs index efc84b61ea1e..19d8f39fed7e 100644 --- a/tests/kani/UnsizedCoercion/basic_inner_coercion.rs +++ b/tests/kani/UnsizedCoercion/basic_inner_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion from using built-in references and pointers. //! Tests are broken down into different crates to ensure that the reachability works for each case. diff --git a/tests/kani/UnsizedCoercion/basic_outer_coercion.rs b/tests/kani/UnsizedCoercion/basic_outer_coercion.rs index a2b5bfae3129..633dfbdaa995 100644 --- a/tests/kani/UnsizedCoercion/basic_outer_coercion.rs +++ b/tests/kani/UnsizedCoercion/basic_outer_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion from using built-in references and pointers. //! Tests are broken down into different crates to ensure that the reachability works for each case. diff --git a/tests/kani/UnsizedCoercion/box_coercion.rs b/tests/kani/UnsizedCoercion/box_coercion.rs index e022bdef06e1..106f586f8eed 100644 --- a/tests/kani/UnsizedCoercion/box_coercion.rs +++ b/tests/kani/UnsizedCoercion/box_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion when using boxes. //! Tests are broken down into different crates to ensure that the reachability works for each case. diff --git a/tests/kani/UnsizedCoercion/box_inner_coercion.rs b/tests/kani/UnsizedCoercion/box_inner_coercion.rs index 35f10373d5ef..4e8a8b56624c 100644 --- a/tests/kani/UnsizedCoercion/box_inner_coercion.rs +++ b/tests/kani/UnsizedCoercion/box_inner_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion when using boxes. //! Tests are broken down into different crates to ensure that the reachability works for each case. diff --git a/tests/kani/UnsizedCoercion/box_outer_coercion.rs b/tests/kani/UnsizedCoercion/box_outer_coercion.rs index 586f54003cdf..2d91360cd576 100644 --- a/tests/kani/UnsizedCoercion/box_outer_coercion.rs +++ b/tests/kani/UnsizedCoercion/box_outer_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion when using boxes. //! Tests are broken down into different crates to ensure that the reachability works for each case. mod defs; diff --git a/tests/kani/UnsizedCoercion/custom_outer_coercion.rs b/tests/kani/UnsizedCoercion/custom_outer_coercion.rs index 7f7d68ef6e84..6d75204223c1 100644 --- a/tests/kani/UnsizedCoercion/custom_outer_coercion.rs +++ b/tests/kani/UnsizedCoercion/custom_outer_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion when using a custom CoerceUnsized implementation. //! Tests are broken down into different crates to ensure that the reachability works for each case. #![feature(coerce_unsized)] diff --git a/tests/kani/UnsizedCoercion/double_coercion.rs b/tests/kani/UnsizedCoercion/double_coercion.rs index 26cfb585821e..76158c811c00 100644 --- a/tests/kani/UnsizedCoercion/double_coercion.rs +++ b/tests/kani/UnsizedCoercion/double_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion when using box of box. //! Tests are broken down into different crates to ensure that the reachability works for each case. diff --git a/tests/kani/UnsizedCoercion/rc_outer_coercion.rs b/tests/kani/UnsizedCoercion/rc_outer_coercion.rs index 2543498cca80..7a608d7d1d57 100644 --- a/tests/kani/UnsizedCoercion/rc_outer_coercion.rs +++ b/tests/kani/UnsizedCoercion/rc_outer_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion from using reference counter. //! Tests are broken down into different crates to ensure that the reachability works for each case. mod defs; diff --git a/tests/kani/UnsizedCoercion/trait_to_trait_coercion.rs b/tests/kani/UnsizedCoercion/trait_to_trait_coercion.rs index 32875f8b8e22..b7750db487e2 100644 --- a/tests/kani/UnsizedCoercion/trait_to_trait_coercion.rs +++ b/tests/kani/UnsizedCoercion/trait_to_trait_coercion.rs @@ -1,6 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --mir-linker --enable-unstable //! Check the basic coercion from using built-in references and pointers. //! Tests are broken down into different crates to ensure that the reachability works for each case. diff --git a/tests/perf/string/src/any_str.rs b/tests/perf/string/src/any_str.rs index 3b58d56750a7..6b3f9cffbc37 100644 --- a/tests/perf/string/src/any_str.rs +++ b/tests/perf/string/src/any_str.rs @@ -1,10 +1,5 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// -// kani-flags: --enable-unstable --mir-linker -// -//! This test is to check MIR linker state of the art. -//! I.e.: Currently, this should fail with missing function definition. #[kani::proof] #[kani::unwind(4)] diff --git a/tests/slow/kani/Strings/copy_empty_string_by_intrinsic.rs b/tests/slow/kani/Strings/copy_empty_string_by_intrinsic.rs index a0c1bef7d003..596bedaa7825 100644 --- a/tests/slow/kani/Strings/copy_empty_string_by_intrinsic.rs +++ b/tests/slow/kani/Strings/copy_empty_string_by_intrinsic.rs @@ -1,7 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// kani-flags: --enable-unstable --mir-linker //! Make sure we can handle explicit copy_nonoverlapping on empty string //! This used to trigger an issue: https://github.com/model-checking/kani/issues/241 diff --git a/tests/ui/mir-linker/generic-harness/incorrect.rs b/tests/ui/mir-linker/generic-harness/incorrect.rs index e68eba6ec834..e52b06801517 100644 --- a/tests/ui/mir-linker/generic-harness/incorrect.rs +++ b/tests/ui/mir-linker/generic-harness/incorrect.rs @@ -1,8 +1,6 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT // -// kani-flags: --enable-unstable --mir-linker -// //! Checks that we correctly fail if the harness is a generic function. #[kani::proof] From 68387e4cf462541028892772d2fc12da8abd6a6c Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 2 Oct 2024 14:01:39 -0400 Subject: [PATCH 109/159] Enable doctests to avoid coding mistakes in our API documentation (#3557) In order to enable the tests, I had to fix a few tests, enhance tests with hidden code, and mark a few as `no_run` instead of `rust`. The tests marked as `no_run` will at least compile, but we cannot execute them otherwise execution would be stuck in an infinite loop, since we provide dummy implementation to a lot of our intrinsics with `loop {}`. Finally, as I was cleaning up the documentation, I noticed that we accidently published an API that was meant to be internal to Kani. I marked this API as deprecated for now. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- library/kani/src/contracts.rs | 43 ++++++-- library/kani/src/invariant.rs | 33 ++++++- library/kani/src/lib.rs | 2 + library/kani/src/shadow.rs | 2 +- library/kani/src/slice.rs | 3 +- library/kani_core/src/lib.rs | 98 +++++++++++++------ scripts/kani-regression.sh | 4 +- .../deprecated_warning.expected | 1 + .../ui/check_deprecated/deprecated_warning.rs | 10 ++ 9 files changed, 152 insertions(+), 44 deletions(-) create mode 100644 tests/ui/check_deprecated/deprecated_warning.expected create mode 100644 tests/ui/check_deprecated/deprecated_warning.rs diff --git a/library/kani/src/contracts.rs b/library/kani/src/contracts.rs index beddd7cc580e..2dc2bd2a4957 100644 --- a/library/kani/src/contracts.rs +++ b/library/kani/src/contracts.rs @@ -25,7 +25,7 @@ //! simple division function `my_div`: //! //! ``` -//! fn my_div(dividend: u32, divisor: u32) -> u32 { +//! fn my_div(dividend: usize, divisor: usize) -> usize { //! dividend / divisor //! } //! ``` @@ -35,8 +35,12 @@ //! allows us to declare constraints on what constitutes valid inputs to our //! function. In this case we would want to disallow a divisor that is `0`. //! -//! ```ignore +//! ``` +//! # use kani::requires; //! #[requires(divisor != 0)] +//! # fn my_div(dividend: usize, divisor: usize) -> usize { +//! # dividend / divisor +//! # } //! ``` //! //! This is called a precondition, because it is enforced before (pre-) the @@ -51,7 +55,11 @@ //! approximation of the result of division for instance could be this: //! //! ``` -//! #[ensures(|result : &u32| *result <= dividend)] +//! # use kani::ensures; +//! #[ensures(|result : &usize| *result <= dividend)] +//! # fn my_div(dividend: usize, divisor: usize) -> usize { +//! # dividend / divisor +//! # } //! ``` //! //! This is called a postcondition and it also has access to the arguments and @@ -66,9 +74,11 @@ //! order does not matter. In our example putting them together looks like this: //! //! ``` -//! #[kani::requires(divisor != 0)] -//! #[kani::ensures(|result : &u32| *result <= dividend)] -//! fn my_div(dividend: u32, divisor: u32) -> u32 { +//! use kani::{requires, ensures}; +//! +//! #[requires(divisor != 0)] +//! #[ensures(|result : &usize| *result <= dividend)] +//! fn my_div(dividend: usize, divisor: usize) -> usize { //! dividend / divisor //! } //! ``` @@ -84,9 +94,18 @@ //! function we want to check. //! //! ``` +//! # use kani::{requires, ensures}; +//! # +//! # #[requires(divisor != 0)] +//! # #[ensures(|result : &usize| *result <= dividend)] +//! # fn my_div(dividend: usize, divisor: usize) -> usize { +//! # dividend / divisor +//! # } +//! # //! #[kani::proof_for_contract(my_div)] //! fn my_div_harness() { -//! my_div(kani::any(), kani::any()) } +//! my_div(kani::any(), kani::any()); +//! } //! ``` //! //! The harness is checked like any other by running `cargo kani` and can be @@ -104,10 +123,18 @@ //! the contract will be used automatically. //! //! ``` +//! # use kani::{requires, ensures}; +//! # +//! # #[requires(divisor != 0)] +//! # #[ensures(|result : &usize| *result <= dividend)] +//! # fn my_div(dividend: usize, divisor: usize) -> usize { +//! # dividend / divisor +//! # } +//! # //! #[kani::proof] //! #[kani::stub_verified(my_div)] //! fn use_div() { -//! let v = vec![...]; +//! let v = kani::vec::any_vec::(); //! let some_idx = my_div(v.len() - 1, 3); //! v[some_idx]; //! } diff --git a/library/kani/src/invariant.rs b/library/kani/src/invariant.rs index 068cdedc277e..b1bac031b677 100644 --- a/library/kani/src/invariant.rs +++ b/library/kani/src/invariant.rs @@ -39,6 +39,14 @@ /// ``` /// You can specify its safety invariant as: /// ```rust +/// # #[derive(kani::Arbitrary)] +/// # pub struct MyDate { +/// # day: u8, +/// # month: u8, +/// # year: i64, +/// # } +/// # fn days_in_month(_: i64, _: u8) -> u8 { 31 } +/// /// impl kani::Invariant for MyDate { /// fn is_safe(&self) -> bool { /// self.month > 0 @@ -49,12 +57,33 @@ /// } /// ``` /// And use it to check that your APIs are safe: -/// ```rust +/// ```no_run +/// # use kani::Invariant; +/// # +/// # #[derive(kani::Arbitrary)] +/// # pub struct MyDate { +/// # day: u8, +/// # month: u8, +/// # year: i64, +/// # } +/// # +/// # fn days_in_month(_: i64, _: u8) -> u8 { todo!() } +/// # fn increase_date(_: &mut MyDate, _: u8) { todo!() } +/// # +/// # impl Invariant for MyDate { +/// # fn is_safe(&self) -> bool { +/// # self.month > 0 +/// # && self.month <= 12 +/// # && self.day > 0 +/// # && self.day <= days_in_month(self.year, self.month) +/// # } +/// # } +/// # /// #[kani::proof] /// fn check_increase_date() { /// let mut date: MyDate = kani::any(); /// // Increase date by one day -/// increase_date(date, 1); +/// increase_date(&mut date, 1); /// assert!(date.is_safe()); /// } /// ``` diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 053af760067b..ce1eeb992121 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -19,6 +19,8 @@ #![feature(ptr_metadata)] #![feature(f16)] #![feature(f128)] +// Need to add this since we are deprecating `kani::check`. Remove this when we remove that API. +#![allow(deprecated)] // Allow us to use `kani::` to access crate features. extern crate self as kani; diff --git a/library/kani/src/shadow.rs b/library/kani/src/shadow.rs index a7ea57c6fd40..48ce8f0f9211 100644 --- a/library/kani/src/shadow.rs +++ b/library/kani/src/shadow.rs @@ -10,7 +10,7 @@ //! //! # Example //! -//! ``` +//! ```no_run //! use kani::shadow::ShadowMem; //! use std::alloc::{alloc, Layout}; //! diff --git a/library/kani/src/slice.rs b/library/kani/src/slice.rs index 1b13de29d589..c419a881fa55 100644 --- a/library/kani/src/slice.rs +++ b/library/kani/src/slice.rs @@ -9,7 +9,8 @@ use crate::{any, assume}; /// /// # Example: /// -/// ```rust +/// ```no_run +/// # fn foo(_: &[i32]) {} /// let arr = [1, 2, 3]; /// let slice = kani::slice::any_slice_of_array(&arr); /// foo(slice); // where foo is a function that takes a slice and verifies a property about it diff --git a/library/kani_core/src/lib.rs b/library/kani_core/src/lib.rs index 5df8f2228c62..839313f084c2 100644 --- a/library/kani_core/src/lib.rs +++ b/library/kani_core/src/lib.rs @@ -42,6 +42,7 @@ macro_rules! kani_lib { pub use kani_core::*; kani_core::kani_intrinsics!(core); kani_core::generate_arbitrary!(core); + kani_core::check_intrinsic!(); pub mod mem { kani_core::kani_mem!(core); @@ -58,6 +59,10 @@ macro_rules! kani_lib { kani_core::kani_intrinsics!(std); kani_core::generate_arbitrary!(std); + kani_core::check_intrinsic! { + msg="This API will be made private in future releases.", vis=pub + } + pub mod mem { kani_core::kani_mem!(std); } @@ -85,7 +90,7 @@ macro_rules! kani_intrinsics { /// /// The code snippet below should never panic. /// - /// ```rust + /// ```no_run /// let i : i32 = kani::any(); /// kani::assume(i > 10); /// if i < 0 { @@ -95,7 +100,7 @@ macro_rules! kani_intrinsics { /// /// The following code may panic though: /// - /// ```rust + /// ```no_run /// let i : i32 = kani::any(); /// assert!(i < 0, "This may panic and verification should fail."); /// kani::assume(i > 10); @@ -118,7 +123,7 @@ macro_rules! kani_intrinsics { /// /// # Example: /// - /// ```rust + /// ```no_run /// let x: bool = kani::any(); /// let y = !x; /// kani::assert(x || y, "ORing a boolean variable with its negation must be true") @@ -138,35 +143,15 @@ macro_rules! kani_intrinsics { assert!(cond, "{}", msg); } - /// Creates an assertion of the specified condition and message, 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 + /// ```no_run + /// # use crate::kani; + /// # + /// # let array: [u8; 10] = kani::any(); + /// # let slice = kani::slice::any_slice_of_array(&array); /// kani::cover(slice.len() == 0, "The slice may have a length of 0"); /// ``` /// @@ -193,7 +178,11 @@ macro_rules! kani_intrinsics { /// 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 + /// ```no_run + /// # use std::num::NonZeroU8; + /// # use crate::kani; + /// # + /// # fn fn_under_verification(_: NonZeroU8) {} /// let inputA = kani::any::(); /// fn_under_verification(inputA); /// ``` @@ -231,7 +220,11 @@ macro_rules! kani_intrinsics { /// 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 + /// ```no_run + /// # use std::num::NonZeroU8; + /// # use crate::kani; + /// # + /// # fn fn_under_verification(_: u8) {} /// let inputA: u8 = kani::any_where(|x| *x < 12); /// fn_under_verification(inputA); /// ``` @@ -460,3 +453,48 @@ macro_rules! kani_intrinsics { } }; } + +#[macro_export] +macro_rules! check_intrinsic { + ($(msg=$msg:literal, vis=$vis:vis)?) => { + /// Creates a non-fatal property with the specified condition and message. + /// + /// This check will not impact the program control flow even when it fails. + /// + /// # Example: + /// + /// ```no_run + /// let x: bool = kani::any(); + /// let y = !x; + /// kani::check(x || y, "ORing a boolean variable with its negation must be true"); + /// kani::check(x == y, "A boolean variable is always different than its negation"); + /// kani::cover!(true, "This should still be reachable"); + /// ``` + /// + /// # Deprecated + /// + /// This function was meant to be internal only, and it was added to Kani's public interface + /// by mistake. Thus, it will be made private in future releases. + /// Instead, we recommend users to either use `assert` or `cover` to create properties they + /// would like to verify. + /// + /// See for more details. + #[cfg(not(feature = "concrete_playback"))] + #[inline(never)] + #[rustc_diagnostic_item = "KaniCheck"] + // TODO: Remove the `#![allow(deprecated)]` inside kani's crate once this is made private. + $(#[deprecated(since="0.55.0", note=$msg)])? + $($vis)? const fn check(cond: bool, msg: &'static str) { + let _ = cond; + let _ = msg; + } + + #[cfg(feature = "concrete_playback")] + #[inline(never)] + #[rustc_diagnostic_item = "KaniCheck"] + $(#[deprecated(since="0.55.0", note=$msg)])? + $($vis)? const fn check(cond: bool, msg: &'static str) { + assert!(cond, "{}", msg); + } + }; +} diff --git a/scripts/kani-regression.sh b/scripts/kani-regression.sh index bd6b04d7386e..be2548235ce6 100755 --- a/scripts/kani-regression.sh +++ b/scripts/kani-regression.sh @@ -41,8 +41,8 @@ cargo test -p cprover_bindings cargo test -p kani-compiler cargo test -p kani-driver cargo test -p kani_metadata -# skip doc tests and enable assertions to fail -cargo test -p kani --lib --features concrete_playback +# Use concrete playback to enable assertions failure +cargo test -p kani --features concrete_playback # Test the actual macros, skipping doc tests and enabling extra traits for "syn" # so we can debug print AST RUSTFLAGS=--cfg=kani_sysroot cargo test -p kani_macros --features syn/extra-traits --lib diff --git a/tests/ui/check_deprecated/deprecated_warning.expected b/tests/ui/check_deprecated/deprecated_warning.expected new file mode 100644 index 000000000000..3a0760035f8b --- /dev/null +++ b/tests/ui/check_deprecated/deprecated_warning.expected @@ -0,0 +1 @@ +warning: use of deprecated function `kani::check`: This API will be made private in future releases. diff --git a/tests/ui/check_deprecated/deprecated_warning.rs b/tests/ui/check_deprecated/deprecated_warning.rs new file mode 100644 index 000000000000..f3d791256081 --- /dev/null +++ b/tests/ui/check_deprecated/deprecated_warning.rs @@ -0,0 +1,10 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: --only-codegen + +/// Ensure that Kani prints a deprecation warning if users invoke `kani::check`. +#[kani::proof] +fn internal_api() { + kani::check(kani::any(), "oops"); +} From f3ed5e80fb24e2e8256244b93657bb99821eaaff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:22:55 -0400 Subject: [PATCH 110/159] Automatic toolchain upgrade to nightly-2024-10-03 (#3564) Update Rust toolchain from nightly-2024-10-02 to nightly-2024-10-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 bd0b55ef531f..7ef640dbfdc9 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-10-02" +channel = "nightly-2024-10-03" components = ["llvm-tools", "rustc-dev", "rust-src", "rustfmt"] From d2f5dbe04ade9bafd72d0dcf174e0d71e245f1ee Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Fri, 4 Oct 2024 20:04:11 -0400 Subject: [PATCH 111/159] Add experimental API to generate arbitrary pointers (#3538) This change adds a pointer generator that can non-deterministically generate a pointer with different properties. This generator allows users to build pointers with different allocation status, initialization and alignment. It contains an internal buffer that it uses to generate `InBounds` and `OutOfBounds` pointers. In those cases, the pointers will have the same provenance as the generator, and the same lifetime. This approach is different than generating a pointer from an arbitrary `usize`. Kani uses demonic non-determinism to track allocation lifetimes, which makes hard to reason about during verification. I.e., one cannot assume a pointer is valid, and initialized, and this can only be accomplished by manually tracking the pointer status. I added the new API under `-Z mem-predicates` since it allows reasoning about memory, and I was hoping this wouldn't need another unstable flag. :smile: 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: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> --- library/kani_core/src/arbitrary.rs | 19 + library/kani_core/src/arbitrary/pointer.rs | 371 ++++++++++++++++++ library/kani_macros/src/derive.rs | 51 ++- .../arbitrary/ptrs/pointer_generator.expected | 21 + .../arbitrary/ptrs/pointer_generator.rs | 38 ++ .../ptrs/pointer_generator_error.expected | 9 + .../arbitrary/ptrs/pointer_generator_error.rs | 12 + .../arbitrary/ptrs/pointer_inbounds.expected | 39 ++ .../arbitrary/ptrs/pointer_inbounds.rs | 56 +++ .../verify_std_cmd/verify_core.rs | 11 + .../verify_std_cmd/verify_std.expected | 5 +- .../verify_std_cmd/verify_std.sh | 9 +- .../arbitrary-ptr-doc/doc_examples.expected | 24 ++ tests/ui/arbitrary-ptr-doc/doc_examples.rs | 90 +++++ 14 files changed, 743 insertions(+), 12 deletions(-) create mode 100644 library/kani_core/src/arbitrary/pointer.rs create mode 100644 tests/expected/arbitrary/ptrs/pointer_generator.expected create mode 100644 tests/expected/arbitrary/ptrs/pointer_generator.rs create mode 100644 tests/expected/arbitrary/ptrs/pointer_generator_error.expected create mode 100644 tests/expected/arbitrary/ptrs/pointer_generator_error.rs create mode 100644 tests/expected/arbitrary/ptrs/pointer_inbounds.expected create mode 100644 tests/expected/arbitrary/ptrs/pointer_inbounds.rs create mode 100644 tests/ui/arbitrary-ptr-doc/doc_examples.expected create mode 100644 tests/ui/arbitrary-ptr-doc/doc_examples.rs diff --git a/library/kani_core/src/arbitrary.rs b/library/kani_core/src/arbitrary.rs index 8c6cfd335104..1dc30feca566 100644 --- a/library/kani_core/src/arbitrary.rs +++ b/library/kani_core/src/arbitrary.rs @@ -7,11 +7,16 @@ //! by an unconstrained symbolic value of their size (e.g., `u8`, `u16`, `u32`, etc.). //! //! TODO: Use this inside kani library so that we dont have to maintain two copies of the same proc macro for arbitrary. + +mod pointer; + #[macro_export] #[allow(clippy::crate_in_macro_def)] macro_rules! generate_arbitrary { ($core:path) => { use core_path::marker::{PhantomData, PhantomPinned}; + use core_path::mem::MaybeUninit; + use core_path::ptr::{self, addr_of_mut}; use $core as core_path; pub trait Arbitrary @@ -157,6 +162,15 @@ macro_rules! generate_arbitrary { } } + impl Arbitrary for MaybeUninit + where + T: Arbitrary, + { + fn any() -> Self { + if crate::kani::any() { MaybeUninit::new(T::any()) } else { MaybeUninit::uninit() } + } + } + arbitrary_tuple!(A); arbitrary_tuple!(A, B); arbitrary_tuple!(A, B, C); @@ -169,6 +183,11 @@ macro_rules! generate_arbitrary { arbitrary_tuple!(A, B, C, D, E, F, G, H, I, J); arbitrary_tuple!(A, B, C, D, E, F, G, H, I, J, K); arbitrary_tuple!(A, B, C, D, E, F, G, H, I, J, K, L); + + pub use self::arbitrary_ptr::*; + mod arbitrary_ptr { + kani_core::ptr_generator!(); + } }; } diff --git a/library/kani_core/src/arbitrary/pointer.rs b/library/kani_core/src/arbitrary/pointer.rs new file mode 100644 index 000000000000..0e987b1260c6 --- /dev/null +++ b/library/kani_core/src/arbitrary/pointer.rs @@ -0,0 +1,371 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This macro generates the logic required to generate pointers with arbitrary statuses. +#[allow(clippy::crate_in_macro_def)] +#[macro_export] +macro_rules! ptr_generator { + () => { + use core::marker::PhantomData; + use core::mem::MaybeUninit; + use core::ptr::{self, addr_of_mut}; + use crate::kani; + + /// Pointer generator that can be used to generate arbitrary pointers. + /// + /// This generator allows users to build pointers with different safety properties. + /// This is different than creating a pointer that can have any address, since it will never + /// point to a previously allocated object. + /// See [this section](crate::PointerGenerator#pointer-generator-vs-pointer-with-any-address) + /// for more details. + /// + /// The generator contains an internal buffer of a constant generic size, `BYTES`, that it + /// uses to generate `InBounds` and `OutOfBounds` pointers. + /// In those cases, the generated pointers will have the same provenance as the generator, + /// and the same lifetime. + /// The address of an `InBounds` pointer will represent all possible addresses in the range + /// of the generator's buffer address. + /// + /// For other allocation statuses, the generator will create a pointer that satisfies the + /// given condition. + /// The pointer address will **not** represent all possible addresses that satisfies the + /// given allocation status. + /// + /// For example: + /// ```no_run + /// # use kani::*; + /// # #[kani::proof] + /// # fn harness() { + /// let mut generator = PointerGenerator::<10>::new(); + /// let arbitrary = generator.any_alloc_status::(); + /// kani::assume(arbitrary.status == AllocationStatus::InBounds); + /// // Pointer may be unaligned, but it should be in-bounds, so it is safe to write to + /// unsafe { arbitrary.ptr.write_unaligned(kani::any()) } + /// # } + /// ``` + /// + /// The generator is parameterized by the number of bytes of its internal buffer. + /// See [pointer_generator] function if you would like to create a generator that fits + /// a minimum number of objects of a given type. Example: + /// + /// ```no_run + /// # use kani::*; + /// # #[allow(unused)] + /// # #[kani::proof] + /// # fn harness() { + /// // These generators have the same capacity of 6 bytes. + /// let generator1 = PointerGenerator::<6>::new(); + /// let generator2 = pointer_generator::(); + /// # } + /// ``` + /// + /// ## Buffer size + /// + /// The internal buffer is used to generate pointers, and its size determines the maximum + /// number of pointers it can generate without overlapping. + /// Larger values will impact the maximum distance between generated pointers. + /// + /// We recommend that you pick a size that is at least big enough to + /// cover the cases where all pointers produced are non-overlapping. + /// The buffer size in bytes must be big enough to fit distinct objects for each call + /// of generate pointer. + /// For example, generating two `*mut u8` and one `*mut u32` requires a buffer + /// of at least 6 bytes. + /// + /// This guarantees that your harness covers cases where all generated pointers + /// point to allocated positions that do not overlap. For example: + /// + /// ```no_run + /// # use kani::*; + /// # #[kani::proof] + /// # fn harness() { + /// let mut generator = PointerGenerator::<6>::new(); + /// let ptr1: *mut u8 = generator.any_in_bounds().ptr; + /// let ptr2: *mut u8 = generator.any_in_bounds().ptr; + /// let ptr3: *mut u32 = generator.any_in_bounds().ptr; + /// // This cover is satisfied. + /// cover!((ptr1 as usize) >= (ptr2 as usize) + size_of::() + /// && (ptr2 as usize) >= (ptr3 as usize) + size_of::()); + /// // As well as having overlapping pointers. + /// cover!((ptr1 as usize) == (ptr3 as usize)); + /// # } + /// ``` + /// + /// The first cover will be satisfied, since there exists at least one path where + /// the generator produces inbounds pointers that do not overlap. Such as this scenario: + /// + /// ```text + /// +--------+--------+--------+--------+--------+--------+ + /// | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | + /// +--------+--------+--------+--------+--------+--------+ + /// <--------------- ptr3 --------------><--ptr2-><--ptr1-> + /// ``` + /// + /// I.e., the generator buffer is large enough to fit all 3 objects without overlapping. + /// + /// In contrast, if we had used a size of 1 element, all calls to `any_in_bounds()` would + /// return elements that overlap, and the first cover would no longer be satisfied. + /// + /// Note that the generator requires a minimum number of 1 byte, otherwise the + /// `InBounds` case would never be covered. + /// Compilation will fail if you try to create a generator of size `0`. + /// + /// Additionally, the verification will fail if you try to generate a pointer for a type + /// with size greater than the buffer size. + /// + /// Use larger buffer size if you want to cover scenarios where the distance + /// between the generated pointers matters. + /// + /// The only caveats of using very large numbers are: + /// 1. The value cannot exceed the solver maximum object size (currently 2^48 by default), neither Rust's + /// maximum object size (`isize::MAX`). + /// 2. Larger sizes could impact performance as they can lead to an exponential increase in the number of possibilities of pointer placement within the buffer. + /// + /// # Pointer provenance + /// + /// The pointer returned in the `InBounds` and `OutOfBounds` case will have the same + /// provenance as the generator. + /// + /// Use the same generator if you want to handle cases where 2 or more pointers may overlap. E.g.: + /// ```no_run + /// # use kani::*; + /// # #[kani::proof] + /// # fn harness() { + /// let mut generator = pointer_generator::(); + /// let ptr1 = generator.any_in_bounds::().ptr; + /// let ptr2 = generator.any_in_bounds::().ptr; + /// // This cover is satisfied. + /// cover!(ptr1 == ptr2) + /// # } + /// ``` + /// + /// If you want to cover cases where two or more pointers may not have the same + /// provenance, you will need to instantiate multiple generators. + /// You can also apply non-determinism to cover cases where the pointers may or may not + /// have the same provenance. E.g.: + /// + /// ```no_run + /// # use kani::*; + /// # unsafe fn my_target(_ptr1: *const T, _ptr2: *const T) {} + /// # #[kani::proof] + /// # fn harness() { + /// let mut generator1 = pointer_generator::(); + /// let mut generator2 = pointer_generator::(); + /// let ptr1: *const char = generator1.any_in_bounds().ptr; + /// let ptr2: *const char = if kani::any() { + /// // Pointers will have same provenance and may overlap. + /// generator1.any_in_bounds().ptr + /// } else { + /// // Pointers will have different provenance and will not overlap. + /// generator2.any_in_bounds().ptr + /// }; + /// // Invoke the function under verification + /// unsafe { my_target(ptr1, ptr2) }; + /// # } + /// ``` + /// + /// # Pointer Generator vs Pointer with any address + /// + /// Creating a pointer using the generator is different than generating a pointer + /// with any address. + /// + /// I.e.: + /// ```no_run + /// # use kani::*; + /// # #[kani::proof] + /// # #[allow(unused)] + /// # fn harness() { + /// // This pointer represents any address, and it may point to anything in memory, + /// // allocated or not. + /// let ptr1 = kani::any::() as *const u8; + /// + /// // This pointer address will either point to unallocated memory, to a dead object + /// // or to allocated memory within the generator address space. + /// let mut generator = PointerGenerator::<5>::new(); + /// let ptr2: *const u8 = generator.any_alloc_status().ptr; + /// # } + /// ``` + /// + /// Kani cannot reason about a pointer allocation status (except for asserting its validity). + /// Thus, the generator was introduced to help writing harnesses that need to impose + /// constraints to the arbitrary pointer allocation status. + /// It also allow us to restrict the pointer provenance, excluding for example the address of + /// variables that are not available in the current context. + /// As a limitation, it will not cover the entire address space that a pointer can take. + /// + /// If your harness does not need to reason about pointer allocation, for example, verifying + /// pointer wrapping arithmetic, using a pointer with any address will allow you to cover + /// all possible scenarios. + #[derive(Debug)] + pub struct PointerGenerator { + // Internal allocation that may be used to generate valid pointers. + buf: MaybeUninit<[u8; BYTES]>, + } + + /// Enumeration with the cases currently covered by the pointer generator. + #[derive(Copy, Clone, Debug, PartialEq, Eq, kani::Arbitrary)] + pub enum AllocationStatus { + /// Dangling pointers + Dangling, + /// Pointer to dead object + DeadObject, + /// Null pointers + Null, + /// In bounds pointer (it may be unaligned) + InBounds, + /// The pointer cannot be read / written to for the given type since one or more bytes + /// would be out of bounds of the current allocation. + OutOfBounds, + } + + /// Holds information about a pointer that is generated non-deterministically. + #[derive(Debug)] + pub struct ArbitraryPointer<'a, T> { + /// The pointer that was generated. + pub ptr: *mut T, + /// The expected allocation status. + pub status: AllocationStatus, + /// Whether the pointer was generated with an initialized value or not. + pub is_initialized: bool, + /// Lifetime for this object. + phantom: PhantomData<&'a T>, + } + + impl PointerGenerator { + const BUF_LEN: usize = BYTES; + const VALID : () = assert!(BYTES > 0, "PointerGenerator requires at least one byte."); + + /// Create a new PointerGenerator. + #[kani::unstable_feature( + feature = "mem-predicates", + issue = 2690, + reason = "experimental memory predicates and manipulation feature" + )] + pub fn new() -> Self { + let _ = Self::VALID; + PointerGenerator { buf: MaybeUninit::uninit() } + } + + /// Creates a raw pointer with non-deterministic properties. + /// + /// The pointer returned is either dangling or has the same provenance of the generator. + #[kani::unstable_feature( + feature = "mem-predicates", + issue = 2690, + reason = "experimental memory predicates and manipulation feature" + )] + pub fn any_alloc_status<'a, T>(&'a mut self) -> ArbitraryPointer<'a, T> + where T: kani::Arbitrary + { + assert!(core::mem::size_of::() <= Self::BUF_LEN, + "Cannot generate in-bounds object of the requested type. Buffer is not big enough." + ); + + let status = kani::any(); + let ptr = match status { + AllocationStatus::Dangling => { + // Generate potentially unaligned pointer. + let offset = kani::any_where(|b: &usize| *b < size_of::()); + crate::ptr::NonNull::::dangling().as_ptr().wrapping_add(offset) + } + AllocationStatus::DeadObject => { + let mut obj: T = kani::any(); + &mut obj as *mut _ + } + AllocationStatus::Null => crate::ptr::null_mut::(), + AllocationStatus::InBounds => { + return self.create_in_bounds_ptr(); + } + AllocationStatus::OutOfBounds => { + // Generate potentially unaligned pointer. + let buf_ptr = addr_of_mut!(self.buf) as *mut u8; + let offset = kani::any_where(|b: &usize| *b < size_of::()); + unsafe { buf_ptr.add(Self::BUF_LEN - offset) as *mut T } + } + }; + + ArbitraryPointer { + ptr, + is_initialized: false, + status, + phantom: PhantomData, + } + } + + /// Creates a in-bounds raw pointer with non-deterministic properties. + /// + /// The pointer points to an allocated location with the same provenance of the generator. + /// The pointer may be unaligned, and the pointee may be uninitialized. + /// + /// ```no_run + /// # use kani::*; + /// # #[kani::proof] + /// # fn check_distance() { + /// let mut generator = PointerGenerator::<6>::new(); + /// let ptr1: *mut u8 = generator.any_in_bounds().ptr; + /// let ptr2: *mut u8 = generator.any_in_bounds().ptr; + /// // SAFETY: Both pointers have the same provenance. + /// let distance = unsafe { ptr1.offset_from(ptr2) }; + /// assert!(distance > -5 && distance < 5) + /// # } + /// ``` + #[kani::unstable_feature( + feature = "mem-predicates", + issue = 2690, + reason = "experimental memory predicates and manipulation feature" + )] + pub fn any_in_bounds<'a, T>(&'a mut self) -> ArbitraryPointer<'a, T> + where T: kani::Arbitrary { + assert!(core::mem::size_of::() <= Self::BUF_LEN, + "Cannot generate in-bounds object of the requested type. Buffer is not big enough." + ); + self.create_in_bounds_ptr() + } + + /// This is the inner logic to create an arbitrary pointer that is inbounds. + /// + /// Note that pointer may be unaligned. + fn create_in_bounds_ptr<'a, T>(&'a mut self) -> ArbitraryPointer<'a, T> + where T: kani::Arbitrary { + assert!(core::mem::size_of::() <= Self::BUF_LEN, + "Cannot generate in-bounds object of the requested type. Buffer is not big enough." + ); + let buf_ptr = addr_of_mut!(self.buf) as *mut u8; + let offset = kani::any_where(|b: &usize| *b <= Self::BUF_LEN - size_of::()); + let ptr = unsafe { buf_ptr.add(offset) as *mut T }; + let is_initialized = kani::any(); + if is_initialized { + unsafe { ptr.write_unaligned(kani::any()) }; + } + ArbitraryPointer { + ptr, + is_initialized, + status: AllocationStatus::InBounds, + phantom: PhantomData, + } + } + } + + kani_core::ptr_generator_fn!(); + }; +} + +#[cfg(not(feature = "no_core"))] +#[macro_export] +macro_rules! ptr_generator_fn { + () => { + /// Create a pointer generator that fits at least `N` elements of type `T`. + pub fn pointer_generator() + -> PointerGenerator<{ size_of::() * NUM_ELTS }> { + PointerGenerator::<{ size_of::() * NUM_ELTS }>::new() + } + }; +} + +/// Don't generate the pointer_generator function here since it requires generic constant +/// expression. +#[cfg(feature = "no_core")] +#[macro_export] +macro_rules! ptr_generator_fn { + () => {}; +} diff --git a/library/kani_macros/src/derive.rs b/library/kani_macros/src/derive.rs index 04b288069387..258afc36af77 100644 --- a/library/kani_macros/src/derive.rs +++ b/library/kani_macros/src/derive.rs @@ -18,10 +18,37 @@ use syn::{ parse_quote, }; +#[cfg(feature = "no_core")] +macro_rules! kani_path { + ($span:expr) => { + quote_spanned! { $span => core::kani } + }; + () => { + quote! { core::kani } + }; +} + +#[cfg(not(feature = "no_core"))] +macro_rules! kani_path { + ($span:expr) => { + quote_spanned! { $span => kani } + }; + () => { + quote! { kani } + }; +} + +/// Generate the Arbitrary implementation for the given type. +/// +/// Note that we cannot use `proc_macro_crate::crate_name()` to discover the name for `kani` crate +/// since we define it as an extern crate via `rustc` command line. +/// +/// In order to support core, we check the `no_core` feature. pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::TokenStream { let trait_name = "Arbitrary"; let derive_item = parse_macro_input!(item as DeriveInput); let item_name = &derive_item.ident; + let kani_path = kani_path!(); let body = fn_any_body(&item_name, &derive_item.data); // Get the safety constraints (if any) to produce type-safe values @@ -36,11 +63,11 @@ pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::Tok let field_refs = field_refs(&item_name, &derive_item.data); quote! { // The generated implementation. - impl #impl_generics kani::Arbitrary for #item_name #ty_generics #where_clause { + impl #impl_generics #kani_path::Arbitrary for #item_name #ty_generics #where_clause { fn any() -> Self { let obj = #body; #field_refs - kani::assume(#safety_conds); + #kani_path::assume(#safety_conds); obj } } @@ -48,7 +75,7 @@ pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::Tok } else { quote! { // The generated implementation. - impl #impl_generics kani::Arbitrary for #item_name #ty_generics #where_clause { + impl #impl_generics #kani_path::Arbitrary for #item_name #ty_generics #where_clause { fn any() -> Self { #body } @@ -60,9 +87,10 @@ pub fn expand_derive_arbitrary(item: proc_macro::TokenStream) -> proc_macro::Tok /// Add a bound `T: Arbitrary` to every type parameter T. fn add_trait_bound_arbitrary(mut generics: Generics) -> Generics { + let kani_path = kani_path!(); generics.params.iter_mut().for_each(|param| { if let GenericParam::Type(type_param) = param { - type_param.bounds.push(parse_quote!(kani::Arbitrary)); + type_param.bounds.push(parse_quote!(#kani_path::Arbitrary)); } }); generics @@ -222,8 +250,10 @@ fn init_symbolic_item(ident: &Ident, fields: &Fields) -> TokenStream { // Expands to an expression like // Self(kani::any(), kani::any(), ..., kani::any()); let init = fields.unnamed.iter().map(|field| { - quote_spanned! {field.span()=> - kani::any() + let span = field.span(); + let kani_path = kani_path!(span); + quote_spanned! {span=> + #kani_path::any() } }); quote! { @@ -349,8 +379,9 @@ fn fn_any_enum(ident: &Ident, data: &DataEnum) -> TokenStream { } }); + let kani_path = kani_path!(); quote! { - match kani::any() { + match #kani_path::any() { #(#arms)* } } @@ -376,6 +407,7 @@ pub fn expand_derive_invariant(item: proc_macro::TokenStream) -> proc_macro::Tok let trait_name = "Invariant"; let derive_item = parse_macro_input!(item as DeriveInput); let item_name = &derive_item.ident; + let kani_path = kani_path!(); let safe_body = safe_body_with_calls(&item_name, &derive_item, trait_name); let field_refs = field_refs(&item_name, &derive_item.data); @@ -387,7 +419,7 @@ pub fn expand_derive_invariant(item: proc_macro::TokenStream) -> proc_macro::Tok let expanded = quote! { // The generated implementation. - impl #impl_generics kani::Invariant for #item_name #ty_generics #where_clause { + impl #impl_generics #kani_path::Invariant for #item_name #ty_generics #where_clause { fn is_safe(&self) -> bool { let obj = self; #field_refs @@ -463,9 +495,10 @@ fn has_field_safety_constraints_inner(_ident: &Ident, fields: &Fields) -> bool { /// Add a bound `T: Invariant` to every type parameter T. pub fn add_trait_bound_invariant(mut generics: Generics) -> Generics { + let kani_path = kani_path!(); generics.params.iter_mut().for_each(|param| { if let GenericParam::Type(type_param) = param { - type_param.bounds.push(parse_quote!(kani::Invariant)); + type_param.bounds.push(parse_quote!(#kani_path::Invariant)); } }); generics diff --git a/tests/expected/arbitrary/ptrs/pointer_generator.expected b/tests/expected/arbitrary/ptrs/pointer_generator.expected new file mode 100644 index 000000000000..4627d354aebb --- /dev/null +++ b/tests/expected/arbitrary/ptrs/pointer_generator.expected @@ -0,0 +1,21 @@ +Checking harness check_arbitrary_ptr... + +Status: SUCCESS\ +Description: ""OutOfBounds"" + +Status: SUCCESS\ +Description: ""InBounds"" + +Status: SUCCESS\ +Description: ""NullPtr"" + +Status: FAILURE\ +Description: ""DeadObject"" + +Status: SATISFIED\ +Description: "Dangling" + +Status: UNREACHABLE\ +Description: ""Dangling write"" + +Verification failed for - check_arbitrary_ptr diff --git a/tests/expected/arbitrary/ptrs/pointer_generator.rs b/tests/expected/arbitrary/ptrs/pointer_generator.rs new file mode 100644 index 000000000000..214c7fad2bfa --- /dev/null +++ b/tests/expected/arbitrary/ptrs/pointer_generator.rs @@ -0,0 +1,38 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z mem-predicates +//! Check the behavior of the new `PointerGenerator`. +extern crate kani; + +use kani::{AllocationStatus, PointerGenerator, cover}; + +/// Harness that checks that all cases are covered and the code behaves as expected. +/// +/// Note that for `DeadObject`, `Dangling`, and `OutOfBounds` the predicate will fail due to demonic non-determinism. +#[kani::proof] +fn check_arbitrary_ptr() { + let mut generator = PointerGenerator::<10>::new(); + let arbitrary = generator.any_alloc_status::(); + let ptr = arbitrary.ptr; + match arbitrary.status { + AllocationStatus::Dangling => { + cover!(true, "Dangling"); + assert!(!kani::mem::can_write_unaligned(ptr), "Dangling write"); + } + AllocationStatus::Null => { + assert!(!kani::mem::can_write_unaligned(ptr), "NullPtr"); + } + AllocationStatus::DeadObject => { + // Due to demonic non-determinism, the API will trigger an error. + assert!(!kani::mem::can_write_unaligned(ptr), "DeadObject"); + } + AllocationStatus::OutOfBounds => { + assert!(!kani::mem::can_write_unaligned(ptr), "OutOfBounds"); + } + AllocationStatus::InBounds => { + // This should always succeed + assert!(kani::mem::can_write_unaligned(ptr), "InBounds"); + } + }; +} diff --git a/tests/expected/arbitrary/ptrs/pointer_generator_error.expected b/tests/expected/arbitrary/ptrs/pointer_generator_error.expected new file mode 100644 index 000000000000..a0592c586a03 --- /dev/null +++ b/tests/expected/arbitrary/ptrs/pointer_generator_error.expected @@ -0,0 +1,9 @@ +error[E0080]: evaluation of `kani::PointerGenerator::<0>::VALID` failed\ + +the evaluated program panicked at 'PointerGenerator requires at least one byte.' + +note: the above error was encountered while instantiating `fn kani::PointerGenerator::<0>::new`\ +pointer_generator_error.rs\ +|\ +| let _generator = PointerGenerator::<0>::new();\ +| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/expected/arbitrary/ptrs/pointer_generator_error.rs b/tests/expected/arbitrary/ptrs/pointer_generator_error.rs new file mode 100644 index 000000000000..30b50f3699e3 --- /dev/null +++ b/tests/expected/arbitrary/ptrs/pointer_generator_error.rs @@ -0,0 +1,12 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z mem-predicates +//! Check misusage of pointer generator fails compilation. +extern crate kani; + +use kani::PointerGenerator; + +pub fn check_invalid_generator() { + let _generator = PointerGenerator::<0>::new(); +} diff --git a/tests/expected/arbitrary/ptrs/pointer_inbounds.expected b/tests/expected/arbitrary/ptrs/pointer_inbounds.expected new file mode 100644 index 000000000000..838829163a36 --- /dev/null +++ b/tests/expected/arbitrary/ptrs/pointer_inbounds.expected @@ -0,0 +1,39 @@ +Checking harness check_overlap... + +Status: SATISFIED\ +Description: "Same" + +Status: SATISFIED\ +Description: "Overlap" + +Status: SATISFIED\ +Description: "Greater" + +Status: SATISFIED\ +Description: "Smaller" + +Checking harness check_alignment... + +Status: SUCCESS\ +Description: ""Aligned"" + +Status: SUCCESS\ +Description: ""Unaligned"" + +Checking harness check_inbounds_initialized... + +Status: SUCCESS\ +Description: ""ValidRead"" + +Checking harness check_inbounds... + +Status: SATISFIED\ +Description: "Uninitialized" + +Status: SATISFIED\ +Description: "Initialized" + +Status: SUCCESS\ +Description: ""ValidWrite"" + +Complete - 4 successfully verified harnesses, 0 failures, 4 total. diff --git a/tests/expected/arbitrary/ptrs/pointer_inbounds.rs b/tests/expected/arbitrary/ptrs/pointer_inbounds.rs new file mode 100644 index 000000000000..36e4abeff48b --- /dev/null +++ b/tests/expected/arbitrary/ptrs/pointer_inbounds.rs @@ -0,0 +1,56 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// kani-flags: -Z mem-predicates +//! Check different cases for `PointerGenerator` for in-bounds pointers. +//! TODO: Enable initialization checks (`-Z uninit-checks`) once we add support to unions. +//! The current instrumentation does not work in the presence of MaybeUninit which we use +//! to implement PointerGenerator. +//! Kani will detect the usage of MaybeUninit and fail the verification. +extern crate kani; + +use kani::PointerGenerator; + +#[kani::proof] +fn check_inbounds() { + let mut generator = kani::pointer_generator::(); + let ptr = generator.any_in_bounds::().ptr; + kani::cover!(!kani::mem::can_read_unaligned(ptr), "Uninitialized"); + kani::cover!(kani::mem::can_read_unaligned(ptr), "Initialized"); + assert!(kani::mem::can_write_unaligned(ptr), "ValidWrite"); +} + +#[kani::proof] +fn check_inbounds_initialized() { + let mut generator = kani::pointer_generator::(); + let arbitrary = generator.any_in_bounds::(); + kani::assume(arbitrary.is_initialized); + assert!(kani::mem::can_read_unaligned(arbitrary.ptr), "ValidRead"); +} + +#[kani::proof] +fn check_alignment() { + let mut generator = kani::pointer_generator::(); + let ptr: *mut char = generator.any_in_bounds().ptr; + if ptr.is_aligned() { + assert!(kani::mem::can_write(ptr), "Aligned"); + } else { + assert!(!kani::mem::can_write(ptr), "Not aligned"); + assert!(kani::mem::can_write_unaligned(ptr), "Unaligned"); + } +} + +#[kani::proof] +fn check_overlap() { + let mut generator = kani::pointer_generator::(); + let ptr_1 = generator.any_in_bounds::().ptr; + let ptr_2 = generator.any_in_bounds::().ptr; + kani::cover!(ptr_1 == ptr_2, "Same"); + kani::cover!(ptr_1 == unsafe { ptr_2.byte_add(1) }, "Overlap"); + + let distance = unsafe { ptr_1.offset_from(ptr_2) }; + kani::cover!(distance > 0, "Greater"); + kani::cover!(distance < 0, "Smaller"); + + assert!(distance >= -4 && distance <= 4, "Expected a maximum distance of 4 elements"); +} diff --git a/tests/script-based-pre/verify_std_cmd/verify_core.rs b/tests/script-based-pre/verify_std_cmd/verify_core.rs index 9bdabb32dde3..551345653c7b 100644 --- a/tests/script-based-pre/verify_std_cmd/verify_core.rs +++ b/tests/script-based-pre/verify_std_cmd/verify_core.rs @@ -76,4 +76,15 @@ pub mod verify { unsafe fn add_one(inout: *mut [u32]) { inout.as_mut_unchecked().iter_mut().for_each(|e| *e += 1) } + + /// Test that arbitrary pointer works as expected. + /// Disable it for uninit checks, since these checks do not support `MaybeUninit` which is used + /// by the pointer generator. + #[kani::proof] + #[cfg(not(uninit_checks))] + fn check_any_ptr() { + let mut generator = kani::PointerGenerator::<8>::new(); + let ptr = generator.any_in_bounds::().ptr; + assert!(kani::mem::can_write_unaligned(ptr)); + } } diff --git a/tests/script-based-pre/verify_std_cmd/verify_std.expected b/tests/script-based-pre/verify_std_cmd/verify_std.expected index 16705ffcfefa..157adb94ba16 100644 --- a/tests/script-based-pre/verify_std_cmd/verify_std.expected +++ b/tests/script-based-pre/verify_std_cmd/verify_std.expected @@ -15,10 +15,13 @@ VERIFICATION:- SUCCESSFUL Checking harness verify::check_swap_tuple... VERIFICATION:- SUCCESSFUL +Checking harness verify::check_any_ptr... +VERIFICATION:- SUCCESSFUL + Checking harness num::verify::check_non_zero... VERIFICATION:- SUCCESSFUL -Complete - 6 successfully verified harnesses, 0 failures, 6 total. +Complete - 7 successfully verified harnesses, 0 failures, 7 total. [TEST] Run kani verify-std -Z uninit-checks 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 f06e8a8f9921..022e7b79de4a 100755 --- a/tests/script-based-pre/verify_std_cmd/verify_std.sh +++ b/tests/script-based-pre/verify_std_cmd/verify_std.sh @@ -52,10 +52,15 @@ 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 -Z mem-predicates +kani verify-std -Z unstable-options "${TMP_DIR}/library" \ + --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing \ + -Z mem-predicates echo "[TEST] Run kani verify-std -Z uninit-checks" -RUSTFLAGS="--cfg=uninit_checks" kani verify-std -Z unstable-options "${TMP_DIR}/library" --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing -Z mem-predicates -Z uninit-checks +export RUSTFLAGS="--cfg=uninit_checks" +kani verify-std -Z unstable-options "${TMP_DIR}/library" \ + --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing \ + -Z mem-predicates -Z uninit-checks # Cleanup rm -r ${TMP_DIR} diff --git a/tests/ui/arbitrary-ptr-doc/doc_examples.expected b/tests/ui/arbitrary-ptr-doc/doc_examples.expected new file mode 100644 index 000000000000..19c01006f313 --- /dev/null +++ b/tests/ui/arbitrary-ptr-doc/doc_examples.expected @@ -0,0 +1,24 @@ +Checking harness check_distance... +VERIFICATION:- SUCCESSFUL + +Checking harness diff_from_usize... +VERIFICATION:- SUCCESSFUL + +Checking harness usage_example... +VERIFICATION:- SUCCESSFUL + +Checking harness pointer_may_be_same... + ** 1 of 1 cover properties satisfied +VERIFICATION:- SUCCESSFUL + +Checking harness generator_large_enough... + ** 2 of 2 cover properties satisfied +VERIFICATION:- SUCCESSFUL + +Checking harness same_capacity... +VERIFICATION:- SUCCESSFUL + +Checking harness basic_inbounds... +VERIFICATION:- SUCCESSFUL + +Complete - 7 successfully verified harnesses, 0 failures, 7 total. diff --git a/tests/ui/arbitrary-ptr-doc/doc_examples.rs b/tests/ui/arbitrary-ptr-doc/doc_examples.rs new file mode 100644 index 000000000000..319863f6f6ef --- /dev/null +++ b/tests/ui/arbitrary-ptr-doc/doc_examples.rs @@ -0,0 +1,90 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// kani-flags: -Z mem-predicates + +//! These are copies of the examples we added to our documentation. +//! We currently cannot run our examples using Kani. +//! We may be able to leverage `--runtool` from rustdoc once its stabilized. See +//! . +#![allow(unused)] + +extern crate kani; + +use kani::*; +#[kani::proof] +fn basic_inbounds() { + let mut generator = PointerGenerator::<10>::new(); + let arbitrary = generator.any_alloc_status::(); + kani::assume(arbitrary.status == AllocationStatus::InBounds); + // Pointer may be unaligned, but it should be in-bounds, so it is safe to write to + unsafe { arbitrary.ptr.write_unaligned(kani::any()) } +} + +#[kani::proof] +fn same_capacity() { + // These generators have the same capacity of 6 bytes. + let generator1 = PointerGenerator::<6>::new(); + let generator2 = pointer_generator::(); +} + +#[kani::proof] +fn generator_large_enough() { + let mut generator = PointerGenerator::<6>::new(); + let ptr1: *mut u8 = generator.any_in_bounds().ptr; + let ptr2: *mut u8 = generator.any_in_bounds().ptr; + let ptr3: *mut u32 = generator.any_in_bounds().ptr; + // This cover is satisfied. + cover!( + (ptr1 as usize) >= (ptr2 as usize) + size_of::() + && (ptr2 as usize) >= (ptr3 as usize) + size_of::() + ); + // As well as having overlapping pointers. + cover!((ptr1 as usize) == (ptr3 as usize)); +} + +#[kani::proof] +fn pointer_may_be_same() { + let mut generator = pointer_generator::(); + let ptr1 = generator.any_in_bounds::().ptr; + let ptr2 = generator.any_in_bounds::().ptr; + // This cover is satisfied. + cover!(ptr1 == ptr2) +} +unsafe fn my_target(_ptr1: *const T, _ptr2: *const T) {} + +#[kani::proof] +fn usage_example() { + let mut generator1 = pointer_generator::(); + let mut generator2 = pointer_generator::(); + let ptr1: *const char = generator1.any_in_bounds().ptr; + let ptr2: *const char = if kani::any() { + // Pointers will have same provenance and may overlap. + generator1.any_in_bounds().ptr + } else { + // Pointers will have different provenance and will not overlap. + generator2.any_in_bounds().ptr + }; + // Invoke the function under verification + unsafe { my_target(ptr1, ptr2) }; +} +#[kani::proof] +fn diff_from_usize() { + // This pointer represents any address, and it may point to anything in memory, + // allocated or not. + let ptr1 = kani::any::() as *const u8; + + // This pointer address will either point to unallocated memory, to a dead object + // or to allocated memory within the generator address space. + let mut generator = PointerGenerator::<5>::new(); + let ptr2: *const u8 = generator.any_alloc_status().ptr; +} +#[kani::proof] +fn check_distance() { + let mut generator = PointerGenerator::<6>::new(); + let ptr1: *mut u8 = generator.any_in_bounds().ptr; + let ptr2: *mut u8 = generator.any_in_bounds().ptr; + // SAFETY: Both pointers have the same provenance. + let distance = unsafe { ptr1.offset_from(ptr2) }; + assert!(distance >= -5 && distance <= 5) +} From 74228dd07cb53210dcfb5f18bcb85127bab39b09 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:49:14 -0400 Subject: [PATCH 112/159] Automatic cargo update to 2024-10-07 (#3572) Dependency upgrade resulting from `cargo update`. Co-authored-by: tautschnig <1144736+tautschnig@users.noreply.github.com> --- Cargo.lock | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index acc0cc485bdb..0cd3c1af05f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,9 +147,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.18" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -411,6 +411,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.5.0" @@ -428,12 +434,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -720,12 +726,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "os_info" @@ -788,12 +791,6 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -901,9 +898,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] @@ -1117,7 +1114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "serde", ] From 1c38609ec35f591898b94ec39427d1b5b0cad824 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:29:25 -0700 Subject: [PATCH 113/159] Bump tests/perf/s2n-quic from `2a735a9` to `17171ec` (#3573) Bumps [tests/perf/s2n-quic](https://github.com/aws/s2n-quic) from `2a735a9` to `17171ec`.
Commits
  • 17171ec build(deps): update hashbrown requirement from 0.14 to 0.15 (#2341)
  • c9a86c3 feat(s2n-quic-dc): shrink path secret & fix fixed-map allocation (#2340)
  • cb5087c feat(s2n-quic-dc): Further shrink path secret entry (#2339)
  • 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 2a735a983a54..17171ece180d 160000 --- a/tests/perf/s2n-quic +++ b/tests/perf/s2n-quic @@ -1 +1 @@ -Subproject commit 2a735a983a5426b13c926d19090d32ea0a99ea36 +Subproject commit 17171ece180d6512f21dd98cdc8637e33646d994 From 21f4af94201eaf412ed7802892721ebcbd539558 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Mon, 7 Oct 2024 17:46:25 -0400 Subject: [PATCH 114/159] Fix issue when linking rlib + another library type (#3576) Invoking the native linker was overriding the `json` we create in Kani's compiler. Thus, invoke the native linker first, then create the `json` files. Resolves #3569 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../compiler_interface.rs | 21 +++++++++---------- .../cdylib-rlib/Cargo.toml | 13 ++++++++++++ .../supported-lib-types/cdylib-rlib/expected | 2 ++ 3 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 tests/cargo-ui/supported-lib-types/cdylib-rlib/Cargo.toml create mode 100644 tests/cargo-ui/supported-lib-types/cdylib-rlib/expected diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 85117b6a2974..da211c58946c 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -403,13 +403,7 @@ impl CodegenBackend for GotocCodegenBackend { /// We need to emit `rlib` files normally if requested. Cargo expects these in some /// circumstances and sends them to subsequent builds with `-L`. /// - /// We CAN NOT invoke the native linker, because that will fail. We don't have real objects. - /// What determines whether the native linker is invoked or not is the set of `crate_types`. - /// Types such as `bin`, `cdylib`, `dylib` will trigger the native linker. - /// - /// Thus, we manually build the rlib file including only the `rmeta` file. - /// - /// For cases where no metadata file was requested, we stub the file requested by writing the + /// For other crate types, we stub the file requested by writing the /// path of the `kani-metadata.json` file so `kani-driver` can safely find the latest metadata. /// See for more details. fn link( @@ -419,6 +413,14 @@ impl CodegenBackend for GotocCodegenBackend { outputs: &OutputFilenames, ) -> Result<(), ErrorGuaranteed> { let requested_crate_types = &codegen_results.crate_info.crate_types; + // Create the rlib if one was requested. + if requested_crate_types.iter().any(|crate_type| *crate_type == CrateType::Rlib) { + link_binary(sess, &ArArchiveBuilderBuilder, &codegen_results, outputs)?; + } + + // But override all the other outputs. + // Note: Do this after `link_binary` call, since it may write to the object files + // and override the json we are creating. for crate_type in requested_crate_types { let out_fname = out_filename( sess, @@ -428,10 +430,7 @@ impl CodegenBackend for GotocCodegenBackend { ); let out_path = out_fname.as_path(); debug!(?crate_type, ?out_path, "link"); - if *crate_type == CrateType::Rlib { - // Emit the `rlib` that contains just one file: `.rmeta` - link_binary(sess, &ArArchiveBuilderBuilder, &codegen_results, outputs)? - } else { + if *crate_type != CrateType::Rlib { // Write the location of the kani metadata file in the requested compiler output file. let base_filepath = outputs.path(OutputType::Object); let base_filename = base_filepath.as_path(); diff --git a/tests/cargo-ui/supported-lib-types/cdylib-rlib/Cargo.toml b/tests/cargo-ui/supported-lib-types/cdylib-rlib/Cargo.toml new file mode 100644 index 000000000000..fcf91e061e57 --- /dev/null +++ b/tests/cargo-ui/supported-lib-types/cdylib-rlib/Cargo.toml @@ -0,0 +1,13 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +[package] +name = "supported-lib" +version = "0.1.0" +edition = "2021" +description = "Test that Kani correctly handle supported crate types" + +[lib] +name = "lib" +crate-type = ["cdylib", "rlib"] +path = "../src/lib.rs" + diff --git a/tests/cargo-ui/supported-lib-types/cdylib-rlib/expected b/tests/cargo-ui/supported-lib-types/cdylib-rlib/expected new file mode 100644 index 000000000000..426470e8702c --- /dev/null +++ b/tests/cargo-ui/supported-lib-types/cdylib-rlib/expected @@ -0,0 +1,2 @@ +Checking harness check_ok... +VERIFICATION:- SUCCESSFUL From 91044db940d11193a75bef8414f65f1fd5832503 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 8 Oct 2024 07:16:12 -0700 Subject: [PATCH 115/159] Running `verify-std` no longer changes Cargo files (#3577) In order to create a dummy crate we were using `cargo init` command. However, this command will interact with any existing workspace. Instead, explicitly create a dummy `Cargo.toml` and `src/lib.rs`. Resolves #3574 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- kani-driver/src/call_cargo.rs | 35 +++++++++++++++-- .../verify_std_cmd/verify_std.expected | 4 ++ .../verify_std_cmd/verify_std.sh | 39 ++++++++++++++++--- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index 191b104a8c1c..bc0e9d8361d7 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -46,10 +46,39 @@ pub struct CargoOutputs { impl KaniSession { /// Create a new cargo library in the given path. + /// + /// Since we cannot create a new workspace with `cargo init --lib`, we create the dummy + /// crate manually. =( See . + /// + /// Without setting up a new workspace, cargo init will modify the workspace where this is + /// running. See for details. pub fn cargo_init_lib(&self, path: &Path) -> Result<()> { - let mut cmd = setup_cargo_command()?; - cmd.args(["init", "--lib", path.to_string_lossy().as_ref()]); - self.run_terminal(cmd) + let toml_path = path.join("Cargo.toml"); + if toml_path.exists() { + bail!("Cargo.toml already exists in {}", path.display()); + } + + // Create folder for library + fs::create_dir_all(path.join("src"))?; + + // Create dummy crate and write dummy body + let lib_path = path.join("src/lib.rs"); + fs::write(&lib_path, "pub fn dummy() {}")?; + + // Create Cargo.toml + fs::write( + &toml_path, + r#"[package] +name = "dummy" +version = "0.1.0" + +[lib] +crate-type = ["lib"] + +[workspace] +"#, + )?; + Ok(()) } pub fn cargo_build_std(&self, std_path: &Path, krate_path: &Path) -> Result> { diff --git a/tests/script-based-pre/verify_std_cmd/verify_std.expected b/tests/script-based-pre/verify_std_cmd/verify_std.expected index 157adb94ba16..3c1f474af0e7 100644 --- a/tests/script-based-pre/verify_std_cmd/verify_std.expected +++ b/tests/script-based-pre/verify_std_cmd/verify_std.expected @@ -1,3 +1,7 @@ +[TEST] Only codegen inside library folder +No kani crate inside Cargo.toml as expected + + [TEST] Run kani verify-std Checking harness verify::dummy_proof... 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 022e7b79de4a..e7276867a2a5 100755 --- a/tests/script-based-pre/verify_std_cmd/verify_std.sh +++ b/tests/script-based-pre/verify_std_cmd/verify_std.sh @@ -50,17 +50,44 @@ cp ${TMP_DIR}/library/std/src/lib.rs ${TMP_DIR}/std_lib.rs echo '#![cfg_attr(kani, feature(kani))]' > ${TMP_DIR}/library/std/src/lib.rs cat ${TMP_DIR}/std_lib.rs >> ${TMP_DIR}/library/std/src/lib.rs +# Test that the command works inside the library folder and does not change +# the existing workspace +# See https://github.com/model-checking/kani/issues/3574 +echo "[TEST] Only codegen inside library folder" +pushd "${TMP_DIR}/library" >& /dev/null +RUSTFLAGS="--cfg=uninit_checks" kani verify-std \ + -Z unstable-options \ + . \ + -Z function-contracts \ + -Z stubbing \ + -Z mem-predicates \ + --only-codegen +popd +# Grep should not find anything and exit status is 1. +grep -c kani ${TMP_DIR}/library/Cargo.toml \ + && echo "Unexpected kani crate inside Cargo.toml" \ + || echo "No kani crate inside Cargo.toml as expected" + 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 +# Test that uninit-checks basic setup works on a no-core library echo "[TEST] Run kani verify-std -Z uninit-checks" -export RUSTFLAGS="--cfg=uninit_checks" -kani verify-std -Z unstable-options "${TMP_DIR}/library" \ - --target-dir "${TMP_DIR}/target" -Z function-contracts -Z stubbing \ - -Z mem-predicates -Z uninit-checks +RUSTFLAGS="--cfg=uninit_checks" kani verify-std \ + -Z unstable-options \ + "${TMP_DIR}/library" \ + --target-dir "${TMP_DIR}/target" \ + -Z function-contracts \ + -Z stubbing \ + -Z mem-predicates \ + -Z uninit-checks # Cleanup rm -r ${TMP_DIR} From e7ab0903fb6c8da27b96d7148226d6808c1c2a86 Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:54:42 -0700 Subject: [PATCH 116/159] Add an LLBC backend (#3514) This PR adds a new codegen backend that generates low-level borrow calculus (LLBC), which is the format defined by [Charon](https://github.com/AeneasVerif/charon) and [Aeneas](https://github.com/AeneasVerif/aeneas). The backend can be invoked using `-Zaeneas`, and will generate a `.llbc` file. This file can then be passed to Aeneas to generate Lean for example. Currently, Aeneas needs to be manually run on the generated LLBC. The PR translates stable MIR to unstructured LLBC (ULLBC) and then uses the Charon library to apply transformations that reconstruct the control flow to regain some of the high-level structure (loops, if statements, etc.). In this PR, very few MIR constructs are handled, but the PR is already pretty big. More support to come in subsequent PRs. Call-outs: - Charon is currently not released on crates.io, so the library is added as a dependency through GitHub with a particular commit. - The Kani driver is currenty heavily tailored towards CBMC, so currently it's expecting a goto file, and thus errors out when the Aeneas backend is invoked. 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/deny.yml | 2 + .github/workflows/format-check.yml | 2 +- .github/workflows/kani.yml | 2 + .gitignore | 3 + .gitmodules | 3 + Cargo.lock | 1060 ++++++++++++++++- charon | 1 + deny.toml | 10 +- kani-compiler/Cargo.toml | 4 +- kani-compiler/src/args.rs | 26 +- .../codegen_aeneas_llbc/compiler_interface.rs | 411 +++++++ .../codegen_aeneas_llbc/mir_to_ullbc/mod.rs | 802 +++++++++++++ kani-compiler/src/codegen_aeneas_llbc/mod.rs | 10 + kani-compiler/src/kani_compiler.rs | 35 +- kani-compiler/src/main.rs | 2 + kani-driver/src/args/mod.rs | 29 + kani-driver/src/call_cargo.rs | 3 + kani-driver/src/call_single_file.rs | 19 +- kani_metadata/src/unstable.rs | 2 + rustfmt.toml | 1 + scripts/codegen-firecracker.sh | 2 +- scripts/std-lib-regression.sh | 2 +- tests/expected/llbc/basic0/expected | 8 + tests/expected/llbc/basic0/test.rs | 15 + tests/expected/llbc/basic1/expected | 15 + tests/expected/llbc/basic1/test.rs | 15 + 26 files changed, 2416 insertions(+), 68 deletions(-) create mode 160000 charon create mode 100644 kani-compiler/src/codegen_aeneas_llbc/compiler_interface.rs create mode 100644 kani-compiler/src/codegen_aeneas_llbc/mir_to_ullbc/mod.rs create mode 100644 kani-compiler/src/codegen_aeneas_llbc/mod.rs create mode 100644 tests/expected/llbc/basic0/expected create mode 100644 tests/expected/llbc/basic0/test.rs create mode 100644 tests/expected/llbc/basic1/expected create mode 100644 tests/expected/llbc/basic1/test.rs diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index 7ce00cabd2f9..a5db349f8abc 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -18,6 +18,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - uses: EmbarkStudios/cargo-deny-action@v2 with: arguments: --all-features --workspace diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index cc13306a4eaf..81253f1a9790 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -47,7 +47,7 @@ jobs: - name: 'Run Clippy' run: | - cargo clippy --all -- -D warnings + cargo clippy --workspace --exclude charon --exclude macros --no-deps -- -D warnings - name: 'Print Clippy Statistics' run: | diff --git a/.github/workflows/kani.yml b/.github/workflows/kani.yml index a565c9cd4cbe..d8cb0fc2bb2d 100644 --- a/.github/workflows/kani.yml +++ b/.github/workflows/kani.yml @@ -98,6 +98,8 @@ jobs: steps: - name: Checkout Kani uses: actions/checkout@v4 + with: + submodules: recursive - name: Install book dependencies run: ./scripts/setup/ubuntu/install_doc_deps.sh diff --git a/.gitignore b/.gitignore index a2defc0df119..db10863e2b0f 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,9 @@ package-lock.json ## Rustdoc GUI tests tests/rustdoc-gui/src/**.lock +## Charon/Aeneas LLBC files +*.llbc + # Before adding new lines, see the comment at the top. /.ninja_deps /.ninja_log diff --git a/.gitmodules b/.gitmodules index b02c263a898e..c7e25f120c95 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ path = tests/perf/s2n-quic url = https://github.com/aws/s2n-quic branch = main +[submodule "charon"] + path = charon + url = https://github.com/AeneasVerif/charon diff --git a/Cargo.lock b/Cargo.lock index 0cd3c1af05f4..41fb02073f6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -24,6 +39,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anstream" version = "0.6.15" @@ -79,18 +103,122 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_cmd" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "assert_tokens_eq" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b21f4c5ba5c8b55031306325f196df925939bcc2bd7188ce68fabd93fb4f149" +dependencies = [ + "ansi_term", + "ctor", + "difference", + "output_vt100", + "snafu", +] + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "bincode_derive", + "serde", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "brownstone" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5839ee4f953e811bfdcf223f509cb2c6a3e1447959b0bff459405575bc17f22" +dependencies = [ + "arrayvec 0.7.6", +] + +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata 0.4.8", + "serde", +] + [[package]] name = "build-kani" version = "0.55.0" @@ -139,12 +267,66 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cc" +version = "1.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "charon" +version = "0.1.36" +dependencies = [ + "anyhow", + "assert_cmd", + "clap", + "colored", + "convert_case 0.6.0", + "derivative", + "derive-visitor", + "env_logger", + "hashlink", + "hax-frontend-exporter", + "ignore", + "im", + "index_vec", + "indoc", + "itertools 0.13.0", + "lazy_static", + "libtest-mimic", + "log", + "macros", + "nom", + "nom-supreme", + "petgraph", + "pretty", + "regex", + "rustc_version", + "serde", + "serde-map-to-array", + "serde_json", + "serde_stacker", + "snapbox", + "stacker", + "take_mut", + "tempfile", + "toml", + "tracing", + "tracing-subscriber", + "tracing-tree 0.3.1", + "walkdir", + "which", +] + [[package]] name = "clap" version = "4.5.19" @@ -174,9 +356,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", ] [[package]] @@ -191,6 +373,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "comfy-table" version = "7.1.1" @@ -233,6 +425,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cprover_bindings" version = "0.55.0" @@ -316,6 +523,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote 1.0.37", + "syn 1.0.109", +] + [[package]] name = "deranged" version = "0.3.11" @@ -325,6 +542,63 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "derive-visitor" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d47165df83b9707cbada3216607a5d66125b6a66906de0bc1216c0669767ca9e" +dependencies = [ + "derive-visitor-macros", +] + +[[package]] +name = "derive-visitor-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427b39a85fecafea16b1a5f3f50437151022e35eb4fe038107f08adbf7f8def6" +dependencies = [ + "convert_case 0.4.0", + "itertools 0.10.5", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "either" version = "1.13.0" @@ -337,6 +611,29 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -353,6 +650,41 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + +[[package]] +name = "ext-trait" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" +dependencies = [ + "ext-trait-proc_macros", +] + +[[package]] +name = "ext-trait-proc_macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "extension-traits" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" +dependencies = [ + "ext-trait", +] + [[package]] name = "fastrand" version = "2.1.1" @@ -385,12 +717,31 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", +] + [[package]] name = "graph-cycles" version = "0.1.0" @@ -417,12 +768,69 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "hax-adt-into" +version = "0.1.0-alpha.1" +source = "git+https://github.com/hacspec/hax?branch=main#496e381186de242f46ee2fd2b4b8971c70aa07a4" +dependencies = [ + "itertools 0.11.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "hax-frontend-exporter" +version = "0.1.0-alpha.1" +source = "git+https://github.com/hacspec/hax?branch=main#496e381186de242f46ee2fd2b4b8971c70aa07a4" +dependencies = [ + "bincode", + "extension-traits", + "hax-adt-into", + "hax-frontend-exporter-options", + "itertools 0.11.0", + "lazy_static", + "paste", + "schemars", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "hax-frontend-exporter-options" +version = "0.1.0-alpha.1" +source = "git+https://github.com/hacspec/hax?branch=main#496e381186de242f46ee2fd2b4b8971c70aa07a4" +dependencies = [ + "bincode", + "hax-adt-into", + "schemars", + "serde", + "serde_json", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "home" version = "0.5.9" @@ -432,6 +840,57 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.8", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indent_write" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" + +[[package]] +name = "index_vec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44faf5bb8861a9c72e20d3fb0fdbd59233e43056e2b80475ab0aacdc2e781355" +dependencies = [ + "serde", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -442,12 +901,36 @@ dependencies = [ "hashbrown 0.15.0", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -463,6 +946,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "joinery" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" + [[package]] name = "kani" version = "0.55.0" @@ -475,24 +964,25 @@ dependencies = [ name = "kani-compiler" version = "0.55.0" dependencies = [ + "charon", "clap", "cprover_bindings", "home", - "itertools", + "itertools 0.13.0", "kani_metadata", "lazy_static", "num", - "quote", + "quote 1.0.37", "regex", "serde", "serde_json", "shell-words", "strum", "strum_macros", - "syn", + "syn 2.0.79", "tracing", "tracing-subscriber", - "tracing-tree", + "tracing-tree 0.4.0", ] [[package]] @@ -545,9 +1035,9 @@ name = "kani_macros" version = "0.55.0" dependencies = [ "proc-macro-error2", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", ] [[package]] @@ -573,6 +1063,18 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +[[package]] +name = "libtest-mimic" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58" +dependencies = [ + "clap", + "escape8259", + "termcolor", + "threadpool", +] + [[package]] name = "linear-map" version = "1.2.0" @@ -605,6 +1107,16 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "macros" +version = "0.1.0" +dependencies = [ + "assert_tokens_eq", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", +] + [[package]] name = "matchers" version = "0.1.0" @@ -618,13 +1130,57 @@ dependencies = [ name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memuse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom-supreme" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd3ae6c901f1959588759ff51c95d24b491ecb9ff91aa9c2ef4acc5b1dcab27" +dependencies = [ + "brownstone", + "indent_write", + "joinery", + "memchr", + "nom", +] [[package]] -name = "memuse" -version = "0.2.1" +name = "normalize-line-endings" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "nu-ansi-term" @@ -724,6 +1280,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -740,6 +1315,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + [[package]] name = "overload" version = "0.1.1" @@ -766,9 +1350,15 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.1" @@ -806,14 +1396,52 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "predicates" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579" +dependencies = [ + "arrayvec 0.5.2", + "typed-arena", + "unicode-width", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.86", + "quote 1.0.37", ] [[package]] @@ -823,9 +1451,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid", ] [[package]] @@ -837,13 +1474,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.86", ] [[package]] @@ -876,6 +1531,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.10.0" @@ -955,6 +1619,15 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.37" @@ -1001,6 +1674,30 @@ dependencies = [ "strum_macros", ] +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "serde_derive_internals", + "syn 2.0.79", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1025,15 +1722,35 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-map-to-array" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c14b52efc56c711e0dbae3f26e0cc233f5dac336c1bf0b07e1b7dc2dca3b2cc7" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", ] [[package]] @@ -1057,6 +1774,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_stacker" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "babfccff5773ff80657f0ecf553c7c516bdc2eb16389c0918b36b73e7015276e" +dependencies = [ + "serde", + "stacker", +] + [[package]] name = "serde_test" version = "1.0.177" @@ -1094,12 +1821,91 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "snafu" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b028158eb06caa8345bee10cccfb25fa632beccf0ef5308832b4fd4b78a7db48" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf50aaef500c248a590e2696e8bf8c7620ca2235b9bb90a70363d82dd1abec6a" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", +] + +[[package]] +name = "snapbox" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba434818a8a9b1b106404288d6bd75a94348aae8fc9a518b211b609a36a54bc" +dependencies = [ + "anstream", + "anstyle", + "normalize-line-endings", + "similar", + "snapbox-macros", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" +dependencies = [ + "anstream", +] + +[[package]] +name = "stacker" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + [[package]] name = "std" version = "0.55.0" @@ -1137,10 +1943,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", - "proc-macro2", - "quote", + "proc-macro2 1.0.86", + "quote 1.0.37", "rustversion", - "syn", + "syn 2.0.79", +] + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "unicode-ident", ] [[package]] @@ -1149,11 +1977,17 @@ version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.86", + "quote 1.0.37", "unicode-ident", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + [[package]] name = "tempfile" version = "3.13.0" @@ -1167,6 +2001,21 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" version = "1.0.64" @@ -1182,9 +2031,9 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", ] [[package]] @@ -1197,6 +2046,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.3.36" @@ -1279,9 +2137,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", ] [[package]] @@ -1337,6 +2195,18 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-tree" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b56c62d2c80033cb36fae448730a2f2ef99410fe3ecbffc916681a32f6807dbe" +dependencies = [ + "nu-ansi-term 0.50.1", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + [[package]] name = "tracing-tree" version = "0.4.0" @@ -1349,18 +2219,42 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -1385,6 +2279,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1453,13 +2353,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1468,7 +2377,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1477,28 +2401,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1511,24 +2453,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1566,7 +2532,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.79", ] diff --git a/charon b/charon new file mode 160000 index 000000000000..cdc1dcde447a --- /dev/null +++ b/charon @@ -0,0 +1 @@ +Subproject commit cdc1dcde447a50cbc20336c79b21b42ac977b7fd diff --git a/deny.toml b/deny.toml index 733f91e12f36..e44b234f4ca8 100644 --- a/deny.toml +++ b/deny.toml @@ -11,7 +11,10 @@ yanked = "deny" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ - #"RUSTSEC-0000-0000", + # This is for a crate that is used by Charon + "RUSTSEC-2021-0139", + # This is for a crate that is used by Charon + "RUSTSEC-2020-0095", ] # This section is considered when running `cargo deny check licenses` @@ -21,6 +24,7 @@ ignore = [ allow = [ "MIT", "Apache-2.0", + "MPL-2.0", ] confidence-threshold = 0.8 @@ -46,4 +50,6 @@ wildcards = "allow" unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] -allow-git = [] +allow-git = [ + "https://github.com/hacspec/hax", +] diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 9ca8d10f5275..0360763f9068 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -10,6 +10,7 @@ publish = false [dependencies] cbmc = { path = "../cprover_bindings", package = "cprover_bindings", optional = true } +charon = { path = "../charon/charon", optional = true, default-features = false } clap = { version = "4.4.11", features = ["derive", "cargo"] } home = "0.5" itertools = "0.13" @@ -30,7 +31,8 @@ tracing-tree = "0.4.0" # Future proofing: enable backend dependencies using feature. [features] -default = ['cprover'] +default = ['aeneas', 'cprover'] +aeneas = ['charon'] cprover = ['cbmc', 'num', 'serde'] write_json_symtab = [] diff --git a/kani-compiler/src/args.rs b/kani-compiler/src/args.rs index 3fa74b0e5aba..b5b799321cf3 100644 --- a/kani-compiler/src/args.rs +++ b/kani-compiler/src/args.rs @@ -1,9 +1,23 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use strum_macros::{AsRefStr, EnumString, VariantNames}; +use strum_macros::{AsRefStr, Display, EnumString, VariantNames}; use tracing_subscriber::filter::Directive; +#[derive(Debug, Default, Display, Clone, Copy, AsRefStr, EnumString, VariantNames, PartialEq, Eq)] +#[strum(serialize_all = "snake_case")] +pub enum BackendOption { + /// Aeneas (LLBC) backend + #[cfg(feature = "aeneas")] + Aeneas, + + /// CProver (Goto) backend + #[cfg(feature = "cprover")] + #[strum(serialize = "cprover")] + #[default] + CProver, +} + #[derive(Debug, Default, Clone, Copy, AsRefStr, EnumString, VariantNames, PartialEq, Eq)] #[strum(serialize_all = "snake_case")] pub enum ReachabilityType { @@ -69,11 +83,13 @@ pub struct Arguments { /// Pass the kani version to the compiler to ensure cache coherence. check_version: Option, #[clap(long)] - /// A legacy flag that is now ignored. - goto_c: bool, - /// Enable specific checks. - #[clap(long)] pub ub_check: Vec, + /// Option name used to select which backend to use. + #[clap(long = "backend", default_value_t = BackendOption::CProver)] + pub backend: BackendOption, + /// Print the final LLBC file to stdout. This requires `-Zaeneas`. + #[clap(long)] + pub print_llbc: bool, } #[derive(Debug, Clone, Copy, AsRefStr, EnumString, VariantNames, PartialEq, Eq)] diff --git a/kani-compiler/src/codegen_aeneas_llbc/compiler_interface.rs b/kani-compiler/src/codegen_aeneas_llbc/compiler_interface.rs new file mode 100644 index 000000000000..f9922f237bd5 --- /dev/null +++ b/kani-compiler/src/codegen_aeneas_llbc/compiler_interface.rs @@ -0,0 +1,411 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This file contains the code necessary to interface with the compiler backend + +use crate::args::ReachabilityType; +use crate::codegen_aeneas_llbc::mir_to_ullbc::Context; +use crate::kani_middle::attributes::KaniAttributes; +use crate::kani_middle::check_reachable_items; +use crate::kani_middle::codegen_units::{CodegenUnit, CodegenUnits}; +use crate::kani_middle::provide; +use crate::kani_middle::reachability::{collect_reachable_items, filter_crate_items}; +use crate::kani_middle::transform::{BodyTransformation, GlobalPasses}; +use crate::kani_queries::QueryDb; +use charon_lib::ast::TranslatedCrate; +use charon_lib::errors::ErrorCtx; +use charon_lib::transform::TransformCtx; +use charon_lib::transform::ctx::TransformOptions; +use kani_metadata::ArtifactType; +use kani_metadata::{AssignsContract, CompilerArtifactStub}; +use rustc_codegen_ssa::back::archive::{ + ArArchiveBuilder, ArchiveBuilder, ArchiveBuilderBuilder, DEFAULT_OBJECT_READER, +}; +use rustc_codegen_ssa::back::link::link_binary; +use rustc_codegen_ssa::traits::CodegenBackend; +use rustc_codegen_ssa::{CodegenResults, CrateInfo}; +use rustc_data_structures::fx::FxIndexMap; +use rustc_errors::{DEFAULT_LOCALE_RESOURCE, ErrorGuaranteed}; +use rustc_hir::def_id::{DefId as InternalDefId, LOCAL_CRATE}; +use rustc_metadata::EncodedMetadata; +use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; +use rustc_middle::ty::TyCtxt; +use rustc_middle::util::Providers; +use rustc_session::Session; +use rustc_session::config::{CrateType, OutputFilenames, OutputType}; +use rustc_session::output::out_filename; +use rustc_smir::rustc_internal; +use stable_mir::mir::mono::{Instance, MonoItem}; +use stable_mir::{CrateDef, DefId}; +use std::any::Any; +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use std::time::Instant; +use tracing::{debug, info, trace}; + +#[derive(Clone)] +pub struct LlbcCodegenBackend { + /// The query is shared with `KaniCompiler` and it is initialized as part of `rustc` + /// initialization, which may happen after this object is created. + /// Since we don't have any guarantees on when the compiler creates the Backend object, neither + /// in which thread it will be used, we prefer to explicitly synchronize any query access. + queries: Arc>, +} + +impl LlbcCodegenBackend { + pub fn new(queries: Arc>) -> Self { + LlbcCodegenBackend { queries } + } + + /// Generate code that is reachable from the given starting points. + /// + /// Invariant: iff `check_contract.is_some()` then `return.2.is_some()` + fn codegen_items( + &self, + tcx: TyCtxt, + starting_items: &[MonoItem], + llbc_file: &Path, + _check_contract: Option, + mut transformer: BodyTransformation, + ) -> (Vec, Option) { + let (items, call_graph) = with_timer( + || collect_reachable_items(tcx, &mut transformer, starting_items), + "codegen reachability analysis", + ); + + // Retrieve all instances from the currently codegened items. + let instances = items + .iter() + .filter_map(|item| match 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, + }) + .collect(); + + // Apply all transformation passes, including global passes. + let mut global_passes = GlobalPasses::new(&self.queries.lock().unwrap(), tcx); + global_passes.run_global_passes( + &mut transformer, + tcx, + starting_items, + instances, + call_graph, + ); + + let queries = self.queries.lock().unwrap().clone(); + check_reachable_items(tcx, &queries, &items); + + // Follow rustc naming convention (cx is abbrev for context). + // https://rustc-dev-guide.rust-lang.org/conventions.html#naming-conventions + + // Create a Charon transformation context that will be populated with translation results + let mut ccx = create_charon_transformation_context(tcx); + + // Translate all the items + for item in &items { + match item { + MonoItem::Fn(instance) => { + let mut fcx = + Context::new(tcx, *instance, &mut ccx.translated, &mut ccx.errors); + let _ = fcx.translate(); + } + MonoItem::Static(_def) => todo!(), + MonoItem::GlobalAsm(_) => {} // We have already warned above + } + } + + trace!("# ULLBC after translation from MIR:\n\n{}\n", ccx); + + // # Reorder the graph of dependencies and compute the strictly + // connex components to: + // - compute the order in which to extract the definitions + // - find the recursive definitions + // - group the mutually recursive definitions + let reordered_decls = charon_lib::reorder_decls::compute_reordered_decls(&ccx); + ccx.translated.ordered_decls = Some(reordered_decls); + + // + // ================= + // **Micro-passes**: + // ================= + // At this point, the bulk of the translation is done. From now onwards, + // we simply apply some micro-passes to make the code cleaner, before + // serializing the result. + + // Run the micro-passes that clean up bodies. + for pass in charon_lib::transform::ULLBC_PASSES.iter() { + pass.transform_ctx(&mut ccx) + } + + // # Go from ULLBC to LLBC (Low-Level Borrow Calculus) by reconstructing + // the control flow. + charon_lib::ullbc_to_llbc::translate_functions(&mut ccx); + + trace!("# LLBC resulting from control-flow reconstruction:\n\n{}\n", ccx); + + // Run the micro-passes that clean up bodies. + for pass in charon_lib::transform::LLBC_PASSES.iter() { + pass.transform_ctx(&mut ccx) + } + + // Print the LLBC if requested. This is useful for expected tests. + if queries.args().print_llbc { + println!("# Final LLBC before serialization:\n\n{}\n", ccx); + } else { + debug!("# Final LLBC before serialization:\n\n{}\n", ccx); + } + + // Display an error report about the external dependencies, if necessary + ccx.errors.report_external_deps_errors(); + + let crate_data: charon_lib::export::CrateData = charon_lib::export::CrateData::new(&ccx); + + // No output should be generated if user selected no_codegen. + if !tcx.sess.opts.unstable_opts.no_codegen && tcx.sess.opts.output_types.should_codegen() { + // # Final step: generate the files. + // `crate_data` is set by our callbacks when there is no fatal error. + let mut pb = llbc_file.to_path_buf(); + pb.set_extension("llbc"); + println!("Writing LLBC file to {}", pb.display()); + if let Err(()) = crate_data.serialize_to_file(&pb) { + tcx.sess.dcx().err("Failed to write LLBC file"); + } + } + + (items, None) + } +} + +impl CodegenBackend for LlbcCodegenBackend { + fn provide(&self, providers: &mut Providers) { + provide::provide(providers, &self.queries.lock().unwrap()); + } + + fn print_version(&self) { + println!("Kani-llbc version: {}", env!("CARGO_PKG_VERSION")); + } + + fn locale_resource(&self) -> &'static str { + // We don't currently support multiple languages. + DEFAULT_LOCALE_RESOURCE + } + + fn codegen_crate( + &self, + tcx: TyCtxt, + rustc_metadata: EncodedMetadata, + _need_metadata_module: bool, + ) -> Box { + let ret_val = rustc_internal::run(tcx, || { + // Queries shouldn't change today once codegen starts. + let queries = self.queries.lock().unwrap().clone(); + + // Codegen all items that need to be processed according to the selected reachability mode: + // + // - Harnesses: Generate one model per local harnesses (marked with `kani::proof` attribute). + // - Tests: Generate one model per test harnesses. + // - PubFns: Generate code for all reachable logic starting from the local public functions. + // - None: Don't generate code. This is used to compile dependencies. + let base_filepath = tcx.output_filenames(()).path(OutputType::Object); + let base_filename = base_filepath.as_path(); + let reachability = queries.args().reachability_analysis; + match reachability { + ReachabilityType::Harnesses => { + let mut units = CodegenUnits::new(&queries, tcx); + let modifies_instances = vec![]; + // Cross-crate collecting of all items that are reachable from the crate harnesses. + 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 model_path = units.harness_model_path(*harness).unwrap(); + let contract_metadata = + contract_metadata_for_harness(tcx, harness.def.def_id()).unwrap(); + let (_items, contract_info) = self.codegen_items( + tcx, + &[MonoItem::Fn(*harness)], + model_path, + contract_metadata, + transformer, + ); + transformer = BodyTransformation::new(&queries, tcx, &unit); + if let Some(_assigns_contract) = contract_info { + //self.queries.lock().unwrap().register_assigns_contract( + // canonical_mangled_name(harness).intern(), + // assigns_contract, + //); + } + } + } + units.store_modifies(&modifies_instances); + units.write_metadata(&queries, tcx); + } + ReachabilityType::Tests => todo!(), + ReachabilityType::None => {} + ReachabilityType::PubFns => { + let unit = CodegenUnit::default(); + let transformer = BodyTransformation::new(&queries, tcx, &unit); + let main_instance = + stable_mir::entry_fn().map(|main_fn| Instance::try_from(main_fn).unwrap()); + let local_reachable = filter_crate_items(tcx, |_, instance| { + let def_id = rustc_internal::internal(tcx, instance.def.def_id()); + Some(instance) == main_instance || tcx.is_reachable_non_generic(def_id) + }) + .into_iter() + .map(MonoItem::Fn) + .collect::>(); + let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); + let (_items, contract_info) = self.codegen_items( + tcx, + &local_reachable, + &model_path, + Default::default(), + transformer, + ); + assert!(contract_info.is_none()); + } + } + + if reachability != ReachabilityType::None && reachability != ReachabilityType::Harnesses + { + // In a workspace, cargo seems to be using the same file prefix to build a crate that is + // a package lib and also a dependency of another package. + // To avoid overriding the metadata for its verification, we skip this step when + // reachability is None, even because there is nothing to record. + } + codegen_results(tcx, rustc_metadata) + }); + ret_val.unwrap() + } + + fn join_codegen( + &self, + ongoing_codegen: Box, + _sess: &Session, + _filenames: &OutputFilenames, + ) -> (CodegenResults, FxIndexMap) { + match ongoing_codegen.downcast::<(CodegenResults, FxIndexMap)>() + { + Ok(val) => *val, + Err(val) => panic!("unexpected error: {:?}", (*val).type_id()), + } + } + + /// Emit output files during the link stage if it was requested. + /// + /// We need to emit `rlib` files normally if requested. Cargo expects these in some + /// circumstances and sends them to subsequent builds with `-L`. + /// + /// We CAN NOT invoke the native linker, because that will fail. We don't have real objects. + /// What determines whether the native linker is invoked or not is the set of `crate_types`. + /// Types such as `bin`, `cdylib`, `dylib` will trigger the native linker. + /// + /// Thus, we manually build the rlib file including only the `rmeta` file. + /// + /// For cases where no metadata file was requested, we stub the file requested by writing the + /// path of the `kani-metadata.json` file so `kani-driver` can safely find the latest metadata. + /// See for more details. + fn link( + &self, + sess: &Session, + codegen_results: CodegenResults, + outputs: &OutputFilenames, + ) -> Result<(), ErrorGuaranteed> { + let requested_crate_types = &codegen_results.crate_info.crate_types; + for crate_type in requested_crate_types { + let out_fname = out_filename( + sess, + *crate_type, + outputs, + codegen_results.crate_info.local_crate_name, + ); + let out_path = out_fname.as_path(); + debug!(?crate_type, ?out_path, "link"); + if *crate_type == CrateType::Rlib { + // Emit the `rlib` that contains just one file: `.rmeta` + link_binary(sess, &ArArchiveBuilderBuilder, &codegen_results, outputs)? + } else { + // Write the location of the kani metadata file in the requested compiler output file. + let base_filepath = outputs.path(OutputType::Object); + let base_filename = base_filepath.as_path(); + let content_stub = CompilerArtifactStub { + metadata_path: base_filename.with_extension(ArtifactType::Metadata), + }; + let out_file = File::create(out_path).unwrap(); + serde_json::to_writer(out_file, &content_stub).unwrap(); + } + } + Ok(()) + } +} + +struct ArArchiveBuilderBuilder; +impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder { + fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box { + Box::new(ArArchiveBuilder::new(sess, &DEFAULT_OBJECT_READER)) + } +} + +fn contract_metadata_for_harness( + tcx: TyCtxt, + def_id: DefId, +) -> Result, ErrorGuaranteed> { + let attrs = KaniAttributes::for_def_id(tcx, def_id); + Ok(attrs.interpret_for_contract_attribute().map(|(_, id, _)| id)) +} + +/// Return a struct that contains information about the codegen results as expected by `rustc`. +fn codegen_results(tcx: TyCtxt, rustc_metadata: EncodedMetadata) -> Box { + let work_products = FxIndexMap::::default(); + Box::new(( + CodegenResults { + modules: vec![], + allocator_module: None, + metadata_module: None, + metadata: rustc_metadata, + crate_info: CrateInfo::new(tcx, tcx.sess.target.arch.clone().to_string()), + }, + work_products, + )) +} + +/// Execute the provided function and measure the clock time it took for its execution. +/// Log the time with the given description. +pub fn with_timer(func: F, description: &str) -> T +where + F: FnOnce() -> T, +{ + let start = Instant::now(); + let ret = func(); + let elapsed = start.elapsed(); + info!("Finished {description} in {}s", elapsed.as_secs_f32()); + ret +} + +fn create_charon_transformation_context(tcx: TyCtxt) -> TransformCtx { + let options = TransformOptions { + no_code_duplication: false, + hide_marker_traits: false, + item_opacities: Vec::new(), + }; + let crate_name = tcx.crate_name(LOCAL_CRATE).as_str().into(); + let translated = TranslatedCrate { crate_name, ..TranslatedCrate::default() }; + let errors = ErrorCtx { + continue_on_failure: true, + errors_as_warnings: false, + dcx: tcx.dcx(), + decls_with_errors: HashSet::new(), + ignored_failed_decls: HashSet::new(), + dep_sources: HashMap::new(), + def_id: None, + error_count: 0, + }; + TransformCtx { options, translated, errors } +} diff --git a/kani-compiler/src/codegen_aeneas_llbc/mir_to_ullbc/mod.rs b/kani-compiler/src/codegen_aeneas_llbc/mir_to_ullbc/mod.rs new file mode 100644 index 000000000000..62462915480e --- /dev/null +++ b/kani-compiler/src/codegen_aeneas_llbc/mir_to_ullbc/mod.rs @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Modifications Copyright Kani Contributors +// See GitHub history for details. + +//! This module contains a context for translating stable MIR into Charon's +//! unstructured low-level borrow calculus (ULLBC) + +use core::panic; +use std::path::PathBuf; + +use charon_lib::ast::CastKind as CharonCastKind; +use charon_lib::ast::Place as CharonPlace; +use charon_lib::ast::ProjectionElem as CharonProjectionElem; +use charon_lib::ast::Rvalue as CharonRvalue; +use charon_lib::ast::Span as CharonSpan; +use charon_lib::ast::meta::{AttrInfo, Loc, RawSpan}; +use charon_lib::ast::types::Ty as CharonTy; +use charon_lib::ast::{AbortKind, Body as CharonBody, Var, VarId, make_locals_generator}; +use charon_lib::ast::{ + AnyTransId, Assert, BodyId, BuiltinTy, Disambiguator, FileName, FunDecl, FunSig, GenericArgs, + GenericParams, IntegerTy, ItemKind, ItemMeta, ItemOpacity, Literal, LiteralTy, Name, Opaque, + PathElem, RawConstantExpr, RefKind, Region as CharonRegion, ScalarValue, TranslatedCrate, + TypeId, +}; +use charon_lib::ast::{ + BinOp as CharonBinOp, Call, FnOperand, FnPtr, FunDeclId, FunId, FunIdOrTraitMethodRef, + VariantId, +}; +use charon_lib::ast::{ + BorrowKind as CharonBorrowKind, ConstantExpr, Operand as CharonOperand, UnOp, +}; +use charon_lib::common::Error; +use charon_lib::errors::ErrorCtx; +use charon_lib::ids::Vector; +use charon_lib::ullbc_ast::{ + BlockData, BlockId, BodyContents, ExprBody, RawStatement, RawTerminator, + Statement as CharonStatement, SwitchTargets as CharonSwitchTargets, + Terminator as CharonTerminator, +}; +use charon_lib::{error_assert, error_or_panic}; +use rustc_errors::MultiSpan; +use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; +use rustc_span::def_id::DefId as InternalDefId; +use stable_mir::abi::PassMode; +use stable_mir::mir::mono::Instance; +use stable_mir::mir::{ + BasicBlock, BinOp, Body, BorrowKind, CastKind, ConstOperand, Mutability, Operand, Place, + ProjectionElem, Rvalue, Statement, StatementKind, SwitchTargets, Terminator, TerminatorKind, +}; +use stable_mir::ty::{ + Allocation, ConstantKind, IndexedVal, IntTy, MirConst, Region, RegionKind, RigidTy, Span, Ty, + TyKind, UintTy, +}; + +use stable_mir::{CrateDef, DefId}; +use tracing::{debug, trace}; + +/// A context for translating a single MIR function to ULLBC. +/// The results of the translation are stored in the `translated` field. +pub struct Context<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + instance: Instance, + translated: &'a mut TranslatedCrate, + errors: &'a mut ErrorCtx<'tcx>, +} + +impl<'a, 'tcx> Context<'a, 'tcx> { + /// Create a new context for translating the function `instance`, populating + /// the results of the translation in `translated` + pub fn new( + tcx: TyCtxt<'tcx>, + instance: Instance, + translated: &'a mut TranslatedCrate, + errors: &'a mut ErrorCtx<'tcx>, + ) -> Self { + Self { tcx, instance, translated, errors } + } + + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn span_err>(&mut self, span: S, msg: &str) { + self.errors.span_err(span, msg); + } + + fn continue_on_failure(&self) -> bool { + self.errors.continue_on_failure() + } + + /// Perform the translation + pub fn translate(&mut self) -> Result<(), ()> { + // Charon's `id_map` is in terms of internal `DefId` + let def_id = rustc_internal::internal(self.tcx(), self.instance.def.def_id()); + + // TODO: might want to populate `errors.dep_sources` to help with + // debugging + + let fid = self.register_fun_decl_id(def_id); + + let item_meta = match self.translate_item_meta_from_rid(self.instance) { + Ok(item_meta) => item_meta, + Err(_) => { + return Err(()); + } + }; + + let signature = self.translate_function_signature(); + let body = match self.translate_function_body() { + Ok(body) => body, + Err(_) => { + return Err(()); + } + }; + + let fun_decl = FunDecl { + def_id: fid, + rust_id: def_id, + item_meta, + signature, + kind: ItemKind::Regular, + body: Ok(body), + }; + + self.translated.fun_decls.set_slot(fid, fun_decl); + + Ok(()) + } + + /// Get or create a `FunDeclId` for the given function + fn register_fun_decl_id(&mut self, def_id: InternalDefId) -> FunDeclId { + let tid = match self.translated.id_map.get(&def_id) { + Some(tid) => *tid, + None => { + let tid = AnyTransId::Fun(self.translated.fun_decls.reserve_slot()); + self.translated.id_map.insert(def_id, tid); + self.translated.reverse_id_map.insert(tid, def_id); + self.translated.all_ids.insert(tid); + tid + } + }; + *tid.as_fun() + } + + /// Compute the meta information for a Rust item identified by its id. + fn translate_item_meta_from_rid(&mut self, instance: Instance) -> Result { + let span = self.translate_instance_span(instance); + let name = self.def_id_to_name(instance.def.def_id())?; + // TODO: populate the source text + let source_text = None; + // TODO: populate the attribute info + let attr_info = + AttrInfo { attributes: Vec::new(), inline: None, rename: None, public: true }; + + // Aeneas only translates items that are local to the top-level crate + // Since we want all reachable items (including those in external + // crates) to be translated, always set `is_local` to true + let is_local = true; + + // For now, assume all items are transparent + let opacity = ItemOpacity::Transparent; + + Ok(ItemMeta { span, source_text, attr_info, name, is_local, opacity }) + } + + /// Retrieve an item name from a [DefId]. + /// This function is adapted from Charon: + /// https://github.com/AeneasVerif/charon/blob/53530427db2941ce784201e64086766504bc5642/charon/src/bin/charon-driver/translate/translate_ctx.rs#L344 + fn def_id_to_name(&mut self, def_id: DefId) -> Result { + trace!("{:?}", def_id); + let def_id = rustc_internal::internal(self.tcx(), def_id); + let tcx = self.tcx(); + let span = tcx.def_span(def_id); + + // We have to be a bit careful when retrieving names from def ids. For instance, + // due to reexports, [`TyCtxt::def_path_str`](TyCtxt::def_path_str) might give + // different names depending on the def id on which it is called, even though + // those def ids might actually identify the same definition. + // For instance: `std::boxed::Box` and `alloc::boxed::Box` are actually + // the same (the first one is a reexport). + // This is why we implement a custom function to retrieve the original name + // (though this makes us lose aliases - we may want to investigate this + // issue in the future). + + // We lookup the path associated to an id, and convert it to a name. + // Paths very precisely identify where an item is. There are important + // subcases, like the items in an `Impl` block: + // ``` + // impl List { + // fn new() ... + // } + // ``` + // + // One issue here is that "List" *doesn't appear* in the path, which would + // look like the following: + // + // `TypeNS("Crate") :: Impl :: ValueNs("new")` + // ^^^ + // This is where "List" should be + // + // For this reason, whenever we find an `Impl` path element, we actually + // lookup the type of the sub-path, from which we can derive a name. + // + // Besides, as there may be several "impl" blocks for one type, each impl + // block is identified by a unique number (rustc calls this a + // "disambiguator"), which we grab. + // + // Example: + // ======== + // For instance, if we write the following code in crate `test` and module + // `bla`: + // ``` + // impl Foo { + // fn foo() { ... } + // } + // + // impl Foo { + // fn bar() { ... } + // } + // ``` + // + // The names we will generate for `foo` and `bar` are: + // `[Ident("test"), Ident("bla"), Ident("Foo"), Disambiguator(0), Ident("foo")]` + // `[Ident("test"), Ident("bla"), Ident("Foo"), Disambiguator(1), Ident("bar")]` + let mut found_crate_name = false; + let mut name: Vec = Vec::new(); + + let def_path = tcx.def_path(def_id); + let crate_name = tcx.crate_name(def_path.krate).to_string(); + + let parents: Vec<_> = { + let mut parents = vec![def_id]; + let mut cur_id = def_id; + while let Some(parent) = tcx.opt_parent(cur_id) { + parents.push(parent); + cur_id = parent; + } + parents.into_iter().rev().collect() + }; + + // Rk.: below we try to be as tight as possible with regards to sanity + // checks, to make sure we understand what happens with def paths, and + // fail whenever we get something which is even slightly outside what + // we expect. + for cur_id in parents { + let data = tcx.def_key(cur_id).disambiguated_data; + // Match over the key data + let disambiguator = Disambiguator::new(data.disambiguator as usize); + use rustc_hir::definitions::DefPathData; + match &data.data { + DefPathData::TypeNs(symbol) => { + error_assert!(self, span, data.disambiguator == 0); // Sanity check + name.push(PathElem::Ident(symbol.to_string(), disambiguator)); + } + DefPathData::ValueNs(symbol) => { + // I think `disambiguator != 0` only with names introduced by macros (though + // not sure). + name.push(PathElem::Ident(symbol.to_string(), disambiguator)); + } + DefPathData::CrateRoot => { + // Sanity check + error_assert!(self, span, data.disambiguator == 0); + + // This should be the beginning of the path + error_assert!(self, span, name.is_empty()); + found_crate_name = true; + name.push(PathElem::Ident(crate_name.clone(), disambiguator)); + } + DefPathData::Impl => todo!(), + DefPathData::OpaqueTy => { + // TODO: do nothing for now + } + DefPathData::MacroNs(symbol) => { + error_assert!(self, span, data.disambiguator == 0); // Sanity check + + // There may be namespace collisions between, say, function + // names and macros (not sure). However, this isn't much + // of an issue here, because for now we don't expose macros + // in the AST, and only use macro names in [register], for + // instance to filter opaque modules. + name.push(PathElem::Ident(symbol.to_string(), disambiguator)); + } + DefPathData::Closure => { + // TODO: this is not very satisfactory, but on the other hand + // we should be able to extract closures in local let-bindings + // (i.e., we shouldn't have to introduce top-level let-bindings). + name.push(PathElem::Ident("closure".to_string(), disambiguator)) + } + DefPathData::ForeignMod => { + // Do nothing, functions in `extern` blocks are in the same namespace as the + // block. + } + _ => { + error_or_panic!(self, span, format!("Unexpected DefPathData: {:?}", data)); + } + } + } + + // We always add the crate name + if !found_crate_name { + name.push(PathElem::Ident(crate_name, Disambiguator::new(0))); + } + + trace!("{:?}", name); + Ok(Name { name }) + } + + /// Compute the span information for the given instance + fn translate_instance_span(&mut self, instance: Instance) -> CharonSpan { + self.translate_span(instance.def.span()) + } + + /// Compute the span information for MIR span + fn translate_span(&mut self, span: Span) -> CharonSpan { + let filename = FileName::Local(PathBuf::from(span.get_filename())); + let file_id = match self.translated.file_to_id.get(&filename) { + Some(file_id) => *file_id, + None => { + let file_id = self.translated.id_to_file.push(filename.clone()); + self.translated.file_to_id.insert(filename, file_id); + file_id + } + }; + let lineinfo = span.get_lines(); + let rspan = RawSpan { + file_id, + beg: Loc { line: lineinfo.start_line, col: lineinfo.start_col }, + end: Loc { line: lineinfo.end_line, col: lineinfo.end_col }, + rust_span_data: rustc_internal::internal(self.tcx(), span).data(), + }; + + // TODO: populate `generated_from_span` info + CharonSpan { span: rspan, generated_from_span: None } + } + + fn translate_function_signature(&mut self) -> FunSig { + let instance = self.instance; + let fn_abi = instance.fn_abi().unwrap(); + let requires_caller_location = self.requires_caller_location(instance); + let num_args = fn_abi.args.len(); + let args = fn_abi + .args + .iter() + .enumerate() + .filter_map(|(idx, arg_abi)| { + // We ignore zero-sized parameters. + // See https://github.com/model-checking/kani/issues/274 for more details. + // We also ingore the last parameter if the function requires + // caller location. + if arg_abi.mode == PassMode::Ignore + || (requires_caller_location && idx + 1 == num_args) + { + None + } else { + let ty = arg_abi.ty; + debug!(?idx, ?arg_abi, "fn_typ"); + Some(self.translate_ty(ty)) + } + }) + .collect(); + + debug!(?args, ?fn_abi, "function_type"); + let ret_type = self.translate_ty(fn_abi.ret.ty); + + // TODO: populate the rest of the information (`is_unsafe`, `is_closure`, etc.) + FunSig { + is_unsafe: false, + is_closure: false, + closure_info: None, + generics: GenericParams::default(), + parent_params_info: None, + inputs: args, + output: ret_type, + } + } + + fn translate_function_body(&mut self) -> Result { + let instance = self.instance; + let mir_body = instance.body().unwrap(); + let body_id = self.translated.bodies.reserve_slot(); + let body = self.translate_body(mir_body); + self.translated.bodies.set_slot(body_id, body); + Ok(body_id) + } + + fn translate_body(&mut self, mir_body: Body) -> CharonBody { + let span = self.translate_span(mir_body.span); + let arg_count = self.instance.fn_abi().unwrap().args.len(); + let locals = self.translate_body_locals(&mir_body); + let body: BodyContents = + mir_body.blocks.iter().map(|bb| self.translate_block(bb)).collect(); + + let body_expr = ExprBody { span, arg_count, locals, body }; + CharonBody::Unstructured(body_expr) + } + + fn requires_caller_location(&self, instance: Instance) -> bool { + let instance_internal = rustc_internal::internal(self.tcx(), instance); + instance_internal.def.requires_caller_location(self.tcx()) + } + + fn translate_ty(&self, ty: Ty) -> CharonTy { + match ty.kind() { + TyKind::RigidTy(rigid_ty) => self.translate_rigid_ty(rigid_ty), + _ => todo!(), + } + } + + fn translate_rigid_ty(&self, rigid_ty: RigidTy) -> CharonTy { + debug!("translate_rigid_ty: {rigid_ty:?}"); + match rigid_ty { + RigidTy::Bool => CharonTy::Literal(LiteralTy::Bool), + RigidTy::Char => CharonTy::Literal(LiteralTy::Char), + RigidTy::Int(it) => CharonTy::Literal(LiteralTy::Integer(translate_int_ty(it))), + RigidTy::Uint(uit) => CharonTy::Literal(LiteralTy::Integer(translate_uint_ty(uit))), + RigidTy::Never => CharonTy::Never, + RigidTy::Str => CharonTy::Adt( + TypeId::Builtin(BuiltinTy::Str), + // TODO: find out whether any of the information below should be + // populated for strings + GenericArgs { + regions: Vector::new(), + types: Vector::new(), + const_generics: Vector::new(), + trait_refs: Vector::new(), + }, + ), + RigidTy::Ref(region, ty, mutability) => CharonTy::Ref( + self.translate_region(region), + Box::new(self.translate_ty(ty)), + match mutability { + Mutability::Mut => RefKind::Mut, + Mutability::Not => RefKind::Shared, + }, + ), + RigidTy::Tuple(ty) => { + let types = ty.iter().map(|ty| self.translate_ty(*ty)).collect(); + // TODO: find out if any of the information below is needed + let generic_args = GenericArgs { + regions: Vector::new(), + types, + const_generics: Vector::new(), + trait_refs: Vector::new(), + }; + CharonTy::Adt(TypeId::Tuple, generic_args) + } + RigidTy::FnDef(def_id, args) => { + if !args.0.is_empty() { + unimplemented!("generic args are not yet handled"); + } + let sig = def_id.fn_sig().value; + let inputs = sig.inputs().iter().map(|ty| self.translate_ty(*ty)).collect(); + let output = self.translate_ty(sig.output()); + // TODO: populate regions? + CharonTy::Arrow(Vector::new(), inputs, Box::new(output)) + } + _ => todo!(), + } + } + + fn translate_body_locals(&mut self, mir_body: &Body) -> Vector { + // Charon expects the locals in the following order: + // - the local used for the return value (index 0) + // - the input arguments + // - the remaining locals, used for the intermediate computations + let mut locals = Vector::new(); + { + let mut add_variable = make_locals_generator(&mut locals); + mir_body.local_decls().for_each(|(_, local)| { + add_variable(self.translate_ty(local.ty)); + }); + } + locals + } + + fn translate_block(&mut self, bb: &BasicBlock) -> BlockData { + let statements = + bb.statements.iter().filter_map(|stmt| self.translate_statement(stmt)).collect(); + let terminator = self.translate_terminator(&bb.terminator); + BlockData { statements, terminator } + } + + fn translate_statement(&mut self, stmt: &Statement) -> Option { + let content = match &stmt.kind { + StatementKind::Assign(place, rhs) => Some(RawStatement::Assign( + self.translate_place(&place), + self.translate_rvalue(&rhs), + )), + StatementKind::SetDiscriminant { place, variant_index } => { + Some(RawStatement::SetDiscriminant( + self.translate_place(&place), + VariantId::from_usize(variant_index.to_index()), + )) + } + StatementKind::StorageLive(_) => None, + StatementKind::StorageDead(local) => { + Some(RawStatement::StorageDead(VarId::from_usize(*local))) + } + StatementKind::Nop => None, + _ => todo!(), + }; + if let Some(content) = content { + let span = self.translate_span(stmt.span); + return Some(CharonStatement { span, content }); + }; + None + } + + fn translate_terminator(&mut self, terminator: &Terminator) -> CharonTerminator { + let span = self.translate_span(terminator.span); + let content = match &terminator.kind { + TerminatorKind::Return => RawTerminator::Return, + TerminatorKind::Goto { target } => { + RawTerminator::Goto { target: BlockId::from_usize(*target) } + } + TerminatorKind::Unreachable => RawTerminator::Abort(AbortKind::UndefinedBehavior), + TerminatorKind::Drop { place, target, .. } => RawTerminator::Drop { + place: self.translate_place(&place), + target: BlockId::from_usize(*target), + }, + TerminatorKind::SwitchInt { discr, targets } => { + let (discr, targets) = self.translate_switch_targets(discr, targets); + RawTerminator::Switch { discr, targets } + } + TerminatorKind::Call { func, args, destination, target, .. } => { + debug!("translate_call: {func:?} {args:?} {destination:?} {target:?}"); + let fn_ty = func.ty(self.instance.body().unwrap().locals()).unwrap(); + let fn_ptr = match fn_ty.kind() { + TyKind::RigidTy(RigidTy::FnDef(def, args)) => { + let instance = Instance::resolve(def, &args).unwrap(); + let def_id = rustc_internal::internal(self.tcx(), instance.def.def_id()); + let fid = self.register_fun_decl_id(def_id); + FnPtr { + func: FunIdOrTraitMethodRef::Fun(FunId::Regular(fid)), + // TODO: populate generics? + generics: GenericArgs { + regions: Vector::new(), + types: Vector::new(), + const_generics: Vector::new(), + trait_refs: Vector::new(), + }, + } + } + TyKind::RigidTy(RigidTy::FnPtr(..)) => todo!(), + x => unreachable!( + "Function call where the function was of unexpected type: {:?}", + x + ), + }; + let func = FnOperand::Regular(fn_ptr); + let call = Call { + func, + args: args.iter().map(|arg| self.translate_operand(arg)).collect(), + dest: self.translate_place(destination), + }; + RawTerminator::Call { call, target: target.map(BlockId::from_usize) } + } + TerminatorKind::Assert { cond, expected, msg: _, target, .. } => { + RawTerminator::Assert { + assert: Assert { cond: self.translate_operand(cond), expected: *expected }, + target: BlockId::from_usize(*target), + } + } + _ => todo!(), + }; + CharonTerminator { span, content } + } + + fn translate_place(&self, place: &Place) -> CharonPlace { + let projection = self.translate_projection(&place.projection); + let local = place.local; + let var_id = VarId::from_usize(local); + CharonPlace { var_id, projection } + } + + fn translate_rvalue(&self, rvalue: &Rvalue) -> CharonRvalue { + trace!("translate_rvalue: {rvalue:?}"); + match rvalue { + Rvalue::Use(operand) => CharonRvalue::Use(self.translate_operand(operand)), + Rvalue::Repeat(_operand, _) => todo!(), + Rvalue::Ref(_region, kind, place) => { + CharonRvalue::Ref(self.translate_place(&place), translate_borrow_kind(kind)) + } + Rvalue::AddressOf(_, _) => todo!(), + Rvalue::Len(place) => CharonRvalue::Len( + self.translate_place(&place), + self.translate_ty(rvalue.ty(self.instance.body().unwrap().locals()).unwrap()), + None, + ), + Rvalue::Cast(kind, operand, ty) => CharonRvalue::UnaryOp( + UnOp::Cast(self.translate_cast(*kind, operand, *ty)), + self.translate_operand(operand), + ), + Rvalue::BinaryOp(bin_op, lhs, rhs) => CharonRvalue::BinaryOp( + translate_bin_op(*bin_op), + self.translate_operand(lhs), + self.translate_operand(rhs), + ), + Rvalue::CheckedBinaryOp(_, _, _) => todo!(), + Rvalue::UnaryOp(_, _) => todo!(), + Rvalue::Discriminant(_) => todo!(), + Rvalue::Aggregate(_, _) => todo!(), + Rvalue::ShallowInitBox(_, _) => todo!(), + Rvalue::CopyForDeref(_) => todo!(), + Rvalue::ThreadLocalRef(_) => todo!(), + _ => todo!(), + } + } + + fn translate_operand(&self, operand: &Operand) -> CharonOperand { + trace!("translate_operand: {operand:?}"); + match operand { + Operand::Constant(constant) => CharonOperand::Const(self.translate_constant(constant)), + Operand::Copy(place) => CharonOperand::Copy(self.translate_place(&place)), + Operand::Move(place) => CharonOperand::Move(self.translate_place(&place)), + } + } + + fn translate_constant(&self, constant: &ConstOperand) -> ConstantExpr { + trace!("translate_constant: {constant:?}"); + let value = self.translate_constant_value(&constant.const_); + ConstantExpr { value, ty: self.translate_ty(constant.ty()) } + } + + fn translate_constant_value(&self, constant: &MirConst) -> RawConstantExpr { + trace!("translate_constant_value: {constant:?}"); + match constant.kind() { + ConstantKind::Allocated(alloc) => self.translate_allocation(alloc, constant.ty()), + ConstantKind::Ty(_) => todo!(), + ConstantKind::ZeroSized => todo!(), + ConstantKind::Unevaluated(_) => todo!(), + ConstantKind::Param(_) => todo!(), + } + } + + fn translate_allocation(&self, alloc: &Allocation, ty: Ty) -> RawConstantExpr { + match ty.kind() { + TyKind::RigidTy(RigidTy::Int(it)) => { + let value = alloc.read_int().unwrap(); + let scalar_value = match it { + IntTy::I8 => ScalarValue::I8(value as i8), + IntTy::I16 => ScalarValue::I16(value as i16), + IntTy::I32 => ScalarValue::I32(value as i32), + IntTy::I64 => ScalarValue::I64(value as i64), + IntTy::I128 => ScalarValue::I128(value), + IntTy::Isize => ScalarValue::Isize(value as i64), + }; + RawConstantExpr::Literal(Literal::Scalar(scalar_value)) + } + TyKind::RigidTy(RigidTy::Uint(uit)) => { + let value = alloc.read_uint().unwrap(); + let scalar_value = match uit { + UintTy::U8 => ScalarValue::U8(value as u8), + UintTy::U16 => ScalarValue::U16(value as u16), + UintTy::U32 => ScalarValue::U32(value as u32), + UintTy::U64 => ScalarValue::U64(value as u64), + UintTy::U128 => ScalarValue::U128(value), + UintTy::Usize => ScalarValue::Usize(value as u64), + }; + RawConstantExpr::Literal(Literal::Scalar(scalar_value)) + } + TyKind::RigidTy(RigidTy::Bool) => { + let value = alloc.read_bool().unwrap(); + RawConstantExpr::Literal(Literal::Bool(value)) + } + TyKind::RigidTy(RigidTy::Char) => { + let value = char::from_u32(alloc.read_uint().unwrap() as u32); + RawConstantExpr::Literal(Literal::Char(value.unwrap())) + } + _ => todo!(), + } + } + + fn translate_cast(&self, _kind: CastKind, _operand: &Operand, _ty: Ty) -> CharonCastKind { + todo!() + } + + fn translate_switch_targets( + &self, + discr: &Operand, + targets: &SwitchTargets, + ) -> (CharonOperand, CharonSwitchTargets) { + trace!("translate_switch_targets: {discr:?} {targets:?}"); + let ty = discr.ty(self.instance.body().unwrap().locals()).unwrap(); + let discr = self.translate_operand(discr); + let charon_ty = self.translate_ty(ty); + let switch_targets = if ty.kind().is_bool() { + // Charon/Aeneas expects types with a bool discriminant to be translated to an `If` + // `len` includes the `otherwise` branch + assert_eq!(targets.len(), 2); + let (value, bb) = targets.branches().last().unwrap(); + let (then_bb, else_bb) = + if value == 0 { (targets.otherwise(), bb) } else { (bb, targets.otherwise()) }; + CharonSwitchTargets::If(BlockId::from_usize(then_bb), BlockId::from_usize(else_bb)) + } else { + let CharonTy::Literal(LiteralTy::Integer(int_ty)) = charon_ty else { + panic!("Expected integer type for switch discriminant"); + }; + let branches = targets + .branches() + .map(|(value, bb)| { + let scalar_val = match int_ty { + IntegerTy::I8 => ScalarValue::I8(value as i8), + IntegerTy::I16 => ScalarValue::I16(value as i16), + IntegerTy::I32 => ScalarValue::I32(value as i32), + IntegerTy::I64 => ScalarValue::I64(value as i64), + IntegerTy::I128 => ScalarValue::I128(value as i128), + IntegerTy::Isize => ScalarValue::Isize(value as i64), + IntegerTy::U8 => ScalarValue::U8(value as u8), + IntegerTy::U16 => ScalarValue::U16(value as u16), + IntegerTy::U32 => ScalarValue::U32(value as u32), + IntegerTy::U64 => ScalarValue::U64(value as u64), + IntegerTy::U128 => ScalarValue::U128(value), + IntegerTy::Usize => ScalarValue::Usize(value as u64), + }; + (scalar_val, BlockId::from_usize(bb)) + }) + .collect(); + let otherwise = BlockId::from_usize(targets.otherwise()); + CharonSwitchTargets::SwitchInt(int_ty, branches, otherwise) + }; + (discr, switch_targets) + } + + fn translate_projection(&self, projection: &[ProjectionElem]) -> Vec { + projection.iter().map(|elem| self.translate_projection_elem(elem)).collect() + } + + fn translate_projection_elem(&self, projection_elem: &ProjectionElem) -> CharonProjectionElem { + match projection_elem { + ProjectionElem::Deref => CharonProjectionElem::Deref, + _ => todo!(), + } + } + + fn translate_region(&self, region: Region) -> CharonRegion { + match region.kind { + RegionKind::ReStatic => CharonRegion::Static, + RegionKind::ReErased => CharonRegion::Erased, + RegionKind::ReEarlyParam(_) + | RegionKind::ReBound(_, _) + | RegionKind::RePlaceholder(_) => todo!(), + } + } +} + +fn translate_int_ty(int_ty: IntTy) -> IntegerTy { + match int_ty { + IntTy::I8 => IntegerTy::I8, + IntTy::I16 => IntegerTy::I16, + IntTy::I32 => IntegerTy::I32, + IntTy::I64 => IntegerTy::I64, + IntTy::I128 => IntegerTy::I128, + // TODO: assumes 64-bit platform + IntTy::Isize => IntegerTy::I64, + } +} + +fn translate_uint_ty(uint_ty: UintTy) -> IntegerTy { + match uint_ty { + UintTy::U8 => IntegerTy::U8, + UintTy::U16 => IntegerTy::U16, + UintTy::U32 => IntegerTy::U32, + UintTy::U64 => IntegerTy::U64, + UintTy::U128 => IntegerTy::U128, + // TODO: assumes 64-bit platform + UintTy::Usize => IntegerTy::U64, + } +} + +fn translate_bin_op(bin_op: BinOp) -> CharonBinOp { + match bin_op { + BinOp::Add | BinOp::AddUnchecked => CharonBinOp::Add, + BinOp::Sub | BinOp::SubUnchecked => CharonBinOp::Sub, + BinOp::Mul | BinOp::MulUnchecked => CharonBinOp::Mul, + BinOp::Div => CharonBinOp::Div, + BinOp::Rem => CharonBinOp::Rem, + BinOp::BitXor => CharonBinOp::BitXor, + BinOp::BitAnd => CharonBinOp::BitAnd, + BinOp::BitOr => CharonBinOp::BitOr, + BinOp::Shl | BinOp::ShlUnchecked => CharonBinOp::Shl, + BinOp::Shr | BinOp::ShrUnchecked => CharonBinOp::Shr, + BinOp::Eq => CharonBinOp::Eq, + BinOp::Lt => CharonBinOp::Lt, + BinOp::Le => CharonBinOp::Le, + BinOp::Ne => CharonBinOp::Ne, + BinOp::Ge => CharonBinOp::Ge, + BinOp::Gt => CharonBinOp::Gt, + BinOp::Cmp => todo!(), + BinOp::Offset => todo!(), + } +} + +fn translate_borrow_kind(kind: &BorrowKind) -> CharonBorrowKind { + match kind { + BorrowKind::Shared => CharonBorrowKind::Shared, + BorrowKind::Mut { .. } => CharonBorrowKind::Mut, + BorrowKind::Fake(_kind) => todo!(), + } +} diff --git a/kani-compiler/src/codegen_aeneas_llbc/mod.rs b/kani-compiler/src/codegen_aeneas_llbc/mod.rs new file mode 100644 index 000000000000..085c42ea4119 --- /dev/null +++ b/kani-compiler/src/codegen_aeneas_llbc/mod.rs @@ -0,0 +1,10 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This module hosts a codegen backend that produces low-level borrow calculus +//! (LLBC), which is the format defined by Charon/Aeneas + +mod compiler_interface; +mod mir_to_ullbc; + +pub use compiler_interface::LlbcCodegenBackend; diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index 58c22f940352..a6b6cf3a03af 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -15,7 +15,9 @@ //! in order to apply the stubs. For the subsequent runs, we add the stub configuration to //! `-C llvm-args`. -use crate::args::Arguments; +use crate::args::{Arguments, BackendOption}; +#[cfg(feature = "aeneas")] +use crate::codegen_aeneas_llbc::LlbcCodegenBackend; #[cfg(feature = "cprover")] use crate::codegen_cprover_gotoc::GotocCodegenBackend; use crate::kani_middle::check_crate_items; @@ -42,16 +44,37 @@ pub fn run(args: Vec) -> ExitCode { } } -/// Configure the cprover backend that generate goto-programs. -#[cfg(feature = "cprover")] +/// Configure the Aeneas backend that generates LLBC. +fn aeneas_backend(_queries: Arc>) -> Box { + #[cfg(feature = "aeneas")] + return Box::new(LlbcCodegenBackend::new(_queries)); + #[cfg(not(feature = "aeneas"))] + unreachable!() +} + +/// Configure the cprover backend that generates goto-programs. +fn cprover_backend(_queries: Arc>) -> Box { + #[cfg(feature = "cprover")] + return Box::new(GotocCodegenBackend::new(_queries)); + #[cfg(not(feature = "cprover"))] + unreachable!() +} + +#[cfg(any(feature = "aeneas", feature = "cprover"))] fn backend(queries: Arc>) -> Box { - Box::new(GotocCodegenBackend::new(queries)) + let backend = queries.lock().unwrap().args().backend; + match backend { + #[cfg(feature = "aeneas")] + BackendOption::Aeneas => aeneas_backend(queries), + #[cfg(feature = "cprover")] + BackendOption::CProver => cprover_backend(queries), + } } /// Fallback backend. It will trigger an error if no backend has been enabled. -#[cfg(not(feature = "cprover"))] +#[cfg(not(any(feature = "aeneas", feature = "cprover")))] fn backend(queries: Arc>) -> Box { - compile_error!("No backend is available. Only supported value today is `cprover`"); + compile_error!("No backend is available. Use `aeneas` or `cprover`."); } /// This object controls the compiler behavior. diff --git a/kani-compiler/src/main.rs b/kani-compiler/src/main.rs index 3a7816c1b084..1ba05d990955 100644 --- a/kani-compiler/src/main.rs +++ b/kani-compiler/src/main.rs @@ -38,6 +38,8 @@ extern crate stable_mir; extern crate tempfile; mod args; +#[cfg(feature = "aeneas")] +mod codegen_aeneas_llbc; #[cfg(feature = "cprover")] mod codegen_cprover_gotoc; mod intrinsics; diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 573ad21ab31a..30c6bd7073a6 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -271,6 +271,10 @@ pub struct VerificationArgs { #[arg(long, hide_short_help = true)] pub coverage: bool, + /// Print final LLBC for Aeneas backend. This requires the `-Z aeneas` option. + #[arg(long, hide = true)] + pub print_llbc: bool, + /// Arguments to pass down to Cargo #[command(flatten)] pub cargo: CargoCommonArgs, @@ -609,6 +613,23 @@ impl ValidateArgs for VerificationArgs { )); } + if self.print_llbc && !self.common_args.unstable_features.contains(UnstableFeature::Aeneas) + { + return Err(Error::raw( + ErrorKind::MissingRequiredArgument, + "The `--print-llbc` argument is unstable and requires `-Z aeneas` to be used.", + )); + } + + // TODO: error out for other CBMC-backend-specific arguments + if self.common_args.unstable_features.contains(UnstableFeature::Aeneas) + && !self.cbmc_args.is_empty() + { + return Err(Error::raw( + ErrorKind::ArgumentConflict, + "The `--cbmc-args` argument cannot be used with -Z aeneas.", + )); + } Ok(()) } } @@ -899,4 +920,12 @@ mod tests { check_invalid_args("kani input.rs --package foo".split_whitespace()); check_invalid_args("kani input.rs --exclude bar --workspace".split_whitespace()); } + + #[test] + fn check_cbmc_args_aeneas_backend() { + let args = "kani input.rs -Z aeneas --enable-unstable --cbmc-args --object-bits 10" + .split_whitespace(); + let err = StandaloneArgs::try_parse_from(args).unwrap().validate().unwrap_err(); + assert_eq!(err.kind(), ErrorKind::ArgumentConflict); + } } diff --git a/kani-driver/src/call_cargo.rs b/kani-driver/src/call_cargo.rs index bc0e9d8361d7..b566f0ff79c7 100644 --- a/kani-driver/src/call_cargo.rs +++ b/kani-driver/src/call_cargo.rs @@ -193,6 +193,9 @@ crate-type = ["lib"] // Arguments that will only be passed to the target package. let mut pkg_args: Vec = vec![]; pkg_args.extend(["--".to_string(), self.reachability_arg()]); + if let Some(backend_arg) = self.backend_arg() { + pkg_args.push(backend_arg); + } let mut found_target = false; let packages = packages_to_verify(&self.args, &metadata)?; diff --git a/kani-driver/src/call_single_file.rs b/kani-driver/src/call_single_file.rs index 63ca71d5b8d1..e33fbe874946 100644 --- a/kani-driver/src/call_single_file.rs +++ b/kani-driver/src/call_single_file.rs @@ -53,6 +53,9 @@ impl KaniSession { ) -> Result<()> { let mut kani_args = self.kani_compiler_flags(); kani_args.push(format!("--reachability={}", self.reachability_mode())); + if self.args.common_args.unstable_features.contains(UnstableFeature::Aeneas) { + kani_args.push("--backend=aeneas".into()); + } let lib_path = lib_folder().unwrap(); let mut rustc_args = self.kani_rustc_flags(LibConfig::new(lib_path)); @@ -95,6 +98,14 @@ impl KaniSession { to_rustc_arg(vec![format!("--reachability={}", self.reachability_mode())]) } + pub fn backend_arg(&self) -> Option { + if self.args.common_args.unstable_features.contains(UnstableFeature::Aeneas) { + Some(to_rustc_arg(vec!["--backend=aeneas".into()])) + } else { + None + } + } + /// These arguments are arguments passed to kani-compiler that are `kani` compiler specific. pub fn kani_compiler_flags(&self) -> Vec { let mut flags = vec![check_version()]; @@ -142,11 +153,11 @@ impl KaniSession { flags.push("--ub-check=uninit".into()); } - flags.extend(self.args.common_args.unstable_features.as_arguments().map(str::to_string)); + if self.args.print_llbc { + flags.push("--print-llbc".into()); + } - // This argument will select the Kani flavour of the compiler. It will be removed before - // rustc driver is invoked. - flags.push("--goto-c".into()); + flags.extend(self.args.common_args.unstable_features.as_arguments().map(str::to_string)); flags } diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index 11df998c820f..abab48304914 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -88,6 +88,8 @@ pub enum UnstableFeature { /// Automatically check that no invalid value is produced which is considered UB in Rust. /// Note that this does not include checking uninitialized value. ValidValueChecks, + /// Aeneas/LLBC + Aeneas, /// Ghost state and shadow memory APIs. GhostState, /// Automatically check that uninitialized memory is not used. diff --git a/rustfmt.toml b/rustfmt.toml index 44cbfe4a3dc9..f895fafbbdef 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -15,4 +15,5 @@ ignore = [ # For some reason, this is not working without the directory wildcard. "**/firecracker", "**/tests/perf/s2n-quic/", + "**/charon/charon/", ] diff --git a/scripts/codegen-firecracker.sh b/scripts/codegen-firecracker.sh index 3ac7dfa6585d..bffb27023412 100755 --- a/scripts/codegen-firecracker.sh +++ b/scripts/codegen-firecracker.sh @@ -42,7 +42,7 @@ RUST_FLAGS=( "--kani-compiler" "-Cpanic=abort" "-Zalways-encode-mir" - "-Cllvm-args=--goto-c" + "-Cllvm-args=--backend=cprover" "-Cllvm-args=--ignore-global-asm" "-Cllvm-args=--reachability=pub_fns" "--sysroot=${KANI_DIR}/target/kani" diff --git a/scripts/std-lib-regression.sh b/scripts/std-lib-regression.sh index a7881f1dc19a..b010da4581f6 100755 --- a/scripts/std-lib-regression.sh +++ b/scripts/std-lib-regression.sh @@ -71,7 +71,7 @@ RUST_FLAGS=( "--kani-compiler" "-Cpanic=abort" "-Zalways-encode-mir" - "-Cllvm-args=--goto-c" + "-Cllvm-args=--backend=cprover" "-Cllvm-args=--ignore-global-asm" "-Cllvm-args=--reachability=pub_fns" "-Cllvm-args=--build-std" diff --git a/tests/expected/llbc/basic0/expected b/tests/expected/llbc/basic0/expected new file mode 100644 index 000000000000..112a67a21548 --- /dev/null +++ b/tests/expected/llbc/basic0/expected @@ -0,0 +1,8 @@ +fn test::is_zero(@1: i32) -> bool\ +{\ + let @0: bool; // return\ + let @1: i32; // arg #1\ + + @0 := copy (@1) == const (0 : i32)\ + return\ +} diff --git a/tests/expected/llbc/basic0/test.rs b/tests/expected/llbc/basic0/test.rs new file mode 100644 index 000000000000..5025994ab31c --- /dev/null +++ b/tests/expected/llbc/basic0/test.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zaeneas --print-llbc + +//! This test checks that Kani's LLBC backend handles basic expressions, in this +//! case an equality between a variable and a constant + +fn is_zero(i: i32) -> bool { + i == 0 +} + +#[kani::proof] +fn main() { + let _ = is_zero(0); +} diff --git a/tests/expected/llbc/basic1/expected b/tests/expected/llbc/basic1/expected new file mode 100644 index 000000000000..9cb0bef6f7c6 --- /dev/null +++ b/tests/expected/llbc/basic1/expected @@ -0,0 +1,15 @@ +fn test::select(@1: bool, @2: i32, @3: i32) -> i32 +{ + let @0: i32; // return + let @1: bool; // arg #1 + let @2: i32; // arg #2 + let @3: i32; // arg #3 + + if copy (@1) { + @0 := copy (@2) + } + else { + @0 := copy (@3) + } + return +} diff --git a/tests/expected/llbc/basic1/test.rs b/tests/expected/llbc/basic1/test.rs new file mode 100644 index 000000000000..92818fb93bfb --- /dev/null +++ b/tests/expected/llbc/basic1/test.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zaeneas --print-llbc + +//! This test checks that Kani's LLBC backend handles basic expressions, in this +//! case an if condition + +fn select(s: bool, x: i32, y: i32) -> i32 { + if s { x } else { y } +} + +#[kani::proof] +fn main() { + let _ = select(true, 3, 7); +} From f17cc4d31e0f61055b4997c41cb84c5ece71aed8 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 8 Oct 2024 18:08:56 -0700 Subject: [PATCH 117/159] Bump Kani version to 0.56.0 (#3581) Bump our crates version and exclude `charon` from our workspace (otherwise building the workspace wouldn't work). See my comment for the original notes. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- CHANGELOG.md | 21 ++ Cargo.lock | 511 +++------------------------------ Cargo.toml | 3 +- cprover_bindings/Cargo.toml | 2 +- kani-compiler/Cargo.toml | 2 +- kani-driver/Cargo.toml | 2 +- kani_metadata/Cargo.toml | 2 +- library/kani/Cargo.toml | 2 +- library/kani_core/Cargo.toml | 2 +- library/kani_macros/Cargo.toml | 2 +- library/std/Cargo.toml | 2 +- tools/build-kani/Cargo.toml | 2 +- 12 files changed, 72 insertions(+), 481 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e35af4c8fa24..23d740f534a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ This file contains notable changes (e.g. breaking changes, major changes, etc.) This file was introduced starting Kani 0.23.0, so it only contains changes from version 0.23.0 onwards. +## [0.56.0] + +### Breaking Changes + +* Remove obsolete linker options (`--mir-linker` and `--legacy-linker`) by @zhassan-aws in https://github.com/model-checking/kani/pull/3559 +* Deprecate `kani::check` by @celinval in https://github.com/model-checking/kani/pull/3557 + +### What's Changed + +* Enable stubbing and function contracts for primitive types by @celinval in https://github.com/model-checking/kani/pull/3496 +* Instrument validity checks for pointer to reference casts for slices and str's by @zhassan-aws in https://github.com/model-checking/kani/pull/3513 +* Fail compilation if `proof_for_contract` is added to generic function by @carolynzech in https://github.com/model-checking/kani/pull/3522 +* Fix storing coverage data in cargo projects by @adpaco-aws in https://github.com/model-checking/kani/pull/3527 +* Add experimental API to generate arbitrary pointers by @celinval in https://github.com/model-checking/kani/pull/3538 +* Running `verify-std` no longer changes Cargo files by @celinval in https://github.com/model-checking/kani/pull/3577 +* Add an LLBC backend by @zhassan-aws in https://github.com/model-checking/kani/pull/3514 +* Rust toolchain upgraded to nightly-2024-10-03 by @qinheping @tautschnig @celinval +* CBMC upgraded to 6.3.1 by @tautschnig in https://github.com/model-checking/kani/pull/3537 + +**Full Changelog**: https://github.com/model-checking/kani/compare/kani-0.55.0...kani-0.56.0 + ## [0.55.0] ### Major/Breaking Changes diff --git a/Cargo.lock b/Cargo.lock index 41fb02073f6a..21997d7bc332 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "ahash" version = "0.8.11" @@ -39,15 +24,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anstream" version = "0.6.15" @@ -131,59 +107,12 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "assert_tokens_eq" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b21f4c5ba5c8b55031306325f196df925939bcc2bd7188ce68fabd93fb4f149" -dependencies = [ - "ansi_term", - "ctor", - "difference", - "output_vt100", - "snafu", -] - [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "bincode" -version = "2.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" -dependencies = [ - "bincode_derive", - "serde", -] - -[[package]] -name = "bincode_derive" -version = "2.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" -dependencies = [ - "virtue", -] - [[package]] name = "bitflags" version = "2.6.0" @@ -221,7 +150,7 @@ dependencies = [ [[package]] name = "build-kani" -version = "0.55.0" +version = "0.56.0" dependencies = [ "anyhow", "cargo_metadata", @@ -295,14 +224,11 @@ dependencies = [ "derive-visitor", "env_logger", "hashlink", - "hax-frontend-exporter", - "ignore", "im", "index_vec", "indoc", "itertools 0.13.0", "lazy_static", - "libtest-mimic", "log", "macros", "nom", @@ -315,15 +241,12 @@ dependencies = [ "serde-map-to-array", "serde_json", "serde_stacker", - "snapbox", "stacker", "take_mut", - "tempfile", "toml", "tracing", "tracing-subscriber", "tracing-tree 0.3.1", - "walkdir", "which", ] @@ -356,8 +279,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.79", ] @@ -442,7 +365,7 @@ dependencies = [ [[package]] name = "cprover_bindings" -version = "0.55.0" +version = "0.56.0" dependencies = [ "lazy_static", "linear-map", @@ -523,16 +446,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote 1.0.37", - "syn 1.0.109", -] - [[package]] name = "deranged" version = "0.3.11" @@ -548,8 +461,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -570,17 +483,11 @@ checksum = "427b39a85fecafea16b1a5f3f50437151022e35eb4fe038107f08adbf7f8def6" dependencies = [ "convert_case 0.4.0", "itertools 0.10.5", - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 1.0.109", ] -[[package]] -name = "difference" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" - [[package]] name = "difflib" version = "0.4.0" @@ -593,12 +500,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - [[package]] name = "either" version = "1.13.0" @@ -650,41 +551,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "escape8259" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" - -[[package]] -name = "ext-trait" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" -dependencies = [ - "ext-trait-proc_macros", -] - -[[package]] -name = "ext-trait-proc_macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", -] - -[[package]] -name = "extension-traits" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" -dependencies = [ - "ext-trait", -] - [[package]] name = "fastrand" version = "2.1.1" @@ -717,31 +583,12 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "globset" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata 0.4.8", - "regex-syntax 0.8.5", -] - [[package]] name = "graph-cycles" version = "0.1.0" @@ -778,59 +625,12 @@ dependencies = [ "serde", ] -[[package]] -name = "hax-adt-into" -version = "0.1.0-alpha.1" -source = "git+https://github.com/hacspec/hax?branch=main#496e381186de242f46ee2fd2b4b8971c70aa07a4" -dependencies = [ - "itertools 0.11.0", - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", -] - -[[package]] -name = "hax-frontend-exporter" -version = "0.1.0-alpha.1" -source = "git+https://github.com/hacspec/hax?branch=main#496e381186de242f46ee2fd2b4b8971c70aa07a4" -dependencies = [ - "bincode", - "extension-traits", - "hax-adt-into", - "hax-frontend-exporter-options", - "itertools 0.11.0", - "lazy_static", - "paste", - "schemars", - "serde", - "serde_json", - "tracing", -] - -[[package]] -name = "hax-frontend-exporter-options" -version = "0.1.0-alpha.1" -source = "git+https://github.com/hacspec/hax?branch=main#496e381186de242f46ee2fd2b4b8971c70aa07a4" -dependencies = [ - "bincode", - "hax-adt-into", - "schemars", - "serde", - "serde_json", -] - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "home" version = "0.5.9" @@ -846,22 +646,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "ignore" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata 0.4.8", - "same-file", - "walkdir", - "winapi-util", -] - [[package]] name = "im" version = "15.1.0" @@ -922,15 +706,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -954,7 +729,7 @@ checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" [[package]] name = "kani" -version = "0.55.0" +version = "0.56.0" dependencies = [ "kani_core", "kani_macros", @@ -962,7 +737,7 @@ dependencies = [ [[package]] name = "kani-compiler" -version = "0.55.0" +version = "0.56.0" dependencies = [ "charon", "clap", @@ -972,7 +747,7 @@ dependencies = [ "kani_metadata", "lazy_static", "num", - "quote 1.0.37", + "quote", "regex", "serde", "serde_json", @@ -987,7 +762,7 @@ dependencies = [ [[package]] name = "kani-driver" -version = "0.55.0" +version = "0.56.0" dependencies = [ "anyhow", "cargo_metadata", @@ -1016,7 +791,7 @@ dependencies = [ [[package]] name = "kani-verifier" -version = "0.55.0" +version = "0.56.0" dependencies = [ "anyhow", "home", @@ -1025,24 +800,24 @@ dependencies = [ [[package]] name = "kani_core" -version = "0.55.0" +version = "0.56.0" dependencies = [ "kani_macros", ] [[package]] name = "kani_macros" -version = "0.55.0" +version = "0.56.0" dependencies = [ "proc-macro-error2", - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.79", ] [[package]] name = "kani_metadata" -version = "0.55.0" +version = "0.56.0" dependencies = [ "clap", "cprover_bindings", @@ -1063,18 +838,6 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" -[[package]] -name = "libtest-mimic" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58" -dependencies = [ - "clap", - "escape8259", - "termcolor", - "threadpool", -] - [[package]] name = "linear-map" version = "1.2.0" @@ -1111,9 +874,8 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" name = "macros" version = "0.1.0" dependencies = [ - "assert_tokens_eq", - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.79", ] @@ -1144,15 +906,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - [[package]] name = "nom" version = "7.1.3" @@ -1176,12 +929,6 @@ dependencies = [ "nom", ] -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1280,25 +1027,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.20.2" @@ -1315,15 +1043,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] - [[package]] name = "overload" version = "0.1.1" @@ -1353,12 +1072,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pathdiff" version = "0.2.1" @@ -1440,8 +1153,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", ] [[package]] @@ -1451,20 +1164,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.79", ] -[[package]] -name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid", -] - [[package]] name = "proc-macro2" version = "1.0.86" @@ -1483,22 +1187,13 @@ dependencies = [ "cc", ] -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2", ] [[package]] @@ -1674,30 +1369,6 @@ dependencies = [ "strum_macros", ] -[[package]] -name = "schemars" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "serde_derive_internals", - "syn 2.0.79", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -1737,19 +1408,8 @@ version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 2.0.79", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.79", ] @@ -1827,12 +1487,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "similar" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" - [[package]] name = "sized-chunks" version = "0.6.5" @@ -1849,50 +1503,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "snafu" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b028158eb06caa8345bee10cccfb25fa632beccf0ef5308832b4fd4b78a7db48" -dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf50aaef500c248a590e2696e8bf8c7620ca2235b9bb90a70363d82dd1abec6a" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", -] - -[[package]] -name = "snapbox" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba434818a8a9b1b106404288d6bd75a94348aae8fc9a518b211b609a36a54bc" -dependencies = [ - "anstream", - "anstyle", - "normalize-line-endings", - "similar", - "snapbox-macros", -] - -[[package]] -name = "snapbox-macros" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" -dependencies = [ - "anstream", -] - [[package]] name = "stacker" version = "0.1.17" @@ -1908,7 +1518,7 @@ dependencies = [ [[package]] name = "std" -version = "0.55.0" +version = "0.56.0" dependencies = [ "kani", ] @@ -1943,31 +1553,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "rustversion", "syn 2.0.79", ] -[[package]] -name = "syn" -version = "0.15.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid", -] - [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "unicode-ident", ] @@ -1977,8 +1576,8 @@ version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "unicode-ident", ] @@ -2001,15 +1600,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "termtree" version = "0.4.1" @@ -2031,8 +1621,8 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.79", ] @@ -2046,15 +1636,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - [[package]] name = "time" version = "0.3.36" @@ -2137,8 +1718,8 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.79", ] @@ -2249,12 +1830,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -2279,12 +1854,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "virtue" -version = "0.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" - [[package]] name = "wait-timeout" version = "0.2.0" @@ -2532,7 +2101,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.79", ] diff --git a/Cargo.toml b/Cargo.toml index ee9848b578dc..42c6f2c722b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-verifier" -version = "0.55.0" +version = "0.56.0" edition = "2021" description = "A bit-precise model checker for Rust." readme = "README.md" @@ -56,6 +56,7 @@ default-members = [ exclude = [ "build", + "charon", "target", # dependency tests have their own workspace "tests/kani-dependency-test/dependency3", diff --git a/cprover_bindings/Cargo.toml b/cprover_bindings/Cargo.toml index 008c81aef2ad..e26f23d5c081 100644 --- a/cprover_bindings/Cargo.toml +++ b/cprover_bindings/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cprover_bindings" -version = "0.55.0" +version = "0.56.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-compiler/Cargo.toml b/kani-compiler/Cargo.toml index 0360763f9068..7b17e6073bb3 100644 --- a/kani-compiler/Cargo.toml +++ b/kani-compiler/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-compiler" -version = "0.55.0" +version = "0.56.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index 7485d2279ad6..555534959474 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani-driver" -version = "0.55.0" +version = "0.56.0" edition = "2021" description = "Build a project with Kani and run all proof harnesses" license = "MIT OR Apache-2.0" diff --git a/kani_metadata/Cargo.toml b/kani_metadata/Cargo.toml index 18eadc4095ed..2a0a03401c40 100644 --- a/kani_metadata/Cargo.toml +++ b/kani_metadata/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_metadata" -version = "0.55.0" +version = "0.56.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani/Cargo.toml b/library/kani/Cargo.toml index fa50783516f4..3ad1b286ebe6 100644 --- a/library/kani/Cargo.toml +++ b/library/kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani" -version = "0.55.0" +version = "0.56.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_core/Cargo.toml b/library/kani_core/Cargo.toml index 447cd0b3f298..df55e6278282 100644 --- a/library/kani_core/Cargo.toml +++ b/library/kani_core/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_core" -version = "0.55.0" +version = "0.56.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 6930366b84fc..a7876176adb2 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "kani_macros" -version = "0.55.0" +version = "0.56.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 12c923e9b655..e1e353704723 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -5,7 +5,7 @@ # Note: this package is intentionally named std to make sure the names of # standard library symbols are preserved name = "std" -version = "0.55.0" +version = "0.56.0" edition = "2021" license = "MIT OR Apache-2.0" publish = false diff --git a/tools/build-kani/Cargo.toml b/tools/build-kani/Cargo.toml index 41095f1d7c3c..25e022e70d3f 100644 --- a/tools/build-kani/Cargo.toml +++ b/tools/build-kani/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "build-kani" -version = "0.55.0" +version = "0.56.0" edition = "2021" description = "Builds Kani, Sysroot and release bundle." license = "MIT OR Apache-2.0" From c64575219fc672f3884a2e965f5f1438c7d2033c Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Wed, 9 Oct 2024 08:55:32 -0700 Subject: [PATCH 118/159] Fix the computation of the number of bytes of a pointer offset (#3584) This PR fixes the logic that computes the number of bytes of a pointer offset. The logic was incorrectly using the size of the pointer as opposed to the size of the pointee. This fixes the soundness issue in #3582 (but not the spurious failures). 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/rvalue.rs | 2 +- .../offset-from-bytes-overflow/expected | 2 +- tests/expected/offset-overflow/expected | 2 +- tests/expected/offset-overflow/main.rs | 9 ++++--- tests/expected/ptr-offset-overflow/expected | 5 ++++ tests/expected/ptr-offset-overflow/main.rs | 27 +++++++++++++++++++ 6 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 tests/expected/ptr-offset-overflow/expected create mode 100644 tests/expected/ptr-offset-overflow/main.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index 7a4594a181e3..14e012aac76b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -410,7 +410,7 @@ impl<'tcx> GotocCtx<'tcx> { // Check that computing `offset` in bytes would not overflow let (offset_bytes, bytes_overflow_check) = self.count_in_bytes( ce2.clone().cast_to(Type::ssize_t()), - ty, + pointee_type_stable(ty).unwrap(), Type::ssize_t(), "offset", loc, diff --git a/tests/expected/offset-from-bytes-overflow/expected b/tests/expected/offset-from-bytes-overflow/expected index 9613638bf8f9..bcf0242f9e9d 100644 --- a/tests/expected/offset-from-bytes-overflow/expected +++ b/tests/expected/offset-from-bytes-overflow/expected @@ -1,2 +1,2 @@ FAILURE\ -attempt to compute offset which would overflow +attempt to compute number in bytes which would overflow diff --git a/tests/expected/offset-overflow/expected b/tests/expected/offset-overflow/expected index 72ded2ac24c1..bcf0242f9e9d 100644 --- a/tests/expected/offset-overflow/expected +++ b/tests/expected/offset-overflow/expected @@ -1,2 +1,2 @@ FAILURE\ -attempt to compute offset which would overflow \ No newline at end of file +attempt to compute number in bytes which would overflow diff --git a/tests/expected/offset-overflow/main.rs b/tests/expected/offset-overflow/main.rs index 55b79df854cc..43f294848012 100644 --- a/tests/expected/offset-overflow/main.rs +++ b/tests/expected/offset-overflow/main.rs @@ -8,10 +8,13 @@ use std::intrinsics::offset; #[kani::proof] fn test_offset_overflow() { - let s: &str = "123"; - let ptr: *const u8 = s.as_ptr(); + let a: [i32; 3] = [1, 2, 3]; + let ptr: *const i32 = a.as_ptr(); + // a value that when multiplied by the size of i32 (i.e. 4 bytes) + // would overflow `isize` + let count: isize = isize::MAX / 4 + 1; unsafe { - let _d = offset(ptr, isize::MAX / 8); + let _d = offset(ptr, count); } } diff --git a/tests/expected/ptr-offset-overflow/expected b/tests/expected/ptr-offset-overflow/expected new file mode 100644 index 000000000000..ad33ce91dabb --- /dev/null +++ b/tests/expected/ptr-offset-overflow/expected @@ -0,0 +1,5 @@ +std::ptr::const_ptr::::offset.arithmetic_overflow\ +Status: FAILURE\ +Description: "offset: attempt to compute number in bytes which would overflow" + +VERIFICATION:- FAILED diff --git a/tests/expected/ptr-offset-overflow/main.rs b/tests/expected/ptr-offset-overflow/main.rs new file mode 100644 index 000000000000..d2877addb923 --- /dev/null +++ b/tests/expected/ptr-offset-overflow/main.rs @@ -0,0 +1,27 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! This test checks that Kani detects the overflow in pointer offset + +use std::convert::TryFrom; + +struct Foo { + arr: [i32; 4096], +} + +#[cfg_attr(kani, kani::proof)] +fn main() { + let f = Foo { arr: [0; 4096] }; + assert_eq!(std::mem::size_of::(), 16384); + // a large enough count that causes `count * size_of::()` to overflow + // `isize` without overflowing `usize` + let count: usize = 562949953421320; + // the `unwrap` ensures that it indeed doesn't overflow `usize` + let bytes = count.checked_mul(std::mem::size_of::()).unwrap(); + // ensure that it overflows `isize`: + assert!(isize::try_from(bytes).is_err()); + + let ptr: *const Foo = &f as *const Foo; + // this should fail because `count * size_of::` overflows `isize` + let _p = unsafe { ptr.offset(count as isize) }; +} From 04000248aaa2d44d2c990c059542c9245c78eab0 Mon Sep 17 00:00:00 2001 From: Carolyn Zech Date: Wed, 9 Oct 2024 12:27:13 -0400 Subject: [PATCH 119/159] List Subcommand (Implementation) (#3523) Implementation of the list subcommand (and updates to the RFC). As a larger test, I ran on the standard library (`kani list -Z list -Z function-contracts -Z mem-predicates ./library --std`) and manually verified that the results were correct. I pasted the output below. Contracts: | | Function | Contract Harnesses (#[kani::proof_for_contract]) | | ----- | ------------------------------------------------ | ------------------------------------------------------ | | | alloc::layout::Layout::from_size_align_unchecked | alloc::layout::verify::check_from_size_align_unchecked | | | ascii::ascii_char::AsciiChar::from_u8 | ascii::ascii_char::verify::check_from_u8 | | | ascii::ascii_char::AsciiChar::from_u8_unchecked | ascii::ascii_char::verify::check_from_u8_unchecked | | | char::convert::from_u32_unchecked | char::convert::verify::check_from_u32_unchecked | | | char::methods::verify::as_ascii_clone | char::methods::verify::check_as_ascii_ascii_char | | | | char::methods::verify::check_as_ascii_non_ascii_char | | | intrinsics::typed_swap | intrinsics::verify::check_typed_swap_u8 | | | | intrinsics::verify::check_typed_swap_char | | | | intrinsics::verify::check_typed_swap_non_zero | | | mem::swap | mem::verify::check_swap_primitive | | | | mem::verify::check_swap_adt_no_drop | | | ptr::align_offset | ptr::verify::check_align_offset_zst | | | | ptr::verify::check_align_offset_u8 | | | | ptr::verify::check_align_offset_u16 | | | | ptr::verify::check_align_offset_u32 | | | | ptr::verify::check_align_offset_u64 | | | | ptr::verify::check_align_offset_u128 | | | | ptr::verify::check_align_offset_4096 | | | | ptr::verify::check_align_offset_5 | | | ptr::alignment::Alignment::as_nonzero | ptr::alignment::verify::check_as_nonzero | | | ptr::alignment::Alignment::as_usize | ptr::alignment::verify::check_as_usize | | | ptr::alignment::Alignment::log2 | ptr::alignment::verify::check_log2 | | | ptr::alignment::Alignment::mask | ptr::alignment::verify::check_mask | | | ptr::alignment::Alignment::new | ptr::alignment::verify::check_new | | | ptr::alignment::Alignment::new_unchecked | ptr::alignment::verify::check_new_unchecked | | | ptr::alignment::Alignment::of | ptr::alignment::verify::check_of_i32 | | | ptr::non_null::NonNull::::new | ptr::non_null::verify::non_null_check_new | | | ptr::non_null::NonNull::::new_unchecked | ptr::non_null::verify::non_null_check_new_unchecked | | | ptr::read_volatile | ptr::verify::check_read_u128 | | | ptr::unique::Unique::::as_non_null_ptr | ptr::unique::verify::check_as_non_null_ptr | | | ptr::unique::Unique::::as_ptr | ptr::unique::verify::check_as_ptr | | | ptr::unique::Unique::::new | ptr::unique::verify::check_new | | | ptr::unique::Unique::::new_unchecked | ptr::unique::verify::check_new_unchecked | | | ptr::verify::mod_inv_copy | ptr::verify::check_mod_inv | | | ptr::write_volatile | NONE | | Total | 24 | 34 | Standard Harnesses (#[kani::proof]): 1. ptr::unique::verify::check_as_mut 2. ptr::unique::verify::check_as_ref 3. ptr::unique::verify::check_cast Terminal view (`--pretty` format): list 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 Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> --- Cargo.lock | 11 ++ .../compiler_interface.rs | 7 +- .../src/kani_middle/codegen_units.rs | 7 +- kani-compiler/src/kani_middle/metadata.rs | 51 ++++- kani-driver/Cargo.toml | 3 +- kani-driver/src/args/list_args.rs | 127 ++++++++++++ kani-driver/src/args/mod.rs | 8 + kani-driver/src/list/collect_metadata.rs | 101 ++++++++++ kani-driver/src/list/mod.rs | 14 ++ kani-driver/src/list/output.rs | 180 ++++++++++++++++++ kani-driver/src/main.rs | 8 + kani-driver/src/metadata.rs | 2 + kani-driver/src/version.rs | 2 +- kani_metadata/src/lib.rs | 15 +- kani_metadata/src/unstable.rs | 2 + rfc/src/rfcs/0013-list.md | 112 ++++++----- tests/script-based-pre/cargo_list/Cargo.toml | 10 + tests/script-based-pre/cargo_list/config.yml | 4 + .../script-based-pre/cargo_list/list.expected | 52 +++++ tests/script-based-pre/cargo_list/list.sh | 10 + tests/script-based-pre/cargo_list/src/lib.rs | 68 +++++++ .../cargo_list/src/standard_harnesses.rs | 15 ++ tests/script-based-pre/kani_list/config.yml | 4 + .../script-based-pre/kani_list/list.expected | 52 +++++ tests/script-based-pre/kani_list/list.sh | 10 + tests/script-based-pre/kani_list/src/lib.rs | 71 +++++++ 26 files changed, 883 insertions(+), 63 deletions(-) create mode 100644 kani-driver/src/args/list_args.rs create mode 100644 kani-driver/src/list/collect_metadata.rs create mode 100644 kani-driver/src/list/mod.rs create mode 100644 kani-driver/src/list/output.rs create mode 100644 tests/script-based-pre/cargo_list/Cargo.toml create mode 100644 tests/script-based-pre/cargo_list/config.yml create mode 100644 tests/script-based-pre/cargo_list/list.expected create mode 100755 tests/script-based-pre/cargo_list/list.sh create mode 100644 tests/script-based-pre/cargo_list/src/lib.rs create mode 100644 tests/script-based-pre/cargo_list/src/standard_harnesses.rs create mode 100644 tests/script-based-pre/kani_list/config.yml create mode 100644 tests/script-based-pre/kani_list/list.expected create mode 100755 tests/script-based-pre/kani_list/list.sh create mode 100644 tests/script-based-pre/kani_list/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 21997d7bc332..580cdf70946a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "colour" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b536eebcabe54980476d120a182f7da2268fe02d22575cca99cee5fdda178280" +dependencies = [ + "winapi", +] + [[package]] name = "comfy-table" version = "7.1.1" @@ -767,6 +776,7 @@ dependencies = [ "anyhow", "cargo_metadata", "clap", + "colour", "comfy-table", "console", "glob", @@ -1419,6 +1429,7 @@ version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ + "indexmap", "itoa", "memchr", "ryu", diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index da211c58946c..25bc0cbe8ad1 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -20,9 +20,8 @@ use cbmc::RoundingMode; use cbmc::goto_program::Location; use cbmc::irep::goto_binary_serde::write_goto_binary_file; use cbmc::{InternedString, MachineModel}; -use kani_metadata::UnsupportedFeature; use kani_metadata::artifact::convert_type; -use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; +use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata, UnsupportedFeature}; use kani_metadata::{AssignsContract, CompilerArtifactStub}; use rustc_codegen_ssa::back::archive::{ ArArchiveBuilder, ArchiveBuilder, ArchiveBuilderBuilder, DEFAULT_OBJECT_READER, @@ -643,6 +642,10 @@ impl GotoCodegenResults { proof_harnesses: proofs, unsupported_features, test_harnesses: tests, + // We don't collect the contracts metadata because the FunctionWithContractPass + // removes any contracts logic for ReachabilityType::Test or ReachabilityType::PubFns, + // which are the two ReachabilityTypes under which the compiler calls this function. + contracted_functions: vec![], } } diff --git a/kani-compiler/src/kani_middle/codegen_units.rs b/kani-compiler/src/kani_middle/codegen_units.rs index 071e880ffd9f..ebb12f7656b6 100644 --- a/kani-compiler/src/kani_middle/codegen_units.rs +++ b/kani-compiler/src/kani_middle/codegen_units.rs @@ -9,7 +9,7 @@ use crate::args::ReachabilityType; use crate::kani_middle::attributes::is_proof_harness; -use crate::kani_middle::metadata::gen_proof_metadata; +use crate::kani_middle::metadata::{gen_contracts_metadata, gen_proof_metadata}; use crate::kani_middle::reachability::filter_crate_items; use crate::kani_middle::resolve::expect_resolve_fn; use crate::kani_middle::stubbing::{check_compatibility, harness_stub_map}; @@ -93,7 +93,7 @@ impl CodegenUnits { /// Write compilation metadata into a file. pub fn write_metadata(&self, queries: &QueryDb, tcx: TyCtxt) { - let metadata = self.generate_metadata(); + let metadata = self.generate_metadata(tcx); let outpath = metadata_output_path(tcx); store_metadata(queries, &metadata, &outpath); } @@ -103,7 +103,7 @@ impl CodegenUnits { } /// Generate [KaniMetadata] for the target crate. - fn generate_metadata(&self) -> KaniMetadata { + fn generate_metadata(&self, tcx: TyCtxt) -> KaniMetadata { let (proof_harnesses, test_harnesses) = self.harness_info.values().cloned().partition(|md| md.attributes.is_proof_harness()); KaniMetadata { @@ -111,6 +111,7 @@ impl CodegenUnits { proof_harnesses, unsupported_features: vec![], test_harnesses, + contracted_functions: gen_contracts_metadata(tcx), } } } diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index db6b6b06149a..c92b20cf49d6 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -3,15 +3,16 @@ //! This module handles Kani metadata generation. For example, generating HarnessMetadata for a //! given function. +use std::collections::HashMap; use std::path::Path; -use crate::kani_middle::attributes::test_harness_name; +use crate::kani_middle::attributes::{KaniAttributes, test_harness_name}; +use crate::kani_middle::{SourceLocation, stable_fn_def}; +use kani_metadata::ContractedFunction; use kani_metadata::{ArtifactType, HarnessAttributes, HarnessKind, HarnessMetadata}; use rustc_middle::ty::TyCtxt; -use stable_mir::CrateDef; use stable_mir::mir::mono::Instance; - -use super::{SourceLocation, attributes::KaniAttributes}; +use stable_mir::{CrateDef, CrateItems, DefId}; /// Create the harness metadata for a proof harness for a given function. pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> HarnessMetadata { @@ -40,6 +41,48 @@ pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> } } +/// Collects contract and contract harness metadata. +/// +/// For each function with contracts (or that is a target of a contract harness), +/// construct a `ContractedFunction` object for it. +pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec { + // We work with `stable_mir::CrateItem` instead of `stable_mir::Instance` to include generic items + let crate_items: CrateItems = stable_mir::all_local_items(); + + let mut fn_to_data: HashMap = HashMap::new(); + + for item in crate_items { + let function = item.name(); + let file = SourceLocation::new(item.span()).filename; + let attributes = KaniAttributes::for_def_id(tcx, item.def_id()); + + if attributes.has_contract() { + fn_to_data.insert(item.def_id(), ContractedFunction { + function, + file, + harnesses: vec![], + }); + } else if let Some((target_name, internal_def_id, _)) = + attributes.interpret_for_contract_attribute() + { + let target_def_id = stable_fn_def(tcx, internal_def_id) + .expect("The target of a proof for contract should be a function definition") + .def_id(); + if let Some(cf) = fn_to_data.get_mut(&target_def_id) { + cf.harnesses.push(function); + } else { + fn_to_data.insert(target_def_id, ContractedFunction { + function: target_name.to_string(), + file, + harnesses: vec![function], + }); + } + } + } + + fn_to_data.into_values().collect() +} + /// Create the harness metadata for a test description. #[allow(dead_code)] pub fn gen_test_metadata( diff --git a/kani-driver/Cargo.toml b/kani-driver/Cargo.toml index 555534959474..67214738fa7c 100644 --- a/kani-driver/Cargo.toml +++ b/kani-driver/Cargo.toml @@ -18,8 +18,9 @@ anyhow = "1" console = "0.15.1" once_cell = "1.19.0" serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde_json = { version = "1", features = ["preserve_order"] } clap = { version = "4.4.11", features = ["derive"] } +colour = "2.1.0" glob = "0.3" toml = "0.8" regex = "1.6" diff --git a/kani-driver/src/args/list_args.rs b/kani-driver/src/args/list_args.rs new file mode 100644 index 000000000000..7129bd0a85c4 --- /dev/null +++ b/kani-driver/src/args/list_args.rs @@ -0,0 +1,127 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Implements the subcommand handling of the list subcommand + +use std::path::PathBuf; + +use crate::args::ValidateArgs; +use clap::{Error, Parser, ValueEnum, error::ErrorKind}; +use kani_metadata::UnstableFeature; + +use super::VerificationArgs; + +/// List information relevant to verification +#[derive(Debug, Parser)] +pub struct CargoListArgs { + #[command(flatten)] + pub verify_opts: VerificationArgs, + + /// Output format + #[clap(long, default_value = "pretty")] + pub format: Format, +} + +/// List information relevant to verification +#[derive(Debug, Parser)] +pub struct StandaloneListArgs { + /// Rust file to verify + #[arg(required = true)] + pub input: PathBuf, + + #[arg(long, hide = true)] + pub crate_name: Option, + + #[command(flatten)] + pub verify_opts: VerificationArgs, + + /// Output format + #[clap(long, default_value = "pretty")] + pub format: Format, + + /// Pass this flag to run the `list` command on the standard library. + /// Ensure that the provided `path` is the `library` folder. + #[arg(long)] + pub std: bool, +} + +/// Message formats available for the subcommand. +#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, strum_macros::Display)] +#[strum(serialize_all = "kebab-case")] +pub enum Format { + /// Print diagnostic messages in a user friendly format. + Pretty, + /// Print diagnostic messages in JSON format. + Json, +} + +impl ValidateArgs for CargoListArgs { + fn validate(&self) -> Result<(), Error> { + self.verify_opts.validate()?; + if !self.verify_opts.common_args.unstable_features.contains(UnstableFeature::List) { + return Err(Error::raw( + ErrorKind::MissingRequiredArgument, + "The `list` subcommand is unstable and requires -Z list", + )); + } + + Ok(()) + } +} + +impl ValidateArgs for StandaloneListArgs { + fn validate(&self) -> Result<(), Error> { + self.verify_opts.validate()?; + if !self.verify_opts.common_args.unstable_features.contains(UnstableFeature::List) { + return Err(Error::raw( + ErrorKind::MissingRequiredArgument, + "The `list` subcommand is unstable and requires -Z list", + )); + } + + if self.std { + if !self.input.exists() { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: `` argument `{}` does not exist", + self.input.display() + ), + )) + } else if !self.input.is_dir() { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: `` argument `{}` is not a directory", + self.input.display() + ), + )) + } else { + let full_path = self.input.canonicalize()?; + let dir = full_path.file_stem().unwrap(); + if dir != "library" { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: Expected `` to point to the `library` folder \ + containing the standard library crates.\n\ + Found `{}` folder instead", + dir.to_string_lossy() + ), + )) + } else { + Ok(()) + } + } + } else if self.input.is_file() { + Ok(()) + } else { + Err(Error::raw( + ErrorKind::InvalidValue, + format!( + "Invalid argument: Input invalid. `{}` is not a regular file.", + self.input.display() + ), + )) + } + } +} diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 30c6bd7073a6..8aa758219524 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -5,6 +5,7 @@ pub mod assess_args; pub mod cargo; pub mod common; +pub mod list_args; pub mod playback_args; pub mod std_args; @@ -94,6 +95,8 @@ pub enum StandaloneSubcommand { Playback(Box), /// Verify the rust standard library. VerifyStd(Box), + /// Execute the list subcommand + List(Box), } #[derive(Debug, clap::Parser)] @@ -119,6 +122,9 @@ pub enum CargoKaniSubcommand { /// Execute concrete playback testcases of a local package. Playback(Box), + + /// List metadata relevant to verification, e.g., harnesses. + List(Box), } // Common arguments for invoking Kani for verification purpose. This gets put into KaniContext, @@ -421,6 +427,7 @@ impl ValidateArgs for StandaloneArgs { match &self.command { Some(StandaloneSubcommand::VerifyStd(args)) => args.validate()?, + Some(StandaloneSubcommand::List(args)) => args.validate()?, // TODO: Invoke PlaybackArgs::validate() None | Some(StandaloneSubcommand::Playback(..)) => {} }; @@ -467,6 +474,7 @@ impl ValidateArgs for CargoKaniSubcommand { // Assess doesn't implement validation yet. CargoKaniSubcommand::Assess(_) => Ok(()), CargoKaniSubcommand::Playback(playback) => playback.validate(), + CargoKaniSubcommand::List(list) => list.validate(), } } } diff --git a/kani-driver/src/list/collect_metadata.rs b/kani-driver/src/list/collect_metadata.rs new file mode 100644 index 000000000000..99da0477314d --- /dev/null +++ b/kani-driver/src/list/collect_metadata.rs @@ -0,0 +1,101 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// This module invokes the compiler to gather the metadata for the list subcommand, then post-processes the output. + +use std::collections::{BTreeMap, BTreeSet}; + +use crate::{ + InvocationType, + args::list_args::{CargoListArgs, Format, StandaloneListArgs}, + list::Totals, + list::output::{json, pretty}, + project::{Project, cargo_project, standalone_project, std_project}, + session::KaniSession, + version::print_kani_version, +}; +use anyhow::Result; +use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata}; + +/// Process the KaniMetadata output from kani-compiler and output the list subcommand results +fn process_metadata(metadata: Vec, format: Format) -> Result<()> { + // We use ordered maps and sets so that the output is in lexicographic order (and consistent across invocations). + + // Map each file to a vector of its harnesses. + let mut standard_harnesses: BTreeMap> = BTreeMap::new(); + let mut contract_harnesses: BTreeMap> = BTreeMap::new(); + + let mut contracted_functions: BTreeSet = BTreeSet::new(); + + let mut total_standard_harnesses = 0; + let mut total_contract_harnesses = 0; + + for kani_meta in metadata { + for harness_meta in kani_meta.proof_harnesses { + match harness_meta.attributes.kind { + HarnessKind::Proof => { + total_standard_harnesses += 1; + if let Some(harnesses) = standard_harnesses.get_mut(&harness_meta.original_file) + { + harnesses.insert(harness_meta.pretty_name); + } else { + standard_harnesses.insert( + harness_meta.original_file, + BTreeSet::from([harness_meta.pretty_name]), + ); + } + } + HarnessKind::ProofForContract { .. } => { + total_contract_harnesses += 1; + if let Some(harnesses) = contract_harnesses.get_mut(&harness_meta.original_file) + { + harnesses.insert(harness_meta.pretty_name); + } else { + contract_harnesses.insert( + harness_meta.original_file, + BTreeSet::from([harness_meta.pretty_name]), + ); + } + } + HarnessKind::Test => {} + } + } + + contracted_functions.extend(kani_meta.contracted_functions.into_iter()); + } + + let totals = Totals { + standard_harnesses: total_standard_harnesses, + contract_harnesses: total_contract_harnesses, + contracted_functions: contracted_functions.len(), + }; + + match format { + Format::Pretty => pretty(standard_harnesses, contracted_functions, totals), + Format::Json => json(standard_harnesses, contract_harnesses, contracted_functions, totals), + } +} + +pub fn list_cargo(args: CargoListArgs) -> Result<()> { + let session = KaniSession::new(args.verify_opts)?; + if !session.args.common_args.quiet { + print_kani_version(InvocationType::CargoKani(vec![])); + } + + let project = cargo_project(&session, false)?; + process_metadata(project.metadata, args.format) +} + +pub fn list_standalone(args: StandaloneListArgs) -> Result<()> { + let session = KaniSession::new(args.verify_opts)?; + if !session.args.common_args.quiet { + print_kani_version(InvocationType::Standalone); + } + + let project: Project = if args.std { + std_project(&args.input, &session)? + } else { + standalone_project(&args.input, args.crate_name, &session)? + }; + + process_metadata(project.metadata, args.format) +} diff --git a/kani-driver/src/list/mod.rs b/kani-driver/src/list/mod.rs new file mode 100644 index 000000000000..0987a0e9c927 --- /dev/null +++ b/kani-driver/src/list/mod.rs @@ -0,0 +1,14 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Implements the list subcommand logic + +pub mod collect_metadata; +mod output; + +/// Stores the total count of standard harnesses, contract harnesses, +/// and functions under contract across all `KaniMetadata` objects. +struct Totals { + standard_harnesses: usize, + contract_harnesses: usize, + contracted_functions: usize, +} diff --git a/kani-driver/src/list/output.rs b/kani-driver/src/list/output.rs new file mode 100644 index 000000000000..79a5fcf6fe5e --- /dev/null +++ b/kani-driver/src/list/output.rs @@ -0,0 +1,180 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! This module handles outputting the result for the list subcommand + +use std::{ + cmp::max, + collections::{BTreeMap, BTreeSet}, + fs::File, + io::BufWriter, +}; + +use crate::{list::Totals, version::KANI_VERSION}; +use anyhow::Result; +use colour::print_ln_bold; +use kani_metadata::ContractedFunction; +use serde_json::json; + +// Represents the version of our JSON file format. +// Increment this version (according to semantic versioning rules) whenever the JSON output format changes. +const FILE_VERSION: &str = "0.1"; +const JSON_FILENAME: &str = "kani-list.json"; + +/// Construct the table of contracts information. +fn construct_contracts_table( + contracted_functions: BTreeSet, + totals: Totals, +) -> Vec { + const NO_HARNESSES_MSG: &str = "NONE"; + + // Since the harnesses will be separated by newlines, the column length is equal to the length of the longest harness + fn column_len(harnesses: &[String]) -> usize { + harnesses.iter().map(|s| s.len()).max().unwrap_or(NO_HARNESSES_MSG.len()) + } + + // Contracts table headers + const FUNCTION_HEADER: &str = "Function"; + const CONTRACT_HARNESSES_HEADER: &str = "Contract Harnesses (#[kani::proof_for_contract])"; + + // Contracts table totals row + const TOTALS_HEADER: &str = "Total"; + let functions_total = totals.contracted_functions.to_string(); + let harnesses_total = totals.contract_harnesses.to_string(); + + let mut table_rows: Vec = vec![]; + let mut max_function_fmt_width = max(FUNCTION_HEADER.len(), functions_total.len()); + let mut max_contract_harnesses_fmt_width = + max(CONTRACT_HARNESSES_HEADER.len(), harnesses_total.len()); + + let mut data_rows: Vec<(String, Vec)> = vec![]; + + for cf in contracted_functions { + max_function_fmt_width = max(max_function_fmt_width, cf.function.len()); + max_contract_harnesses_fmt_width = + max(max_contract_harnesses_fmt_width, column_len(&cf.harnesses)); + + data_rows.push((cf.function, cf.harnesses)); + } + + let function_sep = "-".repeat(max_function_fmt_width); + let contract_harnesses_sep = "-".repeat(max_contract_harnesses_fmt_width); + let totals_sep = "-".repeat(TOTALS_HEADER.len()); + + let sep_row = format!("| {totals_sep} | {function_sep} | {contract_harnesses_sep} |"); + table_rows.push(sep_row.clone()); + + let function_space = " ".repeat(max_function_fmt_width - FUNCTION_HEADER.len()); + let contract_harnesses_space = + " ".repeat(max_contract_harnesses_fmt_width - CONTRACT_HARNESSES_HEADER.len()); + let totals_space = " ".repeat(TOTALS_HEADER.len()); + + let header_row = format!( + "| {totals_space} | {FUNCTION_HEADER}{function_space} | {CONTRACT_HARNESSES_HEADER}{contract_harnesses_space} |" + ); + table_rows.push(header_row); + table_rows.push(sep_row.clone()); + + for (function, harnesses) in data_rows { + let function_space = " ".repeat(max_function_fmt_width - function.len()); + let first_harness = harnesses.first().map_or(NO_HARNESSES_MSG, |v| v); + let contract_harnesses_space = + " ".repeat(max_contract_harnesses_fmt_width - first_harness.len()); + + let first_row = format!( + "| {totals_space} | {function}{function_space} | {first_harness}{contract_harnesses_space} |" + ); + table_rows.push(first_row); + + for subsequent_harness in harnesses.iter().skip(1) { + let function_space = " ".repeat(max_function_fmt_width); + let contract_harnesses_space = + " ".repeat(max_contract_harnesses_fmt_width - subsequent_harness.len()); + let row = format!( + "| {totals_space} | {function_space} | {subsequent_harness}{contract_harnesses_space} |" + ); + table_rows.push(row); + } + + table_rows.push(sep_row.clone()) + } + + let total_function_space = " ".repeat(max_function_fmt_width - functions_total.len()); + let total_harnesses_space = + " ".repeat(max_contract_harnesses_fmt_width - harnesses_total.len()); + + let totals_row = format!( + "| {TOTALS_HEADER} | {functions_total}{total_function_space} | {harnesses_total}{total_harnesses_space} |" + ); + + table_rows.push(totals_row); + table_rows.push(sep_row.clone()); + + table_rows +} + +/// Output results as a table printed to the terminal. +pub fn pretty( + standard_harnesses: BTreeMap>, + contracted_functions: BTreeSet, + totals: Totals, +) -> Result<()> { + const CONTRACTS_SECTION: &str = "Contracts:"; + const HARNESSES_SECTION: &str = "Standard Harnesses (#[kani::proof]):"; + const NO_CONTRACTS_MSG: &str = "No contracts or contract harnesses found."; + const NO_HARNESSES_MSG: &str = "No standard harnesses found."; + + print_ln_bold!("\n{CONTRACTS_SECTION}"); + + if contracted_functions.is_empty() { + println!("{NO_CONTRACTS_MSG}"); + } else { + let table_rows = construct_contracts_table(contracted_functions, totals); + println!("{}", table_rows.join("\n")); + }; + + print_ln_bold!("\n{HARNESSES_SECTION}"); + if standard_harnesses.is_empty() { + println!("{NO_HARNESSES_MSG}"); + } + + let mut std_harness_index = 0; + + for (_, harnesses) in standard_harnesses { + for harness in harnesses { + println!("{}. {harness}", std_harness_index + 1); + std_harness_index += 1; + } + } + + println!(); + + Ok(()) +} + +/// Output results as a JSON file. +pub fn json( + standard_harnesses: BTreeMap>, + contract_harnesses: BTreeMap>, + contracted_functions: BTreeSet, + totals: Totals, +) -> Result<()> { + let out_file = File::create(JSON_FILENAME).unwrap(); + let writer = BufWriter::new(out_file); + + let json_obj = json!({ + "kani-version": KANI_VERSION, + "file-version": FILE_VERSION, + "standard-harnesses": &standard_harnesses, + "contract-harnesses": &contract_harnesses, + "contracts": &contracted_functions, + "totals": { + "standard-harnesses": totals.standard_harnesses, + "contract-harnesses": totals.contract_harnesses, + "functions-under-contract": totals.contracted_functions, + } + }); + + serde_json::to_writer_pretty(writer, &json_obj)?; + + Ok(()) +} diff --git a/kani-driver/src/main.rs b/kani-driver/src/main.rs index e8ede555f180..88f92b3a70f6 100644 --- a/kani-driver/src/main.rs +++ b/kani-driver/src/main.rs @@ -12,6 +12,7 @@ use args_toml::join_args; use crate::args::StandaloneSubcommand; use crate::concrete_playback::playback::{playback_cargo, playback_standalone}; +use crate::list::collect_metadata::{list_cargo, list_standalone}; use crate::project::Project; use crate::session::KaniSession; use crate::version::print_kani_version; @@ -33,6 +34,7 @@ mod cbmc_property_renderer; mod concrete_playback; mod coverage; mod harness_runner; +mod list; mod metadata; mod project; mod session; @@ -67,6 +69,10 @@ fn cargokani_main(input_args: Vec) -> Result<()> { let args = args::CargoKaniArgs::parse_from(&input_args); check_is_valid(&args); + if let Some(CargoKaniSubcommand::List(args)) = args.command { + return list_cargo(*args); + } + let session = session::KaniSession::new(args.verify_opts)?; if !session.args.common_args.quiet { @@ -80,6 +86,7 @@ fn cargokani_main(input_args: Vec) -> Result<()> { Some(CargoKaniSubcommand::Playback(args)) => { return playback_cargo(*args); } + Some(CargoKaniSubcommand::List(_)) => unreachable!(), None => {} } @@ -98,6 +105,7 @@ fn standalone_main() -> Result<()> { let (session, project) = match args.command { Some(StandaloneSubcommand::Playback(args)) => return playback_standalone(*args), + Some(StandaloneSubcommand::List(args)) => return list_standalone(*args), Some(StandaloneSubcommand::VerifyStd(args)) => { let session = KaniSession::new(args.verify_opts)?; if !session.args.common_args.quiet { diff --git a/kani-driver/src/metadata.rs b/kani-driver/src/metadata.rs index 625d8b4bbcaa..174ce55187a6 100644 --- a/kani-driver/src/metadata.rs +++ b/kani-driver/src/metadata.rs @@ -96,6 +96,7 @@ pub fn merge_kani_metadata(files: Vec) -> KaniMetadata { proof_harnesses: vec![], unsupported_features: vec![], test_harnesses: vec![], + contracted_functions: vec![], }; for md in files { // Note that we're taking ownership of the original vec, and so we can move the data into the new data structure. @@ -104,6 +105,7 @@ pub fn merge_kani_metadata(files: Vec) -> KaniMetadata { // https://github.com/model-checking/kani/issues/1758 result.unsupported_features.extend(md.unsupported_features); result.test_harnesses.extend(md.test_harnesses); + result.contracted_functions.extend(md.contracted_functions); } result } diff --git a/kani-driver/src/version.rs b/kani-driver/src/version.rs index 95d98b0d6d3e..e1b995c3cd53 100644 --- a/kani-driver/src/version.rs +++ b/kani-driver/src/version.rs @@ -7,7 +7,7 @@ const KANI_RUST_VERIFIER: &str = "Kani Rust Verifier"; /// We assume this is the same as the `kani-verifier` version, but we should /// make sure it's enforced through CI: /// -const KANI_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub(crate) const KANI_VERSION: &str = env!("CARGO_PKG_VERSION"); /// Print Kani version. At present, this is only release version information. pub(crate) fn print_kani_version(invocation_type: InvocationType) { diff --git a/kani_metadata/src/lib.rs b/kani_metadata/src/lib.rs index fa5a8828b6be..96caf92133e9 100644 --- a/kani_metadata/src/lib.rs +++ b/kani_metadata/src/lib.rs @@ -3,9 +3,8 @@ extern crate clap; -use std::{collections::HashSet, path::PathBuf}; - use serde::{Deserialize, Serialize}; +use std::{collections::HashSet, path::PathBuf}; pub use artifact::ArtifactType; pub use cbmc_solver::CbmcSolver; @@ -32,6 +31,18 @@ pub struct KaniMetadata { pub unsupported_features: Vec, /// If crates are built in test-mode, then test harnesses will be recorded here. pub test_harnesses: Vec, + /// The functions with contracts in this crate + pub contracted_functions: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct ContractedFunction { + /// The fully qualified name the user gave to the function (i.e. includes the module path). + pub function: String, + /// The (currently full-) path to the file this function was declared within. + pub file: String, + /// The pretty names of the proof harnesses (`#[kani::proof_for_contract]`) for this function + pub harnesses: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/kani_metadata/src/unstable.rs b/kani_metadata/src/unstable.rs index abab48304914..a602c33e0326 100644 --- a/kani_metadata/src/unstable.rs +++ b/kani_metadata/src/unstable.rs @@ -96,6 +96,8 @@ pub enum UnstableFeature { UninitChecks, /// Enable an unstable option or subcommand. UnstableOptions, + /// The list subcommand [RFC 13](https://model-checking.github.io/kani/rfc/rfcs/0013-list.html) + List, } impl UnstableFeature { diff --git a/rfc/src/rfcs/0013-list.md b/rfc/src/rfcs/0013-list.md index bdbf4681f430..0d2baee2b594 100644 --- a/rfc/src/rfcs/0013-list.md +++ b/rfc/src/rfcs/0013-list.md @@ -1,8 +1,8 @@ - **Feature Name:** List Subcommand - **Feature Request Issue:** [#2573](https://github.com/model-checking/kani/issues/2573), [#1612](https://github.com/model-checking/kani/issues/1612) - **RFC PR:** #3463 -- **Status:** Under Review -- **Version:** 1 +- **Status:** Unstable +- **Version:** 2 ------------------- @@ -20,53 +20,50 @@ This feature will not cause any regressions for exisiting users. ## User Experience -Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand will take the option `--message-format=[pretty|json]`, which changes the output format. The default is `pretty`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. +Users run a `list` subcommand, which prints metadata about the harnesses and contracts in each crate under verification. The subcommand takes two options: +- `--message-format=[pretty|json]`: choose the output format. The default is `pretty`, which prints to the terminal. The `json` option creates and writes to a JSON file instead. +- `--std`: this option should be specified when listing the harnesses and contracts in the standard library. This option is only available for `kani list` (not `cargo kani list`), which mirrors the verification workflow for the standard library. -This subcommand will not fail. In the case that it does not find any harnesses or contracts, it will print a message informing the user of that fact. +This subcommand does not fail. In the case that it does not find any harnesses or contracts, it prints a message informing the user of that fact. ### Pretty Format -The default format, `pretty`, will print the harnesses and contracts in a project, along with the total counts of each. +The default format, `pretty`, prints a "Contracts" table and a "Standard Harnesses" list. +Each row of the "Contracts" table consists of a function under contract and its contract harnesses. +The results are printed in lexicographic order. For example: ``` Kani Rust Verifier 0.54.0 (standalone) -Standard Harnesses: -- example::verify::check_new -- example::verify::check_modify - -Contract Harnesses: -- example::verify::check_foo_u32 -- example::verify::check_foo_u64 -- example::verify::check_func -- example::verify::check_bar - Contracts: -|--------------------------|-----------------------------------------------| -| Function | Contract Harnesses | -|--------------------------|-----------------------------------------------| -| example::impl::func | example::verify::check_func | -|--------------------------|-----------------------------------------------| -| example::impl::bar | example::verify::check_bar | -|--------------------------|-----------------------------------------------| -| example::impl::foo | example::verify::check_foo_u32, | -| | example::verify::check_foo_u64 | -|--------------------------|-----------------------------------------------| -| example::prep::parse | NONE | -|--------------------------|-----------------------------------------------| - -Totals: -- Standard Harnesses: 2 -- Contract Harnesses: 4 -- Functions with Contracts: 4 -- Contracts: 10 +|-------|-------------------------|--------------------------------------------------| +| | Function | Contract Harnesses (#[kani::proof_for_contract]) | +|-------|-------------------------|--------------------------------------------------| +| | example::impl::bar | example::verify::check_bar | +|-------|-------------------------|--------------------------------------------------| +| | example::impl::baz | example::verify::check_baz | +|-------|-------------------------|--------------------------------------------------| +| | example::impl::foo | example::verify::check_foo_u32 | +| | | example::verify::check_foo_u64 | +|-------|-------------------------|--------------------------------------------------| +| | example::impl::func | example::verify::check_func | +|-------|-------------------------|--------------------------------------------------| +| | example::prep::parse | NONE | +|-------|-------------------------|--------------------------------------------------| +| Total | 5 | 5 | +|-------|-------------------------|--------------------------------------------------| + +Standard Harnesses (#[kani::proof]): +1. example::verify::check_modify +2. example::verify::check_new ``` -A "Standard Harness" is a `#[proof]` harness, while a "Contract Harness" is a `#[proof_for_contract]` harness. - -All sections will be present in the output, regardless of the result. If a list is empty, Kani will output a `NONE` string. +All sections will be present in the output, regardless of the result. +If there are no harnesses for a function under contract, Kani inserts `NONE` in the "Contract Harnesses" column. +If the "Contracts" section is empty, Kani prints a message that "No contracts or contract harnesses were found." +If the "Standard Harnesses" section is empty, Kani prints a message that "No standard harnesses were found." ### JSON Format @@ -96,6 +93,7 @@ For example: file: /Users/johnsmith/example/kani_contract_proofs.rs harnesses: [ example::verify::check_bar, + example::verify::check_baz, example::verify::check_foo_u32, example::verify::check_foo_u64, example::verify::check_func @@ -104,14 +102,14 @@ For example: ], contracts: [ { - function: example::impl::func + function: example::impl::bar file: /Users/johnsmith/example/impl.rs - harnesses: [example::verify::check_func] + harnesses: [example::verify::check_bar] }, { - function: example::impl::bar + function: example::impl::baz file: /Users/johnsmith/example/impl.rs - harnesses: [example::verify::check_bar] + harnesses: [example::verify::check_baz] }, { function: example::impl::foo @@ -121,6 +119,11 @@ For example: example::verify::check_foo_u64 ] }, + { + function: example::impl::func + file: /Users/johnsmith/example/impl.rs + harnesses: [example::verify::check_func] + }, { function: example::prep::parse file: /Users/johnsmith/example/prep.rs @@ -129,9 +132,8 @@ For example: ], totals: { standard-harnesses: 2, - contract-harnesses: 4, - functions-with-contracts: 4, - contracts: 10, + contract-harnesses: 5, + functions-with-contracts: 5, } } ``` @@ -141,9 +143,16 @@ If there is no result for a given field (e.g., there are no contracts), Kani wil ## Software Design -We will add a new subcommand to `kani-driver`. +### Driver/Metdata Changes -*We will update this section once the UX is finalized*. +We add a new `list` subcommand to `kani-driver`, which invokes the compiler to collect metadata, then post-processes that metadata and outputs the result. +We extend `KaniMetadata` to include a new field containing each function under contract and its contract harnesses. + +### Compiler Changes + +In `codegen_crate`, we update the generation of `KaniMetadata` to include the new contracts information. +We iterate through each local item in the crate. +Each time we find a function under contract or a contract harness, we include it in the metadata. ## Rationale and alternatives @@ -152,19 +161,22 @@ Users of Kani may have many questions about their project--not only where their 1. Where are the harnesses? 2. Where are the contracts? 3. Which contracts are verified, and by which harnesses? -4. How many harnesses and contracts are there? +4. How many harnesses and functions under contract are there? We believe these questions are the most important for our use cases of tracking verification progress for customers and the standard library. The UX is designed to answer these questions clearly and concisely. We could have a more verbose or granular output, e.g., printing the metadata on a per-crate or per-module level, or including stubs or other attributes. Such a design would have the benefit of providing more information, with the disadvantage of being more complex to implement and more information for the user to process. - If we do not implement this feature, users will have to obtain this metadata through manual searching, or by writing a script to do it themselves. This feature will improve our internal productivity by automating the process. +The Contracts table is close to Markdown, but not quite Markdown--it includes line separators between each row, when Markdown would only have a separator for the header. +We include the separator because without it, it can be difficult to tell from reading the terminal output which entries are in the same row. +The user can transform the table to Markdown by deleting these separators, and we can trivially add a Markdown option in the future if there is demand for it. + ## Open questions -1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts, the number of `requires`, `ensures`, or `modifies` contracts, or the locations of the contracts. +1. Do we want to include more contracts information? We could print more granular information about contracts, e.g., the text of the contracts or the number of contracts. 2. More generally, we could introduce additional options that collect information about other Kani attributes (e.g., stubs). The default would be to leave them out, but this way a user could get more verbose output if they so choose. -3. Do we want to add a filtering option? For instance, `--harnesses ` and `--contracts `, where `pattern` corresponds to a Rust-style path. For example, `kani list --harnesses "my_crate::my_module::*"` would include all harnesses with that path prefix, while `kani list --contracts "my_crate::my_module::*"` would include all functions under contract with that path prefix. +3. Do we want to add a filtering option? For instance, `--harnesses ` and `--contracts `, where `pattern` corresponds to a Rust-style path. For example, `kani list --harnesses "my_crate::my_module::*"` would include all harnesses with that path prefix, while `kani list --contracts "my_crate::my_module::*"` would include all functions under contract with that path prefix. (If we do this work, we could use it to improve our `--harness` [pattern handling for verification](https://github.com/model-checking/kani/blob/main/kani-driver/src/metadata.rs#L187-L189)). ## Out of scope / Future Improvements @@ -179,4 +191,4 @@ fn check() { See [this blog post](https://model-checking.github.io/kani-verifier-blog/2022/10/27/using-kani-with-the-bolero-property-testing-framework.html) for more information. -There's no easy way for us to know whether a harness comes from Bolero, since Bolero takes care of rewriting the test to use Kani syntax and invoking the Kani engine. By the time the harness gets to Kani, there's no way for us to tell it apart from a regular harness. Fixing this would require some changes to our Bolero integration. \ No newline at end of file +There's no easy way for us to know whether a harness comes from Bolero, since Bolero takes care of rewriting the test to use Kani syntax and invoking the Kani engine. By the time the harness gets to Kani, there's no way for us to tell it apart from a regular harness. Fixing this would require some changes to our Bolero integration. diff --git a/tests/script-based-pre/cargo_list/Cargo.toml b/tests/script-based-pre/cargo_list/Cargo.toml new file mode 100644 index 000000000000..2f213d2fccd7 --- /dev/null +++ b/tests/script-based-pre/cargo_list/Cargo.toml @@ -0,0 +1,10 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +[package] +name = "cargo_list" +version = "0.1.0" +edition = "2021" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] } diff --git a/tests/script-based-pre/cargo_list/config.yml b/tests/script-based-pre/cargo_list/config.yml new file mode 100644 index 000000000000..4eac6f79588c --- /dev/null +++ b/tests/script-based-pre/cargo_list/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: list.sh +expected: list.expected diff --git a/tests/script-based-pre/cargo_list/list.expected b/tests/script-based-pre/cargo_list/list.expected new file mode 100644 index 000000000000..fb168c5a3bd2 --- /dev/null +++ b/tests/script-based-pre/cargo_list/list.expected @@ -0,0 +1,52 @@ +{ + "kani-version": + "file-version": "0.1", + "standard-harnesses": { + "src/standard_harnesses.rs": [ + "standard_harnesses::example::verify::check_modify", + "standard_harnesses::example::verify::check_new" + ] + }, + "contract-harnesses": { + "src/lib.rs": [ + "example::verify::check_bar", + "example::verify::check_foo_u32", + "example::verify::check_foo_u64", + "example::verify::check_func" + ] + }, + "contracts": [ + { + "function": "example::implementation::bar", + "file": "src/lib.rs", + "harnesses": [ + "example::verify::check_bar" + ] + }, + { + "function": "example::implementation::foo", + "file": "src/lib.rs", + "harnesses": [ + "example::verify::check_foo_u32", + "example::verify::check_foo_u64" + ] + }, + { + "function": "example::implementation::func", + "file": "src/lib.rs", + "harnesses": [ + "example::verify::check_func" + ] + }, + { + "function": "example::prep::parse", + "file": "src/lib.rs", + "harnesses": [] + } + ], + "totals": { + "standard-harnesses": 2, + "contract-harnesses": 4, + "functions-under-contract": 4 + } +} diff --git a/tests/script-based-pre/cargo_list/list.sh b/tests/script-based-pre/cargo_list/list.sh new file mode 100755 index 000000000000..6b1ab80b0f5f --- /dev/null +++ b/tests/script-based-pre/cargo_list/list.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# Check that the JSON file produced by `cargo kani list` is correct. +# Note that the list.expected file omits the value for "kani-version" +# to avoid having to update the test every time we bump versions. + +cargo kani list -Z list -Z function-contracts --format json +cat "kani-list.json" diff --git a/tests/script-based-pre/cargo_list/src/lib.rs b/tests/script-based-pre/cargo_list/src/lib.rs new file mode 100644 index 000000000000..e7382a9124a3 --- /dev/null +++ b/tests/script-based-pre/cargo_list/src/lib.rs @@ -0,0 +1,68 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! This test replicates the module structure from the running example in the list RFC. +//! It ensures that the list command works across modules, and with modifies clauses, history expressions, and generic functions. + +mod standard_harnesses; + +#[cfg(kani)] +mod example { + mod prep { + #[kani::requires(s.len() < 10)] + fn parse(s: &str) -> u32 { + s.parse().unwrap() + } + } + + pub mod implementation { + #[kani::requires(*x < 4)] + #[kani::requires(*x > 2)] + #[kani::ensures(|_| old(*x - 1) == *x)] + #[kani::ensures(|_| *x == 4)] + #[kani::modifies(x)] + pub fn bar(x: &mut u32) { + *x += 1; + } + + #[kani::requires(*x < 100)] + #[kani::modifies(x)] + pub fn func(x: &mut i32) { + *x *= 1; + } + + #[kani::requires(true)] + #[kani::ensures(|_| old(*x) == *x)] + pub fn foo(x: &mut T) -> T { + *x + } + } + + mod verify { + use crate::example::implementation; + + #[kani::proof_for_contract(implementation::bar)] + fn check_bar() { + let mut x = 7; + implementation::bar(&mut x); + } + + #[kani::proof_for_contract(implementation::foo)] + fn check_foo_u32() { + let mut x: u32 = 7; + implementation::foo(&mut x); + } + + #[kani::proof_for_contract(implementation::foo)] + fn check_foo_u64() { + let mut x: u64 = 7; + implementation::foo(&mut x); + } + + #[kani::proof_for_contract(implementation::func)] + fn check_func() { + let mut x = 7; + implementation::func(&mut x); + } + } +} diff --git a/tests/script-based-pre/cargo_list/src/standard_harnesses.rs b/tests/script-based-pre/cargo_list/src/standard_harnesses.rs new file mode 100644 index 000000000000..5df490461d0c --- /dev/null +++ b/tests/script-based-pre/cargo_list/src/standard_harnesses.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! Test that the cargo list command can find Kani attributes across multiple files. + +#[cfg(kani)] +mod example { + mod verify { + #[kani::proof] + fn check_modify() {} + + #[kani::proof] + fn check_new() {} + } +} diff --git a/tests/script-based-pre/kani_list/config.yml b/tests/script-based-pre/kani_list/config.yml new file mode 100644 index 000000000000..4eac6f79588c --- /dev/null +++ b/tests/script-based-pre/kani_list/config.yml @@ -0,0 +1,4 @@ +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT +script: list.sh +expected: list.expected diff --git a/tests/script-based-pre/kani_list/list.expected b/tests/script-based-pre/kani_list/list.expected new file mode 100644 index 000000000000..e2ed4506eb4e --- /dev/null +++ b/tests/script-based-pre/kani_list/list.expected @@ -0,0 +1,52 @@ +{ + "kani-version": + "file-version": "0.1", + "standard-harnesses": { + "src/lib.rs": [ + "example::verify::check_modify", + "example::verify::check_new" + ] + }, + "contract-harnesses": { + "src/lib.rs": [ + "example::verify::check_bar", + "example::verify::check_foo_u32", + "example::verify::check_foo_u64", + "example::verify::check_func" + ] + }, + "contracts": [ + { + "function": "example::implementation::bar", + "file": "src/lib.rs", + "harnesses": [ + "example::verify::check_bar" + ] + }, + { + "function": "example::implementation::foo", + "file": "src/lib.rs", + "harnesses": [ + "example::verify::check_foo_u32", + "example::verify::check_foo_u64" + ] + }, + { + "function": "example::implementation::func", + "file": "src/lib.rs", + "harnesses": [ + "example::verify::check_func" + ] + }, + { + "function": "example::prep::parse", + "file": "src/lib.rs", + "harnesses": [] + } + ], + "totals": { + "standard-harnesses": 2, + "contract-harnesses": 4, + "functions-under-contract": 4 + } +} diff --git a/tests/script-based-pre/kani_list/list.sh b/tests/script-based-pre/kani_list/list.sh new file mode 100755 index 000000000000..e7bb6f081044 --- /dev/null +++ b/tests/script-based-pre/kani_list/list.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Copyright Kani Contributors +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# Check that the JSON file produced by `kani list` is correct. +# Note that the list.expected file omits the value for "kani-version" +# to avoid having to update the test every time we bump versions. + +kani list -Z list -Z function-contracts src/lib.rs --format json +cat "kani-list.json" diff --git a/tests/script-based-pre/kani_list/src/lib.rs b/tests/script-based-pre/kani_list/src/lib.rs new file mode 100644 index 000000000000..69dbba5a9e0f --- /dev/null +++ b/tests/script-based-pre/kani_list/src/lib.rs @@ -0,0 +1,71 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +//! This test replicates the module structure from the running example in the list RFC. +//! It ensures that the list command across modules, and with modifies clauses, history expressions, and generic functions. + +mod example { + mod prep { + #[kani::requires(s.len() < 10)] + fn parse(s: &str) -> u32 { + s.parse().unwrap() + } + } + + pub mod implementation { + #[kani::requires(*x < 4)] + #[kani::requires(*x > 2)] + #[kani::ensures(|_| old(*x - 1) == *x)] + #[kani::ensures(|_| *x == 4)] + #[kani::modifies(x)] + pub fn bar(x: &mut u32) { + *x += 1; + } + + #[kani::requires(true)] + #[kani::ensures(|_| old(*x) == *x)] + pub fn foo(x: &mut T) -> T { + *x + } + + #[kani::requires(*x < 100)] + #[kani::modifies(x)] + pub fn func(x: &mut i32) { + *x *= 1; + } + } + + mod verify { + use crate::example::implementation; + + #[kani::proof_for_contract(implementation::bar)] + fn check_bar() { + let mut x = 7; + implementation::bar(&mut x); + } + + #[kani::proof_for_contract(implementation::foo)] + fn check_foo_u32() { + let mut x: u32 = 7; + implementation::foo(&mut x); + } + + #[kani::proof_for_contract(implementation::foo)] + fn check_foo_u64() { + let mut x: u64 = 7; + implementation::foo(&mut x); + } + + #[kani::proof_for_contract(implementation::func)] + fn check_func() { + let mut x = 7; + implementation::func(&mut x); + } + + #[kani::proof] + fn check_modify() {} + + #[kani::proof] + fn check_new() {} + } +} From 0182e99acdfff89f3f55b7324823d8d7c540a959 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 9 Oct 2024 12:21:59 -0700 Subject: [PATCH 120/159] Update the release notes to include new changes (#3588) Added the two changes that we would like to include in the release. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23d740f534a9..3835d15177dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,10 @@ This file was introduced starting Kani 0.23.0, so it only contains changes from ## [0.56.0] -### Breaking Changes +### Major/Breaking Changes * Remove obsolete linker options (`--mir-linker` and `--legacy-linker`) by @zhassan-aws in https://github.com/model-checking/kani/pull/3559 +* List Subcommand by @carolynzech in https://github.com/model-checking/kani/pull/3523 * Deprecate `kani::check` by @celinval in https://github.com/model-checking/kani/pull/3557 ### What's Changed @@ -20,6 +21,7 @@ This file was introduced starting Kani 0.23.0, so it only contains changes from * Add experimental API to generate arbitrary pointers by @celinval in https://github.com/model-checking/kani/pull/3538 * Running `verify-std` no longer changes Cargo files by @celinval in https://github.com/model-checking/kani/pull/3577 * Add an LLBC backend by @zhassan-aws in https://github.com/model-checking/kani/pull/3514 +* Fix the computation of the number of bytes of a pointer offset by @zhassan-aws in https://github.com/model-checking/kani/pull/3584 * Rust toolchain upgraded to nightly-2024-10-03 by @qinheping @tautschnig @celinval * CBMC upgraded to 6.3.1 by @tautschnig in https://github.com/model-checking/kani/pull/3537 From b29e74f3aa859655dc3c424c60501a1715e7f560 Mon Sep 17 00:00:00 2001 From: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> Date: Wed, 9 Oct 2024 21:51:28 -0700 Subject: [PATCH 121/159] Remove the overflow checks for wrapping_offset (#3589) This PR removes the overflow checks done for the `arith_offset` intrinsic, which is used in implementation of `ptr::wrapping_offset`. According to the documentation, this operation is always safe: https://doc.rust-lang.org/std/primitive.pointer.html#method.wrapping_offset https://doc.rust-lang.org/std/intrinsics/fn.arith_offset.html See https://github.com/model-checking/kani/issues/3582#issuecomment-2403453345 for the context. Resolves #3582 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --- .../codegen/intrinsic.rs | 49 +++---------------- .../codegen_cprover_gotoc/codegen/rvalue.rs | 7 +++ tests/expected/arith-offset-overflow/expected | 3 +- tests/expected/arith-offset-overflow/main.rs | 5 +- tests/expected/offset-overflow/expected | 2 +- tests/expected/offset-overflow/main.rs | 11 ++--- .../expected | 0 .../main.rs | 0 .../wrapping-offset-bytes-overflow/expected | 3 +- .../wrapping-offset-bytes-overflow/main.rs | 4 +- 10 files changed, 27 insertions(+), 57 deletions(-) rename tests/expected/{ptr-offset-overflow => ptr-offset-overflow-bytes}/expected (100%) rename tests/expected/{ptr-offset-overflow => ptr-offset-overflow-bytes}/main.rs (100%) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs index 79afe39fd89e..75fbfe4fc307 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs @@ -296,9 +296,7 @@ impl<'tcx> GotocCtx<'tcx> { Intrinsic::AddWithOverflow => { self.codegen_op_with_overflow(BinaryOperator::OverflowResultPlus, fargs, place, loc) } - Intrinsic::ArithOffset => { - self.codegen_offset(intrinsic_str, instance, fargs, place, loc) - } + Intrinsic::ArithOffset => self.codegen_arith_offset(fargs, place, loc), Intrinsic::AssertInhabited => { self.codegen_assert_intrinsic(instance, intrinsic_str, span) } @@ -1017,51 +1015,16 @@ impl<'tcx> GotocCtx<'tcx> { /// Computes the offset from a pointer. /// - /// Note that this function handles code generation for: - /// 1. The `offset` intrinsic. - /// - /// 2. The `arith_offset` intrinsic. + /// This function handles code generation for the `arith_offset` intrinsic. /// - /// - /// Note(std): We don't check that the starting or resulting pointer stay - /// within bounds of the object they point to. Doing so causes spurious - /// failures due to the usage of these intrinsics in the standard library. - /// See for more details. - /// Also, note that this isn't a requirement for `arith_offset`, but it's - /// one of the safety conditions specified for `offset`: - /// - fn codegen_offset( - &mut self, - intrinsic: &str, - instance: Instance, - mut fargs: Vec, - p: &Place, - loc: Location, - ) -> Stmt { + /// According to the documenation, the operation is always safe. + fn codegen_arith_offset(&mut self, mut fargs: Vec, p: &Place, loc: Location) -> Stmt { let src_ptr = fargs.remove(0); let offset = fargs.remove(0); - // Check that computing `offset` in bytes would not overflow - let args = instance_args(&instance); - let ty = args.0[0].expect_ty(); - let (offset_bytes, bytes_overflow_check) = - self.count_in_bytes(offset.clone(), *ty, Type::ssize_t(), intrinsic, loc); - - // Check that the computation would not overflow an `isize` - // These checks may allow a wrapping-around behavior in CBMC: - // https://github.com/model-checking/kani/issues/1150 - let dst_ptr_of = src_ptr.clone().cast_to(Type::ssize_t()).add_overflow(offset_bytes); - let overflow_check = self.codegen_assert_assume( - dst_ptr_of.overflowed.not(), - PropertyClass::ArithmeticOverflow, - "attempt to compute offset which would overflow", - loc, - ); - - // Re-compute `dst_ptr` with standard addition to avoid conversion + // Compute `dst_ptr` with standard addition to avoid conversion let dst_ptr = src_ptr.plus(offset); - let expr_place = self.codegen_expr_to_place_stable(p, dst_ptr, loc); - Stmt::block(vec![bytes_overflow_check, overflow_check, expr_place], loc) + self.codegen_expr_to_place_stable(p, dst_ptr, loc) } /// ptr_offset_from returns the offset between two pointers diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs index 14e012aac76b..e5396468a1f3 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs @@ -420,6 +420,13 @@ impl<'tcx> GotocCtx<'tcx> { // https://doc.rust-lang.org/std/primitive.pointer.html#method.offset // These checks may allow a wrapping-around behavior in CBMC: // https://github.com/model-checking/kani/issues/1150 + // Note(std): We don't check that the starting or resulting pointer stay + // within bounds of the object they point to. Doing so causes spurious + // failures due to the usage of these intrinsics in the standard library. + // See for more details. + // Note that this is one of the safety conditions for `offset`: + // + let overflow_res = ce1.clone().cast_to(Type::ssize_t()).add_overflow(offset_bytes); let overflow_check = self.codegen_assert_assume( overflow_res.overflowed.not(), diff --git a/tests/expected/arith-offset-overflow/expected b/tests/expected/arith-offset-overflow/expected index 72ded2ac24c1..34c886c358cb 100644 --- a/tests/expected/arith-offset-overflow/expected +++ b/tests/expected/arith-offset-overflow/expected @@ -1,2 +1 @@ -FAILURE\ -attempt to compute offset which would overflow \ No newline at end of file +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/arith-offset-overflow/main.rs b/tests/expected/arith-offset-overflow/main.rs index 0a5f3bce348b..b91a17edb69c 100644 --- a/tests/expected/arith-offset-overflow/main.rs +++ b/tests/expected/arith-offset-overflow/main.rs @@ -1,8 +1,9 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -// Checks that `arith_offset` fails if the offset computation would -// result in an arithmetic overflow +// Checks that `arith_offset` succeeds even if the offset computation would +// result in an arithmetic overflow as it uses wrapping: +// https://doc.rust-lang.org/std/intrinsics/fn.arith_offset.html #![feature(core_intrinsics)] use std::intrinsics::arith_offset; diff --git a/tests/expected/offset-overflow/expected b/tests/expected/offset-overflow/expected index bcf0242f9e9d..9613638bf8f9 100644 --- a/tests/expected/offset-overflow/expected +++ b/tests/expected/offset-overflow/expected @@ -1,2 +1,2 @@ FAILURE\ -attempt to compute number in bytes which would overflow +attempt to compute offset which would overflow diff --git a/tests/expected/offset-overflow/main.rs b/tests/expected/offset-overflow/main.rs index 43f294848012..fc9f36b9cc5d 100644 --- a/tests/expected/offset-overflow/main.rs +++ b/tests/expected/offset-overflow/main.rs @@ -8,13 +8,12 @@ use std::intrinsics::offset; #[kani::proof] fn test_offset_overflow() { - let a: [i32; 3] = [1, 2, 3]; - let ptr: *const i32 = a.as_ptr(); + let s: &str = "123"; + let ptr: *const u8 = s.as_ptr(); - // a value that when multiplied by the size of i32 (i.e. 4 bytes) - // would overflow `isize` - let count: isize = isize::MAX / 4 + 1; unsafe { - let _d = offset(ptr, count); + // This should fail because adding `isize::MAX` to `ptr` would overflow + // `isize` + let _d = offset(ptr, isize::MAX); } } diff --git a/tests/expected/ptr-offset-overflow/expected b/tests/expected/ptr-offset-overflow-bytes/expected similarity index 100% rename from tests/expected/ptr-offset-overflow/expected rename to tests/expected/ptr-offset-overflow-bytes/expected diff --git a/tests/expected/ptr-offset-overflow/main.rs b/tests/expected/ptr-offset-overflow-bytes/main.rs similarity index 100% rename from tests/expected/ptr-offset-overflow/main.rs rename to tests/expected/ptr-offset-overflow-bytes/main.rs diff --git a/tests/expected/wrapping-offset-bytes-overflow/expected b/tests/expected/wrapping-offset-bytes-overflow/expected index 449c4dae7bf9..34c886c358cb 100644 --- a/tests/expected/wrapping-offset-bytes-overflow/expected +++ b/tests/expected/wrapping-offset-bytes-overflow/expected @@ -1,2 +1 @@ -FAILURE\ -arith_offset: attempt to compute number in bytes which would overflow \ No newline at end of file +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/wrapping-offset-bytes-overflow/main.rs b/tests/expected/wrapping-offset-bytes-overflow/main.rs index edbe310d21cc..81a3fadecaf8 100644 --- a/tests/expected/wrapping-offset-bytes-overflow/main.rs +++ b/tests/expected/wrapping-offset-bytes-overflow/main.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // Check that an offset (computed with `wrapping_offset`) that overflows an -// `isize::MAX` triggers a verification failure. +// `isize::MAX` does NOT trigger a verification failure as the operation is +// always safe: +// https://doc.rust-lang.org/std/primitive.pointer.html#method.wrapping_offset use std::convert::TryInto; #[kani::proof] From 53d9a5267709b78684e1f51744f786f3bafeb414 Mon Sep 17 00:00:00 2001 From: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:03:24 -0400 Subject: [PATCH 122/159] `kani-cov`: A coverage tool for Kani (#3121) This PR introduces `kani-cov`, a coverage-oriented tool for Kani. This new tool will help users aggregate raw coverage results produced by Kani and generate human-readable coverage summaries and reports. `kani-cov` allows using three different subcommands: `merge`, `summary` and `report`. In the following, we will show how each one of these commands can be used. ### The `merge` subcommand The `merge` subcommand takes raw coverage results from Kani (AKA "kaniraw" files) and generates aggregated coverage results which are stored into another file (AKA a "kanicov" file). This new file can then be used for generating summaries or reports. For example, if we assume this Rust code is in `main.rs`: ```rust 1 fn _other_function() { 2 println!("Hello, world!"); 3 } 4 5 fn test_cov(val: u32) -> bool { 6 if val < 3 || val == 42 { 7 true 8 } else { 9 false 10 } 11 } 12 13 #[cfg_attr(kani, kani::proof)] 14 fn main() { 15 let test1 = test_cov(1); 16 let test2 = test_cov(2); 17 assert!(test1); 18 assert!(test2); 19 } ``` and we execute Kani with coverage enabled ``` kani main.rs --coverage -Zsource-coverage ``` the raw coverage results will be saved to a folder ``` [info] Coverage results saved to /home/ubuntu/coverage-experiments/src/kanicov_2024-09-23_23-49 ``` we now can aggregate those results with the `merge` subcommand ``` kani-cov merge kanicov_2024-09-23_23-49/*kaniraw.json ``` which by default produces a `default_kanicov.json` file. We can also use the `--output ` option to change the name of the file. The type of file we just produced is known as the "profile" or the "kanicov" file, and contains aggregated coverage results which may have been originated in multiple verification sessions. This file is a required input for the other two subcommands. ### The `summary` subcommand Now that we have both the "kanicov" file and the "kanimap" file, we are ready to produce coverage metrics with the `summary` subcommand: ``` kani-cov summary kanicov_2024-09-23_23-49/kanicov_2024-09-23_23-49_kanimap.json --profile default_kanicov.json ``` This outputs a table including coverage metrics for functions, lines and regions for each file referenced in the coverage mappings: ``` | Filename | Function (%) | Line (%) | Region (%) | | --------------------------------------------- | ------------ | ------------ | ----------- | | /home/ubuntu/coverage-experiments/src/main.rs | 2/3 (66.67) | 8/12 (66.67) | 4/6 (66.67) | ``` Since the default format is markdown, the table can also be rendered as in this PR description: | Filename | Function (%) | Line (%) | Region (%) | | --------------------------------------------- | ------------ | ------------ | ----------- | | /home/ubuntu/coverage-experiments/src/main.rs | 2/3 (66.67) | 8/12 (66.67) | 4/6 (66.67) | At present, no other formats are available. But the JSON and CSV formats would be really nice and easy to have. ### The `report` subcommand Coverage reports go a little further and allow users to visualize coverage information on the source code. The `report` requires the same input files as the `summary` subcommand: ``` kani-cov report kanicov_2024-09-23_23-49/kanicov_2024-09-23_23-49_kanimap.json --profile default_kanicov.json ``` When calling this command from a terminal, it will render like this: ![Screenshot 2024-10-04 at 5 49 51 PM](https://github.com/user-attachments/assets/65c12d62-4dd8-40e6-bfe9-8fc0e70127d2) However, if you're redirecting the output to a file, it will back off to another text-based mode which simply uses ` ``` ` and ` ''' ` to represent opening and closing escapes for the coverage regions. This is just another format called `escapes` which can be explicitly requested as ``` kani-cov report kanicov_2024-09-23_23-49/kanicov_2024-09-23_23-49_kanimap.json --profile default_kanicov.json --format=escapes ``` Similar to the `summary` command, this will iterate over the files in the coverage mappings, compute coverage information and highlight any uncovered regions (except #3543). ### Testing This PR also changes `compiletest` to automatically call the `merge` and `report` subcommands on the `coverage` test suite. The tests have been blessed using the `--fix-expected` option and this Python3 script that removes the first line (path to the file) and adds `\` after each line so an exact match is expected. ``` import os def process_files(directory): for filename in os.listdir(directory): if filename == "expected": file_path = os.path.join(directory, filename) with open(file_path, "r") as file: lines = file.readlines() # Remove the first line lines = lines[1:] # Add the character `\` after every line processed_lines = [] for i, line in enumerate(lines): processed_lines.append(line.replace("\n", "\\\n")) # Write the processed lines back to the file with open(file_path, "w") as file: file.writelines(processed_lines) ``` ### Callouts / Known issues This PR only introduces `kani-cov`. It doesn't plug `kani-cov` into `kani-driver` so it's automatically run when using the `--coverage` option, nor it prepares the `kani-cov` binary for distribution. This is better to address later. This PR also spawns a few issues: - #3543 forces us to filter out certain coverage results to avoid issues with the report command. It is important that we investigate where these coverage results come from and their semantics as this represents a soundness issue. - #3541 is to avoid duplicating certain data structures that currently live in `kani-driver`. We should probably have a different crate for these structures and import them from `kani-driver` and `kani-cov`, but this is more of a distribution problem that doesn't need to be tackled here. - #3542 requires us to match filenames when extracting function coverage results. It's not a big deal, but it'll likely improve the tool's robustness in the long term. 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 | 42 +++ Cargo.toml | 1 + scripts/kani-regression.sh | 3 + tests/coverage/abort/expected | 34 +- tests/coverage/assert/expected | 27 +- tests/coverage/assert_eq/expected | 19 +- tests/coverage/assert_ne/expected | 23 +- tests/coverage/break/expected | 32 +- tests/coverage/compare/expected | 27 +- tests/coverage/contradiction/expected | 23 +- tests/coverage/debug-assert/expected | 25 +- tests/coverage/div-zero/expected | 20 +- tests/coverage/early-return/expected | 31 +- tests/coverage/if-statement-multi/expected | 37 ++- tests/coverage/if-statement/expected | 34 +- .../assert_uncovered_end/expected | 23 +- .../known_issues/assume_assert/expected | 19 +- .../known_issues/out-of-bounds/expected | 22 +- tests/coverage/known_issues/variant/expected | 46 ++- tests/coverage/multiple-harnesses/expected | 81 ++--- tests/coverage/overflow-failure/expected | 24 +- .../coverage/overflow-full-coverage/expected | 26 +- tests/coverage/while-loop-break/expected | 36 ++- tools/compiletest/src/runtest.rs | 84 ++++- tools/kani-cov/Cargo.toml | 21 ++ tools/kani-cov/src/args.rs | 140 ++++++++ tools/kani-cov/src/coverage.rs | 210 ++++++++++++ tools/kani-cov/src/main.rs | 31 ++ tools/kani-cov/src/merge.rs | 145 +++++++++ tools/kani-cov/src/report.rs | 267 ++++++++++++++++ tools/kani-cov/src/summary.rs | 300 ++++++++++++++++++ 31 files changed, 1621 insertions(+), 232 deletions(-) create mode 100644 tools/kani-cov/Cargo.toml create mode 100644 tools/kani-cov/src/args.rs create mode 100644 tools/kani-cov/src/coverage.rs create mode 100644 tools/kani-cov/src/main.rs create mode 100644 tools/kani-cov/src/merge.rs create mode 100644 tools/kani-cov/src/report.rs create mode 100644 tools/kani-cov/src/summary.rs diff --git a/Cargo.lock b/Cargo.lock index 580cdf70946a..c4709fbe446e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -769,6 +769,20 @@ dependencies = [ "tracing-tree 0.4.0", ] +[[package]] +name = "kani-cov" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "console", + "serde", + "serde_derive", + "serde_json", + "tree-sitter", + "tree-sitter-rust", +] + [[package]] name = "kani-driver" version = "0.56.0" @@ -1811,6 +1825,34 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tree-sitter" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f4cd3642c47a85052a887d86704f4eac272969f61b686bdd3f772122aabaff" +dependencies = [ + "cc", + "regex", + "regex-syntax 0.8.5", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57" + +[[package]] +name = "tree-sitter-rust" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "277690f420bf90741dea984f3da038ace46c4fe6047cba57a66822226cde1c93" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "typed-arena" version = "2.0.2" diff --git a/Cargo.toml b/Cargo.toml index 42c6f2c722b6..3c61638025e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ members = [ "library/std", "tools/compiletest", "tools/build-kani", + "tools/kani-cov", "tools/scanner", "kani-driver", "kani-compiler", diff --git a/scripts/kani-regression.sh b/scripts/kani-regression.sh index be2548235ce6..728129d784b6 100755 --- a/scripts/kani-regression.sh +++ b/scripts/kani-regression.sh @@ -70,6 +70,9 @@ echo "--- Compiletest configuration" cargo run -p compiletest --quiet -- --suite kani --mode cargo-kani --dry-run --verbose echo "-----------------------------" +# Build `kani-cov` +cargo build -p kani-cov + # Extract testing suite information and run compiletest for testp in "${TESTS[@]}"; do testl=($testp) diff --git a/tests/coverage/abort/expected b/tests/coverage/abort/expected index 91142ebf94fc..e9c9727a6f03 100644 --- a/tests/coverage/abort/expected +++ b/tests/coverage/abort/expected @@ -1,13 +1,21 @@ -Source-based code coverage results: - -main.rs (main)\ - * 9:1 - 9:11 COVERED\ - * 10:9 - 10:10 COVERED\ - * 10:14 - 10:18 COVERED\ - * 13:13 - 13:29 COVERED\ - * 14:10 - 15:18 COVERED\ - * 17:13 - 17:29 UNCOVERED\ - * 18:10 - 18:11 COVERED\ - * 20:5 - 20:12 UNCOVERED\ - * 20:20 - 20:41 UNCOVERED\ - * 21:1 - 21:2 UNCOVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Test that the abort() function is respected and nothing beyond it will execute.\ + 5| | \ + 6| | use std::process;\ + 7| | \ + 8| | #[kani::proof]\ + 9| 1| fn main() {\ + 10| 1| for i in 0..4 {\ + 11| | if i == 1 {\ + 12| | // This comes first and it should be reachable.\ + 13| 1| process::abort();\ + 14| 1| }\ + 15| 1| if i == 2 {\ + 16| | // This should never happen.\ + 17| 0| ```process::exit(1)''';\ + 18| 1| } \ + 19| | }\ + 20| 0| ```assert!'''(false, ```"This is unreachable"''');\ + 21| | }\ diff --git a/tests/coverage/assert/expected b/tests/coverage/assert/expected index 46bb664cf6f5..c2f3ffbe733e 100644 --- a/tests/coverage/assert/expected +++ b/tests/coverage/assert/expected @@ -1,9 +1,18 @@ -Source-based code coverage results: - -test.rs (foo) - * 5:1 - 7:13 COVERED\ - * 9:9 - 10:17 COVERED\ - * 10:18 - 13:10 UNCOVERED\ - * 13:10 - 13:11 UNCOVERED\ - * 14:12 - 17:6 COVERED\ - * 18:1 - 18:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | #[kani::proof]\ + 5| 1| fn foo() {\ + 6| 1| let x: i32 = kani::any();\ + 7| 1| if x > 5 {\ + 8| | // fails\ + 9| 1| assert!(x < 4);\ + 10| 1| if x < 3 ```{'''\ + 11| 0| ``` // unreachable'''\ + 12| 0| ``` assert!(x == 2);'''\ + 13| 0| ``` }'''``` '''\ + 14| 1| } else {\ + 15| 1| // passes\ + 16| 1| assert!(x <= 5);\ + 17| 1| }\ + 18| | }\ diff --git a/tests/coverage/assert_eq/expected b/tests/coverage/assert_eq/expected index c2eee7adf803..0cc1e01fbca9 100644 --- a/tests/coverage/assert_eq/expected +++ b/tests/coverage/assert_eq/expected @@ -1,8 +1,11 @@ -Source-based code coverage results: - -test.rs (main)\ - * 5:1 - 6:29 COVERED\ - * 7:25 - 7:27 COVERED\ - * 7:37 - 7:39 COVERED\ - * 8:15 - 10:6 UNCOVERED\ - * 10:6 - 10:7 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | #[kani::proof]\ + 5| 1| fn main() {\ + 6| 1| let x: i32 = kani::any();\ + 7| 1| let y = if x > 10 { 15 } else { 33 };\ + 8| 0| if y > 50 ```{'''\ + 9| 0| ``` assert_eq!(y, 55);'''\ + 10| 1| ``` }'''\ + 11| | }\ diff --git a/tests/coverage/assert_ne/expected b/tests/coverage/assert_ne/expected index c9b727da0f82..73055a14af10 100644 --- a/tests/coverage/assert_ne/expected +++ b/tests/coverage/assert_ne/expected @@ -1,9 +1,14 @@ -Source-based code coverage results: - -test.rs (main)\ - * 5:1 - 7:13 COVERED\ - * 8:13 - 10:18 COVERED\ - * 10:19 - 12:10 UNCOVERED\ - * 12:10 - 12:11 COVERED\ - * 13:6 - 13:7 COVERED\ - * 14:1 - 14:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | #[kani::proof]\ + 5| 1| fn main() {\ + 6| 1| let x: u32 = kani::any();\ + 7| 1| if x > 0 {\ + 8| 1| let y = x / 2;\ + 9| 1| // y is strictly less than x\ + 10| 1| if y == x ```{'''\ + 11| 0| ``` assert_ne!(y, 1);'''\ + 12| 1| ``` }'''\ + 13| 1| }\ + 14| | }\ diff --git a/tests/coverage/break/expected b/tests/coverage/break/expected index 739735cdf1a2..e1570030f6ae 100644 --- a/tests/coverage/break/expected +++ b/tests/coverage/break/expected @@ -1,13 +1,19 @@ -Source-based code coverage results: - -main.rs (find_positive)\ - * 4:1 - 4:47 COVERED\ - * 5:10 - 5:13 COVERED\ - * 5:17 - 5:21 COVERED\ - * 7:20 - 7:29 COVERED\ - * 8:10 - 8:11 COVERED\ - * 11:5 - 11:9 UNCOVERED\ - * 12:1 - 12:2 COVERED - -main.rs (main)\ - * 15:1 - 19:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn find_positive(nums: &[i32]) -> Option {\ + 5| 1| for &num in nums {\ + 6| | if num > 0 {\ + 7| 1| return Some(num);\ + 8| 1| } \ + 9| | }\ + 10| | // `None` is unreachable because there is at least one positive number.\ + 11| 0| ```None'''\ + 12| | }\ + 13| | \ + 14| | #[kani::proof]\ + 15| 1| fn main() {\ + 16| 1| let numbers = [-3, -1, 0, 2, 4];\ + 17| 1| let result = find_positive(&numbers);\ + 18| 1| assert_eq!(result, Some(2));\ + 19| | }\ diff --git a/tests/coverage/compare/expected b/tests/coverage/compare/expected index 153dbfa37d80..3a59376e8ce3 100644 --- a/tests/coverage/compare/expected +++ b/tests/coverage/compare/expected @@ -1,11 +1,16 @@ -Source-based code coverage results: - -main.rs (compare)\ - * 4:1 - 6:14 COVERED\ - * 6:17 - 6:18 COVERED\ - * 6:28 - 6:29 UNCOVERED - -main.rs (main)\ - * 10:1 - 13:14 COVERED\ - * 13:15 - 15:6 COVERED\ - * 15:6 - 15:7 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn compare(x: u16, y: u16) -> u16 {\ + 5| 1| // The case where `x < y` isn't possible so its region is `UNCOVERED`\ + 6| 1| if x >= y { 1 } else { ```0''' }\ + 7| | }\ + 8| | \ + 9| | #[kani::proof]\ + 10| 1| fn main() {\ + 11| 1| let x: u16 = kani::any();\ + 12| 1| let y: u16 = kani::any();\ + 13| 1| if x >= y {\ + 14| 1| compare(x, y);\ + 15| 1| } \ + 16| | }\ diff --git a/tests/coverage/contradiction/expected b/tests/coverage/contradiction/expected index db3676d7da15..7befb09a1f9e 100644 --- a/tests/coverage/contradiction/expected +++ b/tests/coverage/contradiction/expected @@ -1,9 +1,14 @@ -Source-based code coverage results: - -main.rs (contradiction)\ - * 4:1 - 7:13 COVERED\ - * 8:12 - 8:17 COVERED\ - * 8:18 - 10:10 UNCOVERED\ - * 10:10 - 10:11 COVERED\ - * 11:12 - 13:6 COVERED\ - * 14:1 - 14:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | #[kani::proof]\ + 4| 1| fn contradiction() {\ + 5| 1| let x: u8 = kani::any();\ + 6| 1| let mut y: u8 = 0;\ + 7| 1| if x > 5 {\ + 8| 1| if x < 2 ```{'''\ + 9| 0| ``` y = x;'''\ + 10| 1| ``` }'''\ + 11| 1| } else {\ + 12| 1| assert!(x < 10);\ + 13| 1| }\ + 14| | }\ diff --git a/tests/coverage/debug-assert/expected b/tests/coverage/debug-assert/expected index fbe57690d347..82ad7c992ca3 100644 --- a/tests/coverage/debug-assert/expected +++ b/tests/coverage/debug-assert/expected @@ -1,10 +1,15 @@ -Source-based code coverage results: - -main.rs (main)\ - * 10:1 - 10:11 COVERED\ - * 11:9 - 11:10 COVERED\ - * 11:14 - 11:18 COVERED\ - * 12:30 - 12:71 UNCOVERED\ - * 13:9 - 13:23 UNCOVERED\ - * 13:25 - 13:53 UNCOVERED\ - * 15:1 - 15:2 UNCOVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! This test checks that the regions after the `debug_assert` macro are\ + 5| | //! `UNCOVERED`. In fact, for this example, the region associated to `"This\ + 6| | //! should fail and stop the execution"` is also `UNCOVERED` because the macro\ + 7| | //! calls span two regions each.\ + 8| | \ + 9| | #[kani::proof]\ + 10| 1| fn main() {\ + 11| 1| for i in 0..4 {\ + 12| 0| debug_assert!(i > 0, ```"This should fail and stop the execution"''');\ + 13| 0| ```assert!(i == 0''', ```"This should be unreachable"''');\ + 14| | }\ + 15| | }\ diff --git a/tests/coverage/div-zero/expected b/tests/coverage/div-zero/expected index f351005f4f22..503fa9a22a05 100644 --- a/tests/coverage/div-zero/expected +++ b/tests/coverage/div-zero/expected @@ -1,9 +1,11 @@ -Source-based code coverage results: - -test.rs (div)\ - * 4:1 - 5:14 COVERED\ - * 5:17 - 5:22 COVERED\ - * 5:32 - 5:33 UNCOVERED - -test.rs (main)\ - * 9:1 - 11:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn div(x: u16, y: u16) -> u16 {\ + 5| 1| if y != 0 { x / y } else { ```0''' }\ + 6| | }\ + 7| | \ + 8| | #[kani::proof]\ + 9| 1| fn main() {\ + 10| 1| div(11, 3);\ + 11| | }\ diff --git a/tests/coverage/early-return/expected b/tests/coverage/early-return/expected index 53cde3abeaf8..efdd71ee91f0 100644 --- a/tests/coverage/early-return/expected +++ b/tests/coverage/early-return/expected @@ -1,12 +1,19 @@ -Source-based code coverage results: - -main.rs (find_index)\ - * 4:1 - 4:59 COVERED\ - * 5:10 - 5:21 COVERED\ - * 7:20 - 7:31 COVERED\ - * 8:10 - 8:11 COVERED\ - * 10:5 - 10:9 UNCOVERED\ - * 11:1 - 11:2 COVERED - -main.rs (main)\ - * 14:1 - 19:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn find_index(nums: &[i32], target: i32) -> Option {\ + 5| 1| for (index, &num) in nums.iter().enumerate() {\ + 6| | if num == target {\ + 7| 1| return Some(index);\ + 8| 1| } \ + 9| | }\ + 10| 0| ```None'''\ + 11| | }\ + 12| | \ + 13| | #[kani::proof]\ + 14| 1| fn main() {\ + 15| 1| let numbers = [10, 20, 30, 40, 50];\ + 16| 1| let target = 30;\ + 17| 1| let result = find_index(&numbers, target);\ + 18| 1| assert_eq!(result, Some(2));\ + 19| | }\ diff --git a/tests/coverage/if-statement-multi/expected b/tests/coverage/if-statement-multi/expected index 4e8382d10a6f..87b3b80f556d 100644 --- a/tests/coverage/if-statement-multi/expected +++ b/tests/coverage/if-statement-multi/expected @@ -1,11 +1,26 @@ -Source-based code coverage results: - -test.rs (main)\ - * 21:1 - 26:2 COVERED - -test.rs (test_cov)\ - * 16:1 - 17:15 COVERED\ - * 17:19 - 17:28 UNCOVERED\ - * 17:31 - 17:35 COVERED\ - * 17:45 - 17:50 UNCOVERED\ - * 18:1 - 18:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | // kani-flags: --coverage -Zsource-coverage\ + 4| | \ + 5| | //! Checks that we are covering all regions except\ + 6| | //! * the `val == 42` condition\ + 7| | //! * the `false` branch\ + 8| | //!\ + 9| | //! No coverage information is shown for `_other_function` because it's sliced\ + 10| | //! off: \ + 11| | \ + 12| 0| ```fn _other_function() {'''\ + 13| 0| ``` println!("Hello, world!");'''\ + 14| 0| ```}'''\ + 15| | \ + 16| 1| fn test_cov(val: u32) -> bool {\ + 17| 1| if val < 3 || ```val == 42''' { true } else { ```false''' }\ + 18| | }\ + 19| | \ + 20| | #[cfg_attr(kani, kani::proof)]\ + 21| 1| fn main() {\ + 22| 1| let test1 = test_cov(1);\ + 23| 1| let test2 = test_cov(2);\ + 24| 1| assert!(test1);\ + 25| 1| assert!(test2);\ + 26| | }\ diff --git a/tests/coverage/if-statement/expected b/tests/coverage/if-statement/expected index b85b95de9c84..85efc027aa04 100644 --- a/tests/coverage/if-statement/expected +++ b/tests/coverage/if-statement/expected @@ -1,14 +1,20 @@ -Source-based code coverage results: - -main.rs (check_number)\ - * 4:1 - 5:15 COVERED\ - * 7:12 - 7:24 COVERED\ - * 7:27 - 7:46 UNCOVERED\ - * 7:56 - 7:74 COVERED\ - * 8:15 - 8:22 UNCOVERED\ - * 9:9 - 9:19 UNCOVERED\ - * 11:9 - 11:15 UNCOVERED\ - * 13:1 - 13:2 COVERED - -main.rs (main)\ - * 16:1 - 20:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn check_number(num: i32) -> &'static str {\ + 5| 1| if num > 0 {\ + 6| | // The next line is partially covered\ + 7| 1| if num % 2 == 0 { ```"Positive and Even"''' } else { "Positive and Odd" }\ + 8| 0| } else if ```num < 0''' {\ + 9| 0| ```"Negative"'''\ + 10| | } else {\ + 11| 0| ```"Zero"'''\ + 12| | }\ + 13| | }\ + 14| | \ + 15| | #[kani::proof]\ + 16| 1| fn main() {\ + 17| 1| let number = 7;\ + 18| 1| let result = check_number(number);\ + 19| 1| assert_eq!(result, "Positive and Odd");\ + 20| | }\ diff --git a/tests/coverage/known_issues/assert_uncovered_end/expected b/tests/coverage/known_issues/assert_uncovered_end/expected index ceba065ce424..2f6fca5e4aae 100644 --- a/tests/coverage/known_issues/assert_uncovered_end/expected +++ b/tests/coverage/known_issues/assert_uncovered_end/expected @@ -1,9 +1,14 @@ -Source-based code coverage results: - -test.rs (check_assert)\ - * 9:1 - 10:34 COVERED\ - * 11:14 - 13:6 COVERED\ - * 13:6 - 13:7 UNCOVERED - -test.rs (check_assert::{closure#0})\ - * 10:40 - 10:49 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks that `check_assert` is fully covered. At present, the coverage for\ + 5| | //! this test reports an uncovered single-column region at the end of the `if`\ + 6| | //! statement: \ + 7| | \ + 8| | #[kani::proof]\ + 9| 1| fn check_assert() {\ + 10| 1| let x: u32 = kani::any_where(|val| *val == 5);\ + 11| 1| if x > 3 {\ + 12| 1| assert!(x > 4);\ + 13| 1| }``` '''\ + 14| | }\ diff --git a/tests/coverage/known_issues/assume_assert/expected b/tests/coverage/known_issues/assume_assert/expected index 55f3235d7d24..a65a36099d05 100644 --- a/tests/coverage/known_issues/assume_assert/expected +++ b/tests/coverage/known_issues/assume_assert/expected @@ -1,4 +1,15 @@ -Source-based code coverage results: - -main.rs (check_assume_assert)\ - * 11:1 - 15:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! This test should check that the region after `kani::assume(false)` is\ + 5| | //! `UNCOVERED`. However, due to a technical limitation in `rustc`'s coverage\ + 6| | //! instrumentation, only one `COVERED` region is reported for the whole\ + 7| | //! function. More details in\ + 8| | //! .\ + 9| | \ + 10| | #[kani::proof]\ + 11| 1| fn check_assume_assert() {\ + 12| 1| let a: u8 = kani::any();\ + 13| 1| kani::assume(false);\ + 14| 1| assert!(a < 5);\ + 15| | }\ diff --git a/tests/coverage/known_issues/out-of-bounds/expected b/tests/coverage/known_issues/out-of-bounds/expected index 8ab9e2e15627..3924d21de456 100644 --- a/tests/coverage/known_issues/out-of-bounds/expected +++ b/tests/coverage/known_issues/out-of-bounds/expected @@ -1,7 +1,15 @@ -Source-based code coverage results: - -test.rs (get)\ - * 8:1 - 10:2 COVERED - -test.rs (main)\ - * 13:1 - 15:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! This test should check that the return in `get` is `UNCOVERED`. However, the\ + 5| | //! coverage results currently report that the whole function is `COVERED`,\ + 6| | //! likely due to \ + 7| | \ + 8| 1| fn get(s: &[i16], index: usize) -> i16 {\ + 9| 1| s[index]\ + 10| | }\ + 11| | \ + 12| | #[kani::proof]\ + 13| 1| fn main() {\ + 14| 1| get(&[7, -83, 19], 15);\ + 15| | }\ diff --git a/tests/coverage/known_issues/variant/expected b/tests/coverage/known_issues/variant/expected index 13383ed3bab0..6445c038f060 100644 --- a/tests/coverage/known_issues/variant/expected +++ b/tests/coverage/known_issues/variant/expected @@ -1,14 +1,32 @@ -Source-based code coverage results: - -main.rs (main)\ - * 29:1 - 32:2 COVERED - -main.rs (print_direction)\ - * 16:1 - 16:36 COVERED\ - * 18:11 - 18:14 UNCOVERED\ - * 19:26 - 19:47 UNCOVERED\ - * 20:28 - 20:51 UNCOVERED\ - * 21:28 - 21:51 COVERED\ - * 22:34 - 22:63 UNCOVERED\ - * 24:14 - 24:45 UNCOVERED\ - * 26:1 - 26:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks coverage results in an example with a `match` statement matching on\ + 5| | //! all enum variants. Currently, it does not yield the expected results because\ + 6| | //! it reports the `dir` in the match statement as `UNCOVERED`:\ + 7| | //! \ + 8| | \ + 9| | enum Direction {\ + 10| | Up,\ + 11| | Down,\ + 12| | Left,\ + 13| | Right,\ + 14| | }\ + 15| | \ + 16| 1| fn print_direction(dir: Direction) {\ + 17| | // For some reason, `dir`'s span is reported as `UNCOVERED` too\ + 18| 0| match ```dir''' {\ + 19| 0| Direction::Up => ```println!("Going up!")''',\ + 20| 0| Direction::Down => ```println!("Going down!")''',\ + 21| 1| Direction::Left => println!("Going left!"),\ + 22| 0| Direction::Right if 1 == ```1 => println!("Going right!")''',\ + 23| | // This part is unreachable since we cover all variants in the match.\ + 24| 0| _ => ```println!("Not going anywhere!")''',\ + 25| | }\ + 26| | }\ + 27| | \ + 28| | #[kani::proof]\ + 29| 1| fn main() {\ + 30| 1| let direction = Direction::Left;\ + 31| 1| print_direction(direction);\ + 32| | }\ diff --git a/tests/coverage/multiple-harnesses/expected b/tests/coverage/multiple-harnesses/expected index b5362147fed1..6ae834a3bccb 100644 --- a/tests/coverage/multiple-harnesses/expected +++ b/tests/coverage/multiple-harnesses/expected @@ -1,37 +1,44 @@ -Source-based code coverage results: - -main.rs (estimate_size)\ - * 4:1 - 7:15 COVERED\ - * 8:12 - 8:19 COVERED\ - * 9:20 - 9:21 COVERED\ - * 11:20 - 11:21 COVERED\ - * 13:15 - 13:23 COVERED\ - * 14:12 - 14:20 COVERED\ - * 15:20 - 15:21 COVERED\ - * 17:20 - 17:21 COVERED\ - * 20:12 - 20:20 COVERED\ - * 21:20 - 21:21 COVERED\ - * 23:20 - 23:21 COVERED\ - * 26:1 - 26:2 COVERED - -main.rs (fully_covered)\ - * 39:1 - 44:2 COVERED - -Source-based code coverage results: - -main.rs (estimate_size)\ - * 4:1 - 7:15 COVERED\ - * 8:12 - 8:19 COVERED\ - * 9:20 - 9:21 COVERED\ - * 11:20 - 11:21 COVERED\ - * 13:15 - 13:23 COVERED\ - * 14:12 - 14:20 COVERED\ - * 15:20 - 15:21 COVERED\ - * 17:20 - 17:21 COVERED\ - * 20:12 - 20:20 COVERED\ - * 21:20 - 21:21 COVERED\ - * 23:20 - 23:21 UNCOVERED\ - * 26:1 - 26:2 COVERED - -main.rs (mostly_covered)\ - * 30:1 - 35:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 2| fn estimate_size(x: u32) -> u32 {\ + 5| 2| assert!(x < 4096);\ + 6| 2| \ + 7| 2| if x < 256 {\ + 8| 2| if x < 128 {\ + 9| 2| return 1;\ + 10| | } else {\ + 11| 2| return 3;\ + 12| | }\ + 13| 2| } else if x < 1024 {\ + 14| 2| if x > 1022 {\ + 15| 2| return 4;\ + 16| | } else {\ + 17| 2| return 5;\ + 18| | }\ + 19| | } else {\ + 20| 2| if x < 2048 {\ + 21| 2| return 7;\ + 22| | } else {\ + 23| 1| return 9;\ + 24| | }\ + 25| | }\ + 26| | }\ + 27| | \ + 28| | #[cfg(kani)]\ + 29| | #[kani::proof]\ + 30| 1| fn mostly_covered() {\ + 31| 1| let x: u32 = kani::any();\ + 32| 1| kani::assume(x < 2048);\ + 33| 1| let y = estimate_size(x);\ + 34| 1| assert!(y < 10);\ + 35| | }\ + 36| | \ + 37| | #[cfg(kani)]\ + 38| | #[kani::proof]\ + 39| 1| fn fully_covered() {\ + 40| 1| let x: u32 = kani::any();\ + 41| 1| kani::assume(x < 4096);\ + 42| 1| let y = estimate_size(x);\ + 43| 1| assert!(y < 10);\ + 44| | }\ diff --git a/tests/coverage/overflow-failure/expected b/tests/coverage/overflow-failure/expected index db4f29d51336..acba327e1d7a 100644 --- a/tests/coverage/overflow-failure/expected +++ b/tests/coverage/overflow-failure/expected @@ -1,9 +1,15 @@ -Source-based code coverage results: - -test.rs (cond_reduce)\ - * 7:1 - 8:18 COVERED\ - * 8:21 - 8:27 COVERED\ - * 8:37 - 8:38 UNCOVERED - -test.rs (main)\ - * 12:1 - 15:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks that Kani reports the correct coverage results in the case of an\ + 5| | //! arithmetic overflow failure (caused by the second call to `cond_reduce`).\ + 6| | \ + 7| 1| fn cond_reduce(thresh: u32, x: u32) -> u32 {\ + 8| 1| if x > thresh { x - 50 } else { ```x''' }\ + 9| | }\ + 10| | \ + 11| | #[kani::proof]\ + 12| 1| fn main() {\ + 13| 1| cond_reduce(60, 70);\ + 14| 1| cond_reduce(40, 42);\ + 15| | }\ diff --git a/tests/coverage/overflow-full-coverage/expected b/tests/coverage/overflow-full-coverage/expected index 4d17761505eb..4daa50db8510 100644 --- a/tests/coverage/overflow-full-coverage/expected +++ b/tests/coverage/overflow-full-coverage/expected @@ -1,9 +1,17 @@ -Source-based code coverage results: - -test.rs (main)\ - * 12:1 - 17:2 COVERED - -test.rs (reduce)\ - * 7:1 - 8:16 COVERED\ - * 8:19 - 8:27 COVERED\ - * 8:37 - 8:38 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks that Kani reports all regions as `COVERED` as expected in this case\ + 5| | //! where arithmetic overflow failures are prevented.\ + 6| | \ + 7| 1| fn reduce(x: u32) -> u32 {\ + 8| 1| if x > 1000 { x - 1000 } else { x }\ + 9| | }\ + 10| | \ + 11| | #[kani::proof]\ + 12| 1| fn main() {\ + 13| 1| reduce(7);\ + 14| 1| reduce(33);\ + 15| 1| reduce(728);\ + 16| 1| reduce(1079);\ + 17| | }\ diff --git a/tests/coverage/while-loop-break/expected b/tests/coverage/while-loop-break/expected index 34afef9ee12c..4e52fc5804e7 100644 --- a/tests/coverage/while-loop-break/expected +++ b/tests/coverage/while-loop-break/expected @@ -1,13 +1,23 @@ -Source-based code coverage results: - -main.rs (find_first_negative)\ - * 7:1 - 8:22 COVERED\ - * 9:11 - 9:29 COVERED\ - * 10:12 - 10:27 COVERED\ - * 11:20 - 11:37 COVERED\ - * 12:10 - 13:19 COVERED\ - * 15:5 - 15:9 UNCOVERED\ - * 16:1 - 16:2 COVERED - -main.rs (main)\ - * 19:1 - 23:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks coverage results in an example with a `while` loop that returns before\ + 5| | //! running the last iteration.\ + 6| | \ + 7| 1| fn find_first_negative(nums: &[i32]) -> Option {\ + 8| 1| let mut index = 0;\ + 9| 1| while index < nums.len() {\ + 10| 1| if nums[index] < 0 {\ + 11| 1| return Some(nums[index]);\ + 12| 1| }\ + 13| 1| index += 1;\ + 14| | }\ + 15| 0| ```None'''\ + 16| | }\ + 17| | \ + 18| | #[kani::proof]\ + 19| 1| fn main() {\ + 20| 1| let numbers = [1, 2, -3, 4, -5];\ + 21| 1| let result = find_first_negative(&numbers);\ + 22| 1| assert_eq!(result, Some(-3));\ + 23| | }\ diff --git a/tools/compiletest/src/runtest.rs b/tools/compiletest/src/runtest.rs index 6e6425d086c3..1622dc104919 100644 --- a/tools/compiletest/src/runtest.rs +++ b/tools/compiletest/src/runtest.rs @@ -441,8 +441,11 @@ impl<'test> TestCx<'test> { /// Runs Kani in coverage mode on the test file specified by `self.testpaths.file`. fn run_expected_coverage_test(&self) { let proc_res = self.run_kani_with_coverage(); + let cov_results_path = self.extract_cov_results_path(&proc_res); + let (kanimap, kaniraws, kanicov) = self.find_cov_files(&cov_results_path); + let kanicov_proc = self.run_kanicov_report(&kanimap, &kaniraws, &kanicov); let expected_path = self.testpaths.file.parent().unwrap().join("expected"); - self.verify_output(&proc_res, &expected_path); + self.verify_output(&kanicov_proc, &expected_path); } /// Runs Kani with stub implementations of various data structures. @@ -536,11 +539,88 @@ impl<'test> TestCx<'test> { let stamp = crate::stamp(self.config, self.testpaths); fs::write(stamp, "we only support one configuration").unwrap(); } + + /// Run `kani-cov merge` and `kani-cov report` to generate a text-based + /// report and return the `ProcRes` associated to the `kani-cov report` + /// command. + fn run_kanicov_report( + &self, + kanimap: &PathBuf, + kaniraws: &[PathBuf], + kanicov: &PathBuf, + ) -> ProcRes { + let mut kanicov_merge = Command::new("kani-cov"); + kanicov_merge.arg("merge"); + kanicov_merge.args(kaniraws); + kanicov_merge.arg("--output"); + kanicov_merge.arg(kanicov); + let merge_cmd = self.compose_and_run(kanicov_merge); + + if !merge_cmd.status.success() { + self.fatal_proc_rec("test failed: could not run `kani-cov merge` command", &merge_cmd); + } + + let mut kanicov_report = Command::new("kani-cov"); + kanicov_report.arg("report").arg(kanimap).arg("--profile").arg(kanicov); + let report_cmd = self.compose_and_run(kanicov_report); + + if !report_cmd.status.success() { + self.fatal_proc_rec( + "test failed: could not run `kani-cov report` command", + &report_cmd, + ); + } + report_cmd + } + + /// Return the paths to the files to be used for the `kani-cov` commands. + /// Note that `kanimap` and `kaniraws` result from any coverage-enabled Kani + /// run. `kanicov` is the name we will use for the output of the `kani-cov + /// merge` command. + fn find_cov_files(&self, folder_path: &Path) -> (PathBuf, Vec, PathBuf) { + let folder_name = folder_path.file_name().unwrap(); + + let kanimap = folder_path.join(format!("{}_kanimap.json", folder_name.to_string_lossy())); + let kanicov = folder_path.join(format!("{}_kanicov.json", folder_name.to_string_lossy())); + + let kaniraw_glob = format!("{}/*_kaniraw.json", folder_path.display()); + let kaniraws: Vec = glob::glob(&kaniraw_glob) + .expect("Failed to read glob pattern") + .filter_map(|entry| entry.ok()) + .collect(); + + (kanimap, kaniraws, kanicov) + } + + /// Find the path to the folder where the coverage results have been saved. + /// + /// The path is displayed in the output of a coverage-enabled Kani run like + /// this: + /// ```sh + /// Verification Time: XX.XXXXXXXs + /// + /// [info] Coverage results saved to /path/to/cov/results/kanicov__