mirror of
https://github.com/extism/extism.git
synced 2026-01-12 07:18:02 -05:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24622cd511 | ||
|
|
00f9155b9e | ||
|
|
7c770e2f1c | ||
|
|
33ad239c50 | ||
|
|
a62e37c21b | ||
|
|
20ee6620c6 | ||
|
|
db0b04696c | ||
|
|
6cc22bf2de | ||
|
|
52d3c4ffd6 | ||
|
|
b96b28231f | ||
|
|
c1180d576c | ||
|
|
48af68facc | ||
|
|
38e6476797 |
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1 +0,0 @@
|
||||
* @zshipko
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -61,10 +61,10 @@ jobs:
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'ubuntu'
|
||||
target: 'x86_64-unknown-linux-musl'
|
||||
artifact: 'libextism.so'
|
||||
artifact: ''
|
||||
static-artifact: 'libextism.a'
|
||||
static-dll-artifact: ''
|
||||
pc-in: 'extism.pc.in'
|
||||
pc-in: ''
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'windows'
|
||||
target: 'x86_64-pc-windows-gnu'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
[](https://extism.org/discord)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -61,7 +61,7 @@ started:
|
||||
|
||||
# Compile WebAssembly to run in Extism Hosts
|
||||
|
||||
Extism Hosts (running the SDK) must execute WebAssembly code that has a [PDK, or Plug-in Development Kit](https://extism.org/docs/concepts/pdk),
|
||||
Extism Hosts (running the SDK) must execute WebAssembly code that has a PDK
|
||||
library compiled in to the `.wasm` binary. PDKs make it easy for plug-in /
|
||||
extension code authors to read input from the host and return data back, read
|
||||
provided configuration, set/get variables, make outbound HTTP calls if allowed,
|
||||
|
||||
@@ -13,7 +13,7 @@ description = "Traits to make Rust types usable with Extism"
|
||||
anyhow = "1.0.75"
|
||||
base64 = "~0.22"
|
||||
bytemuck = {version = "1.14.0", optional = true }
|
||||
prost = { version = "0.13.1", optional = true }
|
||||
prost = { version = "0.12.0", optional = true }
|
||||
protobuf = { version = "3.2.0", optional = true }
|
||||
rmp-serde = { version = "1.1.2", optional = true }
|
||||
serde = "1.0.186"
|
||||
|
||||
@@ -81,10 +81,10 @@ mod tests {
|
||||
c: true,
|
||||
};
|
||||
let raw = Raw(&x).to_bytes().unwrap();
|
||||
let y = Raw::from_bytes(raw).unwrap();
|
||||
let y = Raw::from_bytes(&raw).unwrap();
|
||||
assert_eq!(&x, y.0);
|
||||
|
||||
let y: Result<Raw<[u8; std::mem::size_of::<TestRaw>()]>, Error> = Raw::from_bytes(raw);
|
||||
let y: Result<Raw<[u8; std::mem::size_of::<TestRaw>()]>, Error> = Raw::from_bytes(&raw);
|
||||
assert!(y.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
3
kernel/.gitignore
vendored
Normal file
3
kernel/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
owi-out
|
||||
*.wat
|
||||
proofs
|
||||
@@ -7,6 +7,7 @@ edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.39"
|
||||
owi = {git = "https://github.com/dylibso/owi-rs"}
|
||||
|
||||
[features]
|
||||
default = ["bounds-checking"]
|
||||
@@ -15,4 +16,4 @@ bounds-checking = []
|
||||
[workspace]
|
||||
members = [
|
||||
"."
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
export CARGO_FLAGS=""
|
||||
|
||||
while getopts d flag
|
||||
|
||||
21
kernel/examples/alloc_length.rs
Normal file
21
kernel/examples/alloc_length.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
use extism_runtime_kernel::*;
|
||||
use owi::*;
|
||||
|
||||
main!({
|
||||
let n = alloc(1024);
|
||||
|
||||
let x = u64();
|
||||
assume(x > 0);
|
||||
|
||||
let m = alloc(x);
|
||||
|
||||
// 1. Length should equal `x` while active
|
||||
assert(length(m) == x);
|
||||
|
||||
// 2. Length should equal `0` after free
|
||||
free(m); // Free the block
|
||||
assert(length(m) == 0);
|
||||
free(n);
|
||||
});
|
||||
24
kernel/examples/error.rs
Normal file
24
kernel/examples/error.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
use extism_runtime_kernel::*;
|
||||
use owi::*;
|
||||
|
||||
main!({
|
||||
let x = u64();
|
||||
let n = u64();
|
||||
let m = u64();
|
||||
assume(x > 0);
|
||||
assume(n > 0);
|
||||
assume(m > 0);
|
||||
|
||||
alloc(n);
|
||||
alloc(m);
|
||||
let n = alloc(x);
|
||||
assert(error_get() == 0);
|
||||
|
||||
error_set(n);
|
||||
assert(error_get() == n);
|
||||
alloc(m);
|
||||
error_set(0);
|
||||
assert(error_get() == 0);
|
||||
});
|
||||
17
kernel/examples/load_store.rs
Normal file
17
kernel/examples/load_store.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
use extism_runtime_kernel::*;
|
||||
use owi::*;
|
||||
|
||||
main!({
|
||||
let x = u64();
|
||||
assume(x > 0);
|
||||
|
||||
let m = alloc(x);
|
||||
assert(length(m) == x);
|
||||
for i in 0..x {
|
||||
store_u8(m + i, i as u8);
|
||||
assert(load_u8(m + i) == i as u8);
|
||||
}
|
||||
free(m);
|
||||
});
|
||||
39
kernel/examples/reuse.rs
Normal file
39
kernel/examples/reuse.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
use extism_runtime_kernel::*;
|
||||
use owi::*;
|
||||
|
||||
main!({
|
||||
let x = u64();
|
||||
assume(x > 0);
|
||||
|
||||
let y = u64();
|
||||
assume(y > 0);
|
||||
|
||||
let mut tmp = 0;
|
||||
|
||||
for _ in 0..y {
|
||||
let m = alloc(x);
|
||||
if tmp == 0 {
|
||||
tmp = m;
|
||||
} else {
|
||||
// Check that the existing block is being re-used
|
||||
assert(m == tmp);
|
||||
}
|
||||
|
||||
assert(length(m) == x);
|
||||
free(m); // Free the block
|
||||
}
|
||||
|
||||
let y = u64();
|
||||
assume(y == x + 1);
|
||||
let n = alloc(y);
|
||||
assert(n > tmp);
|
||||
|
||||
let z = u64();
|
||||
assume(z <= x);
|
||||
assume(x - z < 32);
|
||||
assume(z > 0);
|
||||
let p = alloc(z);
|
||||
assert(p == tmp);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
pub use extism_runtime_kernel::*;
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ impl MemoryRoot {
|
||||
core::ptr::write_bytes(
|
||||
self.blocks.as_mut_ptr() as *mut u8,
|
||||
MemoryStatus::Unused as u8,
|
||||
self_position as usize,
|
||||
self_position as usize - core::mem::size_of::<MemoryRoot>(),
|
||||
);
|
||||
|
||||
// Clear extism runtime metadata
|
||||
@@ -255,6 +255,15 @@ impl MemoryRoot {
|
||||
pub unsafe fn alloc(&mut self, length: u64) -> Option<&'static mut MemoryBlock> {
|
||||
let self_position = self.position.load(Ordering::Acquire);
|
||||
let self_length = self.length.load(Ordering::Acquire);
|
||||
let b = self.find_free_block(length, self_position);
|
||||
|
||||
// If there's a free block then re-use it
|
||||
if let Some(b) = b {
|
||||
b.used = length as usize;
|
||||
b.status
|
||||
.store(MemoryStatus::Active as u8, Ordering::Release);
|
||||
return Some(b);
|
||||
}
|
||||
|
||||
// Get the current index for a new block
|
||||
let curr = self.blocks.as_ptr() as u64 + self_position;
|
||||
@@ -266,21 +275,6 @@ impl MemoryRoot {
|
||||
// When the allocation is larger than the number of bytes available
|
||||
// we will need to try to grow the memory
|
||||
if length_with_block >= mem_left {
|
||||
// If the current position is large enough to hold the length of the block being
|
||||
// allocated then check for existing free blocks that can be re-used before
|
||||
// growing memory
|
||||
if length_with_block <= self_position {
|
||||
let b = self.find_free_block(length, self_position);
|
||||
|
||||
// If there's a free block then re-use it
|
||||
if let Some(b) = b {
|
||||
b.used = length as usize;
|
||||
b.status
|
||||
.store(MemoryStatus::Active as u8, Ordering::Release);
|
||||
return Some(b);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the number of pages needed to cover the remaining bytes
|
||||
let npages = num_pages(length_with_block - mem_left);
|
||||
let x = core::arch::wasm32::memory_grow(0, npages);
|
||||
@@ -495,8 +489,6 @@ pub unsafe fn store_u64(p: Pointer, x: u64) {
|
||||
/// Set the range of the input data in memory
|
||||
/// h must always be a handle so that length works on it
|
||||
/// len must match length(handle)
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
#[no_mangle]
|
||||
pub unsafe fn input_set(h: Handle, len: u64) {
|
||||
let root = MemoryRoot::new();
|
||||
@@ -511,8 +503,6 @@ pub unsafe fn input_set(h: Handle, len: u64) {
|
||||
}
|
||||
|
||||
/// Set the range of the output data in memory
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
#[no_mangle]
|
||||
pub unsafe fn output_set(p: Pointer, len: u64) {
|
||||
let root = MemoryRoot::new();
|
||||
@@ -556,10 +546,7 @@ pub unsafe fn reset() {
|
||||
MemoryRoot::new().reset()
|
||||
}
|
||||
|
||||
/// Set the error message offset, the handle passed to this
|
||||
/// function should not be freed after this call
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
/// Set the error message offset
|
||||
#[no_mangle]
|
||||
pub unsafe fn error_set(h: Handle) {
|
||||
let root = MemoryRoot::new();
|
||||
@@ -594,6 +581,45 @@ mod test {
|
||||
use crate::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
// See https://github.com/extism/extism/pull/659
|
||||
#[wasm_bindgen_test]
|
||||
fn test_659() {
|
||||
unsafe {
|
||||
// Warning: These offsets will need to change if we adjust the kernel memory layout at all
|
||||
reset();
|
||||
assert_eq!(alloc(1065), 77);
|
||||
assert_eq!(alloc(288), 1154);
|
||||
assert_eq!(alloc(128), 1454);
|
||||
assert_eq!(length(1154), 288);
|
||||
assert_eq!(length(1454), 128);
|
||||
free(1454);
|
||||
assert_eq!(alloc(213), 1594);
|
||||
length_unsafe(1594);
|
||||
assert_eq!(alloc(511), 1819);
|
||||
assert_eq!(alloc(4), 1454);
|
||||
assert_eq!(length(1454), 4);
|
||||
assert_eq!(length(1819), 511);
|
||||
assert_eq!(alloc(13), 2342);
|
||||
assert_eq!(length(2342), 13);
|
||||
assert_eq!(alloc(336), 2367);
|
||||
assert_eq!(alloc(1077), 2715);
|
||||
assert_eq!(length(2367), 336);
|
||||
assert_eq!(length(2715), 1077);
|
||||
free(2715);
|
||||
assert_eq!(alloc(1094), 3804);
|
||||
length_unsafe(3804);
|
||||
|
||||
// Allocate 4 bytes, expect to receive address 3788
|
||||
assert_eq!(alloc(4), 3788);
|
||||
|
||||
assert_eq!(alloc(4), 3772);
|
||||
assert_eq!(length(3772), 4);
|
||||
|
||||
// Address 3788 has not been freed yet, so expect it to have 4 bytes allocated
|
||||
assert_eq!(length(3788), 4);
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_oom() {
|
||||
let size = 1024 * 1024 * 5;
|
||||
@@ -612,4 +638,15 @@ mod test {
|
||||
|
||||
assert_eq!(last, 0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_sym() {
|
||||
unsafe {
|
||||
reset();
|
||||
let a = alloc(47);
|
||||
let b = alloc(20);
|
||||
assert_eq!(length(a), 47);
|
||||
assert_eq!(length(b), 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
kernel/verify.sh
Executable file
23
kernel/verify.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
OUT_DIR=./target/wasm32-unknown-unknown/release/examples/
|
||||
get_proof() {
|
||||
cp "$OUT_DIR/$1.wasm" "./proofs/$1.wasm"
|
||||
}
|
||||
|
||||
cargo build --examples --release --target wasm32-unknown-unknown --no-default-features
|
||||
|
||||
mkdir -p proofs
|
||||
get_proof alloc_length
|
||||
get_proof load_store
|
||||
get_proof reuse
|
||||
get_proof error
|
||||
|
||||
for proof in $(ls proofs/*.wasm); do
|
||||
echo "Checking $proof"
|
||||
owi conc "$proof" $@
|
||||
echo
|
||||
echo "---"
|
||||
done
|
||||
@@ -279,7 +279,7 @@ pub struct Manifest {
|
||||
/// the path on disk to the path it should be available inside the plugin.
|
||||
/// For example, `".": "/tmp"` would mount the current directory as `/tmp` inside the module
|
||||
#[serde(default)]
|
||||
pub allowed_paths: Option<BTreeMap<String, PathBuf>>,
|
||||
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
|
||||
|
||||
/// The plugin timeout in milliseconds
|
||||
#[serde(default)]
|
||||
@@ -337,7 +337,8 @@ impl Manifest {
|
||||
}
|
||||
|
||||
/// Add a path to `allowed_paths`
|
||||
pub fn with_allowed_path(mut self, src: String, dest: impl AsRef<Path>) -> Self {
|
||||
pub fn with_allowed_path(mut self, src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Self {
|
||||
let src = src.as_ref().to_path_buf();
|
||||
let dest = dest.as_ref().to_path_buf();
|
||||
match &mut self.allowed_paths {
|
||||
Some(p) => {
|
||||
@@ -354,7 +355,7 @@ impl Manifest {
|
||||
}
|
||||
|
||||
/// Set `allowed_paths`
|
||||
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (String, PathBuf)>) -> Self {
|
||||
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (PathBuf, PathBuf)>) -> Self {
|
||||
self.allowed_paths = Some(paths.collect());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -9,9 +9,8 @@ repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
wasmtime = ">= 20.0.0, < 24.0.0"
|
||||
wasi-common = ">= 20.0.0, < 24.0.0"
|
||||
wiggle = ">= 20.0.0, < 24.0.0"
|
||||
wasmtime = ">= 20.0.0, < 22.0.0"
|
||||
wasi-common = ">= 20.0.0, < 22.0.0"
|
||||
anyhow = "1"
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde_json = "1"
|
||||
@@ -34,7 +33,7 @@ register-filesystem = [] # enables wasm to be loaded from disk
|
||||
http = ["ureq"] # enables extism_http_request
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = { version = "0.27", default-features = false }
|
||||
cbindgen = { version = "0.26", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
|
||||
@@ -12,7 +12,7 @@ To use the `extism` crate, you can add it to your Cargo file:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
extism = "1.4.1"
|
||||
extism = "1.2.0"
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
@@ -6,7 +6,6 @@ const COUNT_VOWELS: &[u8] = include_bytes!("../../wasm/code.wasm");
|
||||
const REFLECT: &[u8] = include_bytes!("../../wasm/reflect.wasm");
|
||||
const ECHO: &[u8] = include_bytes!("../../wasm/echo.wasm");
|
||||
const CONSUME: &[u8] = include_bytes!("../../wasm/consume.wasm");
|
||||
const ALLOCATIONS: &[u8] = include_bytes!("../../wasm/allocations.wasm");
|
||||
|
||||
host_fn!(hello_world (a: String) -> String { Ok(a) });
|
||||
|
||||
@@ -36,21 +35,6 @@ pub fn create_plugin(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn create_plugin_no_cache(c: &mut Criterion) {
|
||||
let mut g = c.benchmark_group("create");
|
||||
g.noise_threshold(1.0);
|
||||
g.significance_level(0.2);
|
||||
g.bench_function("create_plugin_no_cache", |b| {
|
||||
b.iter(|| {
|
||||
let _plugin = PluginBuilder::new(COUNT_VOWELS)
|
||||
.with_cache_disabled()
|
||||
.with_wasi(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, PartialEq)]
|
||||
struct Count {
|
||||
count: u32,
|
||||
@@ -169,17 +153,6 @@ pub fn reflect(c: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocations(c: &mut Criterion) {
|
||||
let mut g = c.benchmark_group("allocations");
|
||||
|
||||
let mut plugin = PluginBuilder::new(ALLOCATIONS).build().unwrap();
|
||||
g.bench_function("allocations", |b| {
|
||||
b.iter(|| {
|
||||
plugin.call::<_, ()>("allocations", "").unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// This is an apples-to-apples comparison of a linked wasm "reflect" function to our host "reflect"
|
||||
// function.
|
||||
pub fn reflect_linked(c: &mut Criterion) {
|
||||
@@ -272,14 +245,12 @@ pub fn reflect_linked(c: &mut Criterion) {
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
allocations,
|
||||
consume,
|
||||
echo,
|
||||
reflect,
|
||||
reflect_linked,
|
||||
basic,
|
||||
create_plugin,
|
||||
create_plugin_no_cache,
|
||||
count_vowels
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
use extism::*;
|
||||
fn main() {
|
||||
let url = Wasm::file("../wasm/read_write.wasm");
|
||||
let manifest = Manifest::new([url])
|
||||
.with_allowed_path("ro:src/tests/data".to_string(), "/data")
|
||||
.with_config_key("path", "/data/data.txt");
|
||||
|
||||
let mut plugin = PluginBuilder::new(manifest)
|
||||
.with_wasi(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
println!("trying to read file: ");
|
||||
|
||||
let res = plugin.call::<&str, &str>("try_read", "").unwrap();
|
||||
|
||||
println!("{:?}", res);
|
||||
|
||||
println!("-----------------------------------------------------");
|
||||
|
||||
println!("trying to write file: ");
|
||||
let line = format!(
|
||||
"Hello World at {:?}\n",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
);
|
||||
let res2 = plugin.call::<&str, &str>("try_write", &line).unwrap();
|
||||
|
||||
println!("{:?}", res2);
|
||||
|
||||
println!("done!");
|
||||
}
|
||||
@@ -195,22 +195,6 @@ ExtismPlugin *extism_plugin_new(const uint8_t *wasm,
|
||||
bool with_wasi,
|
||||
char **errmsg);
|
||||
|
||||
/**
|
||||
* Create a new plugin and set the number of instructions a plugin is allowed to execute
|
||||
*/
|
||||
ExtismPlugin *extism_plugin_new_with_fuel_limit(const uint8_t *wasm,
|
||||
ExtismSize wasm_size,
|
||||
const ExtismFunction **functions,
|
||||
ExtismSize n_functions,
|
||||
bool with_wasi,
|
||||
uint64_t fuel_limit,
|
||||
char **errmsg);
|
||||
|
||||
/**
|
||||
* Enable HTTP response headers in plugins using `extism:host/env::http_request`
|
||||
*/
|
||||
void extism_plugin_allow_http_response_headers(ExtismPlugin *plugin);
|
||||
|
||||
/**
|
||||
* Free the error returned by `extism_plugin_new`, errors returned from `extism_plugin_error` don't need to be freed
|
||||
*/
|
||||
@@ -318,5 +302,5 @@ bool extism_plugin_reset(ExtismPlugin *plugin);
|
||||
const char *extism_version(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// CurrentPlugin stores data that is available to the caller in PDK functions, this should
|
||||
@@ -14,7 +12,6 @@ pub struct CurrentPlugin {
|
||||
pub(crate) linker: *mut wasmtime::Linker<CurrentPlugin>,
|
||||
pub(crate) wasi: Option<Wasi>,
|
||||
pub(crate) http_status: u16,
|
||||
pub(crate) http_headers: Option<std::collections::BTreeMap<String, String>>,
|
||||
pub(crate) available_pages: Option<u32>,
|
||||
pub(crate) memory_limiter: Option<MemoryLimiter>,
|
||||
pub(crate) id: uuid::Uuid,
|
||||
@@ -163,10 +160,10 @@ impl CurrentPlugin {
|
||||
}
|
||||
|
||||
pub fn memory_bytes_mut(&mut self, handle: MemoryHandle) -> Result<&mut [u8], Error> {
|
||||
let (linker, store) = self.linker_and_store();
|
||||
if let Some(mem) = linker.get(&mut *store, EXTISM_ENV_MODULE, "memory") {
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
if let Some(mem) = linker.get(&mut store, EXTISM_ENV_MODULE, "memory") {
|
||||
let mem = mem.into_memory().unwrap();
|
||||
let ptr = unsafe { mem.data_ptr(&*store).add(handle.offset() as usize) };
|
||||
let ptr = unsafe { mem.data_ptr(&store).add(handle.offset() as usize) };
|
||||
if ptr.is_null() {
|
||||
return Ok(&mut []);
|
||||
}
|
||||
@@ -177,10 +174,10 @@ impl CurrentPlugin {
|
||||
}
|
||||
|
||||
pub fn memory_bytes(&mut self, handle: MemoryHandle) -> Result<&[u8], Error> {
|
||||
let (linker, store) = self.linker_and_store();
|
||||
if let Some(mem) = linker.get(&mut *store, EXTISM_ENV_MODULE, "memory") {
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
if let Some(mem) = linker.get(&mut store, EXTISM_ENV_MODULE, "memory") {
|
||||
let mem = mem.into_memory().unwrap();
|
||||
let ptr = unsafe { mem.data_ptr(&*store).add(handle.offset() as usize) };
|
||||
let ptr = unsafe { mem.data_ptr(&store).add(handle.offset() as usize) };
|
||||
if ptr.is_null() {
|
||||
return Ok(&[]);
|
||||
}
|
||||
@@ -190,26 +187,20 @@ impl CurrentPlugin {
|
||||
anyhow::bail!("{} unable to locate extism memory", self.id)
|
||||
}
|
||||
|
||||
pub fn host_context<T: 'static>(&mut self) -> Result<&mut T, Error> {
|
||||
let (linker, store) = self.linker_and_store();
|
||||
let Some(Extern::Global(xs)) = linker.get(&mut *store, EXTISM_ENV_MODULE, "extism_context")
|
||||
pub fn host_context<T: Clone + 'static>(&mut self) -> Result<T, Error> {
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
let Some(Extern::Global(xs)) = linker.get(&mut store, EXTISM_ENV_MODULE, "extism_context")
|
||||
else {
|
||||
anyhow::bail!("unable to locate an extism kernel global: extism_context",)
|
||||
};
|
||||
|
||||
let Val::ExternRef(Some(xs)) = xs.get(&mut *store) else {
|
||||
let Val::ExternRef(Some(xs)) = xs.get(&mut store) else {
|
||||
anyhow::bail!("expected extism_context to be an externref value",)
|
||||
};
|
||||
|
||||
match xs
|
||||
.data_mut(&mut *store)?
|
||||
.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>()
|
||||
{
|
||||
Some(xs) => match xs.downcast_mut::<T>() {
|
||||
Some(xs) => Ok(xs),
|
||||
None => anyhow::bail!("could not downcast extism_context inner value"),
|
||||
},
|
||||
None => anyhow::bail!("could not downcast extism_context"),
|
||||
match xs.data(&mut store)?.downcast_ref::<T>().cloned() {
|
||||
Some(xs) => Ok(xs.clone()),
|
||||
None => anyhow::bail!("could not downcast extism_context",),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,13 +214,9 @@ impl CurrentPlugin {
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
let output = &mut [Val::I64(0)];
|
||||
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "alloc") {
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut *store, &[Val::I64(n as i64)], output)
|
||||
.context("failed to allocate extism memory")
|
||||
)?;
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut store, &[Val::I64(n as i64)], output)?;
|
||||
} else {
|
||||
anyhow::bail!("{} unable to allocate memory", self.id);
|
||||
}
|
||||
@@ -251,15 +238,11 @@ impl CurrentPlugin {
|
||||
|
||||
/// Free a block of Extism plugin memory
|
||||
pub fn memory_free(&mut self, handle: MemoryHandle) -> Result<(), Error> {
|
||||
let (linker, store) = self.linker_and_store();
|
||||
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "free") {
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut *store, &[Val::I64(handle.offset as i64)], &mut [])
|
||||
.context("failed to free extism memory")
|
||||
)?;
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "free") {
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut store, &[Val::I64(handle.offset as i64)], &mut [])?;
|
||||
} else {
|
||||
anyhow::bail!("unable to locate an extism kernel function: free",)
|
||||
}
|
||||
@@ -267,16 +250,12 @@ impl CurrentPlugin {
|
||||
}
|
||||
|
||||
pub fn memory_length(&mut self, offs: u64) -> Result<u64, Error> {
|
||||
let (linker, store) = self.linker_and_store();
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
let output = &mut [Val::I64(0)];
|
||||
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "length") {
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut *store, &[Val::I64(offs as i64)], output)
|
||||
.context("failed to get length of extism memory handle")
|
||||
)?;
|
||||
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "length") {
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut store, &[Val::I64(offs as i64)], output)?;
|
||||
} else {
|
||||
anyhow::bail!("unable to locate an extism kernel function: length",)
|
||||
}
|
||||
@@ -291,16 +270,12 @@ impl CurrentPlugin {
|
||||
}
|
||||
|
||||
pub fn memory_length_unsafe(&mut self, offs: u64) -> Result<u64, Error> {
|
||||
let (linker, store) = self.linker_and_store();
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
let output = &mut [Val::I64(0)];
|
||||
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "length_unsafe") {
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut *store, &[Val::I64(offs as i64)], output)
|
||||
.context("failed to get length of extism memory using length_unsafe")
|
||||
)?;
|
||||
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "length_unsafe") {
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut store, &[Val::I64(offs as i64)], output)?;
|
||||
} else {
|
||||
anyhow::bail!("unable to locate an extism kernel function: length_unsafe",)
|
||||
}
|
||||
@@ -333,7 +308,6 @@ impl CurrentPlugin {
|
||||
manifest: extism_manifest::Manifest,
|
||||
wasi: bool,
|
||||
available_pages: Option<u32>,
|
||||
allow_http_response_headers: bool,
|
||||
id: uuid::Uuid,
|
||||
) -> Result<Self, Error> {
|
||||
let wasi = if wasi {
|
||||
@@ -346,20 +320,9 @@ impl CurrentPlugin {
|
||||
|
||||
if let Some(a) = &manifest.allowed_paths {
|
||||
for (k, v) in a.iter() {
|
||||
let readonly = k.starts_with("ro:");
|
||||
|
||||
let dir_path = if readonly { &k[3..] } else { k };
|
||||
|
||||
let dir = wasi_common::sync::dir::Dir::from_cap_std(
|
||||
wasi_common::sync::Dir::open_ambient_dir(dir_path, auth)?,
|
||||
);
|
||||
|
||||
let file: Box<dyn wasi_common::dir::WasiDir> = if readonly {
|
||||
Box::new(readonly_dir::ReadOnlyDir::new(dir))
|
||||
} else {
|
||||
Box::new(dir)
|
||||
};
|
||||
|
||||
let file = Box::new(wasi_common::sync::dir::Dir::from_cap_std(
|
||||
wasi_common::sync::Dir::open_ambient_dir(k, auth)?,
|
||||
));
|
||||
ctx.push_preopened_dir(file, v)?;
|
||||
}
|
||||
}
|
||||
@@ -377,7 +340,7 @@ impl CurrentPlugin {
|
||||
|
||||
let memory_limiter = if let Some(pgs) = available_pages {
|
||||
let n = pgs as usize * 65536;
|
||||
Some(MemoryLimiter {
|
||||
Some(crate::current_plugin::MemoryLimiter {
|
||||
max_bytes: n,
|
||||
bytes_left: n,
|
||||
})
|
||||
@@ -396,11 +359,6 @@ impl CurrentPlugin {
|
||||
memory_limiter,
|
||||
id,
|
||||
start_time: std::time::Instant::now(),
|
||||
http_headers: if allow_http_response_headers {
|
||||
Some(BTreeMap::new())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -445,12 +403,12 @@ impl CurrentPlugin {
|
||||
/// Clear the current plugin error
|
||||
pub fn clear_error(&mut self) {
|
||||
trace!(plugin = self.id.to_string(), "CurrentPlugin::clear_error");
|
||||
let (linker, store) = self.linker_and_store();
|
||||
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "error_set") {
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "error_set") {
|
||||
let res = f
|
||||
.into_func()
|
||||
.unwrap()
|
||||
.call(&mut *store, &[Val::I64(0)], &mut []);
|
||||
.call(&mut store, &[Val::I64(0)], &mut []);
|
||||
if let Err(e) = res {
|
||||
error!(
|
||||
plugin = self.id.to_string(),
|
||||
@@ -481,15 +439,13 @@ impl CurrentPlugin {
|
||||
pub fn set_error(&mut self, s: impl AsRef<str>) -> Result<(u64, u64), Error> {
|
||||
let s = s.as_ref();
|
||||
debug!(plugin = self.id.to_string(), "set error: {:?}", s);
|
||||
let handle = self.memory_new(s)?;
|
||||
let (linker, store) = self.linker_and_store();
|
||||
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "error_set") {
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut *store, &[Val::I64(handle.offset() as i64)], &mut [])
|
||||
.context("failed to set extism error")
|
||||
let handle = self.current_plugin_mut().memory_new(s)?;
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "error_set") {
|
||||
f.into_func().unwrap().call(
|
||||
&mut store,
|
||||
&[Val::I64(handle.offset() as i64)],
|
||||
&mut [],
|
||||
)?;
|
||||
Ok((handle.offset(), s.len() as u64))
|
||||
} else {
|
||||
@@ -498,13 +454,10 @@ impl CurrentPlugin {
|
||||
}
|
||||
|
||||
pub(crate) fn get_error_position(&mut self) -> (u64, u64) {
|
||||
let (linker, store) = self.linker_and_store();
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
let output = &mut [Val::I64(0)];
|
||||
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "error_get") {
|
||||
if let Err(e) = catch_out_of_fuel!(
|
||||
&store,
|
||||
f.into_func().unwrap().call(&mut *store, &[], output)
|
||||
) {
|
||||
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "error_get") {
|
||||
if let Err(e) = f.into_func().unwrap().call(&mut store, &[], output) {
|
||||
error!(
|
||||
plugin = self.id.to_string(),
|
||||
"unable to call extism:host/env::error_get: {:?}", e
|
||||
|
||||
BIN
runtime/src/extism-runtime.wasm
Normal file → Executable file
BIN
runtime/src/extism-runtime.wasm
Normal file → Executable file
Binary file not shown.
@@ -279,7 +279,7 @@ impl Function {
|
||||
/// For example, the following defines a host function named `add_newline` that takes a
|
||||
/// string parameter and returns a string result:
|
||||
/// ```rust
|
||||
/// extism::host_fn!(add_newline(_user_data: (); a: String) -> String { Ok(a + "\n") });
|
||||
/// extism::host_fn!(add_newline(_user_data: (), a: String) -> String { Ok(a + "\n") });
|
||||
/// ```
|
||||
/// A few things worth noting:
|
||||
/// - The function always returns a `Result` that wraps the specified return type
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
// Makes proc-macros able to resolve `::extism` correctly
|
||||
extern crate self as extism;
|
||||
|
||||
macro_rules! catch_out_of_fuel {
|
||||
($store: expr, $x:expr) => {{
|
||||
let y = $x;
|
||||
if y.is_err() && $store.get_fuel().is_ok_and(|x| x == 0) {
|
||||
Err(Error::msg("plugin ran out of fuel"))
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use extism_convert::*;
|
||||
pub(crate) use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
@@ -29,7 +18,6 @@ pub(crate) mod manifest;
|
||||
pub(crate) mod pdk;
|
||||
mod plugin;
|
||||
mod plugin_builder;
|
||||
mod readonly_dir;
|
||||
mod timer;
|
||||
|
||||
/// Extism C API
|
||||
@@ -89,16 +77,11 @@ pub fn set_log_callback<F: 'static + Clone + Fn(&str)>(
|
||||
filter: impl AsRef<str>,
|
||||
) -> Result<(), Error> {
|
||||
let filter = filter.as_ref();
|
||||
let is_level = tracing::Level::from_str(filter).is_ok();
|
||||
let cfg = tracing_subscriber::FmtSubscriber::builder().with_env_filter({
|
||||
let x = tracing_subscriber::EnvFilter::builder()
|
||||
.with_default_directive(tracing::Level::ERROR.into());
|
||||
if is_level {
|
||||
x.parse_lossy(format!("extism={}", filter))
|
||||
} else {
|
||||
x.parse_lossy(filter)
|
||||
}
|
||||
});
|
||||
let cfg = tracing_subscriber::FmtSubscriber::builder().with_env_filter(
|
||||
tracing_subscriber::EnvFilter::builder()
|
||||
.with_default_directive(tracing::Level::ERROR.into())
|
||||
.parse_lossy(filter),
|
||||
);
|
||||
let w = LogFunction { func };
|
||||
cfg.with_ansi(false)
|
||||
.with_writer(move || w.clone())
|
||||
|
||||
@@ -20,8 +20,6 @@ macro_rules! args {
|
||||
/// Get a configuration value
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i64 (offset)
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
pub(crate) fn config_get(
|
||||
mut caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
@@ -40,7 +38,6 @@ pub(crate) fn config_get(
|
||||
};
|
||||
let val = data.manifest.config.get(key);
|
||||
let ptr = val.map(|x| (x.len(), x.as_ptr()));
|
||||
data.memory_free(handle)?;
|
||||
let mem = match ptr {
|
||||
Some((len, ptr)) => {
|
||||
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
|
||||
@@ -58,9 +55,6 @@ pub(crate) fn config_get(
|
||||
/// Get a variable
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i64 (offset)
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value, but the return value
|
||||
/// will need to be freed
|
||||
pub(crate) fn var_get(
|
||||
mut caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
@@ -79,8 +73,6 @@ pub(crate) fn var_get(
|
||||
};
|
||||
let val = data.vars.get(key);
|
||||
let ptr = val.map(|x| (x.len(), x.as_ptr()));
|
||||
data.memory_free(handle)?;
|
||||
|
||||
let mem = match ptr {
|
||||
Some((len, ptr)) => {
|
||||
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
|
||||
@@ -98,8 +90,6 @@ pub(crate) fn var_get(
|
||||
/// Set a variable, if the value offset is 0 then the provided key will be removed
|
||||
/// Params: i64 (key offset), i64 (value offset)
|
||||
/// Returns: none
|
||||
/// **Note**: this function takes ownership of the handles passed in
|
||||
/// the caller should not `free` these values
|
||||
pub(crate) fn var_set(
|
||||
mut caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
@@ -114,12 +104,12 @@ pub(crate) fn var_set(
|
||||
let voffset = args!(input, 1, i64) as u64;
|
||||
let key_offs = args!(input, 0, i64) as u64;
|
||||
|
||||
let key_handle = match data.memory_handle(key_offs) {
|
||||
Some(h) => h,
|
||||
None => anyhow::bail!("invalid handle offset for var key: {key_offs}"),
|
||||
};
|
||||
let key = {
|
||||
let key = data.memory_str(key_handle)?;
|
||||
let handle = match data.memory_handle(key_offs) {
|
||||
Some(h) => h,
|
||||
None => anyhow::bail!("invalid handle offset for var key: {key_offs}"),
|
||||
};
|
||||
let key = data.memory_str(handle)?;
|
||||
let key_len = key.len();
|
||||
let key_ptr = key.as_ptr();
|
||||
unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(key_ptr, key_len)) }
|
||||
@@ -128,7 +118,6 @@ pub(crate) fn var_set(
|
||||
// Remove if the value offset is 0
|
||||
if voffset == 0 {
|
||||
data.vars.remove(key);
|
||||
data.memory_free(key_handle)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -155,9 +144,6 @@ pub(crate) fn var_set(
|
||||
|
||||
let value = data.memory_bytes(handle)?.to_vec();
|
||||
|
||||
data.memory_free(handle)?;
|
||||
data.memory_free(key_handle)?;
|
||||
|
||||
// Insert the value from memory into the `vars` map
|
||||
data.vars.insert(key.to_string(), value);
|
||||
|
||||
@@ -167,9 +153,6 @@ pub(crate) fn var_set(
|
||||
/// Make an HTTP request
|
||||
/// Params: i64 (offset to JSON encoded HttpRequest), i64 (offset to body or 0)
|
||||
/// Returns: i64 (offset)
|
||||
/// **Note**: this function takes ownership of the handles passed in
|
||||
/// the caller should not `free` these values, the result will need to
|
||||
/// be freed.
|
||||
pub(crate) fn http_request(
|
||||
#[allow(unused_mut)] mut caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
@@ -183,7 +166,6 @@ pub(crate) fn http_request(
|
||||
Some(h) => h,
|
||||
None => anyhow::bail!("http_request input is invalid: {http_req_offset}"),
|
||||
};
|
||||
data.memory_free(handle)?;
|
||||
let req: extism_manifest::HttpRequest = serde_json::from_slice(data.memory_bytes(handle)?)?;
|
||||
output[0] = Val::I64(0);
|
||||
anyhow::bail!(
|
||||
@@ -194,16 +176,12 @@ pub(crate) fn http_request(
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
{
|
||||
data.http_headers.iter_mut().for_each(|x| x.clear());
|
||||
data.http_status = 0;
|
||||
|
||||
use std::io::Read;
|
||||
let handle = match data.memory_handle(http_req_offset) {
|
||||
Some(h) => h,
|
||||
None => anyhow::bail!("invalid handle offset for http request: {http_req_offset}"),
|
||||
};
|
||||
let req: extism_manifest::HttpRequest = serde_json::from_slice(data.memory_bytes(handle)?)?;
|
||||
data.memory_free(handle)?;
|
||||
|
||||
let body_offset = args!(input, 1, i64) as u64;
|
||||
|
||||
@@ -257,19 +235,8 @@ pub(crate) fn http_request(
|
||||
r.call()
|
||||
};
|
||||
|
||||
if let Some(handle) = data.memory_handle(body_offset) {
|
||||
data.memory_free(handle)?;
|
||||
}
|
||||
|
||||
let reader = match res {
|
||||
Ok(res) => {
|
||||
if let Some(headers) = &mut data.http_headers {
|
||||
for name in res.headers_names() {
|
||||
if let Some(h) = res.header(&name) {
|
||||
headers.insert(name, h.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
data.http_status = res.status();
|
||||
Some(res.into_reader())
|
||||
}
|
||||
@@ -327,24 +294,6 @@ pub(crate) fn http_status_code(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the HTTP response headers from the last HTTP request
|
||||
/// Params: none
|
||||
/// Returns: i64 (offset)
|
||||
pub(crate) fn http_headers(
|
||||
mut caller: Caller<CurrentPlugin>,
|
||||
_input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Error> {
|
||||
let data: &mut CurrentPlugin = caller.data_mut();
|
||||
if let Some(h) = &data.http_headers {
|
||||
let headers = serde_json::to_string(h)?;
|
||||
data.memory_set_val(&mut output[0], headers)?;
|
||||
} else {
|
||||
output[0] = Val::I64(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log(
|
||||
level: tracing::Level,
|
||||
mut caller: Caller<CurrentPlugin>,
|
||||
@@ -352,22 +301,13 @@ pub fn log(
|
||||
_output: &mut [Val],
|
||||
) -> Result<(), Error> {
|
||||
let data: &mut CurrentPlugin = caller.data_mut();
|
||||
|
||||
let offset = args!(input, 0, i64) as u64;
|
||||
|
||||
// Check if the current log level should be logged
|
||||
let global_log_level = tracing::level_filters::LevelFilter::current();
|
||||
if global_log_level == tracing::level_filters::LevelFilter::OFF || level > global_log_level {
|
||||
if let Some(handle) = data.memory_handle(offset) {
|
||||
data.memory_free(handle)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let handle = match data.memory_handle(offset) {
|
||||
Some(h) => h,
|
||||
None => anyhow::bail!("invalid handle offset for log message: {offset}"),
|
||||
};
|
||||
|
||||
let id = data.id.to_string();
|
||||
let buf = data.memory_str(handle);
|
||||
|
||||
@@ -391,16 +331,12 @@ pub fn log(
|
||||
},
|
||||
Err(_) => tracing::error!(plugin = id, "unable to log message: {:?}", buf),
|
||||
}
|
||||
|
||||
data.memory_free(handle)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write to logs (warning)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
pub(crate) fn log_warn(
|
||||
caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
@@ -412,8 +348,6 @@ pub(crate) fn log_warn(
|
||||
/// Write to logs (info)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
pub(crate) fn log_info(
|
||||
caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
@@ -425,8 +359,6 @@ pub(crate) fn log_info(
|
||||
/// Write to logs (debug)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
pub(crate) fn log_debug(
|
||||
caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
@@ -438,8 +370,6 @@ pub(crate) fn log_debug(
|
||||
/// Write to logs (error)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
pub(crate) fn log_error(
|
||||
caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
@@ -447,46 +377,3 @@ pub(crate) fn log_error(
|
||||
) -> Result<(), Error> {
|
||||
log(tracing::Level::ERROR, caller, input, _output)
|
||||
}
|
||||
|
||||
/// Write to logs (trace)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
/// **Note**: this function takes ownership of the handle passed in
|
||||
/// the caller should not `free` this value
|
||||
pub(crate) fn log_trace(
|
||||
caller: Caller<CurrentPlugin>,
|
||||
input: &[Val],
|
||||
_output: &mut [Val],
|
||||
) -> Result<(), Error> {
|
||||
log(tracing::Level::TRACE, caller, input, _output)
|
||||
}
|
||||
|
||||
/// Get the log level
|
||||
/// Params: none
|
||||
/// Returns: i32 (log level)
|
||||
pub(crate) fn get_log_level(
|
||||
mut _caller: Caller<CurrentPlugin>,
|
||||
_input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Error> {
|
||||
let level = tracing::level_filters::LevelFilter::current();
|
||||
if level == tracing::level_filters::LevelFilter::OFF {
|
||||
output[0] = Val::I32(i32::MAX)
|
||||
} else {
|
||||
output[0] = Val::I32(log_level_to_int(
|
||||
level.into_level().unwrap_or(tracing::Level::ERROR),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert log level to integer
|
||||
pub(crate) const fn log_level_to_int(level: tracing::Level) -> i32 {
|
||||
match level {
|
||||
tracing::Level::TRACE => 0,
|
||||
tracing::Level::DEBUG => 1,
|
||||
tracing::Level::INFO => 2,
|
||||
tracing::Level::WARN => 3,
|
||||
tracing::Level::ERROR => 4,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub const EXTISM_ENV_MODULE: &str = "extism:host/env";
|
||||
@@ -82,12 +81,6 @@ pub struct Plugin {
|
||||
pub(crate) store_needs_reset: bool,
|
||||
|
||||
pub(crate) debug_options: DebugOptions,
|
||||
|
||||
pub(crate) error_msg: Option<Vec<u8>>,
|
||||
|
||||
pub(crate) fuel: Option<u64>,
|
||||
|
||||
pub(crate) host_context: Rooted<ExternRef>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Plugin {}
|
||||
@@ -219,24 +212,14 @@ fn add_module<T: 'static>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn relink(
|
||||
engine: &Engine,
|
||||
mut store: &mut Store<CurrentPlugin>,
|
||||
imports: &[Function],
|
||||
modules: &BTreeMap<String, Module>,
|
||||
with_wasi: bool,
|
||||
) -> Result<
|
||||
(
|
||||
InstancePre<CurrentPlugin>,
|
||||
Linker<CurrentPlugin>,
|
||||
Rooted<ExternRef>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
) -> Result<(InstancePre<CurrentPlugin>, Linker<CurrentPlugin>), Error> {
|
||||
let mut linker = Linker::new(engine);
|
||||
linker.allow_shadowing(true);
|
||||
|
||||
// Define PDK functions
|
||||
macro_rules! add_funcs {
|
||||
($($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?) => {
|
||||
@@ -255,13 +238,10 @@ fn relink(
|
||||
var_set(I64, I64);
|
||||
http_request(I64, I64) -> I64;
|
||||
http_status_code() -> I32;
|
||||
http_headers() -> I64;
|
||||
log_warn(I64);
|
||||
log_info(I64);
|
||||
log_debug(I64);
|
||||
log_error(I64);
|
||||
log_trace(I64);
|
||||
get_log_level() -> I32;
|
||||
);
|
||||
|
||||
let mut linked = BTreeSet::new();
|
||||
@@ -294,12 +274,9 @@ fn relink(
|
||||
)?;
|
||||
}
|
||||
|
||||
let inner: Box<dyn std::any::Any + Send + Sync> = Box::new(());
|
||||
let host_context = ExternRef::new(store, inner)?;
|
||||
|
||||
let main = &modules[MAIN_KEY];
|
||||
let instance_pre = linker.instantiate_pre(main)?;
|
||||
Ok((instance_pre, linker, host_context))
|
||||
Ok((instance_pre, linker))
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
@@ -310,31 +287,28 @@ impl Plugin {
|
||||
imports: impl IntoIterator<Item = Function>,
|
||||
with_wasi: bool,
|
||||
) -> Result<Plugin, Error> {
|
||||
Self::build_new(
|
||||
PluginBuilder::new(wasm)
|
||||
.with_functions(imports)
|
||||
.with_wasi(with_wasi),
|
||||
)
|
||||
Self::build_new(wasm.into(), imports, with_wasi, Default::default(), None)
|
||||
}
|
||||
|
||||
pub(crate) fn build_new(builder: PluginBuilder) -> Result<Plugin, Error> {
|
||||
pub(crate) fn build_new(
|
||||
wasm: WasmInput<'_>,
|
||||
imports: impl IntoIterator<Item = Function>,
|
||||
with_wasi: bool,
|
||||
debug_options: DebugOptions,
|
||||
cache_dir: Option<Option<PathBuf>>,
|
||||
) -> Result<Plugin, Error> {
|
||||
// Setup wasmtime types
|
||||
let mut config = builder.config.unwrap_or_default();
|
||||
let mut config = Config::new();
|
||||
config
|
||||
.async_support(false)
|
||||
.epoch_interruption(true)
|
||||
.debug_info(builder.debug_options.debug_info)
|
||||
.coredump_on_trap(builder.debug_options.coredump.is_some())
|
||||
.profiler(builder.debug_options.profiling_strategy)
|
||||
.debug_info(debug_options.debug_info)
|
||||
.coredump_on_trap(debug_options.coredump.is_some())
|
||||
.profiler(debug_options.profiling_strategy)
|
||||
.wasm_tail_call(true)
|
||||
.wasm_function_references(true)
|
||||
.wasm_gc(true);
|
||||
|
||||
if builder.fuel.is_some() {
|
||||
config.consume_fuel(true);
|
||||
}
|
||||
|
||||
match builder.cache_config {
|
||||
match cache_dir {
|
||||
Some(None) => (),
|
||||
Some(Some(path)) => {
|
||||
config.cache_config_load(path)?;
|
||||
@@ -351,7 +325,7 @@ impl Plugin {
|
||||
}
|
||||
|
||||
let engine = Engine::new(&config)?;
|
||||
let (manifest, modules) = manifest::load(&engine, builder.source)?;
|
||||
let (manifest, modules) = manifest::load(&engine, wasm)?;
|
||||
if modules.len() <= 1 {
|
||||
anyhow::bail!("No wasm modules provided");
|
||||
} else if !modules.contains_key(MAIN_KEY) {
|
||||
@@ -364,22 +338,13 @@ impl Plugin {
|
||||
let id = uuid::Uuid::new_v4();
|
||||
let mut store = Store::new(
|
||||
&engine,
|
||||
CurrentPlugin::new(
|
||||
manifest,
|
||||
builder.wasi,
|
||||
available_pages,
|
||||
builder.http_response_headers,
|
||||
id,
|
||||
)?,
|
||||
CurrentPlugin::new(manifest, with_wasi, available_pages, id)?,
|
||||
);
|
||||
store.set_epoch_deadline(1);
|
||||
if let Some(fuel) = builder.fuel {
|
||||
store.set_fuel(fuel)?;
|
||||
}
|
||||
|
||||
let imports: Vec<Function> = builder.functions.into_iter().collect();
|
||||
let (instance_pre, linker, host_context) =
|
||||
relink(&engine, &mut store, &imports, &modules, builder.wasi)?;
|
||||
let imports: Vec<Function> = imports.into_iter().collect();
|
||||
let (instance_pre, linker) = relink(&engine, &mut store, &imports, &modules, with_wasi)?;
|
||||
|
||||
let timer_tx = Timer::tx();
|
||||
let mut plugin = Plugin {
|
||||
modules,
|
||||
@@ -394,11 +359,8 @@ impl Plugin {
|
||||
instantiations: 0,
|
||||
output: Output::default(),
|
||||
store_needs_reset: false,
|
||||
debug_options: builder.debug_options,
|
||||
debug_options,
|
||||
_functions: imports,
|
||||
error_msg: None,
|
||||
fuel: builder.fuel,
|
||||
host_context,
|
||||
};
|
||||
|
||||
plugin.current_plugin_mut().store = &mut plugin.store;
|
||||
@@ -427,17 +389,12 @@ impl Plugin {
|
||||
internal.manifest.clone(),
|
||||
internal.wasi.is_some(),
|
||||
internal.available_pages,
|
||||
internal.http_headers.is_some(),
|
||||
self.id,
|
||||
)?,
|
||||
);
|
||||
self.store.set_epoch_deadline(1);
|
||||
|
||||
if let Some(fuel) = self.fuel {
|
||||
self.store.set_fuel(fuel)?;
|
||||
}
|
||||
|
||||
let (instance_pre, linker, host_context) = relink(
|
||||
let (instance_pre, linker) = relink(
|
||||
&engine,
|
||||
&mut self.store,
|
||||
&self._functions,
|
||||
@@ -446,7 +403,6 @@ impl Plugin {
|
||||
)?;
|
||||
self.linker = linker;
|
||||
self.instance_pre = instance_pre;
|
||||
self.host_context = host_context;
|
||||
let store = &mut self.store as *mut _;
|
||||
let linker = &mut self.linker as *mut _;
|
||||
let current_plugin = self.current_plugin_mut();
|
||||
@@ -554,16 +510,10 @@ impl Plugin {
|
||||
.linker
|
||||
.get(&mut self.store, EXTISM_ENV_MODULE, "input_set")
|
||||
{
|
||||
catch_out_of_fuel!(
|
||||
&self.store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(
|
||||
&mut self.store,
|
||||
&[Val::I64(handle.offset() as i64), Val::I64(len as i64)],
|
||||
&mut [],
|
||||
)
|
||||
.context("unable to set extism input")
|
||||
f.into_func().unwrap().call(
|
||||
&mut self.store,
|
||||
&[Val::I64(handle.offset() as i64), Val::I64(len as i64)],
|
||||
&mut [],
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -571,8 +521,7 @@ impl Plugin {
|
||||
self.linker
|
||||
.get(&mut self.store, EXTISM_ENV_MODULE, "extism_context")
|
||||
{
|
||||
ctxt.set(&mut self.store, Val::ExternRef(host_context))
|
||||
.context("unable to set extism host context")?;
|
||||
ctxt.set(&mut self.store, Val::ExternRef(host_context))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -583,13 +532,7 @@ impl Plugin {
|
||||
let id = self.id.to_string();
|
||||
|
||||
if let Some(f) = self.linker.get(&mut self.store, EXTISM_ENV_MODULE, "reset") {
|
||||
catch_out_of_fuel!(
|
||||
&self.store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut self.store, &[], &mut [])
|
||||
.context("extism reset failed")
|
||||
)?;
|
||||
f.into_func().unwrap().call(&mut self.store, &[], &mut [])?;
|
||||
} else {
|
||||
error!(plugin = &id, "call to extism:host/env::reset failed");
|
||||
}
|
||||
@@ -665,28 +608,19 @@ impl Plugin {
|
||||
|
||||
// Initialize the guest runtime
|
||||
pub(crate) fn initialize_guest_runtime(&mut self) -> Result<(), Error> {
|
||||
let store = &mut self.store;
|
||||
let mut store = &mut self.store;
|
||||
if let Some(runtime) = &self.runtime {
|
||||
trace!(plugin = self.id.to_string(), "Plugin::initialize_runtime");
|
||||
match runtime {
|
||||
GuestRuntime::Haskell { init, reactor_init } => {
|
||||
if let Some(reactor_init) = reactor_init {
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
reactor_init
|
||||
.call(&mut *store, &[], &mut [])
|
||||
.context("failed to initialize Haskell reactor runtime")
|
||||
)?;
|
||||
reactor_init.call(&mut store, &[], &mut [])?;
|
||||
}
|
||||
let mut results = vec![Val::I32(0); init.ty(&*store).results().len()];
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
init.call(
|
||||
&mut *store,
|
||||
&[Val::I32(0), Val::I32(0)],
|
||||
results.as_mut_slice(),
|
||||
)
|
||||
.context("failed to initialize Haskell using hs_init")
|
||||
let mut results = vec![Val::I32(0); init.ty(&store).results().len()];
|
||||
init.call(
|
||||
&mut store,
|
||||
&[Val::I32(0), Val::I32(0)],
|
||||
results.as_mut_slice(),
|
||||
)?;
|
||||
debug!(
|
||||
plugin = self.id.to_string(),
|
||||
@@ -694,11 +628,7 @@ impl Plugin {
|
||||
);
|
||||
}
|
||||
GuestRuntime::Wasi { init } => {
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
init.call(&mut *store, &[], &mut [])
|
||||
.context("failed to initialize wasi runtime")
|
||||
)?;
|
||||
init.call(&mut store, &[], &mut [])?;
|
||||
debug!(plugin = self.id.to_string(), "initialied WASI runtime");
|
||||
}
|
||||
}
|
||||
@@ -711,32 +641,20 @@ impl Plugin {
|
||||
fn output_memory_position(&mut self) -> Result<(u64, u64), Error> {
|
||||
let out = &mut [Val::I64(0)];
|
||||
let out_len = &mut [Val::I64(0)];
|
||||
let store = &mut self.store;
|
||||
let mut store = &mut self.store;
|
||||
if let Some(f) = self
|
||||
.linker
|
||||
.get(&mut *store, EXTISM_ENV_MODULE, "output_offset")
|
||||
.get(&mut store, EXTISM_ENV_MODULE, "output_offset")
|
||||
{
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut *store, &[], out)
|
||||
.context("call to set extism output offset failed")
|
||||
)?;
|
||||
f.into_func().unwrap().call(&mut store, &[], out)?;
|
||||
} else {
|
||||
anyhow::bail!("unable to set output")
|
||||
}
|
||||
if let Some(f) = self
|
||||
.linker
|
||||
.get(&mut *store, EXTISM_ENV_MODULE, "output_length")
|
||||
.get(&mut store, EXTISM_ENV_MODULE, "output_length")
|
||||
{
|
||||
catch_out_of_fuel!(
|
||||
&store,
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut *store, &[], out_len)
|
||||
.context("call to set extism output length failed")
|
||||
)?;
|
||||
f.into_func().unwrap().call(&mut store, &[], out_len)?;
|
||||
} else {
|
||||
anyhow::bail!("unable to set output length")
|
||||
}
|
||||
@@ -750,10 +668,10 @@ impl Plugin {
|
||||
fn output<'a, T: FromBytes<'a>>(&'a mut self) -> Result<T, Error> {
|
||||
let offs = self.output.offset;
|
||||
let len = self.output.length;
|
||||
let x = self
|
||||
.current_plugin_mut()
|
||||
.memory_bytes(unsafe { MemoryHandle::new(offs, len) })?;
|
||||
T::from_bytes(x)
|
||||
T::from_bytes(
|
||||
self.current_plugin_mut()
|
||||
.memory_bytes(unsafe { MemoryHandle::new(offs, len) })?,
|
||||
)
|
||||
}
|
||||
|
||||
// Cache output memory and error information after call is complete
|
||||
@@ -778,40 +696,26 @@ impl Plugin {
|
||||
|
||||
// Implements the build of the `call` function, `raw_call` is also used in the SDK
|
||||
// code
|
||||
pub(crate) fn raw_call<T: 'static + Send + Sync>(
|
||||
pub(crate) fn raw_call(
|
||||
&mut self,
|
||||
lock: &mut std::sync::MutexGuard<Option<Instance>>,
|
||||
name: impl AsRef<str>,
|
||||
input: impl AsRef<[u8]>,
|
||||
host_context: Option<T>,
|
||||
host_context: Option<Rooted<ExternRef>>,
|
||||
) -> Result<i32, (Error, i32)> {
|
||||
let name = name.as_ref();
|
||||
let input = input.as_ref();
|
||||
|
||||
if let Some(fuel) = self.fuel {
|
||||
self.store.set_fuel(fuel).map_err(|x| (x, -1))?;
|
||||
if let Err(e) = self.reset_store(lock) {
|
||||
error!(
|
||||
plugin = self.id.to_string(),
|
||||
"call to Plugin::reset_store failed: {e:?}"
|
||||
);
|
||||
}
|
||||
|
||||
catch_out_of_fuel!(&self.store, self.reset_store(lock)).map_err(|x| (x, -1))?;
|
||||
|
||||
self.instantiate(lock).map_err(|e| (e, -1))?;
|
||||
|
||||
// Set host context
|
||||
let r = if let Some(host_context) = host_context {
|
||||
let inner = self
|
||||
.host_context
|
||||
.data_mut(&mut self.store)
|
||||
.map_err(|x| (x, -1))?;
|
||||
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
|
||||
let x: Box<T> = Box::new(host_context);
|
||||
*inner = x;
|
||||
}
|
||||
Some(self.host_context)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.set_input(input.as_ptr(), input.len(), r)
|
||||
self.set_input(input.as_ptr(), input.len(), host_context)
|
||||
.map_err(|x| (x, -1))?;
|
||||
|
||||
let func = match self.get_func(lock, name) {
|
||||
@@ -848,71 +752,40 @@ impl Plugin {
|
||||
let mut results = vec![wasmtime::Val::I32(0); n_results];
|
||||
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
|
||||
|
||||
// Reset host context
|
||||
if let Ok(inner) = self.host_context.data_mut(&mut self.store) {
|
||||
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
|
||||
let x: Box<dyn Any + Send + Sync> = Box::new(());
|
||||
*inner = x;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop timer
|
||||
self.store
|
||||
.epoch_deadline_callback(|_| Ok(UpdateDeadline::Continue(1)));
|
||||
let _ = self.timer_tx.send(TimerAction::Stop { id: self.id });
|
||||
self.store_needs_reset = name == "_start";
|
||||
|
||||
let mut rc = -1;
|
||||
if self.store.get_fuel().is_ok_and(|x| x == 0) {
|
||||
res = Err(Error::msg("plugin ran out of fuel"));
|
||||
} else {
|
||||
// Get extism error
|
||||
let output_res = self.get_output_after_call().map_err(|x| (x, -1));
|
||||
// Get extism error
|
||||
self.get_output_after_call().map_err(|x| (x, -1))?;
|
||||
let mut rc = 0;
|
||||
if !results.is_empty() {
|
||||
rc = results[0].i32().unwrap_or(-1);
|
||||
debug!(plugin = self.id.to_string(), "got return code: {}", rc);
|
||||
}
|
||||
|
||||
// Get the return code
|
||||
if output_res.is_ok() && res.is_ok() {
|
||||
rc = 0;
|
||||
if !results.is_empty() {
|
||||
rc = results[0].i32().unwrap_or(-1);
|
||||
debug!(plugin = self.id.to_string(), "got return code: {}", rc);
|
||||
if self.output.error_offset != 0 && self.output.error_length != 0 {
|
||||
let handle = MemoryHandle {
|
||||
offset: self.output.error_offset,
|
||||
length: self.output.error_length,
|
||||
};
|
||||
if let Ok(e) = self.current_plugin_mut().memory_str(handle) {
|
||||
let x = e.to_string();
|
||||
error!(
|
||||
plugin = self.id.to_string(),
|
||||
"call to {name} returned with error message: {}", x
|
||||
);
|
||||
if let Err(e) = res {
|
||||
res = Err(Error::msg(x).context(e));
|
||||
} else {
|
||||
res = Err(Error::msg(x))
|
||||
}
|
||||
}
|
||||
|
||||
// on extism error
|
||||
if output_res.is_ok() && self.output.error_offset != 0 && self.output.error_length != 0
|
||||
{
|
||||
let handle = MemoryHandle {
|
||||
offset: self.output.error_offset,
|
||||
length: self.output.error_length,
|
||||
};
|
||||
match self.current_plugin_mut().memory_str(handle) {
|
||||
Ok(e) => {
|
||||
let x = e.to_string();
|
||||
error!(
|
||||
plugin = self.id.to_string(),
|
||||
"call to {name} returned with error message: {}", x
|
||||
);
|
||||
if let Err(e) = res {
|
||||
res = Err(Error::msg(x).context(e));
|
||||
} else {
|
||||
res = Err(Error::msg(x))
|
||||
}
|
||||
}
|
||||
Err(msg) => {
|
||||
res = Err(Error::msg(format!(
|
||||
"Call to Extism plugin function {name} encountered an error: {}",
|
||||
msg,
|
||||
)));
|
||||
}
|
||||
}
|
||||
// on wasmtime error
|
||||
} else if let Err(e) = &res {
|
||||
if e.is::<wasmtime::Trap>() {
|
||||
rc = 134; // EXIT_SIGNALED_SIGABRT
|
||||
}
|
||||
// if there was an error retrieving the output
|
||||
} else {
|
||||
output_res?;
|
||||
res = Err(Error::msg(format!(
|
||||
"Call to Extism plugin function {name} encountered an error"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,7 +897,7 @@ impl Plugin {
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let data = input.to_bytes()?;
|
||||
self.raw_call(&mut lock, name, data, None::<()>)
|
||||
self.raw_call(&mut lock, name, data, None)
|
||||
.map_err(|e| e.0)
|
||||
.and_then(move |rc| {
|
||||
if rc != 0 {
|
||||
@@ -1049,7 +922,8 @@ impl Plugin {
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let data = input.to_bytes()?;
|
||||
self.raw_call(&mut lock, name, data, Some(host_context))
|
||||
let ctx = ExternRef::new(&mut self.store, host_context)?;
|
||||
self.raw_call(&mut lock, name, data, Some(ctx))
|
||||
.map_err(|e| e.0)
|
||||
.and_then(move |_| self.output())
|
||||
}
|
||||
@@ -1068,7 +942,7 @@ impl Plugin {
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let data = input.to_bytes().map_err(|e| (e, -1))?;
|
||||
self.raw_call(&mut lock, name, data, None::<()>)
|
||||
self.raw_call(&mut lock, name, data, None)
|
||||
.and_then(move |_| self.output().map_err(|e| (e, -1)))
|
||||
}
|
||||
|
||||
@@ -1079,21 +953,42 @@ impl Plugin {
|
||||
|
||||
pub(crate) fn clear_error(&mut self) -> Result<(), Error> {
|
||||
trace!(plugin = self.id.to_string(), "clearing error");
|
||||
self.error_msg = None;
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "error_set") {
|
||||
let x = f
|
||||
.into_func()
|
||||
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "error_set") {
|
||||
f.into_func()
|
||||
.unwrap()
|
||||
.call(&mut store, &[Val::I64(0)], &mut [])
|
||||
.context("unable to clear error message");
|
||||
catch_out_of_fuel!(&store, x)?;
|
||||
.call(&mut store, &[Val::I64(0)], &mut [])?;
|
||||
Ok(())
|
||||
} else {
|
||||
anyhow::bail!("Plugin::clear_error failed, extism:host/env::error_set not found")
|
||||
}
|
||||
}
|
||||
|
||||
// A convenience method to set the plugin error and return a value
|
||||
pub(crate) fn return_error<E>(
|
||||
&mut self,
|
||||
instance_lock: &mut std::sync::MutexGuard<Option<Instance>>,
|
||||
e: impl std::fmt::Display,
|
||||
x: E,
|
||||
) -> E {
|
||||
if instance_lock.is_none() {
|
||||
error!(
|
||||
plugin = self.id.to_string(),
|
||||
"no instance, unable to set error: {}", e
|
||||
);
|
||||
return x;
|
||||
}
|
||||
match self.current_plugin_mut().set_error(e.to_string()) {
|
||||
Ok((a, b)) => {
|
||||
self.output.error_offset = a;
|
||||
self.output.error_length = b;
|
||||
}
|
||||
Err(e) => {
|
||||
error!(plugin = self.id.to_string(), "unable to set error: {e:?}")
|
||||
}
|
||||
}
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerates the PDK languages that need some additional initialization
|
||||
|
||||
@@ -34,14 +34,11 @@ impl Default for DebugOptions {
|
||||
|
||||
/// PluginBuilder is used to configure and create `Plugin` instances
|
||||
pub struct PluginBuilder<'a> {
|
||||
pub(crate) source: WasmInput<'a>,
|
||||
pub(crate) wasi: bool,
|
||||
pub(crate) functions: Vec<Function>,
|
||||
pub(crate) debug_options: DebugOptions,
|
||||
pub(crate) cache_config: Option<Option<PathBuf>>,
|
||||
pub(crate) fuel: Option<u64>,
|
||||
pub(crate) config: Option<wasmtime::Config>,
|
||||
pub(crate) http_response_headers: bool,
|
||||
source: WasmInput<'a>,
|
||||
wasi: bool,
|
||||
functions: Vec<Function>,
|
||||
debug_options: DebugOptions,
|
||||
cache_config: Option<Option<PathBuf>>,
|
||||
}
|
||||
|
||||
impl<'a> PluginBuilder<'a> {
|
||||
@@ -53,9 +50,6 @@ impl<'a> PluginBuilder<'a> {
|
||||
functions: vec![],
|
||||
debug_options: DebugOptions::default(),
|
||||
cache_config: None,
|
||||
fuel: None,
|
||||
config: None,
|
||||
http_response_headers: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,38 +148,14 @@ impl<'a> PluginBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Limit the number of instructions that can be executed
|
||||
pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
|
||||
self.fuel = Some(fuel);
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure an initial wasmtime config to be passed to the plugin
|
||||
///
|
||||
/// **Warning**: some values might be overwritten by the Extism runtime. In particular:
|
||||
/// - async_support
|
||||
/// - epoch_interruption
|
||||
/// - debug_info
|
||||
/// - coredump_on_trap
|
||||
/// - profiler
|
||||
/// - wasm_tail_call
|
||||
/// - wasm_function_references
|
||||
/// - wasm_gc
|
||||
///
|
||||
/// See the implementation details of [PluginBuilder::build] and [Plugin::build_new] to verify which values are overwritten.
|
||||
pub fn with_wasmtime_config(mut self, config: wasmtime::Config) -> Self {
|
||||
self.config = Some(config);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables `http_response_headers`, which allows for plugins to access response headers when using `extism:host/env::http_request`
|
||||
pub fn with_http_response_headers(mut self, allow: bool) -> Self {
|
||||
self.http_response_headers = allow;
|
||||
self
|
||||
}
|
||||
|
||||
/// Generate a new plugin with the configured settings
|
||||
pub fn build(self) -> Result<Plugin, Error> {
|
||||
Plugin::build_new(self)
|
||||
Plugin::build_new(
|
||||
self.source,
|
||||
self.functions,
|
||||
self.wasi,
|
||||
self.debug_options,
|
||||
self.cache_config,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
use crate::*;
|
||||
|
||||
use wasi_common::{Error, ErrorExt};
|
||||
|
||||
pub struct ReadOnlyDir<D: wasi_common::WasiDir> {
|
||||
inner: std::sync::Arc<D>,
|
||||
}
|
||||
|
||||
impl<D: wasi_common::WasiDir> ReadOnlyDir<D> {
|
||||
pub fn new(inner: D) -> Self {
|
||||
ReadOnlyDir {
|
||||
inner: std::sync::Arc::new(inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wiggle::async_trait]
|
||||
impl<D: wasi_common::WasiDir> wasi_common::WasiDir for ReadOnlyDir<D> {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self.inner.as_any()
|
||||
}
|
||||
|
||||
async fn open_file(
|
||||
&self,
|
||||
symlink_follow: bool,
|
||||
path: &str,
|
||||
oflags: wasi_common::file::OFlags,
|
||||
read: bool,
|
||||
write: bool,
|
||||
fdflags: wasi_common::file::FdFlags,
|
||||
) -> Result<wasi_common::dir::OpenResult, Error> {
|
||||
if write {
|
||||
return Err(Error::not_supported());
|
||||
}
|
||||
self.inner
|
||||
.open_file(symlink_follow, path, oflags, read, false, fdflags)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn create_dir(&self, _path: &str) -> Result<(), Error> {
|
||||
Err(Error::not_supported())
|
||||
}
|
||||
|
||||
async fn readdir(
|
||||
&self,
|
||||
cursor: wasi_common::dir::ReaddirCursor,
|
||||
) -> Result<
|
||||
Box<dyn Iterator<Item = Result<wasi_common::dir::ReaddirEntity, Error>> + Send>,
|
||||
Error,
|
||||
> {
|
||||
self.inner.readdir(cursor).await
|
||||
}
|
||||
|
||||
async fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<(), Error> {
|
||||
Err(Error::not_supported())
|
||||
}
|
||||
|
||||
async fn remove_dir(&self, _path: &str) -> Result<(), Error> {
|
||||
Err(Error::not_supported())
|
||||
}
|
||||
|
||||
async fn unlink_file(&self, _path: &str) -> Result<(), Error> {
|
||||
Err(Error::not_supported())
|
||||
}
|
||||
|
||||
async fn read_link(&self, path: &str) -> Result<std::path::PathBuf, Error> {
|
||||
self.inner.read_link(path).await
|
||||
}
|
||||
|
||||
async fn get_filestat(&self) -> Result<wasi_common::file::Filestat, Error> {
|
||||
self.inner.get_filestat().await
|
||||
}
|
||||
|
||||
async fn get_path_filestat(
|
||||
&self,
|
||||
path: &str,
|
||||
follow_symlinks: bool,
|
||||
) -> Result<wasi_common::file::Filestat, Error> {
|
||||
self.inner.get_path_filestat(path, follow_symlinks).await
|
||||
}
|
||||
|
||||
async fn rename(
|
||||
&self,
|
||||
_path: &str,
|
||||
_dest_dir: &dyn wasi_common::WasiDir,
|
||||
_dest_path: &str,
|
||||
) -> Result<(), Error> {
|
||||
Err(wasi_common::Error::not_supported())
|
||||
}
|
||||
|
||||
async fn hard_link(
|
||||
&self,
|
||||
_path: &str,
|
||||
_target_dir: &dyn wasi_common::WasiDir,
|
||||
_target_path: &str,
|
||||
) -> Result<(), Error> {
|
||||
Err(wasi_common::Error::not_supported())
|
||||
}
|
||||
|
||||
async fn set_times(
|
||||
&self,
|
||||
_path: &str,
|
||||
_atime: std::option::Option<wasi_common::SystemTimeSpec>,
|
||||
_mtime: std::option::Option<wasi_common::SystemTimeSpec>,
|
||||
_follow_symlinks: bool,
|
||||
) -> Result<(), Error> {
|
||||
Err(wasi_common::Error::not_supported())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
use std::{os::raw::c_char, ptr::null_mut};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use crate::*;
|
||||
|
||||
@@ -11,12 +11,6 @@ pub struct ExtismFunction(std::cell::Cell<Option<Function>>);
|
||||
/// The return code used to specify a successful plugin call
|
||||
pub static EXTISM_SUCCESS: i32 = 0;
|
||||
|
||||
fn make_error_msg(s: String) -> Vec<u8> {
|
||||
let mut s = s.into_bytes();
|
||||
s.push(0);
|
||||
s
|
||||
}
|
||||
|
||||
/// A union type for host function argument/return values
|
||||
#[repr(C)]
|
||||
pub union ValUnion {
|
||||
@@ -102,7 +96,7 @@ pub unsafe extern "C" fn extism_current_plugin_host_context(
|
||||
|
||||
let plugin = &mut *plugin;
|
||||
if let Ok(CVoidContainer(ptr)) = plugin.host_context::<CVoidContainer>() {
|
||||
*ptr
|
||||
ptr
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
@@ -237,28 +231,12 @@ pub unsafe extern "C" fn extism_function_new(
|
||||
})
|
||||
.collect();
|
||||
|
||||
// We cannot simply "get" the Vec's storage pointer because
|
||||
// the underlying storage might be invalid when the Vec is empty.
|
||||
// In that case, we return (null, 0).
|
||||
|
||||
let (inputs_ptr, inputs_len) = if inputs.is_empty() {
|
||||
(core::ptr::null(), 0 as Size)
|
||||
} else {
|
||||
(inputs.as_ptr(), inputs.len() as Size)
|
||||
};
|
||||
|
||||
let (output_ptr, output_len) = if output_tmp.is_empty() {
|
||||
(null_mut(), 0 as Size)
|
||||
} else {
|
||||
(output_tmp.as_mut_ptr(), output_tmp.len() as Size)
|
||||
};
|
||||
|
||||
func(
|
||||
plugin,
|
||||
inputs_ptr,
|
||||
inputs_len,
|
||||
output_ptr,
|
||||
output_len,
|
||||
inputs.as_ptr(),
|
||||
inputs.len() as Size,
|
||||
output_tmp.as_mut_ptr(),
|
||||
output_tmp.len() as Size,
|
||||
user_data.as_ptr(),
|
||||
);
|
||||
|
||||
@@ -266,8 +244,8 @@ pub unsafe extern "C" fn extism_function_new(
|
||||
match tmp.t {
|
||||
ValType::I32 => *out = Val::I32(tmp.v.i32),
|
||||
ValType::I64 => *out = Val::I64(tmp.v.i64),
|
||||
ValType::F32 => *out = Val::F32(tmp.v.f32.to_bits()),
|
||||
ValType::F64 => *out = Val::F64(tmp.v.f64.to_bits()),
|
||||
ValType::F32 => *out = Val::F32(tmp.v.f32 as u32),
|
||||
ValType::F64 => *out = Val::F64(tmp.v.f64 as u64),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
@@ -356,71 +334,6 @@ pub unsafe extern "C" fn extism_plugin_new(
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new plugin and set the number of instructions a plugin is allowed to execute
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_new_with_fuel_limit(
|
||||
wasm: *const u8,
|
||||
wasm_size: Size,
|
||||
functions: *mut *const ExtismFunction,
|
||||
n_functions: Size,
|
||||
with_wasi: bool,
|
||||
fuel_limit: u64,
|
||||
errmsg: *mut *mut std::ffi::c_char,
|
||||
) -> *mut Plugin {
|
||||
trace!(
|
||||
"Call to extism_plugin_new_with_fuel_limit with wasm pointer {:?}",
|
||||
wasm
|
||||
);
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
let mut funcs = vec![];
|
||||
|
||||
if !functions.is_null() {
|
||||
for i in 0..n_functions {
|
||||
unsafe {
|
||||
let f = *functions.add(i as usize);
|
||||
if f.is_null() {
|
||||
continue;
|
||||
}
|
||||
if let Some(f) = (*f).0.take() {
|
||||
funcs.push(f);
|
||||
} else {
|
||||
let e = std::ffi::CString::new(
|
||||
"Function cannot be registered with multiple different Plugins",
|
||||
)
|
||||
.unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let plugin = Plugin::build_new(
|
||||
PluginBuilder::new(data)
|
||||
.with_functions(funcs)
|
||||
.with_wasi(with_wasi)
|
||||
.with_fuel_limit(fuel_limit),
|
||||
);
|
||||
|
||||
match plugin {
|
||||
Err(e) => {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(format!("Unable to create Extism plugin: {}", e))
|
||||
.unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
Ok(p) => Box::into_raw(Box::new(p)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable HTTP response headers in plugins using `extism:host/env::http_request`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_allow_http_response_headers(plugin: *mut Plugin) {
|
||||
let plugin = &mut *plugin;
|
||||
plugin.store.data_mut().http_headers = Some(BTreeMap::new());
|
||||
}
|
||||
|
||||
/// Free the error returned by `extism_plugin_new`, errors returned from `extism_plugin_error` don't need to be freed
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_new_error_free(err: *mut std::ffi::c_char) {
|
||||
@@ -481,6 +394,8 @@ pub unsafe extern "C" fn extism_plugin_config(
|
||||
return false;
|
||||
}
|
||||
let plugin = &mut *plugin;
|
||||
let _lock = plugin.instance.clone();
|
||||
let mut lock = _lock.lock().unwrap();
|
||||
|
||||
trace!(
|
||||
plugin = plugin.id.to_string(),
|
||||
@@ -491,8 +406,8 @@ pub unsafe extern "C" fn extism_plugin_config(
|
||||
let json: std::collections::BTreeMap<String, Option<String>> =
|
||||
match serde_json::from_slice(data) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return false;
|
||||
Err(e) => {
|
||||
return plugin.return_error(&mut lock, e, false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -525,6 +440,9 @@ pub unsafe extern "C" fn extism_plugin_function_exists(
|
||||
return false;
|
||||
}
|
||||
let plugin = &mut *plugin;
|
||||
let _lock = plugin.instance.clone();
|
||||
let mut lock = _lock.lock().unwrap();
|
||||
|
||||
let name = std::ffi::CStr::from_ptr(func_name);
|
||||
trace!(
|
||||
plugin = plugin.id.to_string(),
|
||||
@@ -534,8 +452,8 @@ pub unsafe extern "C" fn extism_plugin_function_exists(
|
||||
|
||||
let name = match name.to_str() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return false;
|
||||
Err(e) => {
|
||||
return plugin.return_error(&mut lock, e, false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -591,10 +509,7 @@ pub unsafe extern "C" fn extism_plugin_call_with_host_context(
|
||||
let name = std::ffi::CStr::from_ptr(func_name);
|
||||
let name = match name.to_str() {
|
||||
Ok(name) => name,
|
||||
Err(e) => {
|
||||
plugin.error_msg = Some(make_error_msg(e.to_string()));
|
||||
return -1;
|
||||
}
|
||||
Err(e) => return plugin.return_error(&mut lock, e, -1),
|
||||
};
|
||||
|
||||
trace!(
|
||||
@@ -603,17 +518,13 @@ pub unsafe extern "C" fn extism_plugin_call_with_host_context(
|
||||
name
|
||||
);
|
||||
let input = std::slice::from_raw_parts(data, data_len as usize);
|
||||
let r = if host_context.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(CVoidContainer(host_context))
|
||||
let r = match ExternRef::new(&mut plugin.store, CVoidContainer(host_context)) {
|
||||
Err(e) => return plugin.return_error(&mut lock, e, -1),
|
||||
Ok(x) => x,
|
||||
};
|
||||
let res = plugin.raw_call(&mut lock, name, input, r);
|
||||
let res = plugin.raw_call(&mut lock, name, input, Some(r));
|
||||
match res {
|
||||
Err((e, rc)) => {
|
||||
plugin.error_msg = Some(make_error_msg(e.to_string()));
|
||||
rc
|
||||
}
|
||||
Err((e, rc)) => plugin.return_error(&mut lock, e, rc),
|
||||
Ok(x) => x,
|
||||
}
|
||||
}
|
||||
@@ -636,26 +547,14 @@ pub unsafe extern "C" fn extism_plugin_error(plugin: *mut Plugin) -> *const c_ch
|
||||
let _lock = _lock.lock().unwrap();
|
||||
|
||||
if plugin.output.error_offset == 0 {
|
||||
if let Some(err) = &plugin.error_msg {
|
||||
return err.as_ptr() as *const _;
|
||||
}
|
||||
trace!(plugin = plugin.id.to_string(), "error is NULL");
|
||||
return std::ptr::null();
|
||||
}
|
||||
|
||||
let offs = plugin.output.error_offset;
|
||||
|
||||
let ptr = plugin.current_plugin_mut().memory_ptr().add(offs as usize) as *const _;
|
||||
|
||||
let len = plugin
|
||||
plugin
|
||||
.current_plugin_mut()
|
||||
.memory_length(offs)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut data = std::slice::from_raw_parts(ptr, len as usize).to_vec();
|
||||
data.push(0);
|
||||
plugin.error_msg = Some(data);
|
||||
plugin.error_msg.as_ref().unwrap().as_ptr() as *const _
|
||||
.memory_ptr()
|
||||
.add(plugin.output.error_offset as usize) as *const _
|
||||
}
|
||||
|
||||
/// Get the length of a plugin's output data
|
||||
@@ -730,6 +629,7 @@ pub unsafe extern "C" fn extism_log_file(
|
||||
fn set_log_file(log_file: impl Into<std::path::PathBuf>, filter: &str) -> Result<(), Error> {
|
||||
let log_file = log_file.into();
|
||||
let s = log_file.to_str();
|
||||
|
||||
let is_level = tracing::Level::from_str(filter).is_ok();
|
||||
let cfg = tracing_subscriber::FmtSubscriber::builder().with_env_filter({
|
||||
let x = tracing_subscriber::EnvFilter::builder()
|
||||
@@ -780,7 +680,6 @@ pub unsafe extern "C" fn extism_log_custom(log_level: *const c_char) -> bool {
|
||||
} else {
|
||||
"error"
|
||||
};
|
||||
|
||||
set_log_buffer(level).is_ok()
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
hello world!
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
const WASM_EMPTY: &[u8] = include_bytes!("../../../wasm/empty.wasm");
|
||||
const WASM_UNREACHABLE: &[u8] = include_bytes!("../../../wasm/unreachable.wasm");
|
||||
|
||||
// https://github.com/extism/extism/issues/620
|
||||
#[test]
|
||||
@@ -27,31 +26,3 @@ host_fn!(
|
||||
Ok(path.display().to_string())
|
||||
}
|
||||
);
|
||||
|
||||
// https://github.com/extism/extism/issues/775
|
||||
#[test]
|
||||
fn test_issue_775() {
|
||||
// Load and build plugin
|
||||
let url = Wasm::data(WASM_UNREACHABLE);
|
||||
let manifest = Manifest::new([url]);
|
||||
let mut plugin = PluginBuilder::new(manifest)
|
||||
.with_wasi(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
// Call test method
|
||||
let lock = plugin.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let res = plugin.raw_call(&mut lock, "do_unreachable", b"", None::<()>);
|
||||
let p = match res {
|
||||
Err(e) => {
|
||||
if e.1 == 0 {
|
||||
Err(e.1)
|
||||
} else {
|
||||
Ok(e.1)
|
||||
}
|
||||
}
|
||||
Ok(code) => Err(code),
|
||||
}
|
||||
.unwrap();
|
||||
println!("{}", p);
|
||||
}
|
||||
|
||||
@@ -3,32 +3,36 @@ use quickcheck::*;
|
||||
|
||||
const KERNEL: &[u8] = include_bytes!("../extism-runtime.wasm");
|
||||
|
||||
fn extism_alloc<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, n: u64) -> u64 {
|
||||
fn extism_alloc<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, n: u64) -> u64 {
|
||||
let out_alloc = &mut [Val::I64(0)];
|
||||
instance
|
||||
.get_func(&mut *store, "alloc")
|
||||
.get_func(&mut store, "alloc")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(n as i64)], out_alloc)
|
||||
.call(&mut store, &[Val::I64(n as i64)], out_alloc)
|
||||
.unwrap();
|
||||
out_alloc[0].unwrap_i64() as u64
|
||||
}
|
||||
|
||||
fn extism_length<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
|
||||
fn extism_length<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
|
||||
let out = &mut [Val::I64(0)];
|
||||
instance
|
||||
.get_func(&mut *store, "length")
|
||||
.get_func(&mut store, "length")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64)], out)
|
||||
.call(&mut store, &[Val::I64(p as i64)], out)
|
||||
.unwrap();
|
||||
out[0].unwrap_i64() as u64
|
||||
}
|
||||
|
||||
fn extism_length_unsafe<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
|
||||
fn extism_length_unsafe<T>(
|
||||
mut store: &mut wasmtime::Store<T>,
|
||||
instance: &mut Instance,
|
||||
p: u64,
|
||||
) -> u64 {
|
||||
let out = &mut [Val::I64(0)];
|
||||
instance
|
||||
.get_func(&mut *store, "length_unsafe")
|
||||
.get_func(&mut store, "length_unsafe")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64)], out)
|
||||
.call(&mut store, &[Val::I64(p as i64)], out)
|
||||
.unwrap();
|
||||
out[0].unwrap_i64() as u64
|
||||
}
|
||||
@@ -43,96 +47,122 @@ fn extism_load_u8<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance
|
||||
out[0].unwrap_i32() as u8
|
||||
}
|
||||
|
||||
fn extism_load_u64<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
|
||||
fn extism_load_u64<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
|
||||
let out = &mut [Val::I32(0)];
|
||||
instance
|
||||
.get_func(&mut *store, "load_u64")
|
||||
.get_func(&mut store, "load_u64")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64)], out)
|
||||
.call(&mut store, &[Val::I64(p as i64)], out)
|
||||
.unwrap();
|
||||
out[0].unwrap_i64() as u64
|
||||
}
|
||||
|
||||
fn extism_input_load_u8<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u8 {
|
||||
fn extism_input_load_u8<T>(
|
||||
mut store: &mut wasmtime::Store<T>,
|
||||
instance: &mut Instance,
|
||||
p: u64,
|
||||
) -> u8 {
|
||||
let out = &mut [Val::I32(0)];
|
||||
instance
|
||||
.get_func(&mut *store, "input_load_u8")
|
||||
.get_func(&mut store, "input_load_u8")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64)], out)
|
||||
.call(&mut store, &[Val::I64(p as i64)], out)
|
||||
.unwrap();
|
||||
out[0].unwrap_i32() as u8
|
||||
}
|
||||
|
||||
fn extism_input_load_u64<T>(
|
||||
store: &mut wasmtime::Store<T>,
|
||||
mut store: &mut wasmtime::Store<T>,
|
||||
instance: &mut Instance,
|
||||
p: u64,
|
||||
) -> u64 {
|
||||
let out = &mut [Val::I32(0)];
|
||||
instance
|
||||
.get_func(&mut *store, "input_load_u64")
|
||||
.get_func(&mut store, "input_load_u64")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64)], out)
|
||||
.call(&mut store, &[Val::I64(p as i64)], out)
|
||||
.unwrap();
|
||||
out[0].unwrap_i64() as u64
|
||||
}
|
||||
|
||||
fn extism_store_u8<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64, x: u8) {
|
||||
fn extism_store_u8<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64, x: u8) {
|
||||
instance
|
||||
.get_func(&mut *store, "store_u8")
|
||||
.get_func(&mut store, "store_u8")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64), Val::I32(x as i32)], &mut [])
|
||||
.call(
|
||||
&mut store,
|
||||
&[Val::I64(p as i64), Val::I32(x as i32)],
|
||||
&mut [],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn extism_store_u64<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64, x: u64) {
|
||||
fn extism_store_u64<T>(
|
||||
mut store: &mut wasmtime::Store<T>,
|
||||
instance: &mut Instance,
|
||||
p: u64,
|
||||
x: u64,
|
||||
) {
|
||||
instance
|
||||
.get_func(&mut *store, "store_u64")
|
||||
.get_func(&mut store, "store_u64")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64), Val::I64(x as i64)], &mut [])
|
||||
.call(
|
||||
&mut store,
|
||||
&[Val::I64(p as i64), Val::I64(x as i64)],
|
||||
&mut [],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn extism_free<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) {
|
||||
fn extism_free<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) {
|
||||
instance
|
||||
.get_func(&mut *store, "free")
|
||||
.get_func(&mut store, "free")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64)], &mut [])
|
||||
.call(&mut store, &[Val::I64(p as i64)], &mut [])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn extism_error_set<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) {
|
||||
fn extism_error_set<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) {
|
||||
instance
|
||||
.get_func(&mut *store, "error_set")
|
||||
.get_func(&mut store, "error_set")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64)], &mut [])
|
||||
.call(&mut store, &[Val::I64(p as i64)], &mut [])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn extism_error_get<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance) -> u64 {
|
||||
fn extism_error_get<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance) -> u64 {
|
||||
let out = &mut [Val::I64(0)];
|
||||
instance
|
||||
.get_func(&mut *store, "error_get")
|
||||
.get_func(&mut store, "error_get")
|
||||
.unwrap()
|
||||
.call(store, &[], out)
|
||||
.call(&mut store, &[], out)
|
||||
.unwrap();
|
||||
|
||||
out[0].unwrap_i64() as u64
|
||||
}
|
||||
|
||||
fn extism_reset<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance) {
|
||||
fn extism_reset<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance) {
|
||||
instance
|
||||
.get_func(&mut *store, "reset")
|
||||
.get_func(&mut store, "reset")
|
||||
.unwrap()
|
||||
.call(store, &[], &mut [])
|
||||
.call(&mut store, &[], &mut [])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn extism_input_set<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64, l: u64) {
|
||||
fn extism_input_set<T>(
|
||||
mut store: &mut wasmtime::Store<T>,
|
||||
instance: &mut Instance,
|
||||
p: u64,
|
||||
l: u64,
|
||||
) {
|
||||
instance
|
||||
.get_func(&mut *store, "input_set")
|
||||
.get_func(&mut store, "input_set")
|
||||
.unwrap()
|
||||
.call(store, &[Val::I64(p as i64), Val::I64(l as i64)], &mut [])
|
||||
.call(
|
||||
&mut store,
|
||||
&[Val::I64(p as i64), Val::I64(l as i64)],
|
||||
&mut [],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -153,29 +183,9 @@ fn test_kernel_allocations() {
|
||||
// Test allocations
|
||||
assert_eq!(extism_alloc(&mut store, instance, 0), 0);
|
||||
|
||||
// 512 bytes, test block re-use + splitting
|
||||
let p = extism_alloc(&mut store, instance, 65535);
|
||||
let first_alloc = p;
|
||||
assert_eq!(extism_length(&mut store, instance, p), 65535);
|
||||
assert_eq!(extism_length(&mut store, instance, p + 1), 0);
|
||||
assert_eq!(extism_length(&mut store, instance, p + 2), 0);
|
||||
assert_eq!(extism_length(&mut store, instance, p + 3), 0);
|
||||
assert_eq!(extism_length(&mut store, instance, p + 4), 0);
|
||||
extism_free(&mut store, instance, p);
|
||||
|
||||
// Should re-use the previous block
|
||||
let q = extism_alloc(&mut store, instance, 65535);
|
||||
assert_eq!(q, p);
|
||||
assert_eq!(extism_length(&mut store, instance, q), 65535);
|
||||
extism_free(&mut store, instance, q);
|
||||
|
||||
let r = extism_alloc(&mut store, instance, 65535 - 24);
|
||||
assert_eq!(r, q);
|
||||
assert_eq!(extism_length(&mut store, instance, q), 65535 - 24);
|
||||
extism_free(&mut store, instance, r);
|
||||
|
||||
// 1 byte
|
||||
let p = extism_alloc(&mut store, instance, 1);
|
||||
let first_alloc = p;
|
||||
assert!(p > 0);
|
||||
assert_eq!(extism_length(&mut store, instance, p), 1);
|
||||
assert_eq!(extism_length_unsafe(&mut store, instance, p), 1);
|
||||
@@ -195,8 +205,37 @@ fn test_kernel_allocations() {
|
||||
assert_eq!(extism_length(&mut store, instance, p), 64 - i);
|
||||
assert_eq!(extism_length_unsafe(&mut store, instance, p), 64 - i);
|
||||
extism_free(&mut store, instance, p);
|
||||
|
||||
// should re-use the last allocation
|
||||
let q = extism_alloc(&mut store, instance, 64 - i);
|
||||
assert_eq!(p, q);
|
||||
assert_eq!(extism_length(&mut store, instance, q), 64 - i);
|
||||
assert_eq!(extism_length_unsafe(&mut store, instance, q), 64 - i);
|
||||
extism_free(&mut store, instance, q);
|
||||
}
|
||||
|
||||
// 512 bytes, test block re-use + splitting
|
||||
let p = extism_alloc(&mut store, instance, 512);
|
||||
assert_eq!(extism_length(&mut store, instance, p), 512);
|
||||
assert_eq!(extism_length(&mut store, instance, p + 1), 0);
|
||||
assert_eq!(extism_length(&mut store, instance, p + 2), 0);
|
||||
assert_eq!(extism_length(&mut store, instance, p + 3), 0);
|
||||
assert_eq!(extism_length(&mut store, instance, p + 4), 0);
|
||||
extism_free(&mut store, instance, p);
|
||||
|
||||
// 128 bytes, should be split off the 512 byte block
|
||||
let q = extism_alloc(&mut store, instance, 128);
|
||||
assert!(p <= q && q < p + 512);
|
||||
assert_eq!(extism_length(&mut store, instance, q), 128);
|
||||
extism_free(&mut store, instance, q);
|
||||
|
||||
// 128 bytes, same as above
|
||||
let r = extism_alloc(&mut store, instance, 128);
|
||||
assert!(p <= r && r < p + 512);
|
||||
assert!(r > p);
|
||||
assert_eq!(extism_length(&mut store, instance, r), 128);
|
||||
extism_free(&mut store, instance, q);
|
||||
|
||||
// 100 pages
|
||||
let p = extism_alloc(&mut store, instance, 6553600);
|
||||
assert!(p > 0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use extism_manifest::{HttpRequest, MemoryOptions};
|
||||
use extism_manifest::MemoryOptions;
|
||||
|
||||
use crate::*;
|
||||
use std::{collections::HashMap, io::Write, time::Instant};
|
||||
use std::{io::Write, time::Instant};
|
||||
|
||||
const WASM: &[u8] = include_bytes!("../../../wasm/code-functions.wasm");
|
||||
const WASM_NO_FUNCTIONS: &[u8] = include_bytes!("../../../wasm/code.wasm");
|
||||
@@ -9,8 +9,6 @@ const WASM_LOOP: &[u8] = include_bytes!("../../../wasm/loop.wasm");
|
||||
const WASM_GLOBALS: &[u8] = include_bytes!("../../../wasm/globals.wasm");
|
||||
const WASM_REFLECT: &[u8] = include_bytes!("../../../wasm/reflect.wasm");
|
||||
const WASM_HTTP: &[u8] = include_bytes!("../../../wasm/http.wasm");
|
||||
const WASM_HTTP_HEADERS: &[u8] = include_bytes!("../../../wasm/http_headers.wasm");
|
||||
const WASM_FS: &[u8] = include_bytes!("../../../wasm/read_write.wasm");
|
||||
|
||||
host_fn!(pub hello_world (a: String) -> String { Ok(a) });
|
||||
|
||||
@@ -243,23 +241,6 @@ fn test_timeout() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuel() {
|
||||
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_LOOP)]);
|
||||
let mut plugin = PluginBuilder::new(manifest)
|
||||
.with_wasi(true)
|
||||
.with_fuel_limit(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
for _ in 0..10001 {
|
||||
let output: Result<&[u8], Error> = plugin.call("loop_forever", "abc123");
|
||||
let err = output.unwrap_err().root_cause().to_string();
|
||||
println!("Fuel limited plugin exited with error: {:?}", &err);
|
||||
assert!(err.contains("fuel"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "http")]
|
||||
fn test_http_timeout() {
|
||||
let f = Function::new(
|
||||
"hello_world",
|
||||
@@ -335,10 +316,8 @@ fn test_multiple_instantiations() {
|
||||
#[test]
|
||||
fn test_globals() {
|
||||
let mut plugin = Plugin::new(WASM_GLOBALS, [], true).unwrap();
|
||||
for i in 0..100001 {
|
||||
let Json(count) = plugin
|
||||
.call_with_host_context::<_, Json<Count>, _>("globals", "", ())
|
||||
.unwrap();
|
||||
for i in 0..100000 {
|
||||
let Json(count) = plugin.call::<_, Json<Count>>("globals", "").unwrap();
|
||||
assert_eq!(count.count, i);
|
||||
}
|
||||
}
|
||||
@@ -369,7 +348,7 @@ fn test_call_with_host_context() {
|
||||
[PTR],
|
||||
UserData::default(),
|
||||
|current_plugin, _val, ret, _user_data: UserData<()>| {
|
||||
let foo = current_plugin.host_context::<Foo>()?.clone();
|
||||
let foo = current_plugin.host_context::<Foo>()?;
|
||||
let hnd = current_plugin.memory_new(foo.message)?;
|
||||
ret[0] = current_plugin.memory_to_val(hnd);
|
||||
Ok(())
|
||||
@@ -771,56 +750,3 @@ fn test_linking() {
|
||||
assert_eq!(plugin.call::<&str, i64>("run", "Hello, world!").unwrap(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readonly_dirs() {
|
||||
let wasm = Wasm::data(WASM_FS);
|
||||
let manifest = Manifest::new([wasm])
|
||||
.with_allowed_path("ro:src/tests/data".to_string(), "/data")
|
||||
.with_config_key("path", "/data/data.txt");
|
||||
|
||||
let mut plugin = PluginBuilder::new(manifest)
|
||||
.with_wasi(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let res = plugin.call::<&str, &str>("try_read", "").unwrap();
|
||||
assert_eq!(res, "hello world!");
|
||||
|
||||
let line = "hello world 2";
|
||||
let res2 = plugin.call::<&str, &str>("try_write", line);
|
||||
assert!(
|
||||
res2.is_err(),
|
||||
"Expected try_write to fail, but it succeeded."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "http")]
|
||||
fn test_http_response_headers() {
|
||||
let mut plugin = PluginBuilder::new(
|
||||
Manifest::new([Wasm::data(WASM_HTTP_HEADERS)]).with_allowed_host("extism.org"),
|
||||
)
|
||||
.with_http_response_headers(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
let req = HttpRequest::new("https://extism.org");
|
||||
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
|
||||
println!("{:?}", res);
|
||||
assert_eq!(res["content-type"], "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "http")]
|
||||
fn test_http_response_headers_disabled() {
|
||||
let mut plugin = PluginBuilder::new(
|
||||
Manifest::new([Wasm::data(WASM_HTTP_HEADERS)]).with_allowed_host("extism.org"),
|
||||
)
|
||||
.with_http_response_headers(false)
|
||||
.build()
|
||||
.unwrap();
|
||||
let req = HttpRequest::new("https://extism.org");
|
||||
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_empty());
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user