Compare commits

..

24 Commits

Author SHA1 Message Date
zach
1f1e2699cb v1.0.2 2024-01-19 13:10:13 -08:00
zach
d5dc9b41ab feat: Use quickcheck to test allocations, fix one bug that was uncovered. (#662)
- Adds quickcheck tests for alloc/free/load/store from the kernel, I
wasn't able to include these in the new wasm-bindgen tests because
`getrandom` isn't available on wasm32-unknown-unknown
- Fixes a bug in the calculation of how much memory is needed to
allocate the next block in the kernel. This bug is triggered when
allocating at the end of a page, the size of the MemoryBlock value
wasn't being taken in consideration when determining whether or not to
call memory.grow
2024-01-19 13:04:21 -08:00
zach
822cec4093 v1.0.1 2024-01-18 10:32:04 -08:00
Marton Soos
0b4b732eb8 fix(kernel): Fix calculation of handle offset when splitting re-used memory, add kernel test (#659) 2024-01-17 15:15:51 -08:00
zach
fa368d0b5a feat(convert): add conversions for Option<T> (#658) 2024-01-12 14:45:01 -08:00
Gavin Hayes
092eba5e2f feat: manifest wasm data without base64 (#657)
base64 support is retained. Now `data` can instead be `{"ptr":
ptr_value, "len": len_value}` where `ptr_value` points to a wasm module
and `len_value` is the size of bytes of it.
2024-01-12 16:48:25 -05:00
Benjamin Eckel
94b0b9a430 docs: fix rc version in docs (#656) 2024-01-09 18:56:08 -06:00
Steve Manuel
85cc72e832 docs: add .NET PDK link to table 2024-01-09 07:41:13 -07:00
Steve Manuel
9aee9d8ca5 feat: update README (#655)
Gives the README a bit of a refresh.

---------

Co-authored-by: Benjamin Eckel <bhelx@simst.im>
2024-01-09 07:30:30 -07:00
Gavin Hayes
4012dd22de chore: kernel add Handle and Offset types to differentiate from Pointer (#652)
This cosmetic change to the kernel adds two `u64` types for usage where
`Pointer` was used and it is actually something else or is more
restrictive (a pointer to the start of a data block (handle) or a
relative offset).

Instead of adding `Offset` to match `Length` I'd prefer to use `u64`
directly when referring to those scalars, but not sure if the those
types are preferred?
2024-01-08 12:31:24 -05:00
Steve Manuel
211d55337d feat: add rust-protobuf support to extism-convert (#653)
(code from @zshipko, thank you!)

---------

Co-authored-by: Zach Shipko <zach@dylibso.com>
2024-01-05 16:36:40 -07:00
zach
431bc4d8af cleanup: improve wat detection (again) (#651)
Merged the other one too quickly, this PR is updated with some
additional feedback from @chrisdickinson:

              (Ooh, this could also start with `(;`)
https://github.com/extism/extism/pull/650#discussion_r1443419249

It's also updated to accommodate for modules that start with an open
paren followed by some whitespace. I doubt this covers all of the
permitted syntax but it should be good enough.
2024-01-05 14:58:47 -08:00
zach
cd4fc39655 cleanup: improve wat detection (#650)
- Update to ensure `is_wat` is only true when the input is a valid
string
- Update to allow leading whitespace in a wat file
2024-01-05 14:00:41 -08:00
zach
03e761908c feat: add Plugin::call_get_error_code to get Extism error code along with the error (#649)
- Adds `Plugin::call_get_error_code` to get the Extism error code when a
call fails
2024-01-05 12:52:31 -08:00
zach
26542d5740 feat(kernel): add extism_length_unsafe (#648)
- Adds `length_unsafe` function to the extism kernel, a more performant
`length` for known-valid memory handles

After this is merged I will update go-sdk and js-sdk too.

Closes #643
2024-01-03 09:29:05 -08:00
CosmicHorror
950a0f449f Toggle off default clap feature for cbindgen (#644)
The only feature for `cbindgen` is the `clap` feature for using it as a
standalone binary and isn't needed when using it as a library
2023-12-26 19:40:32 -08:00
zach
c8868c37d8 chore: update wasmtime bounds to include 16.0.0 (#642) 2023-12-20 14:37:43 -08:00
zach
ab812d9281 cleanup: remove default timeout (#641)
The default for `timeout_ms` is currently 30s which was an arbitrary
decision but it forces a user to set `timeout_ms` to null to avoid
having plugins cancelled which is a little strange.
2023-12-18 11:05:12 -08:00
Chris Dickinson
2087398513 v1.0.0-rc7 2023-12-15 15:45:23 -08:00
Chris Dickinson
212e28bec3 fix: give extism_log_drain's param a named type (#638)
This fixes a test failure on the python-sdk [1]. (See also [2]). In
particular, while maturin creates bindings for `extism_log_drain` on
clang platforms, it seems that MSVC cannot generate the required binding
information for `ffi.py`. This results in an `extism_sys` wheel whose
shared object (in this case, a DLL) contains the `extism_log_drain`, but
whose Python FFI bindings don't contain a definition for that function.

Naming the function pointer type parameter resolves the issue. What a
strange issue!

[1]:
https://github.com/extism/python-sdk/actions/runs/7172934060/job/19669775001#step:9:35
[2]:
https://github.com/extism/python-sdk/pull/18#issuecomment-1850892835
2023-12-15 15:40:54 -08:00
zach
fd95729d8d fix(kernel): length function should return 0 for invalid offsets (#635)
Fixes #634 

- Updates `extism_length` to walks the allocation list to determine
valid offsets instead of assuming the provided offset is valid

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: zshipko <zshipko@users.noreply.github.com>
2023-12-14 16:55:13 -08:00
Muhammad Azeez
49e28892bc ci: release extism.dll.lib and extism.dll.a (#633)
Related to #141 and #584 
Follow up of #632
2023-12-13 22:29:29 +03:00
zach
a5edf58747 fix(kernel): improve performance after large allocations, add extism_plugin_reset to give users more control when dealing with large allocations (#627)
See https://github.com/extism/cpp-sdk/issues/15

- Limits a call to memset in the kernel to the size of the current
memory offset instead of the total size of memory.
- Adds `extism_plugin_reset` to the C API and `extism::Plugin::reset` to
Rust

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: zshipko <zshipko@users.noreply.github.com>
2023-12-12 13:56:34 -08:00
zach
e5ffabb975 feat: Add extism_convert::Raw to allow direct encoding using bytemuck (#626)
- Adds `extism_convert::Raw` to encode certain types using their direct
memory representations using https://github.com/Lokathor/bytemuck
- Only enabled on little-endian targets to prevent memory mismatch
issues
- Allows for certain types of structs to be encoded using their
in-memory representation
- Makes passing structs between Rust and C easier since none of the
other encodings are available in C by default.

## Usage
After making a bytemuck-compatible struct:
```rust
use bytemuck::{Zeroable, Pod};

#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
#[repr(C)]
struct Point {
  x: i32,
  y: i32,
}
```

It can be used in `call`:

```rust
let input = Point { x: 100, y: 50 };
let Raw(pt): Raw<Point> = plugin.call("transform", Raw(&input))?;
```
2023-12-11 11:14:33 -08:00
34 changed files with 822 additions and 3203 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

BIN
.github/assets/logo-horizontal.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

View File

@@ -39,6 +39,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ env.GIT_EXIT_CODE }} != 0
with:
author: "zshipko <zshipko@users.noreply.github.com>"
title: "update(kernel): extism-runtime.wasm in ${{ github.event.pull_request.head.ref }}"
body: "Automated PR to update `runtime/src/extism-runtime.wasm` in PR #${{ github.event.number }}"
base: "${{ github.event.pull_request.head.ref }}"

View File

@@ -28,48 +28,56 @@ jobs:
target: 'x86_64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'macos'
target: 'aarch64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-musl'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-musl'
artifact: ''
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: ''
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-gnu'
artifact: 'extism.dll'
static-artifact: 'libextism.a'
static-dll-artifact: 'libextism.dll.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-msvc'
artifact: 'extism.dll'
static-artifact: 'extism.lib'
static-dll-artifact: 'extism.dll.lib'
pc-in: ''
static-pc-in: ''
@@ -158,7 +166,8 @@ jobs:
cp LICENSE ${SRC_DIR}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} extism.h \
${{ matrix.artifact }} ${{ matrix.static-artifact }} \
${{ matrix.pc-in }} ${{ matrix.static-pc-in }}
${{ matrix.pc-in }} ${{ matrix.static-pc-in }} \
${{ matrix.static-dll-artifact }}
ls -ll ${ARCHIVE}
if &>/dev/null which shasum; then

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
target
runtime/ast.json
runtime/target
Cargo.lock
.DS_Store
.vscode
**/libextism.dylib

2910
Cargo.lock generated

File diff suppressed because it is too large Load Diff

126
README.md
View File

@@ -1,65 +1,111 @@
# [Extism](https://extism.org)
<div align="center">
<a href="https://extism.org">
<picture>
<source media="(prefers-color-scheme: dark)" srcset=".github/assets/logo-horizontal-darkmode.png">
<img alt="Extism - the WebAssembly framework" width="75%" style="max-width: 600px" src=".github/assets/logo-horizontal.png">
</picture>
</a>
[![Discord](https://img.shields.io/discord/1011124058408112148?color=%23404eed&label=Community%20Chat&logo=Discord&logoColor=%23404eed)](https://discord.gg/cx3usBCWnc)
[![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)
![GitHub all releases](https://img.shields.io/github/downloads/extism/extism/total)
![GitHub License](https://img.shields.io/github/license/extism/extism)
![GitHub release (with filter)](https://img.shields.io/github/v/release/extism/extism)
The universal plug-in system. Run WebAssembly extensions inside your app. Use idiomatic Host SDKs for [Go](https://github.com/extism/go-sdk#readme),
[Ruby](https://github.com/extism/ruby-sdk#readme),
[Python](https://github.com/extism/python-sdk#readme),
[JavaScript](https://github.com/extism/js-sdk#readme),
[Rust](/runtime/#readme),
[C](libextism/#readme),
[C++](https://github.com/extism/cpp-sdk/#readme),
[OCaml](https://github.com/extism/ocaml-sdk#readme),
[Haskell](https://github.com/extism/haskell-sdk#readme),
[PHP](https://github.com/extism/php-sdk#readme),
[Elixir](https://github.com/extism/elixir-sdk#readme),
[.NET](https://github.com/extism/dotnet-sdk#readme),
[Java](https://github.com/extism/java-sdk#readme),
[Zig](https://github.com/extism/zig-sdk#readme),
[D](https://github.com/extism/d-sdk#readme),
&amp; more (others coming soon).
</div>
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk#readme), [AssemblyScript](https://github.com/extism/assemblyscript-pdk#readme), [Go](https://github.com/extism/go-pdk#readme), [C/C++](https://github.com/extism/c-pdk#readme), [Haskell](https://github.com/extism/haskell-pdk#readme), [JavaScript](https://github.com/extism/js-pdk#readme), [C#](https://github.com/extism/dotnet-pdk#readme), [F#](https://github.com/extism/dotnet-pdk#readme) and [Zig](https://github.com/extism/zig-pdk#readme).
# Overview
<p align="center">
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/210286900-39b144fd-1b26-4dd0-b7a9-2b5755bc174d.png" alt="Extism embedded SDK language support"/>
</p>
Extism is a lightweight framework for building with WebAssembly (Wasm). It
supports running Wasm code on servers, the edge, CLIs, IoT, browsers and
everything in between. Extism is designed to be "universal" in that it supports
a common interface, no matter where it runs.
Add a flexible, secure, and _bLaZiNg FaSt_ plug-in system to your project. Server, desktop, mobile, web, database -- you name it. Enable users to write and execute safe extensions to your software in **3 easy steps:**
> **Note:** One of the primary use cases for Extism is **building extensible
> software & plugins**. You want to be able to execute arbitrary, untrusted code
> from your users? Extism makes this safe and practical to do.
### 1. Import
Additionally, Extism adds some extra utilities on top of standard Wasm runtimes.
For example, we support persistent memory/module-scope variables, secure &
host-controlled HTTP without WASI, runtime limiters & timers, simpler host
function linking, and more. Extism users build:
Import an Extism Host SDK into your code as a library dependency.
- plug-in systems
- FaaS platforms
- code generators
- web applications
- & much more...
### 2. Integrate
# Run WebAssembly In Your App
Identify the place(s) in your code where some arbitrary logic should run (the plug-in!), returning your code some results.
Pick a SDK to import into your program, and refer to the documentation to get
started:
### 3. Execute
| Type | Language | Source Code | Package |
| ----------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Rust SDK | <img alt="Rust SDK" src="https://extism.org/img/sdk-languages/rust.svg" width="50px"/> | https://github.com/extism/extism/tree/main/runtime | [Crates.io](https://crates.io/crates/extism) |
| JS SDK | <img alt="JS SDK" src="https://extism.org/img/sdk-languages/js.svg" width="50px"/> | https://github.com/extism/js-sdk <br/>(supports Web, Node, Deno & Bun!) | [NPM](https://www.npmjs.com/package/@extism/extism) |
| Elixir SDK | <img alt="Elixir SDK" src="https://extism.org/img/sdk-languages/elixir.svg" width="50px"/> | https://github.com/extism/elixir-sdk | [Hex](https://hex.pm/packages/extism) |
| Go SDK | <img alt="Go SDK" src="https://extism.org/img/sdk-languages/go.svg" width="50px"/> | https://github.com/extism/go-sdk | [Go mod](https://pkg.go.dev/github.com/extism/go-sdk) |
| Haskell SDK | <img alt="Haskell SDK" src="https://extism.org/img/sdk-languages/haskell.svg" width="50px"/> | https://github.com/extism/haskell-sdk | [Hackage](https://hackage.haskell.org/package/extism) |
| 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/) |
| 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) |
| Zig SDK | <img alt="Zig SDK" src="https://extism.org/img/sdk-languages/zig.svg" width="50px"/> | https://github.com/extism/zig-sdk | N/A |
| C SDK | <img alt="C SDK" src="https://extism.org/img/sdk-languages/c.svg" width="50px"/> | https://github.com/extism/extism/tree/main/libextism | N/A |
| C++ SDK | <img alt="C++ SDK" src="https://extism.org/img/sdk-languages/cpp.svg" width="50px"/> | https://github.com/extism/cpp-sdk | N/A |
Load WebAssembly modules at any time in your app's lifetime and Extism will execute them in a secure sandbox, fully isolated from your program's memory.
# Compile WebAssembly to run in Extism Hosts
# API Status
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.
**Please note:** This project still under active development and APIs are still changing. We are aiming for a stable 1.0 release in January, 2024.
The main branch may have breaking changes until that point, but if you starting today, a 1.0.0-rcx release is the best place to start.
Pick a PDK to import into your Wasm program, and refer to the documentation to
get started:
If you experience any problems or have any questions, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know.
Our community is very responsive and happy to help get you started.
| Type | Language | Source Code | Package |
| ------------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
| 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 |
| 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#!) | 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 |
| 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 |
# Support
## Discord
If you experience any problems or have any questions, please join our
[Discord](https://extism.org/discord) and let us know. Our community is very
responsive and happy to help get you started.
## Usage
Head to the [project website](https://extism.org) for more information and docs. Also, consider reading an [overview](https://extism.org/docs/overview) of Extism and its goals & approach.
Head to the [project website](https://extism.org) for more information and docs.
Also, consider reading an [overview](https://extism.org/docs/overview) of Extism
and its goals & approach.
## Contribution
Thank you for considering a contribution to Extism, we are happy to help you make a PR or find something to work on!
Thank you for considering a contribution to Extism, we are happy to help you
make a PR or find something to work on!
The easiest way to start would be to join the [Discord](https://discord.gg/cx3usBCWnc) or open an issue on the [`extism/proposals`](https://github.com/extism/proposals) issue tracker, which can eventually become an Extism Improvement Proposal (EIP).
The easiest way to start would be to join the
[Discord](https://extism.org/discord) or open an issue on the
[`extism/proposals`](https://github.com/extism/proposals) issue tracker, which
can eventually become an Extism Improvement Proposal (EIP).
For more information, please read the
[Contributing](https://extism.org/docs/concepts/contributing) guide.
---
@@ -68,8 +114,8 @@ The easiest way to start would be to join the [Discord](https://discord.gg/cx3us
Extism is an open-source product from the team at:
<p align="left">
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/198204119-5afdebb9-a5d8-4322-bd2a-46179c8d7b24.svg"/></a>
<a href="https://dylibso.com" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/198204119-5afdebb9-a5d8-4322-bd2a-46179c8d7b24.svg"/></a>
</p>
_Reach out and tell us what you're building! We'd love to help._
_Reach out and tell us what you're building! We'd love to help:_
<a href="mailto:hello@dylibso.com">hello@dylibso.com</a>

View File

@@ -12,7 +12,9 @@ description = "Traits to make Rust types usable with Extism"
[dependencies]
anyhow = "1.0.75"
base64 = "~0.21"
bytemuck = {version = "1.14.0", 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"
serde_json = "1.0.105"
@@ -21,6 +23,6 @@ serde_json = "1.0.105"
serde = { version = "1.0.186", features = ["derive"] }
[features]
default = ["msgpack", "protobuf"]
default = ["msgpack", "prost", "raw"]
msgpack = ["rmp-serde"]
protobuf = ["prost"]
raw = ["bytemuck"]

View File

@@ -112,19 +112,19 @@ impl FromBytesOwned for Base64<String> {
/// Protobuf encoding
///
/// Allows for `prost` Protobuf messages to be used as arguments to Extism plugin calls
#[cfg(feature = "protobuf")]
#[cfg(feature = "prost")]
#[derive(Debug)]
pub struct Protobuf<T: prost::Message>(pub T);
pub struct Prost<T: prost::Message>(pub T);
#[cfg(feature = "protobuf")]
impl<T: prost::Message> From<T> for Protobuf<T> {
#[cfg(feature = "prost")]
impl<T: prost::Message> From<T> for Prost<T> {
fn from(data: T) -> Self {
Self(data)
}
}
#[cfg(feature = "protobuf")]
impl<'a, T: prost::Message> ToBytes<'a> for Protobuf<T> {
#[cfg(feature = "prost")]
impl<'a, T: prost::Message> ToBytes<'a> for Prost<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -132,9 +132,56 @@ impl<'a, T: prost::Message> ToBytes<'a> for Protobuf<T> {
}
}
#[cfg(feature = "protobuf")]
impl<T: Default + prost::Message> FromBytesOwned for Protobuf<T> {
#[cfg(feature = "prost")]
impl<T: Default + prost::Message> FromBytesOwned for Prost<T> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Protobuf(T::decode(data)?))
Ok(Prost(T::decode(data)?))
}
}
/// Protobuf encoding
///
/// Allows for `rust-protobuf` Protobuf messages to be used as arguments to Extism plugin calls
#[cfg(feature = "protobuf")]
pub struct Protobuf<T: protobuf::Message>(pub T);
#[cfg(feature = "protobuf")]
impl<'a, T: protobuf::Message> ToBytes<'a> for Protobuf<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.0.write_to_bytes()?)
}
}
#[cfg(feature = "protobuf")]
impl<T: Default + protobuf::Message> FromBytesOwned for Protobuf<T> {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
Ok(Protobuf(T::parse_from_bytes(data)?))
}
}
/// Raw does no conversion, it just copies the memory directly.
/// Note: This will only work for types that implement [bytemuck::Pod](https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html)
#[cfg(all(feature = "raw", target_endian = "little"))]
pub struct Raw<'a, T: bytemuck::Pod>(pub &'a T);
#[cfg(all(feature = "raw", target_endian = "little"))]
impl<'a, T: bytemuck::Pod> ToBytes<'a> for Raw<'a, T> {
type Bytes = &'a [u8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(bytemuck::bytes_of(self.0))
}
}
#[cfg(all(feature = "raw", target_endian = "little"))]
impl<'a, T: bytemuck::Pod> FromBytes<'a> for Raw<'a, T> {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
let x = bytemuck::try_from_bytes(data).map_err(|x| Error::msg(x.to_string()))?;
Ok(Raw(x))
}
}
#[cfg(all(feature = "raw", target_endian = "big"))]
compile_error!("The raw feature is only supported on little endian targets");

View File

@@ -98,3 +98,13 @@ impl<'a, T: FromBytes<'a>> FromBytes<'a> for std::io::Cursor<T> {
Ok(std::io::Cursor::new(T::from_bytes(data)?))
}
}
impl<'a, T: FromBytes<'a>> FromBytes<'a> for Option<T> {
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
if data.is_empty() {
return Ok(None);
}
T::from_bytes(data).map(Some)
}
}

View File

@@ -18,9 +18,15 @@ pub use encoding::{Base64, Json};
#[cfg(feature = "msgpack")]
pub use encoding::Msgpack;
#[cfg(feature = "prost")]
pub use encoding::Prost;
#[cfg(feature = "protobuf")]
pub use encoding::Protobuf;
#[cfg(all(feature = "raw", target_endian = "little"))]
pub use encoding::Raw;
pub use from_bytes::{FromBytes, FromBytesOwned};
pub use memory_handle::MemoryHandle;
pub use to_bytes::ToBytes;

View File

@@ -20,6 +20,7 @@ fn roundtrip_json() {
}
#[test]
#[cfg(feature = "msgpack")]
fn roundtrip_msgpack() {
let x = Testing {
a: "foobar".to_string(),
@@ -37,3 +38,53 @@ fn roundtrip_base64() {
let Base64(s): Base64<String> = FromBytes::from_bytes(bytes.as_bytes()).unwrap();
assert_eq!(s, "this is a test");
}
#[test]
fn rountrip_option() {
// `None` case
let e0: Option<Json<Testing>> = FromBytes::from_bytes(&[]).unwrap();
let b = e0.to_bytes().unwrap();
let e1: Option<Json<Testing>> = FromBytes::from_bytes(&b).unwrap();
assert!(e0.is_none());
assert_eq!(e0.is_none(), e1.is_none());
// `Some` case
let x = Testing {
a: "foobar".to_string(),
b: 123,
c: 456.7,
};
let bytes = Json(&x).to_bytes().unwrap();
let y: Option<Json<Testing>> = FromBytes::from_bytes(&bytes).unwrap();
let b = ToBytes::to_bytes(&y).unwrap();
let z: Option<Json<Testing>> = FromBytes::from_bytes(&b).unwrap();
assert_eq!(y.unwrap().0, z.unwrap().0);
}
#[cfg(all(feature = "raw", target_endian = "little"))]
mod tests {
use crate::*;
#[test]
fn test_raw() {
#[derive(Debug, Clone, Copy, PartialEq)]
struct TestRaw {
a: i32,
b: f64,
c: bool,
}
unsafe impl bytemuck::Pod for TestRaw {}
unsafe impl bytemuck::Zeroable for TestRaw {}
let x = TestRaw {
a: 123,
b: 45678.91011,
c: true,
};
let raw = Raw(&x).to_bytes().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);
assert!(y.is_ok());
}
}

View File

@@ -100,3 +100,14 @@ impl<'a, T: ToBytes<'a>> ToBytes<'a> for &'a T {
<T as ToBytes>::to_bytes(self)
}
}
impl<'a, T: ToBytes<'a>> ToBytes<'a> for Option<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
match self {
Some(x) => x.to_bytes().map(|x| x.as_ref().to_vec()),
None => Ok(vec![]),
}
}
}

94
flake.lock generated
View File

@@ -1,94 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1698420672,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
"owner": "nix-community",
"repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1701693815,
"narHash": "sha256-7BkrXykVWfkn6+c1EhFA3ko4MLi3gVG0p9G96PNnKTM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "09ec6a0881e1a36c29d67497693a67a16f4da573",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1701693815,
"narHash": "sha256-7BkrXykVWfkn6+c1EhFA3ko4MLi3gVG0p9G96PNnKTM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "09ec6a0881e1a36c29d67497693a67a16f4da573",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"naersk": "naersk",
"nixpkgs": "nixpkgs_2"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,32 +0,0 @@
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { self, flake-utils, naersk, nixpkgs }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = (import nixpkgs) {
inherit system;
};
naersk' = pkgs.callPackage naersk {};
in {
# For `nix build` & `nix run`:
defaultPackage = naersk'.buildPackage {
name = "libextism";
src = ./.;
copyLibs = true;
postInstall = "mkdir -p $out/include; cp runtime/extism.h $out/include";
};
# For `nix develop`:
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ rustc cargo clippy gnumake ];
};
}
);
}

7
kernel/Cargo.lock generated
View File

@@ -1,7 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "extism-runtime-kernel"
version = "0.1.0"

View File

@@ -5,6 +5,9 @@ edition = "2021"
[dependencies]
[dev-dependencies]
wasm-bindgen-test = "0.3.39"
[features]
default = ["bounds-checking"]
bounds-checking = []

View File

@@ -3,7 +3,7 @@
pub use extism_runtime_kernel::*;
#[cfg(target_arch = "wasm32")]
#[cfg(all(target_arch = "wasm32", not(test)))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
core::arch::wasm32::unreachable()

View File

@@ -39,7 +39,7 @@
use core::sync::atomic::*;
pub type Pointer = u64;
pub type Length = u64;
pub type Handle = u64;
/// WebAssembly page size
const PAGE_SIZE: usize = 65536;
@@ -81,13 +81,13 @@ pub struct MemoryRoot {
/// Offset of error block
pub error: AtomicU64,
/// Input position in memory
pub input_offset: Pointer,
pub input_offset: Handle,
/// Input length
pub input_length: Length,
pub input_length: u64,
/// Output position in memory
pub output_offset: Pointer,
/// Output length
pub output_length: Length,
pub output_length: u64,
/// A pointer to the start of the first block
pub blocks: [MemoryBlock; 0],
}
@@ -139,7 +139,9 @@ impl MemoryRoot {
}
// Ensure that at least one page is allocated to store the `MemoryRoot` data
if core::arch::wasm32::memory_size(0) == 0 && core::arch::wasm32::memory_grow(0, 1) == usize::MAX {
if core::arch::wasm32::memory_size(0) == 0
&& core::arch::wasm32::memory_grow(0, 1) == usize::MAX
{
core::arch::wasm32::unreachable()
}
@@ -168,12 +170,15 @@ impl MemoryRoot {
/// Resets the position of the allocator and zeroes out all allocations
pub unsafe fn reset(&mut self) {
// Clear allocated data
let self_position = self.position.fetch_and(0, Ordering::SeqCst);
core::ptr::write_bytes(
self.blocks.as_mut_ptr() as *mut u8,
0,
self.length.load(Ordering::Acquire) as usize,
MemoryStatus::Unused as u8,
self_position as usize,
);
self.position.store(0, Ordering::Release);
// Clear extism runtime metadata
self.error.store(0, Ordering::Release);
self.input_offset = 0;
self.input_length = 0;
@@ -201,7 +206,7 @@ impl MemoryRoot {
// is used to avoid loading the allocators position more than once when performing an allocation.
unsafe fn find_free_block(
&mut self,
length: Length,
length: u64,
self_position: u64,
) -> Option<&'static mut MemoryBlock> {
// Get the first block
@@ -223,7 +228,7 @@ impl MemoryRoot {
if status == MemoryStatus::Free as u8 && b.size >= length as usize {
// Split block if there is too much excess
if b.size - length as usize >= 128 {
b.size -= length as usize;
b.size -= length as usize + core::mem::size_of::<MemoryBlock>();
b.used = 0;
let block1 = b.data.as_mut_ptr().add(b.size) as *mut MemoryBlock;
@@ -247,7 +252,7 @@ impl MemoryRoot {
/// Create a new `MemoryBlock`, when `Some(block)` is returned, `block` will contain at least enough room for `length` bytes
/// but may be as large as `length` + `BLOCK_SPLIT_SIZE` bytes. When `None` is returned the allocation has failed.
pub unsafe fn alloc(&mut self, length: Length) -> Option<&'static mut MemoryBlock> {
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);
@@ -265,12 +270,13 @@ impl MemoryRoot {
// Get the number of bytes available
let mem_left = self_length - self_position - core::mem::size_of::<MemoryRoot>() as u64;
let length_with_block = length + core::mem::size_of::<MemoryBlock>() as u64;
// When the allocation is larger than the number of bytes available
// we will need to try to grow the memory
if length >= mem_left {
if length_with_block >= mem_left {
// Calculate the number of pages needed to cover the remaining bytes
let npages = num_pages(length - mem_left);
let npages = num_pages(length_with_block - mem_left);
let x = core::arch::wasm32::memory_grow(0, npages);
if x == usize::MAX {
return None;
@@ -301,9 +307,26 @@ impl MemoryRoot {
if !Self::pointer_in_bounds_fast(offs) {
return None;
}
let ptr = offs - core::mem::size_of::<MemoryBlock>() as u64;
let ptr = ptr as *mut MemoryBlock;
Some(&mut *ptr)
// Get the first block
let mut block = self.blocks.as_mut_ptr();
// Only loop while the block pointer is less then the current position
while (block as u64) < self.blocks.as_ptr() as u64 + offs {
let b = &mut *block;
// Get the block status, this lets us know if we are able to re-use it
let status = b.status.load(Ordering::Acquire);
if status == MemoryStatus::Active as u8 && b.data.as_ptr() as Pointer == offs {
return Some(b);
}
// Get the next block
block = b.next_ptr();
}
None
}
}
@@ -328,21 +351,21 @@ impl MemoryBlock {
/// Allocate a block of memory and return the offset
#[no_mangle]
pub unsafe fn alloc(n: Length) -> Pointer {
pub unsafe fn alloc(n: u64) -> Handle {
if n == 0 {
return 0;
}
let region = MemoryRoot::new();
let block = region.alloc(n);
match block {
Some(block) => block.data.as_mut_ptr() as Pointer,
Some(block) => block.data.as_mut_ptr() as Handle,
None => 0,
}
}
/// Free allocated memory
#[no_mangle]
pub unsafe fn free(p: Pointer) {
pub unsafe fn free(p: Handle) {
if p == 0 {
return;
}
@@ -360,13 +383,42 @@ pub unsafe fn free(p: Pointer) {
}
/// Get the length of an allocated memory block
///
/// Note: this should only be called on memory handles returned
/// by a call to `alloc` - it will return garbage on invalid offsets
#[no_mangle]
pub unsafe fn length(p: Pointer) -> Length {
pub unsafe fn length_unsafe(p: Handle) -> u64 {
if p == 0 {
return 0;
}
if !MemoryRoot::pointer_in_bounds_fast(p) {
return 0;
}
let ptr = p - core::mem::size_of::<MemoryBlock>() as u64;
let block = &mut *(ptr as *mut MemoryBlock);
// Simplest sanity check to verify the pointer is a block
if block.status.load(Ordering::Acquire) != MemoryStatus::Active as u8 {
return 0;
}
block.used as u64
}
/// Get the length but returns 0 if the offset is not a valid handle.
///
/// Note: this function walks each node in the allocations list, which ensures correctness, but is also
/// slow
#[no_mangle]
pub unsafe fn length(p: Pointer) -> u64 {
if p == 0 {
return 0;
}
if let Some(block) = MemoryRoot::new().find_block(p) {
block.used as Length
block.used as u64
} else {
0
}
@@ -394,24 +446,24 @@ pub unsafe fn load_u64(p: Pointer) -> u64 {
/// Load a byte from the input data
#[no_mangle]
pub unsafe fn input_load_u8(p: Pointer) -> u8 {
pub unsafe fn input_load_u8(offset: u64) -> u8 {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
if p >= root.input_length {
if offset >= root.input_length {
return 0;
}
*((root.input_offset + p) as *mut u8)
*((root.input_offset + offset) as *mut u8)
}
/// Load a u64 from the input data
#[no_mangle]
pub unsafe fn input_load_u64(p: Pointer) -> u64 {
pub unsafe fn input_load_u64(offset: u64) -> u64 {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
if p + core::mem::size_of::<u64>() as Pointer > root.input_length {
if offset + core::mem::size_of::<u64>() as u64 > root.input_length {
return 0;
}
*((root.input_offset + p) as *mut u64)
*((root.input_offset + offset) as *mut u64)
}
/// Write a byte in Extism-managed memory
@@ -435,22 +487,24 @@ 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)
#[no_mangle]
pub unsafe fn input_set(p: Pointer, len: Length) {
pub unsafe fn input_set(h: Handle, len: u64) {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
{
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
if !root.pointer_in_bounds(h) || !root.pointer_in_bounds(h + len - 1) {
return;
}
}
root.input_offset = p;
root.input_offset = h;
root.input_length = len;
}
/// Set the range of the output data in memory
#[no_mangle]
pub unsafe fn output_set(p: Pointer, len: Length) {
pub unsafe fn output_set(p: Pointer, len: u64) {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
{
@@ -464,25 +518,25 @@ pub unsafe fn output_set(p: Pointer, len: Length) {
/// Get the input length
#[no_mangle]
pub fn input_length() -> Length {
pub fn input_length() -> u64 {
unsafe { MemoryRoot::new().input_length }
}
/// Get the input offset in Exitsm-managed memory
#[no_mangle]
pub fn input_offset() -> Length {
pub fn input_offset() -> Handle {
unsafe { MemoryRoot::new().input_offset }
}
/// Get the output length
#[no_mangle]
pub fn output_length() -> Length {
pub fn output_length() -> u64 {
unsafe { MemoryRoot::new().output_length }
}
/// Get the output offset in Extism-managed memory
#[no_mangle]
pub unsafe fn output_offset() -> Length {
pub unsafe fn output_offset() -> Pointer {
MemoryRoot::new().output_offset
}
@@ -494,30 +548,75 @@ pub unsafe fn reset() {
/// Set the error message offset
#[no_mangle]
pub unsafe fn error_set(ptr: Pointer) {
pub unsafe fn error_set(h: Handle) {
let root = MemoryRoot::new();
// Allow ERROR to be set to 0
if ptr == 0 {
root.error.store(ptr, Ordering::SeqCst);
if h == 0 {
root.error.store(h, Ordering::SeqCst);
return;
}
#[cfg(feature = "bounds-checking")]
if !root.pointer_in_bounds(ptr) {
if !root.pointer_in_bounds(h) {
return;
}
root.error.store(ptr, Ordering::SeqCst);
root.error.store(h, Ordering::SeqCst);
}
/// Get the error message offset, if it's `0` then no error has been set
#[no_mangle]
pub unsafe fn error_get() -> Pointer {
pub unsafe fn error_get() -> Handle {
MemoryRoot::new().error.load(Ordering::SeqCst)
}
/// Get the position of the allocator, this can be used as an indication of how many bytes are currently in-use
#[no_mangle]
pub unsafe fn memory_bytes() -> Length {
pub unsafe fn memory_bytes() -> u64 {
MemoryRoot::new().length.load(Ordering::Acquire)
}
#[cfg(test)]
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);
}
}
}

5
kernel/test.sh Executable file
View File

@@ -0,0 +1,5 @@
# install wasm-bindgen-cli to get wasm-bindgen-runner if it is not installed yet
which wasm-bindgen-test-runner 1>/dev/null || cargo install -f wasm-bindgen-cli
# run tests with the wasm-bindgen-runner
CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner cargo test --release --target=wasm32-unknown-unknown

View File

@@ -90,6 +90,14 @@ 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.
```c
bool extism_plugin_reset(ExtismPlugin *plugin);
```
### `extism_log_file`
Set log file and level.

View File

@@ -1,9 +1,11 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Manifest",
"description": "The `Manifest` type is used to configure the runtime and specify how to load modules.",
"type": "object",
"properties": {
"allowed_hosts": {
"description": "Specifies which hosts may be accessed via HTTP, if this is empty then no hosts may be accessed. Wildcards may be used.",
"default": null,
"type": [
"array",
@@ -14,6 +16,7 @@
}
},
"allowed_paths": {
"description": "Specifies which paths should be made available on disk when using WASI. This is a mapping from 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",
"default": null,
"type": [
"object",
@@ -24,6 +27,7 @@
}
},
"config": {
"description": "Config values are made accessible using the PDK `extism_config_get` function",
"default": {},
"type": "object",
"additionalProperties": {
@@ -31,6 +35,7 @@
}
},
"memory": {
"description": "Memory options",
"default": {
"max_pages": null
},
@@ -41,6 +46,8 @@
]
},
"timeout_ms": {
"description": "The plugin timeout, by default this is set to 30s",
"default": null,
"type": [
"integer",
"null"
@@ -49,6 +56,7 @@
"minimum": 0.0
},
"wasm": {
"description": "WebAssembly modules, the `main` module should be named `main` or listed last",
"default": [],
"type": "array",
"items": {
@@ -56,11 +64,14 @@
}
}
},
"additionalProperties": false,
"definitions": {
"MemoryOptions": {
"description": "Configure memory settings",
"type": "object",
"properties": {
"max_pages": {
"description": "The max number of WebAssembly pages that should be allocated",
"type": [
"integer",
"null"
@@ -68,23 +79,28 @@
"format": "uint32",
"minimum": 0.0
}
}
},
"additionalProperties": false
},
"Wasm": {
"description": "The `Wasm` type specifies how to access a WebAssembly module",
"anyOf": [
{
"description": "From disk",
"type": "object",
"required": [
"path"
],
"properties": {
"hash": {
"description": "Module hash, if the data loaded from disk or via HTTP doesn't match an error will be raised",
"type": [
"string",
"null"
]
},
"name": {
"description": "Module name, this is used by Extism to determine which is the `main` module",
"type": [
"string",
"null"
@@ -93,45 +109,72 @@
"path": {
"type": "string"
}
}
},
"additionalProperties": false
},
{
"description": "From memory",
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"type": "string",
"format": "string"
"type": [
"string",
"object"
],
"required": [
"len",
"ptr"
],
"properties": {
"len": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"ptr": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"hash": {
"description": "Module hash, if the data loaded from disk or via HTTP doesn't match an error will be raised",
"type": [
"string",
"null"
]
},
"name": {
"description": "Module name, this is used by Extism to determine which is the `main` module",
"type": [
"string",
"null"
]
}
}
},
"additionalProperties": false
},
{
"description": "Via HTTP",
"type": "object",
"required": [
"url"
],
"properties": {
"hash": {
"description": "Module hash, if the data loaded from disk or via HTTP doesn't match an error will be raised",
"type": [
"string",
"null"
]
},
"headers": {
"description": "Request headers",
"default": {},
"type": "object",
"additionalProperties": {
@@ -139,21 +182,25 @@
}
},
"method": {
"description": "Request method",
"type": [
"string",
"null"
]
},
"name": {
"description": "Module name, this is used by Extism to determine which is the `main` module",
"type": [
"string",
"null"
]
},
"url": {
"description": "The request URL",
"type": "string"
}
}
},
"additionalProperties": false
}
]
}

View File

@@ -111,8 +111,8 @@ pub enum Wasm {
/// From memory
Data {
#[serde(with = "base64")]
#[cfg_attr(feature = "json_schema", schemars(schema_with = "base64_schema"))]
#[serde(with = "wasmdata")]
#[cfg_attr(feature = "json_schema", schemars(schema_with = "wasmdata_schema"))]
data: Vec<u8>,
#[serde(flatten)]
meta: WasmMetadata,
@@ -195,11 +195,25 @@ impl Wasm {
}
}
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
struct DataPtrLength {
ptr: u64,
len: u64,
}
#[cfg(feature = "json_schema")]
fn base64_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
fn wasmdata_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
use schemars::{schema::SchemaObject, JsonSchema};
let mut schema: SchemaObject = <String>::json_schema(gen).into();
schema.format = Some("string".to_owned());
let objschema: SchemaObject = <DataPtrLength>::json_schema(gen).into();
let types = schemars::schema::SingleOrVec::<schemars::schema::InstanceType>::Vec(vec![
schemars::schema::InstanceType::String,
schemars::schema::InstanceType::Object,
]);
schema.instance_type = Some(types);
schema.object = objschema.object;
schema.into()
}
@@ -231,20 +245,15 @@ pub struct Manifest {
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
/// The plugin timeout, by default this is set to 30s
#[serde(default = "default_timeout")]
#[serde(default)]
pub timeout_ms: Option<u64>,
}
fn default_timeout() -> Option<u64> {
Some(30000)
}
impl Manifest {
/// Create a new manifest
pub fn new(wasm: impl IntoIterator<Item = impl Into<Wasm>>) -> Manifest {
Manifest {
wasm: wasm.into_iter().map(|x| x.into()).collect(),
timeout_ms: default_timeout(),
..Default::default()
}
}
@@ -340,10 +349,12 @@ impl Manifest {
}
}
mod base64 {
mod wasmdata {
use crate::DataPtrLength;
use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Serialize};
use serde::{Deserializer, Serializer};
use std::slice;
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
let base64 = general_purpose::STANDARD.encode(v.as_slice());
@@ -351,10 +362,22 @@ mod base64 {
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
let base64 = String::deserialize(d)?;
general_purpose::STANDARD
.decode(base64.as_bytes())
.map_err(serde::de::Error::custom)
#[derive(Deserialize)]
#[serde(untagged)]
enum WasmDataTypes {
String(String),
DataPtrLength(DataPtrLength),
}
Ok(match WasmDataTypes::deserialize(d)? {
WasmDataTypes::String(string) => general_purpose::STANDARD
.decode(string.as_bytes())
.map_err(serde::de::Error::custom)?,
WasmDataTypes::DataPtrLength(ptrlen) => {
let slice =
unsafe { slice::from_raw_parts(ptrlen.ptr as *const u8, ptrlen.len as usize) };
slice.to_vec()
}
})
}
}

View File

@@ -9,8 +9,8 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = ">= 14.0.0, < 16.0.0"
wasmtime-wasi = ">= 14.0.0, < 16.0.0"
wasmtime = ">= 14.0.0, < 17.0.0"
wasmtime-wasi = ">= 14.0.0, < 17.0.0"
anyhow = "1"
serde = {version = "1", features = ["derive"]}
serde_json = "1"
@@ -33,10 +33,12 @@ register-filesystem = [] # enables wasm to be loaded from disk
http = ["ureq"] # enables extism_http_request
[build-dependencies]
cbindgen = "0.26"
cbindgen = { version = "0.26", default-features = false }
[dev-dependencies]
criterion = "0.5.1"
quickcheck = "1"
rand = "0.8.5"
[[bench]]
name = "bench"

View File

@@ -12,7 +12,7 @@ To use the `extism` crate, you can add it to your Cargo file:
```toml
[dependencies]
extism = "^1.0.0-rc3"
extism = "1.0.0"
```
## Environment variables

View File

@@ -118,9 +118,7 @@ pub fn echo(c: &mut Criterion) {
pub fn reflect(c: &mut Criterion) {
let mut g = c.benchmark_group("reflect");
g.sample_size(500);
g.noise_threshold(1.0);
g.significance_level(0.2);
let mut plugin = PluginBuilder::new(REFLECT)
.with_wasi(true)
.with_function(
@@ -132,22 +130,23 @@ pub fn reflect(c: &mut Criterion) {
)
.build()
.unwrap();
for (i, elements) in [
b"a".repeat(65536),
b"a".repeat(65536 * 10),
b"a".repeat(65536 * 100),
b"a".repeat(65536),
]
.iter()
.enumerate()
{
g.throughput(criterion::Throughput::Bytes(elements.len() as u64));
g.bench_with_input(
format!("reflect {} bytes", 10u32.pow(i as u32) * 65536),
format!("{i}: reflect {} bytes", elements.len()),
elements,
|b, elems| {
b.iter(|| {
assert_eq!(elems, plugin.call::<_, &[u8]>("reflect", &elems).unwrap());
// plugin.reset().unwrap();
});
},
);

View File

@@ -97,6 +97,11 @@ typedef void (*ExtismFunctionType)(ExtismCurrentPlugin *plugin,
ExtismSize n_outputs,
void *data);
/**
* Log drain callback
*/
typedef void (*ExtismLogDrainFunctionType)(const char *data, ExtismSize size);
#ifdef __cplusplus
@@ -264,7 +269,12 @@ bool extism_log_custom(const char *log_level);
* Calls the provided callback function for each buffered log line.
* This is only needed when `extism_log_custom` is used.
*/
void extism_log_drain(void (*handler)(const char*, uintptr_t));
void extism_log_drain(ExtismLogDrainFunctionType handler);
/**
* Reset the Extism runtime, this will invalidate all allocated memory
*/
bool extism_plugin_reset(ExtismPlugin *plugin);
/**
* Get the Extism version string

View File

@@ -87,18 +87,27 @@ impl CurrentPlugin {
}
/// Access memory bytes as `str`
pub fn memory_str(&mut self, handle: MemoryHandle) -> Result<&mut str, Error> {
let bytes = self.memory_bytes(handle)?;
pub fn memory_str_mut(&mut self, handle: MemoryHandle) -> Result<&mut str, Error> {
let bytes = self.memory_bytes_mut(handle)?;
let s = std::str::from_utf8_mut(bytes)?;
Ok(s)
}
pub fn memory_str(&mut self, handle: MemoryHandle) -> Result<&str, Error> {
let bytes = self.memory_bytes(handle)?;
let s = std::str::from_utf8(bytes)?;
Ok(s)
}
/// Allocate a handle large enough for the encoded Rust type and copy it into Extism memory
pub fn memory_new<'a, T: ToBytes<'a>>(&mut self, t: T) -> Result<MemoryHandle, Error> {
let data = t.to_bytes()?;
let data = data.as_ref();
if data.is_empty() {
return Ok(MemoryHandle::null());
}
let handle = self.memory_alloc(data.len() as u64)?;
let bytes = self.memory_bytes(handle)?;
let bytes = self.memory_bytes_mut(handle)?;
bytes.copy_from_slice(data.as_ref());
Ok(handle)
}
@@ -144,7 +153,7 @@ impl CurrentPlugin {
Ok(())
}
pub fn memory_bytes(&mut self, handle: MemoryHandle) -> Result<&mut [u8], Error> {
pub fn memory_bytes_mut(&mut self, handle: MemoryHandle) -> Result<&mut [u8], Error> {
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();
@@ -158,6 +167,20 @@ impl CurrentPlugin {
anyhow::bail!("{} unable to locate extism memory", self.id)
}
pub fn memory_bytes(&mut self, handle: MemoryHandle) -> Result<&[u8], Error> {
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) };
if ptr.is_null() {
return Ok(&[]);
}
return Ok(unsafe { std::slice::from_raw_parts(ptr, handle.len()) });
}
anyhow::bail!("{} unable to locate extism memory", self.id)
}
pub fn memory_alloc(&mut self, n: u64) -> Result<MemoryHandle, Error> {
if n == 0 {
return Ok(MemoryHandle {
@@ -223,6 +246,26 @@ impl CurrentPlugin {
Ok(len)
}
pub fn memory_length_unsafe(&mut self, offs: u64) -> Result<u64, Error> {
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") {
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",)
}
let len = output[0].unwrap_i64() as u64;
trace!(
plugin = self.id.to_string(),
"memory_length_unsafe({}) = {}",
offs,
len
);
Ok(len)
}
/// Access a plugin's variables
pub fn vars(&self) -> &std::collections::BTreeMap<String, Vec<u8>> {
&self.vars

Binary file not shown.

View File

@@ -117,10 +117,17 @@ pub(crate) fn load(
match input {
WasmInput::Data(data) => {
let has_magic = data.len() >= 4 && data[0..4] == WASM_MAGIC;
let is_wat = data.starts_with(b"(module") || data.starts_with(b";;");
let s = std::str::from_utf8(&data);
let is_wat = s.is_ok_and(|s| {
let s = s.trim_start();
let starts_with_module = s.len() > 2
&& data[0] == b'(' // First character is `(`
&& s[1..].trim_start().starts_with("module"); // Then `module` (after any whitespace)
starts_with_module || s.starts_with(";;") || s.starts_with("(;")
});
if !has_magic && !is_wat {
trace!("Loading manifest");
if let Ok(s) = std::str::from_utf8(&data) {
if let Ok(s) = s {
let t = if let Ok(t) = toml::from_str::<extism_manifest::Manifest>(s) {
trace!("Manifest is TOML");
modules(engine, &t, &mut mods)?;

View File

@@ -74,7 +74,7 @@ pub struct Plugin {
/// Set to `true` when de-initializarion may have occured (i.e.a call to `_start`),
/// in this case we need to re-initialize the entire module.
pub(crate) needs_reset: bool,
pub(crate) store_needs_reset: bool,
pub(crate) debug_options: DebugOptions,
}
@@ -303,7 +303,7 @@ impl Plugin {
cancel_handle: CancelHandle { id, timer_tx },
instantiations: 0,
output: Output::default(),
needs_reset: false,
store_needs_reset: false,
debug_options,
_functions: imports,
};
@@ -324,7 +324,7 @@ impl Plugin {
&mut self,
instance_lock: &mut std::sync::MutexGuard<Option<Instance>>,
) -> Result<(), Error> {
if self.instantiations > 100 {
if self.store_needs_reset {
let engine = self.store.engine().clone();
let internal = self.current_plugin_mut();
self.store = Store::new(
@@ -355,9 +355,9 @@ impl Plugin {
}
self.instantiations = 0;
self.instance_pre = self.linker.instantiate_pre(main)?;
**instance_lock = None;
self.store_needs_reset = false;
}
**instance_lock = None;
Ok(())
}
@@ -428,12 +428,7 @@ impl Plugin {
let bytes = unsafe { std::slice::from_raw_parts(input, len) };
debug!(plugin = &id, "input size: {}", bytes.len());
if let Some(f) = self.linker.get(&mut self.store, EXTISM_ENV_MODULE, "reset") {
f.into_func().unwrap().call(&mut self.store, &[], &mut [])?;
} else {
error!(plugin = &id, "call to extism:host/env::reset failed");
}
self.reset()?;
let handle = self.current_plugin_mut().memory_new(bytes)?;
if let Some(f) = self
@@ -450,6 +445,19 @@ impl Plugin {
Ok(())
}
/// Reset Extism runtime, this will invalidate all allocated memory
pub fn reset(&mut self) -> Result<(), Error> {
let id = self.id.to_string();
if let Some(f) = self.linker.get(&mut self.store, EXTISM_ENV_MODULE, "reset") {
f.into_func().unwrap().call(&mut self.store, &[], &mut [])?;
} else {
error!(plugin = &id, "call to extism:host/env::reset failed");
}
Ok(())
}
/// Determine if wasi is enabled
pub fn has_wasi(&self) -> bool {
self.current_plugin().wasi.is_some()
@@ -615,14 +623,11 @@ impl Plugin {
let name = name.as_ref();
let input = input.as_ref();
if self.needs_reset {
if let Err(e) = self.reset_store(lock) {
error!(
plugin = self.id.to_string(),
"call to Plugin::reset_store failed: {e:?}"
);
}
self.needs_reset = false;
if let Err(e) = self.reset_store(lock) {
error!(
plugin = self.id.to_string(),
"call to Plugin::reset_store failed: {e:?}"
);
}
self.instantiate(lock).map_err(|e| (e, -1))?;
@@ -667,7 +672,7 @@ impl Plugin {
self.store
.epoch_deadline_callback(|_| Ok(UpdateDeadline::Continue(1)));
let _ = self.timer_tx.send(TimerAction::Stop { id: self.id });
self.needs_reset = name == "_start";
self.store_needs_reset = name == "_start";
// Get extism error
self.get_output_after_call().map_err(|x| (x, -1))?;
@@ -819,6 +824,24 @@ impl Plugin {
.and_then(move |_| self.output())
}
/// Similar to `Plugin::call`, but returns the Extism error code along with the
/// `Error`. It is assumed if `Ok(_)` is returned that the error code was `0`.
///
/// All Extism plugin calls return an error code, `Plugin::call` consumes the error code,
/// while `Plugin::call_get_error_code` preserves it - this function should only be used
/// when you need to inspect the actual return value of a plugin function when it fails.
pub fn call_get_error_code<'a, 'b, T: ToBytes<'a>, U: FromBytes<'b>>(
&'b mut self,
name: impl AsRef<str>,
input: T,
) -> Result<U, (Error, i32)> {
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)
.and_then(move |_| self.output().map_err(|e| (e, -1)))
}
/// Get a `CancelHandle`, which can be used from another thread to cancel a running plugin
pub fn cancel_handle(&self) -> CancelHandle {
self.cancel_handle.clone()

View File

@@ -38,6 +38,9 @@ pub type ExtismFunctionType = extern "C" fn(
data: *mut std::ffi::c_void,
);
/// Log drain callback
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
impl From<&wasmtime::Val> for ExtismVal {
fn from(value: &wasmtime::Val) -> Self {
match value.ty() {
@@ -667,11 +670,11 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
#[no_mangle]
/// 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: extern "C" fn(*const std::ffi::c_char, usize)) {
pub unsafe extern "C" fn extism_log_drain(handler: ExtismLogDrainFunctionType) {
if let Some(buf) = &mut LOG_BUFFER {
if let Ok(mut buf) = buf.buffer.lock() {
for (line, len) in buf.drain(..) {
handler(line.as_ptr(), len);
handler(line.as_ptr(), len as u64);
}
}
}
@@ -702,6 +705,30 @@ impl std::io::Write for LogBuffer {
}
}
/// Reset the Extism runtime, this will invalidate all allocated memory
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_reset(plugin: *mut Plugin) -> bool {
let plugin = &mut *plugin;
if let Err(e) = plugin.reset() {
error!(
plugin = plugin.id.to_string(),
"unable to reset plugin: {}",
e.to_string()
);
if let Err(e) = plugin.current_plugin_mut().set_error(e.to_string()) {
error!(
plugin = plugin.id.to_string(),
"unable to set error after failed plugin reset: {}",
e.to_string()
);
}
false
} else {
true
}
}
/// Get the Extism version string
#[no_mangle]
pub unsafe extern "C" fn extism_version() -> *const c_char {

View File

@@ -1,4 +1,5 @@
use crate::*;
use quickcheck::*;
const KERNEL: &[u8] = include_bytes!("../extism-runtime.wasm");
@@ -22,6 +23,20 @@ fn extism_length<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance,
out[0].unwrap_i64() as 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")
.unwrap()
.call(&mut store, &[Val::I64(p as i64)], out)
.unwrap();
out[0].unwrap_i64() as u64
}
fn extism_load_u8<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u8 {
let out = &mut [Val::I32(0)];
instance
@@ -173,6 +188,7 @@ fn test_kernel_allocations() {
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);
extism_free(&mut store, instance, p);
// 2 bytes
@@ -180,24 +196,31 @@ fn test_kernel_allocations() {
assert!(x > 0);
assert!(x != p);
assert_eq!(extism_length(&mut store, instance, x), 2);
assert_eq!(extism_length_unsafe(&mut store, instance, x), 2);
extism_free(&mut store, instance, x);
for i in 0..64 {
let p = extism_alloc(&mut store, instance, 64 - i);
assert!(p > 0);
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
@@ -210,7 +233,7 @@ fn test_kernel_allocations() {
let r = extism_alloc(&mut store, instance, 128);
assert!(p <= r && r < p + 512);
assert!(r > p);
assert_eq!(extism_length(&mut store, instance, q), 128);
assert_eq!(extism_length(&mut store, instance, r), 128);
extism_free(&mut store, instance, q);
// 100 pages
@@ -297,3 +320,141 @@ fn test_load_input() {
// Out of bounds should return 0
assert_eq!(extism_input_load_u64(&mut store, instance, 123457), 0);
}
#[test]
fn test_failed_quickcheck1() {
let (mut store, mut instance) = init_kernel_test();
let allocs = [
20622, 23162, 58594, 32421, 25928, 44611, 26318, 24455, 5798, 60202, 42126, 64928, 57832,
50888, 63256, 37562, 46334, 47985, 60836, 28132, 65535, 37800, 33150, 48768, 38457, 57249,
5734, 58587, 26294, 26653, 24519, 1,
];
extism_reset(&mut store, &mut instance);
for a in allocs {
println!("Alloc: {a}");
let n = extism_alloc(&mut store, &mut instance, a);
if n == 0 {
continue;
}
assert_eq!(a, extism_length(&mut store, &mut instance, n));
}
}
quickcheck! {
fn check_alloc(amounts: Vec<u16>) -> bool {
let (mut store, mut instance) = init_kernel_test();
let instance = &mut instance;
for a in amounts {
let ptr = extism_alloc(&mut store, instance, a as u64);
if ptr == 0 || ptr == u64::MAX {
continue
}
if extism_length(&mut store, instance, ptr) != a as u64 {
return false
}
}
true
}
}
quickcheck! {
fn check_large_alloc(amounts: Vec<u32>) -> bool {
let (mut store, mut instance) = init_kernel_test();
let instance = &mut instance;
for a in amounts {
let ptr = extism_alloc(&mut store, instance, a as u64);
if ptr == 0 || ptr == u64::MAX {
continue
}
if extism_length(&mut store, instance, ptr) != a as u64 {
return false
}
}
true
}
}
quickcheck! {
fn check_alloc_with_frees(amounts: Vec<u16>) -> bool {
let (mut store, mut instance) = init_kernel_test();
let instance = &mut instance;
let mut prev = 0;
for a in amounts {
let ptr = extism_alloc(&mut store, instance, a as u64);
if ptr == 0 {
continue
}
if extism_length(&mut store, instance, ptr) != a as u64 {
return false
}
if a % 2 == 0 {
extism_free(&mut store, instance, ptr);
} else if a % 3 == 0 {
extism_free(&mut store, instance, prev);
}
prev = ptr;
}
true
}
}
quickcheck! {
fn check_large_alloc_with_frees(amounts: Vec<u32>) -> bool {
let (mut store, mut instance) = init_kernel_test();
let instance = &mut instance;
let mut prev = 0;
for a in amounts {
let ptr = extism_alloc(&mut store, instance, a as u64);
if ptr == 0 || ptr == u64::MAX {
continue
}
if extism_length(&mut store, instance, ptr) != a as u64 {
return false
}
if a % 2 == 0 {
extism_free(&mut store, instance, ptr);
} else if a % 3 == 0 {
extism_free(&mut store, instance, prev);
}
prev = ptr;
}
true
}
}
quickcheck! {
fn check_alloc_with_load_and_store(amounts: Vec<u16>) -> bool {
use rand::Rng;
let mut rng = rand::thread_rng();
let (mut store, mut instance) = init_kernel_test();
let instance = &mut instance;
for a in amounts {
let ptr = extism_alloc(&mut store, instance, a as u64);
if ptr == 0 || ptr == u64::MAX {
continue
}
if extism_length(&mut store, instance, ptr) != a as u64 {
return false
}
for _ in 0..16 {
let i = rng.gen_range(ptr..ptr+a as u64);
extism_store_u8(&mut store, instance, i, i as u8);
if extism_load_u8(&mut store, instance, i as u64) != i as u8 {
return false
}
}
}
true
}
}

View File

@@ -39,11 +39,12 @@ pub struct Count {
#[test]
fn it_works() {
tracing_subscriber::fmt()
let log = tracing_subscriber::fmt()
.with_ansi(false)
.with_env_filter("extism=debug")
.with_writer(std::fs::File::create("test.log").unwrap())
.init();
.try_init()
.is_ok();
let wasm_start = Instant::now();
@@ -143,8 +144,10 @@ fn it_works() {
println!("wasm function call (avg, N = {}): {:?}", num_tests, avg);
// Check that log file was written to
let meta = std::fs::metadata("test.log").unwrap();
assert!(meta.len() > 0);
if log {
let meta = std::fs::metadata("test.log").unwrap();
assert!(meta.len() > 0);
}
}
#[test]
@@ -283,7 +286,7 @@ fn test_multiple_instantiations() {
#[test]
fn test_globals() {
let mut plugin = Plugin::new(WASM_GLOBALS, [], true).unwrap();
for i in 0..1000 {
for i in 0..100000 {
let Json(count) = plugin.call::<_, Json<Count>>("globals", "").unwrap();
assert_eq!(count.count, i);
}
@@ -572,3 +575,21 @@ fn test_disable_cache() {
assert!(t < t1);
}
#[test]
fn test_manifest_ptr_len() {
let manifest = serde_json::json!({
"wasm" : [
{
"data" : {
"ptr" : WASM_NO_FUNCTIONS.as_ptr() as u64,
"len" : WASM_NO_FUNCTIONS.len()
}
}
]
});
let mut plugin = Plugin::new(manifest.to_string().as_bytes(), [], true).unwrap();
let output = plugin.call("count_vowels", "abc123").unwrap();
let count: serde_json::Value = serde_json::from_slice(output).unwrap();
assert_eq!(count.get("count").unwrap().as_i64().unwrap(), 1);
}