Compare commits

..

40 Commits
v1.1.0 ... owi

Author SHA1 Message Date
zach
24622cd511 wip 2024-06-14 18:52:50 -07:00
zach
00f9155b9e feat: switch to owi conc, avoid needing wasm2wat 2024-06-14 18:46:27 -07:00
zach
7c770e2f1c feat(kernel): add proof for error handling 2024-06-14 18:46:27 -07:00
zach
33ad239c50 wip: reuse proof 2024-06-14 18:46:27 -07:00
zach
a62e37c21b cleanup: reduce the size of the allocation used in load_store example 2024-06-14 18:46:27 -07:00
zach
20ee6620c6 cleanup: improve verification 2024-06-14 18:46:27 -07:00
zach
db0b04696c cleanup: move proofs to examples directory 2024-06-14 18:46:27 -07:00
zach
6cc22bf2de cleanup: use new names from owi 2024-06-14 18:46:11 -07:00
zach
52d3c4ffd6 cleanup: add some comments to verification tests 2024-06-14 18:46:11 -07:00
zach
b96b28231f cleanup: ignore verification artifacts 2024-06-14 18:46:11 -07:00
zach
c1180d576c checkpoint: use owi crate from git 2024-06-14 18:46:11 -07:00
zach
48af68facc cleanup: improve owi verification 2024-06-14 18:46:11 -07:00
zach
38e6476797 wip 2024-06-14 18:46:09 -07:00
zach
6a18512fc0 v1.4.1 2024-06-14 10:12:55 -07:00
Gavin Hayes
8a95a18920 docs: add CPAN link for perl-sdk (#729) 2024-06-14 12:32:51 -04:00
zach
9dbc22830e fix: use wasi-common to avoid issues with tokio (#728)
See https://github.com/bytecodealliance/wasmtime/issues/8799

I will make a 1.4.1 release after this is merged
2024-06-14 09:22:55 -07:00
zach
c3e912dffb v1.4.0 2024-06-12 15:17:45 -07:00
Gavin Hayes
c4b82e3eda fix(libextism): improve static linking pkgconfig (#726)
This includes three fixes:

* link static library by absolute path (should fix static linking on
Mac/ with `lld`)
* link static library with `-framework Security` on Mac
* link `libpthread` , this should be a no-op on systems with modern
libc, but should fix static linking on systems with glibc pre 2.34 see
https://developers.redhat.com/articles/2021/12/17/why-glibc-234-removed-libpthread
and error
http://www.cpantesters.org/cpan/report/669fdfc8-25a4-11ef-9502-51fb6d8775ea

---------

Co-authored-by: zach <zachshipko@gmail.com>
Co-authored-by: zach <zach@dylibso.com>
2024-06-11 16:22:55 -04:00
zach
2a7345a480 fix: return error when non-zero exit code in returned (#727)
Returns an error when a plugin call returns a non-zero exit code

See https://github.com/extism/go-pdk/issues/33
2024-06-11 12:34:10 -07:00
Gavin Hayes
9099cc73c5 fix(libextism): examples and docs (#724)
Big thanks to @konsumer for reporting the issues!
2024-06-07 14:42:20 -04:00
zach
3f54892a39 refactor!: update to wasmtime 20 or greater (#723)
- Breaking: No longer copies Extism config values into WASI environment
variables because the new interface doesn't allow for the environment to
be updated - these should be accessed using the Extism config functions
instead
- Requires wasmtime 20 or greater
- Enables wasm-gc
- Similar to https://github.com/extism/extism/pull/697 without sockets
or additional support for command modules
2024-06-05 12:57:25 -07:00
zach
ecf18a2d81 fix: re-use linking code from Plugin::new in Plugin::reset_store (#722) 2024-05-31 08:25:06 -07:00
zach
9bc1fc73f2 chore: rename kernel/.cargo/config to kernel/.cargo/config.toml (#721) 2024-05-28 18:26:19 -07:00
zach
2bf391f236 doc: update API.md for latest changes (#720) 2024-05-28 09:27:17 -07:00
Muhammad Azeez
5da0eb38ec refactor: remove HttpRequest.header alias (#718)
https://github.com/extism/dotnet-pdk/pull/84 brought to my attention
that the Rust SDK supports both `headers` and `header` while the go sdk
only supports `headers`. After talking to Zach, we decided to remove the
`header` alias from the Rust SDK too, since it's obsolete and we want
people to use `headers`
2024-05-22 20:34:35 +03:00
zach
7cb6c53910 v1.3.0 2024-05-22 10:07:06 -07:00
zach
0882f35300 fix: respect overall timeout when using http requests (#717)
Currently HTTP requests can extend beyond the configured timeout for a
plugin - this PR sets the timeout of the HTTP request to the remaining
time left if a timeout is set in the manifest.
2024-05-21 20:11:00 -07:00
Chris Dickinson
5d9c8c5d05 feat: per call context (#711)
Add `plugin.call_with_host_context` and `current_plugin.host_context`
methods, enabling per-call context to be looped from the guest invocation
to any host functions it calls. In an HTTP server environment, this enables
re-using a plugin across multiple requests while switching out backing
connections and user information in host functions. (Imagine a host
function, `update_user` -- previously the plugin would have to have been
aware of the user to pass to the host function. Now that information is
ambient.)

This is a backwards-compatible change and requires no changes to
existing plugins.

Implement by adding a global, mutable externref to the extism kernel.
Since most programming languages, including Rust, don't let you define
these natively, we accomplish this by using `wasm-merge` to combine the
kernel Wasm with Wasm generated by a WAT file containing only the
global.

(This pattern might be useful for other Wasm constructs we can't use
directly from Rust, like `v128` in argument parameters.)

Wasmtime requires extern refs to be `Any + Send + Sync + 'static`; we
additionally add `Clone`. I haven't tried this with an `Arc` directly,
but it should work at least for container structs that hold `Arc`'s
themselves.
2024-05-21 11:53:43 -07:00
Gavin Hayes
75e92c40a0 fix(libextism): namespace ExtismValType (#715)
Some of the old constants conflicted with Perl headers used for build
Perl extensions such the
[perl-sdk](002e4437ac/Extism/lib/Extism/XS.xs).
This fix would allow removing the copy of extism.h (inlined in that file
linked) out of the perl-sdk.

These constants especially `I32` likely have conflicts with many other
large C codebases.

A new extism release with this fix should not be made until the affected
sdks (cpp-sdk) have an update ready.
2024-05-20 11:58:04 -04:00
Benjamin Eckel
5373f7d88d docs: Add more context to linking example (#707)
Just adding more context in response to an inquiry about how this works.
2024-04-28 15:11:39 -05:00
Roland Fredenhagen
c5a23a31d8 feat: add id() to CurrentPlugin (#705)
fixes  #704
2024-04-15 09:03:48 -07:00
Steve Manuel
7206e2b362 chore: add perl-sdk to README list 2024-04-10 22:43:27 -06:00
Steve Manuel
20f551f019 chore: fix broken links in crate readme (#701) 2024-04-04 10:23:46 -06:00
zach
9aa817def7 cleanup: add paths to errors, clippy (#700) 2024-03-27 14:54:03 -07:00
zach
054a29e91d v1.2.0 2024-03-12 08:52:04 -07:00
zach
d32d4a3dd7 fix(pdk): return error when no response is available (#694) 2024-03-11 10:32:31 -07:00
Steve Manuel
5f62554aa1 chore: update badge to reflect rust installs (#693) 2024-03-08 11:29:07 -07:00
zach
d47af24552 feat: add ability to configure size of the Extism var store (#692)
- Adds `memory.max_var_bytes` to the manifest to limit the number of
bytes allowed to be stored in Extism vars - if `max_var_bytes` is set to
0 then vars are disabled.
- Adds some builder functions to `MemoryOptions` struct
- Sets the default var store size to 1mb
- Includes a test to make sure `var_set` returns an error when the limit
is reached
2024-03-07 09:55:02 -08:00
dependabot[bot]
8a29e5b1d4 chore(deps): Update base64 requirement from ~0.21 to ~0.22 (#690)
Updates the requirements on
[base64](https://github.com/marshallpierce/rust-base64) to permit the
latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md">base64's
changelog</a>.</em></p>
<blockquote>
<h1>0.22.0</h1>
<ul>
<li><code>DecodeSliceError::OutputSliceTooSmall</code> is now
conservative rather than precise. That is, the error will only occur if
the decoded output <em>cannot</em> fit, meaning that
<code>Engine::decode_slice</code> can now be used with exactly-sized
output slices. As part of this, <code>Engine::internal_decode</code> now
returns <code>DecodeSliceError</code> instead of
<code>DecodeError</code>, but that is not expected to affect any
external callers.</li>
<li><code>DecodeError::InvalidLength</code> now refers specifically to
the <em>number of valid symbols</em> being invalid (i.e. <code>len % 4
== 1</code>), rather than just the number of input bytes. This avoids
confusing scenarios when based on interpretation you could make a case
for either <code>InvalidLength</code> or <code>InvalidByte</code> being
appropriate.</li>
<li>Decoding is somewhat faster (5-10%)</li>
</ul>
<h1>0.21.7</h1>
<ul>
<li>Support getting an alphabet's contents as a str via
<code>Alphabet::as_str()</code></li>
</ul>
<h1>0.21.6</h1>
<ul>
<li>Improved introductory documentation and example</li>
</ul>
<h1>0.21.5</h1>
<ul>
<li>Add <code>Debug</code> and <code>Clone</code> impls for the general
purpose Engine</li>
</ul>
<h1>0.21.4</h1>
<ul>
<li>Make <code>encoded_len</code> <code>const</code>, allowing the
creation of arrays sized to encode compile-time-known data lengths</li>
</ul>
<h1>0.21.3</h1>
<ul>
<li>Implement <code>source</code> instead of <code>cause</code> on Error
types</li>
<li>Roll back MSRV to 1.48.0 so Debian can continue to live in a time
warp</li>
<li>Slightly faster chunked encoding for short inputs</li>
<li>Decrease binary size</li>
</ul>
<h1>0.21.2</h1>
<ul>
<li>Rollback MSRV to 1.57.0 -- only dev dependencies need 1.60, not the
main code</li>
</ul>
<h1>0.21.1</h1>
<ul>
<li>Remove the possibility of panicking during decoded length
calculations</li>
<li><code>DecoderReader</code> no longer sometimes erroneously ignores
padding <a
href="https://redirect.github.com/marshallpierce/rust-base64/issues/226">#226</a></li>
</ul>
<h2>Breaking changes</h2>
<ul>
<li><code>Engine.internal_decode</code> return type changed</li>
<li>Update MSRV to 1.60.0</li>
</ul>
<h1>0.21.0</h1>
<h2>Migration</h2>
<h3>Functions</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5d70ba7576"><code>5d70ba7</code></a>
Merge pull request <a
href="https://redirect.github.com/marshallpierce/rust-base64/issues/269">#269</a>
from marshallpierce/mp/decode-precisely</li>
<li><a
href="efb6c006c7"><code>efb6c00</code></a>
Release notes</li>
<li><a
href="2b91084a31"><code>2b91084</code></a>
Add some tests to boost coverage</li>
<li><a
href="9e9c7abe65"><code>9e9c7ab</code></a>
Engine::internal_decode now returns DecodeSliceError</li>
<li><a
href="a8a60f43c5"><code>a8a60f4</code></a>
Decode main loop improvements</li>
<li><a
href="a25be0667c"><code>a25be06</code></a>
Simplify leftover output writes</li>
<li><a
href="9979cc33bb"><code>9979cc3</code></a>
Keep morsels as separate bytes</li>
<li><a
href="37670c5ec2"><code>37670c5</code></a>
Bump dev toolchain version (<a
href="https://redirect.github.com/marshallpierce/rust-base64/issues/268">#268</a>)</li>
<li><a
href="9652c78773"><code>9652c78</code></a>
v0.21.7</li>
<li><a
href="08deccf703"><code>08deccf</code></a>
provide as_str() method to return the alphabet characters (<a
href="https://redirect.github.com/marshallpierce/rust-base64/issues/264">#264</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/marshallpierce/rust-base64/compare/v0.21.0...v0.22.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 09:04:37 -08:00
zach
4e0cd3b1cf doc: remove old default for timeout_ms (#688) 2024-02-26 16:29:35 -08:00
40 changed files with 782 additions and 214 deletions

View File

@@ -21,6 +21,9 @@ 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

@@ -4,10 +4,12 @@ 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)
@@ -29,7 +31,8 @@ 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)%" libextism/extism-static.pc.in > libextism/extism-static.pc
sed -e "s%@CMAKE_INSTALL_PREFIX@%$(DEST)%" \
-e "s%Libs: %Libs: $(EXTRA_LIBS) %" 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)
![GitHub all releases](https://img.shields.io/github/downloads/extism/extism/total)
![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,6 +51,7 @@ 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) |

View File

@@ -11,7 +11,7 @@ description = "Traits to make Rust types usable with Extism"
[dependencies]
anyhow = "1.0.75"
base64 = "~0.21"
base64 = "~0.22"
bytemuck = {version = "1.14.0", optional = true }
prost = { version = "0.12.0", optional = true }
protobuf = { version = "3.2.0", optional = true }

3
kernel/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
owi-out
*.wat
proofs

View File

@@ -7,6 +7,7 @@ edition = "2021"
[dev-dependencies]
wasm-bindgen-test = "0.3.39"
owi = {git = "https://github.com/dylibso/owi-rs"}
[features]
default = ["bounds-checking"]
@@ -15,4 +16,4 @@ bounds-checking = []
[workspace]
members = [
"."
]
]

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env bash
set -e
export CARGO_FLAGS=""
while getopts d flag
@@ -17,6 +19,9 @@ 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

@@ -0,0 +1,21 @@
#![no_main]
#![no_std]
use extism_runtime_kernel::*;
use owi::*;
main!({
let n = alloc(1024);
let x = u64();
assume(x > 0);
let m = alloc(x);
// 1. Length should equal `x` while active
assert(length(m) == x);
// 2. Length should equal `0` after free
free(m); // Free the block
assert(length(m) == 0);
free(n);
});

24
kernel/examples/error.rs Normal file
View File

@@ -0,0 +1,24 @@
#![no_main]
#![no_std]
use extism_runtime_kernel::*;
use owi::*;
main!({
let x = u64();
let n = u64();
let m = u64();
assume(x > 0);
assume(n > 0);
assume(m > 0);
alloc(n);
alloc(m);
let n = alloc(x);
assert(error_get() == 0);
error_set(n);
assert(error_get() == n);
alloc(m);
error_set(0);
assert(error_get() == 0);
});

View File

@@ -0,0 +1,17 @@
#![no_main]
#![no_std]
use extism_runtime_kernel::*;
use owi::*;
main!({
let x = u64();
assume(x > 0);
let m = alloc(x);
assert(length(m) == x);
for i in 0..x {
store_u8(m + i, i as u8);
assert(load_u8(m + i) == i as u8);
}
free(m);
});

39
kernel/examples/reuse.rs Normal file
View File

@@ -0,0 +1,39 @@
#![no_main]
#![no_std]
use extism_runtime_kernel::*;
use owi::*;
main!({
let x = u64();
assume(x > 0);
let y = u64();
assume(y > 0);
let mut tmp = 0;
for _ in 0..y {
let m = alloc(x);
if tmp == 0 {
tmp = m;
} else {
// Check that the existing block is being re-used
assert(m == tmp);
}
assert(length(m) == x);
free(m); // Free the block
}
let y = u64();
assume(y == x + 1);
let n = alloc(y);
assert(n > tmp);
let z = u64();
assume(z <= x);
assume(x - z < 32);
assume(z > 0);
let p = alloc(z);
assert(p == tmp);
});

View File

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

View File

@@ -1,5 +1,5 @@
#![no_main]
#![no_std]
#![no_main]
pub use extism_runtime_kernel::*;

View File

@@ -175,7 +175,7 @@ impl MemoryRoot {
core::ptr::write_bytes(
self.blocks.as_mut_ptr() as *mut u8,
MemoryStatus::Unused as u8,
self_position as usize,
self_position as usize - core::mem::size_of::<MemoryRoot>(),
);
// Clear extism runtime metadata
@@ -638,4 +638,15 @@ mod test {
assert_eq!(last, 0);
}
#[wasm_bindgen_test]
fn test_sym() {
unsafe {
reset();
let a = alloc(47);
let b = alloc(20);
assert_eq!(length(a), 47);
assert_eq!(length(b), 20);
}
}
}

23
kernel/verify.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -e
OUT_DIR=./target/wasm32-unknown-unknown/release/examples/
get_proof() {
cp "$OUT_DIR/$1.wasm" "./proofs/$1.wasm"
}
cargo build --examples --release --target wasm32-unknown-unknown --no-default-features
mkdir -p proofs
get_proof alloc_length
get_proof load_store
get_proof reuse
get_proof error
for proof in $(ls proofs/*.wasm); do
echo "Checking $proof"
owi conc "$proof" $@
echo
echo "---"
done

2
libextism/.clang-format Normal file
View File

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

View File

@@ -14,7 +14,9 @@ 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
- `errmsg`: error message during plugin creation, this should be freed with
`extism_plugin_new_error_free`
```c
ExtismPlugin extism_plugin_new(const uint8_t *wasm,
@@ -24,6 +26,17 @@ 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`
@@ -33,6 +46,8 @@ 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.
@@ -43,6 +58,8 @@ bool extism_plugin_config(ExtismPlugin *plugin,
ExtismSize json_size);
```
---
### `extism_plugin_function_exists`
Returns true if `func_name` exists.
@@ -52,6 +69,8 @@ bool extism_plugin_function_exists(ExtismPlugin *plugin,
const char *func_name);
```
---
### `extism_plugin_call`
Call a function.
@@ -59,6 +78,8 @@ 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,
@@ -66,6 +87,28 @@ 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`
@@ -74,6 +117,8 @@ 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.
@@ -82,6 +127,8 @@ 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.
@@ -90,6 +137,8 @@ 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.
@@ -98,6 +147,8 @@ Reset the Extism runtime, this will invalidate all allocated memory.
bool extism_plugin_reset(ExtismPlugin *plugin);
```
---
### `extism_log_file`
Set log file and level.
@@ -106,6 +157,8 @@ 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`
@@ -115,6 +168,8 @@ 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.
@@ -124,6 +179,8 @@ 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.
@@ -132,6 +189,8 @@ 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
@@ -140,6 +199,19 @@ 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
@@ -148,6 +220,8 @@ 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
@@ -156,6 +230,8 @@ 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
@@ -164,6 +240,8 @@ 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
@@ -190,6 +268,8 @@ ExtismFunction *extism_function_new(const char *name,
void (*free_user_data)(void *_));
```
---
### `extism_function_set_namespace`
Set the namespace of an `ExtismFunction`
@@ -198,6 +278,8 @@ Set the namespace of an `ExtismFunction`
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
```
---
### `extism_function_free`
Free an `ExtismFunction`
@@ -206,6 +288,8 @@ Free an `ExtismFunction`
void extism_function_free(ExtismFunction *ptr);
```
---
### `extism_plugin_cancel_handle`
Get handle for plugin cancellation
@@ -214,6 +298,8 @@ Get handle for plugin cancellation
const ExtismCancelHandle *extism_plugin_cancel_handle(const ExtismPlugin *plugin);
```
---
### `extism_plugin_cancel`
Cancel a running plugin from another thread
@@ -222,12 +308,14 @@ Cancel a running plugin from another thread
bool extism_plugin_cancel(const ExtismCancelHandle *handle);
```
---
## Type definitions:
### `ExtismPlugin`
```c
typedef int32_t ExtismPlugin;
typedef struct ExtismPlugin ExtismPlugin;
```
### `ExtismSize`
@@ -259,3 +347,5 @@ 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
$(CC) -g -o example example.c -l:libextism.a -lm -lpthread
# if needed, set PKG_CONFIG_PATH= to the directory with extism*.pc installed
LDFLAGS=`pkg-config --libs extism`

View File

@@ -56,7 +56,6 @@ 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) {
@@ -64,9 +63,9 @@ void print_plugin_output(ExtismPlugin *plugin, int32_t rc){
return;
}
size_t outlen = extism_plugin_output_length(plugin);
ExtismSize outlen = extism_plugin_output_length(plugin);
const uint8_t *out = extism_plugin_output_data(plugin);
write(STDOUT_FILENO, out, outlen);
fwrite(out, 1, outlen, stdout);
}
int main(void) {
@@ -106,9 +105,9 @@ if (rc != EXTISM_SUCCESS) {
exit(2);
}
size_t outlen = extism_plugin_output_length(plugin);
ExtismSize outlen = extism_plugin_output_length(plugin);
const uint8_t *out = extism_plugin_output_data(plugin);
write(STDOUT_FILENO, out, outlen);
fwrite(out, 1, outlen, stdout);
```
Will print
@@ -171,7 +170,6 @@ 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;
@@ -184,14 +182,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,
size_t ninputs, ExtismVal *outputs, size_t noutputs,
ExtismSize ninputs, ExtismVal *outputs, ExtismSize 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;
size_t keylen = extism_current_plugin_memory_length(plugin, offs);
ExtismSize keylen = extism_current_plugin_memory_length(plugin, offs);
// Allocate a new block to return
uint64_t outoffs =
@@ -205,23 +203,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 = PTR;
outputs[0].t = EXTISM_PTR;
outputs[0].v.i64 = outoffs;
}
void kv_set(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
size_t ninputs, ExtismVal *outputs, size_t noutputs,
ExtismSize ninputs, ExtismVal *outputs, ExtismSize 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;
size_t keylen = extism_current_plugin_memory_length(plugin, keyoffs);
ExtismSize 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;
size_t valuelen = extism_current_plugin_memory_length(plugin, valueoffs);
ExtismSize valuelen = extism_current_plugin_memory_length(plugin, valueoffs);
// Set key => value
fake_kv_store_set(
@@ -234,13 +232,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[] = {PTR};
const ExtismValType kv_get_outputs[] = {PTR};
const ExtismValType kv_get_inputs[] = {EXTISM_PTR};
const ExtismValType kv_get_outputs[] = {EXTISM_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[] = {PTR};
const ExtismValType kv_set_outputs[] = {PTR};
const ExtismValType kv_set_inputs[] = {EXTISM_PTR};
const ExtismValType kv_set_outputs[] = {EXTISM_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};
@@ -288,5 +286,3 @@ 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, uintptr_t length) {
void log_handler(const char *line, ExtismSize 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[] = {PTR};
ExtismValType outputs[] = {PTR};
ExtismValType inputs[] = {EXTISM_PTR};
ExtismValType outputs[] = {EXTISM_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: -L${libdir} -l:libextism.a
Libs.private: -lm
Libs: ${libdir}/libextism.a
Libs.private: -lm -lpthread
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
Libs.private: -lm -lpthread
Cflags: -I${includedir}

View File

@@ -10,7 +10,7 @@ version.workspace = true
[dependencies]
serde = { version = "1", features = ["derive"] }
base64 = "~0.21"
base64 = "~0.22"
schemars = { version = "0.8", optional = true }
serde_json = "1"

View File

@@ -38,7 +38,8 @@
"description": "Memory options",
"default": {
"max_http_response_bytes": null,
"max_pages": null
"max_pages": null,
"max_var_bytes": null
},
"allOf": [
{
@@ -47,7 +48,7 @@
]
},
"timeout_ms": {
"description": "The plugin timeout, by default this is set to 30s",
"description": "The plugin timeout in milliseconds",
"default": null,
"type": [
"integer",
@@ -89,6 +90,16 @@
],
"format": "uint32",
"minimum": 0.0
},
"max_var_bytes": {
"description": "The maximum number of bytes allowed to be used by plugin vars. Setting this to 0 will disable Extism vars. The default value is 1mb.",
"default": 1048576,
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false

View File

@@ -10,12 +10,45 @@ 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
#[serde(default)]
pub max_http_response_bytes: Option<u64>,
/// The maximum number of bytes allowed to be used by plugin vars. Setting this to 0
/// will disable Extism vars. The default value is 1mb.
#[serde(default = "default_var_bytes")]
pub max_var_bytes: Option<u64>,
}
impl MemoryOptions {
/// Create an empty `MemoryOptions` value
pub fn new() -> Self {
Default::default()
}
/// Set max pages
pub fn with_max_pages(mut self, pages: u32) -> Self {
self.max_pages = Some(pages);
self
}
/// Set max HTTP response size
pub fn with_max_http_response_bytes(mut self, bytes: u64) -> Self {
self.max_http_response_bytes = Some(bytes);
self
}
/// Set max size of Extism vars
pub fn with_max_var_bytes(mut self, bytes: u64) -> Self {
self.max_var_bytes = Some(bytes);
self
}
}
fn default_var_bytes() -> Option<u64> {
Some(1024 * 1024)
}
/// Generic HTTP request structure
@@ -28,7 +61,6 @@ pub struct HttpRequest {
/// Request headers
#[serde(default)]
#[serde(alias = "header")]
pub headers: std::collections::BTreeMap<String, String>,
/// Request method
@@ -249,7 +281,7 @@ pub struct Manifest {
#[serde(default)]
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
/// The plugin timeout, by default this is set to 30s
/// The plugin timeout in milliseconds
#[serde(default)]
pub timeout_ms: Option<u64>,
}

View File

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

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"
extism = "1.2.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 [Extism::CurrentPlugin](https://docs.rs/extism/latest/extism/struct.CurrentPlugin.html) and [Extism::CurrentPlugin](https://docs.rs/extism/latest/extism/struct.UserData.html) types.
> *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.
Now we can invoke the event:

View File

@@ -7,7 +7,7 @@ fn main() {
#define EXTISM_SUCCESS 0
/** An alias for I64 to signify an Extism pointer */
#define PTR I64
#define EXTISM_PTR ExtismValType_I64
";
if let Ok(x) = cbindgen::Builder::new()
.with_crate(".")

View File

@@ -2,6 +2,8 @@ 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(),
@@ -10,6 +12,9 @@ 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 PTR I64
#define EXTISM_PTR ExtismValType_I64
/**
@@ -20,31 +20,31 @@ typedef enum {
/**
* Signed 32 bit integer.
*/
I32,
ExtismValType_I32,
/**
* Signed 64 bit integer.
*/
I64,
ExtismValType_I64,
/**
* Floating point 32 bit integer.
*/
F32,
ExtismValType_F32,
/**
* Floating point 64 bit integer.
*/
F64,
ExtismValType_F64,
/**
* A 128 bit number.
*/
V128,
ExtismValType_V128,
/**
* A reference to a Wasm function.
*/
FuncRef,
ExtismValType_FuncRef,
/**
* A reference to opaque data in the Wasm instance.
*/
ExternRef,
ExtismValType_ExternRef,
} ExtismValType;
/**
@@ -113,6 +113,12 @@ 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.
@@ -231,6 +237,20 @@ 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`
*/

View File

@@ -15,6 +15,7 @@ pub struct CurrentPlugin {
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 {}
@@ -62,6 +63,11 @@ 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 {
@@ -181,6 +187,23 @@ impl CurrentPlugin {
anyhow::bail!("{} unable to locate extism memory", self.id)
}
pub fn host_context<T: Clone + 'static>(&mut self) -> Result<T, Error> {
let (linker, mut store) = self.linker_and_store();
let Some(Extern::Global(xs)) = linker.get(&mut store, EXTISM_ENV_MODULE, "extism_context")
else {
anyhow::bail!("unable to locate an extism kernel global: extism_context",)
};
let Val::ExternRef(Some(xs)) = xs.get(&mut store) else {
anyhow::bail!("expected extism_context to be an externref value",)
};
match xs.data(&mut store)?.downcast_ref::<T>().cloned() {
Some(xs) => Ok(xs.clone()),
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 {
@@ -288,25 +311,29 @@ impl CurrentPlugin {
id: uuid::Uuid,
) -> Result<Self, Error> {
let wasi = if wasi {
let auth = wasmtime_wasi::ambient_authority();
let mut ctx = wasmtime_wasi::WasiCtxBuilder::new();
for (k, v) in manifest.config.iter() {
ctx.env(k, v)?;
}
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);
if let Some(a) = &manifest.allowed_paths {
for (k, v) in a.iter() {
let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth)?;
ctx.preopened_dir(d, v)?;
let file = Box::new(wasi_common::sync::dir::Dir::from_cap_std(
wasi_common::sync::Dir::open_ambient_dir(k, auth)?,
));
ctx.push_preopened_dir(file, v)?;
}
}
// Enable WASI output, typically used for debugging purposes
if std::env::var("EXTISM_ENABLE_WASI_OUTPUT").is_ok() {
ctx.inherit_stdout().inherit_stderr();
ctx.set_stderr(Box::new(wasi_common::sync::stdio::stderr()));
ctx.set_stdout(Box::new(wasi_common::sync::stdio::stdout()));
}
Some(Wasi { ctx: ctx.build() })
Some(Wasi { ctx })
} else {
None
};
@@ -331,6 +358,7 @@ impl CurrentPlugin {
available_pages,
memory_limiter,
id,
start_time: std::time::Instant::now(),
})
}
@@ -441,6 +469,18 @@ 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 {
@@ -452,14 +492,6 @@ 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) }
}

Binary file not shown.

View File

@@ -4,6 +4,7 @@ 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 {
@@ -37,8 +38,13 @@ impl From<wasmtime::ValType> for ValType {
F32 => ValType::F32,
F64 => ValType::F64,
V128 => ValType::V128,
FuncRef => ValType::FuncRef,
ExternRef => ValType::ExternRef,
Ref(t) => {
if t.heap_type().is_func() {
ValType::FuncRef
} else {
ValType::ExternRef
}
}
}
}
}
@@ -52,8 +58,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,
}
}
}
@@ -71,7 +77,9 @@ pub struct CPtr {
/// UserDataHandle is an untyped version of `UserData` that is stored inside `Function` to keep a live reference.
#[derive(Clone)]
pub(crate) enum UserDataHandle {
#[allow(dead_code)]
C(Arc<CPtr>),
#[allow(dead_code)]
Rust(Arc<std::sync::Mutex<dyn std::any::Any>>),
}
@@ -168,8 +176,8 @@ pub struct Function {
/// Module name
pub(crate) namespace: Option<String>,
/// Function type
pub(crate) ty: wasmtime::FuncType,
pub(crate) params: Vec<ValType>,
pub(crate) results: Vec<ValType>,
/// Function handle
pub(crate) f: Arc<FunctionInner>,
@@ -182,8 +190,8 @@ impl Function {
/// Create a new host function
pub fn new<T: 'static, F>(
name: impl Into<String>,
args: impl IntoIterator<Item = ValType>,
returns: impl IntoIterator<Item = ValType>,
params: impl IntoIterator<Item = ValType>,
results: impl IntoIterator<Item = ValType>,
user_data: UserData<T>,
f: F,
) -> Function
@@ -195,13 +203,13 @@ impl Function {
{
let data = user_data.clone();
let name = name.into();
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:?}");
let params = params.into_iter().collect();
let results = results.into_iter().collect();
trace!("Creating function {name}: params={params:?}, results={results:?}");
Function {
name,
ty,
params,
results,
f: Arc::new(
move |mut caller: Caller<_>, inp: &[Val], outp: &mut [Val]| {
let x = data.clone();
@@ -216,6 +224,22 @@ 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
@@ -239,9 +263,14 @@ impl Function {
self
}
/// Get function type
pub fn ty(&self) -> &wasmtime::FuncType {
&self.ty
/// Get param types
pub fn params(&self) -> &[ValType] {
&self.params
}
/// Get result types
pub fn results(&self) -> &[ValType] {
&self.results
}
}

View File

@@ -3,7 +3,7 @@ use crate::*;
/// WASI context
pub struct Wasi {
/// wasi
pub ctx: wasmtime_wasi::WasiCtx,
pub ctx: wasi_common::WasiCtx,
}
/// InternalExt provides a unified way of acessing `memory`, `store` and `internal` values
@@ -12,10 +12,6 @@ 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

@@ -47,9 +47,13 @@ fn to_module(engine: &Engine, wasm: &extism_manifest::Wasm) -> Result<(String, M
let name = meta.name.as_deref().unwrap_or(MAIN_KEY).to_string();
// Load file
let mut buf = Vec::new();
let mut file = std::fs::File::open(path)?;
file.read_to_end(&mut buf)?;
let buf = std::fs::read(path).map_err(|err| {
Error::msg(format!(
"Unable to load Wasm file \"{}\": {}",
path.display(),
err.kind()
))
})?;
check_hash(&meta.hash, &buf)?;
Ok((name, Module::new(engine, buf)?))

View File

@@ -97,19 +97,13 @@ pub(crate) fn var_set(
) -> Result<(), Error> {
let data: &mut CurrentPlugin = caller.data_mut();
let mut size = 0;
for v in data.vars.values() {
size += v.len();
if data.manifest.memory.max_var_bytes.is_some_and(|x| x == 0) {
anyhow::bail!("Vars are disabled by this host")
}
let voffset = args!(input, 1, i64) as u64;
// If the store is larger than 100MB then stop adding things
if size > 1024 * 1024 * 100 && voffset != 0 {
return Err(Error::msg("Variable store is full"));
}
let key_offs = args!(input, 0, i64) as u64;
let key = {
let handle = match data.memory_handle(key_offs) {
Some(h) => h,
@@ -132,6 +126,22 @@ pub(crate) fn var_set(
None => anyhow::bail!("invalid handle offset for var value: {voffset}"),
};
let mut size = std::mem::size_of::<String>()
+ std::mem::size_of::<Vec<u8>>()
+ key.len()
+ handle.length as usize;
for (k, v) in data.vars.iter() {
size += k.len();
size += v.len();
size += std::mem::size_of::<String>() + std::mem::size_of::<Vec<u8>>();
}
// If the store is larger than the configured size, or 1mb by default, then stop adding things
if size > data.manifest.memory.max_var_bytes.unwrap_or(1024 * 1024) as usize && voffset != 0 {
return Err(Error::msg("Variable store is full"));
}
let value = data.memory_bytes(handle)?.to_vec();
// Insert the value from memory into the `vars` map
@@ -207,6 +217,11 @@ 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,
@@ -226,11 +241,18 @@ pub(crate) fn http_request(
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();
Some(res.into_reader())
} else {
None
return Err(Error::msg(msg));
}
}
};

View File

@@ -1,4 +1,5 @@
use std::{
any::Any,
collections::{BTreeMap, BTreeSet},
path::PathBuf,
};
@@ -100,14 +101,6 @@ 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)
}
@@ -193,6 +186,12 @@ fn add_module<T: 'static>(
}
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(
@@ -213,6 +212,73 @@ fn add_module<T: 'static>(
Ok(())
}
fn relink(
engine: &Engine,
mut store: &mut Store<CurrentPlugin>,
imports: &[Function],
modules: &BTreeMap<String, Module>,
with_wasi: bool,
) -> Result<(InstancePre<CurrentPlugin>, Linker<CurrentPlugin>), Error> {
let mut linker = Linker::new(engine);
// 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;
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 {
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 main = &modules[MAIN_KEY];
let instance_pre = linker.instantiate_pre(main)?;
Ok((instance_pre, linker))
}
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.
@@ -239,7 +305,8 @@ impl Plugin {
.coredump_on_trap(debug_options.coredump.is_some())
.profiler(debug_options.profiling_strategy)
.wasm_tail_call(true)
.wasm_function_references(true);
.wasm_function_references(true)
.wasm_gc(true);
match cache_dir {
Some(None) => (),
@@ -275,64 +342,9 @@ impl Plugin {
);
store.set_epoch_deadline(1);
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> = imports.into_iter().collect();
let (instance_pre, linker) = relink(&engine, &mut store, &imports, &modules, with_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,
@@ -370,6 +382,7 @@ 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(
@@ -380,6 +393,16 @@ impl Plugin {
)?,
);
self.store.set_epoch_deadline(1);
let (instance_pre, linker) = relink(
&engine,
&mut self.store,
&self._functions,
&self.modules,
with_wasi,
)?;
self.linker = linker;
self.instance_pre = instance_pre;
let store = &mut self.store as *mut _;
let linker = &mut self.linker as *mut _;
let current_plugin = self.current_plugin_mut();
@@ -390,14 +413,7 @@ 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;
}
@@ -450,7 +466,7 @@ impl Plugin {
if let Some(f) = x.func() {
let (params, mut results) = (f.params(), f.results());
match (params.len(), results.len()) {
(0, 1) => results.next() == Some(wasmtime::ValType::I32),
(0, 1) => matches!(results.next(), Some(wasmtime::ValType::I32)),
(0, 0) => true,
_ => false,
}
@@ -462,7 +478,12 @@ impl Plugin {
}
// Store input in memory and re-initialize `Internal` pointer
pub(crate) fn set_input(&mut self, input: *const u8, mut len: usize) -> Result<(), Error> {
pub(crate) fn set_input(
&mut self,
input: *const u8,
mut len: usize,
host_context: Option<Rooted<ExternRef>>,
) -> Result<(), Error> {
self.output = Output::default();
self.clear_error()?;
let id = self.id.to_string();
@@ -496,6 +517,13 @@ impl Plugin {
)?;
}
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))?;
}
Ok(())
}
@@ -588,7 +616,7 @@ impl Plugin {
if let Some(reactor_init) = reactor_init {
reactor_init.call(&mut store, &[], &mut [])?;
}
let mut results = vec![Val::null(); init.ty(&store).results().len()];
let mut results = vec![Val::I32(0); init.ty(&store).results().len()];
init.call(
&mut store,
&[Val::I32(0), Val::I32(0)],
@@ -673,6 +701,7 @@ impl Plugin {
lock: &mut std::sync::MutexGuard<Option<Instance>>,
name: impl AsRef<str>,
input: impl AsRef<[u8]>,
host_context: Option<Rooted<ExternRef>>,
) -> Result<i32, (Error, i32)> {
let name = name.as_ref();
let input = input.as_ref();
@@ -686,7 +715,7 @@ impl Plugin {
self.instantiate(lock).map_err(|e| (e, -1))?;
self.set_input(input.as_ptr(), input.len())
self.set_input(input.as_ptr(), input.len(), host_context)
.map_err(|x| (x, -1))?;
let func = match self.get_func(lock, name) {
@@ -717,9 +746,10 @@ 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::null(); n_results];
let mut results = vec![wasmtime::Val::I32(0); n_results];
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
// Stop timer
@@ -804,13 +834,7 @@ impl Plugin {
}
}
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)
});
let wasi_exit_code = e.downcast_ref::<wasi_common::I32Exit>().map(|e| e.0);
if let Some(exit_code) = wasi_exit_code {
debug!(
plugin = self.id.to_string(),
@@ -873,7 +897,33 @@ 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)
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()
}
})
}
pub fn call_with_host_context<'a, 'b, T, U, C>(
&'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,
{
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let data = input.to_bytes()?;
let ctx = ExternRef::new(&mut self.store, host_context)?;
self.raw_call(&mut lock, name, data, Some(ctx))
.map_err(|e| e.0)
.and_then(move |_| self.output())
}
@@ -892,7 +942,7 @@ impl Plugin {
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let data = input.to_bytes().map_err(|e| (e, -1))?;
self.raw_call(&mut lock, name, data)
self.raw_call(&mut lock, name, data, None)
.and_then(move |_| self.output().map_err(|e| (e, -1)))
}

View File

@@ -41,9 +41,9 @@ pub type ExtismFunctionType = extern "C" fn(
/// 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() {
impl ExtismVal {
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Self {
match value.ty(ctx) {
wasmtime::ValType::I32 => ExtismVal {
t: ValType::I32,
v: ValUnion {
@@ -84,6 +84,24 @@ 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]
@@ -200,7 +218,11 @@ pub unsafe extern "C" fn extism_function_new(
output_types.clone(),
user_data,
move |plugin, inputs, outputs, user_data| {
let inputs: Vec<_> = inputs.iter().map(ExtismVal::from).collect();
let store = &*plugin.store;
let inputs: Vec<_> = inputs
.iter()
.map(|x| ExtismVal::from_val(x, store))
.collect();
let mut output_tmp: Vec<_> = output_types
.iter()
.map(|t| ExtismVal {
@@ -389,20 +411,6 @@ pub unsafe extern "C" fn extism_plugin_config(
}
};
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() {
@@ -464,6 +472,31 @@ 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;
@@ -485,8 +518,11 @@ pub unsafe extern "C" fn extism_plugin_call(
name
);
let input = std::slice::from_raw_parts(data, data_len as usize);
let res = plugin.raw_call(&mut lock, name, input);
let r = match ExternRef::new(&mut plugin.store, CVoidContainer(host_context)) {
Err(e) => return plugin.return_error(&mut lock, e, -1),
Ok(x) => x,
};
let res = plugin.raw_call(&mut lock, name, input, Some(r));
match res {
Err((e, rc)) => plugin.return_error(&mut lock, e, rc),
Ok(x) => x,
@@ -671,7 +707,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) = &mut LOG_BUFFER {
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,3 +1,5 @@
use extism_manifest::MemoryOptions;
use crate::*;
use std::{io::Write, time::Instant};
@@ -238,6 +240,34 @@ fn test_timeout() {
assert!(err == "timeout");
}
#[test]
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
});
@@ -305,6 +335,42 @@ 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>()?;
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)));
@@ -594,6 +660,29 @@ fn test_manifest_ptr_len() {
assert_eq!(count.get("count").unwrap().as_i64().unwrap(), 1);
}
#[test]
fn test_no_vars() {
let data = br#"
(module
(import "extism:host/env" "var_set" (func $var_set (param i64 i64)))
(import "extism:host/env" "input_offset" (func $input_offset (result i64)))
(func (export "test") (result i32)
(call $input_offset)
(call $input_offset)
(call $var_set)
(i32.const 0)
)
)
"#;
let manifest = Manifest::new([Wasm::data(data)])
.with_memory_options(MemoryOptions::new().with_max_var_bytes(1));
let mut plugin = Plugin::new(manifest, [], true).unwrap();
let output: Result<(), Error> = plugin.call("test", b"A".repeat(1024));
assert!(output.is_err());
let output: Result<(), Error> = plugin.call("test", vec![]);
assert!(output.is_ok());
}
#[test]
fn test_linking() {
let manifest = Manifest::new([