use js_sys::Number; use std::cell::{Cell, RefCell}; use std::rc::Rc; use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; #[wasm_bindgen(module = "tests/wasm/closures.js")] extern "C" { fn works_call(a: &dyn Fn()); fn works_thread(a: &dyn Fn(u32) -> u32) -> u32; fn cannot_reuse_call(a: &dyn Fn()); #[wasm_bindgen(catch)] fn cannot_reuse_call_again() -> Result<(), JsValue>; fn long_lived_call1(a: &Closure); fn long_lived_call2(a: &Closure u32>) -> u32; fn many_arity_call1(a: &Closure); fn many_arity_call2(a: &Closure); fn many_arity_call3(a: &Closure); fn many_arity_call4(a: &Closure); fn many_arity_call5(a: &Closure); fn many_arity_call6(a: &Closure); fn many_arity_call7(a: &Closure); fn many_arity_call8(a: &Closure); fn many_arity_call9(a: &Closure); #[wasm_bindgen(js_name = many_arity_call1)] fn many_arity_call_mut1(a: &Closure); #[wasm_bindgen(js_name = many_arity_call2)] fn many_arity_call_mut2(a: &Closure); #[wasm_bindgen(js_name = many_arity_call3)] fn many_arity_call_mut3(a: &Closure); #[wasm_bindgen(js_name = many_arity_call4)] fn many_arity_call_mut4(a: &Closure); #[wasm_bindgen(js_name = many_arity_call5)] fn many_arity_call_mut5(a: &Closure); #[wasm_bindgen(js_name = many_arity_call6)] fn many_arity_call_mut6(a: &Closure); #[wasm_bindgen(js_name = many_arity_call7)] fn many_arity_call_mut7(a: &Closure); #[wasm_bindgen(js_name = many_arity_call8)] fn many_arity_call_mut8(a: &Closure); #[wasm_bindgen(js_name = many_arity_call9)] fn many_arity_call_mut9(a: &Closure); fn option_call1(a: Option<&Closure>); fn option_call2(a: Option<&Closure u32>>) -> u32; fn option_call3(a: Option<&Closure>) -> bool; #[wasm_bindgen(js_name = many_arity_call1)] fn many_arity_stack1(a: &dyn Fn()); #[wasm_bindgen(js_name = many_arity_call2)] fn many_arity_stack2(a: &dyn Fn(u32)); #[wasm_bindgen(js_name = many_arity_call3)] fn many_arity_stack3(a: &dyn Fn(u32, u32)); #[wasm_bindgen(js_name = many_arity_call4)] fn many_arity_stack4(a: &dyn Fn(u32, u32, u32)); #[wasm_bindgen(js_name = many_arity_call5)] fn many_arity_stack5(a: &dyn Fn(u32, u32, u32, u32)); #[wasm_bindgen(js_name = many_arity_call6)] fn many_arity_stack6(a: &dyn Fn(u32, u32, u32, u32, u32)); #[wasm_bindgen(js_name = many_arity_call7)] fn many_arity_stack7(a: &dyn Fn(u32, u32, u32, u32, u32, u32)); #[wasm_bindgen(js_name = many_arity_call8)] fn many_arity_stack8(a: &dyn Fn(u32, u32, u32, u32, u32, u32, u32)); #[wasm_bindgen(js_name = many_arity_call9)] fn many_arity_stack9(a: &dyn Fn(u32, u32, u32, u32, u32, u32, u32, u32)); fn long_lived_dropping_cache(a: &Closure); #[wasm_bindgen(catch)] fn long_lived_dropping_call() -> Result<(), JsValue>; fn long_lived_option_dropping_cache(a: Option<&Closure>) -> bool; #[wasm_bindgen(catch)] fn long_lived_option_dropping_call() -> Result<(), JsValue>; fn long_fnmut_recursive_cache(a: &Closure); #[wasm_bindgen(catch)] fn long_fnmut_recursive_call() -> Result<(), JsValue>; fn fnmut_call(a: &mut dyn FnMut()); fn fnmut_thread(a: &mut dyn FnMut(u32) -> u32) -> u32; fn fnmut_bad_call(a: &mut dyn FnMut()); #[wasm_bindgen(catch)] fn fnmut_bad_again(a: bool) -> Result<(), JsValue>; fn string_arguments_call(a: &mut dyn FnMut(String)); fn string_ret_call(a: &mut dyn FnMut(String) -> String); fn drop_during_call_save(a: &Closure); fn drop_during_call_call(); fn js_test_closure_returner(); fn calling_it_throws(a: &Closure) -> bool; fn call_val(f: &JsValue); #[wasm_bindgen(js_name = calling_it_throws)] fn call_val_throws(f: &JsValue) -> bool; fn pass_reference_first_arg_twice( a: RefFirstArgument, b: &Closure, c: &Closure, ); #[wasm_bindgen(js_name = pass_reference_first_arg_twice)] fn pass_reference_first_arg_twice2( a: RefFirstArgument, b: &mut dyn FnMut(&RefFirstArgument), c: &mut dyn FnMut(&RefFirstArgument), ); fn call_destroyed(a: &JsValue); fn js_store_forgotten_closure(closure: &Closure); fn js_call_forgotten_closure(); #[wasm_bindgen(js_name = many_arity_call2)] fn externref_call(a: &Closure); #[wasm_bindgen(js_name = many_arity_call2)] fn named_externref_call(a: &Closure); } #[wasm_bindgen_test] fn works() { let a = Cell::new(false); works_call(&|| a.set(true)); assert!(a.get()); assert_eq!(works_thread(&|a| a + 1), 3); } #[wasm_bindgen_test] fn cannot_reuse() { cannot_reuse_call(&|| {}); assert!(cannot_reuse_call_again().is_err()); } #[wasm_bindgen_test] fn debug() { let closure = Closure::wrap(Box::new(|| {}) as Box); assert_eq!(&format!("{:?}", closure), "Closure { ... }"); } #[wasm_bindgen_test] fn long_lived() { let hit = Rc::new(Cell::new(false)); let hit2 = hit.clone(); let a = Closure::new(move || hit2.set(true)); assert!(!hit.get()); long_lived_call1(&a); assert!(hit.get()); let hit = Rc::new(Cell::new(false)); { let hit = hit.clone(); let a = Closure::new(move |x| { hit.set(true); x + 3 }); assert_eq!(long_lived_call2(&a), 5); } assert!(hit.get()); } #[wasm_bindgen_test] fn many_arity() { many_arity_call1(&Closure::new(|| {})); many_arity_call2(&Closure::new(|a| assert_eq!(a, 1))); many_arity_call3(&Closure::new(|a, b| assert_eq!((a, b), (1, 2)))); many_arity_call4(&Closure::new(|a, b, c| assert_eq!((a, b, c), (1, 2, 3)))); many_arity_call5(&Closure::new(|a, b, c, d| { assert_eq!((a, b, c, d), (1, 2, 3, 4)) })); many_arity_call6(&Closure::new(|a, b, c, d, e| { assert_eq!((a, b, c, d, e), (1, 2, 3, 4, 5)) })); many_arity_call7(&Closure::new(|a, b, c, d, e, f| { assert_eq!((a, b, c, d, e, f), (1, 2, 3, 4, 5, 6)) })); many_arity_call8(&Closure::new(|a, b, c, d, e, f, g| { assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7)) })); many_arity_call9(&Closure::new(|a, b, c, d, e, f, g, h| { assert_eq!((a, b, c, d, e, f, g, h), (1, 2, 3, 4, 5, 6, 7, 8)) })); let s = String::new(); many_arity_call_mut1(&Closure::once(move || drop(s))); let s = String::new(); many_arity_call_mut2(&Closure::once(move |a| { drop(s); assert_eq!(a, 1); })); let s = String::new(); many_arity_call_mut3(&Closure::once(move |a, b| { drop(s); assert_eq!((a, b), (1, 2)); })); let s = String::new(); many_arity_call_mut4(&Closure::once(move |a, b, c| { drop(s); assert_eq!((a, b, c), (1, 2, 3)); })); let s = String::new(); many_arity_call_mut5(&Closure::once(move |a, b, c, d| { drop(s); assert_eq!((a, b, c, d), (1, 2, 3, 4)); })); let s = String::new(); many_arity_call_mut6(&Closure::once(move |a, b, c, d, e| { drop(s); assert_eq!((a, b, c, d, e), (1, 2, 3, 4, 5)); })); let s = String::new(); many_arity_call_mut7(&Closure::once(move |a, b, c, d, e, f| { drop(s); assert_eq!((a, b, c, d, e, f), (1, 2, 3, 4, 5, 6)); })); let s = String::new(); many_arity_call_mut8(&Closure::once(move |a, b, c, d, e, f, g| { drop(s); assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7)); })); let s = String::new(); many_arity_call_mut9(&Closure::once(move |a, b, c, d, e, f, g, h| { drop(s); assert_eq!((a, b, c, d, e, f, g, h), (1, 2, 3, 4, 5, 6, 7, 8)); })); many_arity_stack1(&(|| {})); many_arity_stack2(&(|a| assert_eq!(a, 1))); many_arity_stack3(&(|a, b| assert_eq!((a, b), (1, 2)))); many_arity_stack4(&(|a, b, c| assert_eq!((a, b, c), (1, 2, 3)))); many_arity_stack5(&(|a, b, c, d| assert_eq!((a, b, c, d), (1, 2, 3, 4)))); many_arity_stack6(&(|a, b, c, d, e| assert_eq!((a, b, c, d, e), (1, 2, 3, 4, 5)))); many_arity_stack7(&(|a, b, c, d, e, f| assert_eq!((a, b, c, d, e, f), (1, 2, 3, 4, 5, 6)))); many_arity_stack8( &(|a, b, c, d, e, f, g| assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7))), ); many_arity_stack9( &(|a, b, c, d, e, f, g, h| assert_eq!((a, b, c, d, e, f, g, h), (1, 2, 3, 4, 5, 6, 7, 8))), ); } #[wasm_bindgen_test] fn option() { let hit = Rc::new(Cell::new(false)); let hit2 = hit.clone(); let a = Closure::new(move || hit2.set(true)); assert!(!hit.get()); option_call1(Some(&a)); assert!(hit.get()); let hit = Rc::new(Cell::new(false)); { let hit = hit.clone(); let a = Closure::new(move |x| { hit.set(true); x + 3 }); assert_eq!(option_call2(Some(&a)), 5); } assert!(hit.get()); assert!(option_call3(None)); } struct Dropper(Rc>); impl Drop for Dropper { fn drop(&mut self) { assert!(!self.0.get()); self.0.set(true); } } #[wasm_bindgen_test] fn call_fn_once_twice() { let dropped = Rc::new(Cell::new(false)); let dropper = Dropper(dropped.clone()); let called = Rc::new(Cell::new(false)); let c = Closure::once({ let called = called.clone(); move || { assert!(!called.get()); called.set(true); drop(dropper); } }); many_arity_call_mut1(&c); assert!(called.get()); assert!(dropped.get()); assert!(calling_it_throws(&c)); } #[wasm_bindgen_test] fn once_into_js() { let dropped = Rc::new(Cell::new(false)); let dropper = Dropper(dropped.clone()); let called = Rc::new(Cell::new(false)); let f = Closure::once_into_js({ let called = called.clone(); move || { assert!(!called.get()); called.set(true); drop(dropper); } }); call_val(&f); assert!(called.get()); assert!(dropped.get()); assert!(call_val_throws(&f)); } #[wasm_bindgen_test] fn long_lived_dropping() { let hit = Rc::new(Cell::new(false)); let hit2 = hit.clone(); let a = Closure::new(move || hit2.set(true)); long_lived_dropping_cache(&a); assert!(!hit.get()); assert!(long_lived_dropping_call().is_ok()); assert!(hit.get()); drop(a); assert!(long_lived_dropping_call().is_err()); } #[wasm_bindgen_test] fn long_lived_option_dropping() { let hit = Rc::new(Cell::new(false)); let hit2 = hit.clone(); let a = Closure::new(move || hit2.set(true)); assert!(!long_lived_option_dropping_cache(None)); assert!(long_lived_option_dropping_cache(Some(&a))); assert!(!hit.get()); assert!(long_lived_option_dropping_call().is_ok()); assert!(hit.get()); drop(a); assert!(long_lived_option_dropping_call().is_err()); } #[wasm_bindgen_test] fn long_fnmut_recursive() { let a = Closure::new(|| { assert!(long_fnmut_recursive_call().is_err()); }); long_fnmut_recursive_cache(&a); assert!(long_fnmut_recursive_call().is_ok()); } #[wasm_bindgen_test] fn fnmut() { let mut a = false; fnmut_call(&mut || a = true); assert!(a); let mut x = false; assert_eq!( fnmut_thread(&mut |a| { x = true; a + 1 }), 3 ); assert!(x); } #[wasm_bindgen_test] fn fnmut_bad() { let mut x = true; let mut hits = 0; fnmut_bad_call(&mut || { hits += 1; if fnmut_bad_again(hits == 1).is_err() { return; } x = false; }); assert_eq!(hits, 1); assert!(x); assert!(fnmut_bad_again(true).is_err()); } #[wasm_bindgen_test] fn string_arguments() { let mut x = false; string_arguments_call(&mut |s| { assert_eq!(s, "foo"); x = true; }); assert!(x); } #[wasm_bindgen_test] fn string_ret() { let mut x = false; string_ret_call(&mut |mut s| { assert_eq!(s, "foo"); s.push_str("bar"); x = true; s }); assert!(x); } #[wasm_bindgen_test] fn drop_drops() { static mut HIT: bool = false; struct A; impl Drop for A { fn drop(&mut self) { unsafe { HIT = true; } } } let a = A; let x: Closure = Closure::new(move || drop(&a)); drop(x); unsafe { assert!(HIT); } } #[wasm_bindgen_test] fn drop_during_call_ok() { static mut HIT: bool = false; struct A; impl Drop for A { fn drop(&mut self) { unsafe { HIT = true; } } } let rc = Rc::new(RefCell::new(None)); let rc2 = rc.clone(); let x = 3; let a = A; let x: Closure = Closure::new(move || { // "drop ourselves" drop(rc2.borrow_mut().take().unwrap()); // `A` should not have been destroyed as a result unsafe { assert!(!HIT); } // allocate some heap memory to try to paper over our `3` drop(String::from("1234567890")); // make sure our closure memory is still valid assert_eq!(x, 3); // make sure `A` is bound to our closure environment. drop(&a); unsafe { assert!(!HIT); } }); drop_during_call_save(&x); *rc.borrow_mut() = Some(x); drop(rc); unsafe { assert!(!HIT); } drop_during_call_call(); unsafe { assert!(HIT); } } #[wasm_bindgen_test] fn test_closure_returner() { type ClosureType = dyn FnMut() -> BadStruct; use js_sys::{Object, Reflect}; js_test_closure_returner(); #[wasm_bindgen] pub struct ClosureHandle(Closure); #[wasm_bindgen] pub struct BadStruct {} #[wasm_bindgen] pub fn closure_returner() -> Result { let o = Object::new(); let some_fn = Closure::wrap(Box::new(move || BadStruct {}) as Box); Reflect::set( &o, &JsValue::from("someKey"), some_fn.as_ref().unchecked_ref(), ) .unwrap(); Reflect::set( &o, &JsValue::from("handle"), &JsValue::from(ClosureHandle(some_fn)), ) .unwrap(); Ok(o) } } #[wasm_bindgen] pub struct RefFirstArgument { contents: u32, } #[wasm_bindgen_test] fn reference_as_first_argument_builds_at_all() { #[wasm_bindgen] extern "C" { fn ref_first_arg1(a: &dyn Fn(&JsValue)); fn ref_first_arg2(a: &mut dyn FnMut(&JsValue)); fn ref_first_arg3(a: &Closure); fn ref_first_arg4(a: &Closure); fn ref_first_custom1(a: &dyn Fn(&RefFirstArgument)); fn ref_first_custom2(a: &mut dyn FnMut(&RefFirstArgument)); fn ref_first_custom3(a: &Closure); fn ref_first_custom4(a: &Closure); } Closure::wrap(Box::new(|_: &JsValue| ()) as Box); Closure::wrap(Box::new(|_: &JsValue| ()) as Box); Closure::once(|_: &JsValue| ()); Closure::once_into_js(|_: &JsValue| ()); Closure::wrap(Box::new(|_: &RefFirstArgument| ()) as Box); Closure::wrap(Box::new(|_: &RefFirstArgument| ()) as Box); Closure::once(|_: &RefFirstArgument| ()); Closure::once_into_js(|_: &RefFirstArgument| ()); } #[wasm_bindgen_test] fn reference_as_first_argument_works() { let a = Rc::new(Cell::new(0)); let b = { let a = a.clone(); Closure::once(move |x: &RefFirstArgument| { assert_eq!(a.get(), 0); assert_eq!(x.contents, 3); a.set(a.get() + 1); }) }; let c = { let a = a.clone(); Closure::once(move |x: &RefFirstArgument| { assert_eq!(a.get(), 1); assert_eq!(x.contents, 3); a.set(a.get() + 1); }) }; pass_reference_first_arg_twice(RefFirstArgument { contents: 3 }, &b, &c); assert_eq!(a.get(), 2); } #[wasm_bindgen_test] fn reference_as_first_argument_works2() { let a = Cell::new(0); pass_reference_first_arg_twice2( RefFirstArgument { contents: 3 }, &mut |x: &RefFirstArgument| { assert_eq!(a.get(), 0); assert_eq!(x.contents, 3); a.set(a.get() + 1); }, &mut |x: &RefFirstArgument| { assert_eq!(a.get(), 1); assert_eq!(x.contents, 3); a.set(a.get() + 1); }, ); assert_eq!(a.get(), 2); } #[wasm_bindgen_test] fn call_destroyed_doesnt_segfault() { struct A(i32, i32); impl Drop for A { fn drop(&mut self) { assert_eq!(self.0, self.1); } } let a = A(1, 1); let a = Closure::wrap(Box::new(move || drop(&a)) as Box); let b = a.as_ref().clone(); drop(a); call_destroyed(&b); let a = A(2, 2); let a = Closure::wrap(Box::new(move || drop(&a)) as Box); let b = a.as_ref().clone(); drop(a); call_destroyed(&b); let a = A(1, 1); let a = Closure::wrap(Box::new(move |_: &JsValue| drop(&a)) as Box); let b = a.as_ref().clone(); drop(a); call_destroyed(&b); let a = A(2, 2); let a = Closure::wrap(Box::new(move |_: &JsValue| drop(&a)) as Box); let b = a.as_ref().clone(); drop(a); call_destroyed(&b); } #[wasm_bindgen_test] fn forget_works() { let a = Closure::wrap(Box::new(|| {}) as Box); js_store_forgotten_closure(&a); a.forget(); js_call_forgotten_closure(); } #[wasm_bindgen_test] fn named_externref_no_duplicate_adapter() { externref_call(&Closure::new(|a| assert_eq!(a, 1))); named_externref_call(&Closure::new(|a| assert_eq!(a, 1))); }