Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upPromise rooting is not sound #15085
Promise rooting is not sound #15085
Comments
|
It may also be a case of the Promise object being moved out of the nursery, rather than collected. I don't know enough about SM to determine that yet. |
|
If I can help you with this anyway just let me know! |
|
The js-side |
|
If the underlying JS I'm pretty sure that this will become relevant for |
|
Yes, the instances are created via Promise::new which uses NewPromiseObject. |
|
That sounds like it wouldn't work for WebIDL APIs that accept existing Promises, though, which could be created via |
It wouldn't, no. Since that needs to be supported, Oh, and I just realize that pre-tenuring wouldn't fully solve this, because compacting GC would still cause |
|
https://gist.github.com/jdm/b36d32d887e1fd6887b6d60c6cae218d is the testcase I'm using to try to reproduce this outside of Servo. I'm triggering a minor GC via DumpHeap, but it doesn't crash like I would expect it to. |
|
It turns out my testcase wasn't replicating the issue from Servo correctly - specifically, it was stashing the promise object in a Heap value, which correctly uses post-barriers to ensure that the pointer gets updated when the promise object is moved out of the nursery. Servo's |
|
There are two possible solutions that I see:
I'm not sure why we aren't using Heap already. I'm guessing either performance concerns or historical artifact of when Heap wasn't supported from Rust code yet? |
diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs
index 89bbd08..a727e50 100644
--- a/components/script/dom/promise.rs
+++ b/components/script/dom/promise.rs
@@ -90,12 +90,12 @@ impl Promise {
#[allow(unsafe_code, unrooted_must_root)]
unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> {
assert!(IsPromiseObject(obj));
- let mut promise = Promise {
+ let promise = Promise {
reflector: Reflector::new(),
permanent_js_root: MutHeapJSVal::new(),
};
- promise.init_reflector(obj.get());
- let promise = Rc::new(promise);
+ let mut promise = Rc::new(promise);
+ Rc::get_mut(&mut promise).unwrap().init_reflector(obj.get());
promise.initialize(cx);
promise
}
diff --git a/components/script/dom/bindings/reflector.rs b/components/script/dom/bindings/reflector.rs
index eebea3a..6f93745 100644
--- a/components/script/dom/bindings/reflector.rs
+++ b/components/script/dom/bindings/reflector.rs
@@ -8,6 +8,7 @@ use dom::bindings::conversions::DerivedFrom;
use dom::bindings::js::Root;
use dom::globalscope::GlobalScope;
use js::jsapi::{HandleObject, JSContext, JSObject};
+use js::rust::GCMethods;
use std::cell::UnsafeCell;
use std::ptr;
@@ -58,6 +59,7 @@ impl Reflector {
assert!((*obj).is_null());
assert!(!object.is_null());
*obj = object;
+ GCMethods::post_barrier(obj, ptr::null_mut(), object);
}
} |
|
And a straightforward testcase, now that the problem is fully clear: diff --git a/tests/wpt/mozilla/tests/mozilla/promise.html b/tests/wpt/mozilla/tests/mozilla/promise.html
index fa16edc..729e878 100644
--- a/tests/wpt/mozilla/tests/mozilla/promise.html
+++ b/tests/wpt/mozilla/tests/mozilla/promise.html
@@ -58,6 +58,7 @@
var p = new Promise(function() {});
var start = Date.now();
t.resolvePromiseDelayed(p, 'success', 100);
+ test.step_timeout(function() { window.gc() }, 0);
return p.then(function(v) {
var end = Date.now();
assert_greater_than_equal(end - start, 100); |
Use Heap instead of UnsafeCell in DOM reflectors The previous `Reflector` implementation did not use post barriers, so we could crash when storing nursery objects in a `Reflector` structure that were later moved out of the nursery. - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #15085 - [X] There are tests for these changes <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15118) <!-- Reviewable:end -->
Use Heap instead of UnsafeCell in DOM reflectors The previous `Reflector` implementation did not use post barriers, so we could crash when storing nursery objects in a `Reflector` structure that were later moved out of the nursery. - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #15085 - [X] There are tests for these changes <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15118) <!-- Reviewable:end -->
Use Heap instead of UnsafeCell in DOM reflectors The previous `Reflector` implementation did not use post barriers, so we could crash when storing nursery objects in a `Reflector` structure that were later moved out of the nursery. - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #15085 - [X] There are tests for these changes <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15118) <!-- Reviewable:end -->
The second commit in #15080 exposes a crash that involves a
TrustedPromisevalue that has been garbage collected by the time we try to turn it into a stack root once more. The backtrace shows a nursery GC occurring, which means that our trace hooks don't get called. The promise object is nursery-allocated, I guess, which would explain why it's GCed during the minor collection.