#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ContractProfile {
Universal,
Rust,
Pmat,
Stack { manifest_path: PathBuf },
Custom { claim_ids: Vec<String> },
}
impl ContractProfile {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn detect(project_path: &Path) -> Self {
let config_path = project_path.join(".pmat-work").join("config.toml");
if config_path.exists() {
if let Ok(config) = DbcConfig::load_from_path(&config_path) {
if let Some(profile) = config.profile_override {
return profile;
}
}
}
let stack_path = project_path.join(".dbc-stack.toml");
if stack_path.exists() {
return ContractProfile::Stack {
manifest_path: stack_path,
};
}
let pmat_db = project_path.join(".pmat").join("context.db");
let pmat_idx = project_path.join(".pmat").join("context.idx");
if pmat_db.exists() || pmat_idx.exists() {
return ContractProfile::Pmat;
}
if project_path.join("Cargo.toml").exists() {
return ContractProfile::Rust;
}
if project_path.join(".git").exists() {
return ContractProfile::Universal;
}
ContractProfile::Universal
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn name(&self) -> &str {
match self {
ContractProfile::Universal => "Universal",
ContractProfile::Rust => "Rust",
ContractProfile::Pmat => "Pmat",
ContractProfile::Stack { .. } => "Stack",
ContractProfile::Custom { .. } => "Custom",
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn required_tools(&self) -> Vec<RequiredTool> {
match self {
ContractProfile::Universal => vec![RequiredTool {
name: "git".to_string(),
claim_id: "require.compiles".to_string(),
install_hint: "Install git from https://git-scm.com".to_string(),
}],
ContractProfile::Rust => vec![
RequiredTool {
name: "cargo".to_string(),
claim_id: "require.compiles".to_string(),
install_hint: "Install Rust from https://rustup.rs".to_string(),
},
RequiredTool {
name: "cargo-clippy".to_string(),
claim_id: "invariant.lint".to_string(),
install_hint: "rustup component add clippy".to_string(),
},
RequiredTool {
name: "cargo-llvm-cov".to_string(),
claim_id: "ensure.coverage".to_string(),
install_hint: "cargo +nightly install cargo-llvm-cov".to_string(),
},
RequiredTool {
name: "cargo-audit".to_string(),
claim_id: "ensure.supply_chain".to_string(),
install_hint: "cargo install cargo-audit".to_string(),
},
],
ContractProfile::Pmat => vec![
RequiredTool {
name: "cargo".to_string(),
claim_id: "require.compiles".to_string(),
install_hint: "Install Rust from https://rustup.rs".to_string(),
},
RequiredTool {
name: "cargo-clippy".to_string(),
claim_id: "invariant.lint".to_string(),
install_hint: "rustup component add clippy".to_string(),
},
RequiredTool {
name: "cargo-llvm-cov".to_string(),
claim_id: "ensure.coverage".to_string(),
install_hint: "cargo +nightly install cargo-llvm-cov".to_string(),
},
RequiredTool {
name: "cargo-audit".to_string(),
claim_id: "ensure.supply_chain".to_string(),
install_hint: "cargo install cargo-audit".to_string(),
},
RequiredTool {
name: "pmat".to_string(),
claim_id: "invariant.satd".to_string(),
install_hint: "cargo install pmat".to_string(),
},
],
ContractProfile::Stack { .. } | ContractProfile::Custom { .. } => {
vec![]
}
}
}
}
#[derive(Debug, Clone)]
pub struct RequiredTool {
pub name: String,
pub claim_id: String,
pub install_hint: String,
}
impl RequiredTool {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn is_available(&self) -> bool {
which_tool(&self.name)
}
}
#[derive(Debug, Clone)]
pub struct MissingTool {
pub name: String,
pub claim_id: String,
pub install_hint: String,
}
fn which_tool(name: &str) -> bool {
match name {
"cargo-clippy" => {
std::process::Command::new("cargo")
.args(["clippy", "--version"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
"cargo-llvm-cov" => {
std::process::Command::new("cargo")
.args(["llvm-cov", "--version"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
"cargo-audit" => {
std::process::Command::new("cargo")
.args(["audit", "--version"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
_ => {
std::process::Command::new("which")
.arg(name)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn check_toolchain(profile: &ContractProfile, _project_path: &Path) -> Vec<MissingTool> {
profile
.required_tools()
.into_iter()
.filter(|tool| !tool.is_available())
.map(|tool| MissingTool {
name: tool.name,
claim_id: tool.claim_id,
install_hint: tool.install_hint,
})
.collect()
}
#[derive(Debug, Clone, Default)]
pub struct DbcConfig {
pub profile_override: Option<ContractProfile>,
pub thresholds: DbcThresholdOverrides,
pub rescue_enabled: Option<bool>,
pub pre_commit_hook: bool,
}
#[derive(Debug, Clone, Default)]
pub struct DbcThresholdOverrides {
pub coverage_pct: Option<f64>,
pub max_complexity: Option<u32>,
pub max_file_lines: Option<usize>,
}
impl DbcConfig {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn load(project_path: &Path) -> Self {
let config_path = project_path.join(".pmat-work").join("config.toml");
Self::load_from_path(&config_path).unwrap_or_default()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn load_from_path(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read config: {}", path.display()))?;
Self::parse_toml(&content)
}
fn parse_toml(content: &str) -> Result<Self> {
let table: toml::Table = content
.parse()
.context("Failed to parse config.toml")?;
let mut config = DbcConfig::default();
if let Some(dbc) = table.get("dbc").and_then(|v| v.as_table()) {
if let Some(profile_str) = dbc.get("profile").and_then(|v| v.as_str()) {
config.profile_override = match profile_str {
"universal" => Some(ContractProfile::Universal),
"rust" => Some(ContractProfile::Rust),
"pmat" => Some(ContractProfile::Pmat),
"custom" => {
let claims = dbc
.get("claims")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
Some(ContractProfile::Custom { claim_ids: claims })
}
_ => None,
};
}
if let Some(thresholds) = dbc.get("thresholds").and_then(|v| v.as_table()) {
if let Some(cov) = thresholds.get("coverage_pct").and_then(|v| v.as_float()) {
config.thresholds.coverage_pct = Some(cov);
}
if let Some(cx) = thresholds.get("max_complexity").and_then(|v| v.as_integer()) {
config.thresholds.max_complexity = Some(cx as u32);
}
if let Some(fl) = thresholds.get("max_file_lines").and_then(|v| v.as_integer()) {
config.thresholds.max_file_lines = Some(fl as usize);
}
}
if let Some(rescue) = dbc.get("rescue").and_then(|v| v.as_table()) {
if let Some(enabled) = rescue.get("enabled").and_then(|v| v.as_bool()) {
config.rescue_enabled = Some(enabled);
}
}
if let Some(checkpoints) = dbc.get("checkpoints").and_then(|v| v.as_table()) {
if let Some(hook) = checkpoints.get("pre_commit_hook").and_then(|v| v.as_bool()) {
config.pre_commit_hook = hook;
}
}
}
Ok(config)
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn claims_for_profile(
profile: &ContractProfile,
config: &DbcConfig,
) -> Vec<ContractClause> {
let max_complexity = config.thresholds.max_complexity.unwrap_or(20) as f64;
let max_file_lines = config.thresholds.max_file_lines.unwrap_or(500) as f64;
let coverage_pct = config.thresholds.coverage_pct.unwrap_or(95.0);
match profile {
ContractProfile::Universal => universal_claims(max_file_lines),
ContractProfile::Rust => rust_claims(max_complexity, max_file_lines, coverage_pct),
ContractProfile::Pmat => pmat_claims(max_complexity, max_file_lines, coverage_pct),
ContractProfile::Custom { claim_ids } => {
let all = pmat_claims(max_complexity, max_file_lines, coverage_pct);
all.into_iter()
.filter(|c| claim_ids.contains(&c.id))
.collect()
}
ContractProfile::Stack { manifest_path } => {
match std::fs::read_to_string(manifest_path) {
Ok(content) => match StackManifest::parse(&content) {
Ok(manifest) => manifest.resolve_base_claims(config),
Err(_) => universal_claims(max_file_lines),
},
Err(_) => universal_claims(max_file_lines),
}
}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn classify_claims(
clauses: &[ContractClause],
) -> (Vec<ContractClause>, Vec<ContractClause>, Vec<ContractClause>) {
let mut require = Vec::new();
let mut ensure = Vec::new();
let mut invariant = Vec::new();
for clause in clauses {
match clause.kind {
ClauseKind::Require => require.push(clause.clone()),
ClauseKind::Ensure => ensure.push(clause.clone()),
ClauseKind::Invariant => invariant.push(clause.clone()),
}
}
(require, ensure, invariant)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn apply_exclusions(
clauses: Vec<ContractClause>,
without: &[String],
) -> (Vec<ContractClause>, Vec<ExcludedClaim>) {
let (excluded_clauses, active): (Vec<_>, Vec<_>) =
clauses.into_iter().partition(|c| without.contains(&c.id));
let excluded = excluded_clauses
.into_iter()
.map(|c| ExcludedClaim {
flag: format!("--without {}", c.id),
id: c.id,
reason: "developer_excluded".to_string(),
})
.collect();
(active, excluded)
}