Skip to content

Commit a26de5a

Browse files
authored
Report correct package count in assess (rust-lang#2026)
1 parent ba9b41b commit a26de5a

File tree

17 files changed

+246
-53
lines changed

17 files changed

+246
-53
lines changed

kani-driver/src/assess/mod.rs

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use anyhow::Result;
5+
use kani_metadata::KaniMetadata;
56

67
use crate::metadata::merge_kani_metadata;
78
use crate::project;
@@ -25,18 +26,28 @@ pub(crate) fn cargokani_assess_main(mut session: KaniSession) -> Result<()> {
2526
}
2627

2728
let project = project::cargo_project(&session)?;
29+
let cargo_metadata = project.cargo_metadata.as_ref().expect("built with cargo");
2830

29-
let crate_count = project.metadata.len();
31+
let packages_metadata = if project.merged_artifacts {
32+
// With the legacy linker we can't expect to find the metadata structure we'd expect
33+
// so we just use it as-is. This does mean the "package count" will be wrong, but
34+
// we will at least continue to see everything.
35+
project.metadata.clone()
36+
} else {
37+
reconstruct_metadata_structure(cargo_metadata, &project.metadata)?
38+
};
39+
40+
// We don't really have a list of crates that went into building our various targets,
41+
// so we can't easily count them.
3042

31-
// An interesting thing to print here would be "number of crates without any warnings"
32-
// however this will have to wait until a refactoring of how we aggregate metadata
33-
// from multiple crates together here.
34-
// tracking for that: https://github.com/model-checking/kani/issues/1758
35-
println!("Analyzed {crate_count} crates");
43+
// It would also be interesting to classify them by whether they build without warnings or not.
44+
// Tracking for the latter: https://github.com/model-checking/kani/issues/1758
3645

37-
let metadata = merge_kani_metadata(project.metadata.clone());
46+
println!("Found {} packages", packages_metadata.len());
47+
48+
let metadata = merge_kani_metadata(packages_metadata.clone());
3849
if !metadata.unsupported_features.is_empty() {
39-
println!("{}", table_unsupported_features::build(&metadata));
50+
println!("{}", table_unsupported_features::build(&packages_metadata));
4051
} else {
4152
println!("No crates contained Rust features unsupported by Kani");
4253
}
@@ -67,3 +78,41 @@ pub(crate) fn cargokani_assess_main(mut session: KaniSession) -> Result<()> {
6778

6879
Ok(())
6980
}
81+
82+
/// Merges a collection of Kani metadata by figuring out which package each belongs to, from cargo metadata.
83+
///
84+
/// This function, properly speaking, should not exist. We should have this information already from `Project`.
85+
/// This should function should be removable when we fix how driver handles metadata:
86+
/// <https://github.com/model-checking/kani/issues/1758>
87+
fn reconstruct_metadata_structure(
88+
cargo_metadata: &cargo_metadata::Metadata,
89+
kani_metadata: &[KaniMetadata],
90+
) -> Result<Vec<KaniMetadata>> {
91+
let mut search = kani_metadata.to_owned();
92+
let mut results = vec![];
93+
for package in &cargo_metadata.packages {
94+
let mut artifacts = vec![];
95+
for target in &package.targets {
96+
// cargo_metadata doesn't provide name mangling help here?
97+
// we need to know cargo's name changes when it's given to rustc
98+
let target_name = target.name.replace('-', "_");
99+
if let Some(i) = search.iter().position(|x| x.crate_name == target_name) {
100+
let md = search.swap_remove(i);
101+
artifacts.push(md);
102+
} else {
103+
println!(
104+
"Didn't find metadata for target {} in package {}",
105+
target.name, package.name
106+
)
107+
}
108+
}
109+
let mut merged = crate::metadata::merge_kani_metadata(artifacts);
110+
merged.crate_name = package.name.clone();
111+
results.push(merged);
112+
}
113+
if !search.is_empty() {
114+
let search_names: Vec<_> = search.into_iter().map(|x| x.crate_name).collect();
115+
println!("Found remaining (unused) metadata after reconstruction: {:?}", search_names);
116+
}
117+
Ok(results)
118+
}

kani-driver/src/assess/table_failure_reasons.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl TableRow for FailureReasonsTableRow {
6868
}
6969

7070
fn compare(&self, right: &Self) -> Ordering {
71-
self.count.cmp(&right.count).reverse()
71+
self.count.cmp(&right.count).reverse().then_with(|| self.reason.cmp(&right.reason))
7272
}
7373
}
7474

kani-driver/src/assess/table_unsupported_features.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4-
use std::cmp::Ordering;
4+
use std::{cmp::Ordering, collections::HashSet};
55

