Skip to content

Commit

Permalink
Merge pull request #547 from CryZe/wasi
Browse files Browse the repository at this point in the history
  • Loading branch information
CryZe authored Jul 28, 2022
2 parents 99760ff + 7319df1 commit 526e60f
Show file tree
Hide file tree
Showing 28 changed files with 446 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ serde = { version = "1.0.98", default-features = false, features = [
"alloc",
"derive",
] }
serde_json = { version = "1.0.8", default-features = false, features = ["alloc"] }
serde_json = { version = "1.0.60", default-features = false, features = ["alloc"] }
smallstr = { version = "0.3.0", default-features = false }
snafu = { version = "0.7.0", default-features = false }
unicase = "2.6.0"
Expand Down Expand Up @@ -155,6 +155,7 @@ wasm-web = [
]
networking = ["std", "splits-io-api"]
auto-splitting = ["std", "livesplit-auto-splitting", "tokio", "log"]
unstable-auto-splitting = ["livesplit-auto-splitting?/unstable"]

[lib]
bench = false
Expand Down
4 changes: 4 additions & 0 deletions crates/livesplit-auto-splitting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ wasmtime = { version = "0.39.1", default-features = false, features = [
"cranelift",
"parallel-compilation",
] }
wasmtime-wasi = { version = "0.39.1", optional = true }

[features]
unstable = ["wasmtime-wasi"]
3 changes: 3 additions & 0 deletions crates/livesplit-auto-splitting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,6 @@ extern "C" {
pub fn runtime_print_message(text_ptr: *const u8, text_len: usize);
}
```

On top of the runtime's API, there's also unstable `WASI` support via the
`unstable` feature.
3 changes: 3 additions & 0 deletions crates/livesplit-auto-splitting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@
//! pub fn runtime_print_message(text_ptr: *const u8, text_len: usize);
//! }
//! ```
//!
//! On top of the runtime's API, there's also unstable `WASI` support via the
//! `unstable` feature.

#![warn(
clippy::complexity,
Expand Down
31 changes: 31 additions & 0 deletions crates/livesplit-auto-splitting/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use sysinfo::{ProcessRefreshKind, RefreshKind, System, SystemExt};
use wasmtime::{
Caller, Config, Engine, Extern, Linker, Memory, Module, OptLevel, Store, Trap, TypedFunc,
};
#[cfg(feature = "unstable")]
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder};

/// An error that is returned when the creation of a new runtime fails.
#[derive(Debug, Snafu)]
Expand Down Expand Up @@ -49,6 +51,19 @@ pub enum CreationError {
/// The WebAssembly module has no exported memory called `memory`, which is
/// a requirement.
MissingMemory,

#[cfg(feature = "unstable")]
/// Failed linking the WebAssembly System Interface (WASI).
Wasi {
/// The underlying error.
source: anyhow::Error,
},
#[cfg(feature = "unstable")]
/// Failed running the WebAssembly System Interface (WASI) `_start` function.
WasiStart {
/// The underlying error.
source: wasmtime::Trap,
},
}

/// An error that is returned when executing the WebAssembly module fails.
Expand All @@ -72,6 +87,8 @@ pub struct Context<T: Timer> {
timer: T,
memory: Option<Memory>,
process_list: ProcessList,
#[cfg(feature = "unstable")]
wasi: WasiCtx,
}

/// A threadsafe handle used to interrupt the execution of the script.
Expand Down Expand Up @@ -149,17 +166,31 @@ impl<T: Timer> Runtime<T> {
timer,
memory: None,
process_list: ProcessList::new(),
#[cfg(feature = "unstable")]
wasi: WasiCtxBuilder::new().build(),
},
);

store.set_epoch_deadline(1);

let mut linker = Linker::new(&engine);
bind_interface(&mut linker)?;

#[cfg(feature = "unstable")]
wasmtime_wasi::add_to_linker(&mut linker, |ctx| &mut ctx.wasi).context(Wasi)?;

let instance = linker
.instantiate(&mut store, &module)
.context(ModuleInstantiation)?;

#[cfg(feature = "unstable")]
// TODO: _start is kind of correct, but not in the long term. They are
// intending for us to use a different function for libraries. Look into
// reactors.
if let Ok(func) = instance.get_typed_func(&mut store, "_start") {
func.call(&mut store, ()).context(WasiStart)?;
}

let update = instance
.get_typed_func(&mut store, "update")
.context(MissingUpdateFunction)?;
Expand Down
182 changes: 182 additions & 0 deletions crates/livesplit-auto-splitting/tests/sandboxing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#![cfg(feature = "unstable")]

use livesplit_auto_splitting::{Runtime, Timer, TimerState};
use log::Log;
use std::{
cell::RefCell,
ffi::OsStr,
fmt::Write,
fs,
path::PathBuf,
process::{Command, Stdio},
thread,
time::Duration,
};

thread_local! {
static BUF: RefCell<Option<String>> = RefCell::new(None);
}
struct Logger;
static LOGGER: Logger = Logger;

impl Log for Logger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if record.target() != "Auto Splitter" {
return;
}
BUF.with(|b| {
if let Some(b) = &mut *b.borrow_mut() {
let _ = writeln!(b, "{}", record.args());
}
});
}
fn flush(&self) {}
}

