use crate::backend::SafetyBounds;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct SafetyManifest {
pub synth_version: String,
pub target_triple: String,
pub safety_bounds: SafetyBounds,
pub safety_div_zero: bool,
pub safety_div_overflow: bool,
pub linear_memory_bytes: u32,
}
impl SafetyManifest {
pub fn to_json(&self) -> String {
let mut out = String::new();
out.push_str("{\n");
out.push_str(&format!(
" \"synth_version\": {},\n",
json_string(&self.synth_version)
));
out.push_str(&format!(
" \"target_triple\": {},\n",
json_string(&self.target_triple)
));
out.push_str(&format!(
" \"safety_bounds\": {},\n",
json_string(self.safety_bounds.as_str())
));
out.push_str(&format!(
" \"safety_div_zero\": {},\n",
self.safety_div_zero
));
out.push_str(&format!(
" \"safety_div_overflow\": {},\n",
self.safety_div_overflow
));
out.push_str(&format!(
" \"linear_memory_bytes\": {}\n",
self.linear_memory_bytes
));
out.push_str("}\n");
out
}
pub fn sidecar_path(elf_path: &Path) -> PathBuf {
let mut p = elf_path.to_path_buf();
let stem = elf_path
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "out".to_string());
p.set_file_name(format!("{}.safety-manifest.json", stem));
p
}
}
fn json_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => {
out.push_str(&format!("\\u{:04x}", c as u32));
}
c => out.push(c),
}
}
out.push('"');
out
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn sample() -> SafetyManifest {
SafetyManifest {
synth_version: "0.3.1".to_string(),
target_triple: "thumbv7em-none-eabi".to_string(),
safety_bounds: SafetyBounds::Mpu,
safety_div_zero: true,
safety_div_overflow: true,
linear_memory_bytes: 65536,
}
}
#[test]
fn sidecar_path_strips_elf_extension() {
let p = SafetyManifest::sidecar_path(&PathBuf::from("/tmp/foo.elf"));
assert_eq!(p, PathBuf::from("/tmp/foo.safety-manifest.json"));
}
#[test]
fn sidecar_path_handles_missing_extension() {
let p = SafetyManifest::sidecar_path(&PathBuf::from("out"));
assert_eq!(p, PathBuf::from("out.safety-manifest.json"));
}
#[test]
fn json_round_trip_shape() {
let json = sample().to_json();
assert!(json.contains("\"synth_version\": \"0.3.1\""));
assert!(json.contains("\"target_triple\": \"thumbv7em-none-eabi\""));
assert!(json.contains("\"safety_bounds\": \"mpu\""));
assert!(json.contains("\"safety_div_zero\": true"));
assert!(json.contains("\"safety_div_overflow\": true"));
assert!(json.contains("\"linear_memory_bytes\": 65536"));
}
#[test]
fn json_escapes_quotes_in_triple() {
let m = SafetyManifest {
target_triple: "weird-\"quoted\"-triple".to_string(),
..sample()
};
let json = m.to_json();
assert!(json.contains("weird-\\\"quoted\\\"-triple"));
}
#[test]
fn json_none_bounds_serialises() {
let m = SafetyManifest {
safety_bounds: SafetyBounds::None,
..sample()
};
assert!(m.to_json().contains("\"safety_bounds\": \"none\""));
}
}