Skip to content

Commit ace07c8

Browse files
delanjdmatbrakhi
authored
Implement saveJobQueue() for the SpiderMonkey Debugger API (#595)
* Disable assert in saveJobQueue() Co-authored-by: Josh Matthews <[email protected]> Signed-off-by: Delan Azabani <[email protected]> * Implement saveJobQueue() as required by SpiderMonkey Co-authored-by: atbrakhi <[email protected]> Signed-off-by: Delan Azabani <[email protected]> * Fix use-after-free check and add comments Co-authored-by: atbrakhi <[email protected]> Signed-off-by: Delan Azabani <[email protected]> * Fix formatting Signed-off-by: Delan Azabani <[email protected]> Co-authored-by: atbrakhi <[email protected]> * Clarify comment a bit Signed-off-by: Delan Azabani <[email protected]> Co-authored-by: atbrakhi <[email protected]> * Removed unused JSContext field Co-authored-by: atbrakhi <[email protected]> Signed-off-by: Delan Azabani <[email protected]> * Amend comment about OOM conditions Co-authored-by: atbrakhi <[email protected]> Signed-off-by: Delan Azabani <[email protected]> * Make push and pop traps return const pointers Co-authored-by: atbrakhi <[email protected]> Signed-off-by: Delan Azabani <[email protected]> * Add dropInterruptQueues trap to help Servo avoid memory leak Co-authored-by: atbrakhi <[email protected]> Signed-off-by: Delan Azabani <[email protected]> --------- Signed-off-by: Delan Azabani <[email protected]> Co-authored-by: Josh Matthews <[email protected]> Co-authored-by: atbrakhi <[email protected]>
1 parent b14aebf commit ace07c8

File tree

1 file changed

+95
-6
lines changed

1 file changed

+95
-6
lines changed

mozjs-sys/src/jsglue.cpp

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,29 @@ struct JobQueueTraps {
4949
JS::HandleObject allocationSite,
5050
JS::HandleObject incumbentGlobal) = 0;
5151
bool (*empty)(const void* queue);
52+
53+
// Create a new queue, push it onto an embedder-side stack, and return the new
54+
// queue.
55+
const void* (*pushNewInterruptQueue)(void* aInterruptQueues);
56+
// Destroy the queue most recently created by pushNewInterruptQueue(),
57+
// returning its address so we can check if we are restoring the saved queue
58+
// over the correct queue.
59+
const void* (*popInterruptQueue)(void* aInterruptQueues);
60+
// Destroy the embedder-side stack of interrupt queues.
61+
void (*dropInterruptQueues)(void* aInterruptQueues);
5262
};
5363

