diff --git a/crates/wasm-bindgen-cli-support/src/ts.rs b/crates/wasm-bindgen-cli-support/src/ts.rs index 385b57d2..87ced752 100644 --- a/crates/wasm-bindgen-cli-support/src/ts.rs +++ b/crates/wasm-bindgen-cli-support/src/ts.rs @@ -389,6 +389,13 @@ impl Js { imports.push_str("__wasm_bindgen_object_drop_ref: dropRef,\n"); } + if self.wasm_import_needed("__wbindgen_throw", m) { + self.expose_get_string_from_wasm(); + imports.push_str("__wbindgen_throw: function(ptr: number, len: number) { + throw new Error(getStringFromWasm(ptr, len)); + },\n"); + } + let mut writes = String::new(); if self.exposed_globals.contains(&"memory") { writes.push_str("memory = exports.memory;\n"); diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index ac8e8088..a4ea61b2 100644 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -127,8 +127,8 @@ fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) { let free_fn = s.free_function(); (my_quote! { #[no_mangle] - pub unsafe extern fn #free_fn(ptr: *mut ::std::cell::RefCell<#name>) { - assert!(!ptr.is_null()); + pub unsafe extern fn #free_fn(ptr: *mut ::wasm_bindgen::__rt::WasmRefCell<#name>) { + ::wasm_bindgen::__rt::assert_not_null(ptr); drop(Box::from_raw(ptr)); } }).to_tokens(into); @@ -174,9 +174,9 @@ fn bindgen(export_name: &syn::Lit, let mut offset = 0; if let Receiver::StructMethod(class, _, _) = receiver { - args.push(my_quote! { me: *mut ::std::cell::RefCell<#class> }); + args.push(my_quote! { me: *mut ::wasm_bindgen::__rt::WasmRefCell<#class> }); arg_conversions.push(my_quote! { - assert!(!me.is_null()); + ::wasm_bindgen::__rt::assert_not_null(me); let me = unsafe { &*me }; }); offset = 1; @@ -216,9 +216,9 @@ fn bindgen(export_name: &syn::Lit, }); } ast::Type::ByValue(name) => { - args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> }); + args.push(my_quote! { #ident: *mut ::wasm_bindgen::__rt::WasmRefCell<#name> }); arg_conversions.push(my_quote! { - assert!(!#ident.is_null()); + ::wasm_bindgen::__rt::assert_not_null(#ident); let #ident = unsafe { (*#ident).borrow_mut(); Box::from_raw(#ident).into_inner() @@ -226,17 +226,17 @@ fn bindgen(export_name: &syn::Lit, }); } ast::Type::ByRef(name) => { - args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> }); + args.push(my_quote! { #ident: *mut ::wasm_bindgen::__rt::WasmRefCell<#name> }); arg_conversions.push(my_quote! { - assert!(!#ident.is_null()); + ::wasm_bindgen::__rt::assert_not_null(#ident); let #ident = unsafe { (*#ident).borrow() }; let #ident = &*#ident; }); } ast::Type::ByMutRef(name) => { - args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> }); + args.push(my_quote! { #ident: *mut ::wasm_bindgen::__rt::WasmRefCell<#name> }); arg_conversions.push(my_quote! { - assert!(!#ident.is_null()); + ::wasm_bindgen::__rt::assert_not_null(#ident); let mut #ident = unsafe { (*#ident).borrow_mut() }; let #ident = &mut *#ident; }); @@ -275,9 +275,9 @@ fn bindgen(export_name: &syn::Lit, convert_ret = my_quote! { Box::into_raw(Box::new(#ret)) }; } Some(&ast::Type::ByValue(name)) => { - ret_ty = my_quote! { -> *mut ::std::cell::RefCell<#name> }; + ret_ty = my_quote! { -> *mut ::wasm_bindgen::__rt::WasmRefCell<#name> }; convert_ret = my_quote! { - Box::into_raw(Box::new(::std::cell::RefCell::new(#ret))) + Box::into_raw(Box::new(::wasm_bindgen::__rt::WasmRefCell::new(#ret))) }; } Some(&ast::Type::JsObject) => { @@ -299,6 +299,17 @@ fn bindgen(export_name: &syn::Lit, my_quote! { #[no_mangle] pub extern fn __wbindgen_malloc(size: usize) -> *mut u8 { + // Any malloc request this big is bogus anyway. If this actually + // goes down to `Vec` we trigger a whole bunch of panicking + // machinery to get pulled in from libstd anyway as it'll verify + // the size passed in below. + // + // Head this all off by just aborting on too-big sizes. This + // avoids panicking (code bloat) and gives a better error + // message too hopefully. + if size >= usize::max_value() / 2 { + ::wasm_bindgen::throw("invalid malloc request"); + } let mut ret = Vec::with_capacity(size); let ptr = ret.as_mut_ptr(); ::std::mem::forget(ret); diff --git a/src/lib.rs b/src/lib.rs index 8f73f2b4..ee1f92ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,3 +53,141 @@ impl Drop for JsObject { } } } + +#[cold] +pub fn throw(s: &str) -> ! { + extern { + fn __wbindgen_throw(a: *const u8, b: usize) -> !; + } + unsafe { + __wbindgen_throw(s.as_ptr(), s.len()); + } +} + +#[doc(hidden)] +pub mod __rt { + use std::cell::{Cell, UnsafeCell}; + use std::ops::{Deref, DerefMut}; + + #[inline] + pub fn assert_not_null(s: *mut T) { + if s.is_null() { + super::throw("null pointer passed to rust"); + } + } + + /// A vendored version of `RefCell` from the standard library. + /// + /// Now why, you may ask, would we do that? Surely `RefCell` in libstd is + /// quite good. And you're right, it is indeed quite good! Functionally + /// nothing more is needed from `RefCell` in the standard library but for + /// now this crate is also sort of optimizing for compiled code size. + /// + /// One major factor to larger binaries in Rust is when a panic happens. + /// Panicking in the standard library involves a fair bit of machinery + /// (formatting, panic hooks, synchronization, etc). It's all worthwhile if + /// you need it but for something like `WasmRefCell` here we don't actually + /// need all that! + /// + /// This is just a wrapper around all Rust objects passed to JS intended to + /// guard accidental reentrancy, so this vendored version is intended solely + /// to not panic in libstd. Instead when it "panics" it calls our `throw` + /// function in this crate which raises an error in JS. + pub struct WasmRefCell { + borrow: Cell, + value: UnsafeCell, + } + + impl WasmRefCell { + pub fn new(value: T) -> WasmRefCell { + WasmRefCell { + value: UnsafeCell::new(value), + borrow: Cell::new(0), + } + } + + pub fn borrow(&self) -> Ref { + unsafe { + if self.borrow.get() == usize::max_value() { + borrow_fail(); + } + self.borrow.set(self.borrow.get() + 1); + Ref { + value: &*self.value.get(), + borrow: &self.borrow, + } + } + } + + pub fn borrow_mut(&self) -> RefMut { + unsafe { + if self.borrow.get() != 0 { + borrow_fail(); + } + self.borrow.set(usize::max_value()); + RefMut { + value: &mut *self.value.get(), + borrow: &self.borrow, + } + } + } + + pub fn into_inner(self) -> T { + unsafe { + self.value.into_inner() + } + } + } + + pub struct Ref<'b, T: 'b> { + value: &'b T, + borrow: &'b Cell, + } + + impl<'b, T> Deref for Ref<'b, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + self.value + } + } + + impl<'b, T> Drop for Ref<'b, T> { + fn drop(&mut self) { + self.borrow.set(self.borrow.get() - 1); + } + } + + pub struct RefMut<'b, T: 'b> { + value: &'b mut T, + borrow: &'b Cell, + } + + impl<'b, T> Deref for RefMut<'b, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + self.value + } + } + + impl<'b, T> DerefMut for RefMut<'b, T> { + #[inline] + fn deref_mut(&mut self) -> &mut T { + self.value + } + } + + impl<'b, T> Drop for RefMut<'b, T> { + fn drop(&mut self) { + self.borrow.set(0); + } + } + + fn borrow_fail() -> ! { + super::throw("recursive use of an object detected which would lead to \ + unsafe aliasing in rust"); + } +} diff --git a/tests/classes.rs b/tests/classes.rs index 203ef308..0125c0cc 100644 --- a/tests/classes.rs +++ b/tests/classes.rs @@ -156,12 +156,11 @@ fn exceptions() { 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/); + assert.throws(() => a.free(), /null pointer passed to rust/); let b = wasm.A.new(); b.foo(b); - assert.throws(() => b.bar(b), /RuntimeError: unreachable/); + assert.throws(() => b.bar(b), /recursive use of an object/); let c = wasm.A.new(); let d = wasm.B.new();