summaryrefslogtreecommitdiffstats
path: root/js/src/vm/DebuggerMemory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/DebuggerMemory.cpp')
-rw-r--r--js/src/vm/DebuggerMemory.cpp460
1 files changed, 460 insertions, 0 deletions
diff --git a/js/src/vm/DebuggerMemory.cpp b/js/src/vm/DebuggerMemory.cpp
new file mode 100644
index 000000000..c120a0057
--- /dev/null
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -0,0 +1,460 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "vm/DebuggerMemory.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Move.h"
+#include "mozilla/Vector.h"
+
+#include <stdlib.h>
+
+#include "jsalloc.h"
+#include "jscntxt.h"
+#include "jscompartment.h"
+
+#include "builtin/MapObject.h"
+#include "gc/Marking.h"
+#include "js/Debug.h"
+#include "js/TracingAPI.h"
+#include "js/UbiNode.h"
+#include "js/UbiNodeCensus.h"
+#include "js/Utility.h"
+#include "vm/Debugger.h"
+#include "vm/GlobalObject.h"
+#include "vm/SavedStacks.h"
+
+#include "vm/Debugger-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using JS::ubi::BreadthFirst;
+using JS::ubi::Edge;
+using JS::ubi::Node;
+
+using mozilla::Forward;
+using mozilla::Maybe;
+using mozilla::Move;
+using mozilla::Nothing;
+
+/* static */ DebuggerMemory*
+DebuggerMemory::create(JSContext* cx, Debugger* dbg)
+{
+ Value memoryProtoValue = dbg->object->getReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO);
+ RootedObject memoryProto(cx, &memoryProtoValue.toObject());
+ RootedNativeObject memory(cx, NewNativeObjectWithGivenProto(cx, &class_, memoryProto));
+ if (!memory)
+ return nullptr;
+
+ dbg->object->setReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_INSTANCE, ObjectValue(*memory));
+ memory->setReservedSlot(JSSLOT_DEBUGGER, ObjectValue(*dbg->object));
+
+ return &memory->as<DebuggerMemory>();
+}
+
+Debugger*
+DebuggerMemory::getDebugger()
+{
+ const Value& dbgVal = getReservedSlot(JSSLOT_DEBUGGER);
+ return Debugger::fromJSObject(&dbgVal.toObject());
+}
+
+/* static */ bool
+DebuggerMemory::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
+ "Debugger.Source");
+ return false;
+}
+
+/* static */ const Class DebuggerMemory::class_ = {
+ "Memory",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_COUNT)
+};
+
+/* static */ DebuggerMemory*
+DebuggerMemory::checkThis(JSContext* cx, CallArgs& args, const char* fnName)
+{
+ const Value& thisValue = args.thisv();
+
+ if (!thisValue.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+ InformalValueTypeName(thisValue));
+ return nullptr;
+ }
+
+ JSObject& thisObject = thisValue.toObject();
+ if (!thisObject.is<DebuggerMemory>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
+ class_.name, fnName, thisObject.getClass()->name);
+ return nullptr;
+ }
+
+ // Check for Debugger.Memory.prototype, which has the same class as
+ // Debugger.Memory instances, however doesn't actually represent an instance
+ // of Debugger.Memory. It is the only object that is<DebuggerMemory>() but
+ // doesn't have a Debugger instance.
+ if (thisObject.as<DebuggerMemory>().getReservedSlot(JSSLOT_DEBUGGER).isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
+ class_.name, fnName, "prototype object");
+ return nullptr;
+ }
+
+ return &thisObject.as<DebuggerMemory>();
+}
+
+/**
+ * Get the |DebuggerMemory*| from the current this value and handle any errors
+ * that might occur therein.
+ *
+ * These parameters must already exist when calling this macro:
+ * - JSContext* cx
+ * - unsigned argc
+ * - Value* vp
+ * - const char* fnName
+ * These parameters will be defined after calling this macro:
+ * - CallArgs args
+ * - DebuggerMemory* memory (will be non-null)
+ */
+#define THIS_DEBUGGER_MEMORY(cx, argc, vp, fnName, args, memory) \
+ CallArgs args = CallArgsFromVp(argc, vp); \
+ Rooted<DebuggerMemory*> memory(cx, checkThis(cx, args, fnName)); \
+ if (!memory) \
+ return false
+
+static bool
+undefined(CallArgs& args)
+{
+ args.rval().setUndefined();
+ return true;
+}
+
+/* static */ bool
+DebuggerMemory::setTrackingAllocationSites(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(set trackingAllocationSites)", args, memory);
+ if (!args.requireAtLeast(cx, "(set trackingAllocationSites)", 1))
+ return false;
+
+ Debugger* dbg = memory->getDebugger();
+ bool enabling = ToBoolean(args[0]);
+
+ if (enabling == dbg->trackingAllocationSites)
+ return undefined(args);
+
+ dbg->trackingAllocationSites = enabling;
+
+ if (!dbg->enabled)
+ return undefined(args);
+
+ if (enabling) {
+ if (!dbg->addAllocationsTrackingForAllDebuggees(cx)) {
+ dbg->trackingAllocationSites = false;
+ return false;
+ }
+ } else {
+ dbg->removeAllocationsTrackingForAllDebuggees();
+ }
+
+ return undefined(args);
+}
+
+/* static */ bool
+DebuggerMemory::getTrackingAllocationSites(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(get trackingAllocationSites)", args, memory);
+ args.rval().setBoolean(memory->getDebugger()->trackingAllocationSites);
+ return true;
+}
+
+/* static */ bool
+DebuggerMemory::drainAllocationsLog(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "drainAllocationsLog", args, memory);
+ Debugger* dbg = memory->getDebugger();
+
+ if (!dbg->trackingAllocationSites) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_TRACKING_ALLOCATIONS,
+ "drainAllocationsLog");
+ return false;
+ }
+
+ size_t length = dbg->allocationsLog.length();
+
+ RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!result)
+ return false;
+ result->ensureDenseInitializedLength(cx, 0, length);
+
+ for (size_t i = 0; i < length; i++) {
+ RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!obj)
+ return false;
+
+ // Don't pop the AllocationsLogEntry yet. The queue's links are followed
+ // by the GC to find the AllocationsLogEntry, but are not barriered, so
+ // we must edit them with great care. Use the queue entry in place, and
+ // then pop and delete together.
+ Debugger::AllocationsLogEntry& entry = dbg->allocationsLog.front();
+
+ RootedValue frame(cx, ObjectOrNullValue(entry.frame));
+ if (!DefineProperty(cx, obj, cx->names().frame, frame))
+ return false;
+
+ RootedValue timestampValue(cx, NumberValue(entry.when));
+ if (!DefineProperty(cx, obj, cx->names().timestamp, timestampValue))
+ return false;
+
+ RootedString className(cx, Atomize(cx, entry.className, strlen(entry.className)));
+ if (!className)
+ return false;
+ RootedValue classNameValue(cx, StringValue(className));
+ if (!DefineProperty(cx, obj, cx->names().class_, classNameValue))
+ return false;
+
+ RootedValue ctorName(cx, NullValue());
+ if (entry.ctorName)
+ ctorName.setString(entry.ctorName);
+ if (!DefineProperty(cx, obj, cx->names().constructor, ctorName))
+ return false;
+
+ RootedValue size(cx, NumberValue(entry.size));
+ if (!DefineProperty(cx, obj, cx->names().size, size))
+ return false;
+
+ RootedValue inNursery(cx, BooleanValue(entry.inNursery));
+ if (!DefineProperty(cx, obj, cx->names().inNursery, inNursery))
+ return false;
+
+ result->setDenseElement(i, ObjectValue(*obj));
+
+ // Pop the front queue entry, and delete it immediately, so that the GC
+ // sees the AllocationsLogEntry's HeapPtr barriers run atomically with
+ // the change to the graph (the queue link).
+ if (!dbg->allocationsLog.popFront()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ dbg->allocationsLogOverflowed = false;
+ args.rval().setObject(*result);
+ return true;
+}
+
+/* static */ bool
+DebuggerMemory::getMaxAllocationsLogLength(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(get maxAllocationsLogLength)", args, memory);
+ args.rval().setInt32(memory->getDebugger()->maxAllocationsLogLength);
+ return true;
+}
+
+/* static */ bool
+DebuggerMemory::setMaxAllocationsLogLength(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(set maxAllocationsLogLength)", args, memory);
+ if (!args.requireAtLeast(cx, "(set maxAllocationsLogLength)", 1))
+ return false;
+
+ int32_t max;
+ if (!ToInt32(cx, args[0], &max))
+ return false;
+
+ if (max < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
+ "(set maxAllocationsLogLength)'s parameter",
+ "not a positive integer");
+ return false;
+ }
+
+ Debugger* dbg = memory->getDebugger();
+ dbg->maxAllocationsLogLength = max;
+
+ while (dbg->allocationsLog.length() > dbg->maxAllocationsLogLength) {
+ if (!dbg->allocationsLog.popFront()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/* static */ bool
+DebuggerMemory::getAllocationSamplingProbability(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(get allocationSamplingProbability)", args, memory);
+ args.rval().setDouble(memory->getDebugger()->allocationSamplingProbability);
+ return true;
+}
+
+/* static */ bool
+DebuggerMemory::setAllocationSamplingProbability(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(set allocationSamplingProbability)", args, memory);
+ if (!args.requireAtLeast(cx, "(set allocationSamplingProbability)", 1))
+ return false;
+
+ double probability;
+ if (!ToNumber(cx, args[0], &probability))
+ return false;
+
+ // Careful! This must also reject NaN.
+ if (!(0.0 <= probability && probability <= 1.0)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
+ "(set allocationSamplingProbability)'s parameter",
+ "not a number between 0 and 1");
+ return false;
+ }
+
+ Debugger* dbg = memory->getDebugger();
+ if (dbg->allocationSamplingProbability != probability) {
+ dbg->allocationSamplingProbability = probability;
+
+ // If this is a change any debuggees would observe, have all debuggee
+ // compartments recompute their sampling probabilities.
+ if (dbg->enabled && dbg->trackingAllocationSites) {
+ for (auto r = dbg->debuggees.all(); !r.empty(); r.popFront())
+ r.front()->compartment()->chooseAllocationSamplingProbability();
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/* static */ bool
+DebuggerMemory::getAllocationsLogOverflowed(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(get allocationsLogOverflowed)", args, memory);
+ args.rval().setBoolean(memory->getDebugger()->allocationsLogOverflowed);
+ return true;
+}
+
+/* static */ bool
+DebuggerMemory::getOnGarbageCollection(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(get onGarbageCollection)", args, memory);
+ return Debugger::getHookImpl(cx, args, *memory->getDebugger(), Debugger::OnGarbageCollection);
+}
+
+/* static */ bool
+DebuggerMemory::setOnGarbageCollection(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "(set onGarbageCollection)", args, memory);
+ return Debugger::setHookImpl(cx, args, *memory->getDebugger(), Debugger::OnGarbageCollection);
+}
+
+
+/* Debugger.Memory.prototype.takeCensus */
+
+JS_PUBLIC_API(void)
+JS::dbg::SetDebuggerMallocSizeOf(JSContext* cx, mozilla::MallocSizeOf mallocSizeOf)
+{
+ cx->debuggerMallocSizeOf = mallocSizeOf;
+}
+
+JS_PUBLIC_API(mozilla::MallocSizeOf)
+JS::dbg::GetDebuggerMallocSizeOf(JSContext* cx)
+{
+ return cx->debuggerMallocSizeOf;
+}
+
+using JS::ubi::Census;
+using JS::ubi::CountTypePtr;
+using JS::ubi::CountBasePtr;
+
+// The takeCensus function works in three phases:
+//
+// 1) We examine the 'breakdown' property of our 'options' argument, and
+// use that to build a CountType tree.
+//
+// 2) We create a count node for the root of our CountType tree, and then walk
+// the heap, counting each node we find, expanding our tree of counts as we
+// go.
+//
+// 3) We walk the tree of counts and produce JavaScript objects reporting the
+// accumulated results.
+bool
+DebuggerMemory::takeCensus(JSContext* cx, unsigned argc, Value* vp)
+{
+ THIS_DEBUGGER_MEMORY(cx, argc, vp, "Debugger.Memory.prototype.census", args, memory);
+
+ Census census(cx);
+ if (!census.init())
+ return false;
+ CountTypePtr rootType;
+
+ RootedObject options(cx);
+ if (args.get(0).isObject())
+ options = &args[0].toObject();
+
+ if (!JS::ubi::ParseCensusOptions(cx, census, options, rootType))
+ return false;
+
+ JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
+ if (!rootCount)
+ return false;
+ JS::ubi::CensusHandler handler(census, rootCount, cx->runtime()->debuggerMallocSizeOf);
+
+ Debugger* dbg = memory->getDebugger();
+ RootedObject dbgObj(cx, dbg->object);
+
+ // Populate our target set of debuggee zones.
+ for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
+ if (!census.targetZones.put(r.front()->zone()))
+ return false;
+ }
+
+ {
+ Maybe<JS::AutoCheckCannotGC> maybeNoGC;
+ JS::ubi::RootList rootList(cx, maybeNoGC);
+ if (!rootList.init(dbgObj)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ JS::ubi::CensusTraversal traversal(cx, handler, maybeNoGC.ref());
+ if (!traversal.init()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ traversal.wantNames = false;
+
+ if (!traversal.addStart(JS::ubi::Node(&rootList)) ||
+ !traversal.traverse())
+ {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ return handler.report(cx, args.rval());
+}
+
+
+/* Debugger.Memory property and method tables. */
+
+
+/* static */ const JSPropertySpec DebuggerMemory::properties[] = {
+ JS_PSGS("trackingAllocationSites", getTrackingAllocationSites, setTrackingAllocationSites, 0),
+ JS_PSGS("maxAllocationsLogLength", getMaxAllocationsLogLength, setMaxAllocationsLogLength, 0),
+ JS_PSGS("allocationSamplingProbability", getAllocationSamplingProbability, setAllocationSamplingProbability, 0),
+ JS_PSG("allocationsLogOverflowed", getAllocationsLogOverflowed, 0),
+
+ JS_PSGS("onGarbageCollection", getOnGarbageCollection, setOnGarbageCollection, 0),
+ JS_PS_END
+};
+
+/* static */ const JSFunctionSpec DebuggerMemory::methods[] = {
+ JS_FN("drainAllocationsLog", DebuggerMemory::drainAllocationsLog, 0, 0),
+ JS_FN("takeCensus", takeCensus, 0, 0),
+ JS_FS_END
+};