Compare commits

..

1 Commits

Author SHA1 Message Date
zach
8263790a74 feat: add Plugin::call_with_arg 2024-04-09 10:51:08 -07:00
50 changed files with 677 additions and 2001 deletions

1
.github/CODEOWNERS vendored
View File

@@ -1 +0,0 @@
* @zshipko

View File

@@ -1,4 +1,4 @@
on:
on:
pull_request:
paths:
- .github/actions/extism/**
@@ -116,9 +116,4 @@ jobs:
path: target/**
key: ${{ runner.os }}-target-${{ github.sha }}
- run: cargo install cargo-criterion
- run:
cargo criterion
- run: |
git fetch
git checkout main
cargo criterion
- run: cargo criterion

View File

@@ -21,9 +21,6 @@ jobs:
target: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- name: install wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Install deps
run: |
sudo apt install wabt --yes

View File

@@ -18,7 +18,7 @@ jobs:
uses: actions/setup-dotnet@v3.0.3
with:
dotnet-version: 7.x
- uses: dawidd6/action-download-artifact@v6
- uses: dawidd6/action-download-artifact@v2
with:
workflow: release.yml
name: release-artifacts

View File

@@ -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'

View File

@@ -4,12 +4,10 @@ AEXT=a
FEATURES?=default
DEFAULT_FEATURES?=yes
RUST_TARGET?=
EXTRA_LIBS=
UNAME := $(shell uname -s)
ifeq ($(UNAME),Darwin)
SOEXT=dylib
EXTRA_LIBS=-framework Security
endif
ifeq ($(DEFAULT_FEATURES),no)
@@ -31,8 +29,7 @@ endif
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml $(TARGET_FLAGS)
sed -e "s%@CMAKE_INSTALL_PREFIX@%$(DEST)%" libextism/extism.pc.in > libextism/extism.pc
sed -e "s%@CMAKE_INSTALL_PREFIX@%$(DEST)%" \
-e "s%Libs: %Libs: $(EXTRA_LIBS) %" libextism/extism-static.pc.in > libextism/extism-static.pc
sed -e "s%@CMAKE_INSTALL_PREFIX@%$(DEST)%" libextism/extism-static.pc.in > libextism/extism-static.pc
bench:
@(cargo criterion $(TARGET_FLAGS) || echo 'For nicer output use cargo-criterion: `cargo install cargo-criterion` - using `cargo bench`') && cargo bench $(TARGET_FLAGS)

View File

@@ -8,7 +8,7 @@
[![Discord](https://img.shields.io/discord/1011124058408112148?color=%23404eed&label=Community%20Chat&logo=Discord&logoColor=%23404eed)](https://extism.org/discord)
![GitHub Org's stars](https://img.shields.io/github/stars/extism)
![Downloads](https://img.shields.io/crates/d/extism-manifest)
![Downloads](https://img.shields.io/crates/d/extism)
![GitHub License](https://img.shields.io/github/license/extism/extism)
![GitHub release (with filter)](https://img.shields.io/github/v/release/extism/extism)
@@ -51,7 +51,6 @@ started:
| Java SDK | <img alt="Java SDK" src="https://extism.org/img/sdk-languages/java-android.svg" width="50px"/> | https://github.com/extism/java-sdk | [Sonatype](https://central.sonatype.com/artifact/org.extism.sdk/extism) |
| .NET SDK | <img alt=".NET SDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-sdk <br/>(supports C# & F#!) | [Nuget](https://www.nuget.org/packages/Extism.Sdk) |
| OCaml SDK | <img alt="OCaml SDK" src="https://extism.org/img/sdk-languages/ocaml.svg" width="50px"/> | https://github.com/extism/ocaml-sdk | [opam](https://opam.ocaml.org/packages/extism/) |
| Perl SDK | <img alt="Perl SDK" src="https://extism.org/img/sdk-languages/perl.svg" width="50px"/> | https://github.com/extism/perl-sdk | [CPAN](https://metacpan.org/pod/Extism) |
| PHP SDK | <img alt="PHP SDK" src="https://extism.org/img/sdk-languages/php.svg" width="50px"/> | https://github.com/extism/php-sdk | [Packagist](https://packagist.org/packages/extism/extism) |
| Python SDK | <img alt="Python SDK" src="https://extism.org/img/sdk-languages/python.svg" width="50px"/> | https://github.com/extism/python-sdk | [PyPi](https://pypi.org/project/extism/) |
| Ruby SDK | <img alt="Ruby SDK" src="https://extism.org/img/sdk-languages/ruby.svg" width="50px"/> | https://github.com/extism/ruby-sdk | [RubyGems](https://rubygems.org/gems/extism) |
@@ -61,11 +60,11 @@ 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), 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, and more.
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,
and more.
Pick a PDK to import into your Wasm program, and refer to the documentation to
get started:
@@ -74,73 +73,13 @@ get started:
| ------------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
| Rust PDK | <img alt="Rust PDK" src="https://extism.org/img/sdk-languages/rust.svg" width="50px"/> | https://github.com/extism/rust-pdk | [Crates.io](https://crates.io/crates/extism-pdk) |
| JS PDK | <img alt="JS PDK" src="https://extism.org/img/sdk-languages/js.svg" width="50px"/> | https://github.com/extism/js-pdk | N/A |
| Python PDK | <img alt="Python PDK" src="https://extism.org/img/sdk-languages/python.svg" width="50px"/> | https://github.com/extism/python-pdk | N/A |
| Go PDK | <img alt="Go PDK" src="https://extism.org/img/sdk-languages/go.svg" width="50px"/> | https://github.com/extism/go-pdk | [Go mod](https://pkg.go.dev/github.com/extism/go-pdk) |
| Haskell PDK | <img alt="Haskell PDK" src="https://extism.org/img/sdk-languages/haskell.svg" width="50px"/> | https://github.com/extism/haskell-pdk | [Hackage](https://hackage.haskell.org/package/extism-pdk) |
| AssemblyScript PDK | <img alt="AssemblyScript PDK" src="https://extism.org/img/sdk-languages/assemblyscript.svg" width="50px"/> | https://github.com/extism/assemblyscript-pdk | [NPM](https://www.npmjs.com/package/@extism/as-pdk) |
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | [Nuget](https://www.nuget.org/packages/Extism.Pdk) |
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | https://www.nuget.org/packages/Extism.Pdk |
| C PDK | <img alt="C PDK" src="https://extism.org/img/sdk-languages/c.svg" width="50px"/> | https://github.com/extism/c-pdk | N/A |
| C++ PDK | <img alt="C++ PDK" src="https://extism.org/img/sdk-languages/cpp.svg" width="50px"/> | https://github.com/extism/cpp-pdk | N/A |
| Zig PDK | <img alt="Zig PDK" src="https://extism.org/img/sdk-languages/zig.svg" width="50px"/> | https://github.com/extism/zig-pdk | N/A |
# Generating Bindings
It's often very useful to define a schema to describe the function signatures
and types you want to use between Extism SDK and PDK languages.
[XTP Bindgen](https://github.com/dylibso/xtp-bindgen) is an open source
framework to generate PDK bindings for Extism plug-ins. It's used by the
[XTP Platform](https://www.getxtp.com/), but can be used outside of the platform
to define any Extism compatible plug-in system.
## 1. Install the `xtp` CLI.
See installation instructions
[here](https://docs.xtp.dylibso.com/docs/cli#installation).
## 2. Create a schema using our OpenAPI-inspired IDL:
```yaml
version: v1-draft
exports:
CountVowels:
input:
type: string
contentType: text/plain; charset=utf-8
output:
$ref: "#/components/schemas/VowelReport"
contentType: application/json
# components.schemas defined in example-schema.yaml...
```
> See an example in [example-schema.yaml](./example-schema.yaml), or a full
> "kitchen sink" example on
> [the docs page](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema/).
## 3. Generate bindings to use from your plugins:
```
xtp plugin init --schema-file ./example-schema.yaml
> 1. TypeScript
2. Go
3. Rust
4. Python
5. C#
6. Zig
7. C++
8. GitHub Template
9. Local Template
```
This will create an entire boilerplate plugin project for you to get started
with. Implement the empty function(s), and run `xtp plugin build` to compile
your plugin.
> For more information about XTP Bindgen, see the
> [dylibso/xtp-bindgen](https://github.com/dylibso/xtp-bindgen) repository and
> the official
> [XTP Schema documentation](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema).
# Support
## Discord

View File

@@ -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"

View File

@@ -55,7 +55,7 @@ encoding!(pub Json, serde_json::to_vec, serde_json::from_slice);
#[cfg(feature = "msgpack")]
encoding!(pub Msgpack, rmp_serde::to_vec, rmp_serde::from_slice);
impl ToBytes<'_> for serde_json::Value {
impl<'a> ToBytes<'a> for serde_json::Value {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -85,7 +85,7 @@ impl<T: AsRef<[u8]>> From<T> for Base64<T> {
}
}
impl<T: AsRef<[u8]>> ToBytes<'_> for Base64<T> {
impl<'a, T: AsRef<[u8]>> ToBytes<'a> for Base64<T> {
type Bytes = String;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -124,7 +124,7 @@ impl<T: prost::Message> From<T> for Prost<T> {
}
#[cfg(feature = "prost")]
impl<T: prost::Message> ToBytes<'_> for Prost<T> {
impl<'a, T: prost::Message> ToBytes<'a> for Prost<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -146,7 +146,7 @@ impl<T: Default + prost::Message> FromBytesOwned for Prost<T> {
pub struct Protobuf<T: protobuf::Message>(pub T);
#[cfg(feature = "protobuf")]
impl<T: protobuf::Message> ToBytes<'_> for Protobuf<T> {
impl<'a, T: protobuf::Message> ToBytes<'a> for Protobuf<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {

View File

@@ -62,7 +62,7 @@ fn rountrip_option() {
}
#[cfg(all(feature = "raw", target_endian = "little"))]
mod raw_tests {
mod tests {
use crate::*;
#[test]
@@ -81,10 +81,10 @@ mod raw_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());
}
}

View File

@@ -61,21 +61,21 @@ pub trait ToBytes<'a> {
fn to_bytes(&self) -> Result<Self::Bytes, Error>;
}
impl ToBytes<'_> for () {
impl<'a> ToBytes<'a> for () {
type Bytes = [u8; 0];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok([])
}
}
impl ToBytes<'_> for Vec<u8> {
impl<'a> ToBytes<'a> for Vec<u8> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
}
}
impl ToBytes<'_> for String {
impl<'a> ToBytes<'a> for String {
type Bytes = String;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
@@ -96,7 +96,7 @@ impl<'a> ToBytes<'a> for &'a str {
}
}
impl ToBytes<'_> for f64 {
impl<'a> ToBytes<'a> for f64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -104,7 +104,7 @@ impl ToBytes<'_> for f64 {
}
}
impl ToBytes<'_> for f32 {
impl<'a> ToBytes<'a> for f32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -112,7 +112,7 @@ impl ToBytes<'_> for f32 {
}
}
impl ToBytes<'_> for i64 {
impl<'a> ToBytes<'a> for i64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -120,7 +120,7 @@ impl ToBytes<'_> for i64 {
}
}
impl ToBytes<'_> for i32 {
impl<'a> ToBytes<'a> for i32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -128,7 +128,7 @@ impl ToBytes<'_> for i32 {
}
}
impl ToBytes<'_> for u64 {
impl<'a> ToBytes<'a> for u64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -136,7 +136,7 @@ impl ToBytes<'_> for u64 {
}
}
impl ToBytes<'_> for u32 {
impl<'a> ToBytes<'a> for u32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {

View File

@@ -1,28 +0,0 @@
# yaml-language-server: $schema=https://xtp.dylibso.com/assets/wasm/schema.json
# Learn more at https://docs.xtp.dylibso.com/docs/concepts/xtp-schema
version: v1-draft
exports:
CountVowels:
input:
type: string
contentType: text/plain; charset=utf-8
output:
$ref: "#/components/schemas/VowelReport"
contentType: application/json
components:
schemas:
VowelReport:
description: The result of counting vowels on the Vowels input.
properties:
count:
type: integer
format: int32
description: The count of vowels for input string.
total:
type: integer
format: int32
description: The cumulative amount of vowels counted, if this keeps state across multiple function calls.
nullable: true
vowels:
type: string
description: The set of vowels used to get the count, e.g. "aAeEiIoOuU"

View File

@@ -17,9 +17,6 @@ done
cargo build --package extism-runtime-kernel --bin extism-runtime --release --target wasm32-unknown-unknown $CARGO_FLAGS
cp target/wasm32-unknown-unknown/release/extism-runtime.wasm .
wasm-strip extism-runtime.wasm
mv extism-runtime.wasm ../runtime/src/extism-runtime.wasm
wasm-tools parse extism-context.wat -o extism-context.wasm
wasm-merge --enable-reference-types ./extism-runtime.wasm runtime extism-context.wasm context -o ../runtime/src/extism-runtime.wasm
rm extism-context.wasm
rm extism-runtime.wasm
wasm-strip ../runtime/src/extism-runtime.wasm

View File

@@ -1,3 +0,0 @@
(module
(global (export "extism_context") (mut externref) (ref.null extern))
)

View File

@@ -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;

View File

@@ -1,2 +0,0 @@
BasedOnStyle: LLVM
IndentWidth: 2

View File

@@ -14,9 +14,7 @@ Create a new plugin.
- `functions`: is an array of `ExtismFunction*`
- `n_functions`: is the number of functions
- `with_wasi`: enables/disables WASI
- `errmsg`: error message during plugin creation, this should be freed with
`extism_plugin_new_error_free`
- `errmsg`: error message during plugin creation
```c
ExtismPlugin extism_plugin_new(const uint8_t *wasm,
@@ -26,17 +24,6 @@ ExtismPlugin extism_plugin_new(const uint8_t *wasm,
bool with_wasi,
char **errmsg);
```
---
### `extism_plugin_new_error_free`
Frees the error message returned when creating a plugin
```c
void extism_plugin_new_error_free(char *err);
```
---
### `extism_plugin_free`
@@ -46,8 +33,6 @@ Remove a plugin from the registry and free associated memory.
void extism_plugin_free(ExtismPlugin *plugin);
```
---
### `extism_plugin_config`
Update plugin config values, this will merge with the existing values.
@@ -58,8 +43,6 @@ bool extism_plugin_config(ExtismPlugin *plugin,
ExtismSize json_size);
```
---
### `extism_plugin_function_exists`
Returns true if `func_name` exists.
@@ -69,8 +52,6 @@ bool extism_plugin_function_exists(ExtismPlugin *plugin,
const char *func_name);
```
---
### `extism_plugin_call`
Call a function.
@@ -78,8 +59,6 @@ Call a function.
- `data`: is the input data
- `data_len`: is the length of `data`
Returns `0` when the call is successful.
```c
int32_t extism_plugin_call(ExtismPlugin *plugin,
const char *func_name,
@@ -87,28 +66,6 @@ int32_t extism_plugin_call(ExtismPlugin *plugin,
ExtismSize data_len);
```
---
### `extism_plugin_call_with_host_context`
Call a function with additional host context that can be accessed from inside host functions.
- `func_name`: is the function to call
- `data`: is the input data
- `data_len`: is the length of `data`
- `host_ctx`: an opaque pointer that can be accessed in host functions
Returns `0` when the call is successful.
```c
int32_t extism_plugin_call_with_host_context(ExtismPlugin *plugin,
const char *func_name,
const uint8_t *data,
ExtismSize data_len,
void *host_ctx);
```
---
### `extism_plugin_error`
Get the error associated with a `Plugin`
@@ -117,8 +74,6 @@ Get the error associated with a `Plugin`
const char *extism_plugin_error(ExtismPlugin *plugin);
```
---
### `extism_plugin_output_length`
Get the length of a plugin's output data.
@@ -127,8 +82,6 @@ Get the length of a plugin's output data.
ExtismSize extism_plugin_output_length(ExtismPlugin *plugin);
```
---
### `extism_plugin_output_data`
Get the plugin's output data.
@@ -137,8 +90,6 @@ Get the plugin's output data.
const uint8_t *extism_plugin_output_data(ExtismPlugin *plugin);
```
---
### `extism_plugin_reset`
Reset the Extism runtime, this will invalidate all allocated memory.
@@ -147,8 +98,6 @@ Reset the Extism runtime, this will invalidate all allocated memory.
bool extism_plugin_reset(ExtismPlugin *plugin);
```
---
### `extism_log_file`
Set log file and level.
@@ -157,8 +106,6 @@ Set log file and level.
bool extism_log_file(const char *filename, const char *log_level);
```
---
### `extism_log_custom`
Enable a custom log handler, this will buffer logs until `extism_log_drain`
@@ -168,8 +115,6 @@ is called Log level should be one of: info, error, trace, debug, warn
bool extism_log_custom(const char *log_level);
```
---
### `extism_log_drain`
Calls the provided callback function for each buffered log line.
@@ -179,8 +124,6 @@ This is only needed when `extism_log_custom` is used.
void extism_log_drain(void (*handler)(const char *, uintptr_t));
```
---
### `extism_version`
Get the Extism version string.
@@ -189,8 +132,6 @@ Get the Extism version string.
const char *extism_version(void);
```
---
### `extism_current_plugin_memory`
Returns a pointer to the memory of the currently running plugin
@@ -199,19 +140,6 @@ Returns a pointer to the memory of the currently running plugin
uint8_t *extism_current_plugin_memory(ExtismCurrentPlugin *plugin);
```
---
### `extism_current_plugin_host_context`
Get access to the host context, passed in using `extism_plugin_call_with_host_context`
```c
void *extism_current_plugin_host_context(ExtismCurrentPlugin *plugin);
```
---
### `extism_current_plugin_memory_alloc`
Allocate a memory block in the currently running plugin
@@ -220,8 +148,6 @@ Allocate a memory block in the currently running plugin
uint64_t extism_current_plugin_memory_alloc(ExtismCurrentPlugin *plugin, ExtismSize n);
```
---
### `extism_current_plugin_memory_length`
Get the length of an allocated block
@@ -230,8 +156,6 @@ Get the length of an allocated block
ExtismSize extism_current_plugin_memory_length(ExtismCurrentPlugin *plugin, ExtismSize n);
```
---
### `extism_current_plugin_memory_free`
Free an allocated memory block
@@ -240,8 +164,6 @@ Free an allocated memory block
void extism_current_plugin_memory_free(ExtismCurrentPlugin *plugin, uint64_t ptr);
```
---
### `extism_function_new`
Create a new host function
- `name`: function name, this should be valid UTF-8
@@ -268,8 +190,6 @@ ExtismFunction *extism_function_new(const char *name,
void (*free_user_data)(void *_));
```
---
### `extism_function_set_namespace`
Set the namespace of an `ExtismFunction`
@@ -278,8 +198,6 @@ Set the namespace of an `ExtismFunction`
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
```
---
### `extism_function_free`
Free an `ExtismFunction`
@@ -288,8 +206,6 @@ Free an `ExtismFunction`
void extism_function_free(ExtismFunction *ptr);
```
---
### `extism_plugin_cancel_handle`
Get handle for plugin cancellation
@@ -298,8 +214,6 @@ Get handle for plugin cancellation
const ExtismCancelHandle *extism_plugin_cancel_handle(const ExtismPlugin *plugin);
```
---
### `extism_plugin_cancel`
Cancel a running plugin from another thread
@@ -308,14 +222,12 @@ Cancel a running plugin from another thread
bool extism_plugin_cancel(const ExtismCancelHandle *handle);
```
---
## Type definitions:
### `ExtismPlugin`
```c
typedef struct ExtismPlugin ExtismPlugin;
typedef int32_t ExtismPlugin;
```
### `ExtismSize`
@@ -347,5 +259,3 @@ typedef struct ExtismCurrentPlugin ExtismCurrentPlugin;
```c
typedef struct ExtismCancelHandle ExtismCancelHandle;
```

View File

@@ -4,7 +4,7 @@ build:
.PHONY: static
static:
$(CC) -g -o example example.c -l:libextism.a -lm -lpthread
$(CC) -g -o example example.c -l:libextism.a -lm
# if needed, set PKG_CONFIG_PATH= to the directory with extism*.pc installed
LDFLAGS=`pkg-config --libs extism`

View File

@@ -56,6 +56,7 @@ Since you may not have an Extism plug-in on hand to test, let's load a demo plug
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void print_plugin_output(ExtismPlugin *plugin, int32_t rc){
if (rc != EXTISM_SUCCESS) {
@@ -63,9 +64,9 @@ void print_plugin_output(ExtismPlugin *plugin, int32_t rc){
return;
}
ExtismSize outlen = extism_plugin_output_length(plugin);
size_t outlen = extism_plugin_output_length(plugin);
const uint8_t *out = extism_plugin_output_data(plugin);
fwrite(out, 1, outlen, stdout);
write(STDOUT_FILENO, out, outlen);
}
int main(void) {
@@ -105,9 +106,9 @@ if (rc != EXTISM_SUCCESS) {
exit(2);
}
ExtismSize outlen = extism_plugin_output_length(plugin);
size_t outlen = extism_plugin_output_length(plugin);
const uint8_t *out = extism_plugin_output_data(plugin);
fwrite(out, 1, outlen, stdout);
write(STDOUT_FILENO, out, outlen);
```
Will print
@@ -170,6 +171,7 @@ We want to expose two functions to our plugin, `kv_write(key: String, value: Byt
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// A stubbed out KV store
typedef struct KVStore KVStore;
@@ -182,14 +184,14 @@ extern const uint32_t fake_kv_store_get(KVStore *kv, const char *key,
// Our host functions to access the fake KV store
void kv_get(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
ExtismSize ninputs, ExtismVal *outputs, ExtismSize noutputs,
size_t ninputs, ExtismVal *outputs, size_t noutputs,
void *userdata) {
// Cast the userdata pointer
KVStore *kv = (KVStore *)userdata;
// Get the offset to the key in the plugin memory
uint64_t offs = inputs[0].v.i64;
ExtismSize keylen = extism_current_plugin_memory_length(plugin, offs);
size_t keylen = extism_current_plugin_memory_length(plugin, offs);
// Allocate a new block to return
uint64_t outoffs =
@@ -203,23 +205,23 @@ void kv_get(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
*(uint64_t *)(extism_current_plugin_memory(plugin) + outoffs) = value;
// Return the offset to our allocated block
outputs[0].t = EXTISM_PTR;
outputs[0].t = PTR;
outputs[0].v.i64 = outoffs;
}
void kv_set(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
ExtismSize ninputs, ExtismVal *outputs, ExtismSize noutputs,
size_t ninputs, ExtismVal *outputs, size_t noutputs,
void *userdata) {
// Cast the userdata pointer
KVStore *kv = (KVStore *)userdata;
// Get the offset to the key in the plugin memory
uint64_t keyoffs = inputs[0].v.i64;
ExtismSize keylen = extism_current_plugin_memory_length(plugin, keyoffs);
size_t keylen = extism_current_plugin_memory_length(plugin, keyoffs);
// Get the offset to the value in the plugin memory
uint64_t valueoffs = inputs[1].v.i64;
ExtismSize valuelen = extism_current_plugin_memory_length(plugin, valueoffs);
size_t valuelen = extism_current_plugin_memory_length(plugin, valueoffs);
// Set key => value
fake_kv_store_set(
@@ -232,13 +234,13 @@ int main(void) {
const char *manifest = "{\"wasm\": [{\"url\": "
"\"https://github.com/extism/plugins/releases/latest/"
"download/count_vowels_kvstore.wasm\"}]}";
const ExtismValType kv_get_inputs[] = {EXTISM_PTR};
const ExtismValType kv_get_outputs[] = {EXTISM_PTR};
const ExtismValType kv_get_inputs[] = {PTR};
const ExtismValType kv_get_outputs[] = {PTR};
ExtismFunction *kv_get_fn = extism_function_new(
"kv_get", kv_get_inputs, 1, kv_get_outputs, 1, kv_get, kv, NULL);
const ExtismValType kv_set_inputs[] = {EXTISM_PTR};
const ExtismValType kv_set_outputs[] = {EXTISM_PTR};
const ExtismValType kv_set_inputs[] = {PTR};
const ExtismValType kv_set_outputs[] = {PTR};
ExtismFunction *kv_set_fn = extism_function_new(
"kv_set", kv_set_inputs, 1, kv_set_outputs, 1, kv_set, kv, NULL);
const ExtismFunction *functions[] = {kv_get_fn};
@@ -286,3 +288,5 @@ print_plugin_output(plugin, extism_plugin_call(plugin, "count_vowels",
# => Writing value=6 from key=count-vowels"
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
```

View File

@@ -9,7 +9,7 @@
#include <sys/stat.h>
#include <unistd.h>
void log_handler(const char *line, ExtismSize length) {
void log_handler(const char *line, uintptr_t length) {
fwrite(line, length, 1, stderr);
}
@@ -63,8 +63,8 @@ int main(int argc, char *argv[]) {
size_t len = 0;
uint8_t *data = read_file("../wasm/code-functions.wasm", &len);
ExtismValType inputs[] = {EXTISM_PTR};
ExtismValType outputs[] = {EXTISM_PTR};
ExtismValType inputs[] = {PTR};
ExtismValType outputs[] = {PTR};
ExtismFunction *f =
extism_function_new("hello_world", inputs, 1, outputs, 1, hello_world,
"Hello, again!", free_data);

View File

@@ -5,6 +5,6 @@ includedir=${prefix}/include
Version: 1.0.0
Name: Extism
Description: The framework for building with WebAssembly (wasm).
Libs: ${libdir}/libextism.a
Libs.private: -lm -lpthread
Libs: -L${libdir} -l:libextism.a
Libs.private: -lm
Cflags: -I${includedir}

View File

@@ -6,5 +6,5 @@ Version: 1.0.0
Name: Extism
Description: The framework for building with WebAssembly (wasm).
Libs: -L${libdir} -lextism
Libs.private: -lm -lpthread
Libs.private: -lm
Cflags: -I${includedir}

View File

@@ -10,6 +10,7 @@ pub type ManifestMemory = MemoryOptions;
#[serde(deny_unknown_fields)]
pub struct MemoryOptions {
/// The max number of WebAssembly pages that should be allocated
#[serde(alias = "max")]
pub max_pages: Option<u32>,
/// The maximum number of bytes allowed in an HTTP response
@@ -61,6 +62,7 @@ pub struct HttpRequest {
/// Request headers
#[serde(default)]
#[serde(alias = "header")]
pub headers: std::collections::BTreeMap<String, String>,
/// Request method
@@ -269,8 +271,8 @@ pub struct Manifest {
/// Config values are made accessible using the PDK `extism_config_get` function
#[serde(default)]
pub config: BTreeMap<String, String>,
#[serde(default)]
/// Specifies which hosts may be accessed via HTTP, if this is empty then
/// no hosts may be accessed. Wildcards may be used.
pub allowed_hosts: Option<Vec<String>>,
@@ -279,7 +281,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 +339,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 +357,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
}
@@ -417,14 +420,14 @@ mod wasmdata {
}
}
impl From<Manifest> for std::borrow::Cow<'_, [u8]> {
impl<'a> From<Manifest> for std::borrow::Cow<'a, [u8]> {
fn from(m: Manifest) -> Self {
let s = serde_json::to_vec(&m).unwrap();
std::borrow::Cow::Owned(s)
}
}
impl From<&Manifest> for std::borrow::Cow<'_, [u8]> {
impl<'a> From<&Manifest> for std::borrow::Cow<'a, [u8]> {
fn from(m: &Manifest) -> Self {
let s = serde_json::to_vec(&m).unwrap();
std::borrow::Cow::Owned(s)

View File

@@ -9,16 +9,15 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = {version = ">= 26.0.0, < 27.0.0"}
wasi-common = {version = ">= 26.0.0, < 27.0.0"}
wiggle = {version = ">= 26.0.0, < 27.0.0"}
wasmtime = ">= 14.0.0, < 18.0.0"
wasmtime-wasi = ">= 14.0.0, < 18.0.0"
anyhow = "1"
serde = {version = "1", features = ["derive"]}
serde_json = "1"
toml = "0.8"
sha2 = "0.10"
tracing = "0.1"
tracing-subscriber = {version = "0.3.18", features = ["std", "env-filter", "fmt"]}
tracing-subscriber = {version = "0.3", features = ["std", "env-filter", "fmt"]}
url = "2"
glob = "0.3"
ureq = {version = "2.5", optional=true}
@@ -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"

View File

@@ -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.0.0"
```
## Environment variables
@@ -201,7 +201,7 @@ fn main() {
}
```
> *Note*: In order to write host functions you should get familiar with the methods on the [CurrentPlugin](https://docs.rs/extism/latest/extism/struct.CurrentPlugin.html) and [UserData](https://docs.rs/extism/latest/extism/enum.UserData.html) types.
> *Note*: In order to write host functions you should get familiar with the methods on the [Extism::CurrentPlugin](https://docs.rs/extism/latest/extism/struct.CurrentPlugin.html) and [Extism::CurrentPlugin](https://docs.rs/extism/latest/extism/struct.UserData.html) types.
Now we can invoke the event:

View File

@@ -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,46 +35,6 @@ pub fn create_plugin(c: &mut Criterion) {
});
}
pub fn create_compiled(c: &mut Criterion) {
let mut g = c.benchmark_group("create");
g.noise_threshold(1.0);
g.significance_level(0.2);
g.bench_function("create_compiled", |b| {
b.iter(|| {
let plugin = PluginBuilder::new(COUNT_VOWELS).with_wasi(true);
let _compiled = CompiledPlugin::new(plugin).unwrap();
})
});
}
pub fn create_plugin_compiled(c: &mut Criterion) {
let mut g = c.benchmark_group("create");
g.noise_threshold(1.0);
g.significance_level(0.2);
let plugin = PluginBuilder::new(COUNT_VOWELS).with_wasi(true);
let compiled = CompiledPlugin::new(plugin).unwrap();
g.bench_function("create_plugin_compiled", |b| {
b.iter(|| {
let _plugin = Plugin::new_from_compiled(&compiled).unwrap();
})
});
}
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,
@@ -194,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) {
@@ -297,16 +245,12 @@ pub fn reflect_linked(c: &mut Criterion) {
criterion_group!(
benches,
allocations,
consume,
echo,
reflect,
reflect_linked,
basic,
create_plugin,
create_plugin_compiled,
create_plugin_no_cache,
create_compiled,
count_vowels
);
criterion_main!(benches);

View File

@@ -7,7 +7,7 @@ fn main() {
#define EXTISM_SUCCESS 0
/** An alias for I64 to signify an Extism pointer */
#define EXTISM_PTR ExtismValType_I64
#define PTR I64
";
if let Ok(x) = cbindgen::Builder::new()
.with_crate(".")
@@ -24,7 +24,6 @@ fn main() {
.rename_item("CurrentPlugin", "ExtismCurrentPlugin")
.rename_item("CancelHandle", "ExtismCancelHandle")
.rename_item("Plugin", "ExtismPlugin")
.rename_item("CompiledPlugin", "ExtismCompiledPlugin")
.rename_item("Function", "ExtismFunction")
.with_style(cbindgen::Style::Type)
.generate()

View File

@@ -1,36 +0,0 @@
use extism::*;
fn main() {
let url = Wasm::file("../wasm/read_write.wasm");
let manifest = Manifest::new([url])
// This will fail because we're using a readonly path (specified with the `ro:` prefix)
// to overwrite the data file, remove `ro:` from the path on the following line
.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!("-----------------------------------------------------");
// If the allowed path is readonly then writing back to the file should fail
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!");
}

View File

@@ -2,8 +2,6 @@ use extism::*;
fn main() {
let manifest = Manifest::new([
// upper.wat provides an export called `host_reflect` that takes a string
// and returns the same string uppercased
Wasm::File {
// See https://github.com/extism/plugins/blob/main/upper.wat
path: "../wasm/upper.wasm".into(),
@@ -12,9 +10,6 @@ fn main() {
hash: None,
},
},
// reflect expects host_reflect to be imported: https://github.com/extism/plugins/blob/e5578bbbdd87f9936a0a8d36df629768b2eff6bb/reflect/src/lib.rs#L5
// Extism will link the export from upper.wat to the import of reflect.rs at runtime so it
// can call it
Wasm::File {
// See https://github.com/extism/plugins/tree/main/reflect
path: "../wasm/reflect.wasm".into(),

View File

@@ -10,7 +10,7 @@
#define EXTISM_SUCCESS 0
/** An alias for I64 to signify an Extism pointer */
#define EXTISM_PTR ExtismValType_I64
#define PTR I64
/**
@@ -20,31 +20,31 @@ typedef enum {
/**
* Signed 32 bit integer.
*/
ExtismValType_I32,
I32,
/**
* Signed 64 bit integer.
*/
ExtismValType_I64,
I64,
/**
* Floating point 32 bit integer.
*/
ExtismValType_F32,
F32,
/**
* Floating point 64 bit integer.
*/
ExtismValType_F64,
F64,
/**
* A 128 bit number.
*/
ExtismValType_V128,
V128,
/**
* A reference to a Wasm function.
*/
ExtismValType_FuncRef,
FuncRef,
/**
* A reference to opaque data in the Wasm instance.
*/
ExtismValType_ExternRef,
ExternRef,
} ExtismValType;
/**
@@ -52,8 +52,6 @@ typedef enum {
*/
typedef struct ExtismCancelHandle ExtismCancelHandle;
typedef struct ExtismCompiledPlugin ExtismCompiledPlugin;
/**
* CurrentPlugin stores data that is available to the caller in PDK functions, this should
* only be accessed from inside a host function
@@ -115,12 +113,6 @@ extern "C" {
*/
const uint8_t *extism_plugin_id(ExtismPlugin *plugin);
/**
* Get the current plugin's associated host context data. Returns null if call was made without
* host context.
*/
void *extism_current_plugin_host_context(ExtismCurrentPlugin *plugin);
/**
* Returns a pointer to the memory of the currently running plugin
* NOTE: this should only be called from host functions.
@@ -181,21 +173,6 @@ void extism_function_free(ExtismFunction *f);
*/
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
/**
* Pre-compile an Extism plugin
*/
ExtismCompiledPlugin *extism_compiled_plugin_new(const uint8_t *wasm,
ExtismSize wasm_size,
const ExtismFunction **functions,
ExtismSize n_functions,
bool with_wasi,
char **errmsg);
/**
* Free `ExtismCompiledPlugin`
*/
void extism_compiled_plugin_free(ExtismCompiledPlugin *plugin);
/**
* Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
*
@@ -212,34 +189,13 @@ ExtismPlugin *extism_plugin_new(const uint8_t *wasm,
bool with_wasi,
char **errmsg);
/**
* Create a new plugin from an `ExtismCompiledPlugin`
*/
ExtismPlugin *extism_plugin_new_from_compiled(const ExtismCompiledPlugin *compiled, 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
*/
void extism_plugin_new_error_free(char *err);
/**
* Free `ExtismPlugin`
* Remove a plugin from the registry and free associated memory
*/
void extism_plugin_free(ExtismPlugin *plugin);
@@ -275,20 +231,6 @@ int32_t extism_plugin_call(ExtismPlugin *plugin,
const uint8_t *data,
ExtismSize data_len);
/**
* Call a function with host context.
*
* `func_name`: is the function to call
* `data`: is the input data
* `data_len`: is the length of `data`
* `host_context`: a pointer to context data that will be available in host functions
*/
int32_t extism_plugin_call_with_host_context(ExtismPlugin *plugin,
const char *func_name,
const uint8_t *data,
ExtismSize data_len,
void *host_context);
/**
* Get the error associated with a `Plugin`
*/
@@ -340,5 +282,5 @@ bool extism_plugin_reset(ExtismPlugin *plugin);
const char *extism_version(void);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
} // extern "C"
#endif // __cplusplus

View File

@@ -1,5 +1,3 @@
use anyhow::Context;
use crate::*;
/// CurrentPlugin stores data that is available to the caller in PDK functions, this should
@@ -14,11 +12,9 @@ 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,
pub(crate) start_time: std::time::Instant,
}
unsafe impl Send for CurrentPlugin {}
@@ -56,12 +52,7 @@ impl wasmtime::ResourceLimiter for MemoryLimiter {
Ok(true)
}
fn table_growing(
&mut self,
_current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool> {
fn table_growing(&mut self, _current: u32, desired: u32, maximum: Option<u32>) -> Result<bool> {
if let Some(max) = maximum {
return Ok(desired <= max);
}
@@ -71,11 +62,6 @@ impl wasmtime::ResourceLimiter for MemoryLimiter {
}
impl CurrentPlugin {
/// Gets `Plugin`'s ID
pub fn id(&self) -> uuid::Uuid {
self.id
}
/// Get a `MemoryHandle` from a memory offset
pub fn memory_handle(&mut self, offs: u64) -> Option<MemoryHandle> {
if offs == 0 {
@@ -168,10 +154,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 []);
}
@@ -182,10 +168,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(&[]);
}
@@ -195,29 +181,6 @@ 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")
else {
anyhow::bail!("unable to locate an extism kernel global: extism_context",)
};
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"),
}
}
pub fn memory_alloc(&mut self, n: u64) -> Result<MemoryHandle, Error> {
if n == 0 {
return Ok(MemoryHandle {
@@ -228,13 +191,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);
}
@@ -256,15 +215,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",)
}
@@ -272,16 +227,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",)
}
@@ -296,16 +247,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",)
}
@@ -338,51 +285,41 @@ 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 {
let auth = wasi_common::sync::ambient_authority();
let random = wasi_common::sync::random_ctx();
let clocks = wasi_common::sync::clocks_ctx();
let sched = wasi_common::sync::sched_ctx();
let table = wasi_common::Table::new();
let ctx = wasi_common::WasiCtx::new(random, clocks, sched, table);
let auth = wasmtime_wasi::ambient_authority();
let mut ctx = wasmtime_wasi::WasiCtxBuilder::new();
for (k, v) in manifest.config.iter() {
ctx.env(k, v)?;
}
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)
};
ctx.push_preopened_dir(file, v)?;
let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth).map_err(|err| {
Error::msg(format!(
"Unable to preopen directory \"{}\": {}",
k.display(),
err.kind()
))
})?;
ctx.preopened_dir(d, v)?;
}
}
// Enable WASI output, typically used for debugging purposes
if std::env::var("EXTISM_ENABLE_WASI_OUTPUT").is_ok() {
ctx.set_stderr(Box::new(wasi_common::sync::stdio::stderr()));
ctx.set_stdout(Box::new(wasi_common::sync::stdio::stdout()));
ctx.inherit_stdout().inherit_stderr();
}
Some(Wasi { ctx })
Some(Wasi { ctx: ctx.build() })
} else {
None
};
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,
})
@@ -400,12 +337,6 @@ impl CurrentPlugin {
available_pages,
memory_limiter,
id,
start_time: std::time::Instant::now(),
http_headers: if allow_http_response_headers {
Some(BTreeMap::new())
} else {
None
},
})
}
@@ -450,12 +381,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(),
@@ -486,15 +417,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 {
@@ -503,13 +432,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
@@ -521,18 +447,6 @@ impl CurrentPlugin {
let length = self.memory_length(offs).unwrap_or_default();
(offs, length)
}
/// Returns the remaining time before a plugin will timeout, or
/// `None` if no timeout is configured in the manifest
pub fn time_remaining(&self) -> Option<std::time::Duration> {
if let Some(x) = &self.manifest.timeout_ms {
let elapsed = &self.start_time.elapsed().as_millis();
let ms_left = x.saturating_sub(*elapsed as u64);
return Some(std::time::Duration::from_millis(ms_left));
}
None
}
}
impl Internal for CurrentPlugin {
@@ -544,6 +458,14 @@ impl Internal for CurrentPlugin {
unsafe { &mut *self.store }
}
fn linker(&self) -> &Linker<CurrentPlugin> {
unsafe { &*self.linker }
}
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin> {
unsafe { &mut *self.linker }
}
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>) {
unsafe { (&mut *self.linker, &mut *self.store) }
}

