diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d3cd70d..5bbac96 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -57,11 +57,11 @@ jobs:
python-version: '3.9'
check-latest: true
- - name: Load cached Poetry installation
- uses: actions/cache@v2
- with:
- path: ~/.local
- key: poetry-0
+ # - name: Load cached Poetry installation
+ # uses: actions/cache@v2
+ # with:
+ # path: ~/.local
+ # key: poetry-0
- name: Install Poetry
uses: snok/install-poetry@v1
@@ -107,5 +107,11 @@ jobs:
# opam install -y .
# cd ocaml
# opam exec -- dune exec extism
-
-
\ No newline at end of file
+
+ - name: Setup Haskell env
+ uses: haskell/actions/setup@v2
+
+ - name: Test Haskell SDK
+ run: |
+ cd haskell
+ LD_LIBRARY_PATH=/usr/local/lib cabal test
diff --git a/.gitignore b/.gitignore
index af390fa..51accc9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,5 @@ ocaml/duniverse
ocaml/_build
wasm/rust-pdk/target
php/Extism.php
+dist-newstyle
+.stack-work
\ No newline at end of file
diff --git a/README.md b/README.md
index 912e090..d43decf 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ The universal plug-in system. Run WebAssembly extensions inside your app. Use id
[Ruby](https://extism.org/docs/integrate-into-your-codebase/ruby-host-sdk), [Python](https://extism.org/docs/integrate-into-your-codebase/python-host-sdk),
[Node](https://extism.org/docs/integrate-into-your-codebase/node-host-sdk), [Rust](https://extism.org/docs/integrate-into-your-codebase/rust-host-sdk),
[C](https://extism.org/docs/integrate-into-your-codebase/c-host-sdk), [C++](https://extism.org/docs/integrate-into-your-codebase/cpp-host-sdk),
-[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk) & more (others coming soon).
+[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk), [Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk) & more (others coming soon).
Plug-in development kits (PDK) for plug-in authors supported in Rust, AssemblyScript, Go, C/C++.
@@ -48,4 +48,4 @@ Extism is an open-source product from the team at:
-_Reach out and tell us what you're building! We'd love to help._
\ No newline at end of file
+_Reach out and tell us what you're building! We'd love to help._
diff --git a/dune-project b/dune-project
index 6d3e009..0a7785c 100644
--- a/dune-project
+++ b/dune-project
@@ -7,20 +7,18 @@
(source
(github extism/extism))
-(authors "Author Name")
+(authors "Extism Authors ")
-(maintainers "Maintainer Name")
+(maintainers "Extism Authors ")
-(license LICENSE)
+(license BSD-3)
-(documentation https://url/to/documentation)
+(documentation https://github.com/extism/extism)
(package
(name extism)
- (synopsis "A short synopsis")
- (description "A longer description")
+ (synopsis "Extism bindings")
+ (description "Bindings to Extism, the universal plugin system")
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64)
(tags
- (topics "to describe" your project)))
-
-; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
+ (topics wasm plugin)))
diff --git a/extism.opam b/extism.opam
index ff58f32..8363b4b 100644
--- a/extism.opam
+++ b/extism.opam
@@ -1,13 +1,13 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
-synopsis: "A short synopsis"
-description: "A longer description"
-maintainer: ["Maintainer Name"]
-authors: ["Author Name"]
-license: "LICENSE"
-tags: ["topics" "to describe" "your" "project"]
+synopsis: "Extism bindings"
+description: "Bindings to Extism, the universal plugin system"
+maintainer: ["Extism Authors "]
+authors: ["Extism Authors "]
+license: "BSD-3"
+tags: ["topics" "wasm" "plugin"]
homepage: "https://github.com/extism/extism"
-doc: "https://url/to/documentation"
+doc: "https://github.com/extism/extism"
bug-reports: "https://github.com/extism/extism/issues"
depends: [
"ocaml"
@@ -15,6 +15,7 @@ depends: [
"ctypes-foreign"
"bigstringaf"
"ppx_yojson_conv"
+ "base64"
"odoc" {with-doc}
]
build: [
diff --git a/haskell/CHANGELOG.md b/haskell/CHANGELOG.md
new file mode 100644
index 0000000..e6392dd
--- /dev/null
+++ b/haskell/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for extism
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/haskell/Example.hs b/haskell/Example.hs
new file mode 100644
index 0000000..9532671
--- /dev/null
+++ b/haskell/Example.hs
@@ -0,0 +1,17 @@
+module Main where
+
+import System.Exit (exitFailure, exitSuccess)
+import qualified Data.ByteString as B
+import Extism
+import Extism.Manifest
+
+main = do
+ plugin <- Extism.registerManifest (manifest [wasmFile "../wasm/code.wasm"]) False
+ res <- Extism.call plugin "count_vowels" (Extism.toByteString "this is a test")
+ case res of
+ Right (Error msg) -> do
+ _ <- putStrLn msg
+ exitFailure
+ Left bs -> do
+ _ <- putStrLn (Extism.fromByteString bs)
+ exitSuccess
diff --git a/haskell/extism.cabal b/haskell/extism.cabal
new file mode 100644
index 0000000..44e1e86
--- /dev/null
+++ b/haskell/extism.cabal
@@ -0,0 +1,47 @@
+cabal-version: 2.4
+name: extism
+version: 0.0.1.0
+
+-- A short (one-line) description of the package.
+synopsis: Extism bindings
+
+-- A longer description of the package.
+description: Bindings to Extism, the universal plugin system
+
+-- A URL where users can report bugs.
+bug-reports: https://github.com/extism/extism
+
+-- The license under which the package is released.
+license: BSD-3-Clause
+
+author: Extism authors
+maintainer: oss@extism.org
+
+-- A copyright notice.
+-- copyright:
+category: Plugins
+extra-source-files: CHANGELOG.md
+
+library
+ exposed-modules: Extism Extism.Manifest
+
+ -- Modules included in this library but not exported.
+ other-modules:
+
+ -- LANGUAGE extensions used by modules in this package.
+ -- other-extensions:
+ build-depends:
+ base ^>=4.16.1.0
+ , bytestring
+ , base64-bytestring
+ , json
+ hs-source-dirs: src
+ default-language: Haskell2010
+ extra-libraries: extism
+ extra-lib-dirs: /usr/local/lib
+
+Test-Suite extism-example
+ type: exitcode-stdio-1.0
+ main-is: Example.hs
+ build-depends: base, extism, bytestring
+ default-language: Haskell2010
diff --git a/haskell/src/Extism.hs b/haskell/src/Extism.hs
new file mode 100644
index 0000000..de9abc2
--- /dev/null
+++ b/haskell/src/Extism.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Extism (module Extism, module Extism.Manifest) where
+import GHC.Int
+import GHC.Word
+import Foreign.C.Types
+import Foreign.Ptr
+import Foreign.C.String
+import Control.Monad (void)
+import Data.ByteString as B
+import Data.ByteString.Internal (c2w, w2c)
+import Data.ByteString.Unsafe (unsafeUseAsCString)
+import Text.JSON (JSON, toJSObject, encode)
+import Extism.Manifest (Manifest, toString)
+
+foreign import ccall unsafe "extism.h extism_plugin_register" extism_plugin_register :: Ptr Word8 -> Word64 -> CBool -> IO Int32
+foreign import ccall unsafe "extism.h extism_call" extism_call :: Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
+foreign import ccall unsafe "extism.h extism_function_exists" extism_function_exists :: Int32 -> CString -> IO CBool
+foreign import ccall unsafe "extism.h extism_error" extism_error :: Int32 -> IO CString
+foreign import ccall unsafe "extism.h extism_output_length" extism_output_length :: Int32 -> IO Word64
+foreign import ccall unsafe "extism.h extism_output_get" extism_output_get :: Int32 -> Ptr Word8 -> Word64 -> IO ()
+foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
+foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Int32 -> Ptr Word8 -> Int64 -> IO CBool
+
+newtype Plugin = Plugin Int32 deriving Show
+newtype Error = Error String deriving Show
+
+toByteString :: String -> ByteString
+toByteString x = B.pack (Prelude.map c2w x)
+
+fromByteString :: ByteString -> String
+fromByteString bs = Prelude.map w2c $ B.unpack bs
+
+register :: B.ByteString -> Bool -> IO Plugin
+register wasm useWasi =
+ let length = fromIntegral (B.length wasm) in
+ let wasi = fromInteger (if useWasi then 1 else 0) in
+ do
+ p <- unsafeUseAsCString wasm (\s ->
+ extism_plugin_register (castPtr s) length wasi)
+ return $ Plugin p
+
+registerManifest :: Manifest -> Bool -> IO Plugin
+registerManifest manifest useWasi =
+ let wasm = toByteString $ toString manifest in
+ register wasm useWasi
+
+isValid :: Plugin -> Bool
+isValid (Plugin p) = p >= 0
+
+setConfig :: Plugin -> [(String, String)] -> IO ()
+setConfig (Plugin plugin) x =
+ if plugin < 0
+ then return ()
+ else
+ let obj = toJSObject x in
+ let bs = toByteString (encode obj) in
+ let length = fromIntegral (B.length bs) in
+ unsafeUseAsCString bs (\s -> do
+ void $ extism_plugin_config plugin (castPtr s) length)
+
+setLogFile :: String -> String -> IO ()
+setLogFile filename level =
+ withCString filename (\f ->
+ withCString level (\l -> do
+ void $ extism_log_file f l))
+
+functionExists :: Plugin -> String -> IO Bool
+functionExists (Plugin plugin) name = do
+ b <- withCString name (extism_function_exists plugin)
+ if b == 1 then return True else return False
+
+call :: Plugin -> String -> B.ByteString -> IO (Either B.ByteString Error)
+call (Plugin plugin) name input =
+ let length = fromIntegral (B.length input) in
+ do
+ rc <- withCString name (\name ->
+ unsafeUseAsCString input (\input ->
+ extism_call plugin name (castPtr input) length))
+ err <- extism_error plugin
+ if err /= nullPtr
+ then do e <- peekCString err
+ return $ Right (Error e)
+ else if rc == 0
+ then do
+ length <- extism_output_length plugin
+ let output = B.replicate (fromIntegral length) 0
+ () <- unsafeUseAsCString output (\a ->
+ extism_output_get plugin (castPtr a) length)
+ return $ Left output
+ else return $ Right (Error "Call failed")
diff --git a/haskell/src/Extism/Manifest.hs b/haskell/src/Extism/Manifest.hs
new file mode 100644
index 0000000..ddb7928
--- /dev/null
+++ b/haskell/src/Extism/Manifest.hs
@@ -0,0 +1,168 @@
+module Extism.Manifest where
+
+import Text.JSON
+ (
+ JSValue(JSNull, JSString, JSArray),
+ toJSString, showJSON, makeObj, encode
+ )
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Base64 as B64
+import qualified Data.ByteString.Char8 as BS (unpack)
+
+valueOrNull f Nothing = JSNull
+valueOrNull f (Just x) = f x
+makeString s = JSString (toJSString s)
+stringOrNull = valueOrNull makeString
+makeArray f x = JSArray [f a | a <- x]
+filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)]
+mapObj f x = makeObj (filterNulls [(a, f b) | (a, b) <- x])
+isNull JSNull = True
+isNull _ = False
+
+newtype Memory = Memory
+ {
+ memoryMax :: Maybe Int
+ }
+
+class JSONValue a where
+ toJSONValue :: a -> JSValue
+
+instance JSONValue Memory where
+ toJSONValue x =
+ case memoryMax x of
+ Nothing -> makeObj []
+ Just max -> makeObj [("max", showJSON max)]
+
+data HttpRequest = HttpRequest
+ {
+ url :: String
+ , header :: [(String, String)]
+ , method :: Maybe String
+ }
+
+requestObj x =
+ let meth = stringOrNull $ method x in
+ let h = mapObj makeString $ header x in
+ filterNulls [
+ ("url", makeString $ url x),
+ ("header", h),
+ ("method", meth)
+ ]
+
+instance JSONValue HttpRequest where
+ toJSONValue x =
+ makeObj $ requestObj x
+
+data WasmFile = WasmFile
+ {
+ filePath :: String
+ , fileName :: Maybe String
+ , fileHash :: Maybe String
+ }
+
+instance JSONValue WasmFile where
+ toJSONValue x =
+ let path = makeString $ filePath x in
+ let name = stringOrNull $ fileName x in
+ let hash = stringOrNull $ fileHash x in
+ makeObj $ filterNulls [
+ ("path", path),
+ ("name", name),
+ ("hash", hash)
+ ]
+
+data WasmCode = WasmCode
+ {
+ codeBytes :: B.ByteString
+ , codeName :: Maybe String
+ , codeHash :: Maybe String
+ }
+
+
+instance JSONValue WasmCode where
+ toJSONValue x =
+ let bytes = makeString $ BS.unpack $ B64.encode $ codeBytes x in
+ let name = stringOrNull $ codeName x in
+ let hash = stringOrNull $ codeHash x in
+ makeObj $ filterNulls [
+ ("data", bytes),
+ ("name", name),
+ ("hash", hash)
+ ]
+
+data WasmURL = WasmURL
+ {
+ req :: HttpRequest
+ , urlName :: Maybe String
+ , urlHash :: Maybe String
+ }
+
+
+instance JSONValue WasmURL where
+ toJSONValue x =
+ let request = requestObj $ req x in
+ let name = stringOrNull $ urlName x in
+ let hash = stringOrNull $ urlHash x in
+ makeObj $ filterNulls $ ("name", name) : ("hash", hash) : request
+
+data Wasm = File WasmFile | Code WasmCode | URL WasmURL
+
+instance JSONValue Wasm where
+ toJSONValue x =
+ case x of
+ File f -> toJSONValue f
+ Code d -> toJSONValue d
+ URL u -> toJSONValue u
+
+wasmFile :: String -> Wasm
+wasmFile path =
+ File WasmFile { filePath = path, fileName = Nothing, fileHash = Nothing}
+
+wasmURL :: String -> String -> Wasm
+wasmURL method url =
+ let r = HttpRequest { url = url, header = [], method = Just method } in
+ URL WasmURL { req = r, urlName = Nothing, urlHash = Nothing }
+
+wasmCode :: B.ByteString -> Wasm
+wasmCode code =
+ Code WasmCode { codeBytes = code, codeName = Nothing, codeHash = Nothing }
+
+withName :: Wasm -> String -> Wasm
+withName (Code code) name = Code code { codeName = Just name }
+withName (URL url) name = URL url { urlName = Just name }
+withName (File f) name = File f { fileName = Just name }
+
+
+withHash :: Wasm -> String -> Wasm
+withHash (Code code) hash = Code code { codeHash = Just hash }
+withHash (URL url) hash = URL url { urlHash = Just hash }
+withHash (File f) hash = File f { fileHash = Just hash }
+
+data Manifest = Manifest
+ {
+ wasm :: [Wasm]
+ , memory :: Maybe Memory
+ , config :: [(String, String)]
+ }
+
+manifest :: [Wasm] -> Manifest
+manifest wasm = Manifest {wasm = wasm, memory = Nothing, config = []}
+
+withConfig :: Manifest -> [(String, String)] -> Manifest
+withConfig m config =
+ m { config = config }
+
+instance JSONValue Manifest where
+ toJSONValue x =
+ let w = makeArray toJSONValue $ wasm x in
+ let mem = valueOrNull toJSONValue $ memory x in
+ let c = mapObj makeString $ config x in
+ makeObj $ filterNulls [
+ ("wasm", w),
+ ("memory", mem),
+ ("config", c)
+ ]
+
+toString :: Manifest -> String
+toString manifest =
+ encode (toJSONValue manifest)
diff --git a/haskell/stack.yaml b/haskell/stack.yaml
new file mode 100644
index 0000000..6e21afc
--- /dev/null
+++ b/haskell/stack.yaml
@@ -0,0 +1,69 @@
+# This file was automatically generated by 'stack init'
+#
+# Some commonly used options have been documented as comments in this file.
+# For advanced use and comprehensive documentation of the format, please see:
+# https://docs.haskellstack.org/en/stable/yaml_configuration/
+
+# Resolver to choose a 'specific' stackage snapshot or a compiler version.
+# A snapshot resolver dictates the compiler version and the set of packages
+# to be used for project dependencies. For example:
+#
+# resolver: lts-3.5
+# resolver: nightly-2015-09-21
+# resolver: ghc-7.10.2
+#
+# The location of a snapshot can be provided as a file or url. Stack assumes
+# a snapshot provided as a file might change, whereas a url resource does not.
+#
+# resolver: ./custom-snapshot.yaml
+# resolver: https://example.com/snapshots/2018-01-01.yaml
+resolver:
+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
+
+# User packages to be built.
+# Various formats can be used as shown in the example below.
+#
+# packages:
+# - some-directory
+# - https://example.com/foo/bar/baz-0.0.2.tar.gz
+# subdirs:
+# - auto-update
+# - wai
+packages:
+- .
+- dist-newstyle/tmp/src-3036517/semialign-1.2.0.1
+- dist-newstyle/tmp/src-3036516/witherable-0.4.2
+# Dependency packages to be pulled from upstream that are not in the resolver.
+# These entries can reference officially published versions as well as
+# forks / in-progress versions pinned to a git hash. For example:
+#
+# extra-deps:
+# - acme-missiles-0.3
+# - git: https://github.com/commercialhaskell/stack.git
+# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+#
+# extra-deps: []
+
+# Override default flag values for local packages and extra-deps
+# flags: {}
+
+# Extra package databases containing global packages
+# extra-package-dbs: []
+
+# Control whether we use the GHC we find on the path
+# system-ghc: true
+#
+# Require a specific version of stack, using version ranges
+# require-stack-version: -any # Default
+# require-stack-version: ">=2.7"
+#
+# Override the architecture used by stack, especially useful on Windows
+# arch: i386
+# arch: x86_64
+#
+# Extra directories used by stack for building
+# extra-include-dirs: [/path/to/dir]
+# extra-lib-dirs: [/path/to/dir]
+#
+# Allow a newer minor version of GHC than the snapshot specifies
+# compiler-check: newer-minor
diff --git a/haskell/stack.yaml.lock b/haskell/stack.yaml.lock
new file mode 100644
index 0000000..8a4b526
--- /dev/null
+++ b/haskell/stack.yaml.lock
@@ -0,0 +1,13 @@
+# This file was autogenerated by Stack.
+# You should not edit this file by hand.
+# For more information, please see the documentation at:
+# https://docs.haskellstack.org/en/stable/lock_files
+
+packages: []
+snapshots:
+- completed:
+ size: 632828
+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
+ sha256: 5b02c2ce430ac62843fb884126765628da2ca2280bb9de0c6635c723e32a9f6b
+ original:
+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml
diff --git a/manifest/Cargo.toml b/manifest/Cargo.toml
index 51a8e06..54e6d53 100644
--- a/manifest/Cargo.toml
+++ b/manifest/Cargo.toml
@@ -10,4 +10,16 @@ description = "Extism plug-in manifest crate"
[dependencies]
serde = {version = "1", features=["derive"]}
-base64 = "0.20.0-alpha"
\ No newline at end of file
+base64 = "0.20.0-alpha"
+schemars = {version = "0.8", optional=true}
+
+[features]
+json_schema = ["schemars"]
+
+[dev-dependencies]
+serde_json = "1"
+
+[[example]]
+name = "json_schema"
+required-features = ["json_schema"]
+
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index f08f43c..3164a56 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -15,8 +15,8 @@ crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-wasmtime = "0.39.1"
-wasmtime-wasi = "0.39.1"
+wasmtime = "0.40.0"
+wasmtime-wasi = "0.40.0"
anyhow = "1"
serde = { version = "1", features=["derive"] }
toml = "0.5"
@@ -26,14 +26,13 @@ log = "0.4"
log4rs = "1.1"
ureq = {version = "2.5", optional=true}
extism-manifest = { version = "0.0.1-alpha", path = "../manifest" }
-pretty-hex = { version = "0.3", optional = true }
+pretty-hex = { version = "0.3" }
[features]
default = ["http", "register-http", "register-filesystem"]
register-http = ["ureq"] # enables wasm to be downloaded using http
register-filesystem = [] # enables wasm to be loaded from disk
http = ["ureq"] # enables extism_http_request
-debug = ["pretty-hex"]
[build-dependencies]
cbindgen = "0.24"
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index e464be3..64e07f7 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -16,4 +16,4 @@ pub use plugin_ref::PluginRef;
pub type Size = u64;
pub type PluginIndex = i32;
-pub(crate) use log::{debug, error, info};
+pub(crate) use log::{debug, error, info, trace};
diff --git a/runtime/src/memory.rs b/runtime/src/memory.rs
index 23b2ea5..5bb0b0d 100644
--- a/runtime/src/memory.rs
+++ b/runtime/src/memory.rs
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use crate::*;
-#[cfg(feature = "debug")]
use pretty_hex::PrettyHex;
/// Handles memory for plugins
@@ -32,6 +31,7 @@ impl PluginMemory {
}
pub(crate) fn store_u8(&mut self, offs: usize, data: u8) -> Result<(), MemoryAccessError> {
+ trace!("store_u8: {data:x} at offset {offs}");
if offs >= self.size() {
// This should raise MemoryAccessError
let buf = &mut [0];
@@ -44,6 +44,7 @@ impl PluginMemory {
/// Read from memory
pub(crate) fn load_u8(&self, offs: usize) -> Result {
+ trace!("load_u8: offset {offs}");
if offs >= self.size() {
// This should raise MemoryAccessError
let buf = &mut [0];
@@ -54,6 +55,7 @@ impl PluginMemory {
}
pub(crate) fn store_u32(&mut self, offs: usize, data: u32) -> Result<(), MemoryAccessError> {
+ trace!("store_u32: {data:x} at offset {offs}");
let handle = MemoryBlock {
offset: offs,
length: 4,
@@ -64,6 +66,7 @@ impl PluginMemory {
/// Read from memory
pub(crate) fn load_u32(&self, offs: usize) -> Result {
+ trace!("load_u32: offset {offs}");
let mut buf = [0; 4];
let handle = MemoryBlock {
@@ -75,6 +78,7 @@ impl PluginMemory {
}
pub(crate) fn store_u64(&mut self, offs: usize, data: u64) -> Result<(), MemoryAccessError> {
+ trace!("store_u64: {data:x} at offset {offs}");
let handle = MemoryBlock {
offset: offs,
length: 8,
@@ -84,6 +88,7 @@ impl PluginMemory {
}
pub(crate) fn load_u64(&self, offs: usize) -> Result {
+ trace!("load_u64: offset {offs}");
let mut buf = [0; 8];
let handle = MemoryBlock {
offset: offs,
@@ -222,11 +227,10 @@ impl PluginMemory {
}
}
- #[cfg(feature = "debug")]
pub fn dump(&self) {
let data = self.memory.data(&self.store);
- println!("{:?}", data[..self.position].hex_dump());
+ trace!("{:?}", data[..self.position].hex_dump());
}
/// Reset memory
diff --git a/runtime/src/plugin.rs b/runtime/src/plugin.rs
index 7135e2b..0d214ba 100644
--- a/runtime/src/plugin.rs
+++ b/runtime/src/plugin.rs
@@ -152,6 +152,7 @@ impl Plugin {
/// Set `last_error` field
pub fn set_error(&mut self, e: impl std::fmt::Debug) {
+ debug!("Set error: {:?}", e);
let x = format!("{:?}", e).into_bytes();
let x = if x[0] == b'"' && x[x.len() - 1] == b'"' {
x[1..x.len() - 1].to_vec()
@@ -181,7 +182,6 @@ impl Plugin {
internal.plugin = ptr;
}
- #[cfg(feature = "debug")]
pub fn dump_memory(&self) {
self.memory.dump();
}
diff --git a/runtime/src/plugin_ref.rs b/runtime/src/plugin_ref.rs
index 50140df..06ff4a2 100644
--- a/runtime/src/plugin_ref.rs
+++ b/runtime/src/plugin_ref.rs
@@ -2,12 +2,17 @@ use crate::*;
// PluginRef is used to access a plugin from the global plugin registry
pub struct PluginRef<'a> {
+ pub id: PluginIndex,
pub plugins: std::sync::MutexGuard<'a, Vec>,
plugin: *mut Plugin,
}
impl<'a> PluginRef<'a> {
pub fn init(mut self) -> Self {
+ trace!(
+ "Resetting memory and clearing error message for plugin {}",
+ self.id,
+ );
// Initialize
self.as_mut().clear_error();
self.as_mut().memory.reset();
@@ -17,20 +22,26 @@ impl<'a> PluginRef<'a> {
/// # Safety
///
/// This function is used to access the static `PLUGINS` registry
- pub unsafe fn new(plugin: PluginIndex) -> Self {
+ pub unsafe fn new(plugin_id: PluginIndex) -> Self {
let mut plugins = match PLUGINS.lock() {
Ok(p) => p,
Err(e) => e.into_inner(),
};
- if plugin < 0 || plugin as usize >= plugins.len() {
+ trace!("Loading plugin {plugin_id}");
+
+ if plugin_id < 0 || plugin_id as usize >= plugins.len() {
drop(plugins);
- panic!("Invalid PluginIndex {plugin}");
+ panic!("Invalid PluginIndex {plugin_id}");
}
- let plugin = plugins.get_unchecked_mut(plugin as usize) as *mut _;
+ let plugin = plugins.get_unchecked_mut(plugin_id as usize) as *mut _;
- PluginRef { plugins, plugin }
+ PluginRef {
+ id: plugin_id,
+ plugins,
+ plugin,
+ }
}
}
@@ -48,6 +59,7 @@ impl<'a> AsMut for PluginRef<'a> {
impl<'a> Drop for PluginRef<'a> {
fn drop(&mut self) {
+ trace!("Dropping plugin {}", self.id);
// Cleanup?
}
}
diff --git a/runtime/src/sdk.rs b/runtime/src/sdk.rs
index 23f4626..3ddf615 100644
--- a/runtime/src/sdk.rs
+++ b/runtime/src/sdk.rs
@@ -11,6 +11,10 @@ pub unsafe extern "C" fn extism_plugin_register(
wasm_size: Size,
with_wasi: bool,
) -> PluginIndex {
+ trace!(
+ "Call to extism_plugin_register with wasm pointer {:?}",
+ wasm
+ );
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let plugin = match Plugin::new(data, with_wasi) {
Ok(x) => x,
@@ -40,6 +44,12 @@ pub unsafe extern "C" fn extism_plugin_config(
) -> bool {
let mut plugin = PluginRef::new(plugin);
+ trace!(
+ "Call to extism_plugin_config for {} with json pointer {:?}",
+ plugin.id,
+ json
+ );
+
let data = std::slice::from_raw_parts(json, json_size as usize);
let json: std::collections::BTreeMap = match serde_json::from_slice(data) {
Ok(x) => x,
@@ -53,6 +63,7 @@ pub unsafe extern "C" fn extism_plugin_config(
let wasi = &mut plugin.memory.store.data_mut().wasi;
let config = &mut plugin.manifest.as_mut().config;
for (k, v) in json.into_iter() {
+ trace!("Config, adding {k}");
let _ = wasi.push_env(&k, &v);
config.insert(k, v);
}
@@ -107,7 +118,6 @@ pub unsafe extern "C" fn extism_call(
Err(e) => return plugin.error(e.context("Unable to allocate bytes"), -1),
};
- #[cfg(feature = "debug")]
plugin.dump_memory();
// Always needs to be called before `func.call()`
@@ -118,14 +128,12 @@ pub unsafe extern "C" fn extism_call(
match func.call(&mut plugin.memory.store, &[], results.as_mut_slice()) {
Ok(r) => r,
Err(e) => {
- #[cfg(feature = "debug")]
plugin.dump_memory();
error!("Call: {e:?}");
return plugin.error(e.context("Call failed"), -1);
}
};
- #[cfg(feature = "debug")]
plugin.dump_memory();
// Return result to caller
@@ -134,22 +142,31 @@ pub unsafe extern "C" fn extism_call(
#[no_mangle]
pub unsafe extern "C" fn extism_error(plugin: PluginIndex) -> *const c_char {
+ trace!("Call to extism_error for plugin {plugin}");
let plugin = PluginRef::new(plugin);
match &plugin.as_ref().last_error {
Some(e) => e.as_ptr() as *const _,
- None => std::ptr::null(),
+ None => {
+ trace!("Error is NULL");
+ std::ptr::null()
+ }
}
}
#[no_mangle]
pub unsafe extern "C" fn extism_output_length(plugin: PluginIndex) -> Size {
+ trace!("Call to extism_output_length for plugin {plugin}");
let plugin = PluginRef::new(plugin);
- plugin.as_ref().memory.store.data().output_length as Size
+ let len = plugin.as_ref().memory.store.data().output_length as Size;
+ trace!("Output length: {len}");
+ len
}
#[no_mangle]
pub unsafe extern "C" fn extism_output_get(plugin: PluginIndex, buf: *mut u8, len: Size) {
+ trace!("Call to extism_output_get for plugin {plugin}, length {len}");
+
let plugin = PluginRef::new(plugin);
let data = plugin.as_ref().memory.store.data();
@@ -170,16 +187,21 @@ pub unsafe extern "C" fn extism_log_file(
log_level: *const c_char,
) -> bool {
use log::LevelFilter;
+ use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
- use log4rs::config::{Appender, Config, Root};
+ use log4rs::config::{Appender, Config, Logger, Root};
use log4rs::encode::pattern::PatternEncoder;
- let file = std::ffi::CStr::from_ptr(filename);
- let file = match file.to_str() {
- Ok(x) => x,
- Err(_) => {
- return false;
+ let file = if !filename.is_null() {
+ let file = std::ffi::CStr::from_ptr(filename);
+ match file.to_str() {
+ Ok(x) => x,
+ Err(_) => {
+ return false;
+ }
}
+ } else {
+ "-"
};
let level = if log_level.is_null() {
@@ -201,28 +223,34 @@ pub unsafe extern "C" fn extism_log_file(
}
};
- let logfile = match FileAppender::builder()
- .encoder(Box::new(PatternEncoder::new("{d}: {l} - {m}\n")))
- .build(file)
- {
- Ok(x) => x,
- Err(e) => {
- error!("Unable to set up log encoder: {e:?}");
- return false;
+ let encoder = Box::new(PatternEncoder::new("{t} {l} {d} (({f}:{L})) - {m}\n"));
+
+ let logfile: Box = if file == "-" {
+ let console = ConsoleAppender::builder().encoder(encoder);
+ Box::new(console.build())
+ } else {
+ match FileAppender::builder().encoder(encoder).build(file) {
+ Ok(x) => Box::new(x),
+ Err(e) => {
+ error!("Unable to set up log encoder: {e:?}");
+ return false;
+ }
}
};
let config = match Config::builder()
- .appender(Appender::builder().build("logfile", Box::new(logfile)))
- .build(Root::builder().appender("logfile").build(level))
+ .appender(Appender::builder().build("logfile", logfile))
+ .logger(Logger::builder().appender("logfile").build("extism", level))
+ .build(Root::builder().build(LevelFilter::Off))
{
Ok(x) => x,
- Err(e) => {
- error!("Unable to configure log file: {e:?}");
+ Err(_) => {
return false;
}
};
- log4rs::init_config(config).expect("Log initialization failed");
+ if log4rs::init_config(config).is_err() {
+ return false;
+ }
true
}