use core::cell::RefCell;
use codec::{Decode, Encode};
use gear_sandbox_host::sandbox::{self as sandbox_env, env::Instantiate};
use sp_wasm_interface::{
util,
wasmtime::{AsContext, AsContextMut, Func, Val},
Caller, FunctionContext, HostPointer, Pointer, StoreData, Value, WordSize,
};
struct Store {
store_data_key: usize,
store: sandbox_env::Store<Func>,
}
impl Store {
pub fn new() -> Self {
Self {
store_data_key: 0,
store: sandbox_env::Store::new(sandbox_env::SandboxBackend::Wasmer),
}
}
pub fn get(&mut self, store_data_key: usize) -> &mut sandbox_env::Store<Func> {
if self.store_data_key != store_data_key {
self.store_data_key = store_data_key;
self.store.clear();
}
&mut self.store
}
}
thread_local! {
static SANDBOXES: RefCell<Store> = RefCell::new(Store::new());
}
pub fn init() {
SANDBOXES.with(|sandboxes| {
let _store = sandboxes.borrow_mut().get(0);
})
}
struct SandboxContext<'a, 'b> {
caller: &'a mut Caller<'b, StoreData>,
dispatch_thunk: Func,
state: u32,
}
impl<'a, 'b> sandbox_env::SandboxContext for SandboxContext<'a, 'b> {
fn invoke(
&mut self,
invoke_args_ptr: Pointer<u8>,
invoke_args_len: WordSize,
func_idx: sandbox_env::SupervisorFuncIndex,
) -> gear_sandbox_host::error::Result<i64> {
let mut ret_vals = [Val::null()];
let result = self.dispatch_thunk.call(
&mut self.caller,
&[
Val::I32(u32::from(invoke_args_ptr) as i32),
Val::I32(invoke_args_len as i32),
Val::I32(self.state as i32),
Val::I32(usize::from(func_idx) as i32),
],
&mut ret_vals,
);
match result {
Ok(()) => {
if let Some(ret_val) = ret_vals[0].i64() {
Ok(ret_val)
} else {
Err("Supervisor function returned unexpected result!".into())
}
}
Err(err) => Err(err.to_string().into()),
}
}
fn read_memory_into(
&self,
address: Pointer<u8>,
dest: &mut [u8],
) -> sp_wasm_interface::Result<()> {
util::read_memory_into(self.caller.as_context(), address, dest)
}
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> sp_wasm_interface::Result<()> {
util::write_memory_from(self.caller.as_context_mut(), address, data)
}
fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result<Pointer<u8>> {
util::allocate_memory(self.caller, size)
}
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> sp_wasm_interface::Result<()> {
util::deallocate_memory(self.caller, ptr)
}
}
fn read_memory(
ctx: impl AsContext<Data = StoreData>,
address: Pointer<u8>,
size: WordSize,
) -> sp_wasm_interface::Result<Vec<u8>> {
let mut vec = vec![0; size as usize];
util::read_memory_into(ctx, address, &mut vec)?;
Ok(vec)
}
fn trace(func: &str, caller: &Caller<'_, StoreData>) {
let data_ptr: *const _ = caller.data();
let caller_ptr: *const _ = caller;
let thread_id = std::thread::current().id();
log::trace!(
"{func}; data_ptr = {:#x?}, caller_ptr = {:#x?}, thread_id = {:?}",
data_ptr as usize,
caller_ptr as usize,
thread_id,
);
}
pub fn get_buff(context: &mut dyn FunctionContext, memory_idx: u32) -> HostPointer {
use gear_sandbox_host::util::MemoryTransfer;
let mut method_result: HostPointer = u32::MAX.into();
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("get_buff", caller);
let data_ptr: *const _ = caller.data();
method_result = SANDBOXES.with(|sandboxes| {
let mut memory = sandboxes
.borrow_mut()
.get(data_ptr as usize)
.memory(memory_idx)
.expect("Failed to get memory buffer pointer: cannot get backend memory");
memory.get_buff() as HostPointer
});
});
method_result
}
pub fn get_global_val(
context: &mut dyn FunctionContext,
instance_idx: u32,
name: &str,
) -> Option<Value> {
let mut method_result = None::<Value>;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("get_global_val", caller);
let data_ptr: *const _ = caller.data();
method_result = SANDBOXES.with(|sandboxes| {
sandboxes
.borrow_mut()
.get(data_ptr as usize)
.instance(instance_idx)
.map(|i| i.get_global_val(name))
.map_err(|e| e.to_string())
.expect("Failed to get global from sandbox")
});
});
method_result
}
pub fn get_instance_ptr(context: &mut dyn FunctionContext, instance_id: u32) -> HostPointer {
let mut method_result: HostPointer = u32::MAX.into();
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("get_instance_ptr", caller);
let data_ptr: *const _ = caller.data();
method_result = SANDBOXES.with(|sandboxes| {
let instance = sandboxes
.borrow_mut()
.get(data_ptr as usize)
.instance(instance_id)
.expect("Failed to get sandboxed instance");
instance.as_ref().get_ref() as *const gear_sandbox_host::sandbox::SandboxInstance
as HostPointer
});
});
method_result
}
pub fn instance_teardown(context: &mut dyn FunctionContext, instance_idx: u32) {
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("instance_teardown", caller);
let data_ptr: *const _ = caller.data();
SANDBOXES.with(|sandboxes| {
sandboxes
.borrow_mut()
.get(data_ptr as usize)
.instance_teardown(instance_idx)
.expect("Failed to teardown sandbox instance")
})
});
}
pub fn instantiate(
context: &mut dyn FunctionContext,
dispatch_thunk_id: u32,
wasm_code: &[u8],
raw_env_def: &[u8],
state_ptr: Pointer<u8>,
version: Instantiate,
) -> u32 {
let mut method_result = u32::MAX;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("instantiate", caller);
let dispatch_thunk = {
let table = caller
.data()
.table
.expect("Runtime doesn't have a table; sandbox is unavailable");
let table_item = table.get(caller.as_context_mut(), dispatch_thunk_id);
*table_item
.expect("dispatch_thunk_id is out of bounds")
.funcref()
.expect("dispatch_thunk_idx should be a funcref")
.expect("dispatch_thunk_idx should point to actual func")
};
let data_ptr: *const _ = caller.data();
let store_data_key = data_ptr as usize;
let guest_env = SANDBOXES.with(|sandboxes| {
let mut store_ref = sandboxes.borrow_mut();
let store = store_ref.get(store_data_key);
sandbox_env::GuestEnvironment::decode(store, raw_env_def)
});
let Ok(guest_env) = guest_env else {
method_result = sandbox_env::env::ERR_MODULE;
return;
};
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
SANDBOXES.with(|sandboxes| {
sandboxes.borrow_mut().get(store_data_key).instantiate(
version,
wasm_code,
guest_env,
&mut SandboxContext {
caller,
dispatch_thunk,
state: state_ptr.into(),
},
)
})
}));
let result = match result {
Ok(result) => result,
Err(error) => std::panic::resume_unwind(error),
};
let instance_idx_or_err_code = match result {
Ok(instance) => SANDBOXES.with(|sandboxes| {
let mut store_ref = sandboxes.borrow_mut();
let store = store_ref.get(store_data_key);
instance.register(store, dispatch_thunk)
}),
Err(sandbox_env::InstantiationError::StartTrapped) => sandbox_env::env::ERR_EXECUTION,
Err(_) => sandbox_env::env::ERR_MODULE,
};
method_result = instance_idx_or_err_code;
});
method_result
}
pub fn invoke(
context: &mut dyn FunctionContext,
instance_idx: u32,
function: &str,
mut args: &[u8],
return_val_ptr: Pointer<u8>,
return_val_len: u32,
state_ptr: Pointer<u8>,
) -> u32 {
use sandbox_env::SandboxContext as _;
let mut method_result = u32::MAX;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("invoke", caller);
log::trace!("invoke, instance_idx={instance_idx}");
let args = Vec::<sp_wasm_interface::Value>::decode(&mut args)
.expect("Can't decode serialized arguments for the invocation")
.into_iter()
.collect::<Vec<_>>();
let data_ptr: *const _ = caller.data();
let (instance, dispatch_thunk) = SANDBOXES.with(|sandboxes| {
let mut store_ref = sandboxes.borrow_mut();
let store = store_ref.get(data_ptr as usize);
let instance = store
.instance(instance_idx)
.expect("backend instance not found");
let dispatch_thunk = store
.dispatch_thunk(instance_idx)
.expect("dispatch_thunk not found");
(instance, dispatch_thunk)
});
let mut sandbox_context = SandboxContext {
caller,
dispatch_thunk,
state: state_ptr.into(),
};
let result = instance.invoke(function, &args, &mut sandbox_context);
method_result = match result {
Ok(None) => sandbox_env::env::ERR_OK,
Ok(Some(val)) => {
sp_wasm_interface::ReturnValue::Value(val).using_encoded(|val| {
if val.len() > return_val_len as usize {
panic!("Return value buffer is too small");
}
sandbox_context
.write_memory(return_val_ptr, val)
.expect("can't write return value");
sandbox_env::env::ERR_OK
})
}
Err(e) => {
log::trace!("e = {e:?}");
sandbox_env::env::ERR_EXECUTION
}
};
});
method_result
}
pub fn memory_get(
context: &mut dyn FunctionContext,
memory_idx: u32,
offset: u32,
buf_ptr: Pointer<u8>,
buf_len: u32,
) -> u32 {
use gear_sandbox_host::util::MemoryTransfer;
let mut method_result = u32::MAX;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("memory_get", caller);
let data_ptr: *const _ = caller.data();
let sandboxed_memory = SANDBOXES.with(|sandboxes| {
sandboxes
.borrow_mut()
.get(data_ptr as usize)
.memory(memory_idx)
.expect("sandboxed memory not found")
});
let len = buf_len as usize;
let buffer = match sandboxed_memory.read(Pointer::new(offset), len) {
Err(_) => {
method_result = sandbox_env::env::ERR_OUT_OF_BOUNDS;
return;
}
Ok(buffer) => buffer,
};
method_result = match util::write_memory_from(caller, buf_ptr, &buffer) {
Ok(_) => sandbox_env::env::ERR_OK,
Err(_) => sandbox_env::env::ERR_OUT_OF_BOUNDS,
};
});
method_result
}
pub fn memory_grow(context: &mut dyn FunctionContext, memory_idx: u32, size: u32) -> u32 {
use gear_sandbox_host::util::MemoryTransfer;
let mut method_result = u32::MAX;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("memory_grow", caller);
let data_ptr: *const _ = caller.data();
method_result = SANDBOXES.with(|sandboxes| {
let mut memory = sandboxes
.borrow_mut()
.get(data_ptr as usize)
.memory(memory_idx)
.expect("Failed to grow memory: cannot get backend memory");
memory.memory_grow(size).expect("Failed to grow memory")
});
});
method_result
}
pub fn memory_new(context: &mut dyn FunctionContext, initial: u32, maximum: u32) -> u32 {
let mut method_result = u32::MAX;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("memory_new", caller);
let data_ptr: *const _ = caller.data();
method_result = SANDBOXES.with(|sandboxes| {
sandboxes
.borrow_mut()
.get(data_ptr as usize)
.new_memory(initial, maximum)
.map_err(|e| e.to_string())
.expect("Failed to create new memory with sandbox")
});
});
method_result
}
pub fn memory_set(
context: &mut dyn FunctionContext,
memory_idx: u32,
offset: u32,
val_ptr: Pointer<u8>,
val_len: u32,
) -> u32 {
use gear_sandbox_host::util::MemoryTransfer;
let mut method_result = u32::MAX;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("memory_set", caller);
let Ok(buffer) = read_memory(&caller, val_ptr, val_len) else {
method_result = sandbox_env::env::ERR_OUT_OF_BOUNDS;
return;
};
let data_ptr: *const _ = caller.data();
method_result = SANDBOXES.with(|sandboxes| {
let sandboxed_memory = sandboxes
.borrow_mut()
.get(data_ptr as usize)
.memory(memory_idx)
.expect("memory_set: not found");
match sandboxed_memory.write_from(Pointer::new(offset), &buffer) {
Ok(_) => sandbox_env::env::ERR_OK,
Err(_) => sandbox_env::env::ERR_OUT_OF_BOUNDS,
}
});
});
method_result
}
pub fn memory_size(context: &mut dyn FunctionContext, memory_idx: u32) -> u32 {
use gear_sandbox_host::util::MemoryTransfer;
let mut method_result = 0;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("memory_size", caller);
let data_ptr: *const _ = caller.data();
method_result = SANDBOXES.with(|sandboxes| {
let mut memory = sandboxes
.borrow_mut()
.get(data_ptr as usize)
.memory(memory_idx)
.expect("Failed to get memory size: cannot get backend memory");
memory.memory_size()
});
});
method_result
}
pub fn memory_teardown(context: &mut dyn FunctionContext, memory_idx: u32) {
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("memory_teardown", caller);
let data_ptr: *const _ = caller.data();
SANDBOXES.with(|sandboxes| {
sandboxes
.borrow_mut()
.get(data_ptr as usize)
.memory_teardown(memory_idx)
.expect("Failed to teardown sandbox memory")
});
});
}
pub fn set_global_val(
context: &mut dyn FunctionContext,
instance_idx: u32,
name: &str,
value: Value,
) -> u32 {
let mut method_result = u32::MAX;
sp_wasm_interface::with_caller_mut(context, |caller| {
trace("set_global_val", caller);
log::trace!("set_global_val, instance_idx={instance_idx}");
let data_ptr: *const _ = caller.data();
let result = SANDBOXES.with(|sandboxes| {
let instance = sandboxes
.borrow_mut()
.get(data_ptr as usize)
.instance(instance_idx)
.map_err(|e| e.to_string())
.expect("Failed to set global in sandbox");
instance.set_global_val(name, value)
});
log::trace!("set_global_val, name={name}, value={value:?}, result={result:?}",);
method_result = match result {
Ok(None) => sandbox_env::env::ERROR_GLOBALS_NOT_FOUND,
Ok(Some(_)) => sandbox_env::env::ERROR_GLOBALS_OK,
Err(_) => sandbox_env::env::ERROR_GLOBALS_OTHER,
};
});
method_result
}