struct DummyTimer;

impl Timer for DummyTimer {
fn state(&self) -> TimerState {
TimerState::NotRunning
}
fn start(&mut self) {}
fn split(&mut self) {}
fn reset(&mut self) {}
fn set_game_time(&mut self, time: time::Duration) {}
fn pause_game_time(&mut self) {}
fn resume_game_time(&mut self) {}
fn set_variable(&mut self, key: &str, value: &str) {}
}

fn compile(crate_name: &str) -> anyhow::Result<Runtime<DummyTimer>> {
let mut path = PathBuf::from("tests");
path.push("test-cases");
path.push(crate_name);

let output = Command::new("cargo")
.current_dir(&path)
.arg("build")
.arg("--target")
.arg("wasm32-wasi")
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
.unwrap();

if !output.status.success() {
let output = String::from_utf8_lossy(&output.stderr);
panic!("{}", output);
}

path.push("target");
path.push("wasm32-wasi");
path.push("debug");
let wasm_path = fs::read_dir(path)
.unwrap()
.find_map(|e| {
let path = e.unwrap().path();
if path.extension() == Some(OsStr::new("wasm")) {
Some(path)
} else {
None
}
})
.unwrap();

Ok(Runtime::new(&wasm_path, DummyTimer)?)
}

fn run(crate_name: &str) -> anyhow::Result<()> {
let mut runtime = compile(crate_name)?;
runtime.step()?;
Ok(())
}

#[test]
fn empty() {
run("empty").unwrap();
}

#[test]
fn proc_exit() {
assert!(run("proc-exit").is_err());
}

#[test]
fn create_file() {
run("create-file").unwrap();
}

#[test]
fn stdout() {
let _ = log::set_logger(&LOGGER);
log::set_max_level(log::LevelFilter::Trace);
BUF.with(|b| *b.borrow_mut() = Some(String::new()));
run("stdout").unwrap();
let output = BUF.with(|b| b.borrow_mut().take());
// FIXME: For now we don't actually hook up stdout or stderr.
assert_eq!(output.unwrap(), "");
}

#[test]
fn segfault() {
assert!(run("segfault").is_err());
}

#[test]
fn env() {
run("env").unwrap();
assert!(std::env::var("AUTOSPLITTER_HOST_SHOULDNT_SEE_THIS").is_err());
}

#[test]
fn threads() {
// There's no threads in WASI / WASM yet, so this is expected to trap.
assert!(run("threads").is_err());
}

#[test]
fn sleep() {
// FIXME: Sleeping can basically deadlock the code. We should have a limit on
// how long it can sleep.
run("sleep").unwrap();
}

#[test]
fn time() {
run("time").unwrap();
}

#[test]
fn random() {
run("random").unwrap();
}

// #[test]
// fn poll() {
// // FIXME: This is basically what happens at the lower levels of sleeping. You
// // can block on file descriptors and have a timeout with this. Both of which
// // could deadlock the script.
// run("poll").unwrap();
// }

#[test]
fn infinite_loop() {
let mut runtime = compile("infinite-loop").unwrap();

let interrupt = runtime.interrupt_handle();

thread::spawn(move || {
thread::sleep(Duration::from_secs(5));
interrupt.interrupt();
});

assert!(runtime.step().is_err());
}

// FIXME: Test Network

// FIXME: Test heavy amounts of allocations
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "create-file"
version = "0.1.0"
authors = ["Christopher Serr <christopher.serr@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace]

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#[no_mangle]
pub extern "C" fn update() {
assert!(std::fs::write(
"shouldnt_exist.txt",
"This file should never exist. File a bug if you see this.",
)
.is_err());
}

fn main() {}
11 changes: 11 additions & 0 deletions crates/livesplit-auto-splitting/tests/test-cases/empty/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "empty"
version = "0.1.0"
authors = ["Christopher Serr <christopher.serr@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace]

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn update() {}

fn main() {}
11 changes: 11 additions & 0 deletions crates/livesplit-auto-splitting/tests/test-cases/env/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "env"
version = "0.1.0"
authors = ["Christopher Serr <christopher.serr@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace]

[dependencies]
19 changes: 19 additions & 0 deletions crates/livesplit-auto-splitting/tests/test-cases/env/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::{env, path::Path};

#[no_mangle]
pub extern "C" fn update() {
assert!(env::args().next().is_none());
assert!(env::current_exe().is_err());
assert_eq!(env::current_dir().unwrap(), Path::new("/"));
assert!(env::vars().next().is_none());

env::set_var("AUTOSPLITTER_HOST_SHOULDNT_SEE_THIS", "YES");

// but auto splitter should
assert_eq!(
env::var("AUTOSPLITTER_HOST_SHOULDNT_SEE_THIS").unwrap(),
"YES",
);
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "infinite-loop"
version = "0.1.0"
authors = ["Christopher Serr <christopher.serr@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace]

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[no_mangle]
pub extern "C" fn update() {
loop {
std::thread::yield_now();
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "proc-exit"
version = "0.1.0"
authors = ["Christopher Serr <christopher.serr@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace]

[dependencies]
Loading

0 comments on commit 526e60f

Please sign in to comment.