From 62808e8d99b64b35ce892f533332ca290fe5185b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 18 Dec 2017 14:44:09 -0800 Subject: [PATCH] Add some tests for exceptions --- crates/wasm-bindgen-cli-support/src/js.rs | 30 +++++----- crates/wasm-bindgen-macro/Cargo.toml | 2 +- crates/wasm-bindgen-macro/src/lib.rs | 68 ++++++++++++----------- tests/classes.rs | 35 ++++++++++++ tests/simple.rs | 26 +++++++++ 5 files changed, 115 insertions(+), 46 deletions(-) diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index a7e06224bc9..6481fb14b57 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -6,6 +6,7 @@ pub struct Js { pub expose_global_exports: bool, pub expose_get_string_from_wasm: bool, pub expose_pass_string_to_wasm: bool, + pub expose_assert_num: bool, pub expose_token: bool, pub exports: Vec<(String, String)>, pub classes: Vec, @@ -24,17 +25,6 @@ impl Js { } pub fn generate_free_function(&mut self, func: &shared::Function) { - let simple = func.arguments.iter().all(|t| t.is_number()) && - func.ret.as_ref().map(|t| t.is_number()).unwrap_or(true); - - if simple { - self.exports.push(( - func.name.clone(), - format!("obj.instance.exports.{}", func.name), - )); - return - } - let ret = self.generate_function(&format!("function {}", func.name), &func.name, false, @@ -118,7 +108,11 @@ impl Js { passed_args.push_str(arg); }; match *arg { - shared::Type::Number => pass(&name), + shared::Type::Number => { + self.expose_assert_num = true; + arg_conversions.push_str(&format!("_assertNum({});\n", name)); + pass(&name) + } shared::Type::BorrowedStr | shared::Type::String => { self.expose_global_exports = true; @@ -207,15 +201,25 @@ impl Js { globals.push_str("\ const token = Symbol('foo'); function _checkToken(sym) { - if (token != sym) + if (token !== sym) throw new Error('cannot invoke `new` directly'); } "); } + if self.expose_assert_num { + globals.push_str("\ + function _assertNum(n) { + if (typeof(n) !== 'number') + throw new Error('expected a number argument'); + } + "); + } if self.expose_pass_string_to_wasm { if self.nodejs { globals.push_str(" function passStringToWasm(arg) { + if (typeof(n) !== 'string') + throw new Error('expected a string argument'); const buf = Buffer.from(arg); const len = buf.length; const ptr = exports.__wbindgen_malloc(len); diff --git a/crates/wasm-bindgen-macro/Cargo.toml b/crates/wasm-bindgen-macro/Cargo.toml index 20ecb718b2a..df4fa503480 100644 --- a/crates/wasm-bindgen-macro/Cargo.toml +++ b/crates/wasm-bindgen-macro/Cargo.toml @@ -10,6 +10,6 @@ proc-macro = true syn = { git = 'https://github.com/dtolnay/syn', features = ['full'] } synom = { git = 'https://github.com/dtolnay/syn' } quote = { git = 'https://github.com/dtolnay/quote' } -proc-macro2 = "0.1" +proc-macro2 = { version = "0.1", features = ["unstable"] } serde_json = "1" wasm-bindgen-shared = { path = "../wasm-bindgen-shared" } diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index 581841c9e1b..237379076e1 100644 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -12,7 +12,7 @@ extern crate wasm_bindgen_shared; use std::sync::atomic::*; use proc_macro::TokenStream; -use proc_macro2::Literal; +use proc_macro2::{Literal, Span}; use quote::{Tokens, ToTokens}; mod ast; @@ -20,6 +20,10 @@ mod ast; static MALLOC_GENERATED: AtomicBool = ATOMIC_BOOL_INIT; static BOXED_STR_GENERATED: AtomicBool = ATOMIC_BOOL_INIT; +macro_rules! my_quote { + ($($t:tt)*) => (quote_spanned!(Span::call_site(), $($t)*)) +} + #[proc_macro] pub fn wasm_bindgen(input: TokenStream) -> TokenStream { let file = syn::parse::(input) @@ -69,7 +73,7 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream { }; let generated_static_length = generated_static.len(); - (quote! { + (my_quote! { #[no_mangle] #[allow(non_upper_case_globals)] pub static #generated_static_name: [u8; #generated_static_length] = @@ -100,7 +104,7 @@ fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) { let name = &s.name; let free_fn = s.free_function(); - (quote! { + (my_quote! { #[no_mangle] pub unsafe extern fn #free_fn(ptr: *mut ::std::cell::RefCell<#name>) { assert!(!ptr.is_null()); @@ -149,8 +153,8 @@ fn bindgen(export_name: &syn::Lit, let mut offset = 0; if let Receiver::StructMethod(class, _, _) = receiver { - args.push(quote! { me: *mut ::std::cell::RefCell<#class> }); - arg_conversions.push(quote! { + args.push(my_quote! { me: *mut ::std::cell::RefCell<#class> }); + arg_conversions.push(my_quote! { assert!(!me.is_null()); let me = unsafe { &*me }; }); @@ -162,15 +166,15 @@ fn bindgen(export_name: &syn::Lit, let ident = syn::Ident::from(format!("arg{}", i)); match *ty { ast::Type::Integer(i) => { - args.push(quote! { #ident: #i }); + args.push(my_quote! { #ident: #i }); } ast::Type::BorrowedStr => { malloc = !MALLOC_GENERATED.swap(true, Ordering::SeqCst); let ptr = syn::Ident::from(format!("arg{}_ptr", i)); let len = syn::Ident::from(format!("arg{}_len", i)); - args.push(quote! { #ptr: *const u8 }); - args.push(quote! { #len: usize }); - arg_conversions.push(quote! { + args.push(my_quote! { #ptr: *const u8 }); + args.push(my_quote! { #len: usize }); + arg_conversions.push(my_quote! { let #ident = unsafe { let slice = ::std::slice::from_raw_parts(#ptr, #len); ::std::str::from_utf8_unchecked(slice) @@ -181,9 +185,9 @@ fn bindgen(export_name: &syn::Lit, malloc = !MALLOC_GENERATED.swap(true, Ordering::SeqCst); let ptr = syn::Ident::from(format!("arg{}_ptr", i)); let len = syn::Ident::from(format!("arg{}_len", i)); - args.push(quote! { #ptr: *mut u8 }); - args.push(quote! { #len: usize }); - arg_conversions.push(quote! { + args.push(my_quote! { #ptr: *mut u8 }); + args.push(my_quote! { #len: usize }); + arg_conversions.push(my_quote! { let #ident = unsafe { let vec = ::std::vec::Vec::from_raw_parts(#ptr, #len, #len); ::std::string::String::from_utf8_unchecked(vec) @@ -191,8 +195,8 @@ fn bindgen(export_name: &syn::Lit, }); } ast::Type::ByValue(name) => { - args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> }); - arg_conversions.push(quote! { + args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> }); + arg_conversions.push(my_quote! { assert!(!#ident.is_null()); let #ident = unsafe { (*#ident).borrow_mut(); @@ -201,53 +205,53 @@ fn bindgen(export_name: &syn::Lit, }); } ast::Type::ByRef(name) => { - args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> }); - arg_conversions.push(quote! { + args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> }); + arg_conversions.push(my_quote! { assert!(!#ident.is_null()); let #ident = unsafe { (*#ident).borrow() }; let #ident = &*#ident; }); } ast::Type::ByMutRef(name) => { - args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> }); - arg_conversions.push(quote! { + args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> }); + arg_conversions.push(my_quote! { assert!(!#ident.is_null()); let mut #ident = unsafe { (*#ident).borrow_mut() }; let #ident = &mut *#ident; }); } } - converted_arguments.push(quote! { #ident }); + converted_arguments.push(my_quote! { #ident }); } let ret_ty; let convert_ret; match ret_type { Some(&ast::Type::Integer(i)) => { - ret_ty = quote! { -> #i }; - convert_ret = quote! { #ret }; + ret_ty = my_quote! { -> #i }; + convert_ret = my_quote! { #ret }; } Some(&ast::Type::BorrowedStr) => panic!("can't return a borrowed string"), Some(&ast::Type::ByRef(_)) => panic!("can't return a borrowed ref"), Some(&ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"), Some(&ast::Type::String) => { boxed_str = !BOXED_STR_GENERATED.swap(true, Ordering::SeqCst); - ret_ty = quote! { -> *mut String }; - convert_ret = quote! { Box::into_raw(Box::new(#ret)) }; + ret_ty = my_quote! { -> *mut String }; + convert_ret = my_quote! { Box::into_raw(Box::new(#ret)) }; } Some(&ast::Type::ByValue(name)) => { - ret_ty = quote! { -> *mut ::std::cell::RefCell<#name> }; - convert_ret = quote! { + ret_ty = my_quote! { -> *mut ::std::cell::RefCell<#name> }; + convert_ret = my_quote! { Box::into_raw(Box::new(::std::cell::RefCell::new(#ret))) }; } None => { - ret_ty = quote! {}; - convert_ret = quote! {}; + ret_ty = my_quote! {}; + convert_ret = my_quote! {}; } } let malloc = if malloc { - quote! { + my_quote! { #[no_mangle] pub extern fn __wbindgen_malloc(size: usize) -> *mut u8 { let mut ret = Vec::with_capacity(size); @@ -262,12 +266,12 @@ fn bindgen(export_name: &syn::Lit, } } } else { - quote! { + my_quote! { } }; let boxed_str = if boxed_str { - quote! { + my_quote! { #[no_mangle] pub unsafe extern fn __wbindgen_boxed_str_len(ptr: *mut String) -> usize { (*ptr).len() @@ -284,11 +288,11 @@ fn bindgen(export_name: &syn::Lit, } } } else { - quote! { + my_quote! { } }; - let tokens = quote! { + let tokens = my_quote! { #malloc #boxed_str diff --git a/tests/classes.rs b/tests/classes.rs index 0f685967d70..ead8cbc633b 100644 --- a/tests/classes.rs +++ b/tests/classes.rs @@ -105,3 +105,38 @@ fn strings() { "#) .test(); } + +#[test] +fn exceptions() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + wasm_bindgen! { + pub struct A { + } + + impl A { + pub fn new() -> A { + A {} + } + } + } + "#) + .file("test.js", r#" + import * as assert from "assert"; + + export function test(wasm) { + assert.throws(() => new wasm.A(), /cannot invoke `new` directly/); + let a = wasm.A.new(); + a.free(); + // TODO: figure out a better error message? + assert.throws(() => a.free(), /RuntimeError: unreachable/); + } + "#) + .test(); +} diff --git a/tests/simple.rs b/tests/simple.rs index 6d873a0138b..1a8e4d258ed 100644 --- a/tests/simple.rs +++ b/tests/simple.rs @@ -101,3 +101,29 @@ fn return_a_string() { "#) .test(); } + +#[test] +fn exceptions() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + wasm_bindgen! { + pub fn foo(_a: u32) {} + pub fn bar(_a: &str) {} + } + "#) + .file("test.js", r#" + import * as assert from "assert"; + + export function test(wasm) { + assert.throws(() => wasm.foo('a'), /expected a number argument/); + assert.throws(() => wasm.bar(3), /expected a string argument/); + } + "#) + .test(); +}