Skip to content

Commit 4dfff36

Browse files
zhassan-awsadpaco-awsdanielsn
authored
Allow user to specify CBMC's solver (rust-lang#2088)
Co-authored-by: Adrian Palacios <[email protected]> Co-authored-by: Daniel Schwartz-Narbonne <[email protected]>
1 parent 493a3ea commit 4dfff36

File tree

35 files changed

+398
-4
lines changed

35 files changed

+398
-4
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ dependencies = [
535535
"tracing",
536536
"tracing-subscriber",
537537
"tracing-tree",
538+
"which",
538539
]
539540

540541
[[package]]
@@ -562,6 +563,8 @@ version = "0.20.0"
562563
dependencies = [
563564
"cprover_bindings",
564565
"serde",
566+
"strum",
567+
"strum_macros",
565568
]
566569

567570
[[package]]

kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use crate::codegen_cprover_gotoc::GotocCtx;
77
use crate::kani_middle::attributes::{extract_integer_argument, partition_kanitool_attributes};
88
use cbmc::goto_program::{Expr, Stmt, Symbol};
99
use cbmc::InternString;
10-
use kani_metadata::HarnessMetadata;
10+
use kani_metadata::{CbmcSolver, HarnessMetadata};
1111
use kani_queries::UserInput;
12-
use rustc_ast::Attribute;
12+
use rustc_ast::{Attribute, MetaItemKind};
1313
use rustc_hir::def::DefKind;
1414
use rustc_hir::def_id::DefId;
1515
use rustc_middle::mir::traversal::reverse_postorder;
@@ -19,6 +19,7 @@ use rustc_middle::ty::{self, Instance};
1919
use std::collections::BTreeMap;
2020
use std::convert::TryInto;
2121
use std::iter::FromIterator;
22+
use std::str::FromStr;
2223
use tracing::{debug, debug_span};
2324