BIN
runtime/src/extism-runtime.wasm Normal file → Executable file

Binary file not shown.

View File

@@ -4,7 +4,6 @@ use wasmtime::Caller;
use crate::{error, trace, CurrentPlugin, Error};
/// An enumeration of all possible value types in WebAssembly.
/// cbindgen:prefix-with-name
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
#[repr(C)]
pub enum ValType {
@@ -38,13 +37,8 @@ impl From<wasmtime::ValType> for ValType {
F32 => ValType::F32,
F64 => ValType::F64,
V128 => ValType::V128,
Ref(t) => {
if t.heap_type().is_func() {
ValType::FuncRef
} else {
ValType::ExternRef
}
}
FuncRef => ValType::FuncRef,
ExternRef => ValType::ExternRef,
}
}
}
@@ -58,8 +52,8 @@ impl From<ValType> for wasmtime::ValType {
F32 => wasmtime::ValType::F32,
F64 => wasmtime::ValType::F64,
V128 => wasmtime::ValType::V128,
FuncRef => wasmtime::ValType::FUNCREF,
ExternRef => wasmtime::ValType::EXTERNREF,
FuncRef => wasmtime::ValType::FuncRef,
ExternRef => wasmtime::ValType::ExternRef,
}
}
}
@@ -176,8 +170,8 @@ pub struct Function {
/// Module name
pub(crate) namespace: Option<String>,
pub(crate) params: Vec<ValType>,
pub(crate) results: Vec<ValType>,
/// Function type
pub(crate) ty: wasmtime::FuncType,
/// Function handle
pub(crate) f: Arc<FunctionInner>,
@@ -190,8 +184,8 @@ impl Function {
/// Create a new host function
pub fn new<T: 'static, F>(
name: impl Into<String>,
params: impl IntoIterator<Item = ValType>,
results: impl IntoIterator<Item = ValType>,
args: impl IntoIterator<Item = ValType>,
returns: impl IntoIterator<Item = ValType>,
user_data: UserData<T>,
f: F,
) -> Function
@@ -203,13 +197,13 @@ impl Function {
{
let data = user_data.clone();
let name = name.into();
let params = params.into_iter().collect();
let results = results.into_iter().collect();
trace!("Creating function {name}: params={params:?}, results={results:?}");
let args = args.into_iter().map(wasmtime::ValType::from);
let returns = returns.into_iter().map(wasmtime::ValType::from);
let ty = wasmtime::FuncType::new(args, returns);
trace!("Creating function {name}: type={ty:?}");
Function {
name,
params,
results,
ty,
f: Arc::new(
move |mut caller: Caller<_>, inp: &[Val], outp: &mut [Val]| {
let x = data.clone();
@@ -224,22 +218,6 @@ impl Function {
}
}
pub(crate) fn ty(&self, engine: &wasmtime::Engine) -> wasmtime::FuncType {
wasmtime::FuncType::new(
engine,
self.params
.iter()
.cloned()
.map(wasmtime::ValType::from)
.collect::<Vec<_>>(),
self.results
.iter()
.cloned()
.map(wasmtime::ValType::from)
.collect::<Vec<_>>(),
)
}
/// Host function name
pub fn name(&self) -> &str {
&self.name
@@ -263,14 +241,9 @@ impl Function {
self
}
/// Get param types
pub fn params(&self) -> &[ValType] {
&self.params
}
/// Get result types
pub fn results(&self) -> &[ValType] {
&self.results
/// Get function type
pub fn ty(&self) -> &wasmtime::FuncType {
&self.ty
}
}
@@ -279,7 +252,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

View File

@@ -3,7 +3,7 @@ use crate::*;
/// WASI context
pub struct Wasi {
/// wasi
pub ctx: wasi_common::WasiCtx,
pub ctx: wasmtime_wasi::WasiCtx,
}
/// InternalExt provides a unified way of acessing `memory`, `store` and `internal` values
@@ -12,6 +12,10 @@ pub(crate) trait Internal {
fn store_mut(&mut self) -> &mut Store<CurrentPlugin>;
fn linker(&self) -> &Linker<CurrentPlugin>;
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin>;
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>);
fn current_plugin(&self) -> &CurrentPlugin {

View File

@@ -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
@@ -39,9 +27,7 @@ pub use current_plugin::CurrentPlugin;
pub use extism_convert::{FromBytes, FromBytesOwned, ToBytes};
pub use extism_manifest::{Manifest, Wasm, WasmMetadata};
pub use function::{Function, UserData, Val, ValType, PTR};
pub use plugin::{
CancelHandle, CompiledPlugin, Plugin, WasmInput, EXTISM_ENV_MODULE, EXTISM_USER_MODULE,
};
pub use plugin::{CancelHandle, Plugin, WasmInput, EXTISM_ENV_MODULE, EXTISM_USER_MODULE};
pub use plugin_builder::{DebugOptions, PluginBuilder};
pub(crate) use internal::{Internal, Wasi};
@@ -91,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())

View File

@@ -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;
@@ -239,11 +217,6 @@ pub(crate) fn http_request(
r = r.set(k, v);
}
// Set HTTP timeout to respect the manifest timeout
if let Some(remaining) = data.time_remaining() {
r = r.timeout(remaining);
}
let res = if body_offset > 0 {
let handle = match data.memory_handle(body_offset) {
Some(h) => h,
@@ -257,29 +230,12 @@ 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())
}
Err(e) => {
// Catch timeout and return
if let Some(d) = data.time_remaining() {
if e.kind() == ureq::ErrorKind::Io && d.as_nanos() == 0 {
anyhow::bail!("timeout");
}
}
let msg = e.to_string();
if let Some(res) = e.into_response() {
data.http_status = res.status();
@@ -327,24 +283,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 +290,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 +320,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 +337,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 +348,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 +359,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 +366,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,
}
}

View File

@@ -1,11 +1,8 @@
use std::{
any::Any,
collections::{BTreeMap, BTreeSet, HashMap},
collections::{BTreeMap, BTreeSet},
path::PathBuf,
};
use anyhow::Context;
use plugin_builder::PluginBuilderOptions;
use crate::*;
pub const EXTISM_ENV_MODULE: &str = "extism:host/env";
@@ -38,66 +35,6 @@ impl CancelHandle {
}
}
#[derive(Clone)]
pub struct CompiledPlugin {
pub(crate) manifest: Manifest,
pub(crate) modules: BTreeMap<String, Module>,
pub(crate) options: PluginBuilderOptions,
pub(crate) engine: wasmtime::Engine,
}
impl CompiledPlugin {
/// Create a new pre-compiled plugin
pub fn new(builder: PluginBuilder) -> Result<CompiledPlugin, Error> {
let mut config = builder.config.unwrap_or_default();
config
.async_support(false)
.epoch_interruption(true)
.debug_info(builder.options.debug_options.debug_info)
.coredump_on_trap(builder.options.debug_options.coredump.is_some())
.profiler(builder.options.debug_options.profiling_strategy)
.wasm_tail_call(true)
.wasm_function_references(true)
.wasm_gc(true);
if builder.options.fuel.is_some() {
config.consume_fuel(true);
}
match &builder.options.cache_config {
Some(None) => (),
Some(Some(path)) => {
config.cache_config_load(path)?;
}
None => {
if let Ok(env) = std::env::var("EXTISM_CACHE_CONFIG") {
if !env.is_empty() {
config.cache_config_load(&env)?;
}
} else {
config.cache_config_load_default()?;
}
}
}
let engine = Engine::new(&config)?;
let (manifest, modules) = manifest::load(&engine, builder.source)?;
if modules.len() <= 1 {
anyhow::bail!("No wasm modules provided");
} else if !modules.contains_key(MAIN_KEY) {
anyhow::bail!("No main module provided");
}
Ok(CompiledPlugin {
manifest,
modules,
options: builder.options,
engine,
})
}
}
/// Plugin contains everything needed to execute a WASM function
pub struct Plugin {
/// A unique ID for each plugin
@@ -143,12 +80,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 {}
@@ -169,6 +100,14 @@ impl Internal for Plugin {
&mut self.store
}
fn linker(&self) -> &Linker<CurrentPlugin> {
&self.linker
}
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin> {
&mut self.linker
}
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>) {
(&mut self.linker, &mut self.store)
}
@@ -199,7 +138,7 @@ pub enum WasmInput<'a> {
ManifestRef(&'a Manifest),
}
impl From<Manifest> for WasmInput<'_> {
impl<'a> From<Manifest> for WasmInput<'a> {
fn from(value: Manifest) -> Self {
WasmInput::Manifest(value)
}
@@ -229,7 +168,7 @@ impl<'a> From<&'a str> for WasmInput<'a> {
}
}
impl From<Vec<u8>> for WasmInput<'_> {
impl<'a> From<Vec<u8>> for WasmInput<'a> {
fn from(value: Vec<u8>) -> Self {
WasmInput::Data(value.into())
}
@@ -242,7 +181,7 @@ impl<'a> From<&'a Vec<u8>> for WasmInput<'a> {
}
fn add_module<T: 'static>(
mut store: &mut Store<T>,
store: &mut Store<T>,
linker: &mut Linker<T>,
linked: &mut BTreeSet<String>,
modules: &BTreeMap<String, Module>,
@@ -253,66 +192,7 @@ fn add_module<T: 'static>(
return Ok(());
}
let mut imports: HashMap<&str, Vec<(&str, ExternType)>> = HashMap::new();
for module_import in module.imports() {
let import_module = module_import.module();
let import_name = module_import.name();
let import_type = module_import.ty();
if imports.contains_key(import_module) {
imports
.get_mut(import_module)
.unwrap()
.push((import_name, import_type));
} else {
imports.insert(import_module, vec![(import_name, import_type)]);
}
}
for (m, v) in imports.into_iter() {
if let Some(src) = modules.get(m) {
for (name, ty) in v {
match src
.get_export(name)
.or_else(|| linker.get(&mut store, m, name).map(|x| x.ty(&store)))
{
None => anyhow::bail!("missing import: {m}::{name}"),
Some(ex) => match (&ex, &ty) {
(ExternType::Func(a), ExternType::Func(b)) => {
if !a.matches(b) {
anyhow::bail!(
"function type mismatch {m}::{name}, got {ty:?} expected {ex:?}"
)
}
}
(ExternType::Global(a), ExternType::Global(b)) => {
if !a.content().matches(b.content()) {
anyhow::bail!(
"global type mismatch {m}::{name}, got {ty:?} expected {ex:?}"
)
}
}
(ExternType::Memory(_), ExternType::Memory(_)) => (),
(ExternType::Table(a), ExternType::Table(b)) => {
if !a.element().matches(b.element()) {
anyhow::bail!(
"table type mismatch {m}::{name}, got {ty:?} expected {ex:?}"
)
}
}
(a, b) => anyhow::bail!("import type mismatch: {a:?} != {b:?}"),
},
}
}
}
}
for import in module.imports() {
let module = import.module();
if module == EXTISM_ENV_MODULE && !matches!(import.ty(), ExternType::Func(_)) {
anyhow::bail!("linked modules cannot access non-function exports of extism kernel");
}
if !linked.contains(import.module()) {
if let Some(m) = modules.get(import.module()) {
add_module(
@@ -333,89 +213,6 @@ 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,
> {
let mut linker = Linker::new(engine);
linker.allow_shadowing(true);
// Define PDK functions
macro_rules! add_funcs {
($($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?) => {
$(
let t = FuncType::new(&engine, [$($args),*], [$($($r),*)?]);
linker.func_new(EXTISM_ENV_MODULE, stringify!($name), t, pdk::$name)?;
)*
};
}
// Add builtins
use wasmtime::ValType::*;
add_funcs!(
config_get(I64) -> I64;
var_get(I64) -> I64;
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();
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
linked.insert(EXTISM_ENV_MODULE.to_string());
// If wasi is enabled then add it to the linker
if with_wasi {
wasi_common::sync::add_to_linker(&mut linker, |x: &mut CurrentPlugin| {
&mut x.wasi.as_mut().unwrap().ctx
})?;
}
for f in imports {
let name = f.name();
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
unsafe {
linker.func_new(ns, name, f.ty(engine).clone(), &*(f.f.as_ref() as *const _))?;
}
}
for (name, module) in modules.iter() {
add_module(
store,
&mut linker,
&mut linked,
modules,
name.clone(),
module,
)?;
}
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))
}
impl Plugin {
/// Create a new plugin from a Manifest or WebAssembly module, and host functions. The `with_wasi`
/// parameter determines whether or not the module should be executed with WASI enabled.
@@ -424,45 +221,121 @@ impl Plugin {
imports: impl IntoIterator<Item = Function>,
with_wasi: bool,
) -> Result<Plugin, Error> {
Self::new_from_compiled(&CompiledPlugin::new(
PluginBuilder::new(wasm)
.with_functions(imports)
.with_wasi(with_wasi),
)?)
Self::build_new(wasm.into(), imports, with_wasi, Default::default(), None)
}
/// Create a new plugin from a pre-compiled plugin
pub fn new_from_compiled(compiled: &CompiledPlugin) -> Result<Plugin, Error> {
let available_pages = compiled.manifest.memory.max_pages;
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 = Config::new();
config
.epoch_interruption(true)
.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);
match cache_dir {
Some(None) => (),
Some(Some(path)) => {
config.cache_config_load(path)?;
}
None => {
if let Ok(env) = std::env::var("EXTISM_CACHE_CONFIG") {
if !env.is_empty() {
config.cache_config_load(&env)?;
}
} else {
config.cache_config_load_default()?;
}
}
}
let engine = Engine::new(&config)?;
let (manifest, modules) = manifest::load(&engine, wasm)?;
if modules.len() <= 1 {
anyhow::bail!("No wasm modules provided");
} else if !modules.contains_key(MAIN_KEY) {
anyhow::bail!("No main module provided");
}
let available_pages = manifest.memory.max_pages;
debug!("Available pages: {available_pages:?}");
let id = uuid::Uuid::new_v4();
let mut store = Store::new(
&compiled.engine,
CurrentPlugin::new(
compiled.manifest.clone(),
compiled.options.wasi,
available_pages,
compiled.options.http_response_headers,
id,
)?,
&engine,
CurrentPlugin::new(manifest, with_wasi, available_pages, id)?,
);
store.set_epoch_deadline(1);
if let Some(fuel) = compiled.options.fuel {
store.set_fuel(fuel)?;
let mut linker = Linker::new(&engine);
let mut imports: Vec<_> = imports.into_iter().collect();
// Define PDK functions
macro_rules! add_funcs {
($($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?) => {
$(
let t = FuncType::new([$($args),*], [$($($r),*)?]);
linker.func_new(EXTISM_ENV_MODULE, stringify!($name), t, pdk::$name)?;
)*
};
}
let imports: Vec<Function> = compiled.options.functions.to_vec();
let (instance_pre, linker, host_context) = relink(
&compiled.engine,
&mut store,
&imports,
&compiled.modules,
compiled.options.wasi,
)?;
// Add builtins
use wasmtime::ValType::*;
add_funcs!(
config_get(I64) -> I64;
var_get(I64) -> I64;
var_set(I64, I64);
http_request(I64, I64) -> I64;
http_status_code() -> I32;
log_warn(I64);
log_info(I64);
log_debug(I64);
log_error(I64);
);
let mut linked = BTreeSet::new();
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
linked.insert(EXTISM_ENV_MODULE.to_string());
// If wasi is enabled then add it to the linker
if with_wasi {
wasmtime_wasi::add_to_linker(&mut linker, |x: &mut CurrentPlugin| {
&mut x.wasi.as_mut().unwrap().ctx
})?;
}
for f in &mut imports {
let name = f.name();
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
unsafe {
linker.func_new(ns, name, f.ty().clone(), &*(f.f.as_ref() as *const _))?;
}
}
for (name, module) in modules.iter() {
add_module(
&mut store,
&mut linker,
&mut linked,
&modules,
name.clone(),
module,
)?;
}
let main = &modules[MAIN_KEY];
let instance_pre = linker.instantiate_pre(main)?;
let timer_tx = Timer::tx();
let mut plugin = Plugin {
modules: compiled.modules.clone(),
modules,
linker,
instance: std::sync::Arc::new(std::sync::Mutex::new(None)),
instance_pre,
@@ -474,11 +347,8 @@ impl Plugin {
instantiations: 0,
output: Output::default(),
store_needs_reset: false,
debug_options: compiled.options.debug_options.clone(),
debug_options,
_functions: imports,
error_msg: None,
fuel: compiled.options.fuel,
host_context,
};
plugin.current_plugin_mut().store = &mut plugin.store;
@@ -500,33 +370,16 @@ impl Plugin {
if self.store_needs_reset {
let engine = self.store.engine().clone();
let internal = self.current_plugin_mut();
let with_wasi = internal.wasi.is_some();
self.store = Store::new(
&engine,
CurrentPlugin::new(
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(
&engine,
&mut self.store,
&self._functions,
&self.modules,
with_wasi,
)?;
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();
@@ -537,7 +390,14 @@ impl Plugin {
.limiter(|internal| internal.memory_limiter.as_mut().unwrap());
}
let main = &self.modules[MAIN_KEY];
for (name, module) in self.modules.iter() {
if name != MAIN_KEY {
self.linker.module(&mut self.store, name, module)?;
}
}
self.instantiations = 0;
self.instance_pre = self.linker.instantiate_pre(main)?;
**instance_lock = None;
self.store_needs_reset = false;
}
@@ -583,14 +443,14 @@ impl Plugin {
}
/// Returns `true` if the given function exists, otherwise `false`
pub fn function_exists(&self, function: impl AsRef<str>) -> bool {
pub fn function_exists(&mut self, function: impl AsRef<str>) -> bool {
self.modules[MAIN_KEY]
.get_export(function.as_ref())
.map(|x| {
if let Some(f) = x.func() {
let (params, mut results) = (f.params(), f.results());
match (params.len(), results.len()) {
(0, 1) => matches!(results.next(), Some(wasmtime::ValType::I32)),
(0, 1) => results.next() == Some(wasmtime::ValType::I32),
(0, 0) => true,
_ => false,
}
@@ -602,12 +462,7 @@ impl Plugin {
}
// Store input in memory and re-initialize `Internal` pointer
pub(crate) fn set_input(
&mut self,
input: *const u8,
mut len: usize,
host_context: Option<Rooted<ExternRef>>,
) -> Result<(), Error> {
pub(crate) fn set_input(&mut self, input: *const u8, mut len: usize) -> Result<(), Error> {
self.output = Output::default();
self.clear_error()?;
let id = self.id.to_string();
@@ -634,27 +489,13 @@ 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 [],
)?;
}
if let Some(Extern::Global(ctxt)) =
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")?;
}
Ok(())
}
@@ -663,13 +504,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");
}
@@ -745,28 +580,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::null(); 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(),
@@ -774,11 +600,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");
}
}
@@ -791,32 +613,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")
}
@@ -830,10 +640,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
@@ -858,40 +668,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<T: 'static + Sync + Send>(
&mut self,
lock: &mut std::sync::MutexGuard<Option<Instance>>,
name: impl AsRef<str>,
input: impl AsRef<[u8]>,
host_context: Option<T>,
arg: Option<T>,
) -> 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())
.map_err(|x| (x, -1))?;
let func = match self.get_func(lock, name) {
@@ -922,19 +718,17 @@ impl Plugin {
.expect("Timer should start");
self.store.epoch_deadline_trap();
self.store.set_epoch_deadline(1);
self.current_plugin_mut().start_time = std::time::Instant::now();
// Call the function
let mut results = vec![wasmtime::Val::I32(0); n_results];
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
let mut results = vec![wasmtime::Val::null(); n_results];
let args = if func.ty(self.store()).params().count() == 0 {
vec![]
} else {
let r = arg.map(wasmtime::ExternRef::new);
vec![wasmtime::Val::ExternRef(r)]
};
// 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;
}
}
let mut res = func.call(self.store_mut(), args.as_slice(), results.as_mut_slice());
// Stop timer
self.store
@@ -942,56 +736,34 @@ impl Plugin {
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.extism_error_is_set() {
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!(
"unable to load error message from memory: {}",
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"
)));
}
}
@@ -1040,18 +812,23 @@ impl Plugin {
}
}
let wasi_exit_code = e.downcast_ref::<wasi_common::I32Exit>().map(|e| e.0);
let wasi_exit_code = e
.downcast_ref::<wasmtime_wasi::I32Exit>()
.map(|e| e.0)
.or_else(|| {
e.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
.map(|e| e.0)
});
if let Some(exit_code) = wasi_exit_code {
debug!(
plugin = self.id.to_string(),
"WASI exit code: {}", exit_code
);
if exit_code == 0 && !self.extism_error_is_set() {
if exit_code == 0 {
return Ok(0);
}
return Err((e, exit_code));
return Err((e.context("WASI exit code"), exit_code));
}
// Handle timeout interrupts
@@ -1079,10 +856,6 @@ impl Plugin {
}
}
fn extism_error_is_set(&self) -> bool {
self.output.error_offset != 0 && self.output.error_length != 0
}
/// Call a function by name with the given input, the return value is
/// the output data returned by the plugin. The return type can be anything that implements
/// [FromBytes]. This data will be invalidated next time the plugin is called.
@@ -1108,32 +881,21 @@ 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 {
Err(Error::msg(format!("Returned non-zero exit code: {rc}")))
} else {
self.output()
}
})
.and_then(move |_| self.output())
}
pub fn call_with_host_context<'a, 'b, T, U, C>(
pub fn call_with_arg<'a, 'b, T: ToBytes<'a>, U: FromBytes<'b>, V: 'static + Send + Sync>(
&'b mut self,
name: impl AsRef<str>,
input: T,
host_context: C,
) -> Result<U, Error>
where
T: ToBytes<'a>,
U: FromBytes<'b>,
C: Any + Send + Sync + 'static,
{
arg: V,
) -> Result<U, Error> {
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))
self.raw_call(&mut lock, name, data, Some(arg))
.map_err(|e| e.0)
.and_then(move |_| self.output())
}
@@ -1152,7 +914,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)))
}
@@ -1163,21 +925,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
@@ -1221,7 +1004,7 @@ macro_rules! typed_plugin {
impl TryFrom<$crate::Plugin> for $name {
type Error = $crate::Error;
fn try_from(x: $crate::Plugin) -> Result<Self, Self::Error> {
fn try_from(mut x: $crate::Plugin) -> Result<Self, Self::Error> {
$(
if !x.function_exists(stringify!($f)) {
return Err($crate::Error::msg(format!("Invalid function: {}", stringify!($f))));
@@ -1240,7 +1023,7 @@ macro_rules! typed_plugin {
impl $name {
$(
pub fn $f<'a, $( $( $lt $( : $clt )? ),+ )? >(&'a mut self, input: $input) -> Result<$output, $crate::Error> {
self.0.call(stringify!($f), input)
self.0.call::<_, _>(stringify!($f), input)
}
)*
}

View File

@@ -34,19 +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) config: Option<wasmtime::Config>,
pub(crate) options: PluginBuilderOptions,
}
#[derive(Clone)]
pub(crate) struct PluginBuilderOptions {
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) http_response_headers: bool,
source: WasmInput<'a>,
wasi: bool,
functions: Vec<Function>,
debug_options: DebugOptions,
cache_config: Option<Option<PathBuf>>,
}
impl<'a> PluginBuilder<'a> {
@@ -54,21 +46,16 @@ impl<'a> PluginBuilder<'a> {
pub fn new(plugin: impl Into<WasmInput<'a>>) -> Self {
PluginBuilder {
source: plugin.into(),
config: None,
options: PluginBuilderOptions {
wasi: false,
functions: vec![],
debug_options: DebugOptions::default(),
cache_config: None,
fuel: None,
http_response_headers: false,
},
wasi: false,
functions: vec![],
debug_options: DebugOptions::default(),
cache_config: None,
}
}
/// Enables WASI if the argument is set to `true`
pub fn with_wasi(mut self, wasi: bool) -> Self {
self.options.wasi = wasi;
self.wasi = wasi;
self
}
@@ -87,8 +74,7 @@ impl<'a> PluginBuilder<'a> {
+ Sync
+ Send,
{
self.options
.functions
self.functions
.push(Function::new(name, args, returns, user_data, f));
self
}
@@ -109,97 +95,67 @@ impl<'a> PluginBuilder<'a> {
+ Sync
+ Send,
{
self.options
.functions
self.functions
.push(Function::new(name, args, returns, user_data, f).with_namespace(namespace));
self
}
/// Add multiple host functions
pub fn with_functions(mut self, f: impl IntoIterator<Item = Function>) -> Self {
self.options.functions.extend(f);
self.functions.extend(f);
self
}
/// Set profiling strategy
pub fn with_profiling_strategy(mut self, p: wasmtime::ProfilingStrategy) -> Self {
self.options.debug_options.profiling_strategy = p;
self.debug_options.profiling_strategy = p;
self
}
/// Enable Wasmtime coredump on trap
pub fn with_coredump(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.options.debug_options.coredump = Some(path.into());
self.debug_options.coredump = Some(path.into());
self
}
/// Enable Extism memory dump when plugin calls return an error
pub fn with_memdump(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.options.debug_options.memdump = Some(path.into());
self.debug_options.memdump = Some(path.into());
self
}
/// Compile with debug info
pub fn with_debug_info(mut self) -> Self {
self.options.debug_options.debug_info = true;
self.debug_options.debug_info = true;
self
}
/// Configure debug options
pub fn with_debug_options(mut self, options: DebugOptions) -> Self {
self.options.debug_options = options;
self.debug_options = options;
self
}
/// Set wasmtime compilation cache config path
pub fn with_cache_config(mut self, dir: impl Into<PathBuf>) -> Self {
self.options.cache_config = Some(Some(dir.into()));
self.cache_config = Some(Some(dir.into()));
self
}
/// Turn wasmtime compilation caching off
pub fn with_cache_disabled(mut self) -> Self {
self.options.cache_config = Some(None);
self
}
/// Limit the number of instructions that can be executed
pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
self.options.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.options.http_response_headers = allow;
self.cache_config = Some(None);
self
}
/// Generate a new plugin with the configured settings
pub fn build(self) -> Result<Plugin, Error> {
Plugin::new_from_compiled(&CompiledPlugin::new(self)?)
}
/// Build new `CompiledPlugin`
pub fn compile(self) -> Result<CompiledPlugin, Error> {
CompiledPlugin::new(self)
Plugin::build_new(
self.source,
self.functions,
self.wasi,
self.debug_options,
self.cache_config,
)
}
}

View File

@@ -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())
}
}

View File

@@ -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 {
@@ -47,33 +41,33 @@ pub type ExtismFunctionType = extern "C" fn(
/// Log drain callback
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
impl ExtismVal {
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Result<Self, Error> {
match value.ty(ctx)? {
wasmtime::ValType::I32 => Ok(ExtismVal {
impl From<&wasmtime::Val> for ExtismVal {
fn from(value: &wasmtime::Val) -> Self {
match value.ty() {
wasmtime::ValType::I32 => ExtismVal {
t: ValType::I32,
v: ValUnion {
i32: value.unwrap_i32(),
},
}),
wasmtime::ValType::I64 => Ok(ExtismVal {
},
wasmtime::ValType::I64 => ExtismVal {
t: ValType::I64,
v: ValUnion {
i64: value.unwrap_i64(),
},
}),
wasmtime::ValType::F32 => Ok(ExtismVal {
},
wasmtime::ValType::F32 => ExtismVal {
t: ValType::F32,
v: ValUnion {
f32: value.unwrap_f32(),
},
}),
wasmtime::ValType::F64 => Ok(ExtismVal {
},
wasmtime::ValType::F64 => ExtismVal {
t: ValType::F64,
v: ValUnion {
f64: value.unwrap_f64(),
},
}),
},
t => todo!("{}", t),
}
}
@@ -90,24 +84,6 @@ pub unsafe extern "C" fn extism_plugin_id(plugin: *mut Plugin) -> *const u8 {
plugin.id.as_bytes().as_ptr()
}
/// Get the current plugin's associated host context data. Returns null if call was made without
/// host context.
#[no_mangle]
pub unsafe extern "C" fn extism_current_plugin_host_context(
plugin: *mut CurrentPlugin,
) -> *mut std::ffi::c_void {
if plugin.is_null() {
return std::ptr::null_mut();
}
let plugin = &mut *plugin;
if let Ok(CVoidContainer(ptr)) = plugin.host_context::<CVoidContainer>() {
*ptr
} else {
std::ptr::null_mut()
}
}
/// Returns a pointer to the memory of the currently running plugin
/// NOTE: this should only be called from host functions.
#[no_mangle]
@@ -224,11 +200,7 @@ pub unsafe extern "C" fn extism_function_new(
output_types.clone(),
user_data,
move |plugin, inputs, outputs, user_data| {
let store = &*plugin.store;
let inputs: Vec<_> = inputs
.iter()
.map(|x| ExtismVal::from_val(x, store).unwrap())
.collect();
let inputs: Vec<_> = inputs.iter().map(ExtismVal::from).collect();
let mut output_tmp: Vec<_> = output_types
.iter()
.map(|t| ExtismVal {
@@ -237,28 +209,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 +222,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!(),
}
}
@@ -302,76 +258,6 @@ pub unsafe extern "C" fn extism_function_set_namespace(
}
}
/// Pre-compile an Extism plugin
#[no_mangle]
pub unsafe extern "C" fn extism_compiled_plugin_new(
wasm: *const u8,
wasm_size: Size,
functions: *mut *const ExtismFunction,
n_functions: Size,
with_wasi: bool,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut CompiledPlugin {
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let mut builder = PluginBuilder::new(data).with_wasi(with_wasi);
if !functions.is_null() {
let funcs = (0..n_functions)
.map(|i| unsafe { *functions.add(i as usize) })
.map(|ptr| {
if ptr.is_null() {
return Err("Cannot pass null pointer");
}
let ExtismFunction(func) = &*ptr;
let Some(func) = func.take() else {
return Err("Function cannot be registered with multiple different Plugins");
};
Ok(func)
})
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|e| {
if !errmsg.is_null() {
let e = std::ffi::CString::new(e.to_string()).unwrap();
*errmsg = e.into_raw();
}
Vec::new()
});
if funcs.len() != n_functions as usize {
return std::ptr::null_mut();
}
builder = builder.with_functions(funcs);
}
CompiledPlugin::new(builder)
.map(|v| Box::into_raw(Box::new(v)))
.unwrap_or_else(|e| {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!("Unable to compile Extism plugin: {}", e))
.unwrap();
*errmsg = e.into_raw();
}
std::ptr::null_mut()
})
}
/// Free `ExtismCompiledPlugin`
#[no_mangle]
pub unsafe extern "C" fn extism_compiled_plugin_free(plugin: *mut CompiledPlugin) {
if plugin.is_null() {
return;
}
let plugin = Box::from_raw(plugin);
trace!("called extism_compiled_plugin_free");
drop(plugin)
}
/// Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
///
/// `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
@@ -388,59 +274,31 @@ pub unsafe extern "C" fn extism_plugin_new(
with_wasi: bool,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut Plugin {
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let funcs = if functions.is_null() {
vec![]
} else {
let funcs = (0..n_functions)
.map(|i| unsafe { *functions.add(i as usize) })
.map(|ptr| {
if ptr.is_null() {
return Err("Cannot pass null pointer");
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;
}
let ExtismFunction(func) = &*ptr;
let Some(func) = func.take() else {
return Err("Function cannot be registered with multiple different Plugins");
};
Ok(func)
})
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|e| {
if !errmsg.is_null() {
let e = std::ffi::CString::new(e.to_string()).unwrap();
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();
}
Vec::new()
});
if funcs.len() != n_functions as usize {
return std::ptr::null_mut();
}
funcs
};
Plugin::new(data, funcs, with_wasi)
.map(|v| Box::into_raw(Box::new(v)))
.unwrap_or_else(|e| {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!("Unable to compile Extism plugin: {}", e))
.unwrap();
*errmsg = e.into_raw();
}
std::ptr::null_mut()
})
}
}
}
/// Create a new plugin from an `ExtismCompiledPlugin`
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_new_from_compiled(
compiled: *const CompiledPlugin,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut Plugin {
let plugin = Plugin::new_from_compiled(&*compiled);
let plugin = Plugin::new(data, funcs, with_wasi);
match plugin {
Err(e) => {
if !errmsg.is_null() {
@@ -454,94 +312,6 @@ pub unsafe extern "C" fn extism_plugin_new_from_compiled(
}
}
/// 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 funcs = if functions.is_null() {
vec![]
} else {
let funcs = (0..n_functions)
.map(|i| unsafe { *functions.add(i as usize) })
.map(|ptr| {
if ptr.is_null() {
return Err("Cannot pass null pointer");
}
let ExtismFunction(func) = &*ptr;
let Some(func) = func.take() else {
return Err("Function cannot be registered with multiple different Plugins");
};
Ok(func)
})
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|e| {
if !errmsg.is_null() {
let e = std::ffi::CString::new(e.to_string()).unwrap();
*errmsg = e.into_raw();
}
Vec::new()
});
if funcs.len() != n_functions as usize {
return std::ptr::null_mut();
}
funcs
};
let compiled = match CompiledPlugin::new(
PluginBuilder::new(data)
.with_functions(funcs)
.with_wasi(with_wasi)
.with_fuel_limit(fuel_limit),
) {
Ok(x) => x,
Err(e) => {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!("Unable to compile Extism plugin: {}", e))
.unwrap();
*errmsg = e.into_raw();
}
return std::ptr::null_mut();
}
};
let plugin = Plugin::new_from_compiled(&compiled);
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) {
@@ -551,7 +321,7 @@ pub unsafe extern "C" fn extism_plugin_new_error_free(err: *mut std::ffi::c_char
drop(std::ffi::CString::from_raw(err))
}
/// Free `ExtismPlugin`
/// Remove a plugin from the registry and free associated memory
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_free(plugin: *mut Plugin) {
if plugin.is_null() {
@@ -602,6 +372,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(),
@@ -612,11 +384,25 @@ 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);
}
};
let wasi = &mut plugin.current_plugin_mut().wasi;
if let Some(Wasi { ctx, .. }) = wasi {
for (k, v) in json.iter() {
match v {
Some(v) => {
let _ = ctx.push_env(k, v);
}
None => {
let _ = ctx.push_env(k, "");
}
}
}
}
let id = plugin.id;
let config = &mut plugin.current_plugin_mut().manifest.config;
for (k, v) in json.into_iter() {
@@ -646,6 +432,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(),
@@ -655,8 +444,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);
}
};
@@ -675,31 +464,6 @@ pub unsafe extern "C" fn extism_plugin_call(
func_name: *const c_char,
data: *const u8,
data_len: Size,
) -> i32 {
extism_plugin_call_with_host_context(plugin, func_name, data, data_len, std::ptr::null_mut())
}
#[derive(Clone)]
#[repr(transparent)]
struct CVoidContainer(*mut std::ffi::c_void);
// "You break it, you buy it."
unsafe impl Send for CVoidContainer {}
unsafe impl Sync for CVoidContainer {}
/// Call a function with host context.
///
/// `func_name`: is the function to call
/// `data`: is the input data
/// `data_len`: is the length of `data`
/// `host_context`: a pointer to context data that will be available in host functions
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_call_with_host_context(
plugin: *mut Plugin,
func_name: *const c_char,
data: *const u8,
data_len: Size,
host_context: *mut std::ffi::c_void,
) -> i32 {
if plugin.is_null() {
return -1;
@@ -712,10 +476,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!(
@@ -724,17 +485,10 @@ 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 res = plugin.raw_call(&mut lock, name, input, r);
let res = plugin.raw_call::<()>(&mut lock, name, input, None);
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,
}
}
@@ -757,26 +511,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
@@ -851,6 +593,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()
@@ -884,7 +627,7 @@ fn set_log_file(log_file: impl Into<std::path::PathBuf>, filter: &str) -> Result
Ok(())
}
static LOG_BUFFER: std::sync::Mutex<Option<LogBuffer>> = std::sync::Mutex::new(None);
static mut LOG_BUFFER: Option<LogBuffer> = None;
/// Enable a custom log handler, this will buffer logs until `extism_log_drain` is called
/// Log level should be one of: info, error, trace, debug, warn
@@ -901,7 +644,6 @@ pub unsafe extern "C" fn extism_log_custom(log_level: *const c_char) -> bool {
} else {
"error"
};
set_log_buffer(level).is_ok()
}
@@ -916,8 +658,8 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
x.parse_lossy(filter)
}
});
*LOG_BUFFER.lock().unwrap() = Some(LogBuffer::default());
let buf = LOG_BUFFER.lock().unwrap().clone().unwrap();
LOG_BUFFER = Some(LogBuffer::default());
let buf = LOG_BUFFER.clone().unwrap();
cfg.with_ansi(false)
.with_writer(move || buf.clone())
.try_init()
@@ -929,7 +671,7 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
/// Calls the provided callback function for each buffered log line.
/// This is only needed when `extism_log_custom` is used.
pub unsafe extern "C" fn extism_log_drain(handler: ExtismLogDrainFunctionType) {
if let Some(buf) = LOG_BUFFER.lock().unwrap().as_mut() {
if let Some(buf) = LOG_BUFFER.as_mut() {
if let Ok(mut buf) = buf.buffer.lock() {
for (line, len) in buf.drain(..) {
handler(line.as_ptr(), len as u64);

View File

@@ -1 +0,0 @@
hello world!

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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) });
@@ -242,51 +240,6 @@ fn test_timeout() {
assert!(err == "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",
[PTR],
[PTR],
UserData::default(),
hello_world,
);
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_HTTP)])
.with_timeout(std::time::Duration::from_millis(1))
.with_allowed_host("www.extism.org");
let mut plugin = Plugin::new(manifest, [f], true).unwrap();
let start = std::time::Instant::now();
let output: Result<&[u8], Error> =
plugin.call("http_request", r#"{"url": "https://www.extism.org"}"#);
let end = std::time::Instant::now();
let time = end - start;
let err = output.unwrap_err().root_cause().to_string();
println!(
"Timed out plugin ran for {:?}, with error: {:?}",
time, &err
);
assert!(err == "timeout");
}
typed_plugin!(pub TestTypedPluginGenerics {
count_vowels<T: FromBytes<'a>>(&str) -> T
});
@@ -335,10 +288,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);
}
}
@@ -356,42 +307,6 @@ fn test_toml_manifest() {
assert_eq!(count.get("count").unwrap().as_i64().unwrap(), 1);
}
#[test]
fn test_call_with_host_context() {
#[derive(Clone)]
struct Foo {
message: String,
}
let f = Function::new(
"host_reflect",
[PTR],
[PTR],
UserData::default(),
|current_plugin, _val, ret, _user_data: UserData<()>| {
let foo = current_plugin.host_context::<Foo>()?.clone();
let hnd = current_plugin.memory_new(foo.message)?;
ret[0] = current_plugin.memory_to_val(hnd);
Ok(())
},
);
let mut plugin = Plugin::new(WASM_REFLECT, [f], true).unwrap();
let message = "hello world";
let output: String = plugin
.call_with_host_context(
"reflect",
"anything, really",
Foo {
message: message.to_string(),
},
)
.unwrap();
assert_eq!(output, message);
}
#[test]
fn test_fuzz_reflect_plugin() {
// assert!(set_log_file("stdout", Some(log::Level::Trace)));
@@ -447,7 +362,7 @@ fn hello_world_set_error(
_user_data: UserData<()>,
) -> Result<(), Error> {
plugin.set_error("TEST")?;
outputs[0] = inputs[0];
outputs[0] = inputs[0].clone();
Ok(())
}
@@ -544,7 +459,7 @@ fn hello_world_user_data(
let mut data = data.lock().unwrap();
let s = _plugin.memory_get_val(&inputs[0])?;
data.write_all(s)?;
outputs[0] = inputs[0];
outputs[0] = inputs[0].clone();
Ok(())
}
@@ -771,56 +686,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());
}

View File

@@ -22,18 +22,18 @@ pub(crate) struct Timer {
#[cfg(not(target_family = "windows"))]
extern "C" fn cleanup_timer() {
let mut timer = match TIMER.lock() {
let mut timer = match unsafe { TIMER.lock() } {
Ok(x) => x,
Err(e) => e.into_inner(),
};
drop(timer.take());
}
static TIMER: std::sync::Mutex<Option<Timer>> = std::sync::Mutex::new(None);
static mut TIMER: std::sync::Mutex<Option<Timer>> = std::sync::Mutex::new(None);
impl Timer {
pub(crate) fn tx() -> std::sync::mpsc::Sender<TimerAction> {
let mut timer = match TIMER.lock() {
let mut timer = match unsafe { TIMER.lock() } {
Ok(x) => x,
Err(e) => e.into_inner(),
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.