mirror of
https://github.com/extism/extism.git
synced 2026-01-09 22:07:57 -05:00
feat: Use quickcheck to test allocations, fix one bug that was uncovered. (#662)
- Adds quickcheck tests for alloc/free/load/store from the kernel, I wasn't able to include these in the new wasm-bindgen tests because `getrandom` isn't available on wasm32-unknown-unknown - Fixes a bug in the calculation of how much memory is needed to allocate the next block in the kernel. This bug is triggered when allocating at the end of a page, the size of the MemoryBlock value wasn't being taken in consideration when determining whether or not to call memory.grow
This commit is contained in:
@@ -270,12 +270,13 @@ impl MemoryRoot {
|
||||
|
||||
// Get the number of bytes available
|
||||
let mem_left = self_length - self_position - core::mem::size_of::<MemoryRoot>() as u64;
|
||||
let length_with_block = length + core::mem::size_of::<MemoryBlock>() as u64;
|
||||
|
||||
// When the allocation is larger than the number of bytes available
|
||||
// we will need to try to grow the memory
|
||||
if length >= mem_left {
|
||||
if length_with_block >= mem_left {
|
||||
// Calculate the number of pages needed to cover the remaining bytes
|
||||
let npages = num_pages(length - mem_left);
|
||||
let npages = num_pages(length_with_block - mem_left);
|
||||
let x = core::arch::wasm32::memory_grow(0, npages);
|
||||
if x == usize::MAX {
|
||||
return None;
|
||||
|
||||
@@ -37,6 +37,8 @@ cbindgen = { version = "0.26", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
quickcheck = "1"
|
||||
rand = "0.8.5"
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
|
||||
Binary file not shown.
@@ -1,4 +1,5 @@
|
||||
use crate::*;
|
||||
use quickcheck::*;
|
||||
|
||||
const KERNEL: &[u8] = include_bytes!("../extism-runtime.wasm");
|
||||
|
||||
@@ -319,3 +320,141 @@ fn test_load_input() {
|
||||
// Out of bounds should return 0
|
||||
assert_eq!(extism_input_load_u64(&mut store, instance, 123457), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failed_quickcheck1() {
|
||||
let (mut store, mut instance) = init_kernel_test();
|
||||
let allocs = [
|
||||
20622, 23162, 58594, 32421, 25928, 44611, 26318, 24455, 5798, 60202, 42126, 64928, 57832,
|
||||
50888, 63256, 37562, 46334, 47985, 60836, 28132, 65535, 37800, 33150, 48768, 38457, 57249,
|
||||
5734, 58587, 26294, 26653, 24519, 1,
|
||||
];
|
||||
|
||||
extism_reset(&mut store, &mut instance);
|
||||
for a in allocs {
|
||||
println!("Alloc: {a}");
|
||||
let n = extism_alloc(&mut store, &mut instance, a);
|
||||
if n == 0 {
|
||||
continue;
|
||||
}
|
||||
assert_eq!(a, extism_length(&mut store, &mut instance, n));
|
||||
}
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn check_alloc(amounts: Vec<u16>) -> bool {
|
||||
let (mut store, mut instance) = init_kernel_test();
|
||||
let instance = &mut instance;
|
||||
for a in amounts {
|
||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
||||
if ptr == 0 || ptr == u64::MAX {
|
||||
continue
|
||||
}
|
||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn check_large_alloc(amounts: Vec<u32>) -> bool {
|
||||
let (mut store, mut instance) = init_kernel_test();
|
||||
let instance = &mut instance;
|
||||
for a in amounts {
|
||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
||||
if ptr == 0 || ptr == u64::MAX {
|
||||
continue
|
||||
}
|
||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn check_alloc_with_frees(amounts: Vec<u16>) -> bool {
|
||||
let (mut store, mut instance) = init_kernel_test();
|
||||
let instance = &mut instance;
|
||||
let mut prev = 0;
|
||||
for a in amounts {
|
||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
||||
if ptr == 0 {
|
||||
continue
|
||||
}
|
||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
||||
return false
|
||||
}
|
||||
|
||||
if a % 2 == 0 {
|
||||
extism_free(&mut store, instance, ptr);
|
||||
} else if a % 3 == 0 {
|
||||
extism_free(&mut store, instance, prev);
|
||||
}
|
||||
|
||||
prev = ptr;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn check_large_alloc_with_frees(amounts: Vec<u32>) -> bool {
|
||||
let (mut store, mut instance) = init_kernel_test();
|
||||
let instance = &mut instance;
|
||||
let mut prev = 0;
|
||||
for a in amounts {
|
||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
||||
if ptr == 0 || ptr == u64::MAX {
|
||||
continue
|
||||
}
|
||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
||||
return false
|
||||
}
|
||||
if a % 2 == 0 {
|
||||
extism_free(&mut store, instance, ptr);
|
||||
} else if a % 3 == 0 {
|
||||
extism_free(&mut store, instance, prev);
|
||||
}
|
||||
|
||||
prev = ptr;
|
||||
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn check_alloc_with_load_and_store(amounts: Vec<u16>) -> bool {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
let (mut store, mut instance) = init_kernel_test();
|
||||
let instance = &mut instance;
|
||||
for a in amounts {
|
||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
||||
if ptr == 0 || ptr == u64::MAX {
|
||||
continue
|
||||
}
|
||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _ in 0..16 {
|
||||
let i = rng.gen_range(ptr..ptr+a as u64);
|
||||
extism_store_u8(&mut store, instance, i, i as u8);
|
||||
if extism_load_u8(&mut store, instance, i as u64) != i as u8 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +39,12 @@ pub struct Count {
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
tracing_subscriber::fmt()
|
||||
let log = tracing_subscriber::fmt()
|
||||
.with_ansi(false)
|
||||
.with_env_filter("extism=debug")
|
||||
.with_writer(std::fs::File::create("test.log").unwrap())
|
||||
.init();
|
||||
.try_init()
|
||||
.is_ok();
|
||||
|
||||
let wasm_start = Instant::now();
|
||||
|
||||
@@ -143,8 +144,10 @@ fn it_works() {
|
||||
println!("wasm function call (avg, N = {}): {:?}", num_tests, avg);
|
||||
|
||||
// Check that log file was written to
|
||||
if log {
|
||||
let meta = std::fs::metadata("test.log").unwrap();
|
||||
assert!(meta.len() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user