wasmerio / wasmer-ruby

💎🕸 WebAssembly runtime for Ruby
https://wasmer.io
MIT License
465 stars 18 forks source link

Challenges in Running JS Interpreter via wasmer-ruby #65

Closed eric-hemasystems closed 1 year ago

eric-hemasystems commented 1 year ago

Summary

My end goal is to be able to execute JavaScript code, server-side in a sandbox from Ruby.

I'm thinking that wasi is a good approach as that offers the sandbox. There is also multiple Javascript engines that have been compiled to wasi modules on wapm such as Spidermonkey (Firefox's JS engine), QuickJS, and duktape.

When I try to load any of these wasi modules I get an error it seems related to the FS API.

Spidermonkey

(irb):4:in `new': Error while importing "wasi_snapshot_preview1"."fd_read": unknown import. Expected Function(FunctionType { params: [I32, I32, I32, I32], results: [I32] }) (RuntimeError)
    from (irb):4:in `<main>'
    from /Users/eric/.rbenv/versions/3.0.4/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
    from /Users/eric/.rbenv/versions/3.0.4/bin/irb:23:in `load'
    from /Users/eric/.rbenv/versions/3.0.4/bin/irb:23:in `<main>'

Duktape

(irb):5:in `new': Error while importing "wasi_unstable"."fd_prestat_get": unknown import. Expected Function(FunctionType { params: [I32, I32], results: [I32] }) (RuntimeError)
    from (irb):5:in `<main>'
    from /Users/eric/.rbenv/versions/3.0.4/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
    from /Users/eric/.rbenv/versions/3.0.4/bin/irb:23:in `load'
    from /Users/eric/.rbenv/versions/3.0.4/bin/irb:23:in `<main>'

QuickJS

(irb):13:in `new': Error while importing "wasi_unstable"."fd_prestat_get": unknown import. Expected Function(FunctionType { params: [I32, I32], results: [I32] }) (RuntimeError)
    from (irb):13:in `<main>'
    from /Users/eric/.rbenv/versions/3.0.4/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
    from /Users/eric/.rbenv/versions/3.0.4/bin/irb:23:in `load'
    from /Users/eric/.rbenv/versions/3.0.4/bin/irb:23:in `<main>'

Am I just doing it wrong?

eric-hemasystems commented 1 year ago

Made some progress. There is a example execution of Spidermonkey in JS here. This seems very similar to the Ruby example here.

Also I found wasmer inspect will list the exports and really the only export that isn't mangled is _start and main which leads me further to think that is the right path.

It looks like my problem before was the failure to get the wasi version to create an import object and then use that to create the instance. Once I did that I could load the wasm just fine. Furthermore calling _start seemed to boot the Spidermonkey repl right up!

Now the problem I am having is I don't want an interactive prompt but be able to have it run a JS script. Based on the JS example I have constructed an environment with the following:

wasi_env =
  Wasmer::Wasi::StateBuilder.new('js.wasm')
    .arguments(%w[-f /input.js])
    .map_directory('/', '.')
    .finalize

There is a file in the current directory that just has 1+1. But despite this it always seems to still give me the repl and I don't think it is executing my script. I've also tried just using the wasmer CLI to see if I can load up the spidermonkey wasm file and give it arguments to run a script but there too I always get the repl. Feels like I'm still missing something.

eric-hemasystems commented 1 year ago

I'm almost there. I found there there is some difference between the Spidermonkey wasm file on WAPM vs the one the JS demo uses. It's about twice the size, the prompt is REPL prompt is different and furthermore it actually processes the arguments. If I set my environment up with:

wasi_env =
  Wasmer::Wasi::StateBuilder.new('js.wasm')
    .arguments(%w[-e console.log(1+1)])
    .finalize

It correctly outputs 2!. Obviously I don't want to pass my script as an inline argument so want to move back to the -f option and map a directory in. If my current directory has a script called input.js and I run my previous environment on the new wasm file I get this:

Error: can't open /input.js: Invalid argument
js.rb:66:in `call': RuntimeError: WASI exited with code: 3 (RuntimeError)
    from js.rb:66:in `<main>'

That first line is stderr from the interpreter which tells me it is seeing my argument correctly but obviously it cannot find the file even though I think I'm mapping the directory in correctly.

Any pointers are appreciated.

eric-hemasystems commented 1 year ago

Nevermind. My JS file wasn't called input.js but test.js. Just a dumb mistake on my part. Closing this issue as it's all just me babbling but maybe if someone else in the future wants to run JS from Ruby in a sandbox this might be useful to them.

eric-hemasystems commented 1 year ago

In case anybody else comes across this in the future and wants to avoid the learning curve I had a code spike of my strategy to run JS in a sandbox from Ruby can be seen at:

https://gist.github.com/eric-hemasystems/1694a226af9058c40dd4a96a94da9402