#![deny(missing_docs)]
#[cfg(fuzzing)]
pub mod dummy;
#[cfg(not(fuzzing))]
mod dummy;
mod info;
mod instrument;
mod parse;
mod rewrite;
mod snapshot;
mod stack_ext;
mod translate;
use wasmparser::WasmFeatures;
pub use wasmtime;
use anyhow::Context;
use dummy::dummy_imports;
use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use std::path::PathBuf;
use std::rc::Rc;
#[cfg(feature = "structopt")]
use structopt::StructOpt;
use wasmtime::{Engine, Extern};
use wasmtime_wasi::{
preview1::{self, WasiP1Ctx},
WasiCtxBuilder,
};
const DEFAULT_INHERIT_STDIO: bool = true;
const DEFAULT_INHERIT_ENV: bool = false;
const DEFAULT_KEEP_INIT_FUNC: bool = false;
const DEFAULT_WASM_MULTI_VALUE: bool = true;
const DEFAULT_WASM_MULTI_MEMORY: bool = true;
const DEFAULT_WASM_BULK_MEMORY: bool = true;
const DEFAULT_WASM_SIMD: bool = true;
const DEFAULT_WASM_REFERENCE_TYPES: bool = true;
pub struct StoreData {
pub wasi_ctx: Option<WasiP1Ctx>,
}
pub(crate) type Store = wasmtime::Store<StoreData>;
pub type Linker = wasmtime::Linker<StoreData>;
#[cfg(feature = "structopt")]
fn parse_map_dirs(s: &str) -> anyhow::Result<(String, PathBuf)> {
let parts: Vec<&str> = s.split("::").collect();
if parts.len() != 2 {
anyhow::bail!("must contain exactly one double colon ('::')");
}
Ok((parts[0].into(), parts[1].into()))
}
#[cfg_attr(feature = "structopt", derive(StructOpt))]
#[derive(Clone)]
pub struct Wizer {
#[cfg_attr(
feature = "structopt",
structopt(short = "f", long = "init-func", default_value = "wizer.initialize")
)]
init_func: String,
#[cfg_attr(
feature = "structopt",
structopt(
short = "r",
long = "rename-func",
alias = "func-rename",
value_name = "dst=src"
)
)]
func_renames: Vec<String>,
#[cfg_attr(feature = "structopt", structopt(long = "allow-wasi"))]
allow_wasi: bool,
#[cfg_attr(feature = "structopt", structopt(long = "preload"))]
preload: Vec<String>,
#[cfg_attr(feature = "structopt", structopt(skip))]
preload_bytes: Vec<(String, Vec<u8>)>,
#[cfg_attr(feature = "structopt", structopt(skip))]
make_linker: Option<Rc<dyn Fn(&wasmtime::Engine) -> anyhow::Result<Linker>>>,
#[cfg_attr(
feature = "structopt",
structopt(long = "inherit-stdio", value_name = "true|false")
)]
inherit_stdio: Option<bool>,
#[cfg_attr(
feature = "structopt",
structopt(long = "inherit-env", value_name = "true|false")
)]
inherit_env: Option<bool>,
#[cfg_attr(
feature = "structopt",
structopt(long = "keep-init-func", value_name = "true|false")
)]
keep_init_func: Option<bool>,
#[cfg_attr(
feature = "structopt",
structopt(long = "dir", parse(from_os_str), value_name = "directory")
)]
dirs: Vec<PathBuf>,
#[cfg_attr(
feature = "structopt",
structopt(long = "mapdir", value_name = "GUEST_DIR::HOST_DIR", parse(try_from_str = parse_map_dirs))
)]
map_dirs: Vec<(String, PathBuf)>,
#[cfg_attr(feature = "structopt", structopt(long, value_name = "true|false"))]
wasm_multi_memory: Option<bool>,
#[cfg_attr(feature = "structopt", structopt(long, value_name = "true|false"))]
wasm_multi_value: Option<bool>,
#[cfg_attr(feature = "structopt", structopt(long, value_name = "true|false"))]
wasm_bulk_memory: Option<bool>,
#[cfg_attr(feature = "structopt", structopt(long, value_name = "true|false"))]
wasm_simd: Option<bool>,
#[cfg_attr(feature = "structopt", structopt(long, value_name = "true|false"))]
wasm_reference_types: Option<bool>,
}
impl std::fmt::Debug for Wizer {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let Wizer {
init_func,
func_renames,
allow_wasi,
preload,
preload_bytes,
make_linker: _,
inherit_stdio,
inherit_env,
keep_init_func,
dirs,
map_dirs,
wasm_multi_memory,
wasm_multi_value,
wasm_bulk_memory,
wasm_simd,
wasm_reference_types,
} = self;
f.debug_struct("Wizer")
.field("init_func", &init_func)
.field("func_renames", &func_renames)
.field("allow_wasi", &allow_wasi)
.field("preload", &preload)
.field("preload_bytes", &preload_bytes)
.field("make_linker", &"..")
.field("inherit_stdio", &inherit_stdio)
.field("inherit_env", &inherit_env)
.field("keep_init_func", &keep_init_func)
.field("dirs", &dirs)
.field("map_dirs", &map_dirs)
.field("wasm_multi_memory", &wasm_multi_memory)
.field("wasm_multi_value", &wasm_multi_value)
.field("wasm_bulk_memory", &wasm_bulk_memory)
.field("wasm_simd", &wasm_simd)
.field("wasm_reference_types", &wasm_reference_types)
.finish()
}
}
struct FuncRenames {
rename_src_to_dst: HashMap<String, String>,
rename_dsts: HashSet<String>,
}
impl FuncRenames {
fn parse(renames: &Vec<String>) -> anyhow::Result<FuncRenames> {
let mut ret = FuncRenames {
rename_src_to_dst: HashMap::new(),
rename_dsts: HashSet::new(),
};
if renames.is_empty() {
return Ok(ret);
}
for rename_spec in renames {
let equal = rename_spec
.trim()
.find('=')
.ok_or_else(|| anyhow::anyhow!("Invalid function rename part: {}", rename_spec))?;
let dst = rename_spec[..equal].to_owned();
let src = rename_spec[equal + 1..].to_owned();
if ret.rename_dsts.contains(&dst) {
anyhow::bail!("Duplicated function rename dst {}", dst);
}
if ret.rename_src_to_dst.contains_key(&src) {
anyhow::bail!("Duplicated function rename src {}", src);
}
ret.rename_dsts.insert(dst.clone());
ret.rename_src_to_dst.insert(src, dst);
}
Ok(ret)
}
}
impl Wizer {
pub fn new() -> Self {
Wizer {
init_func: "wizer.initialize".into(),
func_renames: vec![],
allow_wasi: false,
preload: vec![],
preload_bytes: vec![],
make_linker: None,
inherit_stdio: None,
inherit_env: None,
keep_init_func: None,
dirs: vec![],
map_dirs: vec![],
wasm_multi_memory: None,
wasm_multi_value: None,
wasm_bulk_memory: None,
wasm_simd: None,
wasm_reference_types: None,
}
}
pub fn init_func(&mut self, init_func: impl Into<String>) -> &mut Self {
self.init_func = init_func.into();
self
}
pub fn func_rename(&mut self, new_name: impl Display, old_name: impl Display) -> &mut Self {
self.func_renames.push(format!("{}={}", new_name, old_name));
self
}
pub fn allow_wasi(&mut self, allow: bool) -> anyhow::Result<&mut Self> {
anyhow::ensure!(
self.make_linker.is_none(),
"Cannot use 'allow_wasi' with a custom linker"
);
self.allow_wasi = allow;
Ok(self)
}
pub fn preload(&mut self, name: &str, filename: &str) -> anyhow::Result<&mut Self> {
anyhow::ensure!(
self.make_linker.is_none(),
"Cannot use 'preload' with a custom linker"
);
anyhow::ensure!(
!name.contains("="),
"Module name cannot contain an `=` character"
);
self.preload.push(format!("{}={}", name, filename));
Ok(self)
}
pub fn preload_bytes(
&mut self,
name: &str,
module_bytes: Vec<u8>,
) -> anyhow::Result<&mut Self> {
anyhow::ensure!(
self.make_linker.is_none(),
"Cannot use 'preload_bytes' with a custom linker"
);
self.preload_bytes.push((name.to_owned(), module_bytes));
Ok(self)
}
pub fn make_linker(
&mut self,
make_linker: Option<Rc<dyn Fn(&wasmtime::Engine) -> anyhow::Result<Linker>>>,
) -> anyhow::Result<&mut Self> {
anyhow::ensure!(
!self.allow_wasi,
"Cannot use 'allow_wasi' with a custom linker"
);
self.make_linker = make_linker;
Ok(self)
}
pub fn inherit_stdio(&mut self, inherit: bool) -> &mut Self {
self.inherit_stdio = Some(inherit);
self
}
pub fn inherit_env(&mut self, inherit: bool) -> &mut Self {
self.inherit_env = Some(inherit);
self
}
pub fn keep_init_func(&mut self, keep: bool) -> &mut Self {
self.keep_init_func = Some(keep);
self
}
pub fn dir(&mut self, directory: impl Into<PathBuf>) -> &mut Self {
self.dirs.push(directory.into());
self
}
pub fn map_dir(
&mut self,
guest_dir: impl Into<String>,
host_dir: impl Into<PathBuf>,
) -> &mut Self {
self.map_dirs.push((guest_dir.into(), host_dir.into()));
self
}
pub fn wasm_multi_memory(&mut self, enable: bool) -> &mut Self {
self.wasm_multi_memory = Some(enable);
self
}
pub fn wasm_multi_value(&mut self, enable: bool) -> &mut Self {
self.wasm_multi_value = Some(enable);
self
}
pub fn wasm_bulk_memory(&mut self, enable: bool) -> &mut Self {
self.wasm_bulk_memory = Some(enable);
self
}
pub fn wasm_simd(&mut self, enable: bool) -> &mut Self {
self.wasm_simd = Some(enable);
self
}
pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self {
self.wasm_reference_types = Some(enable);
self
}
pub fn run(&self, wasm: &[u8]) -> anyhow::Result<Vec<u8>> {
let renames = FuncRenames::parse(&self.func_renames)?;
self.wasm_validate(&wasm)?;
let mut cx = parse::parse(wasm)?;
let instrumented_wasm = instrument::instrument(&cx);
if cfg!(debug_assertions) {
if let Err(error) = self.wasm_validate(&instrumented_wasm) {
#[cfg(feature = "wasmprinter")]
let wat = wasmprinter::print_bytes(&wasm)
.unwrap_or_else(|e| format!("Disassembling to WAT failed: {}", e));
#[cfg(not(feature = "wasmprinter"))]
let wat = "`wasmprinter` cargo feature is not enabled".to_string();
panic!(
"instrumented Wasm is not valid: {:?}\n\nWAT:\n{}",
error, wat
);
}
}
let config = self.wasmtime_config()?;
let engine = wasmtime::Engine::new(&config)?;
let wasi_ctx = self.wasi_context()?;
let mut store = wasmtime::Store::new(&engine, StoreData { wasi_ctx });
let module = wasmtime::Module::new(&engine, &instrumented_wasm)
.context("failed to compile the Wasm module")?;
self.validate_init_func(&module)?;
let (instance, has_wasi_initialize) = self.initialize(&engine, &mut store, &module)?;
let snapshot = snapshot::snapshot(&mut store, &instance);
let rewritten_wasm = self.rewrite(
&mut cx,
&mut store,
&snapshot,
&renames,
has_wasi_initialize,
);
if cfg!(debug_assertions) {
if let Err(error) = self.wasm_validate(&rewritten_wasm) {
#[cfg(feature = "wasmprinter")]
let wat = wasmprinter::print_bytes(&wasm)
.unwrap_or_else(|e| format!("Disassembling to WAT failed: {}", e));
#[cfg(not(feature = "wasmprinter"))]
let wat = "`wasmprinter` cargo feature is not enabled".to_string();
panic!("rewritten Wasm is not valid: {:?}\n\nWAT:\n{}", error, wat);
}
}
Ok(rewritten_wasm)
}
fn wasmtime_config(&self) -> anyhow::Result<wasmtime::Config> {
let mut config = wasmtime::Config::new();
wasmtime::Cache::from_file(None).map(|cache| config.cache(Some(cache)))?;
config.wasm_multi_memory(self.wasm_multi_memory.unwrap_or(DEFAULT_WASM_MULTI_MEMORY));
config.wasm_multi_value(self.wasm_multi_value.unwrap_or(DEFAULT_WASM_MULTI_VALUE));
config.wasm_bulk_memory(self.wasm_bulk_memory.unwrap_or(DEFAULT_WASM_BULK_MEMORY));
config.wasm_simd(self.wasm_simd.unwrap_or(DEFAULT_WASM_SIMD));
config.wasm_reference_types(
self.wasm_reference_types
.unwrap_or(DEFAULT_WASM_REFERENCE_TYPES),
);
config.wasm_tail_call(true);
config.wasm_extended_const(true);
config.wasm_threads(false);
Ok(config)
}
fn wasm_features(&self) -> wasmparser::WasmFeatures {
let mut features = WasmFeatures::WASM2;
features.set(
WasmFeatures::MULTI_MEMORY,
self.wasm_multi_memory.unwrap_or(DEFAULT_WASM_MULTI_MEMORY),
);
features.set(
WasmFeatures::MULTI_VALUE,
self.wasm_multi_value.unwrap_or(DEFAULT_WASM_MULTI_VALUE),
);
features.set(
WasmFeatures::BULK_MEMORY,
self.wasm_bulk_memory.unwrap_or(DEFAULT_WASM_BULK_MEMORY),
);
features.set(
WasmFeatures::SIMD,
self.wasm_simd.unwrap_or(DEFAULT_WASM_SIMD),
);
features.set(
WasmFeatures::REFERENCE_TYPES,
self.wasm_reference_types
.unwrap_or(DEFAULT_WASM_REFERENCE_TYPES),
);
features.set(WasmFeatures::TAIL_CALL, true);
features.set(WasmFeatures::EXTENDED_CONST, true);
return features;
}
fn wasm_validate(&self, wasm: &[u8]) -> anyhow::Result<()> {
log::debug!("Validating input Wasm");
let mut validator = wasmparser::Validator::new_with_features(self.wasm_features());
validator.validate_all(wasm)?;
let mut wasm = wasm;
let mut parsers = vec![wasmparser::Parser::new(0)];
while !parsers.is_empty() {
let payload = match parsers.last_mut().unwrap().parse(wasm, true).unwrap() {
wasmparser::Chunk::NeedMoreData(_) => unreachable!(),
wasmparser::Chunk::Parsed { consumed, payload } => {
wasm = &wasm[consumed..];
payload
}
};
match payload {
wasmparser::Payload::CodeSectionEntry(code) => {
let mut ops = code.get_operators_reader().unwrap();
while !ops.eof() {
match ops.read().unwrap() {
wasmparser::Operator::TableCopy { .. } => {
anyhow::bail!("unsupported `table.copy` instruction")
}
wasmparser::Operator::TableInit { .. } => {
anyhow::bail!("unsupported `table.init` instruction")
}
wasmparser::Operator::ElemDrop { .. } => {
anyhow::bail!("unsupported `elem.drop` instruction")
}
wasmparser::Operator::DataDrop { .. } => {
anyhow::bail!("unsupported `data.drop` instruction")
}
wasmparser::Operator::TableSet { .. } => {
anyhow::bail!("unsupported `table.set` instruction")
}
wasmparser::Operator::TableGet { .. } => {
anyhow::bail!("unsupported `table.get` instruction")
}
wasmparser::Operator::RefNull { .. } => {
anyhow::bail!("unsupported `ref.null` instruction")
}
wasmparser::Operator::RefIsNull => {
anyhow::bail!("unsupported `ref.is_null` instruction")
}
wasmparser::Operator::TypedSelect { .. } => {
anyhow::bail!("unsupported typed `select` instruction")
}
wasmparser::Operator::RefFunc { .. } => {
anyhow::bail!("unsupported `ref.func` instruction")
}
wasmparser::Operator::TableSize { .. } => {
anyhow::bail!("unsupported `table.size` instruction")
}
wasmparser::Operator::TableGrow { .. } => {
anyhow::bail!("unsupported `table.grow` instruction")
}
wasmparser::Operator::TableFill { .. } => {
anyhow::bail!("unsupported `table.fill` instruction")
}
_ => continue,
}
}
}
wasmparser::Payload::End(_) => {
parsers.pop();
}
_ => continue,
}
}
Ok(())
}
fn validate_init_func(&self, module: &wasmtime::Module) -> anyhow::Result<()> {
log::debug!("Validating the exported initialization function");
match module.get_export(&self.init_func) {
Some(wasmtime::ExternType::Func(func_ty)) => {
if func_ty.params().len() != 0 || func_ty.results().len() != 0 {
anyhow::bail!(
"the Wasm module's `{}` function export does not have type `[] -> []`",
&self.init_func
);
}
}
Some(_) => anyhow::bail!(
"the Wasm module's `{}` export is not a function",
&self.init_func
),
None => anyhow::bail!(
"the Wasm module does not have a `{}` export",
&self.init_func
),
}
Ok(())
}
fn wasi_context(&self) -> anyhow::Result<Option<WasiP1Ctx>> {
if !self.allow_wasi {
return Ok(None);
}
let mut ctx = WasiCtxBuilder::new();
if self.inherit_stdio.unwrap_or(DEFAULT_INHERIT_STDIO) {
ctx.inherit_stdio();
}
if self.inherit_env.unwrap_or(DEFAULT_INHERIT_ENV) {
ctx.inherit_env();
}
for dir in &self.dirs {
log::debug!("Preopening directory: {}", dir.display());
let guest = dir.display().to_string();
ctx.preopened_dir(
dir,
guest,
wasmtime_wasi::DirPerms::all(),
wasmtime_wasi::FilePerms::all(),
)
.with_context(|| format!("failed to open directory: {}", dir.display()))?;
}
for (guest_dir, host_dir) in &self.map_dirs {
log::debug!("Preopening directory: {guest_dir}::{}", host_dir.display());
ctx.preopened_dir(
host_dir,
guest_dir,
wasmtime_wasi::DirPerms::all(),
wasmtime_wasi::FilePerms::all(),
)
.with_context(|| format!("failed to open directory: {}", host_dir.display()))?;
}
Ok(Some(ctx.build_p1()))
}
fn do_preload(
&self,
engine: &Engine,
store: &mut Store,
linker: &mut Linker,
name: &str,
content: &[u8],
) -> anyhow::Result<()> {
let module =
wasmtime::Module::new(engine, content).context("failed to parse preload module")?;
let instance = wasmtime::Instance::new(&mut *store, &module, &[])
.context("failed to instantiate preload module")?;
linker
.instance(&mut *store, name, instance)
.context("failed to add preload's exports to linker")?;
Ok(())
}
fn initialize(
&self,
engine: &Engine,
store: &mut Store,
module: &wasmtime::Module,
) -> anyhow::Result<(wasmtime::Instance, bool)> {
log::debug!("Calling the initialization function");
let mut linker = if let Some(make_linker) = self.make_linker.as_deref() {
make_linker(store.engine()).context("failed to make custom linker")?
} else {
wasmtime::Linker::new(store.engine())
};
if self.allow_wasi {
preview1::add_to_linker_sync(&mut linker, |ctx: &mut StoreData| {
ctx.wasi_ctx.as_mut().unwrap()
})?;
}
for preload in &self.preload {
if let Some((name, value)) = preload.split_once('=') {
let content = std::fs::read(value).context("failed to read preload module")?;
self.do_preload(engine, &mut *store, &mut linker, &name[..], &content[..])?;
} else {
anyhow::bail!(
"Bad preload option: {} (must be of form `name=file`)",
preload
);
}
}
for (name, bytes) in &self.preload_bytes {
self.do_preload(engine, &mut *store, &mut linker, &name[..], &bytes[..])?;
}
dummy_imports(&mut *store, &module, &mut linker)?;
let instance = linker
.instantiate(&mut *store, module)
.context("failed to instantiate the Wasm module")?;
let mut has_wasi_initialize = false;
if let Some(export) = instance.get_export(&mut *store, "_initialize") {
if let Extern::Func(func) = export {
func.typed::<(), ()>(&store)
.and_then(|f| {
has_wasi_initialize = true;
f.call(&mut *store, ()).map_err(Into::into)
})
.context("calling the Reactor initialization function")?;
if self.init_func == "_initialize" && has_wasi_initialize {
return Ok((instance, has_wasi_initialize));
}
}
}
let init_func = instance
.get_typed_func::<(), ()>(&mut *store, &self.init_func)
.expect("checked by `validate_init_func`");
init_func
.call(&mut *store, ())
.with_context(|| format!("the `{}` function trapped", self.init_func))?;
Ok((instance, has_wasi_initialize))
}
}