Compare commits

..

58 Commits

Author SHA1 Message Date
Chris Dickinson
6cf8251d90 v1.9.0 2024-11-19 14:11:47 -08:00
zach
4d0799ca37 docs: add comment to fs example (#788)
Since it's using a read-only path, we should provide some information
about how to use a read/write path to make this program run without
failing.
2024-11-15 18:21:29 -08:00
zach
14477ceb39 feat: add CompiledPlugin (#784) 2024-11-14 12:32:22 -08:00
zach
af67a6990c chore: update to wasmtime 26 (#783) 2024-11-07 12:36:58 -08:00
zach
72c47fceaf v1.8.0 2024-10-22 09:39:13 -07:00
Benjamin Eckel
f52969aadb Add codeowners file (#780) 2024-10-22 09:35:04 -07:00
zach
7775c57a81 fix: use f32::to_bits and f64::to_bits when constructing Val (#779)
Fixes #739
2024-10-22 09:33:04 -07:00
zach
7b6664d019 feat: add ability to access response headers when using extism:host/env::http_request (#774)
- Adds `extism:host/env::http_headers` to access HTTP response headers
- Adds `PluginBuilder::with_http_response_headers` to enable response
headers from Rust
- Adds `extism_plugin_allow_http_response_headers` to enable response
headers using the C API

TODO:
- [x] Update a PDK to use `extism:host/env::http_headers` so I can test
this
2024-10-14 10:09:28 -07:00
Gavin Hayes
a91846a34b fix(plugin_call): set rc to EXIT_SIGNALED_SIGABRT when wasmtime bails out on plugin call (#776)
Fixes #775

134/EXIT_SIGNALED_SIGABRT was chosen as the wasmtime CLI exits with it
executes `unreachable`.
2024-10-10 15:03:59 -04:00
Han Yang
876a3be147 Fix: no method named free found for mutable reference &mut current_plugin::CurrentPlugin in the current scope (#773)
think there's a typo for this block when the `http` feature is disabled
2024-10-04 07:48:29 -07:00
zach
520d72e408 v1.7.0 2024-09-24 10:17:01 -07:00
zach
3ac3d9abcf cleanup: host takes ownership of memory blocks it gets as arguments (#743)
This PR changes the host to take ownership of memory blocks passed into
Extism host functions, see:
https://github.com/extism/js-sdk/pull/71#issuecomment-2233687933
2024-09-23 10:42:45 -07:00
SebastianHambura
8222164eca Adds more details about with_wasmtime_config() limitations (#770)
PR for https://github.com/extism/extism/pull/764#issuecomment-2368337805

---------

Co-authored-by: Sebastian Hambura <sebastian.hambur@embl.de>
2024-09-23 08:04:29 -07:00
zach
fa81270a5f cleanup(kernel): only try to re-use free blocks before memory.grow (#765)
Updates the kernel to only scan for free blocks before a `memory.grow`
operation, this should improve performance of `alloc` by not iterating
over every block for each allocation.
2024-09-19 15:55:02 -07:00
zach
7bf41c2c7f feat: add PluginBuilder::with_wasmtime_config (#764)
An alternative to #763, this PR allows an initial `wasmtime::Config` to
be passed in when building a plugin. Some of these values may be
overwritten by the Extism runtime, but it allows for things like static
memory size and other low-level details to be handled directly instead
of us having to wrap every option ourselves.
2024-09-19 11:27:27 -07:00
Fabien Benetou
d3a68e2c0c chore: define pdk term in README (#766)
from https://extism.org/docs/concepts/pdk
2024-09-19 08:53:18 -06:00
zach
e31806cdb1 v1.6.0 2024-09-04 12:23:21 -07:00
zach
c2866a7358 fix: better error handling when plugin runs out of fuel (#762)
- Checks error results to determine if a plugin ran out of fuel, if the
fuel is 0 after we get the error, then we return an out of fuel error
message instead.
- Updates `extism_plugin_error` to check `Plugin::error_msg` regardless
of the kernel's error state
2024-09-04 09:42:09 -07:00
Edoardo Vacchi
34096bd9c0 fix: Vec.as_ptr() might return a dangling pointer (#760)
Both [as_ptr][as_ptr] and [as_mut_ptr][as_mut_ptr] are allowed to return
a dangling raw pointer when the Vec size is 0.

The idea is that you should guard that read checking the size. This
probably works well in most cases, but at the very least in the
java-sdk, the JNA machinery tries to be helpful and it dereferences the
pointer, causing a SIGSEGV.

The solution is to check if the resulting vector is empty and return
null instead. A new, empty vector would be better, but I think that
would not solve the problem, because the problem is caused by a new,
empty vector in the first place.

Caveat: this might break consumers downstream.
On the other hand: consumers that do not check for the nInput, nOutput
counts are just waiting to explode, like JNA.

This addresses https://github.com/extism/java-sdk/issues/27

[as_ptr]:
https://doc.rust-lang.org/std/vec/struct.Vec.html#method.as_ptr
[as_mut_ptr]:
https://doc.rust-lang.org/std/vec/struct.Vec.html#method.as_mut_ptr

Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
2024-09-04 09:41:55 -07:00
zach
d2a3699f43 fix: avoid creating too many externrefs (#759)
Updates plugins to allocate a single `ExternRef` for the host context up
front, to avoid running into the `failed to allocate externref` error
from Wasmtime
2024-08-29 17:24:18 -07:00
Steve Manuel
b6e1caad07 feat(runtime): add log level set/get for pdk control (#758)
Co-authored-by: zach <zach@dylibso.com>
2024-08-28 14:31:55 -06:00
zach
e979987dc7 feat: add ability to limit the number of instructions executed by a plugin (#754)
*Note*: this will be limited for the time being as not all runtimes
support this yet
2024-08-23 10:24:28 -07:00
zach
ef2eeab6e3 chore: include support for wasmtime 23.0.0 (#755) 2024-08-22 12:49:58 -07:00
dependabot[bot]
9da8088ebf chore(deps): Update prost requirement from 0.12.0 to 0.13.1 (#741)
Updates the requirements on [prost](https://github.com/tokio-rs/prost)
to permit the latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/prost/blob/master/CHANGELOG.md">prost's
changelog</a>.</em></p>
<blockquote>
<h1>Prost version 0.13.1</h1>
<p><em>PROST!</em> is a <a
href="https://developers.google.com/protocol-buffers/">Protocol
Buffers</a> implementation for the <a
href="https://www.rust-lang.org/">Rust Language</a>. <code>prost</code>
generates simple, idiomatic Rust code from <code>proto2</code> and
<code>proto3</code> files.</p>
<h2>Bug fixes</h2>
<ul>
<li>Enum variant named Error causes ambiguous item (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1098">#1098</a>)</li>
</ul>
<h1>PROST version 0.13.0</h1>
<p><strong>note</strong>: this version was yanked in favor of 0.13.1</p>
<p><em>PROST!</em> is a <a
href="https://developers.google.com/protocol-buffers/">Protocol
Buffers</a> implementation for the <a
href="https://www.rust-lang.org/">Rust Language</a>. <code>prost</code>
generates simple, idiomatic Rust code from <code>proto2</code> and
<code>proto3</code> files.</p>
<p>This major update brings new features and fixes:</p>
<h2>Breaking changes</h2>
<ul>
<li>
<p>derive Copy trait for messages where possible (<a
href="https://redirect.github.com/tokio-rs/prost/issues/950">#950</a>)</p>
<p><code>prost-build</code> will automatically derive <code>trait
Copy</code> for some messages. If you manually implement
<code>Copy</code> you should remove your implementation.</p>
</li>
<li>
<p>Change generated functions signatures to remove type parameters (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1045">#1045</a>)</p>
<p>The function signature of <code>trait Message</code> is changed to
use <code>impl Buf</code> instead of a named generic type. If you
implement <code>trait Message</code>, you should change the function
signature.</p>
</li>
<li>
<p>Lightweight error value in TryFrom<!-- raw HTML omitted --> for enums
(<a
href="https://redirect.github.com/tokio-rs/prost/issues/1010">#1010</a>)</p>
<p>When a <code>impl TryFrom&lt;i32&gt;</code> is generated by
<code>prost</code> derive macros, it will now return the error type
<code>UnknownEnumValue</code> instead of <code>DecodeError</code>. The
new error can be used to retreive the integer value that failed to
convert.</p>
</li>
</ul>
<h2>Features</h2>
<ul>
<li>
<p>fix: Only touch include file if contents is changed (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1058">#1058</a>)</p>
<p>Most generated files are untouched when the contents doesn't change.
Use the same mechanism for include file as well.</p>
</li>
</ul>
<h2>Dependencies</h2>
<ul>
<li>update env_logger requirement from 0.10 to 0.11 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1074">#1074</a>)</li>
<li>update criterion requirement from 0.4 to 0.5 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1071">#1071</a>)</li>
<li>Remove unused libz-sys (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1077">#1077</a>)</li>
<li>build(deps): update itertools requirement from &gt;=0.10, <!-- raw
HTML omitted -->=0.10, &lt;=0.13 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1070">#1070</a>)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li>better checking of tag duplicates, avoid discarding invalid variant
errs (<a
href="https://redirect.github.com/tokio-rs/prost/issues/951">#951</a>)</li>
<li>docs: Fix broken link warnings (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1056">#1056</a>)</li>
<li>Add missing LICENSE symlink (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1086">#1086</a>)</li>
</ul>
<h2>Internal</h2>
<ul>
<li>workspace package metadata (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1036">#1036</a>)</li>
<li>fix: Build error due to merge conflict (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1068">#1068</a>)</li>
<li>build: Fix release scripts (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1055">#1055</a>)</li>
<li>chore: Add ci to check MSRV (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1057">#1057</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f19104a3d4"><code>f19104a</code></a>
chore: prepare 0.13.1 release (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1099">#1099</a>)</li>
<li><a
href="26463f437e"><code>26463f4</code></a>
fix: Enum variant named <code>Error</code> causes ambiguous item (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1098">#1098</a>)</li>
<li><a
href="23f7174969"><code>23f7174</code></a>
chore: Release version 0.13.0 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1093">#1093</a>)</li>
<li><a
href="7a1424cf8c"><code>7a1424c</code></a>
build: Fix prepare-release.sh (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1094">#1094</a>)</li>
<li><a
href="7790799b0d"><code>7790799</code></a>
build(deps): update itertools requirement from &gt;=0.10, &lt;=0.12 to
&gt;=0.10, &lt;=0....</li>
<li><a
href="4a0cc17102"><code>4a0cc17</code></a>
Add missing LICENSE symlink (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1086">#1086</a>)</li>
<li><a
href="ae33a5ea97"><code>ae33a5e</code></a>
ci: Set rust version of clippy job to a fixed version (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1090">#1090</a>)</li>
<li><a
href="ba77654083"><code>ba77654</code></a>
fix: Only touch include file if contents is changed (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1058">#1058</a>)</li>
<li><a
href="e7049d3eb2"><code>e7049d3</code></a>
workspace package metadata (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1036">#1036</a>)</li>
<li><a
href="ef4930c140"><code>ef4930c</code></a>
docs: Fix broken link warnings (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1056">#1056</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/prost/compare/v0.12.0...v0.13.1">compare
view</a></li>
</ul>
</details>
<br />


You can trigger a rebase of this PR 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>

> **Note**
> Automatic rebases have been disabled on this pull request as it has
been open for over 30 days.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 12:49:18 -07:00
dependabot[bot]
7c60b9340a chore(deps): Update cbindgen requirement from 0.26 to 0.27 (#752)
Updates the requirements on
[cbindgen](https://github.com/mozilla/cbindgen) to permit the latest
version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/mozilla/cbindgen/blob/master/CHANGES">cbindgen's
changelog</a>.</em></p>
<blockquote>
<h1>0.27.0</h1>
<pre><code>  * Revert: The `Config` struct now has a private member.
* Allow users to specify a crate version for bindings generation
([#901](https://github.com/mozilla/cbindgen/issues/901)).
* Update MSRV to 1.74
([#912](https://github.com/mozilla/cbindgen/issues/912),
[#987](https://github.com/mozilla/cbindgen/issues/987)).
* Support #[deprecated] on enum variants
([#933](https://github.com/mozilla/cbindgen/issues/933)).
* Support integrating the package_version information in a header file
comment ([#939](https://github.com/mozilla/cbindgen/issues/939)).
* Add a language backend
([#942](https://github.com/mozilla/cbindgen/issues/942)).
* Support generics with defaulted args
([#959](https://github.com/mozilla/cbindgen/issues/959)).
* Add `VaList` compatibility
([#970](https://github.com/mozilla/cbindgen/issues/970)).
</code></pre>
<h1>0.26.0</h1>
<pre><code>  * Fix swapping of `&gt;&gt;=` and `&lt;&lt;=` in constants.
* Add support for #[deprecated]
([#860](https://github.com/mozilla/cbindgen/issues/860)).
  * Built-in support for bitflags 2.0.
  * Support for &quot;C-unwind&quot; ABI.
* Generate bindings for non-public extern items if they are
#[no_mangle].
</code></pre>
<h2>0.25.0</h2>
<pre><code>  * Re-release of yanked 0.24.6 as a major release
  * Update MSRV to 1.57
* Support variadic arguments (`...`)
([#805](https://github.com/mozilla/cbindgen/issues/805))
* Add --depfile option
([#820](https://github.com/mozilla/cbindgen/issues/820))
  * Breaking changes: The `Config` struct now has a private member.
</code></pre>
<h2>0.24.6 (YANKED: depfile option was breaking, see <a
href="https://redirect.github.com/mozilla/cbindgen/issues/841">#841</a>)</h2>
<pre><code>  * Update MSRV to 1.57
* Support variadic arguments (`...`)
([#805](https://github.com/mozilla/cbindgen/issues/805))
* Add --depfile option
([#820](https://github.com/mozilla/cbindgen/issues/820))
</code></pre>
<h2>0.24.5</h2>
<pre><code>  * Don't enforce tempfile version.
</code></pre>
<h2>0.24.4</h2>
<pre><code> * Move expand infinite recursion fix
([#799](https://github.com/mozilla/cbindgen/issues/799))
* Add with_cpp_compat to the builder
([#796](https://github.com/mozilla/cbindgen/issues/796))
* Handle never type in return position consistently
([#780](https://github.com/mozilla/cbindgen/issues/780))
* Fix warnings ([#816](https://github.com/mozilla/cbindgen/issues/816),
[#819](https://github.com/mozilla/cbindgen/issues/819))
* Updated documentation
([#788](https://github.com/mozilla/cbindgen/issues/788),
[#791](https://github.com/mozilla/cbindgen/issues/791),
[#792](https://github.com/mozilla/cbindgen/issues/792),
[#810](https://github.com/mozilla/cbindgen/issues/810),
[#823](https://github.com/mozilla/cbindgen/issues/823))
</code></pre>
<h2>0.24.3</h2>
<pre><code> * Make struct expressions correctly generated through
typedefs ([#768](https://github.com/mozilla/cbindgen/issues/768)).
</code></pre>
<h2>0.24.2</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="58c6156b0d"><code>58c6156</code></a>
Bump MSRV again for clap.</li>
<li><a
href="103a8de0ac"><code>103a8de</code></a>
Cargo update and version bump.</li>
<li><a
href="67cb560430"><code>67cb560</code></a>
Update CHANGES since v0.26.0</li>
<li><a
href="316298182e"><code>3162981</code></a>
Output condition for globals.</li>
<li><a
href="e469e44c00"><code>e469e44</code></a>
Add missing parens to log message</li>
<li><a
href="3cbb637bbf"><code>3cbb637</code></a>
Update cython expectations from the previous patch.</li>
<li><a
href="785e066e03"><code>785e066</code></a>
Fix variadic arguments when used in function pointer</li>
<li><a
href="aa8ea654e1"><code>aa8ea65</code></a>
deps: Update syn to 2.0</li>
<li><a
href="9f632843b8"><code>9f63284</code></a>
Generalize Item to expose documentation and generic params</li>
<li><a
href="1dda6162e5"><code>1dda616</code></a>
Used documented method of specifying variadic arguments</li>
<li>Additional commits viewable in <a
href="https://github.com/mozilla/cbindgen/compare/0.26.0...v0.27.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-08-22 12:27:18 -07:00
Gavin Hayes
00074fd56d feat: add releasing x86_64-unknown-linux-musl dynamic library (#753)
Fixes https://github.com/extism/extism/issues/735
2024-08-19 14:46:08 -04:00
zach
f0c9640e1e cleanup: allow shadowing host functions (#751) 2024-08-08 13:20:40 -07:00
innuendo
d48dc4021c feat(runtime): support log_trace in rust-sdk (#747)
Add support for logtrace in Rust SDK runtime.

Fix `clippy::needless_borrows_for_generic_args` lints.

Ref: https://github.com/extism/extism/issues/744

Co-authored-by: corda.ilaria@gmail.com <ilariac691@gmail.com>
Co-authored-by: Steve Manuel <steve@dylibso.com>
Co-authored-by: Chris Dickinson <chris@dylibso.com>
2024-07-30 11:53:23 -07:00
Steve Manuel
10e44c0006 chore: use more comprehensive crate source 2024-07-30 10:07:49 -06:00
Muhammad Azeez
b7fa319cb9 Add readonly dirs to allowed_paths (#733)
This is a rough POC for allowing people to whitelist a dir as readonly.
When the source path is prefixed with `ro:`, the dir is considered as
readonly. This preserved backward compatibility. This suggestion came up
in https://github.com/extism/go-sdk/pull/1#discussion_r1276700587

Readonly:
```rs
let manifest = Manifest::new([url])
        .with_allowed_path("ro:D:/x/rust/fs/data".to_string(), "/data")
        .with_config_key("path", "/data/data.txt");
```

```
trying to read file:
"Hello World at 1719851282.5109031sHello World at 1719851299.0819795sHello World at 1719851317.8934608s\n"
-----------------------------------------------------
trying to write file:
thread '<unnamed>' panicked at src\lib.rs:24:34:
called `Result::unwrap()` on an `Err` value: Os { code: 58, kind: Unsupported, message: "Not supported" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at runtime\examples\fs.rs:27:6:
called `Result::unwrap()` on an `Err` value: error while executing at wasm backtrace:
    0: 0x234d2 - fs.wasm!__rust_start_panic
    1: 0x232a1 - fs.wasm!rust_panic
    2: 0x231da - fs.wasm!std::panicking::rust_panic_with_hook::hd3fb69bc0aea298a
    3: 0x22467 - fs.wasm!std::panicking::begin_panic_handler::{{closure}}::h4d99b90b43f79472
    4: 0x223ca - fs.wasm!std::sys_common::backtrace::__rust_end_short_backtrace::h5691573a73161cb1
    5: 0x22bca - fs.wasm!rust_begin_unwind
    6: 0x303e9 - fs.wasm!core::panicking::panic_fmt::hdb62f5cdb45533e4
    7: 0x3234d - fs.wasm!core::result::unwrap_failed::h30d23efcc9e41efc
    8: 0x36c2 - fs.wasm!fs::try_write::inner::h0b3b0df8e129f5cc
    9: 0x29cd - fs.wasm!try_write
   10: 0x35e4a - fs.wasm!try_write.command_export
note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable may show more debugging information

Caused by:
    wasm trap: wasm `unreachable` instruction executed

Stack backtrace:
   0: std::backtrace_rs::backtrace::dbghelp64::trace
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\std\src\..\..\backtrace\src\backtrace\dbghelp64.rs:99
   1: std::backtrace_rs::backtrace::trace_unsynchronized
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66
   2: std::backtrace::Backtrace::create
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\std\src\backtrace.rs:331
   3: std::backtrace::Backtrace::capture
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\std\src\backtrace.rs:296
   4: anyhow::error::impl$1::from<wasmtime_environ::trap_encoding::Trap>
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\anyhow-1.0.86\src\error.rs:565
   5: core::convert::impl$3::into<wasmtime_environ::trap_encoding::Trap,anyhow::Error>
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\convert\mod.rs:759
   6: wasmtime_environ::impl$1::into_anyhow<wasmtime_environ::trap_encoding::Trap>
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-environ-22.0.0\src\lib.rs:90
   7: wasmtime::runtime::trap::from_runtime_box
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-22.0.0\src\runtime\trap.rs:118
   8: wasmtime::runtime::func::invoke_wasm_and_catch_traps::closure$0<extism::current_plugin::CurrentPlugin,wasmtime::runtime::func::impl$1::call_unchecked_raw::closure_env$0<extism::current_plugin::CurrentPlugin> >
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-22.0.0\src\runtime\func.rs:1597
   9: enum2$<core::result::Result<tuple$<>,alloc::boxed::Box<wasmtime::runtime::vm::traphandlers::Trap,alloc::alloc::Global> > >::map_err<tuple$<>,alloc::boxed::Box<wasmtime::runtime::vm::traphandlers::Trap,alloc::alloc::Global>,anyhow::Error,wasmtime::runtime:
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\result.rs:829
  10: wasmtime::runtime::func::invoke_wasm_and_catch_traps<extism::current_plugin::CurrentPlugin,wasmtime::runtime::func::impl$1::call_unchecked_raw::closure_env$0<extism::current_plugin::CurrentPlugin> >
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-22.0.0\src\runtime\func.rs:1597
  11: wasmtime::runtime::func::Func::call_unchecked_raw<extism::current_plugin::CurrentPlugin>
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-22.0.0\src\runtime\func.rs:1063
  12: wasmtime::runtime::func::Func::call_unchecked<ref_mut$<wasmtime::runtime::store::context::StoreContextMut<extism::current_plugin::CurrentPlugin> > >
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-22.0.0\src\runtime\func.rs:1049
  13: wasmtime::runtime::func::Func::call_impl_do_call<extism::current_plugin::CurrentPlugin>
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-22.0.0\src\runtime\func.rs:1243
  14: wasmtime::runtime::func::Func::call<ref_mut$<wasmtime::runtime::store::Store<extism::current_plugin::CurrentPlugin> > >
             at C:\Users\muham\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-22.0.0\src\runtime\func.rs:1002
  15: extism::plugin::Plugin::raw_call<ref$<str$>,ref$<str$> >
             at .\src\plugin.rs:753
  16: extism::plugin::Plugin::call<ref$<str$>,ref$<str$>,ref$<str$> >
             at .\src\plugin.rs:900
  17: fs::main
             at .\examples\fs.rs:25
  18: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\ops\function.rs:250
  19: core::hint::black_box
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\hint.rs:337
  20: std::sys_common::backtrace::__rust_begin_short_backtrace<void (*)(),tuple$<> >
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\std\src\sys_common\backtrace.rs:155
  21: std::rt::lang_start::closure$0<tuple$<> >
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\std\src\rt.rs:166
  22: std::rt::lang_start_internal
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\std\src\rt.rs:148
  23: std::rt::lang_start<tuple$<> >
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\std\src\rt.rs:165
  24: main
  25: invoke_main
             at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
  26: __scrt_common_main_seh
             at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
  27: BaseThreadInitThunk
  28: RtlUserThreadStart
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\std\src\panicking.rs:645
   1: core::panicking::panic_fmt
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\core\src\panicking.rs:72
   2: core::result::unwrap_failed
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library\core\src\result.rs:1654
   3: enum2$<core::result::Result<ref$<str$>,anyhow::Error> >::unwrap
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\result.rs:1077
   4: fs::main
             at .\examples\fs.rs:25
   5: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\ops\function.rs:250
   6: core::hint::black_box
             at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6\library\core\src\hint.rs:337
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
error: process didn't exit successfully: `D:\dylibso\extism\target\debug\examples\fs.exe` (exit code: 101)
```

Writable:
```rs
    let manifest = Manifest::new([url])
        .with_allowed_path("D:/x/rust/fs/data".to_string(), "/data")
        .with_config_key("path", "/data/data.txt");

```

```
trying to read file:
"Hello World at 1719851282.5109031sHello World at 1719851299.0819795sHello World at 1719851317.8934608s\n"
-----------------------------------------------------
trying to write file:
"Hello World at 1719851282.5109031sHello World at 1719851299.0819795sHello World at 1719851317.8934608s\nHello World at 1719851500.7803263s\n"
done!
```
2024-07-25 19:40:38 +03:00
zach
d04e2e42bf v1.5.0 2024-07-23 11:07:02 -07:00
zach
6d2735cec7 fix: require error messages to be null terminated in C SDK (#745)
Related to https://github.com/extism/python-sdk/issues/23 - there is
currently no way to get the length of the error message, so we need to
make sure it is a valid C string.
2024-07-23 09:35:59 -07:00
zach
b1d0f335b3 doc: fix usage of host_fn macro in doc example (#742) 2024-07-16 16:40:27 -07:00
Steve Manuel
3a7768ffd5 chore: update readme with crate version (#738) 2024-07-11 07:58:06 -05:00
zach
ee8c41ab26 doc: more information about error_set (#737) 2024-07-10 12:43:37 -07:00
zach
8312e98463 test: add benchmark for creating a plugin with the cache disabled (#736) 2024-07-10 11:18:08 -07:00
zach
17a546b2db chore: support for wasmtime 22 (#731) 2024-06-21 18:17:28 -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
47 changed files with 1797 additions and 622 deletions

1
.github/CODEOWNERS vendored Normal file
View File

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

View File

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

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

@@ -61,10 +61,10 @@ jobs:
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-musl'
artifact: ''
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-gnu'

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)
![Downloads](https://img.shields.io/crates/d/extism)
![Downloads](https://img.shields.io/crates/d/extism-manifest)
![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) |
@@ -60,7 +61,7 @@ started:
# Compile WebAssembly to run in Extism Hosts
Extism Hosts (running the SDK) must execute WebAssembly code that has a PDK
Extism Hosts (running the SDK) must execute WebAssembly code that has a [PDK, or Plug-in Development Kit](https://extism.org/docs/concepts/pdk),
library compiled in to the `.wasm` binary. PDKs make it easy for plug-in /
extension code authors to read input from the host and return data back, read
provided configuration, set/get variables, make outbound HTTP calls if allowed,

View File

@@ -13,7 +13,7 @@ description = "Traits to make Rust types usable with Extism"
anyhow = "1.0.75"
base64 = "~0.22"
bytemuck = {version = "1.14.0", optional = true }
prost = { version = "0.12.0", optional = true }
prost = { version = "0.13.1", optional = true }
protobuf = { version = "3.2.0", optional = true }
rmp-serde = { version = "1.1.2", optional = true }
serde = "1.0.186"

View File

@@ -81,10 +81,10 @@ mod tests {
c: true,
};
let raw = Raw(&x).to_bytes().unwrap();
let y = Raw::from_bytes(&raw).unwrap();
let y = Raw::from_bytes(raw).unwrap();
assert_eq!(&x, y.0);
let y: Result<Raw<[u8; std::mem::size_of::<TestRaw>()]>, Error> = Raw::from_bytes(&raw);
let y: Result<Raw<[u8; std::mem::size_of::<TestRaw>()]>, Error> = Raw::from_bytes(raw);
assert!(y.is_ok());
}
}

View File

@@ -17,6 +17,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,3 @@
(module
(global (export "extism_context") (mut externref) (ref.null extern))
)

View File

@@ -255,15 +255,6 @@ impl MemoryRoot {
pub unsafe fn alloc(&mut self, length: u64) -> Option<&'static mut MemoryBlock> {
let self_position = self.position.load(Ordering::Acquire);
let self_length = self.length.load(Ordering::Acquire);
let b = self.find_free_block(length, self_position);
// If there's a free block then re-use it
if let Some(b) = b {
b.used = length as usize;
b.status
.store(MemoryStatus::Active as u8, Ordering::Release);
return Some(b);
}
// Get the current index for a new block
let curr = self.blocks.as_ptr() as u64 + self_position;
@@ -275,6 +266,21 @@ impl MemoryRoot {
// When the allocation is larger than the number of bytes available
// we will need to try to grow the memory
if length_with_block >= mem_left {
// If the current position is large enough to hold the length of the block being
// allocated then check for existing free blocks that can be re-used before
// growing memory
if length_with_block <= self_position {
let b = self.find_free_block(length, self_position);
// If there's a free block then re-use it
if let Some(b) = b {
b.used = length as usize;
b.status
.store(MemoryStatus::Active as u8, Ordering::Release);
return Some(b);
}
}
// Calculate the number of pages needed to cover the remaining bytes
let npages = num_pages(length_with_block - mem_left);
let x = core::arch::wasm32::memory_grow(0, npages);
@@ -489,6 +495,8 @@ pub unsafe fn store_u64(p: Pointer, x: u64) {
/// Set the range of the input data in memory
/// h must always be a handle so that length works on it
/// len must match length(handle)
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
#[no_mangle]
pub unsafe fn input_set(h: Handle, len: u64) {
let root = MemoryRoot::new();
@@ -503,6 +511,8 @@ pub unsafe fn input_set(h: Handle, len: u64) {
}
/// Set the range of the output data in memory
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
#[no_mangle]
pub unsafe fn output_set(p: Pointer, len: u64) {
let root = MemoryRoot::new();
@@ -546,7 +556,10 @@ pub unsafe fn reset() {
MemoryRoot::new().reset()
}
/// Set the error message offset
/// Set the error message offset, the handle passed to this
/// function should not be freed after this call
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
#[no_mangle]
pub unsafe fn error_set(h: Handle) {
let root = MemoryRoot::new();
@@ -581,45 +594,6 @@ mod test {
use crate::*;
use wasm_bindgen_test::*;
// See https://github.com/extism/extism/pull/659
#[wasm_bindgen_test]
fn test_659() {
unsafe {
// Warning: These offsets will need to change if we adjust the kernel memory layout at all
reset();
assert_eq!(alloc(1065), 77);
assert_eq!(alloc(288), 1154);
assert_eq!(alloc(128), 1454);
assert_eq!(length(1154), 288);
assert_eq!(length(1454), 128);
free(1454);
assert_eq!(alloc(213), 1594);
length_unsafe(1594);
assert_eq!(alloc(511), 1819);
assert_eq!(alloc(4), 1454);
assert_eq!(length(1454), 4);
assert_eq!(length(1819), 511);
assert_eq!(alloc(13), 2342);
assert_eq!(length(2342), 13);
assert_eq!(alloc(336), 2367);
assert_eq!(alloc(1077), 2715);
assert_eq!(length(2367), 336);
assert_eq!(length(2715), 1077);
free(2715);
assert_eq!(alloc(1094), 3804);
length_unsafe(3804);
// Allocate 4 bytes, expect to receive address 3788
assert_eq!(alloc(4), 3788);
assert_eq!(alloc(4), 3772);
assert_eq!(length(3772), 4);
// Address 3788 has not been freed yet, so expect it to have 4 bytes allocated
assert_eq!(length(3788), 4);
}
}
#[wasm_bindgen_test]
fn test_oom() {
let size = 1024 * 1024 * 5;

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,6 @@ 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
@@ -62,7 +61,6 @@ pub struct HttpRequest {
/// Request headers
#[serde(default)]
#[serde(alias = "header")]
pub headers: std::collections::BTreeMap<String, String>,
/// Request method
@@ -281,7 +279,7 @@ pub struct Manifest {
/// the path on disk to the path it should be available inside the plugin.
/// For example, `".": "/tmp"` would mount the current directory as `/tmp` inside the module
#[serde(default)]
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
pub allowed_paths: Option<BTreeMap<String, PathBuf>>,
/// The plugin timeout in milliseconds
#[serde(default)]
@@ -339,8 +337,7 @@ impl Manifest {
}
/// Add a path to `allowed_paths`
pub fn with_allowed_path(mut self, src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Self {
let src = src.as_ref().to_path_buf();
pub fn with_allowed_path(mut self, src: String, dest: impl AsRef<Path>) -> Self {
let dest = dest.as_ref().to_path_buf();
match &mut self.allowed_paths {
Some(p) => {
@@ -357,7 +354,7 @@ impl Manifest {
}
/// Set `allowed_paths`
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (PathBuf, PathBuf)>) -> Self {
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (String, PathBuf)>) -> Self {
self.allowed_paths = Some(paths.collect());
self
}

View File

@@ -9,15 +9,16 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = ">= 14.0.0, < 18.0.0"
wasmtime-wasi = ">= 14.0.0, < 18.0.0"
wasmtime = {version = ">= 26.0.0, < 27.0.0"}
wasi-common = {version = ">= 26.0.0, < 27.0.0"}
wiggle = {version = ">= 26.0.0, < 27.0.0"}
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}
@@ -33,7 +34,7 @@ register-filesystem = [] # enables wasm to be loaded from disk
http = ["ureq"] # enables extism_http_request
[build-dependencies]
cbindgen = { version = "0.26", default-features = false }
cbindgen = { version = "0.27", default-features = false }
[dev-dependencies]
criterion = "0.5.1"

View File

@@ -12,7 +12,7 @@ To use the `extism` crate, you can add it to your Cargo file:
```toml
[dependencies]
extism = "1.0.0"
extism = "1.4.1"
```
## 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

@@ -6,6 +6,7 @@ const COUNT_VOWELS: &[u8] = include_bytes!("../../wasm/code.wasm");
const REFLECT: &[u8] = include_bytes!("../../wasm/reflect.wasm");
const ECHO: &[u8] = include_bytes!("../../wasm/echo.wasm");
const CONSUME: &[u8] = include_bytes!("../../wasm/consume.wasm");
const ALLOCATIONS: &[u8] = include_bytes!("../../wasm/allocations.wasm");
host_fn!(hello_world (a: String) -> String { Ok(a) });
@@ -35,6 +36,46 @@ pub fn create_plugin(c: &mut Criterion) {
});
}
pub fn create_compiled(c: &mut Criterion) {
let mut g = c.benchmark_group("create");
g.noise_threshold(1.0);
g.significance_level(0.2);
g.bench_function("create_compiled", |b| {
b.iter(|| {
let plugin = PluginBuilder::new(COUNT_VOWELS).with_wasi(true);
let _compiled = CompiledPlugin::new(plugin).unwrap();
})
});
}
pub fn create_plugin_compiled(c: &mut Criterion) {
let mut g = c.benchmark_group("create");
g.noise_threshold(1.0);
g.significance_level(0.2);
let plugin = PluginBuilder::new(COUNT_VOWELS).with_wasi(true);
let compiled = CompiledPlugin::new(plugin).unwrap();
g.bench_function("create_plugin_compiled", |b| {
b.iter(|| {
let _plugin = Plugin::new_from_compiled(&compiled).unwrap();
})
});
}
pub fn create_plugin_no_cache(c: &mut Criterion) {
let mut g = c.benchmark_group("create");
g.noise_threshold(1.0);
g.significance_level(0.2);
g.bench_function("create_plugin_no_cache", |b| {
b.iter(|| {
let _plugin = PluginBuilder::new(COUNT_VOWELS)
.with_cache_disabled()
.with_wasi(true)
.build()
.unwrap();
})
});
}
#[derive(Debug, serde::Deserialize, PartialEq)]
struct Count {
count: u32,
@@ -153,6 +194,17 @@ pub fn reflect(c: &mut Criterion) {
}
}
pub fn allocations(c: &mut Criterion) {
let mut g = c.benchmark_group("allocations");
let mut plugin = PluginBuilder::new(ALLOCATIONS).build().unwrap();
g.bench_function("allocations", |b| {
b.iter(|| {
plugin.call::<_, ()>("allocations", "").unwrap();
})
});
}
// This is an apples-to-apples comparison of a linked wasm "reflect" function to our host "reflect"
// function.
pub fn reflect_linked(c: &mut Criterion) {
@@ -245,12 +297,16 @@ pub fn reflect_linked(c: &mut Criterion) {
criterion_group!(
benches,
allocations,
consume,
echo,
reflect,
reflect_linked,
basic,
create_plugin,
create_plugin_compiled,
create_plugin_no_cache,
create_compiled,
count_vowels
);
criterion_main!(benches);

View File

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

36
runtime/examples/fs.rs Normal file
View File

@@ -0,0 +1,36 @@
use extism::*;
fn main() {
let url = Wasm::file("../wasm/read_write.wasm");
let manifest = Manifest::new([url])
// This will fail because we're using a readonly path (specified with the `ro:` prefix)
// to overwrite the data file, remove `ro:` from the path on the following line
.with_allowed_path("ro:src/tests/data".to_string(), "/data")
.with_config_key("path", "/data/data.txt");
let mut plugin = PluginBuilder::new(manifest)
.with_wasi(true)
.build()
.unwrap();
println!("trying to read file: ");
let res = plugin.call::<&str, &str>("try_read", "").unwrap();
println!("{:?}", res);
println!("-----------------------------------------------------");
// If the allowed path is readonly then writing back to the file should fail
println!("trying to write file: ");
let line = format!(
"Hello World at {:?}\n",
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
);
let res2 = plugin.call::<&str, &str>("try_write", &line).unwrap();
println!("{:?}", res2);
println!("done!");
}

View File

@@ -2,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

@@ -1,7 +1,7 @@
use extism::*;
// pretend this is redis or something :)
type KVStore = std::sync::Arc<std::sync::Mutex<std::collections::BTreeMap<String, Vec<u8>>>>;
type KVStore = std::collections::BTreeMap<String, Vec<u8>>;
// When a first argument separated with a semicolon is provided to `host_fn` it is used as the
// variable name and type for the `UserData` parameter

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;
/**
@@ -52,6 +52,8 @@ typedef enum {
*/
typedef struct ExtismCancelHandle ExtismCancelHandle;
typedef struct ExtismCompiledPlugin ExtismCompiledPlugin;
/**
* CurrentPlugin stores data that is available to the caller in PDK functions, this should
* only be accessed from inside a host function
@@ -113,6 +115,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.
@@ -173,6 +181,21 @@ void extism_function_free(ExtismFunction *f);
*/
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
/**
* Pre-compile an Extism plugin
*/
ExtismCompiledPlugin *extism_compiled_plugin_new(const uint8_t *wasm,
ExtismSize wasm_size,
const ExtismFunction **functions,
ExtismSize n_functions,
bool with_wasi,
char **errmsg);
/**
* Free `ExtismCompiledPlugin`
*/
void extism_compiled_plugin_free(ExtismCompiledPlugin *plugin);
/**
* Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
*
@@ -189,13 +212,34 @@ ExtismPlugin *extism_plugin_new(const uint8_t *wasm,
bool with_wasi,
char **errmsg);
/**
* Create a new plugin from an `ExtismCompiledPlugin`
*/
ExtismPlugin *extism_plugin_new_from_compiled(const ExtismCompiledPlugin *compiled, char **errmsg);
/**
* Create a new plugin and set the number of instructions a plugin is allowed to execute
*/
ExtismPlugin *extism_plugin_new_with_fuel_limit(const uint8_t *wasm,
ExtismSize wasm_size,
const ExtismFunction **functions,
ExtismSize n_functions,
bool with_wasi,
uint64_t fuel_limit,
char **errmsg);
/**
* Enable HTTP response headers in plugins using `extism:host/env::http_request`
*/
void extism_plugin_allow_http_response_headers(ExtismPlugin *plugin);
/**
* Free the error returned by `extism_plugin_new`, errors returned from `extism_plugin_error` don't need to be freed
*/
void extism_plugin_new_error_free(char *err);
/**
* Remove a plugin from the registry and free associated memory
* Free `ExtismPlugin`
*/
void extism_plugin_free(ExtismPlugin *plugin);
@@ -231,6 +275,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`
*/
@@ -282,5 +340,5 @@ bool extism_plugin_reset(ExtismPlugin *plugin);
const char *extism_version(void);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
} // extern "C"
#endif // __cplusplus

View File

@@ -1,3 +1,5 @@
use anyhow::Context;
use crate::*;
/// CurrentPlugin stores data that is available to the caller in PDK functions, this should
@@ -12,9 +14,11 @@ pub struct CurrentPlugin {
pub(crate) linker: *mut wasmtime::Linker<CurrentPlugin>,
pub(crate) wasi: Option<Wasi>,
pub(crate) http_status: u16,
pub(crate) http_headers: Option<std::collections::BTreeMap<String, String>>,
pub(crate) available_pages: Option<u32>,
pub(crate) memory_limiter: Option<MemoryLimiter>,
pub(crate) id: uuid::Uuid,
pub(crate) start_time: std::time::Instant,
}
unsafe impl Send for CurrentPlugin {}
@@ -52,7 +56,12 @@ impl wasmtime::ResourceLimiter for MemoryLimiter {
Ok(true)
}
fn table_growing(&mut self, _current: u32, desired: u32, maximum: Option<u32>) -> Result<bool> {
fn table_growing(
&mut self,
_current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool> {
if let Some(max) = maximum {
return Ok(desired <= max);
}
@@ -62,6 +71,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 {
@@ -154,10 +168,10 @@ impl CurrentPlugin {
}
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 (linker, store) = self.linker_and_store();
if let Some(mem) = linker.get(&mut *store, EXTISM_ENV_MODULE, "memory") {
let mem = mem.into_memory().unwrap();
let ptr = unsafe { mem.data_ptr(&store).add(handle.offset() as usize) };
let ptr = unsafe { mem.data_ptr(&*store).add(handle.offset() as usize) };
if ptr.is_null() {
return Ok(&mut []);
}
@@ -168,10 +182,10 @@ impl CurrentPlugin {
}
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 (linker, store) = self.linker_and_store();
if let Some(mem) = linker.get(&mut *store, EXTISM_ENV_MODULE, "memory") {
let mem = mem.into_memory().unwrap();
let ptr = unsafe { mem.data_ptr(&store).add(handle.offset() as usize) };
let ptr = unsafe { mem.data_ptr(&*store).add(handle.offset() as usize) };
if ptr.is_null() {
return Ok(&[]);
}
@@ -181,6 +195,29 @@ impl CurrentPlugin {
anyhow::bail!("{} unable to locate extism memory", self.id)
}
pub fn host_context<T: 'static>(&mut self) -> Result<&mut T, Error> {
let (linker, store) = self.linker_and_store();
let Some(Extern::Global(xs)) = linker.get(&mut *store, EXTISM_ENV_MODULE, "extism_context")
else {
anyhow::bail!("unable to locate an extism kernel global: extism_context",)
};
let Val::ExternRef(Some(xs)) = xs.get(&mut *store) else {
anyhow::bail!("expected extism_context to be an externref value",)
};
match xs
.data_mut(&mut *store)?
.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>()
{
Some(xs) => match xs.downcast_mut::<T>() {
Some(xs) => Ok(xs),
None => anyhow::bail!("could not downcast extism_context inner value"),
},
None => anyhow::bail!("could not downcast extism_context"),
}
}
pub fn memory_alloc(&mut self, n: u64) -> Result<MemoryHandle, Error> {
if n == 0 {
return Ok(MemoryHandle {
@@ -191,9 +228,13 @@ impl CurrentPlugin {
let (linker, mut store) = self.linker_and_store();
let output = &mut [Val::I64(0)];
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "alloc") {
f.into_func()
.unwrap()
.call(&mut store, &[Val::I64(n as i64)], output)?;
catch_out_of_fuel!(
&store,
f.into_func()
.unwrap()
.call(&mut *store, &[Val::I64(n as i64)], output)
.context("failed to allocate extism memory")
)?;
} else {
anyhow::bail!("{} unable to allocate memory", self.id);
}
@@ -215,11 +256,15 @@ impl CurrentPlugin {
/// Free a block of Extism plugin memory
pub fn memory_free(&mut self, handle: MemoryHandle) -> Result<(), Error> {
let (linker, mut store) = self.linker_and_store();
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "free") {
f.into_func()
.unwrap()
.call(&mut store, &[Val::I64(handle.offset as i64)], &mut [])?;
let (linker, store) = self.linker_and_store();
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "free") {
catch_out_of_fuel!(
&store,
f.into_func()
.unwrap()
.call(&mut *store, &[Val::I64(handle.offset as i64)], &mut [])
.context("failed to free extism memory")
)?;
} else {
anyhow::bail!("unable to locate an extism kernel function: free",)
}
@@ -227,12 +272,16 @@ impl CurrentPlugin {
}
pub fn memory_length(&mut self, offs: u64) -> Result<u64, Error> {
let (linker, mut store) = self.linker_and_store();
let (linker, store) = self.linker_and_store();
let output = &mut [Val::I64(0)];
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "length") {
f.into_func()
.unwrap()
.call(&mut store, &[Val::I64(offs as i64)], output)?;
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "length") {
catch_out_of_fuel!(
&store,
f.into_func()
.unwrap()
.call(&mut *store, &[Val::I64(offs as i64)], output)
.context("failed to get length of extism memory handle")
)?;
} else {
anyhow::bail!("unable to locate an extism kernel function: length",)
}
@@ -247,12 +296,16 @@ impl CurrentPlugin {
}
pub fn memory_length_unsafe(&mut self, offs: u64) -> Result<u64, Error> {
let (linker, mut store) = self.linker_and_store();
let (linker, 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)?;
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "length_unsafe") {
catch_out_of_fuel!(
&store,
f.into_func()
.unwrap()
.call(&mut *store, &[Val::I64(offs as i64)], output)
.context("failed to get length of extism memory using length_unsafe")
)?;
} else {
anyhow::bail!("unable to locate an extism kernel function: length_unsafe",)
}
@@ -285,35 +338,51 @@ impl CurrentPlugin {
manifest: extism_manifest::Manifest,
wasi: bool,
available_pages: Option<u32>,
allow_http_response_headers: bool,
id: uuid::Uuid,
) -> Result<Self, Error> {
let wasi = if wasi {
let auth = 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 readonly = k.starts_with("ro:");
let dir_path = if readonly { &k[3..] } else { k };
let dir = wasi_common::sync::dir::Dir::from_cap_std(
wasi_common::sync::Dir::open_ambient_dir(dir_path, auth)?,
);
let file: Box<dyn wasi_common::dir::WasiDir> = if readonly {
Box::new(readonly_dir::ReadOnlyDir::new(dir))
} else {
Box::new(dir)
};
ctx.push_preopened_dir(file, v)?;
}
}
// 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
};
let memory_limiter = if let Some(pgs) = available_pages {
let n = pgs as usize * 65536;
Some(crate::current_plugin::MemoryLimiter {
Some(MemoryLimiter {
max_bytes: n,
bytes_left: n,
})
@@ -331,6 +400,12 @@ impl CurrentPlugin {
available_pages,
memory_limiter,
id,
start_time: std::time::Instant::now(),
http_headers: if allow_http_response_headers {
Some(BTreeMap::new())
} else {
None
},
})
}
@@ -375,12 +450,12 @@ impl CurrentPlugin {
/// Clear the current plugin error
pub fn clear_error(&mut self) {
trace!(plugin = self.id.to_string(), "CurrentPlugin::clear_error");
let (linker, mut store) = self.linker_and_store();
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "error_set") {
let (linker, store) = self.linker_and_store();
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "error_set") {
let res = f
.into_func()
.unwrap()
.call(&mut store, &[Val::I64(0)], &mut []);
.call(&mut *store, &[Val::I64(0)], &mut []);
if let Err(e) = res {
error!(
plugin = self.id.to_string(),
@@ -411,13 +486,15 @@ impl CurrentPlugin {
pub fn set_error(&mut self, s: impl AsRef<str>) -> Result<(u64, u64), Error> {
let s = s.as_ref();
debug!(plugin = self.id.to_string(), "set error: {:?}", s);
let handle = self.current_plugin_mut().memory_new(s)?;
let (linker, mut store) = self.linker_and_store();
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "error_set") {
f.into_func().unwrap().call(
&mut store,
&[Val::I64(handle.offset() as i64)],
&mut [],
let handle = self.memory_new(s)?;
let (linker, store) = self.linker_and_store();
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "error_set") {
catch_out_of_fuel!(
&store,
f.into_func()
.unwrap()
.call(&mut *store, &[Val::I64(handle.offset() as i64)], &mut [])
.context("failed to set extism error")
)?;
Ok((handle.offset(), s.len() as u64))
} else {
@@ -426,10 +503,13 @@ impl CurrentPlugin {
}
pub(crate) fn get_error_position(&mut self) -> (u64, u64) {
let (linker, mut store) = self.linker_and_store();
let (linker, store) = self.linker_and_store();
let output = &mut [Val::I64(0)];
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "error_get") {
if let Err(e) = f.into_func().unwrap().call(&mut store, &[], output) {
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "error_get") {
if let Err(e) = catch_out_of_fuel!(
&store,
f.into_func().unwrap().call(&mut *store, &[], output)
) {
error!(
plugin = self.id.to_string(),
"unable to call extism:host/env::error_get: {:?}", e
@@ -441,6 +521,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 +544,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) }
}

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

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>>),
}
@@ -81,18 +89,18 @@ pub(crate) enum UserDataHandle {
/// using `UserData::get`. The `C` data is stored as a pointer and cleanup function and isn't usable from Rust. The cleanup function
/// will be called when the inner `CPtr` is dropped.
#[derive(Debug)]
pub enum UserData<T: Sync + Clone + Sized> {
pub enum UserData<T: Sized> {
C(Arc<CPtr>),
Rust(T),
Rust(Arc<std::sync::Mutex<T>>),
}
impl<T: Default + Sync + Clone> Default for UserData<T> {
impl<T: Default> Default for UserData<T> {
fn default() -> Self {
UserData::new(T::default())
}
}
impl<T: Sync + Clone> Clone for UserData<T> {
impl<T> Clone for UserData<T> {
fn clone(&self) -> Self {
match self {
UserData::C(ptr) => UserData::C(ptr.clone()),
@@ -101,7 +109,7 @@ impl<T: Sync + Clone> Clone for UserData<T> {
}
}
impl<T: Sync + Clone> UserData<T> {
impl<T> UserData<T> {
/// Create a new `UserData` from an existing pointer and free function, this is used
/// by the C API to wrap C pointers into user data
pub(crate) fn new_pointer(
@@ -126,11 +134,12 @@ impl<T: Sync + Clone> UserData<T> {
///
/// This will wrap the provided value in a reference-counted mutex
pub fn new(x: T) -> Self {
UserData::Rust(x)
let data = Arc::new(std::sync::Mutex::new(x));
UserData::Rust(data)
}
/// Get a copy of the inner value
pub fn get(&self) -> Result<T, Error> {
pub fn get(&self) -> Result<Arc<std::sync::Mutex<T>>, Error> {
match self {
UserData::C { .. } => anyhow::bail!("C UserData should not be used from Rust"),
UserData::Rust(data) => Ok(data.clone()),
@@ -149,8 +158,8 @@ impl Drop for CPtr {
}
}
unsafe impl<T: Sync + Clone> Send for UserData<T> {}
unsafe impl<T: Sync + Clone> Sync for UserData<T> {}
unsafe impl<T> Send for UserData<T> {}
unsafe impl<T> Sync for UserData<T> {}
unsafe impl Send for CPtr {}
unsafe impl Sync for CPtr {}
@@ -167,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>,
@@ -179,10 +188,10 @@ pub struct Function {
impl Function {
/// Create a new host function
pub fn new<T: 'static + Sync + Clone, F>(
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
@@ -194,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();
@@ -210,13 +219,27 @@ impl Function {
namespace: None,
_user_data: match &user_data {
UserData::C(ptr) => UserDataHandle::C(ptr.clone()),
UserData::Rust(x) => {
UserDataHandle::Rust(std::sync::Arc::new(std::sync::Mutex::new(x.clone())))
}
UserData::Rust(x) => UserDataHandle::Rust(x.clone()),
},
}
}
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
@@ -240,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
}
}
@@ -251,7 +279,7 @@ impl Function {
/// For example, the following defines a host function named `add_newline` that takes a
/// string parameter and returns a string result:
/// ```rust
/// extism::host_fn!(add_newline(_user_data: (), a: String) -> String { Ok(a + "\n") });
/// extism::host_fn!(add_newline(_user_data: (); a: String) -> String { Ok(a + "\n") });
/// ```
/// A few things worth noting:
/// - The function always returns a `Result` that wraps the specified return type

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

@@ -1,6 +1,17 @@
// Makes proc-macros able to resolve `::extism` correctly
extern crate self as extism;
macro_rules! catch_out_of_fuel {
($store: expr, $x:expr) => {{
let y = $x;
if y.is_err() && $store.get_fuel().is_ok_and(|x| x == 0) {
Err(Error::msg("plugin ran out of fuel"))
} else {
y
}
}};
}
pub(crate) use extism_convert::*;
pub(crate) use std::collections::BTreeMap;
use std::str::FromStr;
@@ -18,6 +29,7 @@ pub(crate) mod manifest;
pub(crate) mod pdk;
mod plugin;
mod plugin_builder;
mod readonly_dir;
mod timer;
/// Extism C API
@@ -27,7 +39,9 @@ pub use current_plugin::CurrentPlugin;
pub use extism_convert::{FromBytes, FromBytesOwned, ToBytes};
pub use extism_manifest::{Manifest, Wasm, WasmMetadata};
pub use function::{Function, UserData, Val, ValType, PTR};
pub use plugin::{CancelHandle, Plugin, WasmInput, EXTISM_ENV_MODULE, EXTISM_USER_MODULE};
pub use plugin::{
CancelHandle, CompiledPlugin, Plugin, WasmInput, EXTISM_ENV_MODULE, EXTISM_USER_MODULE,
};
pub use plugin_builder::{DebugOptions, PluginBuilder};
pub(crate) use internal::{Internal, Wasi};
@@ -77,11 +91,16 @@ pub fn set_log_callback<F: 'static + Clone + Fn(&str)>(
filter: impl AsRef<str>,
) -> Result<(), Error> {
let filter = filter.as_ref();
let cfg = tracing_subscriber::FmtSubscriber::builder().with_env_filter(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::Level::ERROR.into())
.parse_lossy(filter),
);
let is_level = tracing::Level::from_str(filter).is_ok();
let cfg = tracing_subscriber::FmtSubscriber::builder().with_env_filter({
let x = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::Level::ERROR.into());
if is_level {
x.parse_lossy(format!("extism={}", filter))
} else {
x.parse_lossy(filter)
}
});
let w = LogFunction { func };
cfg.with_ansi(false)
.with_writer(move || w.clone())

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

@@ -20,6 +20,8 @@ macro_rules! args {
/// Get a configuration value
/// Params: i64 (offset)
/// Returns: i64 (offset)
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn config_get(
mut caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -38,6 +40,7 @@ pub(crate) fn config_get(
};
let val = data.manifest.config.get(key);
let ptr = val.map(|x| (x.len(), x.as_ptr()));
data.memory_free(handle)?;
let mem = match ptr {
Some((len, ptr)) => {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
@@ -55,6 +58,9 @@ pub(crate) fn config_get(
/// Get a variable
/// Params: i64 (offset)
/// Returns: i64 (offset)
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value, but the return value
/// will need to be freed
pub(crate) fn var_get(
mut caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -73,6 +79,8 @@ pub(crate) fn var_get(
};
let val = data.vars.get(key);
let ptr = val.map(|x| (x.len(), x.as_ptr()));
data.memory_free(handle)?;
let mem = match ptr {
Some((len, ptr)) => {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
@@ -90,6 +98,8 @@ pub(crate) fn var_get(
/// Set a variable, if the value offset is 0 then the provided key will be removed
/// Params: i64 (key offset), i64 (value offset)
/// Returns: none
/// **Note**: this function takes ownership of the handles passed in
/// the caller should not `free` these values
pub(crate) fn var_set(
mut caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -104,12 +114,12 @@ pub(crate) fn var_set(
let voffset = args!(input, 1, i64) as u64;
let key_offs = args!(input, 0, i64) as u64;
let key_handle = match data.memory_handle(key_offs) {
Some(h) => h,
None => anyhow::bail!("invalid handle offset for var key: {key_offs}"),
};
let key = {
let handle = match data.memory_handle(key_offs) {
Some(h) => h,
None => anyhow::bail!("invalid handle offset for var key: {key_offs}"),
};
let key = data.memory_str(handle)?;
let key = data.memory_str(key_handle)?;
let key_len = key.len();
let key_ptr = key.as_ptr();
unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(key_ptr, key_len)) }
@@ -118,6 +128,7 @@ pub(crate) fn var_set(
// Remove if the value offset is 0
if voffset == 0 {
data.vars.remove(key);
data.memory_free(key_handle)?;
return Ok(());
}
@@ -144,6 +155,9 @@ pub(crate) fn var_set(
let value = data.memory_bytes(handle)?.to_vec();
data.memory_free(handle)?;
data.memory_free(key_handle)?;
// Insert the value from memory into the `vars` map
data.vars.insert(key.to_string(), value);
@@ -153,6 +167,9 @@ pub(crate) fn var_set(
/// Make an HTTP request
/// Params: i64 (offset to JSON encoded HttpRequest), i64 (offset to body or 0)
/// Returns: i64 (offset)
/// **Note**: this function takes ownership of the handles passed in
/// the caller should not `free` these values, the result will need to
/// be freed.
pub(crate) fn http_request(
#[allow(unused_mut)] mut caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -166,6 +183,7 @@ pub(crate) fn http_request(
Some(h) => h,
None => anyhow::bail!("http_request input is invalid: {http_req_offset}"),
};
data.memory_free(handle)?;
let req: extism_manifest::HttpRequest = serde_json::from_slice(data.memory_bytes(handle)?)?;
output[0] = Val::I64(0);
anyhow::bail!(
@@ -176,12 +194,16 @@ pub(crate) fn http_request(
#[cfg(feature = "http")]
{
data.http_headers.iter_mut().for_each(|x| x.clear());
data.http_status = 0;
use std::io::Read;
let handle = match data.memory_handle(http_req_offset) {
Some(h) => h,
None => anyhow::bail!("invalid handle offset for http request: {http_req_offset}"),
};
let req: extism_manifest::HttpRequest = serde_json::from_slice(data.memory_bytes(handle)?)?;
data.memory_free(handle)?;
let body_offset = args!(input, 1, i64) as u64;
@@ -217,6 +239,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,
@@ -230,12 +257,29 @@ pub(crate) fn http_request(
r.call()
};
if let Some(handle) = data.memory_handle(body_offset) {
data.memory_free(handle)?;
}
let reader = match res {
Ok(res) => {
if let Some(headers) = &mut data.http_headers {
for name in res.headers_names() {
if let Some(h) = res.header(&name) {
headers.insert(name, h.to_string());
}
}
}
data.http_status = res.status();
Some(res.into_reader())
}
Err(e) => {
// Catch timeout and return
if let Some(d) = data.time_remaining() {
if e.kind() == ureq::ErrorKind::Io && d.as_nanos() == 0 {
anyhow::bail!("timeout");
}
}
let msg = e.to_string();
if let Some(res) = e.into_response() {
data.http_status = res.status();
@@ -283,6 +327,24 @@ pub(crate) fn http_status_code(
Ok(())
}
/// Get the HTTP response headers from the last HTTP request
/// Params: none
/// Returns: i64 (offset)
pub(crate) fn http_headers(
mut caller: Caller<CurrentPlugin>,
_input: &[Val],
output: &mut [Val],
) -> Result<(), Error> {
let data: &mut CurrentPlugin = caller.data_mut();
if let Some(h) = &data.http_headers {
let headers = serde_json::to_string(h)?;
data.memory_set_val(&mut output[0], headers)?;
} else {
output[0] = Val::I64(0);
}
Ok(())
}
pub fn log(
level: tracing::Level,
mut caller: Caller<CurrentPlugin>,
@@ -290,13 +352,22 @@ pub fn log(
_output: &mut [Val],
) -> Result<(), Error> {
let data: &mut CurrentPlugin = caller.data_mut();
let offset = args!(input, 0, i64) as u64;
// Check if the current log level should be logged
let global_log_level = tracing::level_filters::LevelFilter::current();
if global_log_level == tracing::level_filters::LevelFilter::OFF || level > global_log_level {
if let Some(handle) = data.memory_handle(offset) {
data.memory_free(handle)?;
}
return Ok(());
}
let handle = match data.memory_handle(offset) {
Some(h) => h,
None => anyhow::bail!("invalid handle offset for log message: {offset}"),
};
let id = data.id.to_string();
let buf = data.memory_str(handle);
@@ -320,12 +391,16 @@ pub fn log(
},
Err(_) => tracing::error!(plugin = id, "unable to log message: {:?}", buf),
}
data.memory_free(handle)?;
Ok(())
}
/// Write to logs (warning)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_warn(
caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -337,6 +412,8 @@ pub(crate) fn log_warn(
/// Write to logs (info)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_info(
caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -348,6 +425,8 @@ pub(crate) fn log_info(
/// Write to logs (debug)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_debug(
caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -359,6 +438,8 @@ pub(crate) fn log_debug(
/// Write to logs (error)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_error(
caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -366,3 +447,46 @@ pub(crate) fn log_error(
) -> Result<(), Error> {
log(tracing::Level::ERROR, caller, input, _output)
}
/// Write to logs (trace)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_trace(
caller: Caller<CurrentPlugin>,
input: &[Val],
_output: &mut [Val],
) -> Result<(), Error> {
log(tracing::Level::TRACE, caller, input, _output)
}
/// Get the log level
/// Params: none
/// Returns: i32 (log level)
pub(crate) fn get_log_level(
mut _caller: Caller<CurrentPlugin>,
_input: &[Val],
output: &mut [Val],
) -> Result<(), Error> {
let level = tracing::level_filters::LevelFilter::current();
if level == tracing::level_filters::LevelFilter::OFF {
output[0] = Val::I32(i32::MAX)
} else {
output[0] = Val::I32(log_level_to_int(
level.into_level().unwrap_or(tracing::Level::ERROR),
));
}
Ok(())
}
/// Convert log level to integer
pub(crate) const fn log_level_to_int(level: tracing::Level) -> i32 {
match level {
tracing::Level::TRACE => 0,
tracing::Level::DEBUG => 1,
tracing::Level::INFO => 2,
tracing::Level::WARN => 3,
tracing::Level::ERROR => 4,
}
}

View File

@@ -1,8 +1,11 @@
use std::{
any::Any,
collections::{BTreeMap, BTreeSet},
path::PathBuf,
};
use anyhow::Context;
use plugin_builder::PluginBuilderOptions;
use crate::*;
pub const EXTISM_ENV_MODULE: &str = "extism:host/env";
@@ -35,6 +38,66 @@ impl CancelHandle {
}
}
#[derive(Clone)]
pub struct CompiledPlugin {
pub(crate) manifest: Manifest,
pub(crate) modules: BTreeMap<String, Module>,
pub(crate) options: PluginBuilderOptions,
pub(crate) engine: wasmtime::Engine,
}
impl CompiledPlugin {
/// Create a new pre-compiled plugin
pub fn new(builder: PluginBuilder) -> Result<CompiledPlugin, Error> {
let mut config = builder.config.unwrap_or_default();
config
.async_support(false)
.epoch_interruption(true)
.debug_info(builder.options.debug_options.debug_info)
.coredump_on_trap(builder.options.debug_options.coredump.is_some())
.profiler(builder.options.debug_options.profiling_strategy)
.wasm_tail_call(true)
.wasm_function_references(true)
.wasm_gc(true);
if builder.options.fuel.is_some() {
config.consume_fuel(true);
}
match &builder.options.cache_config {
Some(None) => (),
Some(Some(path)) => {
config.cache_config_load(path)?;
}
None => {
if let Ok(env) = std::env::var("EXTISM_CACHE_CONFIG") {
if !env.is_empty() {
config.cache_config_load(&env)?;
}
} else {
config.cache_config_load_default()?;
}
}
}
let engine = Engine::new(&config)?;
let (manifest, modules) = manifest::load(&engine, builder.source)?;
if modules.len() <= 1 {
anyhow::bail!("No wasm modules provided");
} else if !modules.contains_key(MAIN_KEY) {
anyhow::bail!("No main module provided");
}
Ok(CompiledPlugin {
manifest,
modules,
options: builder.options,
engine,
})
}
}
/// Plugin contains everything needed to execute a WASM function
pub struct Plugin {
/// A unique ID for each plugin
@@ -80,6 +143,12 @@ pub struct Plugin {
pub(crate) store_needs_reset: bool,
pub(crate) debug_options: DebugOptions,
pub(crate) error_msg: Option<Vec<u8>>,
pub(crate) fuel: Option<u64>,
pub(crate) host_context: Rooted<ExternRef>,
}
unsafe impl Send for Plugin {}
@@ -100,14 +169,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 +254,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 +280,89 @@ fn add_module<T: 'static>(
Ok(())
}
#[allow(clippy::type_complexity)]
fn relink(
engine: &Engine,
mut store: &mut Store<CurrentPlugin>,
imports: &[Function],
modules: &BTreeMap<String, Module>,
with_wasi: bool,
) -> Result<
(
InstancePre<CurrentPlugin>,
Linker<CurrentPlugin>,
Rooted<ExternRef>,
),
Error,
> {
let mut linker = Linker::new(engine);
linker.allow_shadowing(true);
// Define PDK functions
macro_rules! add_funcs {
($($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?) => {
$(
let t = FuncType::new(&engine, [$($args),*], [$($($r),*)?]);
linker.func_new(EXTISM_ENV_MODULE, stringify!($name), t, pdk::$name)?;
)*
};
}
// Add builtins
use wasmtime::ValType::*;
add_funcs!(
config_get(I64) -> I64;
var_get(I64) -> I64;
var_set(I64, I64);
http_request(I64, I64) -> I64;
http_status_code() -> I32;
http_headers() -> I64;
log_warn(I64);
log_info(I64);
log_debug(I64);
log_error(I64);
log_trace(I64);
get_log_level() -> I32;
);
let mut linked = BTreeSet::new();
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
linked.insert(EXTISM_ENV_MODULE.to_string());
// If wasi is enabled then add it to the linker
if with_wasi {
wasi_common::sync::add_to_linker(&mut linker, |x: &mut CurrentPlugin| {
&mut x.wasi.as_mut().unwrap().ctx
})?;
}
for f in imports {
let name = f.name();
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
unsafe {
linker.func_new(ns, name, f.ty(engine).clone(), &*(f.f.as_ref() as *const _))?;
}
}
for (name, module) in modules.iter() {
add_module(
store,
&mut linker,
&mut linked,
modules,
name.clone(),
module,
)?;
}
let inner: Box<dyn std::any::Any + Send + Sync> = Box::new(());
let host_context = ExternRef::new(store, inner)?;
let main = &modules[MAIN_KEY];
let instance_pre = linker.instantiate_pre(main)?;
Ok((instance_pre, linker, host_context))
}
impl Plugin {
/// Create a new plugin from a Manifest or WebAssembly module, and host functions. The `with_wasi`
/// parameter determines whether or not the module should be executed with WASI enabled.
@@ -221,121 +371,45 @@ impl Plugin {
imports: impl IntoIterator<Item = Function>,
with_wasi: bool,
) -> Result<Plugin, Error> {
Self::build_new(wasm.into(), imports, with_wasi, Default::default(), None)
Self::new_from_compiled(&CompiledPlugin::new(
PluginBuilder::new(wasm)
.with_functions(imports)
.with_wasi(with_wasi),
)?)
}
pub(crate) fn build_new(
wasm: WasmInput<'_>,
imports: impl IntoIterator<Item = Function>,
with_wasi: bool,
debug_options: DebugOptions,
cache_dir: Option<Option<PathBuf>>,
) -> Result<Plugin, Error> {
// Setup wasmtime types
let mut config = Config::new();
config
.epoch_interruption(true)
.debug_info(debug_options.debug_info)
.coredump_on_trap(debug_options.coredump.is_some())
.profiler(debug_options.profiling_strategy)
.wasm_tail_call(true)
.wasm_function_references(true);
match cache_dir {
Some(None) => (),
Some(Some(path)) => {
config.cache_config_load(path)?;
}
None => {
if let Ok(env) = std::env::var("EXTISM_CACHE_CONFIG") {
if !env.is_empty() {
config.cache_config_load(&env)?;
}
} else {
config.cache_config_load_default()?;
}
}
}
let engine = Engine::new(&config)?;
let (manifest, modules) = manifest::load(&engine, wasm)?;
if modules.len() <= 1 {
anyhow::bail!("No wasm modules provided");
} else if !modules.contains_key(MAIN_KEY) {
anyhow::bail!("No main module provided");
}
let available_pages = manifest.memory.max_pages;
/// Create a new plugin from a pre-compiled plugin
pub fn new_from_compiled(compiled: &CompiledPlugin) -> Result<Plugin, Error> {
let available_pages = compiled.manifest.memory.max_pages;
debug!("Available pages: {available_pages:?}");
let id = uuid::Uuid::new_v4();
let mut store = Store::new(
&engine,
CurrentPlugin::new(manifest, with_wasi, available_pages, id)?,
&compiled.engine,
CurrentPlugin::new(
compiled.manifest.clone(),
compiled.options.wasi,
available_pages,
compiled.options.http_response_headers,
id,
)?,
);
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)?;
)*
};
if let Some(fuel) = compiled.options.fuel {
store.set_fuel(fuel)?;
}
// 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 imports: Vec<Function> = compiled.options.functions.to_vec();
let (instance_pre, linker, host_context) = relink(
&compiled.engine,
&mut store,
&imports,
&compiled.modules,
compiled.options.wasi,
)?;
let timer_tx = Timer::tx();
let mut plugin = Plugin {
modules,
modules: compiled.modules.clone(),
linker,
instance: std::sync::Arc::new(std::sync::Mutex::new(None)),
instance_pre,
@@ -347,8 +421,11 @@ impl Plugin {
instantiations: 0,
output: Output::default(),
store_needs_reset: false,
debug_options,
debug_options: compiled.options.debug_options.clone(),
_functions: imports,
error_msg: None,
fuel: compiled.options.fuel,
host_context,
};
plugin.current_plugin_mut().store = &mut plugin.store;
@@ -370,16 +447,33 @@ impl Plugin {
if self.store_needs_reset {
let engine = self.store.engine().clone();
let internal = self.current_plugin_mut();
let with_wasi = internal.wasi.is_some();
self.store = Store::new(
&engine,
CurrentPlugin::new(
internal.manifest.clone(),
internal.wasi.is_some(),
internal.available_pages,
internal.http_headers.is_some(),
self.id,
)?,
);
self.store.set_epoch_deadline(1);
if let Some(fuel) = self.fuel {
self.store.set_fuel(fuel)?;
}
let (instance_pre, linker, host_context) = relink(
&engine,
&mut self.store,
&self._functions,
&self.modules,
with_wasi,
)?;
self.linker = linker;
self.instance_pre = instance_pre;
self.host_context = host_context;
let store = &mut self.store as *mut _;
let linker = &mut self.linker as *mut _;
let current_plugin = self.current_plugin_mut();
@@ -390,14 +484,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 +537,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 +549,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();
@@ -489,13 +581,27 @@ impl Plugin {
.linker
.get(&mut self.store, EXTISM_ENV_MODULE, "input_set")
{
f.into_func().unwrap().call(
&mut self.store,
&[Val::I64(handle.offset() as i64), Val::I64(len as i64)],
&mut [],
catch_out_of_fuel!(
&self.store,
f.into_func()
.unwrap()
.call(
&mut self.store,
&[Val::I64(handle.offset() as i64), Val::I64(len as i64)],
&mut [],
)
.context("unable to set extism input")
)?;
}
if let Some(Extern::Global(ctxt)) =
self.linker
.get(&mut self.store, EXTISM_ENV_MODULE, "extism_context")
{
ctxt.set(&mut self.store, Val::ExternRef(host_context))
.context("unable to set extism host context")?;
}
Ok(())
}
@@ -504,7 +610,13 @@ impl Plugin {
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 [])?;
catch_out_of_fuel!(
&self.store,
f.into_func()
.unwrap()
.call(&mut self.store, &[], &mut [])
.context("extism reset failed")
)?;
} else {
error!(plugin = &id, "call to extism:host/env::reset failed");
}
@@ -580,19 +692,28 @@ impl Plugin {
// Initialize the guest runtime
pub(crate) fn initialize_guest_runtime(&mut self) -> Result<(), Error> {
let mut store = &mut self.store;
let store = &mut self.store;
if let Some(runtime) = &self.runtime {
trace!(plugin = self.id.to_string(), "Plugin::initialize_runtime");
match runtime {
GuestRuntime::Haskell { init, reactor_init } => {
if let Some(reactor_init) = reactor_init {
reactor_init.call(&mut store, &[], &mut [])?;
catch_out_of_fuel!(
&store,
reactor_init
.call(&mut *store, &[], &mut [])
.context("failed to initialize Haskell reactor runtime")
)?;
}
let mut results = vec![Val::null(); init.ty(&store).results().len()];
init.call(
&mut store,
&[Val::I32(0), Val::I32(0)],
results.as_mut_slice(),
let mut results = vec![Val::I32(0); init.ty(&*store).results().len()];
catch_out_of_fuel!(
&store,
init.call(
&mut *store,
&[Val::I32(0), Val::I32(0)],
results.as_mut_slice(),
)
.context("failed to initialize Haskell using hs_init")
)?;
debug!(
plugin = self.id.to_string(),
@@ -600,7 +721,11 @@ impl Plugin {
);
}
GuestRuntime::Wasi { init } => {
init.call(&mut store, &[], &mut [])?;
catch_out_of_fuel!(
&store,
init.call(&mut *store, &[], &mut [])
.context("failed to initialize wasi runtime")
)?;
debug!(plugin = self.id.to_string(), "initialied WASI runtime");
}
}
@@ -613,20 +738,32 @@ impl Plugin {
fn output_memory_position(&mut self) -> Result<(u64, u64), Error> {
let out = &mut [Val::I64(0)];
let out_len = &mut [Val::I64(0)];
let mut store = &mut self.store;
let store = &mut self.store;
if let Some(f) = self
.linker
.get(&mut store, EXTISM_ENV_MODULE, "output_offset")
.get(&mut *store, EXTISM_ENV_MODULE, "output_offset")
{
f.into_func().unwrap().call(&mut store, &[], out)?;
catch_out_of_fuel!(
&store,
f.into_func()
.unwrap()
.call(&mut *store, &[], out)
.context("call to set extism output offset failed")
)?;
} else {
anyhow::bail!("unable to set output")
}
if let Some(f) = self
.linker
.get(&mut store, EXTISM_ENV_MODULE, "output_length")
.get(&mut *store, EXTISM_ENV_MODULE, "output_length")
{
f.into_func().unwrap().call(&mut store, &[], out_len)?;
catch_out_of_fuel!(
&store,
f.into_func()
.unwrap()
.call(&mut *store, &[], out_len)
.context("call to set extism output length failed")
)?;
} else {
anyhow::bail!("unable to set output length")
}
@@ -640,10 +777,10 @@ impl Plugin {
fn output<'a, T: FromBytes<'a>>(&'a mut self) -> Result<T, Error> {
let offs = self.output.offset;
let len = self.output.length;
T::from_bytes(
self.current_plugin_mut()
.memory_bytes(unsafe { MemoryHandle::new(offs, len) })?,
)
let x = self
.current_plugin_mut()
.memory_bytes(unsafe { MemoryHandle::new(offs, len) })?;
T::from_bytes(x)
}
// Cache output memory and error information after call is complete
@@ -668,25 +805,40 @@ impl Plugin {
// Implements the build of the `call` function, `raw_call` is also used in the SDK
// code
pub(crate) fn raw_call(
pub(crate) fn raw_call<T: 'static + Send + Sync>(
&mut self,
lock: &mut std::sync::MutexGuard<Option<Instance>>,
name: impl AsRef<str>,
input: impl AsRef<[u8]>,
host_context: Option<T>,
) -> Result<i32, (Error, i32)> {
let name = name.as_ref();
let input = input.as_ref();
if let Err(e) = self.reset_store(lock) {
error!(
plugin = self.id.to_string(),
"call to Plugin::reset_store failed: {e:?}"
);
if let Some(fuel) = self.fuel {
self.store.set_fuel(fuel).map_err(|x| (x, -1))?;
}
catch_out_of_fuel!(&self.store, self.reset_store(lock)).map_err(|x| (x, -1))?;
self.instantiate(lock).map_err(|e| (e, -1))?;
self.set_input(input.as_ptr(), input.len())
// Set host context
let r = if let Some(host_context) = host_context {
let inner = self
.host_context
.data_mut(&mut self.store)
.map_err(|x| (x, -1))?;
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
let x: Box<T> = Box::new(host_context);
*inner = x;
}
Some(self.host_context)
} else {
None
};
self.set_input(input.as_ptr(), input.len(), r)
.map_err(|x| (x, -1))?;
let func = match self.get_func(lock, name) {
@@ -717,45 +869,77 @@ 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());
// Reset host context
if let Ok(inner) = self.host_context.data_mut(&mut self.store) {
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
let x: Box<dyn Any + Send + Sync> = Box::new(());
*inner = x;
}
}
// Stop timer
self.store
.epoch_deadline_callback(|_| Ok(UpdateDeadline::Continue(1)));
let _ = self.timer_tx.send(TimerAction::Stop { id: self.id });
self.store_needs_reset = name == "_start";
// Get extism error
self.get_output_after_call().map_err(|x| (x, -1))?;
let mut rc = 0;
if !results.is_empty() {
rc = results[0].i32().unwrap_or(-1);
debug!(plugin = self.id.to_string(), "got return code: {}", rc);
}
let mut rc = -1;
if self.store.get_fuel().is_ok_and(|x| x == 0) {
res = Err(Error::msg("plugin ran out of fuel"));
} else {
// Get extism error
let output_res = self.get_output_after_call().map_err(|x| (x, -1));
if self.output.error_offset != 0 && self.output.error_length != 0 {
let handle = MemoryHandle {
offset: self.output.error_offset,
length: self.output.error_length,
};
if let Ok(e) = self.current_plugin_mut().memory_str(handle) {
let x = e.to_string();
error!(
plugin = self.id.to_string(),
"call to {name} returned with error message: {}", x
);
if let Err(e) = res {
res = Err(Error::msg(x).context(e));
} else {
res = Err(Error::msg(x))
// Get the return code
if output_res.is_ok() && res.is_ok() {
rc = 0;
if !results.is_empty() {
rc = results[0].i32().unwrap_or(-1);
debug!(plugin = self.id.to_string(), "got return code: {}", rc);
}
}
// on extism error
if output_res.is_ok() && self.output.error_offset != 0 && self.output.error_length != 0
{
let handle = MemoryHandle {
offset: self.output.error_offset,
length: self.output.error_length,
};
match self.current_plugin_mut().memory_str(handle) {
Ok(e) => {
let x = e.to_string();
error!(
plugin = self.id.to_string(),
"call to {name} returned with error message: {}", x
);
if let Err(e) = res {
res = Err(Error::msg(x).context(e));
} else {
res = Err(Error::msg(x))
}
}
Err(msg) => {
res = Err(Error::msg(format!(
"Call to Extism plugin function {name} encountered an error: {}",
msg,
)));
}
}
// on wasmtime error
} else if let Err(e) = &res {
if e.is::<wasmtime::Trap>() {
rc = 134; // EXIT_SIGNALED_SIGABRT
}
// if there was an error retrieving the output
} else {
res = Err(Error::msg(format!(
"Call to Extism plugin function {name} encountered an error"
)));
output_res?;
}
}
@@ -804,13 +988,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 +1051,32 @@ 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()?;
self.raw_call(&mut lock, name, data, Some(host_context))
.map_err(|e| e.0)
.and_then(move |_| self.output())
}
@@ -892,7 +1095,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)))
}
@@ -903,42 +1106,21 @@ impl Plugin {
pub(crate) fn clear_error(&mut self) -> Result<(), Error> {
trace!(plugin = self.id.to_string(), "clearing error");
self.error_msg = None;
let (linker, mut store) = self.linker_and_store();
if let Some(f) = linker.get(&mut store, EXTISM_ENV_MODULE, "error_set") {
f.into_func()
#[allow(clippy::needless_borrows_for_generic_args)]
if let Some(f) = linker.get(&mut *store, EXTISM_ENV_MODULE, "error_set") {
let x = f
.into_func()
.unwrap()
.call(&mut store, &[Val::I64(0)], &mut [])?;
.call(&mut store, &[Val::I64(0)], &mut [])
.context("unable to clear error message");
catch_out_of_fuel!(&store, x)?;
Ok(())
} else {
anyhow::bail!("Plugin::clear_error failed, extism:host/env::error_set not found")
}
}
// A convenience method to set the plugin error and return a value
pub(crate) fn return_error<E>(
&mut self,
instance_lock: &mut std::sync::MutexGuard<Option<Instance>>,
e: impl std::fmt::Display,
x: E,
) -> E {
if instance_lock.is_none() {
error!(
plugin = self.id.to_string(),
"no instance, unable to set error: {}", e
);
return x;
}
match self.current_plugin_mut().set_error(e.to_string()) {
Ok((a, b)) => {
self.output.error_offset = a;
self.output.error_length = b;
}
Err(e) => {
error!(plugin = self.id.to_string(), "unable to set error: {e:?}")
}
}
x
}
}
// Enumerates the PDK languages that need some additional initialization

View File

@@ -34,11 +34,19 @@ impl Default for DebugOptions {
/// PluginBuilder is used to configure and create `Plugin` instances
pub struct PluginBuilder<'a> {
source: WasmInput<'a>,
wasi: bool,
functions: Vec<Function>,
debug_options: DebugOptions,
cache_config: Option<Option<PathBuf>>,
pub(crate) source: WasmInput<'a>,
pub(crate) config: Option<wasmtime::Config>,
pub(crate) options: PluginBuilderOptions,
}
#[derive(Clone)]
pub(crate) struct PluginBuilderOptions {
pub(crate) wasi: bool,
pub(crate) functions: Vec<Function>,
pub(crate) debug_options: DebugOptions,
pub(crate) cache_config: Option<Option<PathBuf>>,
pub(crate) fuel: Option<u64>,
pub(crate) http_response_headers: bool,
}
impl<'a> PluginBuilder<'a> {
@@ -46,21 +54,26 @@ impl<'a> PluginBuilder<'a> {
pub fn new(plugin: impl Into<WasmInput<'a>>) -> Self {
PluginBuilder {
source: plugin.into(),
wasi: false,
functions: vec![],
debug_options: DebugOptions::default(),
cache_config: None,
config: None,
options: PluginBuilderOptions {
wasi: false,
functions: vec![],
debug_options: DebugOptions::default(),
cache_config: None,
fuel: None,
http_response_headers: false,
},
}
}
/// Enables WASI if the argument is set to `true`
pub fn with_wasi(mut self, wasi: bool) -> Self {
self.wasi = wasi;
self.options.wasi = wasi;
self
}
/// Add a single host function
pub fn with_function<T: Sync + Clone + 'static, F>(
pub fn with_function<T: 'static, F>(
mut self,
name: impl Into<String>,
args: impl IntoIterator<Item = ValType>,
@@ -74,13 +87,14 @@ impl<'a> PluginBuilder<'a> {
+ Sync
+ Send,
{
self.functions
self.options
.functions
.push(Function::new(name, args, returns, user_data, f));
self
}
/// Add a single host function in a specific namespace
pub fn with_function_in_namespace<T: Sync + Clone + 'static, F>(
pub fn with_function_in_namespace<T: 'static, F>(
mut self,
namespace: impl Into<String>,
name: impl Into<String>,
@@ -95,67 +109,97 @@ impl<'a> PluginBuilder<'a> {
+ Sync
+ Send,
{
self.functions
self.options
.functions
.push(Function::new(name, args, returns, user_data, f).with_namespace(namespace));
self
}
/// Add multiple host functions
pub fn with_functions(mut self, f: impl IntoIterator<Item = Function>) -> Self {
self.functions.extend(f);
self.options.functions.extend(f);
self
}
/// Set profiling strategy
pub fn with_profiling_strategy(mut self, p: wasmtime::ProfilingStrategy) -> Self {
self.debug_options.profiling_strategy = p;
self.options.debug_options.profiling_strategy = p;
self
}
/// Enable Wasmtime coredump on trap
pub fn with_coredump(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.debug_options.coredump = Some(path.into());
self.options.debug_options.coredump = Some(path.into());
self
}
/// Enable Extism memory dump when plugin calls return an error
pub fn with_memdump(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.debug_options.memdump = Some(path.into());
self.options.debug_options.memdump = Some(path.into());
self
}
/// Compile with debug info
pub fn with_debug_info(mut self) -> Self {
self.debug_options.debug_info = true;
self.options.debug_options.debug_info = true;
self
}
/// Configure debug options
pub fn with_debug_options(mut self, options: DebugOptions) -> Self {
self.debug_options = options;
self.options.debug_options = options;
self
}
/// Set wasmtime compilation cache config path
pub fn with_cache_config(mut self, dir: impl Into<PathBuf>) -> Self {
self.cache_config = Some(Some(dir.into()));
self.options.cache_config = Some(Some(dir.into()));
self
}
/// Turn wasmtime compilation caching off
pub fn with_cache_disabled(mut self) -> Self {
self.cache_config = Some(None);
self.options.cache_config = Some(None);
self
}
/// Limit the number of instructions that can be executed
pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
self.options.fuel = Some(fuel);
self
}
/// Configure an initial wasmtime config to be passed to the plugin
///
/// **Warning**: some values might be overwritten by the Extism runtime. In particular:
/// - async_support
/// - epoch_interruption
/// - debug_info
/// - coredump_on_trap
/// - profiler
/// - wasm_tail_call
/// - wasm_function_references
/// - wasm_gc
///
/// See the implementation details of [PluginBuilder::build] and [Plugin::build_new] to verify which values are overwritten.
pub fn with_wasmtime_config(mut self, config: wasmtime::Config) -> Self {
self.config = Some(config);
self
}
/// Enables `http_response_headers`, which allows for plugins to access response headers when using `extism:host/env::http_request`
pub fn with_http_response_headers(mut self, allow: bool) -> Self {
self.options.http_response_headers = allow;
self
}
/// Generate a new plugin with the configured settings
pub fn build(self) -> Result<Plugin, Error> {
Plugin::build_new(
self.source,
self.functions,
self.wasi,
self.debug_options,
self.cache_config,
)
Plugin::new_from_compiled(&CompiledPlugin::new(self)?)
}
/// Build new `CompiledPlugin`
pub fn compile(self) -> Result<CompiledPlugin, Error> {
CompiledPlugin::new(self)
}
}

109
runtime/src/readonly_dir.rs Normal file
View File

@@ -0,0 +1,109 @@
use crate::*;
use wasi_common::{Error, ErrorExt};
pub struct ReadOnlyDir<D: wasi_common::WasiDir> {
inner: std::sync::Arc<D>,
}
impl<D: wasi_common::WasiDir> ReadOnlyDir<D> {
pub fn new(inner: D) -> Self {
ReadOnlyDir {
inner: std::sync::Arc::new(inner),
}
}
}
#[wiggle::async_trait]
impl<D: wasi_common::WasiDir> wasi_common::WasiDir for ReadOnlyDir<D> {
fn as_any(&self) -> &dyn std::any::Any {
self.inner.as_any()
}
async fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: wasi_common::file::OFlags,
read: bool,
write: bool,
fdflags: wasi_common::file::FdFlags,
) -> Result<wasi_common::dir::OpenResult, Error> {
if write {
return Err(Error::not_supported());
}
self.inner
.open_file(symlink_follow, path, oflags, read, false, fdflags)
.await
}
async fn create_dir(&self, _path: &str) -> Result<(), Error> {
Err(Error::not_supported())
}
async fn readdir(
&self,
cursor: wasi_common::dir::ReaddirCursor,
) -> Result<
Box<dyn Iterator<Item = Result<wasi_common::dir::ReaddirEntity, Error>> + Send>,
Error,
> {
self.inner.readdir(cursor).await
}
async fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<(), Error> {
Err(Error::not_supported())
}
async fn remove_dir(&self, _path: &str) -> Result<(), Error> {
Err(Error::not_supported())
}
async fn unlink_file(&self, _path: &str) -> Result<(), Error> {
Err(Error::not_supported())
}
async fn read_link(&self, path: &str) -> Result<std::path::PathBuf, Error> {
self.inner.read_link(path).await
}
async fn get_filestat(&self) -> Result<wasi_common::file::Filestat, Error> {
self.inner.get_filestat().await
}
async fn get_path_filestat(
&self,
path: &str,
follow_symlinks: bool,
) -> Result<wasi_common::file::Filestat, Error> {
self.inner.get_path_filestat(path, follow_symlinks).await
}
async fn rename(
&self,
_path: &str,
_dest_dir: &dyn wasi_common::WasiDir,
_dest_path: &str,
) -> Result<(), Error> {
Err(wasi_common::Error::not_supported())
}
async fn hard_link(
&self,
_path: &str,
_target_dir: &dyn wasi_common::WasiDir,
_target_path: &str,
) -> Result<(), Error> {
Err(wasi_common::Error::not_supported())
}
async fn set_times(
&self,
_path: &str,
_atime: std::option::Option<wasi_common::SystemTimeSpec>,
_mtime: std::option::Option<wasi_common::SystemTimeSpec>,
_follow_symlinks: bool,
) -> Result<(), Error> {
Err(wasi_common::Error::not_supported())
}
}

View File

@@ -1,6 +1,6 @@
#![allow(clippy::missing_safety_doc)]
use std::os::raw::c_char;
use std::{os::raw::c_char, ptr::null_mut};
use crate::*;
@@ -11,6 +11,12 @@ pub struct ExtismFunction(std::cell::Cell<Option<Function>>);
/// The return code used to specify a successful plugin call
pub static EXTISM_SUCCESS: i32 = 0;
fn make_error_msg(s: String) -> Vec<u8> {
let mut s = s.into_bytes();
s.push(0);
s
}
/// A union type for host function argument/return values
#[repr(C)]
pub union ValUnion {
@@ -41,33 +47,33 @@ pub type ExtismFunctionType = extern "C" fn(
/// Log drain callback
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
impl From<&wasmtime::Val> for ExtismVal {
fn from(value: &wasmtime::Val) -> Self {
match value.ty() {
wasmtime::ValType::I32 => ExtismVal {
impl ExtismVal {
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Result<Self, Error> {
match value.ty(ctx)? {
wasmtime::ValType::I32 => Ok(ExtismVal {
t: ValType::I32,
v: ValUnion {
i32: value.unwrap_i32(),
},
},
wasmtime::ValType::I64 => ExtismVal {
}),
wasmtime::ValType::I64 => Ok(ExtismVal {
t: ValType::I64,
v: ValUnion {
i64: value.unwrap_i64(),
},
},
wasmtime::ValType::F32 => ExtismVal {
}),
wasmtime::ValType::F32 => Ok(ExtismVal {
t: ValType::F32,
v: ValUnion {
f32: value.unwrap_f32(),
},
},
wasmtime::ValType::F64 => ExtismVal {
}),
wasmtime::ValType::F64 => Ok(ExtismVal {
t: ValType::F64,
v: ValUnion {
f64: value.unwrap_f64(),
},
},
}),
t => todo!("{}", t),
}
}
@@ -84,6 +90,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 +224,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).unwrap())
.collect();
let mut output_tmp: Vec<_> = output_types
.iter()
.map(|t| ExtismVal {
@@ -209,12 +237,28 @@ pub unsafe extern "C" fn extism_function_new(
})
.collect();
// We cannot simply "get" the Vec's storage pointer because
// the underlying storage might be invalid when the Vec is empty.
// In that case, we return (null, 0).
let (inputs_ptr, inputs_len) = if inputs.is_empty() {
(core::ptr::null(), 0 as Size)
} else {
(inputs.as_ptr(), inputs.len() as Size)
};
let (output_ptr, output_len) = if output_tmp.is_empty() {
(null_mut(), 0 as Size)
} else {
(output_tmp.as_mut_ptr(), output_tmp.len() as Size)
};
func(
plugin,
inputs.as_ptr(),
inputs.len() as Size,
output_tmp.as_mut_ptr(),
output_tmp.len() as Size,
inputs_ptr,
inputs_len,
output_ptr,
output_len,
user_data.as_ptr(),
);
@@ -222,8 +266,8 @@ pub unsafe extern "C" fn extism_function_new(
match tmp.t {
ValType::I32 => *out = Val::I32(tmp.v.i32),
ValType::I64 => *out = Val::I64(tmp.v.i64),
ValType::F32 => *out = Val::F32(tmp.v.f32 as u32),
ValType::F64 => *out = Val::F64(tmp.v.f64 as u64),
ValType::F32 => *out = Val::F32(tmp.v.f32.to_bits()),
ValType::F64 => *out = Val::F64(tmp.v.f64.to_bits()),
_ => todo!(),
}
}
@@ -258,6 +302,56 @@ pub unsafe extern "C" fn extism_function_set_namespace(
}
}
/// Pre-compile an Extism plugin
#[no_mangle]
pub unsafe extern "C" fn extism_compiled_plugin_new(
wasm: *const u8,
wasm_size: Size,
functions: *mut *const ExtismFunction,
n_functions: Size,
with_wasi: bool,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut CompiledPlugin {
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let mut builder = PluginBuilder::new(data).with_wasi(with_wasi);
if !functions.is_null() {
for i in 0..n_functions {
unsafe {
let f = *functions.add(i as usize);
if f.is_null() {
continue;
}
if let Some(f) = (*f).0.take() {
builder.options.functions.push(f);
} else {
let e = std::ffi::CString::new(
"Function cannot be registered with multiple different Plugins",
)
.unwrap();
*errmsg = e.into_raw();
return std::ptr::null_mut();
}
}
}
}
Box::into_raw(Box::new(CompiledPlugin::new(builder).unwrap()))
}
/// Free `ExtismCompiledPlugin`
#[no_mangle]
pub unsafe extern "C" fn extism_compiled_plugin_free(plugin: *mut CompiledPlugin) {
if plugin.is_null() {
return;
}
let plugin = Box::from_raw(plugin);
trace!("called extism_compiled_plugin_free");
drop(plugin)
}
/// Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
///
/// `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
@@ -274,7 +368,6 @@ pub unsafe extern "C" fn extism_plugin_new(
with_wasi: bool,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut Plugin {
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let mut funcs = vec![];
@@ -293,6 +386,7 @@ pub unsafe extern "C" fn extism_plugin_new(
)
.unwrap();
*errmsg = e.into_raw();
return std::ptr::null_mut();
}
}
}
@@ -312,6 +406,106 @@ pub unsafe extern "C" fn extism_plugin_new(
}
}
/// Create a new plugin from an `ExtismCompiledPlugin`
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_new_from_compiled(
compiled: *const CompiledPlugin,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut Plugin {
let plugin = Plugin::new_from_compiled(&*compiled);
match plugin {
Err(e) => {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!("Unable to create Extism plugin: {}", e))
.unwrap();
*errmsg = e.into_raw();
}
std::ptr::null_mut()
}
Ok(p) => Box::into_raw(Box::new(p)),
}
}
/// Create a new plugin and set the number of instructions a plugin is allowed to execute
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_new_with_fuel_limit(
wasm: *const u8,
wasm_size: Size,
functions: *mut *const ExtismFunction,
n_functions: Size,
with_wasi: bool,
fuel_limit: u64,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut Plugin {
trace!(
"Call to extism_plugin_new_with_fuel_limit with wasm pointer {:?}",
wasm
);
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let mut funcs = vec![];
if !functions.is_null() {
for i in 0..n_functions {
unsafe {
let f = *functions.add(i as usize);
if f.is_null() {
continue;
}
if let Some(f) = (*f).0.take() {
funcs.push(f);
} else {
if !errmsg.is_null() {
let e = std::ffi::CString::new(
"Function cannot be registered with multiple different Plugins",
)
.unwrap();
*errmsg = e.into_raw();
}
return std::ptr::null_mut();
}
}
}
}
let compiled = match CompiledPlugin::new(
PluginBuilder::new(data)
.with_functions(funcs)
.with_wasi(with_wasi)
.with_fuel_limit(fuel_limit),
) {
Ok(x) => x,
Err(e) => {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!("Unable to compile Extism plugin: {}", e))
.unwrap();
*errmsg = e.into_raw();
}
return std::ptr::null_mut();
}
};
let plugin = Plugin::new_from_compiled(&compiled);
match plugin {
Err(e) => {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!("Unable to create Extism plugin: {}", e))
.unwrap();
*errmsg = e.into_raw();
}
std::ptr::null_mut()
}
Ok(p) => Box::into_raw(Box::new(p)),
}
}
/// Enable HTTP response headers in plugins using `extism:host/env::http_request`
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_allow_http_response_headers(plugin: *mut Plugin) {
let plugin = &mut *plugin;
plugin.store.data_mut().http_headers = Some(BTreeMap::new());
}
/// Free the error returned by `extism_plugin_new`, errors returned from `extism_plugin_error` don't need to be freed
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_new_error_free(err: *mut std::ffi::c_char) {
@@ -321,7 +515,7 @@ pub unsafe extern "C" fn extism_plugin_new_error_free(err: *mut std::ffi::c_char
drop(std::ffi::CString::from_raw(err))
}
/// Remove a plugin from the registry and free associated memory
/// Free `ExtismPlugin`
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_free(plugin: *mut Plugin) {
if plugin.is_null() {
@@ -372,8 +566,6 @@ pub unsafe extern "C" fn extism_plugin_config(
return false;
}
let plugin = &mut *plugin;
let _lock = plugin.instance.clone();
let mut lock = _lock.lock().unwrap();
trace!(
plugin = plugin.id.to_string(),
@@ -384,25 +576,11 @@ pub unsafe extern "C" fn extism_plugin_config(
let json: std::collections::BTreeMap<String, Option<String>> =
match serde_json::from_slice(data) {
Ok(x) => x,
Err(e) => {
return plugin.return_error(&mut lock, e, false);
Err(_) => {
return false;
}
};
let wasi = &mut plugin.current_plugin_mut().wasi;
if let Some(Wasi { ctx, .. }) = wasi {
for (k, v) in json.iter() {
match v {
Some(v) => {
let _ = ctx.push_env(k, v);
}
None => {
let _ = ctx.push_env(k, "");
}
}
}
}
let id = plugin.id;
let config = &mut plugin.current_plugin_mut().manifest.config;
for (k, v) in json.into_iter() {
@@ -432,9 +610,6 @@ pub unsafe extern "C" fn extism_plugin_function_exists(
return false;
}
let plugin = &mut *plugin;
let _lock = plugin.instance.clone();
let mut lock = _lock.lock().unwrap();
let name = std::ffi::CStr::from_ptr(func_name);
trace!(
plugin = plugin.id.to_string(),
@@ -444,8 +619,8 @@ pub unsafe extern "C" fn extism_plugin_function_exists(
let name = match name.to_str() {
Ok(x) => x,
Err(e) => {
return plugin.return_error(&mut lock, e, false);
Err(_) => {
return false;
}
};
@@ -464,6 +639,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;
@@ -476,7 +676,10 @@ pub unsafe extern "C" fn extism_plugin_call(
let name = std::ffi::CStr::from_ptr(func_name);
let name = match name.to_str() {
Ok(name) => name,
Err(e) => return plugin.return_error(&mut lock, e, -1),
Err(e) => {
plugin.error_msg = Some(make_error_msg(e.to_string()));
return -1;
}
};
trace!(
@@ -485,10 +688,17 @@ 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 = if host_context.is_null() {
None
} else {
Some(CVoidContainer(host_context))
};
let res = plugin.raw_call(&mut lock, name, input, r);
match res {
Err((e, rc)) => plugin.return_error(&mut lock, e, rc),
Err((e, rc)) => {
plugin.error_msg = Some(make_error_msg(e.to_string()));
rc
}
Ok(x) => x,
}
}
@@ -511,14 +721,26 @@ pub unsafe extern "C" fn extism_plugin_error(plugin: *mut Plugin) -> *const c_ch
let _lock = _lock.lock().unwrap();
if plugin.output.error_offset == 0 {
if let Some(err) = &plugin.error_msg {
return err.as_ptr() as *const _;
}
trace!(plugin = plugin.id.to_string(), "error is NULL");
return std::ptr::null();
}
plugin
let offs = plugin.output.error_offset;
let ptr = plugin.current_plugin_mut().memory_ptr().add(offs as usize) as *const _;
let len = plugin
.current_plugin_mut()
.memory_ptr()
.add(plugin.output.error_offset as usize) as *const _
.memory_length(offs)
.unwrap_or_default();
let mut data = std::slice::from_raw_parts(ptr, len as usize).to_vec();
data.push(0);
plugin.error_msg = Some(data);
plugin.error_msg.as_ref().unwrap().as_ptr() as *const _
}
/// Get the length of a plugin's output data
@@ -593,7 +815,6 @@ pub unsafe extern "C" fn extism_log_file(
fn set_log_file(log_file: impl Into<std::path::PathBuf>, filter: &str) -> Result<(), Error> {
let log_file = log_file.into();
let s = log_file.to_str();
let is_level = tracing::Level::from_str(filter).is_ok();
let cfg = tracing_subscriber::FmtSubscriber::builder().with_env_filter({
let x = tracing_subscriber::EnvFilter::builder()
@@ -644,6 +865,7 @@ pub unsafe extern "C" fn extism_log_custom(log_level: *const c_char) -> bool {
} else {
"error"
};
set_log_buffer(level).is_ok()
}
@@ -671,7 +893,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

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

View File

@@ -1,6 +1,7 @@
use crate::*;
const WASM_EMPTY: &[u8] = include_bytes!("../../../wasm/empty.wasm");
const WASM_UNREACHABLE: &[u8] = include_bytes!("../../../wasm/unreachable.wasm");
// https://github.com/extism/extism/issues/620
#[test]
@@ -26,3 +27,31 @@ host_fn!(
Ok(path.display().to_string())
}
);
// https://github.com/extism/extism/issues/775
#[test]
fn test_issue_775() {
// Load and build plugin
let url = Wasm::data(WASM_UNREACHABLE);
let manifest = Manifest::new([url]);
let mut plugin = PluginBuilder::new(manifest)
.with_wasi(true)
.build()
.unwrap();
// Call test method
let lock = plugin.instance.clone();
let mut lock = lock.lock().unwrap();
let res = plugin.raw_call(&mut lock, "do_unreachable", b"", None::<()>);
let p = match res {
Err(e) => {
if e.1 == 0 {
Err(e.1)
} else {
Ok(e.1)
}
}
Ok(code) => Err(code),
}
.unwrap();
println!("{}", p);
}

View File

@@ -3,36 +3,32 @@ use quickcheck::*;
const KERNEL: &[u8] = include_bytes!("../extism-runtime.wasm");
fn extism_alloc<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, n: u64) -> u64 {
fn extism_alloc<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, n: u64) -> u64 {
let out_alloc = &mut [Val::I64(0)];
instance
.get_func(&mut store, "alloc")
.get_func(&mut *store, "alloc")
.unwrap()
.call(&mut store, &[Val::I64(n as i64)], out_alloc)
.call(store, &[Val::I64(n as i64)], out_alloc)
.unwrap();
out_alloc[0].unwrap_i64() as u64
}
fn extism_length<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
fn extism_length<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
let out = &mut [Val::I64(0)];
instance
.get_func(&mut store, "length")
.get_func(&mut *store, "length")
.unwrap()
.call(&mut store, &[Val::I64(p as i64)], out)
.call(store, &[Val::I64(p as i64)], out)
.unwrap();
out[0].unwrap_i64() as u64
}
fn extism_length_unsafe<T>(
mut store: &mut wasmtime::Store<T>,
instance: &mut Instance,
p: u64,
) -> u64 {
fn extism_length_unsafe<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
let out = &mut [Val::I64(0)];
instance
.get_func(&mut store, "length_unsafe")
.get_func(&mut *store, "length_unsafe")
.unwrap()
.call(&mut store, &[Val::I64(p as i64)], out)
.call(store, &[Val::I64(p as i64)], out)
.unwrap();
out[0].unwrap_i64() as u64
}
@@ -47,122 +43,96 @@ fn extism_load_u8<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance
out[0].unwrap_i32() as u8
}
fn extism_load_u64<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
fn extism_load_u64<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u64 {
let out = &mut [Val::I32(0)];
instance
.get_func(&mut store, "load_u64")
.get_func(&mut *store, "load_u64")
.unwrap()
.call(&mut store, &[Val::I64(p as i64)], out)
.call(store, &[Val::I64(p as i64)], out)
.unwrap();
out[0].unwrap_i64() as u64
}
fn extism_input_load_u8<T>(
mut store: &mut wasmtime::Store<T>,
instance: &mut Instance,
p: u64,
) -> u8 {
fn extism_input_load_u8<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) -> u8 {
let out = &mut [Val::I32(0)];
instance
.get_func(&mut store, "input_load_u8")
.get_func(&mut *store, "input_load_u8")
.unwrap()
.call(&mut store, &[Val::I64(p as i64)], out)
.call(store, &[Val::I64(p as i64)], out)
.unwrap();
out[0].unwrap_i32() as u8
}
fn extism_input_load_u64<T>(
mut store: &mut wasmtime::Store<T>,
store: &mut wasmtime::Store<T>,
instance: &mut Instance,
p: u64,
) -> u64 {
let out = &mut [Val::I32(0)];
instance
.get_func(&mut store, "input_load_u64")
.get_func(&mut *store, "input_load_u64")
.unwrap()
.call(&mut store, &[Val::I64(p as i64)], out)
.call(store, &[Val::I64(p as i64)], out)
.unwrap();
out[0].unwrap_i64() as u64
}
fn extism_store_u8<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64, x: u8) {
fn extism_store_u8<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64, x: u8) {
instance
.get_func(&mut store, "store_u8")
.get_func(&mut *store, "store_u8")
.unwrap()
.call(
&mut store,
&[Val::I64(p as i64), Val::I32(x as i32)],
&mut [],
)
.call(store, &[Val::I64(p as i64), Val::I32(x as i32)], &mut [])
.unwrap();
}
fn extism_store_u64<T>(
mut store: &mut wasmtime::Store<T>,
instance: &mut Instance,
p: u64,
x: u64,
) {
fn extism_store_u64<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64, x: u64) {
instance
.get_func(&mut store, "store_u64")
.get_func(&mut *store, "store_u64")
.unwrap()
.call(
&mut store,
&[Val::I64(p as i64), Val::I64(x as i64)],
&mut [],
)
.call(store, &[Val::I64(p as i64), Val::I64(x as i64)], &mut [])
.unwrap();
}
fn extism_free<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) {
fn extism_free<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) {
instance
.get_func(&mut store, "free")
.get_func(&mut *store, "free")
.unwrap()
.call(&mut store, &[Val::I64(p as i64)], &mut [])
.call(store, &[Val::I64(p as i64)], &mut [])
.unwrap();
}
fn extism_error_set<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) {
fn extism_error_set<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64) {
instance
.get_func(&mut store, "error_set")
.get_func(&mut *store, "error_set")
.unwrap()
.call(&mut store, &[Val::I64(p as i64)], &mut [])
.call(store, &[Val::I64(p as i64)], &mut [])
.unwrap();
}
fn extism_error_get<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance) -> u64 {
fn extism_error_get<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance) -> u64 {
let out = &mut [Val::I64(0)];
instance
.get_func(&mut store, "error_get")
.get_func(&mut *store, "error_get")
.unwrap()
.call(&mut store, &[], out)
.call(store, &[], out)
.unwrap();
out[0].unwrap_i64() as u64
}
fn extism_reset<T>(mut store: &mut wasmtime::Store<T>, instance: &mut Instance) {
fn extism_reset<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance) {
instance
.get_func(&mut store, "reset")
.get_func(&mut *store, "reset")
.unwrap()
.call(&mut store, &[], &mut [])
.call(store, &[], &mut [])
.unwrap();
}
fn extism_input_set<T>(
mut store: &mut wasmtime::Store<T>,
instance: &mut Instance,
p: u64,
l: u64,
) {
fn extism_input_set<T>(store: &mut wasmtime::Store<T>, instance: &mut Instance, p: u64, l: u64) {
instance
.get_func(&mut store, "input_set")
.get_func(&mut *store, "input_set")
.unwrap()
.call(
&mut store,
&[Val::I64(p as i64), Val::I64(l as i64)],
&mut [],
)
.call(store, &[Val::I64(p as i64), Val::I64(l as i64)], &mut [])
.unwrap();
}
@@ -183,9 +153,29 @@ fn test_kernel_allocations() {
// Test allocations
assert_eq!(extism_alloc(&mut store, instance, 0), 0);
// 512 bytes, test block re-use + splitting
let p = extism_alloc(&mut store, instance, 65535);
let first_alloc = p;
assert_eq!(extism_length(&mut store, instance, p), 65535);
assert_eq!(extism_length(&mut store, instance, p + 1), 0);
assert_eq!(extism_length(&mut store, instance, p + 2), 0);
assert_eq!(extism_length(&mut store, instance, p + 3), 0);
assert_eq!(extism_length(&mut store, instance, p + 4), 0);
extism_free(&mut store, instance, p);
// Should re-use the previous block
let q = extism_alloc(&mut store, instance, 65535);
assert_eq!(q, p);
assert_eq!(extism_length(&mut store, instance, q), 65535);
extism_free(&mut store, instance, q);
let r = extism_alloc(&mut store, instance, 65535 - 24);
assert_eq!(r, q);
assert_eq!(extism_length(&mut store, instance, q), 65535 - 24);
extism_free(&mut store, instance, r);
// 1 byte
let p = extism_alloc(&mut store, instance, 1);
let first_alloc = p;
assert!(p > 0);
assert_eq!(extism_length(&mut store, instance, p), 1);
assert_eq!(extism_length_unsafe(&mut store, instance, p), 1);
@@ -205,37 +195,8 @@ fn test_kernel_allocations() {
assert_eq!(extism_length(&mut store, instance, p), 64 - i);
assert_eq!(extism_length_unsafe(&mut store, instance, p), 64 - i);
extism_free(&mut store, instance, p);
// should re-use the last allocation
let q = extism_alloc(&mut store, instance, 64 - i);
assert_eq!(p, q);
assert_eq!(extism_length(&mut store, instance, q), 64 - i);
assert_eq!(extism_length_unsafe(&mut store, instance, q), 64 - i);
extism_free(&mut store, instance, q);
}
// 512 bytes, test block re-use + splitting
let p = extism_alloc(&mut store, instance, 512);
assert_eq!(extism_length(&mut store, instance, p), 512);
assert_eq!(extism_length(&mut store, instance, p + 1), 0);
assert_eq!(extism_length(&mut store, instance, p + 2), 0);
assert_eq!(extism_length(&mut store, instance, p + 3), 0);
assert_eq!(extism_length(&mut store, instance, p + 4), 0);
extism_free(&mut store, instance, p);
// 128 bytes, should be split off the 512 byte block
let q = extism_alloc(&mut store, instance, 128);
assert!(p <= q && q < p + 512);
assert_eq!(extism_length(&mut store, instance, q), 128);
extism_free(&mut store, instance, q);
// 128 bytes, same as above
let r = extism_alloc(&mut store, instance, 128);
assert!(p <= r && r < p + 512);
assert!(r > p);
assert_eq!(extism_length(&mut store, instance, r), 128);
extism_free(&mut store, instance, q);
// 100 pages
let p = extism_alloc(&mut store, instance, 6553600);
assert!(p > 0);

View File

@@ -1,7 +1,7 @@
use extism_manifest::MemoryOptions;
use extism_manifest::{HttpRequest, MemoryOptions};
use crate::*;
use std::{io::Write, time::Instant};
use std::{collections::HashMap, io::Write, time::Instant};
const WASM: &[u8] = include_bytes!("../../../wasm/code-functions.wasm");
const WASM_NO_FUNCTIONS: &[u8] = include_bytes!("../../../wasm/code.wasm");
@@ -9,6 +9,8 @@ const WASM_LOOP: &[u8] = include_bytes!("../../../wasm/loop.wasm");
const WASM_GLOBALS: &[u8] = include_bytes!("../../../wasm/globals.wasm");
const WASM_REFLECT: &[u8] = include_bytes!("../../../wasm/reflect.wasm");
const WASM_HTTP: &[u8] = include_bytes!("../../../wasm/http.wasm");
const WASM_HTTP_HEADERS: &[u8] = include_bytes!("../../../wasm/http_headers.wasm");
const WASM_FS: &[u8] = include_bytes!("../../../wasm/read_write.wasm");
host_fn!(pub hello_world (a: String) -> String { Ok(a) });
@@ -240,6 +242,51 @@ fn test_timeout() {
assert!(err == "timeout");
}
#[test]
fn test_fuel() {
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_LOOP)]);
let mut plugin = PluginBuilder::new(manifest)
.with_wasi(true)
.with_fuel_limit(1)
.build()
.unwrap();
for _ in 0..10001 {
let output: Result<&[u8], Error> = plugin.call("loop_forever", "abc123");
let err = output.unwrap_err().root_cause().to_string();
println!("Fuel limited plugin exited with error: {:?}", &err);
assert!(err.contains("fuel"));
}
}
#[test]
#[cfg(feature = "http")]
fn test_http_timeout() {
let f = Function::new(
"hello_world",
[PTR],
[PTR],
UserData::default(),
hello_world,
);
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_HTTP)])
.with_timeout(std::time::Duration::from_millis(1))
.with_allowed_host("www.extism.org");
let mut plugin = Plugin::new(manifest, [f], true).unwrap();
let start = std::time::Instant::now();
let output: Result<&[u8], Error> =
plugin.call("http_request", r#"{"url": "https://www.extism.org"}"#);
let end = std::time::Instant::now();
let time = end - start;
let err = output.unwrap_err().root_cause().to_string();
println!(
"Timed out plugin ran for {:?}, with error: {:?}",
time, &err
);
assert!(err == "timeout");
}
typed_plugin!(pub TestTypedPluginGenerics {
count_vowels<T: FromBytes<'a>>(&str) -> T
});
@@ -288,8 +335,10 @@ fn test_multiple_instantiations() {
#[test]
fn test_globals() {
let mut plugin = Plugin::new(WASM_GLOBALS, [], true).unwrap();
for i in 0..100000 {
let Json(count) = plugin.call::<_, Json<Count>>("globals", "").unwrap();
for i in 0..100001 {
let Json(count) = plugin
.call_with_host_context::<_, Json<Count>, _>("globals", "", ())
.unwrap();
assert_eq!(count.count, i);
}
}
@@ -307,6 +356,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>()?.clone();
let hnd = current_plugin.memory_new(foo.message)?;
ret[0] = current_plugin.memory_to_val(hnd);
Ok(())
},
);
let mut plugin = Plugin::new(WASM_REFLECT, [f], true).unwrap();
let message = "hello world";
let output: String = plugin
.call_with_host_context(
"reflect",
"anything, really",
Foo {
message: message.to_string(),
},
)
.unwrap();
assert_eq!(output, message);
}
#[test]
fn test_fuzz_reflect_plugin() {
// assert!(set_log_file("stdout", Some(log::Level::Trace)));
@@ -453,7 +538,7 @@ fn hello_world_user_data(
_plugin: &mut CurrentPlugin,
inputs: &[Val],
outputs: &mut [Val],
user_data: UserData<std::sync::Arc<std::sync::Mutex<std::fs::File>>>,
user_data: UserData<std::fs::File>,
) -> Result<(), Error> {
let data = user_data.get()?;
let mut data = data.lock().unwrap();
@@ -470,8 +555,7 @@ fn test_userdata() {
if path.exists() {
std::fs::remove_file(&path).unwrap();
}
let file =
std::sync::Arc::new(std::sync::Mutex::new(std::fs::File::create(&path).unwrap()));
let file = std::fs::File::create(&path).unwrap();
let f = Function::new(
"hello_world",
[PTR],
@@ -687,3 +771,56 @@ fn test_linking() {
assert_eq!(plugin.call::<&str, i64>("run", "Hello, world!").unwrap(), 1);
}
}
#[test]
fn test_readonly_dirs() {
let wasm = Wasm::data(WASM_FS);
let manifest = Manifest::new([wasm])
.with_allowed_path("ro:src/tests/data".to_string(), "/data")
.with_config_key("path", "/data/data.txt");
let mut plugin = PluginBuilder::new(manifest)
.with_wasi(true)
.build()
.unwrap();
let res = plugin.call::<&str, &str>("try_read", "").unwrap();
assert_eq!(res, "hello world!");
let line = "hello world 2";
let res2 = plugin.call::<&str, &str>("try_write", line);
assert!(
res2.is_err(),
"Expected try_write to fail, but it succeeded."
);
}
#[test]
#[cfg(feature = "http")]
fn test_http_response_headers() {
let mut plugin = PluginBuilder::new(
Manifest::new([Wasm::data(WASM_HTTP_HEADERS)]).with_allowed_host("extism.org"),
)
.with_http_response_headers(true)
.build()
.unwrap();
let req = HttpRequest::new("https://extism.org");
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
println!("{:?}", res);
assert_eq!(res["content-type"], "text/html; charset=utf-8");
}
#[test]
#[cfg(feature = "http")]
fn test_http_response_headers_disabled() {
let mut plugin = PluginBuilder::new(
Manifest::new([Wasm::data(WASM_HTTP_HEADERS)]).with_allowed_host("extism.org"),
)
.with_http_response_headers(false)
.build()
.unwrap();
let req = HttpRequest::new("https://extism.org");
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
println!("{:?}", res);
assert!(res.is_empty());
}

BIN
wasm/allocations.wasm Executable file

Binary file not shown.

BIN
wasm/http_headers.wasm Executable file

Binary file not shown.

BIN
wasm/read_write.wasm Normal file

Binary file not shown.

BIN
wasm/unreachable.wasm Executable file

Binary file not shown.