mod progress;
mod report;
mod trace;
mod verify;
use std::{path::PathBuf, sync::Arc};
use anyhow::{anyhow, bail};
use clap::{Parser, Subcommand, ValueEnum};
use progress::Bar;
use report::Report;
use scan_core::{CsModel, MtlOracle, Oracle, PgModel, PmtlOracle, Scan, TransitionSystemGenerator};
use trace::TraceArgs;
use verify::VerifyArgs;
#[deny(missing_docs)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Format {
Scxml,
Jani,
Promela,
}
#[deny(missing_docs)]
#[derive(Subcommand)]
enum Commands {
Validate,
#[clap(verbatim_doc_comment)]
Verify {
#[clap(flatten)]
args: VerifyArgs,
#[arg(long, value_enum)]
progress: Option<Bar>,
#[arg(long)]
json: bool,
},
#[clap(verbatim_doc_comment)]
Trace(TraceArgs),
}
const LONG_ABOUT: &str = "SCAN (StatistiCal ANalyzer) is a statistical model checker \
designed to verify large concurrent systems \
for which standard verification techniques do not scale.";
#[derive(Parser)]
#[deny(missing_docs)]
#[command(version, about, long_about=LONG_ABOUT)]
pub struct Cli {
#[arg(value_hint = clap::ValueHint::AnyPath)]
model: PathBuf,
#[arg(short, long, value_enum)]
format: Option<Format>,
#[command(flatten)]
pub verbosity: clap_verbosity_flag::Verbosity,
#[command(subcommand)]
command: Commands,
}
impl Cli {
pub fn run(self) -> anyhow::Result<()> {
let model = std::path::absolute(&self.model)?
.file_name()
.and_then(std::ffi::OsStr::to_str)
.unwrap_or("model")
.to_owned();
if let Some(format) = self.format {
match format {
Format::Scxml => self.run_scxml(&model),
Format::Promela => self.run_promela(&model),
Format::Jani => self.run_jani(&model),
}
} else if self.model.is_dir() {
self.run_scxml(&model)
} else {
let ext = self
.model
.extension()
.ok_or(anyhow!("file extension unknown"))?;
match ext
.to_str()
.ok_or(anyhow!("file extension not recognized"))?
{
"xml" => self.run_scxml(&model),
"jani" => self.run_jani(&model),
"pml" | "prm" => self.run_promela(&model),
_ => bail!("unsupported file format"),
}
}
}
fn run_scxml(self, model: &str) -> anyhow::Result<()> {
use scan_scxml::*;
match self.command {
Commands::Verify {
mut args,
progress,
json,
} => {
args.validate()?;
println!("processing model '{model}' started");
let (scan_def, scxml_model) = load(&self.model, &args.properties, args.all)?;
println!("processing model '{model}' completed successfully");
validate_properties(&args.properties, &scxml_model.guarantees)?;
args.properties = scxml_model.guarantees.clone();
run_verification::<CsModel, PmtlOracle>(model, &args, progress, &scan_def)
.print(json);
}
Commands::Validate => {
println!("processing model '{model}' started");
let (_scan, _scxml_model) = load(&self.model, &[], true)?;
println!("model '{model}' successfully validated");
}
Commands::Trace(mut args) => {
println!("processing model '{model}' started");
let (scan_def, scxml_model) = load(&self.model, &args.properties, args.all)?;
println!("processing model '{model}' completed");
validate_properties(&args.properties, &scxml_model.guarantees)?;
args.properties = scxml_model.guarantees.clone();
let scxml_model = Arc::new(scxml_model);
let tracer = TracePrinter::new(&scxml_model);
println!("trace computation started");
args.trace::<CsModel, PmtlOracle, _>(&scan_def, tracer);
println!("trace computation completed");
}
}
Ok(())
}
fn run_jani(self, model: &str) -> anyhow::Result<()> {
use scan_jani::*;
match self.command {
Commands::Verify {
mut args,
progress,
json,
} => {
args.validate()?;
println!("processing model '{model}' started");
let properties = args.properties.clone();
let (scan, jani_model) = load(&self.model, &properties)?;
println!("processing model '{model}' completed successfully");
validate_properties(&args.properties, &jani_model.guarantees)?;
args.properties = jani_model.guarantees.clone();
run_verification::<PgModel, MtlOracle>(model, &args, progress, &scan).print(json);
}
Commands::Validate => {
println!("processing model '{model}' started");
let (_scan, _jani_model) = load(&self.model, &[])?;
println!("model '{model}' successfully validated");
}
Commands::Trace(args) => {
args.validate()?;
println!("processing model '{model}' started");
let (scan, jani_model) = load(&self.model, &[])?;
println!("processing model '{model}' completed successfully");
let jani_model = Arc::new(jani_model);
let tracer = TracePrinter::new(jani_model);
println!("trace computation started");
args.trace::<PgModel, MtlOracle, _>(&scan, tracer);
println!("trace computation completed");
}
}
Ok(())
}
fn run_promela(self, model: &str) -> anyhow::Result<()> {
use scan_promela::*;
match self.command {
Commands::Verify {
args,
progress,
json,
} => {
args.validate()?;
println!("processing model '{model}' started");
let properties = args.properties.clone();
let (scan, _promela_model) = load(&self.model, &properties, args.all)?;
println!("processing model '{model}' completed");
run_verification::<CsModel, PmtlOracle>(model, &args, progress, &scan).print(json);
}
Commands::Validate => {
println!("processing model '{model}' started");
let (_scan, _jani_model) = load(&self.model, &[], true)?;
println!("model '{model}' successfully validated");
}
Commands::Trace(args) => {
args.validate()?;
println!("processing model '{model}' started");
let (_scan, _promela_model) = load(&self.model, &[], args.all)?;
println!("processing model '{model}' completed");
}
}
Ok(())
}
}
fn validate_properties(props: &[String], all_props: &[String]) -> anyhow::Result<()> {
if let Some(prop) = props.iter().find(|prop| !all_props.contains(prop)) {
Err(anyhow!(
"no property named '{prop}' found in model.\n\nHint: maybe it is misspelled?"
))
} else {
Ok(())
}
}
fn run_verification<'a, Ts, O>(
model: &str,
args: &VerifyArgs,
progress: Option<Bar>,
scan: &'a Scan<Ts, O>,
) -> Report
where
Ts: 'a + TransitionSystemGenerator + Sync,
O: 'a + Oracle + Clone + Sync,
Ts::Ts<'a>: Clone + Sync,
{
println!(
"Verifying {model} (-p {} -c {} -d {}) {:?}",
args.precision, args.confidence, args.duration, args.properties
);
if let Some(bar) = progress {
std::thread::scope(|s| {
s.spawn(|| {
bar.print_progress_bar::<Ts, O>(
args.confidence,
args.precision,
&args.properties,
scan,
);
});
args.verify::<Ts, O>(model.to_owned(), scan)
})
} else {
args.verify::<Ts, O>(model.to_owned(), scan)
}
}
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert();
}