diff --git a/example/plugins/wasm/main.c b/example/plugins/wasm/main.c new file mode 100644 index 00000000..eeebe745 --- /dev/null +++ b/example/plugins/wasm/main.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include + +// zig cc --target=wasm32-wasi main.c -o c.wasm + +int main(int argc, char **argv) +{ + ssize_t n, m; + char buf[BUFSIZ]; + + int in = STDIN_FILENO; + int out = STDOUT_FILENO; + + while ((n = read(in, buf, BUFSIZ)) > 0) + { + char *ptr = buf; + while (n > 0) + { + m = write(out, ptr, (size_t)n); + if (m < 0) + { + fprintf(stderr, "write error: %s\n", strerror(errno)); + exit(1); + } + n -= m; + ptr += m; + } + } + + if (n < 0) + { + fprintf(stderr, "read error: %s\n", strerror(errno)); + exit(1); + } + + return EXIT_SUCCESS; +} diff --git a/example/plugins/wasm/main.go b/example/plugins/wasm/main.go new file mode 100644 index 00000000..518a6e65 --- /dev/null +++ b/example/plugins/wasm/main.go @@ -0,0 +1,27 @@ +//go:build ignore + +// compile with: +// GOOS=wasip1 GOARCH=wasm go build -o go.wasm go.go +// or +// tinygo build -o tgo.wasm -target=wasi -scheduler=none -no-debug main.go + +package main + +import ( + "bufio" + "os" +) + +func main() { + reader := bufio.NewReader(os.Stdin) + writer := bufio.NewWriter(os.Stdout) + + for { + input, err := reader.ReadString('\n') + writer.WriteString(input) + if err != nil { + break + } + } + writer.Flush() +} diff --git a/example/plugins/wasm/main.rs b/example/plugins/wasm/main.rs new file mode 100644 index 00000000..d6e0ec03 --- /dev/null +++ b/example/plugins/wasm/main.rs @@ -0,0 +1,10 @@ +//rustc main.rs --target wasm32-wasip1 -o rust.wasm + +use std::io::{self, Read, Write}; + +fn main() { + let mut buffer = Vec::new(); + let _ = io::stdin().read_to_end(&mut buffer); + let _ = io::stdout().write_all(&buffer); + () +} diff --git a/example/plugins/wasm/main.zig b/example/plugins/wasm/main.zig new file mode 100644 index 00000000..3c151beb --- /dev/null +++ b/example/plugins/wasm/main.zig @@ -0,0 +1,17 @@ +// zig cc --target=wasm32-wasi main.zig -o zig.wasm +const std = @import("std"); + +pub fn main() !void { + var stdin = std.io.getStdIn().reader(); + var stdout = std.io.getStdOut().writer(); + + const bufferSize: usize = 4096; + var buffer: [bufferSize]u8 = undefined; + + while (true) { + const bytesRead = try stdin.read(buffer[0..]); + if (bytesRead == 0) break; + + _ = try stdout.write(buffer[0..bytesRead]); + } +} diff --git a/example/plugins/deepl.go b/example/plugins/yaegi/deepl.go similarity index 100% rename from example/plugins/deepl.go rename to example/plugins/yaegi/deepl.go diff --git a/example/plugins/sha512.go b/example/plugins/yaegi/sha512.go similarity index 100% rename from example/plugins/sha512.go rename to example/plugins/yaegi/sha512.go diff --git a/example/plugins/tee.go b/example/plugins/yaegi/tee.go similarity index 100% rename from example/plugins/tee.go rename to example/plugins/yaegi/tee.go diff --git a/example/plugins/yc.go b/example/plugins/yaegi/yc.go similarity index 100% rename from example/plugins/yc.go rename to example/plugins/yaegi/yc.go diff --git a/go.mod b/go.mod index 3dcb3c34..ba9d7eff 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/robert-nix/ansihtml v1.0.1 github.com/sashabaranov/go-openai v1.29.2 github.com/spf13/pflag v1.0.5 + github.com/tetratelabs/wazero v1.8.0 github.com/titanous/json5 v1.0.0 github.com/tmc/keyring v0.0.0-20230418032330-0c8bdba76fa8 github.com/traefik/yaegi v0.16.1 diff --git a/go.sum b/go.sum index 3db7be26..deca34c9 100644 --- a/go.sum +++ b/go.sum @@ -298,6 +298,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= +github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/titanous/json5 v1.0.0 h1:hJf8Su1d9NuI/ffpxgxQfxh/UiBFZX7bMPid0rIL/7s= github.com/titanous/json5 v1.0.0/go.mod h1:7JH1M8/LHKc6cyP5o5g3CSaRj+mBrIimTxzpvmckH8c= github.com/tmc/keyring v0.0.0-20230418032330-0c8bdba76fa8 h1:DljNLO6GK8JXxL2VTe3dSGhraDappqRW7yrGvsW4U7w= diff --git a/pkg/mutators/single/wazero.go b/pkg/mutators/single/wazero.go new file mode 100644 index 00000000..4e1734b1 --- /dev/null +++ b/pkg/mutators/single/wazero.go @@ -0,0 +1,62 @@ +package mutators + +import ( + "context" + "errors" + "fmt" + "io" + "os" + + "github.com/batmac/ccat/pkg/log" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "github.com/tetratelabs/wazero/sys" + // "github.com/tetratelabs/wazero/api" +) + +// Yaegi is a mutator that executes a yaegi script + +func init() { + singleRegister("wasm", wasm, withDescription("a wasi (wasm) module to apply (path as first argument"), + withConfigBuilder(stdConfigString), + withCategory("plugin"), + ) +} + +func wasm(w io.WriteCloser, r io.ReadCloser, arg any) (int64, error) { + ctx := context.Background() + moduleFile := arg.(string) + + log.Debugln("Create a new WebAssembly runtime") + runtime := wazero.NewRuntime(ctx) + defer runtime.Close(ctx) + + log.Debugln("Instantiate WASI in the runtime") + wasi_snapshot_preview1.MustInstantiate(ctx, runtime) + + log.Debugln("Load your WASM module") + wasmBytes, err := os.ReadFile(moduleFile) + if err != nil { + panic(fmt.Sprintf("Failed to read WASM module: %v", err)) + } + + log.Debugln("Define the configuration with the in-memory file system") + config := wazero.NewModuleConfig(). + WithStdin(r). + WithStdout(w). + WithStderr(os.Stderr) // Set stderr to the standard error of the host process + + log.Debugln("Instantiate the WASM module with the configured input/output") + if _, err := runtime.InstantiateWithConfig(ctx, wasmBytes, config); err != nil { + var exitErr *sys.ExitError + if errors.As(err, &exitErr) && exitErr.ExitCode() != 0 { + log.Debugf("exit_code: %d\n", exitErr.ExitCode()) + } else { + log.Fatal(err) + } + } + w.Close() + + return 0, nil +}