blob: 925ca6b15e2405ee85d4e918de7a9234efc8f7f2 [file] [log] [blame]
#![cfg(feature = "nightly")]
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: &Fn());
fn works_thread(a: &Fn(u32) -> u32) -> u32;
fn cannot_reuse_call(a: &Fn());
#[wasm_bindgen(catch)]
fn cannot_reuse_call_again() -> Result<(), JsValue>;
fn long_lived_call1(a: &Closure<Fn()>);
fn long_lived_call2(a: &Closure<FnMut(u32) -> u32>) -> u32;
fn many_arity_call1(a: &Closure<Fn()>);
fn many_arity_call2(a: &Closure<Fn(u32)>);
fn many_arity_call3(a: &Closure<Fn(u32, u32)>);
fn many_arity_call4(a: &Closure<Fn(u32, u32, u32)>);
fn many_arity_call5(a: &Closure<Fn(u32, u32, u32, u32)>);
fn many_arity_call6(a: &Closure<Fn(u32, u32, u32, u32, u32)>);
fn many_arity_call7(a: &Closure<Fn(u32, u32, u32, u32, u32, u32)>);
fn many_arity_call8(a: &Closure<Fn(u32, u32, u32, u32, u32, u32, u32)>);
fn many_arity_call9(a: &Closure<Fn(u32, u32, u32, u32, u32, u32, u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call1)]
fn many_arity_call_mut1(a: &Closure<FnMut()>);
#[wasm_bindgen(js_name = many_arity_call2)]
fn many_arity_call_mut2(a: &Closure<FnMut(u32)>);
#[wasm_bindgen(js_name = many_arity_call3)]
fn many_arity_call_mut3(a: &Closure<FnMut(u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call4)]
fn many_arity_call_mut4(a: &Closure<FnMut(u32, u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call5)]
fn many_arity_call_mut5(a: &Closure<FnMut(u32, u32, u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call6)]
fn many_arity_call_mut6(a: &Closure<FnMut(u32, u32, u32, u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call7)]
fn many_arity_call_mut7(a: &Closure<FnMut(u32, u32, u32, u32, u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call8)]
fn many_arity_call_mut8(a: &Closure<FnMut(u32, u32, u32, u32, u32, u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call9)]
fn many_arity_call_mut9(a: &Closure<FnMut(u32, u32, u32, u32, u32, u32, u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call1)]
fn many_arity_stack1(a: &Fn());
#[wasm_bindgen(js_name = many_arity_call2)]
fn many_arity_stack2(a: &Fn(u32));
#[wasm_bindgen(js_name = many_arity_call3)]
fn many_arity_stack3(a: &Fn(u32, u32));
#[wasm_bindgen(js_name = many_arity_call4)]
fn many_arity_stack4(a: &Fn(u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call5)]
fn many_arity_stack5(a: &Fn(u32, u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call6)]
fn many_arity_stack6(a: &Fn(u32, u32, u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call7)]
fn many_arity_stack7(a: &Fn(u32, u32, u32, u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call8)]
fn many_arity_stack8(a: &Fn(u32, u32, u32, u32, u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call9)]
fn many_arity_stack9(a: &Fn(u32, u32, u32, u32, u32, u32, u32, u32));
fn long_lived_dropping_cache(a: &Closure<Fn()>);
#[wasm_bindgen(catch)]
fn long_lived_dropping_call() -> Result<(), JsValue>;
fn long_fnmut_recursive_cache(a: &Closure<FnMut()>);
#[wasm_bindgen(catch)]
fn long_fnmut_recursive_call() -> Result<(), JsValue>;
fn fnmut_call(a: &mut FnMut());
fn fnmut_thread(a: &mut FnMut(u32) -> u32) -> u32;
fn fnmut_bad_call(a: &mut FnMut());
#[wasm_bindgen(catch)]
fn fnmut_bad_again(a: bool) -> Result<(), JsValue>;
fn string_arguments_call(a: &mut FnMut(String));
fn string_ret_call(a: &mut FnMut(String) -> String);
fn drop_during_call_save(a: &Closure<Fn()>);
fn drop_during_call_call();
fn js_test_closure_returner();
fn calling_it_throws(a: &Closure<FnMut()>) -> 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<FnMut(&RefFirstArgument)>,
c: &Closure<FnMut(&RefFirstArgument)>,
);
#[wasm_bindgen(js_name = pass_reference_first_arg_twice)]
fn pass_reference_first_arg_twice2(
a: RefFirstArgument,
b: &mut FnMut(&RefFirstArgument),
c: &mut FnMut(&RefFirstArgument),
);
fn call_destroyed(a: &JsValue);
fn js_store_forgotten_closure(closure: &Closure<Fn()>);
fn js_call_forgotten_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<FnMut()>);
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))),
);
}
struct Dropper(Rc<Cell<bool>>);
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_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<Fn()> = 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<Fn()> = 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 = FnMut() -> BadStruct;
use js_sys::{Object, Reflect};
use wasm_bindgen::JsCast;
js_test_closure_returner();
#[wasm_bindgen]
pub struct ClosureHandle(Closure<ClosureType>);
#[wasm_bindgen]
pub struct BadStruct {}
#[wasm_bindgen]
pub fn closure_returner() -> Result<Object, JsValue> {
let o = Object::new();
let some_fn = Closure::wrap(Box::new(move || BadStruct {}) as Box<ClosureType>);
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: &Fn(&JsValue));
fn ref_first_arg2(a: &mut FnMut(&JsValue));
fn ref_first_arg3(a: &Closure<Fn(&JsValue)>);
fn ref_first_arg4(a: &Closure<FnMut(&JsValue)>);
fn ref_first_custom1(a: &Fn(&RefFirstArgument));
fn ref_first_custom2(a: &mut FnMut(&RefFirstArgument));
fn ref_first_custom3(a: &Closure<Fn(&RefFirstArgument)>);
fn ref_first_custom4(a: &Closure<FnMut(&RefFirstArgument)>);
}
Closure::wrap(Box::new(|_: &JsValue| ()) as Box<Fn(&JsValue)>);
Closure::wrap(Box::new(|_: &JsValue| ()) as Box<FnMut(&JsValue)>);
Closure::once(|_: &JsValue| ());
Closure::once_into_js(|_: &JsValue| ());
Closure::wrap(Box::new(|_: &RefFirstArgument| ()) as Box<Fn(&RefFirstArgument)>);
Closure::wrap(Box::new(|_: &RefFirstArgument| ()) as Box<FnMut(&RefFirstArgument)>);
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<Fn()>);
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<FnMut()>);
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<Fn(&JsValue)>);
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<FnMut(&JsValue)>);
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<Fn()>);
js_store_forgotten_closure(&a);
a.forget();
js_call_forgotten_closure();
}