#![allow(clippy::expect_used)]
use core::cell::RefCell;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use polyplug::runtime_store::RuntimeStore;
use polyplug_abi::GuestContractInstance;
use polyplug_abi::ffi::polyplug_host_alloc;
use polyplug_abi::ffi::polyplug_host_free;
use polyplug_abi::tracking::TrackingAllocator;
use polyplug_abi::{
AbiError, AbiErrorCode, Array, Buffer, BundleInitContext, GuestContractHandle,
GuestContractInterface, HostApi, PluginDescriptor, StringView,
};
use polyplug_utils::{BundleId, GuestContractId};
const MEMORY_PLUGIN_SO: &str = env!("MEMORY_PLUGIN_SO");
std::thread_local! {
static STRESS_REGISTRY: RefCell<RuntimeStore> = RefCell::new(RuntimeStore::new());
static TLS_TRACKING_ALLOC: RefCell<unsafe extern "C" fn(usize, usize) -> *mut u8> =
RefCell::new(polyplug_host_alloc);
static TLS_TRACKING_FREE: RefCell<unsafe extern "C" fn(*mut u8, usize, usize)> =
RefCell::new(polyplug_host_free);
}
#[repr(C)]
struct FillArgs {
buf: Buffer,
fill_byte: u8,
}
#[repr(C)]
struct AllocArgs {
host: *const HostApi,
size: u64,
fill_byte: u8,
}
#[repr(C)]
struct ZeroArgs {
buf: Buffer,
sv: StringView,
}
#[repr(C)]
struct ZeroResult {
buf_len: u64,
sv_len: u64,
}
unsafe extern "C" fn stub_find_guest_contract(
_this: *const HostApi,
_contract_id: u64,
_min_version: u32,
) -> GuestContractHandle {
GuestContractHandle::null()
}
unsafe extern "C" fn stub_find_all_guest_contracts(
_this: *const HostApi,
_contract_id: u64,
_min_version: u32,
) -> Array<GuestContractHandle> {
Array::empty()
}
unsafe extern "C" fn stub_resolve_guest_contract(
_this: *const HostApi,
_handle: GuestContractHandle,
) -> *const GuestContractInterface {
core::ptr::null()
}
unsafe extern "C" fn stub_get_host_contract(
_this: *const HostApi,
_contract_id: u64,
_min_version: u32,
) -> polyplug_abi::HostContractInstance {
polyplug_abi::HostContractInstance::null()
}
unsafe extern "C" fn stub_list_bundles(_this: *const HostApi) -> Array<BundleId> {
Array::empty()
}
unsafe extern "C" fn stub_get_dependencies(
_this: *const HostApi,
) -> Array<polyplug_abi::DependencyInfo> {
Array::empty()
}
unsafe extern "C" fn stub_resolve_host_contract_interface(
_this: *const HostApi,
_contract_id: u64,
_min_version: u32,
) -> *const polyplug_abi::HostContractInterface {
core::ptr::null()
}
unsafe extern "C" fn stub_load_bundle(
_this: *const HostApi,
_path: *const u8,
_path_len: usize,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn stub_reload_bundle(
_this: *const HostApi,
_path: *const u8,
_path_len: usize,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn stub_register_host_contract(
_this: *const HostApi,
_interface: *const polyplug_abi::HostContractInterface,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn stub_register_loader(
_this: *const HostApi,
_loader_ptr: *mut core::ffi::c_void,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn stub_get_last_error(
_this: *const HostApi,
_buf: *mut u8,
_buf_len: usize,
) -> usize {
0
}
unsafe extern "C" fn stub_get_error_len(_this: *const HostApi) -> usize {
0
}
unsafe extern "C" fn stub_unload_bundle(
_this: *const HostApi,
_bundle_id: BundleId,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn stub_alloc(_this: *const HostApi, size: usize, align: usize) -> *mut u8 {
polyplug_host_alloc(size, align)
}
unsafe extern "C" fn stub_free(_this: *const HostApi, ptr: *mut u8, size: usize, align: usize) {
unsafe { polyplug_host_free(ptr, size, align) }
}
unsafe extern "C" fn registry_register_callback(
_this: *const HostApi,
descriptor: *const PluginDescriptor,
interface: *const GuestContractInterface,
out_err: *mut AbiError,
) {
if descriptor.is_null() || interface.is_null() {
if !out_err.is_null() {
unsafe {
out_err.write(AbiError {
code: AbiErrorCode::InvalidPointer as u32,
message: StringView::null(),
})
};
}
return;
}
let desc: &PluginDescriptor = unsafe { &*descriptor };
let iface: &GuestContractInterface = unsafe { &*interface };
let contract_name: &str = unsafe {
let bytes: &[u8] =
core::slice::from_raw_parts(desc.contract_name.ptr, desc.contract_name.len);
core::str::from_utf8_unchecked(bytes) };
let result: Result<GuestContractHandle, _> = STRESS_REGISTRY.with(|reg_cell| {
let registry: core::cell::Ref<'_, RuntimeStore> = reg_cell.borrow();
unsafe {
registry.register_guest_contract(
*desc,
interface,
contract_name.to_owned(),
BundleId::from_u64(iface.contract_id.id()),
)
}
});
let err_val: AbiError = match result {
Ok(_) => AbiError {
code: AbiErrorCode::Ok as u32,
message: StringView::null(),
},
Err(_) => AbiError {
code: AbiErrorCode::Generic as u32,
message: StringView::null(),
},
};
if !out_err.is_null() {
unsafe { out_err.write(err_val) };
}
}
fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("parent of crates/polyplug")
.parent()
.expect("workspace root")
.to_path_buf()
}
fn load_memory_plugin() -> libloading::Library {
unsafe { libloading::Library::new(MEMORY_PLUGIN_SO).expect("failed to load memory_plugin .so") }
}
fn init_memory_plugin_interface(library: &libloading::Library) -> *const GuestContractInterface {
STRESS_REGISTRY.with(|cell| {
*cell.borrow_mut() = RuntimeStore::new();
});
let init_fn: libloading::Symbol<
'_,
unsafe extern "C" fn(*const HostApi, *const BundleInitContext) -> AbiError,
> = unsafe {
library
.get(b"polyplug_init\0")
.expect("polyplug_init symbol not found")
};
let host_interface: HostApi = HostApi {
runtime: core::ptr::null_mut(),
register_guest_contract: registry_register_callback,
alloc: stub_alloc,
free: stub_free,
find_guest_contract: stub_find_guest_contract,
find_all_guest_contracts: stub_find_all_guest_contracts,
resolve_guest_contract: stub_resolve_guest_contract,
get_host_contract: stub_get_host_contract,
resolve_host_contract_interface: stub_resolve_host_contract_interface,
list_bundles: stub_list_bundles,
get_dependencies: stub_get_dependencies,
load_bundle: stub_load_bundle,
reload_bundle: stub_reload_bundle,
register_host_contract: stub_register_host_contract,
register_loader: stub_register_loader,
get_last_error: stub_get_last_error,
get_error_len: stub_get_error_len,
unload_bundle: stub_unload_bundle,
log: stub_host_log,
create_guest_instance: stub_create_guest_instance,
destroy_guest_instance: stub_destroy_guest_instance,
revision_counter: stub_revision_counter,
reserved: core::ptr::null(),
};
let ctx: BundleInitContext = BundleInitContext {
bundle_path: StringView::null(),
bundle_id: 0,
};
let init_result: AbiError = unsafe {
init_fn(
&host_interface as *const HostApi,
&ctx as *const BundleInitContext,
)
};
assert_eq!(
init_result.code,
AbiErrorCode::Ok as u32,
"polyplug_init must succeed"
);
let contract_id: GuestContractId = GuestContractId::new("memory.test", 1);
let handle: GuestContractHandle = STRESS_REGISTRY.with(|cell| {
cell.borrow()
.find(contract_id, 0)
.expect("memory.test must be registered")
});
STRESS_REGISTRY.with(|cell| {
cell.borrow()
.resolve_guest_contract(handle)
.expect("interface must be resolvable")
})
}
#[test]
fn stress_large_buffer_fill_and_read() {
let library: libloading::Library = load_memory_plugin();
let interface_ptr: *const GuestContractInterface = init_memory_plugin_interface(&library);
let interface: &GuestContractInterface = unsafe { &*interface_ptr };
const BUFFER_SIZE: usize = 1024 * 1024;
let ptr: *mut u8 = polyplug_host_alloc(BUFFER_SIZE, 1);
assert!(
!ptr.is_null(),
"polyplug_host_alloc must return non-null for 1 MiB"
);
let args: FillArgs = FillArgs {
buf: Buffer {
ptr,
len: 0,
cap: BUFFER_SIZE,
},
fill_byte: 0xAB_u8,
};
let mut out: u32 = 0_u32;
let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions.add(0) };
let dispatch_fn: unsafe extern "C" fn(GuestContractInstance, *const (), *mut (), *mut AbiError) =
unsafe { core::mem::transmute(fn_ptr) };
let mut call_result: AbiError = AbiError::ok();
unsafe {
dispatch_fn(
GuestContractInstance::null(),
&args as *const FillArgs as *const (),
&mut out as *mut u32 as *mut (),
&mut call_result,
)
};
assert_eq!(
call_result.code,
AbiErrorCode::Ok as u32,
"memory_fill_preallocated_buffer must return Ok"
);
assert_eq!(
out as usize, BUFFER_SIZE,
"written byte count must equal buffer capacity"
);
let filled_slice: &[u8] = unsafe { core::slice::from_raw_parts(ptr, BUFFER_SIZE) };
assert!(
filled_slice.iter().all(|&b| b == 0xAB_u8),
"all bytes in 1 MiB buffer must be 0xAB"
);
unsafe { polyplug_host_free(ptr, BUFFER_SIZE, 1) };
core::mem::forget(library);
}
#[test]
fn stress_string_view_non_ascii_utf8() {
let library: libloading::Library = load_memory_plugin();
let interface_ptr: *const GuestContractInterface = init_memory_plugin_interface(&library);
let interface: &GuestContractInterface = unsafe { &*interface_ptr };
let input_bytes: &[u8] = b"caf\xc3\xa9";
let input_sv: StringView = StringView {
ptr: input_bytes.as_ptr(),
len: input_bytes.len(),
};
let mut out_sv: StringView = StringView::null();
let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions.add(2) };
let dispatch_fn: unsafe extern "C" fn(GuestContractInstance, *const (), *mut (), *mut AbiError) =
unsafe { core::mem::transmute(fn_ptr) };
let mut call_result: AbiError = AbiError::ok();
unsafe {
dispatch_fn(
GuestContractInstance::null(),
&input_sv as *const StringView as *const (),
&mut out_sv as *mut StringView as *mut (),
&mut call_result,
)
};
assert_eq!(
call_result.code,
AbiErrorCode::Ok as u32,
"memory_echo_string_view must return Ok"
);
assert_eq!(
out_sv.ptr, input_sv.ptr,
"echoed StringView must have same pointer"
);
assert_eq!(
out_sv.len, input_sv.len,
"echoed StringView must have same length"
);
let returned_bytes: &[u8] = unsafe { core::slice::from_raw_parts(out_sv.ptr, out_sv.len) };
let returned_str: &str =
core::str::from_utf8(returned_bytes).expect("echoed StringView must be valid UTF-8");
assert_eq!(returned_str, "café", "echoed string must equal input");
core::mem::forget(library);
}
#[test]
fn stress_zero_length_buffer_and_string_view() {
let library: libloading::Library = load_memory_plugin();
let interface_ptr: *const GuestContractInterface = init_memory_plugin_interface(&library);
let interface: &GuestContractInterface = unsafe { &*interface_ptr };
let zero_buf: Buffer = Buffer {
ptr: core::ptr::null_mut(),
len: 0,
cap: 0,
};
let zero_sv: StringView = StringView {
ptr: core::ptr::null(),
len: 0,
};
let args: ZeroArgs = ZeroArgs {
buf: zero_buf,
sv: zero_sv,
};
let mut out: ZeroResult = ZeroResult {
buf_len: u64::MAX,
sv_len: u64::MAX,
};
let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions.add(3) };
let dispatch_fn: unsafe extern "C" fn(GuestContractInstance, *const (), *mut (), *mut AbiError) =
unsafe { core::mem::transmute(fn_ptr) };
let mut call_result: AbiError = AbiError::ok();
unsafe {
dispatch_fn(
GuestContractInstance::null(),
&args as *const ZeroArgs as *const (),
&mut out as *mut ZeroResult as *mut (),
&mut call_result,
)
};
assert_eq!(
call_result.code,
AbiErrorCode::Ok as u32,
"memory_zero_length_roundtrip must return Ok"
);
assert_eq!(
out.buf_len, 0_u64,
"zero-length Buffer.len must round-trip as 0"
);
assert_eq!(
out.sv_len, 0_u64,
"zero-length StringView.len must round-trip as 0"
);
core::mem::forget(library);
}
#[test]
fn stress_concurrent_8_threads_no_shared_memory() {
let library: libloading::Library = load_memory_plugin();
let interface_ptr: *const GuestContractInterface = init_memory_plugin_interface(&library);
let interface: &GuestContractInterface = unsafe { &*interface_ptr };
const THREAD_COUNT: usize = 8;
const THREAD_BUFFER_SIZE: usize = 4096;
let alloc_count: Arc<AtomicUsize> = Arc::new(AtomicUsize::new(0));
let free_count: Arc<AtomicUsize> = Arc::new(AtomicUsize::new(0));
std::thread::scope(|s| {
for thread_idx in 0_usize..THREAD_COUNT {
let alloc_counter: Arc<AtomicUsize> = Arc::clone(&alloc_count);
let free_counter: Arc<AtomicUsize> = Arc::clone(&free_count);
let fill_byte: u8 = (0xA0_u8).wrapping_add(thread_idx as u8);
s.spawn(move || {
let ptr: *mut u8 = polyplug_host_alloc(THREAD_BUFFER_SIZE, 1);
assert!(!ptr.is_null(), "thread {}: alloc must succeed", thread_idx);
alloc_counter.fetch_add(1, Ordering::Relaxed);
let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions.add(0) };
let dispatch_fn: unsafe extern "C" fn(GuestContractInstance, *const (), *mut (), *mut AbiError) =
unsafe { core::mem::transmute(fn_ptr) };
let args: FillArgs = FillArgs {
buf: Buffer {
ptr,
len: 0,
cap: THREAD_BUFFER_SIZE,
},
fill_byte,
};
let mut out: u32 = 0_u32;
let mut result: AbiError = AbiError::ok();
unsafe {
dispatch_fn(
GuestContractInstance::null(),
&args as *const FillArgs as *const (),
&mut out as *mut u32 as *mut (),
&mut result,
)
};
assert_eq!(
result.code,
AbiErrorCode::Ok as u32,
"thread {}: fill must return Ok",
thread_idx
);
assert_eq!(
out as usize, THREAD_BUFFER_SIZE,
"thread {}: written count must equal buffer size",
thread_idx
);
let slice: &[u8] = unsafe { core::slice::from_raw_parts(ptr, THREAD_BUFFER_SIZE) };
assert!(
slice.iter().all(|&b| b == fill_byte),
"thread {}: all bytes must equal fill_byte 0x{:02X}",
thread_idx,
fill_byte
);
unsafe { polyplug_host_free(ptr, THREAD_BUFFER_SIZE, 1) };
free_counter.fetch_add(1, Ordering::Relaxed);
});
}
});
assert_eq!(
alloc_count.load(Ordering::Relaxed),
THREAD_COUNT,
"all {} threads must have allocated",
THREAD_COUNT
);
assert_eq!(
free_count.load(Ordering::Relaxed),
THREAD_COUNT,
"all {} threads must have freed",
THREAD_COUNT
);
core::mem::forget(library);
}
#[test]
fn stress_plugin_allocates_returns_to_host_then_host_frees() {
let library: libloading::Library = load_memory_plugin();
let interface_ptr: *const GuestContractInterface = init_memory_plugin_interface(&library);
let interface: &GuestContractInterface = unsafe { &*interface_ptr };
let tracker: TrackingAllocator = TrackingAllocator::new();
let alloc_fn: unsafe extern "C" fn(usize, usize) -> *mut u8 = tracker.alloc_fn();
let free_fn: unsafe extern "C" fn(*mut u8, usize, usize) = tracker.free_fn();
unsafe extern "C" fn tracking_alloc_wrapper(
_this: *const HostApi,
size: usize,
align: usize,
) -> *mut u8 {
TLS_TRACKING_ALLOC.with(|cell| {
let alloc_fn: unsafe extern "C" fn(usize, usize) -> *mut u8 = *cell.borrow();
unsafe { alloc_fn(size, align) }
})
}
unsafe extern "C" fn tracking_free_wrapper(
_this: *const HostApi,
ptr: *mut u8,
size: usize,
align: usize,
) {
TLS_TRACKING_FREE.with(|cell| {
let free_fn: unsafe extern "C" fn(*mut u8, usize, usize) = *cell.borrow();
unsafe { free_fn(ptr, size, align) }
})
}
TLS_TRACKING_ALLOC.with(|cell| *cell.borrow_mut() = alloc_fn);
TLS_TRACKING_FREE.with(|cell| *cell.borrow_mut() = free_fn);
let host_interface: HostApi = HostApi {
runtime: core::ptr::null_mut(),
register_guest_contract: registry_register_callback,
alloc: tracking_alloc_wrapper,
free: tracking_free_wrapper,
find_guest_contract: stub_find_guest_contract,
find_all_guest_contracts: stub_find_all_guest_contracts,
resolve_guest_contract: stub_resolve_guest_contract,
get_host_contract: stub_get_host_contract,
resolve_host_contract_interface: stub_resolve_host_contract_interface,
list_bundles: stub_list_bundles,
get_dependencies: stub_get_dependencies,
load_bundle: stub_load_bundle,
reload_bundle: stub_reload_bundle,
register_host_contract: stub_register_host_contract,
register_loader: stub_register_loader,
get_last_error: stub_get_last_error,
get_error_len: stub_get_error_len,
unload_bundle: stub_unload_bundle,
log: stub_host_log,
create_guest_instance: stub_create_guest_instance,
destroy_guest_instance: stub_destroy_guest_instance,
revision_counter: stub_revision_counter,
reserved: core::ptr::null(),
};
let args: AllocArgs = AllocArgs {
host: &host_interface as *const HostApi,
size: 4096_u64,
fill_byte: 0xCC_u8,
};
let mut out_buf: Buffer = Buffer {
ptr: core::ptr::null_mut(),
len: 0,
cap: 0,
};
let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions.add(1) };
let dispatch_fn: unsafe extern "C" fn(GuestContractInstance, *const (), *mut (), *mut AbiError) =
unsafe { core::mem::transmute(fn_ptr) };
let mut call_result: AbiError = AbiError::ok();
unsafe {
dispatch_fn(
GuestContractInstance::null(),
&args as *const AllocArgs as *const (),
&mut out_buf as *mut Buffer as *mut (),
&mut call_result,
)
};
assert_eq!(
call_result.code,
AbiErrorCode::Ok as u32,
"memory_alloc_buffer_via_host must return Ok"
);
assert!(
!out_buf.ptr.is_null(),
"plugin-allocated buffer pointer must be non-null"
);
assert!(
out_buf.len > 0,
"plugin-allocated buffer len must be non-zero"
);
assert_eq!(
tracker.alloc_count(),
1,
"alloc_count must be 1 after plugin allocated"
);
assert_eq!(
tracker.free_count(),
0,
"free_count must be 0 before we free"
);
let buf_slice: &[u8] = unsafe { core::slice::from_raw_parts(out_buf.ptr, out_buf.len) };
assert!(
buf_slice.iter().all(|&b| b == 0xCC_u8),
"all bytes in plugin-allocated buffer must be 0xCC"
);
let free_fn: unsafe extern "C" fn(*mut u8, usize, usize) = tracker.free_fn();
unsafe { free_fn(out_buf.ptr, out_buf.cap, 1) };
assert_eq!(tracker.alloc_count(), 1, "alloc_count must still be 1");
assert_eq!(tracker.free_count(), 1, "free_count must be 1 after free");
tracker.assert_no_leaks();
core::mem::forget(library);
}
#[test]
fn stress_caller_alloc_plugin_fills_freed_after_use() {
let library: libloading::Library = load_memory_plugin();
let interface_ptr: *const GuestContractInterface = init_memory_plugin_interface(&library);
let interface: &GuestContractInterface = unsafe { &*interface_ptr };
let tracker: TrackingAllocator = TrackingAllocator::new();
let alloc_fn: unsafe extern "C" fn(usize, usize) -> *mut u8 = tracker.alloc_fn();
let ptr: *mut u8 = unsafe { alloc_fn(64, 1) };
assert!(!ptr.is_null(), "tracker alloc must return non-null");
assert_eq!(
tracker.alloc_count(),
1,
"alloc_count must be 1 after caller allocation"
);
let args: FillArgs = FillArgs {
buf: Buffer {
ptr,
len: 0,
cap: 64,
},
fill_byte: 0xDE_u8,
};
let mut out: u32 = 0_u32;
let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions.add(0) };
let dispatch_fn: unsafe extern "C" fn(GuestContractInstance, *const (), *mut (), *mut AbiError) =
unsafe { core::mem::transmute(fn_ptr) };
let mut call_result: AbiError = AbiError::ok();
unsafe {
dispatch_fn(
GuestContractInstance::null(),
&args as *const FillArgs as *const (),
&mut out as *mut u32 as *mut (),
&mut call_result,
)
};
assert_eq!(
call_result.code,
AbiErrorCode::Ok as u32,
"memory_fill_preallocated_buffer must return Ok"
);
assert_eq!(out, 64_u32, "written byte count must be 64");
let filled_slice: &[u8] = unsafe { core::slice::from_raw_parts(ptr, 64) };
assert!(
filled_slice.iter().all(|&b| b == 0xDE_u8),
"all 64 bytes must be 0xDE"
);
let free_fn: unsafe extern "C" fn(*mut u8, usize, usize) = tracker.free_fn();
unsafe { free_fn(ptr, 64, 1) };
assert_eq!(tracker.alloc_count(), 1, "alloc_count must be 1");
assert_eq!(tracker.free_count(), 1, "free_count must be 1 after free");
tracker.assert_no_leaks();
let _workspace_root: PathBuf = workspace_root();
core::mem::forget(library);
}
#[cfg(debug_assertions)]
fn run_double_free_subprocess() -> ! {
let tracker: TrackingAllocator = TrackingAllocator::new();
let alloc: unsafe extern "C" fn(usize, usize) -> *mut u8 = tracker.alloc_fn();
let free_fn: unsafe extern "C" fn(*mut u8, usize, usize) = tracker.free_fn();
let ptr: *mut u8 = unsafe { alloc(64_usize, 1_usize) };
unsafe { free_fn(ptr, 64_usize, 1_usize) };
unsafe { free_fn(ptr, 64_usize, 1_usize) };
std::process::exit(0)
}
#[test]
#[cfg(debug_assertions)]
fn test_double_free_detected() {
const SENTINEL: &str = "POLYPLUG_DOUBLE_FREE_SUBPROCESS";
if std::env::var(SENTINEL).is_ok() {
run_double_free_subprocess();
}
let exe: std::path::PathBuf = std::env::current_exe().expect("current_exe");
let status: std::process::ExitStatus = std::process::Command::new(&exe)
.env(SENTINEL, "1")
.status()
.expect("failed to spawn subprocess");
assert!(
!status.success(),
"double-free subprocess must exit non-zero (aborted)"
);
}
unsafe extern "C" fn stub_host_log(
_this: *const polyplug_abi::HostApi,
_level: u32,
_scope: polyplug_abi::StringView,
_message: polyplug_abi::StringView,
) {
}
unsafe extern "C" fn stub_create_guest_instance(
_this: *const polyplug_abi::HostApi,
_interface: *const polyplug_abi::GuestContractInterface,
_args: *const core::ffi::c_void,
out_instance: *mut polyplug_abi::GuestContractInstance,
) {
if !out_instance.is_null() {
unsafe { out_instance.write(polyplug_abi::GuestContractInstance::null()) };
}
}
unsafe extern "C" fn stub_destroy_guest_instance(
_this: *const polyplug_abi::HostApi,
_interface: *const polyplug_abi::GuestContractInterface,
_instance: polyplug_abi::GuestContractInstance,
) {
}
unsafe extern "C" fn stub_revision_counter(_this: *const polyplug_abi::HostApi) -> *const u64 {
core::ptr::null()
}