use anyhow::{bail, Context, Result};
use std::path::Path;
use std::process::Command;
pub fn verify_self_contained(actor_path: &Path) -> Result<()> {
let validate = Command::new("wasm-tools")
.arg("validate")
.arg(actor_path)
.output()
.context("failed to run `wasm-tools validate` — is `wasm-tools` on PATH?")?;
if !validate.status.success() {
bail!(
"actor wasm failed `wasm-tools validate`:\n{}",
String::from_utf8_lossy(&validate.stderr)
);
}
let printed = Command::new("wasm-tools")
.arg("print")
.arg(actor_path)
.output()
.context("failed to run `wasm-tools print` — is `wasm-tools` on PATH?")?;
if !printed.status.success() {
bail!(
"`wasm-tools print` failed:\n{}",
String::from_utf8_lossy(&printed.stderr)
);
}
let wat = String::from_utf8_lossy(&printed.stdout);
let offenders = non_host_imports(&wat);
if !offenders.is_empty() {
bail!(
"actor is NOT self-contained: found imports other than host \
`theater:simple/*` — memory or the allocator was not internalized \
(was it built plain with packr-guest 0.11.0, or did an old \
--import-memory member slip in?):\n {}",
offenders.join("\n ")
);
}
Ok(())
}
fn non_host_imports(wat: &str) -> Vec<String> {
let mut offenders = Vec::new();
for line in wat.lines() {
let l = line.trim_start();
if let Some(rest) = l.strip_prefix("(import \"") {
let module = rest.split('"').next().unwrap_or("");
if !module.starts_with("theater:simple/") {
offenders.push(l.trim_end().to_string());
}
}
}
offenders
}
#[cfg(test)]
mod tests {
use super::non_host_imports;
#[test]
fn accepts_only_host_imports() {
let wat = r#"
(module
(import "theater:simple/runtime" "log" (func (param i32 i32)))
(import "theater:simple/message-server-host" "register" (func (result i32)))
(func $f)
)"#;
assert!(non_host_imports(wat).is_empty());
}
#[test]
fn flags_imported_memory() {
let wat = r#"
(module
(import "env" "memory" (memory 1))
(import "theater:simple/runtime" "log" (func (param i32 i32)))
)"#;
let bad = non_host_imports(wat);
assert_eq!(bad.len(), 1);
assert!(bad[0].contains("\"env\""), "got: {:?}", bad);
}
#[test]
fn flags_imported_allocator() {
let wat = r#"(module
(import "pack:alloc" "alloc" (func (param i32) (result i32)))
)"#;
let bad = non_host_imports(wat);
assert_eq!(bad.len(), 1);
assert!(bad[0].contains("pack:alloc"), "got: {:?}", bad);
}
#[test]
fn ignores_non_import_lines_mentioning_import() {
let wat = r#"(module
(; import is a great feature ;)
(export "handle-send" (func 0))
)"#;
assert!(non_host_imports(wat).is_empty());
}
}