5464
class RustJobQueue : public JS::JobQueue {
5565
JobQueueTraps mTraps;
5666
const void* mQueue;
67+
void* mInterruptQueues;
5768

5869
public:
59-
RustJobQueue(const JobQueueTraps& aTraps, const void* aQueue)
60-
: mTraps(aTraps), mQueue(aQueue) {}
70+
RustJobQueue(const JobQueueTraps& aTraps, const void* aQueue,
71+
void* aInterruptQueues)
72+
: mTraps(aTraps), mQueue(aQueue), mInterruptQueues(aInterruptQueues) {}
73+
74+
~RustJobQueue() { mTraps.dropInterruptQueues(mInterruptQueues); }
6175

6276
virtual JSObject* getIncumbentGlobal(JSContext* cx) override {
6377
return mTraps.getIncumbentGlobal(mQueue, cx);
@@ -80,9 +94,83 @@ class RustJobQueue : public JS::JobQueue {
8094
bool isDrainingStopped() const override { return false; }
8195

8296
private:
97+
class SavedQueue : public JS::JobQueue::SavedJobQueue {
98+
public:
99+
SavedQueue(const JobQueueTraps& aTraps, void* aInterruptQueues,
100+
const void** aCurrentQueue, const void* aNewQueue)
101+
: mTraps(aTraps),
102+
mInterruptQueues(aInterruptQueues),
103+
mCurrentQueue(aCurrentQueue),
104+
mNewQueue(aNewQueue),
105+
mSavedQueue(*aCurrentQueue) {
106+
// TODO: assert that the context’s jobQueue hasn’t been cleared with
107+
// SetJobQueue(nullptr) or DestroyContext(). Don’t know how to do this
108+
// with only an opaque JSContext decl. Are we allowed to #include
109+
// "vm/JSContext.h"?
110+
//
111+
// MOZ_ASSERT(cx->jobQueue.ref());
112+
113+
// Set the current queue to mNewQueue.
114+
// We need to take care of this, so that we can save the old queue in the
115+
// member initializers above.
116+
*mCurrentQueue = mNewQueue;
117+
}
118+
119+
~SavedQueue() {
120+
// TODO: assert that the context’s jobQueue hasn’t been cleared with
121+
// SetJobQueue(nullptr) or DestroyContext(). Don’t know how to do this
122+
// with only an opaque JSContext decl. Are we allowed to #include
123+
// "vm/JSContext.h"?
124+
//
125+
// MOZ_ASSERT(cx->jobQueue.ref());
126+
127+
// Check that the current queue is empty, as required by the SavedJobQueue
128+
// contract.
129+
MOZ_ASSERT(mTraps.empty(*mCurrentQueue));
130+
131+
// Destroy the topmost queue, checking that it was the queue this
132+
// SavedQueue expects to restore from. Imagine we have normal queue A,
133+
// then we switch to B (SavedQueue from B to A), then we switch to C
134+
// (SavedQueue from C to B). If the SavedQueue from B to A is restored
135+
// before the SavedQueue from C to B, the embedder will destroy both C and
136+
// B, but in the end, the queue will be set to B, a freed queue.
137+
MOZ_ASSERT(mTraps.popInterruptQueue(mInterruptQueues) == mNewQueue);
138+
139+
*mCurrentQueue = mSavedQueue;
140+
}
141+
142+
private:
143+
// Required for embedder FFI.
144+
JobQueueTraps mTraps;
145+
void* mInterruptQueues;
146+
147+
// Pointer to the RustJobQueue::mQueue field to write to when switching.
148+
const void** mCurrentQueue;
149+
150+
// The queue to switch to when saving.
151+
const void* mNewQueue;
152+
153+
// The queue to switch to when restoring.
154+
const void* mSavedQueue;
155+
};
156+
83157
virtual js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext* cx) override {
84-
MOZ_ASSERT(false, "saveJobQueue should not be invoked");
85-
return nullptr;
158+
auto newQueue = mTraps.pushNewInterruptQueue(mInterruptQueues);
159+
// Servo uses infallible allocation here, so it should never return nullptr.
160+
MOZ_ASSERT(!!newQueue);
161+
162+
auto result =
163+
js::MakeUnique<SavedQueue>(mTraps, mInterruptQueues, &mQueue, newQueue);
164+
if (!result) {
165+
// “On OOM, this should call JS_ReportOutOfMemory on the given JSContext,
166+
// and return a null UniquePtr.”
167+
//
168+
// When the allocation in MakeUnique() fails, the SavedQueue constructor
169+
// is never called, so this->mQueue is still set to the old queue.
170+
js::ReportOutOfMemory(cx);
171+
return nullptr;
172+
}
173+
return result;
86174
}
87175
};
88176

@@ -1012,8 +1100,9 @@ JSString* JS_ForgetStringLinearness(JSLinearString* str) {
10121100
return JS_FORGET_STRING_LINEARNESS(str);
10131101
}
10141102

1015-
JS::JobQueue* CreateJobQueue(const JobQueueTraps* aTraps, const void* aQueue) {
1016-
return new RustJobQueue(*aTraps, aQueue);
1103+
JS::JobQueue* CreateJobQueue(const JobQueueTraps* aTraps, const void* aQueue,
1104+
void* aInterruptQueues) {
1105+
return new RustJobQueue(*aTraps, aQueue, aInterruptQueues);
10171106
}
10181107

10191108
void DeleteJobQueue(JS::JobQueue* queue) { delete queue; }

0 commit comments

Comments
 (0)