rustwasm / wasm-bindgen

Facilitating high-level interactions between Wasm modules and JavaScript
https://rustwasm.github.io/docs/wasm-bindgen/
Apache License 2.0
7.51k stars 1.03k forks source link

Feature Request: Allow a way for input checks to be performed before handing off to WASM. #2203

Closed junderw closed 4 years ago

junderw commented 4 years ago

Motivation

I am trying to make a WASM API that mimics an existing API. (https://github.com/bitcoinjs/tiny-secp256k1) This API performs checks using Buffer.isBuffer on the inputs of functions and throws if false. I can not mimic this functionality using wasm-bindgen since any attempt to use extern "C" etc. will convert everything to a Uint8Array, which will return false.

Proposed Solution

A solution could be to have something similar to the typescript definitions tag, but instead pass in a JS function that takes the same number of positional parameters as the rust function it is tagging, and must return a boolean. Below this, include a line of Err(Error::whatever)

enum Error {
  BadFoo,
}

#[wasm-bindgen(javascript_param_check, function_name = do_foo)]
const DO_FOO_PARAM_CHECK: &'static str = r#"
function(a, b, c) {
  return Buffer.isBuffer(a) && b < 3 && c === 'waaaario'
}
// Err(Error::BadFoo)
"#;

#[wasm-bindgen(js_name = doFoo)]
pub fn do_foo(a: Box<[u8]>, b: i64, c: &str) -> Result<(), Error> {

}

Alternatives

An alternative was to create a wrapper for the produced pkg folder contents... but I figured that is probably not a good solution.

Additional Context

None.

junderw commented 4 years ago

Here is my initial implementation Pull Request for the library I was making when I ran into this issue.

I implemented is_buffer locally and ran into problems (Because it was pulling the Box<[u8]> from WASM memory and turning it into Uint8Array before checking so it will always fail) so I didn't bother committing it.

https://github.com/junderw/tiny-secp256k1-wasm/pull/2

alexcrichton commented 4 years ago

I think it should be possible to do this by doing the checks within wasm? Can you take &JsValue and perform the checks on the values provided?

junderw commented 4 years ago

I see. Thanks for the tip.

I was able to get it to work by doing something like:

fn maybe_buffer(buf: &JsValue) -> Option<Vec<u8>> {
    if Buffer::is_buffer(buf) {
        let uarr = buf.unchecked_ref::<Uint8Array>();
        Some(uarr.to_vec())
    } else {
        None
    }
}

Adding this at the top of each function for each expected Buffer parameter works, but it decreased performance significantly for some functions.

Some of my functions were just comparing indices of the Box<[u8]> parameter I was originally receiving.

Is there a (less-safe is ok) more performant way to:

  1. Check accept JsValue as a param and check Buffer.isBuffer properly.
  2. At the same time allow me to figure out the length of the Buffer and compare certain indices as u8 numbers without doing whatever unchecked_ref and to_vec do in the background.

Any help is appreciated. The original request seems moot, so I will close this issue.

junderw commented 4 years ago

Thanks to htd#2473 on Discord, I was able to get a lot of parts performing better by only returning a Uint8Array and only using .to_vec() when I absolutely had to.

I am still looking for a way to pass a Uint8Array into a function that takes a &[u8] without using .to_vec()