summaryrefslogtreecommitdiffstats
path: root/js/src/debugger/DebugScript.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/debugger/DebugScript.cpp411
1 files changed, 411 insertions, 0 deletions
diff --git a/js/src/debugger/DebugScript.cpp b/js/src/debugger/DebugScript.cpp
new file mode 100644
index 0000000000..610784c228
--- /dev/null
+++ b/js/src/debugger/DebugScript.cpp
@@ -0,0 +1,411 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "debugger/DebugScript.h"
+
+#include "mozilla/Assertions.h" // for AssertionConditionType
+#include "mozilla/HashTable.h" // for HashMapEntry, HashTable<>::Ptr, HashMap
+#include "mozilla/UniquePtr.h" // for UniquePtr
+
+#include <utility> // for std::move
+
+#include "debugger/DebugAPI.h" // for DebugAPI
+#include "debugger/Debugger.h" // for JSBreakpointSite, Breakpoint
+#include "gc/Cell.h" // for TenuredCell
+#include "gc/GCContext.h" // for JS::GCContext
+#include "gc/GCEnum.h" // for MemoryUse, MemoryUse::BreakpointSite
+#include "gc/Marking.h" // for IsAboutToBeFinalized
+#include "gc/Zone.h" // for Zone
+#include "gc/ZoneAllocator.h" // for AddCellMemory
+#include "jit/BaselineJIT.h" // for BaselineScript
+#include "vm/BytecodeIterator.h" // for AllBytecodesIterable
+#include "vm/JSContext.h" // for JSContext
+#include "vm/JSScript.h" // for JSScript, DebugScriptMap
+#include "vm/NativeObject.h" // for NativeObject
+#include "vm/Realm.h" // for Realm, AutoRealm
+#include "vm/Runtime.h" // for ReportOutOfMemory
+#include "vm/Stack.h" // for ActivationIterator, Activation
+
+#include "gc/GC-inl.h" // for ZoneCellIter
+#include "gc/GCContext-inl.h" // for JS::GCContext::free_
+#include "gc/Marking-inl.h" // for CheckGCThingAfterMovingGC
+#include "gc/WeakMap-inl.h" // for WeakMap::remove
+#include "vm/BytecodeIterator-inl.h" // for AllBytecodesIterable
+#include "vm/JSContext-inl.h" // for JSContext::check
+#include "vm/JSObject-inl.h" // for NewObjectWithGivenProto
+#include "vm/JSScript-inl.h" // for JSScript::hasBaselineScript
+#include "vm/Realm-inl.h" // for AutoRealm::AutoRealm
+
+namespace js {
+
+const JSClass DebugScriptObject::class_ = {
+ "DebugScriptObject",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE,
+ &classOps_, JS_NULL_CLASS_SPEC};
+
+const JSClassOps DebugScriptObject::classOps_ = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ DebugScriptObject::finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ DebugScriptObject::trace, // trace
+};
+
+/* static */
+DebugScriptObject* DebugScriptObject::create(JSContext* cx,
+ UniqueDebugScript debugScript,
+ size_t nbytes) {
+ auto* object = NewObjectWithGivenProto<DebugScriptObject>(cx, nullptr);
+ if (!object) {
+ return nullptr;
+ }
+
+ object->initReservedSlot(ScriptSlot, PrivateValue(debugScript.release()));
+ AddCellMemory(object, nbytes, MemoryUse::ScriptDebugScript);
+
+ return object;
+}
+
+DebugScript* DebugScriptObject::debugScript() const {
+ return maybePtrFromReservedSlot<DebugScript>(ScriptSlot);
+}
+
+/* static */
+void DebugScriptObject::trace(JSTracer* trc, JSObject* obj) {
+ DebugScript* debugScript = obj->as<DebugScriptObject>().debugScript();
+ if (debugScript) {
+ debugScript->trace(trc);
+ }
+}
+
+/* static */
+void DebugScriptObject::finalize(JS::GCContext* gcx, JSObject* obj) {
+ DebugScriptObject* object = &obj->as<DebugScriptObject>();
+ DebugScript* debugScript = object->debugScript();
+ if (debugScript) {
+ debugScript->delete_(gcx, object);
+ }
+}
+
+/* static */
+DebugScript* DebugScript::get(JSScript* script) {
+ MOZ_ASSERT(script->hasDebugScript());
+ DebugScriptMap* map = script->zone()->debugScriptMap;
+ MOZ_ASSERT(map);
+ DebugScriptMap::Ptr p = map->lookupUnbarriered(script);
+ MOZ_ASSERT(p);
+ return p->value().get()->as<DebugScriptObject>().debugScript();
+}
+
+/* static */
+DebugScript* DebugScript::getOrCreate(JSContext* cx, HandleScript script) {
+ cx->check(script);
+
+ if (script->hasDebugScript()) {
+ return get(script);
+ }
+
+ size_t nbytes = allocSize(script->length());
+ UniqueDebugScript debug(
+ reinterpret_cast<DebugScript*>(cx->pod_calloc<uint8_t>(nbytes)));
+ if (!debug) {
+ return nullptr;
+ }
+
+ debug->codeLength = script->length();
+
+ Rooted<DebugScriptObject*> object(
+ cx, DebugScriptObject::create(cx, std::move(debug), nbytes));
+ if (!object) {
+ return nullptr;
+ }
+
+ /* Create zone's debugScriptMap if necessary. */
+ Zone* zone = script->zone();
+ MOZ_ASSERT(cx->zone() == zone);
+ if (!zone->debugScriptMap) {
+ DebugScriptMap* map = cx->new_<DebugScriptMap>(cx);
+ if (!map) {
+ return nullptr;
+ }
+
+ zone->debugScriptMap = map;
+ }
+
+ MOZ_ASSERT(script->hasBytecode());
+
+ if (!zone->debugScriptMap->putNew(script.get(), object.get())) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ // It is safe to set this: we can't fail after this point.
+ script->setHasDebugScript(true);
+
+ /*
+ * Ensure that any Interpret() instances running on this script have
+ * interrupts enabled. The interrupts must stay enabled until the
+ * debug state is destroyed.
+ */
+ for (ActivationIterator iter(cx); !iter.done(); ++iter) {
+ if (iter->isInterpreter()) {
+ iter->asInterpreter()->enableInterruptsIfRunning(script);
+ }
+ }
+
+ return object->debugScript();
+}
+
+/* static */
+JSBreakpointSite* DebugScript::getBreakpointSite(JSScript* script,
+ jsbytecode* pc) {
+ uint32_t offset = script->pcToOffset(pc);
+ return script->hasDebugScript() ? get(script)->breakpoints[offset] : nullptr;
+}
+
+/* static */
+JSBreakpointSite* DebugScript::getOrCreateBreakpointSite(JSContext* cx,
+ HandleScript script,
+ jsbytecode* pc) {
+ AutoRealm ar(cx, script);
+
+ DebugScript* debug = getOrCreate(cx, script);
+ if (!debug) {
+ return nullptr;
+ }
+
+ JSBreakpointSite*& site = debug->breakpoints[script->pcToOffset(pc)];
+
+ if (!site) {
+ site = cx->new_<JSBreakpointSite>(script, pc);
+ if (!site) {
+ return nullptr;
+ }
+ debug->numSites++;
+ AddCellMemory(script, sizeof(JSBreakpointSite), MemoryUse::BreakpointSite);
+
+ if (script->hasBaselineScript()) {
+ script->baselineScript()->toggleDebugTraps(script, pc);
+ }
+ }
+
+ return site;
+}
+
+/* static */
+void DebugScript::destroyBreakpointSite(JS::GCContext* gcx, JSScript* script,
+ jsbytecode* pc) {
+ DebugScript* debug = get(script);
+ JSBreakpointSite*& site = debug->breakpoints[script->pcToOffset(pc)];
+ MOZ_ASSERT(site);
+ MOZ_ASSERT(site->isEmpty());
+
+ site->delete_(gcx);
+ site = nullptr;
+
+ debug->numSites--;
+ if (!debug->needed()) {
+ DebugAPI::removeDebugScript(gcx, script);
+ }
+
+ if (script->hasBaselineScript()) {
+ script->baselineScript()->toggleDebugTraps(script, pc);
+ }
+}
+
+/* static */
+void DebugScript::clearBreakpointsIn(JS::GCContext* gcx, JSScript* script,
+ Debugger* dbg, JSObject* handler) {
+ MOZ_ASSERT(script);
+ // Breakpoints hold wrappers in the script's compartment for the handler. Make
+ // sure we don't try to search for the unwrapped handler.
+ MOZ_ASSERT_IF(handler, script->compartment() == handler->compartment());
+
+ if (!script->hasDebugScript()) {
+ return;
+ }
+
+ AllBytecodesIterable iter(script);
+ for (BytecodeLocation loc : iter) {
+ JSBreakpointSite* site = getBreakpointSite(script, loc.toRawBytecode());
+ if (site) {
+ Breakpoint* nextbp;
+ for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
+ nextbp = bp->nextInSite();
+ if ((!dbg || bp->debugger == dbg) &&
+ (!handler || bp->getHandler() == handler)) {
+ bp->remove(gcx);
+ }
+ }
+ }
+ }
+}
+
+#ifdef DEBUG
+/* static */
+uint32_t DebugScript::getStepperCount(JSScript* script) {
+ return script->hasDebugScript() ? get(script)->stepperCount : 0;
+}
+#endif // DEBUG
+
+/* static */
+bool DebugScript::incrementStepperCount(JSContext* cx, HandleScript script) {
+ cx->check(script);
+ MOZ_ASSERT(cx->realm()->isDebuggee());
+
+ AutoRealm ar(cx, script);
+
+ DebugScript* debug = getOrCreate(cx, script);
+ if (!debug) {
+ return false;
+ }
+
+ debug->stepperCount++;
+
+ if (debug->stepperCount == 1) {
+ if (script->hasBaselineScript()) {
+ script->baselineScript()->toggleDebugTraps(script, nullptr);
+ }
+ }
+
+ return true;
+}
+
+/* static */
+void DebugScript::decrementStepperCount(JS::GCContext* gcx, JSScript* script) {
+ DebugScript* debug = get(script);
+ MOZ_ASSERT(debug);
+ MOZ_ASSERT(debug->stepperCount > 0);
+
+ debug->stepperCount--;
+
+ if (debug->stepperCount == 0) {
+ if (script->hasBaselineScript()) {
+ script->baselineScript()->toggleDebugTraps(script, nullptr);
+ }
+
+ if (!debug->needed()) {
+ DebugAPI::removeDebugScript(gcx, script);
+ }
+ }
+}
+
+/* static */
+bool DebugScript::incrementGeneratorObserverCount(JSContext* cx,
+ HandleScript script) {
+ cx->check(script);
+ MOZ_ASSERT(cx->realm()->isDebuggee());
+
+ AutoRealm ar(cx, script);
+
+ DebugScript* debug = getOrCreate(cx, script);
+ if (!debug) {
+ return false;
+ }
+
+ debug->generatorObserverCount++;
+
+ // It is our caller's responsibility, before bumping the generator observer
+ // count, to make sure that the baseline code includes the necessary
+ // JSOp::AfterYield instrumentation by calling
+ // {ensure,update}ExecutionObservabilityOfScript.
+ MOZ_ASSERT_IF(script->hasBaselineScript(),
+ script->baselineScript()->hasDebugInstrumentation());
+
+ return true;
+}
+
+/* static */
+void DebugScript::decrementGeneratorObserverCount(JS::GCContext* gcx,
+ JSScript* script) {
+ DebugScript* debug = get(script);
+ MOZ_ASSERT(debug);
+ MOZ_ASSERT(debug->generatorObserverCount > 0);
+
+ debug->generatorObserverCount--;
+
+ if (!debug->needed()) {
+ DebugAPI::removeDebugScript(gcx, script);
+ }
+}
+
+void DebugScript::trace(JSTracer* trc) {
+ for (size_t i = 0; i < codeLength; i++) {
+ JSBreakpointSite* site = breakpoints[i];
+ if (site) {
+ site->trace(trc);
+ }
+ }
+}
+
+/* static */
+void DebugAPI::removeDebugScript(JS::GCContext* gcx, JSScript* script) {
+ if (script->hasDebugScript()) {
+ if (IsAboutToBeFinalizedUnbarriered(script)) {
+ // The script is dying and all breakpoint data will be cleaned up.
+ return;
+ }
+
+ DebugScriptMap* map = script->zone()->debugScriptMap;
+ MOZ_ASSERT(map);
+ DebugScriptMap::Ptr p = map->lookupUnbarriered(script);
+ MOZ_ASSERT(p);
+ map->remove(p);
+ script->setHasDebugScript(false);
+
+ // The DebugScript will be destroyed at the next GC when its owning
+ // DebugScriptObject dies.
+ }
+}
+
+void DebugScript::delete_(JS::GCContext* gcx, DebugScriptObject* owner) {
+ for (size_t i = 0; i < codeLength; i++) {
+ JSBreakpointSite* site = breakpoints[i];
+ if (site) {
+ site->delete_(gcx);
+ }
+ }
+
+ gcx->free_(owner, this, allocSize(codeLength), MemoryUse::ScriptDebugScript);
+}
+
+#ifdef JSGC_HASH_TABLE_CHECKS
+/* static */
+void DebugAPI::checkDebugScriptAfterMovingGC(DebugScript* ds) {
+ for (uint32_t i = 0; i < ds->numSites; i++) {
+ JSBreakpointSite* site = ds->breakpoints[i];
+ if (site) {
+ CheckGCThingAfterMovingGC(site->script.get());
+ }
+ }
+}
+#endif // JSGC_HASH_TABLE_CHECKS
+
+/* static */
+bool DebugAPI::stepModeEnabledSlow(JSScript* script) {
+ return DebugScript::get(script)->stepperCount > 0;
+}
+
+/* static */
+bool DebugAPI::hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc) {
+ JSBreakpointSite* site = DebugScript::getBreakpointSite(script, pc);
+ return !!site;
+}
+
+/* static */
+void DebugAPI::traceDebugScriptMap(JSTracer* trc, DebugScriptMap* map) {
+ map->trace(trc);
+}
+
+/* static */
+void DebugAPI::deleteDebugScriptMap(DebugScriptMap* map) { js_delete(map); }
+
+} // namespace js