Compare commits

...

37 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
31 changed files with 1277 additions and 446 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

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

@@ -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)
@@ -61,7 +61,7 @@ started:
# Compile WebAssembly to run in Extism Hosts
Extism Hosts (running the SDK) must execute WebAssembly code that has a PDK
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

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

View File

@@ -279,7 +279,7 @@ pub struct Manifest {
/// the path on disk to the path it should be available inside the plugin.
/// For example, `".": "/tmp"` would mount the current directory as `/tmp` inside the module
#[serde(default)]
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
pub allowed_paths: Option<BTreeMap<String, PathBuf>>,
/// The plugin timeout in milliseconds
#[serde(default)]
@@ -337,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) => {
@@ -355,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,8 +9,9 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = ">= 20.0.0, < 22.0.0"
wasi-common = ">= 20.0.0, < 22.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"
@@ -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.2.0"
extism = "1.4.1"
```
## Environment variables

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

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

@@ -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
@@ -179,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
*
@@ -195,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);
@@ -302,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,6 +14,7 @@ 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,
@@ -53,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);
}
@@ -160,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 []);
}
@@ -174,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(&[]);
}
@@ -187,20 +195,26 @@ impl CurrentPlugin {
anyhow::bail!("{} unable to locate extism memory", self.id)
}
pub fn host_context<T: Clone + 'static>(&mut self) -> Result<T, Error> {
let (linker, mut store) = self.linker_and_store();
let Some(Extern::Global(xs)) = linker.get(&mut store, EXTISM_ENV_MODULE, "extism_context")
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 {
let Val::ExternRef(Some(xs)) = xs.get(&mut *store) else {
anyhow::bail!("expected extism_context to be an externref value",)
};
match xs.data(&mut store)?.downcast_ref::<T>().cloned() {
Some(xs) => Ok(xs.clone()),
None => anyhow::bail!("could not downcast extism_context",),
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"),
}
}
@@ -214,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);
}
@@ -238,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",)
}
@@ -250,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",)
}
@@ -270,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",)
}
@@ -308,6 +338,7 @@ 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 {
@@ -320,9 +351,20 @@ impl CurrentPlugin {
if let Some(a) = &manifest.allowed_paths {
for (k, v) in a.iter() {
let file = Box::new(wasi_common::sync::dir::Dir::from_cap_std(
wasi_common::sync::Dir::open_ambient_dir(k, auth)?,
));
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)?;
}
}
@@ -340,7 +382,7 @@ impl CurrentPlugin {
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,
})
@@ -359,6 +401,11 @@ impl CurrentPlugin {
memory_limiter,
id,
start_time: std::time::Instant::now(),
http_headers: if allow_http_response_headers {
Some(BTreeMap::new())
} else {
None
},
})
}
@@ -403,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(),
@@ -439,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 {
@@ -454,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

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

Binary file not shown.

View File

@@ -279,7 +279,7 @@ impl Function {
/// For example, the following defines a host function named `add_newline` that takes a
/// string parameter and returns a string result:
/// ```rust
/// extism::host_fn!(add_newline(_user_data: (), a: String) -> String { Ok(a + "\n") });
/// extism::host_fn!(add_newline(_user_data: (); a: String) -> String { Ok(a + "\n") });
/// ```
/// A few things worth noting:
/// - The function always returns a `Result` that wraps the specified return type

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

@@ -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;
@@ -235,8 +257,19 @@ 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())
}
@@ -294,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>,
@@ -301,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);
@@ -331,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],
@@ -348,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],
@@ -359,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],
@@ -370,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],
@@ -377,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,9 +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";
@@ -36,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
@@ -81,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 {}
@@ -212,14 +280,24 @@ 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>), Error> {
) -> 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),*)?);* $(;)?) => {
@@ -238,10 +316,13 @@ fn relink(
var_set(I64, I64);
http_request(I64, I64) -> I64;
http_status_code() -> I32;
http_headers() -> I64;
log_warn(I64);
log_info(I64);
log_debug(I64);
log_error(I64);
log_trace(I64);
get_log_level() -> I32;
);
let mut linked = BTreeSet::new();
@@ -274,9 +355,12 @@ fn relink(
)?;
}
let inner: Box<dyn std::any::Any + Send + Sync> = Box::new(());
let host_context = ExternRef::new(store, inner)?;
let main = &modules[MAIN_KEY];
let instance_pre = linker.instantiate_pre(main)?;
Ok((instance_pre, linker))
Ok((instance_pre, linker, host_context))
}
impl Plugin {
@@ -287,67 +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)
.wasm_gc(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);
if let Some(fuel) = compiled.options.fuel {
store.set_fuel(fuel)?;
}
let imports: Vec<Function> = imports.into_iter().collect();
let (instance_pre, linker) = relink(&engine, &mut store, &imports, &modules, with_wasi)?;
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,
@@ -359,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;
@@ -389,12 +454,17 @@ impl Plugin {
internal.manifest.clone(),
internal.wasi.is_some(),
internal.available_pages,
internal.http_headers.is_some(),
self.id,
)?,
);
self.store.set_epoch_deadline(1);
let (instance_pre, linker) = relink(
if let Some(fuel) = self.fuel {
self.store.set_fuel(fuel)?;
}
let (instance_pre, linker, host_context) = relink(
&engine,
&mut self.store,
&self._functions,
@@ -403,6 +473,7 @@ impl Plugin {
)?;
self.linker = linker;
self.instance_pre = instance_pre;
self.host_context = host_context;
let store = &mut self.store as *mut _;
let linker = &mut self.linker as *mut _;
let current_plugin = self.current_plugin_mut();
@@ -510,10 +581,16 @@ 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")
)?;
}
@@ -521,7 +598,8 @@ impl Plugin {
self.linker
.get(&mut self.store, EXTISM_ENV_MODULE, "extism_context")
{
ctxt.set(&mut self.store, Val::ExternRef(host_context))?;
ctxt.set(&mut self.store, Val::ExternRef(host_context))
.context("unable to set extism host context")?;
}
Ok(())
@@ -532,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");
}
@@ -608,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::I32(0); 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(),
@@ -628,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");
}
}
@@ -641,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")
}
@@ -668,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
@@ -696,26 +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<Rooted<ExternRef>>,
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(), host_context)
// 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) {
@@ -752,40 +875,71 @@ impl Plugin {
let mut results = vec![wasmtime::Val::I32(0); n_results];
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
// Reset host context
if let Ok(inner) = self.host_context.data_mut(&mut self.store) {
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
let x: Box<dyn Any + Send + Sync> = Box::new(());
*inner = x;
}
}
// Stop timer
self.store
.epoch_deadline_callback(|_| Ok(UpdateDeadline::Continue(1)));
let _ = self.timer_tx.send(TimerAction::Stop { id: self.id });
self.store_needs_reset = name == "_start";
// 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?;
}
}
@@ -897,7 +1051,7 @@ impl Plugin {
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let data = input.to_bytes()?;
self.raw_call(&mut lock, name, data, None)
self.raw_call(&mut lock, name, data, None::<()>)
.map_err(|e| e.0)
.and_then(move |rc| {
if rc != 0 {
@@ -922,8 +1076,7 @@ impl Plugin {
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let data = input.to_bytes()?;
let ctx = ExternRef::new(&mut self.store, host_context)?;
self.raw_call(&mut lock, name, data, Some(ctx))
self.raw_call(&mut lock, name, data, Some(host_context))
.map_err(|e| e.0)
.and_then(move |_| self.output())
}
@@ -942,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, None)
self.raw_call(&mut lock, name, data, None::<()>)
.and_then(move |_| self.output().map_err(|e| (e, -1)))
}
@@ -953,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,16 +54,21 @@ 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
}
@@ -74,7 +87,8 @@ impl<'a> PluginBuilder<'a> {
+ Sync
+ Send,
{
self.functions
self.options
.functions
.push(Function::new(name, args, returns, user_data, f));
self
}
@@ -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 {
@@ -42,32 +48,32 @@ pub type ExtismFunctionType = extern "C" fn(
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
impl ExtismVal {
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Self {
match value.ty(ctx) {
wasmtime::ValType::I32 => 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),
}
}
@@ -96,7 +102,7 @@ pub unsafe extern "C" fn extism_current_plugin_host_context(
let plugin = &mut *plugin;
if let Ok(CVoidContainer(ptr)) = plugin.host_context::<CVoidContainer>() {
ptr
*ptr
} else {
std::ptr::null_mut()
}
@@ -221,7 +227,7 @@ pub unsafe extern "C" fn extism_function_new(
let store = &*plugin.store;
let inputs: Vec<_> = inputs
.iter()
.map(|x| ExtismVal::from_val(x, store))
.map(|x| ExtismVal::from_val(x, store).unwrap())
.collect();
let mut output_tmp: Vec<_> = output_types
.iter()
@@ -231,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(),
);
@@ -244,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!(),
}
}
@@ -280,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
@@ -296,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![];
@@ -315,6 +386,7 @@ pub unsafe extern "C" fn extism_plugin_new(
)
.unwrap();
*errmsg = e.into_raw();
return std::ptr::null_mut();
}
}
}
@@ -334,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) {
@@ -343,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() {
@@ -394,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(),
@@ -406,8 +576,8 @@ pub unsafe extern "C" fn extism_plugin_config(
let json: std::collections::BTreeMap<String, Option<String>> =
match serde_json::from_slice(data) {
Ok(x) => x,
Err(e) => {
return plugin.return_error(&mut lock, e, false);
Err(_) => {
return false;
}
};
@@ -440,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(),
@@ -452,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;
}
};
@@ -509,7 +676,10 @@ pub unsafe extern "C" fn extism_plugin_call_with_host_context(
let name = std::ffi::CStr::from_ptr(func_name);
let name = match name.to_str() {
Ok(name) => name,
Err(e) => return plugin.return_error(&mut lock, e, -1),
Err(e) => {
plugin.error_msg = Some(make_error_msg(e.to_string()));
return -1;
}
};
trace!(
@@ -518,13 +688,17 @@ pub unsafe extern "C" fn extism_plugin_call_with_host_context(
name
);
let input = std::slice::from_raw_parts(data, data_len as usize);
let r = match ExternRef::new(&mut plugin.store, CVoidContainer(host_context)) {
Err(e) => return plugin.return_error(&mut lock, e, -1),
Ok(x) => x,
let r = if host_context.is_null() {
None
} else {
Some(CVoidContainer(host_context))
};
let res = plugin.raw_call(&mut lock, name, input, Some(r));
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,
}
}
@@ -547,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
@@ -629,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()
@@ -680,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()
}

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) });
@@ -241,6 +243,23 @@ fn test_timeout() {
}
#[test]
fn test_fuel() {
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_LOOP)]);
let mut plugin = PluginBuilder::new(manifest)
.with_wasi(true)
.with_fuel_limit(1)
.build()
.unwrap();
for _ in 0..10001 {
let output: Result<&[u8], Error> = plugin.call("loop_forever", "abc123");
let err = output.unwrap_err().root_cause().to_string();
println!("Fuel limited plugin exited with error: {:?}", &err);
assert!(err.contains("fuel"));
}
}
#[test]
#[cfg(feature = "http")]
fn test_http_timeout() {
let f = Function::new(
"hello_world",
@@ -316,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);
}
}
@@ -348,7 +369,7 @@ fn test_call_with_host_context() {
[PTR],
UserData::default(),
|current_plugin, _val, ret, _user_data: UserData<()>| {
let foo = current_plugin.host_context::<Foo>()?;
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(())
@@ -750,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.