use clap::{Arg, Command};
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use itertools::Itertools;
use log::LevelFilter;
use move_compiler::shared::PackagePaths;
use move_model::{
model::{FunctionEnv, GlobalEnv, VerificationScope},
options::ModelBuilderOptions,
parse_addresses_from_options, run_model_builder_with_options,
};
use move_prover::{
check_errors, cli::Options, create_and_process_bytecode, generate_boogie, verify_boogie,
};
use move_stackless_bytecode::{mutation_tester::MutationManager, options::ProverOptions};
use std::{
io::Write,
path::PathBuf,
time::{Duration, Instant},
};
struct Runner {
options: Options,
error_writer: StandardStream,
}
pub fn mutate(args: &[String]) {
let cmd_line_parser = Command::new("mutation")
.version("0.1.0")
.about("Mutation tool for the move prover")
.author("The Diem Core Contributors")
.arg(
Arg::new("addresses")
.long("address")
.short('a')
.multiple_occurrences(true)
.number_of_values(1)
.takes_value(true)
.value_name("ADDRESS")
.help("Address specified for the move prover"),
)
.arg(
Arg::new("config")
.short('c')
.long("config")
.takes_value(true)
.multiple_occurrences(true)
.number_of_values(1)
.value_name("CONFIG_PATH")
.help(
"path to a prover toml configuration file. The mutation output will be \
stored at `CONFIG_PATH.data. This can be repeated so different \
configurations can be checked against the same set of input modules.",
),
)
.arg(
Arg::new("dependencies")
.long("dependency")
.short('d')
.multiple_occurrences(true)
.number_of_values(1)
.takes_value(true)
.value_name("PATH_TO_DEPENDENCY")
.help(
"path to a Move file, or a directory which will be searched for \
Move files, containing dependencies which will not be verified",
),
)
.arg(
Arg::new("sources")
.multiple_occurrences(true)
.value_name("PATH_TO_SOURCE_FILE")
.min_values(1)
.help("the source files to verify"),
);
let matches = cmd_line_parser.get_matches_from(args);
let get_vec = |s: &str| -> Vec<String> {
match matches.values_of(s) {
Some(vs) => vs.map(|v| v.to_string()).collect(),
_ => vec![],
}
};
let addresses = get_vec("addresses");
let sources = get_vec("sources");
let deps = get_vec("dependencies");
let configs: Vec<Option<String>> = if matches.is_present("config") {
get_vec("config").into_iter().map(Some).collect_vec()
} else {
vec![None]
};
for config_spec in configs {
let (config, out) = if let Some(config_file) = &config_spec {
let extension = "mod_data";
let out = PathBuf::from(config_file)
.with_extension(extension)
.to_string_lossy()
.to_string();
(config_spec, out)
} else {
(None, "mutation.data".to_string())
};
if let Err(s) = apply_mutation(config.as_ref(), &addresses, &sources, &deps) {
println!("ERROR: execution failed: {}", s);
} else {
println!("results stored at `{}`", out);
}
}
}
fn apply_mutation(
config_file_opt: Option<&String>,
addresses: &[String],
modules: &[String],
dep_dirs: &[String],
) -> anyhow::Result<()> {
println!("building model");
let addrs = parse_addresses_from_options(addresses.to_owned())?;
let env = run_model_builder_with_options(
vec![PackagePaths {
name: None,
paths: modules.to_vec(),
named_address_map: addrs.clone(),
}],
vec![PackagePaths {
name: None,
paths: dep_dirs.to_vec(),
named_address_map: addrs,
}],
ModelBuilderOptions::default(),
)?;
let mut error_writer = StandardStream::stderr(ColorChoice::Auto);
let mut options = if let Some(config_file) = config_file_opt {
Options::create_from_toml_file(config_file)?
} else {
Options::default()
};
options.backend.hard_timeout_secs = 100;
options.verbosity_level = LevelFilter::Error;
options.prover.mutation = true;
options.backend.derive_options();
options.setup_logging();
check_errors(&env, &options, &mut error_writer, "unexpected build errors")?;
let config_descr = "default".to_string();
println!("Starting mutations with config `{}`.", config_descr);
let mut i = 0;
let mut mutation_applied = true;
let mut runner = Runner {
options,
error_writer,
};
while mutation_applied {
i += 1;
println!("Applying add-sub mutation {}", i);
runner.options.prover.mutation_add_sub = i;
env.set_extension(MutationManager {
mutated: false,
add_sub: i,
sub_add: 0,
mul_div: 0,
div_mul: 0,
});
mutation_applied = runner.mutate(&env)?;
if !mutation_applied {
println!("No mutations applied");
}
}
i = 0;
mutation_applied = true;
while mutation_applied {
i += 1;
println!("Applying sub-add mutation {}", i);
runner.options.prover.mutation_sub_add = i;
env.set_extension(MutationManager {
mutated: false,
add_sub: 0,
sub_add: i,
mul_div: 0,
div_mul: 0,
});
mutation_applied = runner.mutate(&env)?;
if !mutation_applied {
println!("No mutations applied");
}
}
i = 0;
mutation_applied = true;
while mutation_applied {
i += 1;
println!("Applying mul-div mutation {}", i);
runner.options.prover.mutation_mul_div = i;
env.set_extension(MutationManager {
mutated: false,
add_sub: 0,
sub_add: 0,
mul_div: i,
div_mul: 0,
});
mutation_applied = runner.mutate(&env)?;
if !mutation_applied {
println!("No mutations applied");
}
}
i = 0;
mutation_applied = true;
while mutation_applied {
i += 1;
println!("Applying div-mul mutation {}", i);
runner.options.prover.mutation_div_mul = i;
env.set_extension(MutationManager {
mutated: false,
add_sub: 0,
sub_add: 0,
mul_div: 0,
div_mul: i,
});
mutation_applied = runner.mutate(&env)?;
if !mutation_applied {
println!("No mutations applied");
}
}
Ok(())
}
impl Runner {
fn mutate(&mut self, env: &GlobalEnv) -> anyhow::Result<bool> {
let mut mutated = false;
for module in env.get_modules() {
if module.is_target() {
for fun in module.get_functions() {
mutated = self.mutate_function(fun)?;
if mutated {
break;
}
}
}
}
Ok(mutated)
}
fn mutate_function(&mut self, fun: FunctionEnv<'_>) -> anyhow::Result<bool> {
let env = fun.module_env.env;
self.options.prover.verify_scope = VerificationScope::Only(fun.get_full_name_str());
ProverOptions::set(env, self.options.prover.clone());
let (duration, status) = self.run_mutated_function(fun.module_env.env)?;
let mutated = env
.get_extension::<MutationManager>()
.map(|e| e.mutated)
.unwrap_or(false);
if mutated {
print!("mutated function {} ..", fun.get_full_name_str());
std::io::stdout().flush()?;
println!("\x08\x08{:.3}s {}.", duration.as_secs_f64(), status);
}
Ok(mutated)
}
fn run_mutated_function(&mut self, env: &GlobalEnv) -> anyhow::Result<(Duration, String)> {
let targets = create_and_process_bytecode(&self.options, env);
check_errors(
env,
&self.options,
&mut self.error_writer,
"unexpected transformation errors",
)?;
let code_writer = generate_boogie(env, &self.options, &targets)?;
check_errors(
env,
&self.options,
&mut self.error_writer,
"unexpected boogie generation errors",
)?;
let now = Instant::now();
verify_boogie(env, &self.options, &targets, code_writer)?;
let status = if env.error_count() > 0 {
if env.has_diag("timeout") {
"timeout"
} else {
"errors"
}
} else {
"ok"
};
env.clear_diag();
Ok((now.elapsed(), status.to_string()))
}
}