use serde::{Deserialize, Serialize};
use std::sync::RwLock;
pub const DEFAULT_PARSE_TIMEOUT_MS: u64 = 10_000;
pub const DEFAULT_MAX_ORIGINS: u32 = 32;
pub const MIN_MAX_ORIGINS: u32 = 1;
pub const DEFAULT_MAX_POINTSTO: u32 = 32;
pub const MIN_MAX_POINTSTO: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct SymexOptions {
pub enabled: bool,
pub cross_file: bool,
pub interprocedural: bool,
pub smt: bool,
}
impl Default for SymexOptions {
fn default() -> Self {
Self {
enabled: true,
cross_file: true,
interprocedural: true,
smt: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct AnalysisOptions {
pub constraint_solving: bool,
pub abstract_interpretation: bool,
pub context_sensitive: bool,
pub symex: SymexOptions,
pub backwards_analysis: bool,
pub parse_timeout_ms: u64,
pub max_origins: u32,
pub max_pointsto: u32,
}
impl Default for AnalysisOptions {
fn default() -> Self {
Self {
constraint_solving: true,
abstract_interpretation: true,
context_sensitive: true,
symex: SymexOptions::default(),
backwards_analysis: false,
parse_timeout_ms: DEFAULT_PARSE_TIMEOUT_MS,
max_origins: DEFAULT_MAX_ORIGINS,
max_pointsto: DEFAULT_MAX_POINTSTO,
}
}
}
static RUNTIME: RwLock<Option<AnalysisOptions>> = RwLock::new(None);
pub fn install(opts: AnalysisOptions) -> bool {
let mut guard = RUNTIME.write().expect("analysis options RwLock poisoned");
if guard.is_some() {
return false;
}
*guard = Some(opts);
true
}
pub fn reinstall(opts: AnalysisOptions) {
*RUNTIME.write().expect("analysis options RwLock poisoned") = Some(opts);
}
pub fn current() -> AnalysisOptions {
if let Some(rt) = *RUNTIME.read().expect("analysis options RwLock poisoned") {
return rt;
}
AnalysisOptions {
constraint_solving: env_bool_default("NYX_CONSTRAINT", true),
abstract_interpretation: env_bool_default("NYX_ABSTRACT_INTERP", true),
context_sensitive: env_bool_default("NYX_CONTEXT_SENSITIVE", true),
symex: SymexOptions {
enabled: env_bool_default("NYX_SYMEX", true),
cross_file: env_bool_default("NYX_CROSS_FILE_SYMEX", true),
interprocedural: env_bool_default("NYX_SYMEX_INTERPROC", true),
smt: env_bool_default("NYX_SMT", true),
},
backwards_analysis: env_bool_default("NYX_BACKWARDS", false),
parse_timeout_ms: env_u64_default("NYX_PARSE_TIMEOUT_MS", DEFAULT_PARSE_TIMEOUT_MS),
max_origins: env_u32_default("NYX_MAX_ORIGINS", DEFAULT_MAX_ORIGINS).max(MIN_MAX_ORIGINS),
max_pointsto: env_u32_default("NYX_MAX_POINTSTO", DEFAULT_MAX_POINTSTO)
.max(MIN_MAX_POINTSTO),
}
}
fn env_bool_default(key: &str, default: bool) -> bool {
match std::env::var(key) {
Ok(v) => !(v == "0" || v.eq_ignore_ascii_case("false")),
Err(_) => default,
}
}
fn env_u64_default(key: &str, default: u64) -> u64 {
match std::env::var(key) {
Ok(v) => v.parse::<u64>().unwrap_or(default),
Err(_) => default,
}
}
fn env_u32_default(key: &str, default: u32) -> u32 {
match std::env::var(key) {
Ok(v) => v.parse::<u32>().unwrap_or(default),
Err(_) => default,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_documented() {
let opts = AnalysisOptions::default();
assert!(opts.constraint_solving);
assert!(opts.abstract_interpretation);
assert!(opts.context_sensitive);
assert!(opts.symex.enabled);
assert!(opts.symex.cross_file);
assert!(opts.symex.interprocedural);
assert!(opts.symex.smt);
assert!(!opts.backwards_analysis, "backwards analysis defaults off");
assert_eq!(opts.parse_timeout_ms, DEFAULT_PARSE_TIMEOUT_MS);
assert_eq!(opts.max_origins, DEFAULT_MAX_ORIGINS);
assert_eq!(opts.max_pointsto, DEFAULT_MAX_POINTSTO);
}
#[test]
fn toml_roundtrip() {
let opts = AnalysisOptions {
constraint_solving: false,
abstract_interpretation: true,
context_sensitive: false,
symex: SymexOptions {
enabled: true,
cross_file: false,
interprocedural: true,
smt: false,
},
backwards_analysis: true,
parse_timeout_ms: 5_000,
max_origins: 64,
max_pointsto: 48,
};
let s = toml::to_string(&opts).unwrap();
let back: AnalysisOptions = toml::from_str(&s).unwrap();
assert_eq!(opts, back);
}
}