1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "debugger/DebugScript.h"
9 #include "mozilla/Assertions.h" // for AssertionConditionType
10 #include "mozilla/HashTable.h" // for HashMapEntry, HashTable<>::Ptr, HashMap
11 #include "mozilla/UniquePtr.h" // for UniquePtr
13 #include <utility> // for std::move
15 #include "debugger/DebugAPI.h" // for DebugAPI
16 #include "debugger/Debugger.h" // for JSBreakpointSite, Breakpoint
17 #include "gc/Cell.h" // for TenuredCell
18 #include "gc/GCContext.h" // for JS::GCContext
19 #include "gc/GCEnum.h" // for MemoryUse, MemoryUse::BreakpointSite
20 #include "gc/Marking.h" // for IsAboutToBeFinalized
21 #include "gc/Zone.h" // for Zone
22 #include "gc/ZoneAllocator.h" // for AddCellMemory
23 #include "jit/BaselineJIT.h" // for BaselineScript
24 #include "vm/BytecodeIterator.h" // for AllBytecodesIterable
25 #include "vm/JSContext.h" // for JSContext
26 #include "vm/JSScript.h" // for JSScript, DebugScriptMap
27 #include "vm/NativeObject.h" // for NativeObject
28 #include "vm/Realm.h" // for Realm, AutoRealm
29 #include "vm/Runtime.h" // for ReportOutOfMemory
30 #include "vm/Stack.h" // for ActivationIterator, Activation
32 #include "gc/GC-inl.h" // for ZoneCellIter
33 #include "gc/GCContext-inl.h" // for JS::GCContext::free_
34 #include "gc/Marking-inl.h" // for CheckGCThingAfterMovingGC
35 #include "gc/WeakMap-inl.h" // for WeakMap::remove
36 #include "vm/BytecodeIterator-inl.h" // for AllBytecodesIterable
37 #include "vm/JSContext-inl.h" // for JSContext::check
38 #include "vm/JSObject-inl.h" // for NewObjectWithGivenProto
39 #include "vm/JSScript-inl.h" // for JSScript::hasBaselineScript
40 #include "vm/Realm-inl.h" // for AutoRealm::AutoRealm
44 const JSClass
DebugScriptObject::class_
= {
46 JSCLASS_HAS_RESERVED_SLOTS(SlotCount
) | JSCLASS_BACKGROUND_FINALIZE
,
47 &classOps_
, JS_NULL_CLASS_SPEC
};
49 const JSClassOps
DebugScriptObject::classOps_
= {
50 nullptr, // addProperty
51 nullptr, // delProperty
53 nullptr, // newEnumerate
55 nullptr, // mayResolve
56 DebugScriptObject::finalize
, // finalize
59 DebugScriptObject::trace
, // trace
63 DebugScriptObject
* DebugScriptObject::create(JSContext
* cx
,
64 UniqueDebugScript debugScript
,
66 auto* object
= NewObjectWithGivenProto
<DebugScriptObject
>(cx
, nullptr);
71 object
->initReservedSlot(ScriptSlot
, PrivateValue(debugScript
.release()));
72 AddCellMemory(object
, nbytes
, MemoryUse::ScriptDebugScript
);
77 DebugScript
* DebugScriptObject::debugScript() const {
78 return maybePtrFromReservedSlot
<DebugScript
>(ScriptSlot
);
82 void DebugScriptObject::trace(JSTracer
* trc
, JSObject
* obj
) {
83 DebugScript
* debugScript
= obj
->as
<DebugScriptObject
>().debugScript();
85 debugScript
->trace(trc
);
90 void DebugScriptObject::finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
91 DebugScriptObject
* object
= &obj
->as
<DebugScriptObject
>();
92 DebugScript
* debugScript
= object
->debugScript();
94 debugScript
->delete_(gcx
, object
);
99 DebugScript
* DebugScript::get(JSScript
* script
) {
100 MOZ_ASSERT(script
->hasDebugScript());
101 DebugScriptMap
* map
= script
->zone()->debugScriptMap
;
103 DebugScriptMap::Ptr p
= map
->lookupUnbarriered(script
);
105 return p
->value().get()->as
<DebugScriptObject
>().debugScript();
109 DebugScript
* DebugScript::getOrCreate(JSContext
* cx
, HandleScript script
) {
112 if (script
->hasDebugScript()) {
116 size_t nbytes
= allocSize(script
->length());
117 UniqueDebugScript
debug(
118 reinterpret_cast<DebugScript
*>(cx
->pod_calloc
<uint8_t>(nbytes
)));
123 debug
->codeLength
= script
->length();
125 Rooted
<DebugScriptObject
*> object(
126 cx
, DebugScriptObject::create(cx
, std::move(debug
), nbytes
));
131 /* Create zone's debugScriptMap if necessary. */
132 Zone
* zone
= script
->zone();
133 MOZ_ASSERT(cx
->zone() == zone
);
134 if (!zone
->debugScriptMap
) {
135 DebugScriptMap
* map
= cx
->new_
<DebugScriptMap
>(cx
);
140 zone
->debugScriptMap
= map
;
143 MOZ_ASSERT(script
->hasBytecode());
145 if (!zone
->debugScriptMap
->putNew(script
.get(), object
.get())) {
146 ReportOutOfMemory(cx
);
150 // It is safe to set this: we can't fail after this point.
151 script
->setHasDebugScript(true);
154 * Ensure that any Interpret() instances running on this script have
155 * interrupts enabled. The interrupts must stay enabled until the
156 * debug state is destroyed.
158 for (ActivationIterator
iter(cx
); !iter
.done(); ++iter
) {
159 if (iter
->isInterpreter()) {
160 iter
->asInterpreter()->enableInterruptsIfRunning(script
);
164 return object
->debugScript();
168 JSBreakpointSite
* DebugScript::getBreakpointSite(JSScript
* script
,
170 uint32_t offset
= script
->pcToOffset(pc
);
171 return script
->hasDebugScript() ? get(script
)->breakpoints
[offset
] : nullptr;
175 JSBreakpointSite
* DebugScript::getOrCreateBreakpointSite(JSContext
* cx
,
178 AutoRealm
ar(cx
, script
);
180 DebugScript
* debug
= getOrCreate(cx
, script
);
185 JSBreakpointSite
*& site
= debug
->breakpoints
[script
->pcToOffset(pc
)];
188 site
= cx
->new_
<JSBreakpointSite
>(script
, pc
);
193 AddCellMemory(script
, sizeof(JSBreakpointSite
), MemoryUse::BreakpointSite
);
195 if (script
->hasBaselineScript()) {
196 script
->baselineScript()->toggleDebugTraps(script
, pc
);
204 void DebugScript::destroyBreakpointSite(JS::GCContext
* gcx
, JSScript
* script
,
206 DebugScript
* debug
= get(script
);
207 JSBreakpointSite
*& site
= debug
->breakpoints
[script
->pcToOffset(pc
)];
209 MOZ_ASSERT(site
->isEmpty());
215 if (!debug
->needed()) {
216 DebugAPI::removeDebugScript(gcx
, script
);
219 if (script
->hasBaselineScript()) {
220 script
->baselineScript()->toggleDebugTraps(script
, pc
);
225 void DebugScript::clearBreakpointsIn(JS::GCContext
* gcx
, JSScript
* script
,
226 Debugger
* dbg
, JSObject
* handler
) {
228 // Breakpoints hold wrappers in the script's compartment for the handler. Make
229 // sure we don't try to search for the unwrapped handler.
230 MOZ_ASSERT_IF(handler
, script
->compartment() == handler
->compartment());
232 if (!script
->hasDebugScript()) {
236 AllBytecodesIterable
iter(script
);
237 for (BytecodeLocation loc
: iter
) {
238 JSBreakpointSite
* site
= getBreakpointSite(script
, loc
.toRawBytecode());
241 for (Breakpoint
* bp
= site
->firstBreakpoint(); bp
; bp
= nextbp
) {
242 nextbp
= bp
->nextInSite();
243 if ((!dbg
|| bp
->debugger
== dbg
) &&
244 (!handler
|| bp
->getHandler() == handler
)) {
254 uint32_t DebugScript::getStepperCount(JSScript
* script
) {
255 return script
->hasDebugScript() ? get(script
)->stepperCount
: 0;
260 bool DebugScript::incrementStepperCount(JSContext
* cx
, HandleScript script
) {
262 MOZ_ASSERT(cx
->realm()->isDebuggee());
264 AutoRealm
ar(cx
, script
);
266 DebugScript
* debug
= getOrCreate(cx
, script
);
271 debug
->stepperCount
++;
273 if (debug
->stepperCount
== 1) {
274 if (script
->hasBaselineScript()) {
275 script
->baselineScript()->toggleDebugTraps(script
, nullptr);
283 void DebugScript::decrementStepperCount(JS::GCContext
* gcx
, JSScript
* script
) {
284 DebugScript
* debug
= get(script
);
286 MOZ_ASSERT(debug
->stepperCount
> 0);
288 debug
->stepperCount
--;
290 if (debug
->stepperCount
== 0) {
291 if (script
->hasBaselineScript()) {
292 script
->baselineScript()->toggleDebugTraps(script
, nullptr);
295 if (!debug
->needed()) {
296 DebugAPI::removeDebugScript(gcx
, script
);
302 bool DebugScript::incrementGeneratorObserverCount(JSContext
* cx
,
303 HandleScript script
) {
305 MOZ_ASSERT(cx
->realm()->isDebuggee());
307 AutoRealm
ar(cx
, script
);
309 DebugScript
* debug
= getOrCreate(cx
, script
);
314 debug
->generatorObserverCount
++;
316 // It is our caller's responsibility, before bumping the generator observer
317 // count, to make sure that the baseline code includes the necessary
318 // JSOp::AfterYield instrumentation by calling
319 // {ensure,update}ExecutionObservabilityOfScript.
320 MOZ_ASSERT_IF(script
->hasBaselineScript(),
321 script
->baselineScript()->hasDebugInstrumentation());
327 void DebugScript::decrementGeneratorObserverCount(JS::GCContext
* gcx
,
329 DebugScript
* debug
= get(script
);
331 MOZ_ASSERT(debug
->generatorObserverCount
> 0);
333 debug
->generatorObserverCount
--;
335 if (!debug
->needed()) {
336 DebugAPI::removeDebugScript(gcx
, script
);
340 void DebugScript::trace(JSTracer
* trc
) {
341 for (size_t i
= 0; i
< codeLength
; i
++) {
342 JSBreakpointSite
* site
= breakpoints
[i
];
350 void DebugAPI::removeDebugScript(JS::GCContext
* gcx
, JSScript
* script
) {
351 if (script
->hasDebugScript()) {
352 if (IsAboutToBeFinalizedUnbarriered(script
)) {
353 // The script is dying and all breakpoint data will be cleaned up.
357 DebugScriptMap
* map
= script
->zone()->debugScriptMap
;
359 DebugScriptMap::Ptr p
= map
->lookupUnbarriered(script
);
362 script
->setHasDebugScript(false);
364 // The DebugScript will be destroyed at the next GC when its owning
365 // DebugScriptObject dies.
369 void DebugScript::delete_(JS::GCContext
* gcx
, DebugScriptObject
* owner
) {
370 for (size_t i
= 0; i
< codeLength
; i
++) {
371 JSBreakpointSite
* site
= breakpoints
[i
];
377 gcx
->free_(owner
, this, allocSize(codeLength
), MemoryUse::ScriptDebugScript
);
380 #ifdef JSGC_HASH_TABLE_CHECKS
382 void DebugAPI::checkDebugScriptAfterMovingGC(DebugScript
* ds
) {
383 for (uint32_t i
= 0; i
< ds
->numSites
; i
++) {
384 JSBreakpointSite
* site
= ds
->breakpoints
[i
];
386 CheckGCThingAfterMovingGC(site
->script
.get());
390 #endif // JSGC_HASH_TABLE_CHECKS
393 bool DebugAPI::stepModeEnabledSlow(JSScript
* script
) {
394 return DebugScript::get(script
)->stepperCount
> 0;
398 bool DebugAPI::hasBreakpointsAtSlow(JSScript
* script
, jsbytecode
* pc
) {
399 JSBreakpointSite
* site
= DebugScript::getBreakpointSite(script
, pc
);
404 void DebugAPI::traceDebugScriptMap(JSTracer
* trc
, DebugScriptMap
* map
) {
409 void DebugAPI::deleteDebugScriptMap(DebugScriptMap
* map
) { js_delete(map
); }