mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 13:48:00 +00:00
Wasm executor should provide stubs for unknown externs (wasmi) (#4550)
Related to #4456
This commit is contained in:
@@ -18,7 +18,23 @@ use sp_runtime::{print, traits::{BlakeTwo256, Hash}};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_core::{ed25519, sr25519};
|
||||
|
||||
extern "C" {
|
||||
#[allow(dead_code)]
|
||||
fn missing_external();
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn yet_another_missing_external();
|
||||
}
|
||||
|
||||
sp_core::wasm_export_functions! {
|
||||
fn test_calling_missing_external() {
|
||||
unsafe { missing_external() }
|
||||
}
|
||||
|
||||
fn test_calling_yet_another_missing_external() {
|
||||
unsafe { yet_another_missing_external() }
|
||||
}
|
||||
|
||||
fn test_data_in(input: Vec<u8>) -> Vec<u8> {
|
||||
print("set_storage");
|
||||
storage::set(b"input", &input);
|
||||
|
||||
@@ -32,6 +32,49 @@ use crate::WasmExecutionMethod;
|
||||
|
||||
pub type TestExternalities = CoreTestExternalities<Blake2Hasher, u64>;
|
||||
|
||||
#[cfg(feature = "wasmtime")]
|
||||
mod wasmtime_missing_externals {
|
||||
use sp_wasm_interface::{Function, FunctionContext, HostFunctions, Result, Signature, Value};
|
||||
|
||||
pub struct WasmtimeHostFunctions;
|
||||
|
||||
impl HostFunctions for WasmtimeHostFunctions {
|
||||
fn host_functions() -> Vec<&'static dyn Function> {
|
||||
vec![MISSING_EXTERNAL_FUNCTION, YET_ANOTHER_MISSING_EXTERNAL_FUNCTION]
|
||||
}
|
||||
}
|
||||
|
||||
struct MissingExternalFunction(&'static str);
|
||||
|
||||
impl Function for MissingExternalFunction {
|
||||
fn name(&self) -> &str { self.0 }
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::new(vec![], None)
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
_context: &mut dyn FunctionContext,
|
||||
_args: &mut dyn Iterator<Item = Value>,
|
||||
) -> Result<Option<Value>> {
|
||||
panic!("should not be called");
|
||||
}
|
||||
}
|
||||
|
||||
static MISSING_EXTERNAL_FUNCTION: &'static MissingExternalFunction =
|
||||
&MissingExternalFunction("missing_external");
|
||||
static YET_ANOTHER_MISSING_EXTERNAL_FUNCTION: &'static MissingExternalFunction =
|
||||
&MissingExternalFunction("yet_another_missing_external");
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasmtime")]
|
||||
type HostFunctions =
|
||||
(wasmtime_missing_externals::WasmtimeHostFunctions, sp_io::SubstrateHostFunctions);
|
||||
|
||||
#[cfg(not(feature = "wasmtime"))]
|
||||
type HostFunctions = sp_io::SubstrateHostFunctions;
|
||||
|
||||
fn call_in_wasm<E: Externalities>(
|
||||
function: &str,
|
||||
call_data: &[u8],
|
||||
@@ -40,13 +83,14 @@ fn call_in_wasm<E: Externalities>(
|
||||
code: &[u8],
|
||||
heap_pages: u64,
|
||||
) -> crate::error::Result<Vec<u8>> {
|
||||
crate::call_in_wasm::<E, sp_io::SubstrateHostFunctions>(
|
||||
crate::call_in_wasm::<E, HostFunctions>(
|
||||
function,
|
||||
call_data,
|
||||
execution_method,
|
||||
ext,
|
||||
code,
|
||||
heap_pages,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,6 +112,44 @@ fn returning_should_work(wasm_method: WasmExecutionMethod) {
|
||||
assert_eq!(output, vec![0u8; 0]);
|
||||
}
|
||||
|
||||
#[test_case(WasmExecutionMethod::Interpreted)]
|
||||
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||
#[should_panic(expected = "Function `missing_external` is only a stub. Calling a stub is not allowed.")]
|
||||
#[cfg(not(feature = "wasmtime"))]
|
||||
fn call_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
call_in_wasm(
|
||||
"test_calling_missing_external",
|
||||
&[],
|
||||
wasm_method,
|
||||
&mut ext,
|
||||
&test_code[..],
|
||||
8,
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
#[test_case(WasmExecutionMethod::Interpreted)]
|
||||
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||
#[should_panic(expected = "Function `yet_another_missing_external` is only a stub. Calling a stub is not allowed.")]
|
||||
#[cfg(not(feature = "wasmtime"))]
|
||||
fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
call_in_wasm(
|
||||
"test_calling_yet_another_missing_external",
|
||||
&[],
|
||||
wasm_method,
|
||||
&mut ext,
|
||||
&test_code[..],
|
||||
8,
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
#[test_case(WasmExecutionMethod::Interpreted)]
|
||||
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||
fn panicking_should_work(wasm_method: WasmExecutionMethod) {
|
||||
|
||||
@@ -67,12 +67,14 @@ pub fn call_in_wasm<E: Externalities, HF: sp_wasm_interface::HostFunctions>(
|
||||
ext: &mut E,
|
||||
code: &[u8],
|
||||
heap_pages: u64,
|
||||
allow_missing_imports: bool,
|
||||
) -> error::Result<Vec<u8>> {
|
||||
let mut instance = wasm_runtime::create_wasm_runtime_with_code(
|
||||
execution_method,
|
||||
heap_pages,
|
||||
code,
|
||||
HF::host_functions(),
|
||||
allow_missing_imports,
|
||||
)?;
|
||||
instance.call(ext, function, call_data)
|
||||
}
|
||||
@@ -103,6 +105,7 @@ mod tests {
|
||||
&mut ext,
|
||||
&WASM_BINARY,
|
||||
8,
|
||||
true,
|
||||
).unwrap();
|
||||
assert_eq!(res, vec![0u8; 0]);
|
||||
}
|
||||
|
||||
@@ -191,10 +191,11 @@ pub fn create_wasm_runtime_with_code(
|
||||
heap_pages: u64,
|
||||
code: &[u8],
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
allow_missing_imports: bool,
|
||||
) -> Result<Box<dyn WasmRuntime>, WasmError> {
|
||||
match wasm_method {
|
||||
WasmExecutionMethod::Interpreted =>
|
||||
sc_executor_wasmi::create_instance(code, heap_pages, host_functions)
|
||||
sc_executor_wasmi::create_instance(code, heap_pages, host_functions, allow_missing_imports)
|
||||
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
|
||||
#[cfg(feature = "wasmtime")]
|
||||
WasmExecutionMethod::Compiled =>
|
||||
@@ -212,7 +213,7 @@ fn create_versioned_wasm_runtime<E: Externalities>(
|
||||
let code = ext
|
||||
.original_storage(well_known_keys::CODE)
|
||||
.ok_or(WasmError::CodeNotFound)?;
|
||||
let mut runtime = create_wasm_runtime_with_code(wasm_method, heap_pages, &code, host_functions)?;
|
||||
let mut runtime = create_wasm_runtime_with_code(wasm_method, heap_pages, &code, host_functions, false)?;
|
||||
|
||||
// Call to determine runtime version.
|
||||
let version_result = {
|
||||
|
||||
@@ -21,7 +21,7 @@ use sc_executor_common::{
|
||||
sandbox,
|
||||
allocator,
|
||||
};
|
||||
use std::{str, mem};
|
||||
use std::{str, mem, cell::RefCell};
|
||||
use wasmi::{
|
||||
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
|
||||
memory_units::Pages, RuntimeValue::{I32, I64, self},
|
||||
@@ -42,6 +42,8 @@ struct FunctionExecutor<'a> {
|
||||
memory: MemoryRef,
|
||||
table: Option<TableRef>,
|
||||
host_functions: &'a [&'static dyn Function],
|
||||
allow_missing_imports: bool,
|
||||
missing_functions: &'a [String],
|
||||
}
|
||||
|
||||
impl<'a> FunctionExecutor<'a> {
|
||||
@@ -50,6 +52,8 @@ impl<'a> FunctionExecutor<'a> {
|
||||
heap_base: u32,
|
||||
t: Option<TableRef>,
|
||||
host_functions: &'a [&'static dyn Function],
|
||||
allow_missing_imports: bool,
|
||||
missing_functions: &'a [String],
|
||||
) -> Result<Self, Error> {
|
||||
Ok(FunctionExecutor {
|
||||
sandbox_store: sandbox::Store::new(),
|
||||
@@ -57,6 +61,8 @@ impl<'a> FunctionExecutor<'a> {
|
||||
memory: m,
|
||||
table: t,
|
||||
host_functions,
|
||||
allow_missing_imports,
|
||||
missing_functions,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -269,14 +275,28 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
struct Resolver<'a>(&'a[&'static dyn Function]);
|
||||
struct Resolver<'a> {
|
||||
host_functions: &'a[&'static dyn Function],
|
||||
allow_missing_imports: bool,
|
||||
missing_functions: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl<'a> Resolver<'a> {
|
||||
fn new(host_functions: &'a[&'static dyn Function], allow_missing_imports: bool) -> Resolver<'a> {
|
||||
Resolver {
|
||||
host_functions,
|
||||
allow_missing_imports,
|
||||
missing_functions: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
|
||||
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
|
||||
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
|
||||
{
|
||||
let signature = sp_wasm_interface::Signature::from(signature);
|
||||
for (function_index, function) in self.0.iter().enumerate() {
|
||||
for (function_index, function) in self.host_functions.iter().enumerate() {
|
||||
if name == function.name() {
|
||||
if signature == function.signature() {
|
||||
return Ok(
|
||||
@@ -295,9 +315,17 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(wasmi::Error::Instantiation(
|
||||
format!("Export {} not found", name),
|
||||
))
|
||||
if self.allow_missing_imports {
|
||||
trace!(target: "wasm-executor", "Could not find function `{}`, a stub will be provided instead.", name);
|
||||
let id = self.missing_functions.borrow().len() + self.host_functions.len();
|
||||
self.missing_functions.borrow_mut().push(name.to_string());
|
||||
|
||||
Ok(wasmi::FuncInstance::alloc_host(signature.into(), id))
|
||||
} else {
|
||||
Err(wasmi::Error::Instantiation(
|
||||
format!("Export {} not found", name),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,16 +334,23 @@ impl<'a> wasmi::Externals for FunctionExecutor<'a> {
|
||||
-> Result<Option<wasmi::RuntimeValue>, wasmi::Trap>
|
||||
{
|
||||
let mut args = args.as_ref().iter().copied().map(Into::into);
|
||||
let function = self.host_functions.get(index).ok_or_else(||
|
||||
Error::from(
|
||||
format!("Could not find host function with index: {}", index),
|
||||
)
|
||||
)?;
|
||||
|
||||
function.execute(self, &mut args)
|
||||
.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
|
||||
.map_err(wasmi::Trap::from)
|
||||
.map(|v| v.map(Into::into))
|
||||
if let Some(function) = self.host_functions.get(index) {
|
||||
function.execute(self, &mut args)
|
||||
.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
|
||||
.map_err(wasmi::Trap::from)
|
||||
.map(|v| v.map(Into::into))
|
||||
} else if self.allow_missing_imports
|
||||
&& index >= self.host_functions.len()
|
||||
&& index < self.host_functions.len() + self.missing_functions.len()
|
||||
{
|
||||
Err(Error::from(format!(
|
||||
"Function `{}` is only a stub. Calling a stub is not allowed.",
|
||||
self.missing_functions[index - self.host_functions.len()],
|
||||
)).into())
|
||||
} else {
|
||||
Err(Error::from(format!("Could not find host function with index: {}", index)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +386,8 @@ fn call_in_wasm_module(
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
host_functions: &[&'static dyn Function],
|
||||
allow_missing_imports: bool,
|
||||
missing_functions: &Vec<String>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// extract a reference to a linear memory, optional reference to a table
|
||||
// and then initialize FunctionExecutor.
|
||||
@@ -360,7 +397,14 @@ fn call_in_wasm_module(
|
||||
.and_then(|e| e.as_table().cloned());
|
||||
let heap_base = get_heap_base(module_instance)?;
|
||||
|
||||
let mut fec = FunctionExecutor::new(memory.clone(), heap_base, table, host_functions)?;
|
||||
let mut fec = FunctionExecutor::new(
|
||||
memory.clone(),
|
||||
heap_base,
|
||||
table,
|
||||
host_functions,
|
||||
allow_missing_imports,
|
||||
missing_functions,
|
||||
)?;
|
||||
|
||||
// Write the call data
|
||||
let offset = fec.allocate_memory(data.len() as u32)?;
|
||||
@@ -397,8 +441,9 @@ fn instantiate_module(
|
||||
heap_pages: usize,
|
||||
module: &Module,
|
||||
host_functions: &[&'static dyn Function],
|
||||
) -> Result<ModuleRef, Error> {
|
||||
let resolver = Resolver(host_functions);
|
||||
allow_missing_imports: bool,
|
||||
) -> Result<(ModuleRef, Vec<String>), Error> {
|
||||
let resolver = Resolver::new(host_functions, allow_missing_imports);
|
||||
// start module instantiation. Don't run 'start' function yet.
|
||||
let intermediate_instance = ModuleInstance::new(
|
||||
module,
|
||||
@@ -416,7 +461,7 @@ fn instantiate_module(
|
||||
// Runtime is not allowed to have the `start` function.
|
||||
Err(Error::RuntimeHasStartFn)
|
||||
} else {
|
||||
Ok(intermediate_instance.assert_no_start())
|
||||
Ok((intermediate_instance.assert_no_start(), resolver.missing_functions.into_inner()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,6 +581,11 @@ pub struct WasmiRuntime {
|
||||
state_snapshot: StateSnapshot,
|
||||
/// The host functions registered for this instance.
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
/// Enable stub generation for functions that are not available in `host_functions`.
|
||||
/// These stubs will error when the wasm blob tries to call them.
|
||||
allow_missing_imports: bool,
|
||||
/// List of missing functions detected during function resolution
|
||||
missing_functions: Vec<String>,
|
||||
}
|
||||
|
||||
impl WasmRuntime for WasmiRuntime {
|
||||
@@ -561,7 +611,15 @@ impl WasmRuntime for WasmiRuntime {
|
||||
error!(target: "wasm-executor", "snapshot restoration failed: {}", e);
|
||||
e
|
||||
})?;
|
||||
call_in_wasm_module(ext, &self.instance, method, data, &self.host_functions)
|
||||
call_in_wasm_module(
|
||||
ext,
|
||||
&self.instance,
|
||||
method,
|
||||
data,
|
||||
&self.host_functions,
|
||||
self.allow_missing_imports,
|
||||
&self.missing_functions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,6 +627,7 @@ pub fn create_instance(
|
||||
code: &[u8],
|
||||
heap_pages: u64,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
allow_missing_imports: bool,
|
||||
) -> Result<WasmiRuntime, WasmError> {
|
||||
let module = Module::from_buffer(&code).map_err(|_| WasmError::InvalidModule)?;
|
||||
|
||||
@@ -579,8 +638,12 @@ pub fn create_instance(
|
||||
let data_segments = extract_data_segments(&code)?;
|
||||
|
||||
// Instantiate this module.
|
||||
let instance = instantiate_module(heap_pages as usize, &module, &host_functions)
|
||||
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
|
||||
let (instance, missing_functions) = instantiate_module(
|
||||
heap_pages as usize,
|
||||
&module,
|
||||
&host_functions,
|
||||
allow_missing_imports,
|
||||
).map_err(|e| WasmError::Instantiation(e.to_string()))?;
|
||||
|
||||
// Take state snapshot before executing anything.
|
||||
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
|
||||
@@ -595,6 +658,8 @@ pub fn create_instance(
|
||||
instance,
|
||||
state_snapshot,
|
||||
host_functions,
|
||||
allow_missing_imports,
|
||||
missing_functions,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ fn call_wasm_method<HF: HostFunctionsT>(method: &str) -> TestExternalities {
|
||||
&mut ext_ext,
|
||||
&WASM_BINARY[..],
|
||||
8,
|
||||
false,
|
||||
).expect(&format!("Executes `{}`", method));
|
||||
|
||||
ext
|
||||
|
||||
Reference in New Issue
Block a user