Skip to content

Commit c504c38

Browse files
authored
Merge pull request #1530 from alexcrichton/drop-glue-closures
Protect against segfaults calling destroyed closures
2 parents f977630 + 542076d commit c504c38

File tree

3 files changed

+59
-4
lines changed

3 files changed

+59
-4
lines changed

src/closure.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,14 @@ macro_rules! doit {
576576
a: usize,
577577
b: usize,
578578
) {
579-
debug_assert!(a != 0, "should never destroy a Fn whose pointer is 0");
579+
// This can be called by the JS glue in erroneous situations
580+
// such as when the closure has already been destroyed. If
581+
// that's the case let's not make things worse by
582+
// segfaulting and/or asserting, so just ignore null
583+
// pointers.
584+
if a == 0 {
585+
return;
586+
}
580587
drop(Box::from_raw(FatPtr::<Fn($($var,)*) -> R> {
581588
fields: (a, b)
582589
}.ptr));
@@ -623,7 +630,10 @@ macro_rules! doit {
623630
a: usize,
624631
b: usize,
625632
) {
626-
debug_assert!(a != 0, "should never destroy a FnMut whose pointer is 0");
633+
// See `Fn()` above for why we simply return
634+
if a == 0 {
635+
return;
636+
}
627637
drop(Box::from_raw(FatPtr::<FnMut($($var,)*) -> R> {
628638
fields: (a, b)
629639
}.ptr));
@@ -736,7 +746,10 @@ unsafe impl<A, R> WasmClosure for Fn(&A) -> R
736746
a: usize,
737747
b: usize,
738748
) {
739-
debug_assert!(a != 0, "should never destroy a Fn whose pointer is 0");
749+
// See `Fn()` above for why we simply return
750+
if a == 0 {
751+
return;
752+
}
740753
drop(Box::from_raw(FatPtr::<Fn(&A) -> R> {
741754
fields: (a, b)
742755
}.ptr));
@@ -781,7 +794,10 @@ unsafe impl<A, R> WasmClosure for FnMut(&A) -> R
781794
a: usize,
782795
b: usize,
783796
) {
784-
debug_assert!(a != 0, "should never destroy a FnMut whose pointer is 0");
797+
// See `Fn()` above for why we simply return
798+
if a == 0 {
799+
return;
800+
}
785801
drop(Box::from_raw(FatPtr::<FnMut(&A) -> R> {
786802
fields: (a, b)
787803
}.ptr));

tests/wasm/closures.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,7 @@ exports.pass_reference_first_arg_twice = (a, b, c) => {
119119
c(a);
120120
a.free();
121121
};
122+
123+
exports.call_destroyed = f => {
124+
assert.throws(f, /invoked recursively or destroyed/);
125+
};

tests/wasm/closures.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ extern "C" {
102102
b: &mut FnMut(&RefFirstArgument),
103103
c: &mut FnMut(&RefFirstArgument),
104104
);
105+
fn call_destroyed(a: &JsValue);
105106
}
106107

107108
#[wasm_bindgen_test]
@@ -522,3 +523,37 @@ fn reference_as_first_argument_works2() {
522523
);
523524
assert_eq!(a.get(), 2);
524525
}
526+
527+
#[wasm_bindgen_test]
528+
fn call_destroyed_doesnt_segfault() {
529+
struct A(i32, i32);
530+
impl Drop for A {
531+
fn drop(&mut self) {
532+
assert_eq!(self.0, self.1);
533+
}
534+
}
535+
536+
let a = A(1, 1);
537+
let a = Closure::wrap(Box::new(move || drop(&a)) as Box<Fn()>);
538+
let b = a.as_ref().clone();
539+
drop(a);
540+
call_destroyed(&b);
541+
542+
let a = A(2, 2);
543+
let a = Closure::wrap(Box::new(move || drop(&a)) as Box<FnMut()>);
544+
let b = a.as_ref().clone();
545+
drop(a);
546+
call_destroyed(&b);
547+
548+
let a = A(1, 1);
549+
let a = Closure::wrap(Box::new(move |_: &JsValue| drop(&a)) as Box<Fn(&JsValue)>);
550+
let b = a.as_ref().clone();
551+
drop(a);
552+
call_destroyed(&b);
553+
554+
let a = A(2, 2);
555+
let a = Closure::wrap(Box::new(move |_: &JsValue| drop(&a)) as Box<FnMut(&JsValue)>);
556+
let b = a.as_ref().clone();
557+
drop(a);
558+
call_destroyed(&b);
559+
}

0 commit comments

Comments
 (0)