2425
/// Codegen MIR functions into gotoc
@@ -353,6 +354,7 @@ impl<'tcx> GotocCtx<'tcx> {
353354
original_file: loc.filename().unwrap(),
354355
original_start_line: loc.start_line().unwrap() as usize,
355356
original_end_line: loc.end_line().unwrap() as usize,
357+
solver: None,
356358
unwind_value: None,
357359
// We record the actual path after codegen before we dump the metadata into a file.
358360
goto_file: None,
@@ -378,6 +380,7 @@ impl<'tcx> GotocCtx<'tcx> {
378380
let mut harness = self.default_kanitool_proof();
379381
for attr in other_attributes.iter() {
380382
match attr.0.as_str() {
383+
"solver" => self.handle_kanitool_solver(attr.1, &mut harness),
381384
"stub" => {
382385
if !self.queries.get_stubbing_enabled() {
383386
self.tcx.sess.span_warn(
@@ -412,6 +415,7 @@ impl<'tcx> GotocCtx<'tcx> {
412415
original_file: loc.filename().unwrap(),
413416
original_start_line: loc.start_line().unwrap() as usize,
414417
original_end_line: loc.end_line().unwrap() as usize,
418+
solver: None,
415419
unwind_value: None,
416420
// We record the actual path after codegen before we dump the metadata into a file.
417421
goto_file: None,
@@ -445,4 +449,68 @@ impl<'tcx> GotocCtx<'tcx> {
445449
}
446450
}
447451
}
452+
453+
/// Set the solver for this proof harness
454+
fn handle_kanitool_solver(&mut self, attr: &Attribute, harness: &mut HarnessMetadata) {
455+
// Make sure the solver is not already set
456+
if harness.solver.is_some() {
457+
self.tcx
458+
.sess
459+
.span_err(attr.span, "only one '#[kani::solver]' attribute is allowed per harness");
460+
return;
461+
}
462+
harness.solver = self.extract_solver_argument(attr);
463+
}
464+
465+
fn extract_solver_argument(&mut self, attr: &Attribute) -> Option<CbmcSolver> {
466+
// TODO: Argument validation should be done as part of the `kani_macros` crate
467+
// <https://github.com/model-checking/kani/issues/2192>
468+
const ATTRIBUTE: &str = "#[kani::solver]";
469+
let invalid_arg_err = |attr: &Attribute| {
470+
self.tcx.sess.span_err(
471+
attr.span,
472+
format!("invalid argument for `{ATTRIBUTE}` attribute, expected one of the supported solvers (e.g. `kissat`) or a SAT solver binary (e.g. `custom=\"<SAT_SOLVER_BINARY>\"`)")
473+
)
474+
};
475+
476+
let attr_args = attr.meta_item_list().unwrap();
477+
if attr_args.len() != 1 {
478+
self.tcx.sess.span_err(
479+
attr.span,
480+
format!(
481+
"the `{ATTRIBUTE}` attribute expects a single argument. Got {} arguments.",
482+
attr_args.len()
483+
),
484+
);
485+
return None;
486+
}
487+
let attr_arg = &attr_args[0];
488+
let meta_item = attr_arg.meta_item();
489+
if meta_item.is_none() {
490+
invalid_arg_err(attr);
491+
return None;
492+
}
493+
let meta_item = meta_item.unwrap();
494+
let ident = meta_item.ident().unwrap();
495+
let ident_str = ident.as_str();
496+
match &meta_item.kind {
497+
MetaItemKind::Word => {
498+
let solver = CbmcSolver::from_str(ident_str);
499+
match solver {
500+
Ok(solver) => Some(solver),
501+
Err(_) => {
502+
self.tcx.sess.span_err(attr.span, format!("unknown solver `{ident_str}`"));
503+
None
504+
}
505+
}
506+
}
507+
MetaItemKind::NameValue(lit) if ident_str == "custom" && lit.kind.is_str() => {
508+
Some(CbmcSolver::Custom(lit.token_lit.symbol.to_string()))
509+
}
510+
_ => {
511+
invalid_arg_err(attr);
512+
None
513+
}
514+
}
515+
}
448516
}

kani-driver/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ strum_macros = {version = "0.24.0"}
3737
tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_debug"]}
3838
tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]}
3939
tracing-tree = "0.2.2"
40+
which = "4.4.0"
4041

4142
# A good set of suggested dependencies can be found in rustup:
4243
# https://github.com/rust-lang/rustup/blob/master/Cargo.toml

kani-driver/src/args.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@
44
#[cfg(feature = "unsound_experiments")]
55
use crate::unsound_experiments::UnsoundExperimentArgs;
66
use crate::util::warning;
7+
use kani_metadata::CbmcSolver;
78

8-
use clap::{error::Error, error::ErrorKind, CommandFactory, ValueEnum};
9+
use clap::builder::{PossibleValue, TypedValueParser};
10+
use clap::{
11+
error::ContextKind, error::ContextValue, error::Error, error::ErrorKind, CommandFactory,
12+
ValueEnum,
13+
};
914
use std::ffi::OsString;
1015
use std::path::PathBuf;
16+
use std::str::FromStr;
17+
use strum::VariantNames;
1118

1219
// By default we configure CBMC to use 16 bits to represent the object bits in pointers.
1320
const DEFAULT_OBJECT_BITS: u32 = 16;
@@ -150,6 +157,9 @@ pub struct KaniArgs {
150157
/// Specify the value used for loop unwinding for the specified harness in CBMC
151158
#[arg(long, requires("harness"))]
152159
pub unwind: Option<u32>,
160+
/// Specify the CBMC solver to use. Overrides the harness `solver` attribute.
161+
#[arg(long, value_parser = CbmcSolverValueParser::new(CbmcSolver::VARIANTS))]
162+
pub solver: Option<CbmcSolver>,
153163
/// Pass through directly to CBMC; must be the last flag.
154164
/// This feature is unstable and it requires `--enable_unstable` to be used
155165
#[arg(
@@ -549,6 +559,61 @@ impl KaniArgs {
549559
}
550560
}
551561

562+
/// clap parser for `CbmcSolver`
563+
#[derive(Clone, Debug)]
564+
pub struct CbmcSolverValueParser(Vec<PossibleValue>);
565+
566+
impl CbmcSolverValueParser {
567+
pub fn new(values: impl Into<CbmcSolverValueParser>) -> Self {
568+
values.into()
569+
}
570+
}
571+
572+
impl TypedValueParser for CbmcSolverValueParser {
573+
type Value = CbmcSolver;
574+
575+
fn parse_ref(
576+
&self,
577+
cmd: &clap::builder::Command,
578+
arg: Option<&clap::builder::Arg>,
579+
value: &std::ffi::OsStr,
580+
) -> Result<Self::Value, clap::error::Error> {
581+
let value = value.to_str().unwrap();
582+
// `value` is one of the possible `CbmcSolver` values or `custom=<binary>`
583+
let segments: Vec<&str> = value.split('=').collect();
584+
585+
let mut err = clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd);
586+
err.insert(ContextKind::InvalidArg, ContextValue::String(arg.unwrap().to_string()));
587+
err.insert(ContextKind::InvalidValue, ContextValue::String(value.to_string()));
588+
589+
if segments.len() == 2 {
590+
if segments[0] != "custom" {
591+
return Err(err);
592+
}
593+
return Ok(CbmcSolver::Custom(segments[1].into()));
594+
} else if segments.len() == 1 {
595+
let solver = CbmcSolver::from_str(value);
596+
return solver.map_err(|_| err);
597+
}
598+
Err(err)
599+
}
600+
601+
/// Used for the help message
602+
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
603+
Some(Box::new(self.0.iter().cloned()))
604+
}
605+
}
606+
607+
impl<I, T> From<I> for CbmcSolverValueParser
608+
where
609+
I: IntoIterator<Item = T>,
610+
T: Into<PossibleValue>,
611+
{
612+
fn from(values: I) -> Self {
613+
Self(values.into_iter().map(|t| t.into()).collect())
614+
}
615+
}
616+
552617
#[cfg(test)]
553618
mod tests {
554619
use clap::Parser;

kani-driver/src/call_cbmc.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use anyhow::{bail, Result};
5-
use kani_metadata::HarnessMetadata;
5+
use kani_metadata::{CbmcSolver, HarnessMetadata};
66
use std::ffi::OsString;
77
use std::fmt::Write;
88
use std::path::Path;
@@ -123,6 +123,8 @@ impl KaniSession {
123123
args.push(unwind_value.to_string().into());
124124
}
125125

126+
self.handle_solver_args(&harness_metadata.solver, &mut args)?;
127+
126128
if self.args.run_sanity_checks {
127129
args.push("--validate-goto-model".into());
128130
args.push("--validate-ssa-equation".into());
@@ -186,6 +188,42 @@ impl KaniSession {
186188

187189
args
188190
}
191+
192+
fn handle_solver_args(
193+
&self,
194+
harness_solver: &Option<CbmcSolver>,
195+
args: &mut Vec<OsString>,
196+
) -> Result<()> {
197+
let solver = if let Some(solver) = &self.args.solver {
198+
// `--solver` option takes precedence over attributes
199+
solver
200+
} else if let Some(solver) = harness_solver {
201+
solver
202+
} else {
203+
// Nothing to do
204+
return Ok(());
205+
};
206+
207+
match solver {
208+
CbmcSolver::Kissat => {
209+
args.push("--external-sat-solver".into());
210+
args.push("kissat".into());
211+
}
212+
CbmcSolver::Minisat => {
213+
// Minisat is currently CBMC's default solver, so no need to
214+
// pass any arguments
215+
}
216+
CbmcSolver::Custom(custom_solver) => {
217+
// Check if the specified binary exists in path
218+
if which::which(custom_solver).is_err() {
219+
bail!("the specified solver \"{custom_solver}\" was not found in path")
220+
}
221+
args.push("--external-sat-solver".into());
222+
args.push(custom_solver.into());
223+
}
224+
}
225+
Ok(())
226+
}
189227
}
190228

191229
impl VerificationResult {

kani-driver/src/metadata.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ pub fn mock_proof_harness(
149149
original_file: "<unknown>".into(),
150150
original_start_line: 0,
151151
original_end_line: 0,
152+
solver: None,
152153
unwind_value,
153154
goto_file: None,
154155
}

kani_metadata/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ publish = false
1313
[dependencies]
1414
serde = {version = "1", features = ["derive"]}
1515
cbmc = { path = "../cprover_bindings", package = "cprover_bindings" }
16+
strum = "0.24.1"
17+
strum_macros = "0.24.3"

kani_metadata/src/cbmc_solver.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
use serde::{Deserialize, Serialize};
5+
use strum_macros::{AsRefStr, EnumString, EnumVariantNames};
6+
7+
/// An enum for CBMC solver options. All variants are handled by Kani, except for
8+
/// the `Custom` one, which it passes as is to CBMC's `--external-sat-solver`
9+
/// option.
10+
#[derive(
11+
Debug,
12+
Clone,
13+
AsRefStr,
14+
EnumString,
15+
EnumVariantNames,
16+
PartialEq,
17+
Eq,
18+
Serialize,
19+
Deserialize
20+
)]
21+
#[strum(serialize_all = "snake_case")]
22+
pub enum CbmcSolver {
23+
/// The kissat solver that is included in the Kani bundle
24+
Kissat,
25+
26+
/// MiniSAT (CBMC's default solver)
27+
Minisat,
28+
29+
/// A custom solver variant whose argument gets passed to
30+
/// `--external-sat-solver`. The specified binary must exist in path.
31+
#[strum(disabled, serialize = "custom=<SAT_SOLVER_BINARY>")]
32+
Custom(String),
33+
}

kani_metadata/src/harness.rs

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

4+
use crate::CbmcSolver;
45
use serde::{Deserialize, Serialize};
56
use std::path::PathBuf;
67

@@ -19,6 +20,8 @@ pub struct HarnessMetadata {
1920
pub original_start_line: usize,
2021
/// The line in that file where the proof harness ends.
2122
pub original_end_line: usize,
23+
/// Optional data to store solver.
24+
pub solver: Option<CbmcSolver>,
2225
/// Optional data to store unwind value.
2326
pub unwind_value: Option<u32>,
2427
/// Optional modeling file that was generated by the compiler that includes this harness.

kani_metadata/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
use serde::{Deserialize, Serialize};
55

66
pub use artifact::ArtifactType;
7+
pub use cbmc_solver::CbmcSolver;
78
pub use harness::*;
89
pub use vtable::*;
910

1011
pub mod artifact;
12+
mod cbmc_solver;
1113
mod harness;
1214
mod vtable;
1315

library/kani_macros/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,29 @@ pub fn stub(attr: TokenStream, item: TokenStream) -> TokenStream {
148148
result
149149
}
150150

151+
#[cfg(not(kani))]
152+
#[proc_macro_attribute]
153+
pub fn solver(_attr: TokenStream, item: TokenStream) -> TokenStream {
154+
// No-op in non-kani mode
155+
item
156+
}
157+
158+
/// Select the SAT solver to use with CBMC for this harness
159+
/// The attribute `#[kani::solver(arg)]` can only be used alongside `#[kani::proof]``
160+
///
161+
/// arg - name of solver, e.g. kissat
162+
#[cfg(kani)]
163+
#[proc_macro_attribute]
164+
pub fn solver(attr: TokenStream, item: TokenStream) -> TokenStream {
165+
let mut result = TokenStream::new();
166+
// Translate `#[kani::solver(arg)]` to `#[kanitool::solver(arg)]`
167+
let insert_string = "#[kanitool::solver(".to_owned() + &attr.to_string() + ")]";
168+
result.extend(insert_string.parse::<TokenStream>().unwrap());
169+
170+
result.extend(item);
171+
result
172+
}
173+
151174
/// Allow users to auto generate Arbitrary implementations by using `#[derive(Arbitrary)]` macro.
152175
#[proc_macro_error]
153176
#[proc_macro_derive(Arbitrary)]

0 commit comments

Comments
 (0)