Mypal/js/src/vm/DebuggerMemory.cpp

461 lines
15 KiB
C++
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/* -*- 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
};