Skip to content

Commit dcbf3aa

Browse files
celinvaladpaco-aws
andauthored
Add a new verify-std subcommand to Kani (rust-lang#3231)
This subcommand will take the path to the standard library. It will then use `cargo build -Z build-std` to build the custom standard library and verify any harness found during the build. ## Call out - So far I only performed manual tests. I'm going to add a few unit tests and a script in the next revision. Resolves rust-lang#3226 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 <[email protected]>
1 parent e9eeef7 commit dcbf3aa

File tree

12 files changed

+290
-71
lines changed

12 files changed

+290
-71
lines changed

kani-compiler/src/kani_middle/intrinsics.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ impl<'tcx> ModelIntrinsics<'tcx> {
5757
let arg_ty = args[0].node.ty(&self.local_decls, tcx);
5858
if arg_ty.is_simd() {
5959
// Get the stub definition.
60-
let stub_id = tcx.get_diagnostic_item(Symbol::intern("KaniModelSimdBitmask")).unwrap();
60+
let Some(stub_id) = tcx.get_diagnostic_item(Symbol::intern("KaniModelSimdBitmask"))
61+
else {
62+
// This should only happen when verifying the standard library.
63+
// We don't need to warn here, since the backend will print unsupported constructs.
64+
return;
65+
};
6166
debug!(?func, ?stub_id, "replace_simd_bitmask");
6267

6368
// Get SIMD information from the type.

kani-driver/src/args/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod assess_args;
66
pub mod cargo;
77
pub mod common;
88
pub mod playback_args;
9+
pub mod std_args;
910

1011
pub use assess_args::*;
1112

@@ -90,6 +91,8 @@ pub struct StandaloneArgs {
9091
pub enum StandaloneSubcommand {
9192
/// Execute concrete playback testcases of a local crate.
9293
Playback(Box<playback_args::KaniPlaybackArgs>),
94+
/// Verify the rust standard library.
95+
VerifyStd(Box<std_args::VerifyStdArgs>),
9396
}
9497

9598
#[derive(Debug, clap::Parser)]
@@ -448,6 +451,13 @@ fn check_no_cargo_opt(is_set: bool, name: &str) -> Result<(), Error> {
448451
impl ValidateArgs for StandaloneArgs {
449452
fn validate(&self) -> Result<(), Error> {
450453
self.verify_opts.validate()?;
454+
455+
match &self.command {
456+
Some(StandaloneSubcommand::VerifyStd(args)) => args.validate()?,
457+
// TODO: Invoke PlaybackArgs::validate()
458+
None | Some(StandaloneSubcommand::Playback(..)) => {}
459+
};
460+
451461
// Cargo target arguments.
452462
check_no_cargo_opt(self.verify_opts.target.bins, "--bins")?;
453463
check_no_cargo_opt(self.verify_opts.target.lib, "--lib")?;

kani-driver/src/args/std_args.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Implements the `verify-std` subcommand handling.
5+
6+
use crate::args::{ValidateArgs, VerificationArgs};
7+
use clap::error::ErrorKind;
8+
use clap::{Error, Parser};
9+
use kani_metadata::UnstableFeature;
10+
use std::path::PathBuf;
11+
12+
/// Verify a local version of the Rust standard library.
13+
///
14+
/// This is an **unstable option** and it the standard library version must be compatible with
15+
/// Kani's toolchain version.
16+
#[derive(Debug, Parser)]
17+
pub struct VerifyStdArgs {
18+
/// The path to the folder containing the crates for the Rust standard library.
19+
/// Note that this directory must be named `library` as used in the Rust toolchain and
20+
/// repository.
21+
pub std_path: PathBuf,
22+
23+
#[command(flatten)]
24+
pub verify_opts: VerificationArgs,
25+
}
26+
27+
impl ValidateArgs for VerifyStdArgs {
28+
fn validate(&self) -> Result<(), Error> {
29+
self.verify_opts.validate()?;
30+
31+
if !self
32+
.verify_opts
33+
.common_args
34+
.unstable_features
35+
.contains(UnstableFeature::UnstableOptions)
36+
{
37+
return Err(Error::raw(
38+
ErrorKind::MissingRequiredArgument,
39+
"The `verify-std` subcommand is unstable and requires -Z unstable-options",
40+
));
41+
}
42+
43+
if !self.std_path.exists() {
44+
Err(Error::raw(
45+
ErrorKind::InvalidValue,
46+
format!(
47+
"Invalid argument: `<STD_PATH>` argument `{}` does not exist",
48+
self.std_path.display()
49+
),
50+
))
51+
} else if !self.std_path.is_dir() {
52+
Err(Error::raw(
53+
ErrorKind::InvalidValue,
54+
format!(
55+
"Invalid argument: `<STD_PATH>` argument `{}` is not a directory",
56+
self.std_path.display()
57+
),
58+
))
59+
} else {
60+
let full_path = self.std_path.canonicalize()?;
61+
let dir = full_path.file_stem().unwrap();
62+
if dir != "library" {
63+
Err(Error::raw(
64+
ErrorKind::InvalidValue,
65+
format!(
66+
"Invalid argument: Expected `<STD_PATH>` to point to the `library` folder \
67+
containing the standard library crates.\n\
68+
Found `{}` folder instead",
69+
dir.to_string_lossy()
70+
),
71+
))
72+
} else {
73+
Ok(())
74+
}
75+
}
76+
}
77+
}

kani-driver/src/call_cargo.rs

Lines changed: 87 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use crate::args::VerificationArgs;
5-
use crate::call_single_file::to_rustc_arg;
5+
use crate::call_single_file::{to_rustc_arg, LibConfig};
66
use crate::project::Artifact;
7-
use crate::session::{setup_cargo_command, KaniSession};
7+
use crate::session::{lib_folder, lib_no_core_folder, setup_cargo_command, KaniSession};
88
use crate::util;
99
use anyhow::{bail, Context, Result};
1010
use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel};
11-
use cargo_metadata::{Message, Metadata, MetadataCommand, Package, Target};
11+
use cargo_metadata::{
12+
Artifact as RustcArtifact, Message, Metadata, MetadataCommand, Package, Target,
13+
};
1214
use kani_metadata::{ArtifactType, CompilerArtifactStub};
1315
use std::ffi::{OsStr, OsString};
1416
use std::fmt::{self, Display};
1517
use std::fs::{self, File};
1618
use std::io::BufReader;
1719
use std::io::IsTerminal;
18-
use std::path::PathBuf;
20+
use std::path::{Path, PathBuf};
1921
use std::process::Command;
2022
use tracing::{debug, trace};
2123

@@ -43,6 +45,47 @@ pub struct CargoOutputs {
4345
}
4446

4547
impl KaniSession {
48+
/// Create a new cargo library in the given path.
49+
pub fn cargo_init_lib(&self, path: &Path) -> Result<()> {
50+
let mut cmd = setup_cargo_command()?;
51+
cmd.args(["init", "--lib", path.to_string_lossy().as_ref()]);
52+
self.run_terminal(cmd)
53+
}
54+
55+
pub fn cargo_build_std(&self, std_path: &Path, krate_path: &Path) -> Result<Vec<Artifact>> {
56+
let lib_path = lib_no_core_folder().unwrap();
57+
let mut rustc_args = self.kani_rustc_flags(LibConfig::new_no_core(lib_path));
58+
rustc_args.push(to_rustc_arg(self.kani_compiler_flags()).into());
59+
rustc_args.push(self.reachability_arg().into());
60+
61+
let mut cargo_args: Vec<OsString> = vec!["build".into()];
62+
cargo_args.append(&mut cargo_config_args());
63+
64+
// Configuration needed to parse cargo compilation status.
65+
cargo_args.push("--message-format".into());
66+
cargo_args.push("json-diagnostic-rendered-ansi".into());
67+
cargo_args.push("-Z".into());
68+
cargo_args.push("build-std=panic_abort,core,std".into());
69+
70+
if self.args.common_args.verbose {
71+
cargo_args.push("-v".into());
72+
}
73+
74+
// Since we are verifying the standard library, we set the reachability to all crates.
75+
let mut cmd = setup_cargo_command()?;
76+
cmd.args(&cargo_args)
77+
.current_dir(krate_path)
78+
.env("RUSTC", &self.kani_compiler)
79+
// Use CARGO_ENCODED_RUSTFLAGS instead of RUSTFLAGS is preferred. See
80+
// https://doc.rust-lang.org/cargo/reference/environment-variables.html
81+
.env("CARGO_ENCODED_RUSTFLAGS", rustc_args.join(OsStr::new("\x1f")))
82+
.env("CARGO_TERM_PROGRESS_WHEN", "never")
83+
.env("__CARGO_TESTS_ONLY_SRC_ROOT", std_path.as_os_str());
84+
85+
let build_artifacts = self.run_build(cmd)?;
86+
Ok(build_artifacts.into_iter().filter_map(map_kani_artifact).collect())
87+
}
88+
4689
/// Calls `cargo_build` to generate `*.symtab.json` files in `target_dir`
4790
pub fn cargo_build(&self, keep_going: bool) -> Result<CargoOutputs> {
4891
let build_target = env!("TARGET"); // see build.rs
@@ -60,7 +103,8 @@ impl KaniSession {
60103
fs::remove_dir_all(&target_dir)?;
61104
}
62105

63-
let mut rustc_args = self.kani_rustc_flags();
106+
let lib_path = lib_folder().unwrap();
107+
let mut rustc_args = self.kani_rustc_flags(LibConfig::new(lib_path));
64108
rustc_args.push(to_rustc_arg(self.kani_compiler_flags()).into());
65109

66110
let mut cargo_args: Vec<OsString> = vec!["rustc".into()];
@@ -120,7 +164,7 @@ impl KaniSession {
120164
.env("CARGO_ENCODED_RUSTFLAGS", rustc_args.join(OsStr::new("\x1f")))
121165
.env("CARGO_TERM_PROGRESS_WHEN", "never");
122166

123-
match self.run_cargo(cmd, verification_target.target()) {
167+
match self.run_build_target(cmd, verification_target.target()) {
124168
Err(err) => {
125169
if keep_going {
126170
let target_str = format!("{verification_target}");
@@ -179,9 +223,9 @@ impl KaniSession {
179223

180224
/// Run cargo and collect any error found.
181225
/// We also collect the metadata file generated during compilation if any.
182-
fn run_cargo(&self, cargo_cmd: Command, target: &Target) -> Result<Option<Artifact>> {
226+
fn run_build(&self, cargo_cmd: Command) -> Result<Vec<RustcArtifact>> {
183227
let support_color = std::io::stdout().is_terminal();
184-
let mut artifact = None;
228+
let mut artifacts = vec![];
185229
if let Some(mut cargo_process) = self.run_piped(cargo_cmd)? {
186230
let reader = BufReader::new(cargo_process.stdout.take().unwrap());
187231
let mut error_count = 0;
@@ -211,33 +255,9 @@ impl KaniSession {
211255
}
212256
},
213257
Message::CompilerArtifact(rustc_artifact) => {
214-
/// Compares two targets, and falls back to a weaker
215-
/// comparison where we avoid dashes in their names.
216-
fn same_target(t1: &Target, t2: &Target) -> bool {
217-
(t1 == t2)
218-
|| (t1.name.replace('-', "_") == t2.name.replace('-', "_")
219-
&& t1.kind == t2.kind
220-
&& t1.src_path == t2.src_path
221-
&& t1.edition == t2.edition
222-
&& t1.doctest == t2.doctest
223-
&& t1.test == t2.test
224-
&& t1.doc == t2.doc)
225-
}
226-
// This used to be `rustc_artifact == *target`, but it
227-
// started to fail after the `cargo` change in
228-
// <https://github.com/rust-lang/cargo/pull/12783>
229-
//
230-
// We should revisit this check after a while to see if
231-
// it's not needed anymore or it can be restricted to
232-
// certain cases.
233-
// TODO: <https://github.com/model-checking/kani/issues/3111>
234-
if same_target(&rustc_artifact.target, target) {
235-
debug_assert!(
236-
artifact.is_none(),
237-
"expected only one artifact for `{target:?}`",
238-
);
239-
artifact = Some(rustc_artifact);
240-
}
258+
// Compares two targets, and falls back to a weaker
259+
// comparison where we avoid dashes in their names.
260+
artifacts.push(rustc_artifact)
241261
}
242262
Message::BuildScriptExecuted(_) | Message::BuildFinished(_) => {
243263
// do nothing
@@ -263,11 +283,40 @@ impl KaniSession {
263283
);
264284
}
265285
}
286+
Ok(artifacts)
287+
}
288+
289+
/// Run cargo and collect any error found.
290+
/// We also collect the metadata file generated during compilation if any for the given target.
291+
fn run_build_target(&self, cargo_cmd: Command, target: &Target) -> Result<Option<Artifact>> {
292+
/// This used to be `rustc_artifact == *target`, but it
293+
/// started to fail after the `cargo` change in
294+
/// <https://github.com/rust-lang/cargo/pull/12783>
295+
///
296+
/// We should revisit this check after a while to see if
297+
/// it's not needed anymore or it can be restricted to
298+
/// certain cases.
299+
/// TODO: <https://github.com/model-checking/kani/issues/3111>
300+
fn same_target(t1: &Target, t2: &Target) -> bool {
301+
(t1 == t2)
302+
|| (t1.name.replace('-', "_") == t2.name.replace('-', "_")
303+
&& t1.kind == t2.kind
304+
&& t1.src_path == t2.src_path
305+
&& t1.edition == t2.edition
306+
&& t1.doctest == t2.doctest
307+
&& t1.test == t2.test
308+
&& t1.doc == t2.doc)
309+
}
310+
311+
let artifacts = self.run_build(cargo_cmd)?;
312+
debug!(?artifacts, "run_build_target");
313+
266314
// We generate kani specific artifacts only for the build target. The build target is
267315
// always the last artifact generated in a build, and all the other artifacts are related
268-
// to dependencies or build scripts. Hence, we need to invoke `map_kani_artifact` only
269-
// for the last compiler artifact.
270-
Ok(artifact.and_then(map_kani_artifact))
316+
// to dependencies or build scripts.
317+
Ok(artifacts.into_iter().rev().find_map(|artifact| {
318+
if same_target(&artifact.target, target) { map_kani_artifact(artifact) } else { None }
319+
}))
271320
}
272321
}
273322

0 commit comments

Comments
 (0)