commit 5b07337477c71fe4cb5d5d1cd67a1d36138d488e
parent 8b5066eeae2530e3653353cfbc2751d4162fd6ae
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date: Tue, 25 Nov 2025 18:24:38 +0000
Bug 2002134 - Disambiguate between unwrapping failure and missing data in JS Microtask Queue r=arai
Differential Revision: https://phabricator.services.mozilla.com/D272772
Diffstat:
4 files changed, 55 insertions(+), 31 deletions(-)
diff --git a/js/public/friend/MicroTask.h b/js/public/friend/MicroTask.h
@@ -157,12 +157,15 @@ JS_PUBLIC_API void RestoreMicroTaskQueue(
// Via the following API functions various host defined data is exposed to the
// embedder (see JobQueue::getHostDefinedData).
//
-// All of these may return null if there's no data, or if there's a
-// security error.
-JS_PUBLIC_API JSObject* MaybeGetHostDefinedDataFromJSMicroTask(
- JSMicroTask* entry);
-JS_PUBLIC_API JSObject* MaybeGetAllocationSiteFromJSMicroTask(
- JSMicroTask* entry);
+// These return true on success and false on failure. They return false if
+// there are any unwrapping issues (e.g., dead wrappers), and true with nullptr
+// if there just isn't any data.
+//
+// This disambiguates between no-data and the dead wrapper case
+JS_PUBLIC_API bool MaybeGetHostDefinedDataFromJSMicroTask(
+ JSMicroTask* entry, MutableHandleObject out);
+JS_PUBLIC_API bool MaybeGetAllocationSiteFromJSMicroTask(
+ JSMicroTask* entry, MutableHandleObject out);
// In some circumstances an entry may not have host defined data but may
// still have a host defined global;
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
@@ -7696,14 +7696,15 @@ inline bool JSObject::is<MicroTaskEntry>() const {
return is<ThenableJob>() || is<PromiseReactionRecord>();
}
-JS_PUBLIC_API JSObject* JS::MaybeGetHostDefinedDataFromJSMicroTask(
- JS::JSMicroTask* entry) {
+JS_PUBLIC_API bool JS::MaybeGetHostDefinedDataFromJSMicroTask(
+ JS::JSMicroTask* entry, MutableHandleObject out) {
+ out.set(nullptr);
JSObject* task = CheckedUnwrapStatic(entry);
if (!task) {
- return nullptr;
+ return false;
}
if (JS_IsDeadWrapper(task)) {
- return nullptr;
+ return false;
}
MOZ_ASSERT(task->is<MicroTaskEntry>());
@@ -7711,34 +7712,44 @@ JS_PUBLIC_API JSObject* JS::MaybeGetHostDefinedDataFromJSMicroTask(
task->as<MicroTaskEntry>().getHostDefinedData().toObjectOrNull();
if (!maybeHostDefined) {
- return nullptr;
+ return true;
}
if (JS_IsDeadWrapper(maybeHostDefined)) {
- return nullptr;
+ return false;
+ }
+
+ JSObject* unwrapped = CheckedUnwrapStatic(maybeHostDefined);
+ if (!unwrapped) {
+ return false;
}
- return CheckedUnwrapStatic(maybeHostDefined);
+ out.set(unwrapped);
+ return true;
}
-JS_PUBLIC_API JSObject* JS::MaybeGetAllocationSiteFromJSMicroTask(
- JS::JSMicroTask* entry) {
+JS_PUBLIC_API bool JS::MaybeGetAllocationSiteFromJSMicroTask(
+ JS::JSMicroTask* entry, MutableHandleObject out) {
JSObject* task = UncheckedUnwrap(entry);
if (JS_IsDeadWrapper(task)) {
- return nullptr;
+ return false;
};
MOZ_ASSERT(task->is<MicroTaskEntry>());
JSObject* maybeWrappedStack = task->as<MicroTaskEntry>().allocationStack();
- // If the stack is in a compartment which has gone away, best we
- // can do is return nullptr.
- if (!maybeWrappedStack || JS_IsDeadWrapper(maybeWrappedStack)) {
- return nullptr;
+ if (!maybeWrappedStack) {
+ out.set(nullptr);
+ return true;
+ }
+
+ if (JS_IsDeadWrapper(maybeWrappedStack)) {
+ return false;
}
JSObject* unwrapped = UncheckedUnwrap(maybeWrappedStack);
MOZ_ASSERT(unwrapped->is<SavedFrame>());
- return unwrapped;
+ out.set(unwrapped);
+ return true;
}
JS_PUBLIC_API JSObject* JS::MaybeGetHostDefinedGlobalFromJSMicroTask(
diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp
@@ -1016,10 +1016,19 @@ static void MOZ_CAN_RUN_SCRIPT RunMicroTask(
if (!callbackGlobal) {
return;
}
- JS::RootedField<JSObject*, 1> hostDefinedData(
- roots, aMicroTask.get().MaybeGetHostDefinedDataFromJSMicroTask());
- JS::RootedField<JSObject*, 2> allocStack(
- roots, aMicroTask.get().MaybeGetAllocationSiteFromJSMicroTask());
+ JS::RootedField<JSObject*, 1> hostDefinedData(roots);
+ JS::RootedField<JSObject*, 2> allocStack(roots);
+
+ // Don't run if we fail to unwrap the host defined data.
+ if (!aMicroTask.get().MaybeGetHostDefinedDataFromJSMicroTask(
+ &hostDefinedData)) {
+ return;
+ }
+
+ // Don't run if we fail to unwrap the stack.
+ if (!aMicroTask.get().MaybeGetAllocationSiteFromJSMicroTask(&allocStack)) {
+ return;
+ }
nsIGlobalObject* incumbentGlobal = nullptr;
diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h
@@ -218,20 +218,21 @@ class MustConsumeMicroTask {
return JS::MaybeGetPromiseFromJSMicroTask(task);
}
- JSObject* MaybeGetHostDefinedDataFromJSMicroTask() {
+ bool MaybeGetHostDefinedDataFromJSMicroTask(
+ JS::MutableHandle<JSObject*> out) {
JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask);
if (!task) {
- return nullptr;
+ return false;
}
- return JS::MaybeGetHostDefinedDataFromJSMicroTask(task);
+ return JS::MaybeGetHostDefinedDataFromJSMicroTask(task, out);
}
- JSObject* MaybeGetAllocationSiteFromJSMicroTask() {
+ bool MaybeGetAllocationSiteFromJSMicroTask(JS::MutableHandle<JSObject*> out) {
JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask);
if (!task) {
- return nullptr;
+ return false;
}
- return JS::MaybeGetAllocationSiteFromJSMicroTask(task);
+ return JS::MaybeGetAllocationSiteFromJSMicroTask(task, out);
}
JSObject* MaybeGetHostDefinedGlobalFromJSMicroTask() {