66
use comfy_table::Table;
77
use kani_metadata::KaniMetadata;
@@ -24,24 +24,26 @@ use super::table_builder::{ColumnType, RenderableTableRow, TableBuilder, TableRo
2424
/// drop_in_place | 2 | 2
2525
/// ===================================================
2626
/// ```
27-
pub(crate) fn build(metadata: &KaniMetadata) -> Table {
27+
pub(crate) fn build(metadata: &[KaniMetadata]) -> Table {
2828
let mut builder = TableBuilder::new();
2929

30-
for item in &metadata.unsupported_features {
31-
// key is unsupported feature name
32-
let mut key = item.feature.clone();
33-
// There are several "feature for <instance of use>" unsupported features.
34-
// We aggregate those here by reducing it to just "feature".
35-
// We should replace this with an enum: https://github.com/model-checking/kani/issues/1765
36-
if let Some((prefix, _)) = key.split_once(" for ") {
37-
key = prefix.to_string();
30+
for package_metadata in metadata {
31+
for item in &package_metadata.unsupported_features {
32+
// key is unsupported feature name
33+
let mut key = item.feature.clone();
34+
// There are several "feature for <instance of use>" unsupported features.
35+
// We aggregate those here by reducing it to just "feature".
36+
// We should replace this with an enum: https://github.com/model-checking/kani/issues/1765
37+
if let Some((prefix, _)) = key.split_once(" for ") {
38+
key = prefix.to_string();
39+
}
40+
41+
builder.add(UnsupportedFeaturesTableRow {
42+
unsupported_feature: key,
43+
crates_impacted: HashSet::from([package_metadata.crate_name.to_owned()]),
44+
instances_of_use: item.locations.len(),
45+
})
3846
}
39-
40-
builder.add(UnsupportedFeaturesTableRow {
41-
unsupported_feature: key,
42-
crates_impacted: 1,
43-
instances_of_use: item.locations.len(),
44-
})
4547
}
4648

4749
builder.render()
@@ -50,7 +52,7 @@ pub(crate) fn build(metadata: &KaniMetadata) -> Table {
5052
#[derive(Default)]
5153
pub struct UnsupportedFeaturesTableRow {
5254
pub unsupported_feature: String,
53-
pub crates_impacted: usize,
55+
pub crates_impacted: HashSet<String>,
5456
pub instances_of_use: usize,
5557
}
5658

@@ -62,15 +64,17 @@ impl TableRow for UnsupportedFeaturesTableRow {
6264
}
6365

6466
fn merge(&mut self, new: Self) {
65-
self.crates_impacted += new.crates_impacted;
67+
self.crates_impacted.extend(new.crates_impacted);
6668
self.instances_of_use += new.instances_of_use;
6769
}
6870

6971
fn compare(&self, right: &Self) -> Ordering {
7072
self.crates_impacted
71-
.cmp(&right.crates_impacted)
73+
.len()
74+
.cmp(&right.crates_impacted.len())
7275
.reverse()
7376
.then_with(|| self.instances_of_use.cmp(&right.instances_of_use).reverse())
77+
.then_with(|| self.unsupported_feature.cmp(&right.unsupported_feature))
7478
}
7579
}
7680
impl RenderableTableRow for UnsupportedFeaturesTableRow {
@@ -86,7 +90,7 @@ impl RenderableTableRow for UnsupportedFeaturesTableRow {
8690
fn row(&self) -> Vec<String> {
8791
vec![
8892
self.unsupported_feature.clone(),
89-
self.crates_impacted.to_string(),
93+
self.crates_impacted.len().to_string(),
9094
self.instances_of_use.to_string(),
9195
]
9296
}

kani-driver/src/call_cargo.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct CargoOutputs {
3232
pub restrictions: Option<PathBuf>,
3333
/// The kani-metadata.json files written by kani-compiler.
3434
pub metadata: Vec<PathBuf>,
35+
/// Recording the cargo metadata from the build
36+
pub cargo_metadata: Metadata,
3537
}
3638

3739
impl KaniSession {
@@ -133,6 +135,7 @@ impl KaniSession {
133135
symtabs: glob(&outdir.join("*.symtab.json"))?,
134136
metadata: glob(&outdir.join("*.kani-metadata.json"))?,
135137
restrictions: self.args.restrict_vtable().then_some(outdir),
138+
cargo_metadata: metadata,
136139
})
137140
}
138141
}

kani-driver/src/project.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ pub struct Project {
5959
/// When this flag is `true`, there should only be up to one artifact of any given type.
6060
/// When this flag is `false`, there may be multiple artifacts for any given type. However,
6161
/// only up to one artifact for each
62-
merged_artifacts: bool,
62+
pub merged_artifacts: bool,
63+
/// Records the cargo metadata from the build, if there was any
64+
pub cargo_metadata: Option<cargo_metadata::Metadata>,
6365
}
6466

6567
impl Project {
@@ -171,7 +173,13 @@ pub fn cargo_project(session: &KaniSession) -> Result<Project> {
171173
dump_metadata(&metadata, &metadata_file);
172174
artifacts.push(Artifact::try_new(&metadata_file, Metadata)?);
173175

174-
Ok(Project { outdir, artifacts, metadata: vec![metadata], merged_artifacts: true })
176+
Ok(Project {
177+
outdir,
178+
artifacts,
179+
metadata: vec![metadata],
180+
merged_artifacts: true,
181+
cargo_metadata: Some(outputs.cargo_metadata),
182+
})
175183
} else {
176184
// For the MIR Linker we know there is only one artifact per verification target. Use
177185
// that in our favor.
@@ -192,7 +200,13 @@ pub fn cargo_project(session: &KaniSession) -> Result<Project> {
192200
debug!(?crate_name, ?crate_metadata, "cargo_project");
193201
metadata.push(crate_metadata);
194202
}
195-
Ok(Project { outdir, artifacts, metadata, merged_artifacts: false })
203+
Ok(Project {
204+
outdir,
205+
artifacts,
206+
metadata,
207+
merged_artifacts: false,
208+
cargo_metadata: Some(outputs.cargo_metadata),
209+
})
196210
}
197211
}
198212

@@ -283,6 +297,7 @@ impl<'a> StandaloneProjectBuilder<'a> {
283297
.filter(|artifact| artifact.path.exists())
284298
.collect(),
285299
merged_artifacts: false,
300+
cargo_metadata: None,
286301
})
287302
}
288303

tests/cargo-kani/assess-works/Cargo.toml renamed to tests/cargo-kani/assess-artifacts/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
# SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
[package]
5-
name = "assess-works"
5+
name = "assess-artifacts"
66
version = "0.1.0"
77
edition = "2021"
88

9-
[dependencies]
10-
anyhow = "1"
9+
# See src/lib.rs for a comment on this tests's purpose
1110

1211
[package.metadata.kani]
1312
flags = { assess=true, enable-unstable=true }
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Found 1 packages
2+
============================================
3+
Unsupported feature | Crates | Instances
4+
| impacted | of use
5+
---------------------+----------+-----------
6+
try | 1 | 2
7+
============================================
8+
=========================================
9+
Reason for failure | Number of tests
10+
-----------------------+-----------------
11+
none (success) | 2
12+
unsupported_construct | 2
13+
=========================================
14+
======================================================================================================
15+
Candidate for proof harness | Location
16+
-------------------------------+----------------------------------------------------------------------
17+
a_supported_test_from_tests |
18+
a_supported_test_from_the_lib |
19+
======================================================================================================
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This test ensures:
5+
//! 1. Assess is able to correctly build and report on a package
6+
//! 2. Assess is able to correctly count the number of packages (1),
7+
//! in the presence of an integration test (which might otherwise
8+
//! look like two crates: 'assess-artifact' and 'integ')
9+
10+
#[test]
11+
fn an_unsupported_test_from_the_lib() {
12+
// unsupported feature: try instrinsic
13+
assert!(std::panic::catch_unwind(|| panic!("test")).is_err());
14+
}
15+
16+
#[test]
17+
fn a_supported_test_from_the_lib() {
18+
assert!(1 == 1);
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
#[test]
5+
fn an_unsupported_test_from_tests() {
6+
// unsupported feature: try instrinsic
7+
assert!(std::panic::catch_unwind(|| panic!("test")).is_err());
8+
}
9+
10+
#[test]
11+
fn a_supported_test_from_tests() {
12+
assert!(1 == 1);
13+
}

tests/cargo-kani/assess-works/expected

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/cargo-kani/assess-works/src/main.rs

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
[package]
5+
name = "assess-artifacts"
6+
version = "0.1.0"
7+
edition = "2021"
8+
9+
# See src/lib.rs for a comment on this tests's purpose
10+
11+
[package.metadata.kani]
12+
flags = { assess=true, enable-unstable=true, workspace=true }
13+
14+
[workspace]
15+
members = ["subpackage"]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Found 2 packages
2+
============================================
3+
Unsupported feature | Crates | Instances
4+
| impacted | of use
5+
---------------------+----------+-----------
6+
try | 2 | 3
7+
============================================
8+
=========================================
9+
Reason for failure | Number of tests
10+
-----------------------+-----------------
11+
unsupported_construct | 3
12+
none (success) | 3
13+
=========================================
14+
=======================================================================================================================
15+
Candidate for proof harness | Location
16+
--------------------------------------+--------------------------------------------------------------------------------
17+
a_supported_test_from_tests |
18+
a_supported_test_from_the_lib |
19+
a_supported_test_from_the_subpackage |
20+
=======================================================================================================================
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This test ensures:
5+
//! 1. Assess is able to correctly build and report on a _workspace_
6+
//! 2. Assess is able to correctly count the number of packages (2),
7+
//! in the presence of an integration test (which might otherwise
8+
//! look like three crates:
9+
//! 'assess-artifact', 'subpackage', and 'integ')
10+
11+
#[test]
12+
fn an_unsupported_test_from_the_lib() {
13+
// unsupported feature: try instrinsic
14+
assert!(std::panic::catch_unwind(|| panic!("test")).is_err());
15+
}
16+
17+
#[test]
18+
fn a_supported_test_from_the_lib() {
19+
assert!(1 == 1);
20+
}

0 commit comments

Comments
 (0)