Bug 993085 - Part 1: Add the Debugger.Memory.prototype.trackingAllocationSites accessor property r=jimb

This commit is contained in:
Nick Fitzgerald 2014-07-21 18:16:13 -07:00
parent 7aa4c7c841
commit b4cf06074c
20 changed files with 494 additions and 78 deletions

View File

@ -362,6 +362,13 @@ class MOZ_STACK_CLASS CallArgs : public detail::CallArgsBase<detail::IncludeUsed
return args;
}
public:
/*
* Returns true if there are at least |required| arguments passed in. If
* false, it reports an error message on the context.
*/
bool requireAtLeast(JSContext *cx, const char *fnname, unsigned required);
};
MOZ_ALWAYS_INLINE CallArgs

View File

@ -0,0 +1,23 @@
# `Debugger.Memory`
If `dbg` is a `Debugger` instance, then `dbg.memory` is an instance of
`Debugger.Memory` whose methods and accessors operate on `dbg`. This class
exists only to hold member functions and accessors related to memory analysis,
keeping them separate from other `Debugger` facilities.
## Accessor Properties of the `Debugger.Memory.prototype` Object
<code id="trackingallocationsites">trackingAllocationSites</code>
: A boolean value indicating whether this `Debugger.Memory` instance is
capturing the JavaScript execution stack when each Object is allocated. This
accessor property has both a getter and setter: assigning to it enables or
disables the allocation site tracking. Reading the accessor produces `true`
if the Debugger is capturing stacks for Object allocations, and `false`
otherwise. Allocation site tracking is initially disabled in a new Debugger.
Assignment is fallible: if the Debugger cannot track allocation sites, it
throws an `Error` instance.
You can retrieve the allocation site for a given object with the
[`Debugger.Object.prototype.allocationSite`][allocation-site] accessor
property.

View File

@ -199,6 +199,11 @@ from its prototype:
wrapper's global, not the wrapped object's global. The result refers to
the global directly, not via a wrapper.
<code id="allocationsite">allocationSite</code>
: If [object allocation site tracking][tracking-allocs] was enabled when this
`Debugger.Object`'s referent was allocated, return the
[JavaScript execution stack][saved-frame] captured at the time of the
allocation. Otherwise, return `null`.
## Function Properties of the Debugger.Object prototype

View File

@ -250,6 +250,9 @@ other kinds of objects.
[`Debugger.Object`][object] instance this method returns does hold a strong
reference to the added global.)
If this debugger is [tracking allocation sites][tracking-allocs] and cannot
track allocation sites for <i>global</i>, this method throws an `Error`.
<code>removeDebuggee(<i>global</i>)</code>
: Remove the global object designated by <i>global</i> from this
`Debugger` instance's set of debuggees. Return `undefined`.

View File

@ -29,6 +29,7 @@ markdown Debugger.Frame.md Debugger-API/Debugger.Frame
markdown Debugger.Object.md Debugger-API/Debugger.Object
label 'object' "Debugger.Object"
label 'allocation-site' '#allocationsite' "Debugger.Object: allocationSite"
markdown Debugger.Script.md Debugger-API/Debugger.Script
label 'script' "Debugger.Script"
@ -36,6 +37,10 @@ markdown Debugger.Script.md Debugger-API/Debugger.Script
markdown Debugger.Source.md Debugger-API/Debugger.Source
label 'source' "Debugger.Source"
markdown Debugger.Memory.md Debugger-API/Debugger.Memory
label 'memory' "Debugger.Memory"
label 'tracking-allocs' '#trackingallocationsites' "Debugger.Memory: trackingAllocationSites"
# Images:
RBASE=https://mdn.mozillademos.org/files
resource 'img-shadows' shadows.svg $RBASE/7225/shadows.svg
@ -45,3 +50,4 @@ resource 'img-example-alert' debugger-alert.png $RBASE/7231
# External links:
absolute-label 'protocol' https://wiki.mozilla.org/Remote_Debugging_Protocol "Remote Debugging Protocol"
absolute-label 'saved-frame' https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/SavedFrame "SavedFrame"

View File

@ -9,6 +9,9 @@ the moment, we have:
- `js/src/doc/Debugger`, SpiderMonkey's JavaScript debugging API, commonly
known as `Debugger`.
- `js/src/doc/SavedFrame`, SpiderMonkey's compact representation for captured
call stacks.
and that's it.
To format the documentation, you'll need to install [Pandoc][], a

View File

@ -0,0 +1,33 @@
# `SavedFrame`
A `SavedFrame` instance is a singly linked list of stack frames. It represents a
JavaScript call stack at a past moment of execution. Younger frames hold a
reference to the frames that invoked them. The older tails are shared across
many younger frames.
## Accessor Properties of the `SavedFrame.prototype` Object
`source`
: The source URL for this stack frame, as a string.
`line`
: The line number for this stack frame.
`column`
: The column number for this stack frame.
`functionDisplayName`
: Either SpiderMonkey's inferred name for this stack frame's function, or
`null`.
`parent`
: Either this stack frame's parent stack frame (the next older frame), or
`null` if this is the oldest frame in the captured stack.
## Function Properties of the `SavedFrame.prototype` Object
`toString`
: Return this frame and its parents formatted as a human readable stack trace
string.

View File

@ -0,0 +1,6 @@
### Description of SavedFrame docs: how to format, where to install.
### See js/src/doc/README.md for a description.
base-url https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/
markdown SavedFrame.md SavedFrame

View File

@ -0,0 +1,37 @@
// Test that we can track allocation sites by setting
// Debugger.Memory.prototype.trackingAllocationSites to true and then get the
// allocation site via Debugger.Object.prototype.allocationSite.
const root = newGlobal();
const dbg = new Debugger();
const wrappedRoot = dbg.addDebuggee(root);
assertEq(dbg.memory.trackingAllocationSites, false);
dbg.memory.trackingAllocationSites = true;
assertEq(dbg.memory.trackingAllocationSites, true);
root.eval("(" + function immediate() {
this.tests = [
{ name: "object literal", object: ({}), line: Error().lineNumber },
{ name: "array literal", object: [], line: Error().lineNumber },
{ name: "regexp literal", object: /(two|2)\s*problems/, line: Error().lineNumber },
{ name: "new constructor", object: new function Ctor(){}, line: Error().lineNumber },
{ name: "new Object", object: new Object(), line: Error().lineNumber },
{ name: "new Array", object: new Array(), line: Error().lineNumber },
{ name: "new Date", object: new Date(), line: Error().lineNumber }
];
} + "());");
dbg.memory.trackingAllocationSites = false;
assertEq(dbg.memory.trackingAllocationSites, false);
for (let { name, object, line } of root.tests) {
print("Entering test: " + name);
let wrappedObject = wrappedRoot.makeDebuggeeValue(object);
let allocationSite = wrappedObject.allocationSite;
print("Allocation site: " + allocationSite);
assertEq(allocationSite.line, line);
}

View File

@ -0,0 +1,19 @@
// Test that we don't get allocation sites when nobody has asked for them.
const root = newGlobal();
const dbg = new Debugger();
const wrappedRoot = dbg.addDebuggee(root);
dbg.memory.trackingAllocationSites = true;
root.eval("this.obj = {};");
dbg.memory.trackingAllocationSites = false;
root.eval("this.obj2 = {};");
let wrappedObj = wrappedRoot.makeDebuggeeValue(root.obj);
let allocationSite = wrappedObj.allocationSite;
assertEq(allocationSite != null && typeof allocationSite == "object", true);
let wrappedObj2 = wrappedRoot.makeDebuggeeValue(root.obj2);
let allocationSite2 = wrappedObj2.allocationSite;
assertEq(allocationSite2, null);

View File

@ -0,0 +1,64 @@
// Test that multiple Debuggers behave reasonably. Since we're not keeping a
// per-compartment count of how many Debuggers have requested allocation
// tracking, assert that attempts to request allocation tracking from multiple
// debuggers throws.
load(libdir + "asserts.js");
let root1 = newGlobal();
let root2 = newGlobal();
let dbg1 = new Debugger();
let dbg2 = new Debugger();
let d1r1 = dbg1.addDebuggee(root1);
let d2r1 = dbg2.addDebuggee(root1);
let wrappedObj, allocationSite;
function isTrackingAllocations(global, dbgObj) {
const site = dbgObj.makeDebuggeeValue(global.eval("({})")).allocationSite;
if (site) {
assertEq(typeof site, "object");
}
return !!site;
}
// Can't track allocations if a different debugger is already tracking them.
dbg1.memory.trackingAllocationSites = true;
assertThrowsInstanceOf(() => dbg2.memory.trackingAllocationSites = true,
Error);
// Removing root as a debuggee from dbg1 should disable the allocation hook.
dbg1.removeDebuggee(root1);
assertEq(isTrackingAllocations(root1, d1r1), false);
// Tracking allocations in dbg2 should work now that dbg1 isn't debugging root1.
dbg2.memory.trackingAllocationSites = true;
assertEq(isTrackingAllocations(root1, d2r1), true);
// Adding root back as a debuggee in dbg1 should fail now because it will
// attempt to track allocations in root, but dbg2 is already doing that.
assertThrowsInstanceOf(() => dbg1.addDebuggee(root1),
Error);
assertEq(dbg1.hasDebuggee(root1), false);
// Adding a new debuggee to a debugger that is tracking allocations should
// enable the hook for the new debuggee.
dbg2.removeDebuggee(root1);
d1r1 = dbg1.addDebuggee(root1);
assertEq(isTrackingAllocations(root1, d1r1), true);
// Setting trackingAllocationSites to true should throw if the debugger cannot
// install the allocation hooks for *every* debuggee.
dbg1.memory.trackingAllocationSites = true;
dbg1.addDebuggee(root1);
dbg2.memory.trackingAllocationSites = false;
let d2r2 = dbg2.addDebuggee(root2);
dbg2.addDebuggee(root1);
assertThrowsInstanceOf(() => dbg2.memory.trackingAllocationSites = true,
Error);
// And after it throws, its trackingAllocationSites accessor should reflect that
// allocation site tracking is still disabled in that Debugger.
assertEq(isTrackingAllocations(root2, d2r2), false);

View File

@ -237,7 +237,7 @@ MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE, 183, 0, JSEXN_TYPEERR, "invalid transferab
MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE, 184, 0, JSEXN_TYPEERR, "duplicate transferable for structured clone")
MSG_DEF(JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE, 185, 0, JSEXN_TYPEERR, "proxy can't report an extensible object as non-extensible")
MSG_DEF(JSMSG_SYMBOL_TO_STRING, 186, 0, JSEXN_TYPEERR, "can't convert symbol to string")
MSG_DEF(JSMSG_UNUSED187, 187, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET, 187, 0, JSEXN_ERR, "Cannot track object allocation, because other tools are already doing so")
MSG_DEF(JSMSG_INCOMPATIBLE_METHOD, 188, 3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}")
MSG_DEF(JSMSG_SYMBOL_TO_PRIMITIVE, 189, 0, JSEXN_TYPEERR, "can't convert symbol object to primitive")
MSG_DEF(JSMSG_UNUSED190, 190, 0, JSEXN_NONE, "")

View File

@ -24,6 +24,7 @@
#include "jscntxt.h"
#include "jsdate.h"
#include "jsexn.h"
#include "jsfriendapi.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsiter.h"
@ -115,6 +116,19 @@ using js::frontend::Parser;
JS_STATIC_ASSERT((jschar)-1 > 0);
JS_STATIC_ASSERT(sizeof(jschar) == 2);
bool
JS::CallArgs::requireAtLeast(JSContext *cx, const char *fnname, unsigned required) {
if (length() < required) {
char numArgsStr[40];
JS_snprintf(numArgsStr, sizeof numArgsStr, "%u", required - 1);
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
fnname, numArgsStr, required == 2 ? "" : "s");
return false;
}
return true;
}
JS_PUBLIC_API(int64_t)
JS_Now()
{

View File

@ -345,6 +345,9 @@ struct JSCompartment
bool hasObjectMetadataCallback() const { return objectMetadataCallback; }
void setObjectMetadataCallback(js::ObjectMetadataCallback callback);
void forgetObjectMetadataCallback() {
objectMetadataCallback = nullptr;
}
bool callObjectMetadataCallback(JSContext *cx, JSObject **obj) const {
return objectMetadataCallback(cx, obj);
}

View File

@ -22,4 +22,11 @@ js::Debugger::onLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok)
return ok;
}
/* static */ inline js::Debugger *
js::Debugger::fromJSObject(JSObject *obj)
{
JS_ASSERT(js::GetObjectClass(obj) == &jsclass);
return (Debugger *) obj->getPrivate();
}
#endif /* vm_Debugger_inl_h */

View File

@ -86,19 +86,6 @@ enum {
/*** Utils ***************************************************************************************/
static bool
ReportMoreArgsNeeded(JSContext *cx, const char *name, unsigned required)
{
JS_ASSERT(required > 0);
JS_ASSERT(required <= 10);
char s[2];
s[0] = '0' + (required - 1);
s[1] = '\0';
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
name, s, required == 2 ? "" : "s");
return false;
}
static inline bool
EnsureFunctionHasScript(JSContext *cx, HandleFunction fun)
{
@ -118,14 +105,8 @@ GetOrCreateFunctionScript(JSContext *cx, HandleFunction fun)
return fun->nonLazyScript();
}
#define REQUIRE_ARGC(name, n) \
JS_BEGIN_MACRO \
if (argc < (n)) \
return ReportMoreArgsNeeded(cx, name, n); \
JS_END_MACRO
static bool
ReportObjectRequired(JSContext *cx)
bool
js::ReportObjectRequired(JSContext *cx)
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
return false;
@ -388,7 +369,7 @@ Breakpoint::nextInSite()
/*** Debugger hook dispatch **********************************************************************/
Debugger::Debugger(JSContext *cx, JSObject *dbg)
: object(dbg), uncaughtExceptionHook(nullptr), enabled(true),
: object(dbg), uncaughtExceptionHook(nullptr), enabled(true), trackingAllocationSites(false),
frames(cx->runtime()), scripts(cx), sources(cx), objects(cx), environments(cx)
{
assertSameCompartment(cx, dbg);
@ -426,13 +407,6 @@ Debugger::init(JSContext *cx)
return ok;
}
Debugger *
Debugger::fromJSObject(JSObject *obj)
{
JS_ASSERT(js::GetObjectClass(obj) == &jsclass);
return (Debugger *) obj->getPrivate();
}
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSCRIPT_OWNER));
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSOURCE_OWNER));
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGOBJECT_OWNER));
@ -1848,8 +1822,10 @@ Debugger::getEnabled(JSContext *cx, unsigned argc, Value *vp)
bool
Debugger::setEnabled(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.set enabled", 1);
THIS_DEBUGGER(cx, argc, vp, "set enabled", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.set enabled", 1))
return false;
bool enabled = ToBoolean(args[0]);
if (enabled != dbg->enabled) {
@ -1896,8 +1872,9 @@ bool
Debugger::setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which)
{
JS_ASSERT(which >= 0 && which < HookCount);
REQUIRE_ARGC("Debugger.setHook", 1);
THIS_DEBUGGER(cx, argc, vp, "setHook", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.setHook", 1))
return false;
if (args[0].isObject()) {
if (!args[0].toObject().isCallable())
return ReportIsNotFunction(cx, args[0], args.length() - 1);
@ -2005,8 +1982,9 @@ Debugger::getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
bool
Debugger::setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.set uncaughtExceptionHook", 1);
THIS_DEBUGGER(cx, argc, vp, "set uncaughtExceptionHook", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.set uncaughtExceptionHook", 1))
return false;
if (!args[0].isNull() && (!args[0].isObject() || !args[0].toObject().isCallable())) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ASSIGN_FUNCTION_OR_NULL,
"uncaughtExceptionHook");
@ -2016,11 +1994,21 @@ Debugger::setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
args.rval().setUndefined();
return true;
}
bool
Debugger::getMemory(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGGER(cx, argc, vp, "get memory", args, dbg);
args.rval().set(dbg->object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE));
Value memoryValue = dbg->object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE);
if (!memoryValue.isObject()) {
RootedObject memory(cx, DebuggerMemory::create(cx, dbg));
if (!memory)
return false;
memoryValue = ObjectValue(*memory);
}
args.rval().set(memoryValue);
return true;
}
@ -2068,8 +2056,9 @@ Debugger::unwrapDebuggeeArgument(JSContext *cx, const Value &v)
bool
Debugger::addDebuggee(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.addDebuggee", 1);
THIS_DEBUGGER(cx, argc, vp, "addDebuggee", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.addDebuggee", 1))
return false;
Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
if (!global)
return false;
@ -2113,9 +2102,10 @@ Debugger::addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp)
bool
Debugger::removeDebuggee(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.removeDebuggee", 1);
THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg);
GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]);
if (!args.requireAtLeast(cx, "Debugger.removeDebuggee", 1))
return false;
Rooted<GlobalObject *> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
if (!global)
return false;
if (dbg->debuggees.has(global)) {
@ -2132,7 +2122,8 @@ Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp)
THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg);
for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
if (!dbg->removeDebuggeeGlobal(cx, e.front(), nullptr, &e))
Rooted<GlobalObject *> global(cx, e.front());
if (!dbg->removeDebuggeeGlobal(cx, global, nullptr, &e))
return false;
}
@ -2143,8 +2134,9 @@ Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp)
bool
Debugger::hasDebuggee(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.hasDebuggee", 1);
THIS_DEBUGGER(cx, argc, vp, "hasDebuggee", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.hasDebuggee", 1))
return false;
GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]);
if (!global)
return false;
@ -2239,14 +2231,7 @@ Debugger::construct(JSContext *cx, unsigned argc, Value *vp)
return false;
for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++)
obj->setReservedSlot(slot, proto->getReservedSlot(slot));
/* Create the Debugger.Memory instance accessible by the
* |Debugger.prototype.memory| getter. */
Value memoryProto = obj->getReservedSlot(JSSLOT_DEBUG_MEMORY_PROTO);
RootedObject memory(cx, NewObjectWithGivenProto(cx, &DebuggerMemory::class_,
&memoryProto.toObject(), nullptr));
if (!memory)
return false;
obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, ObjectValue(*memory));
obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());
/* Construct the underlying C++ object. */
auto dbg = cx->make_unique<Debugger>(cx, obj.get());
@ -2324,6 +2309,22 @@ Debugger::addDebuggeeGlobal(JSContext *cx,
}
}
/*
* If we are tracking allocation sites, we need to add the object metadata
* callback to this debuggee compartment.
*/
bool setMetadataCallback = false;
if (trackingAllocationSites) {
if (debuggeeCompartment->hasObjectMetadataCallback()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET);
return false;
}
debuggeeCompartment->setObjectMetadataCallback(SavedStacksMetadataCallback);
setMetadataCallback = true;
}
/*
* Each debugger-debuggee relation must be stored in up to three places.
* JSCompartment::addDebuggee enables debug mode if needed.
@ -2347,6 +2348,11 @@ Debugger::addDebuggeeGlobal(JSContext *cx,
JS_ASSERT(v->back() == this);
v->popBack();
}
/* Don't leave the object metadata hook set if we OOM'd. */
if (setMetadataCallback)
debuggeeCompartment->forgetObjectMetadataCallback();
return false;
}
@ -2412,10 +2418,17 @@ Debugger::cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global,
bp->destroy(fop);
}
JS_ASSERT_IF(debuggees.empty(), !firstBreakpoint());
/*
* If we are tracking allocation sites, we need to remove the object
* metadata callback from this global's compartment.
*/
if (trackingAllocationSites)
global->compartment()->forgetObjectMetadataCallback();
}
bool
Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
Debugger::removeDebuggeeGlobal(JSContext *cx, Handle<GlobalObject *> global,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum)
{
@ -2424,7 +2437,7 @@ Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
}
bool
Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
Debugger::removeDebuggeeGlobal(JSContext *cx, Handle<GlobalObject *> global,
AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum)
@ -2916,8 +2929,9 @@ Debugger::findAllGlobals(JSContext *cx, unsigned argc, Value *vp)
bool
Debugger::makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.makeGlobalObjectReference", 1);
THIS_DEBUGGER(cx, argc, vp, "makeGlobalObjectReference", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.makeGlobalObjectReference", 1))
return false;
Rooted<GlobalObject *> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
if (!global)
@ -3242,8 +3256,9 @@ ScriptOffset(JSContext *cx, JSScript *script, const Value &v, size_t *offsetp)
static bool
DebuggerScript_getOffsetLine(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Script.getOffsetLine", 1);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetLine", args, obj, script);
if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetLine", 1))
return false;
size_t offset;
if (!ScriptOffset(cx, script, args[0], &offset))
return false;
@ -3609,7 +3624,8 @@ static bool
DebuggerScript_getLineOffsets(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineOffsets", args, obj, script);
REQUIRE_ARGC("Debugger.Script.getLineOffsets", 1);
if (!args.requireAtLeast(cx, "Debugger.Script.getLineOffsets", 1))
return false;
/* Parse lineno argument. */
RootedValue linenoValue(cx, args[0]);
@ -3749,8 +3765,9 @@ Debugger::propagateForcedReturn(JSContext *cx, AbstractFramePtr frame, HandleVal
static bool
DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Script.setBreakpoint", 2);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
if (!args.requireAtLeast(cx, "Debugger.Script.setBreakpoint", 2))
return false;
Debugger *dbg = Debugger::fromChildJSObject(obj);
if (!dbg->observesScript(script)) {
@ -3819,8 +3836,9 @@ DebuggerScript_getBreakpoints(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerScript_clearBreakpoint(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Script.clearBreakpoint", 1);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
if (!args.requireAtLeast(cx, "Debugger.Script.clearBreakpoint", 1))
return false;
Debugger *dbg = Debugger::fromChildJSObject(obj);
JSObject *handler = NonNullObject(cx, args[0]);
@ -3845,8 +3863,9 @@ DebuggerScript_clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerScript_isInCatchScope(JSContext *cx, unsigned argc, Value* vp)
{
REQUIRE_ARGC("Debugger.Script.isInCatchScope", 1);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "isInCatchScope", args, obj, script);
if (!args.requireAtLeast(cx, "Debugger.Script.isInCatchScope", 1))
return false;
size_t offset;
if (!ScriptOffset(cx, script, args[0], &offset))
@ -4652,8 +4671,9 @@ DebuggerFrame_getOnStep(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerFrame_setOnStep(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Frame.set onStep", 1);
THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, frame);
if (!args.requireAtLeast(cx, "Debugger.Frame.set onStep", 1))
return false;
if (!IsValidHook(args[0])) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
return false;
@ -4690,8 +4710,9 @@ DebuggerFrame_getOnPop(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerFrame_setOnPop(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Frame.set onPop", 1);
THIS_FRAME(cx, argc, vp, "set onPop", args, thisobj, frame);
if (!args.requireAtLeast(cx, "Debugger.Frame.set onPop", 1))
return false;
(void) frame; // Silence GCC warning
if (!IsValidHook(args[0])) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
@ -4891,7 +4912,8 @@ static bool
DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
{
THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter);
REQUIRE_ARGC("Debugger.Frame.prototype.eval", 1);
if (!args.requireAtLeast(cx, "Debugger.Frame.prototype.eval", 1))
return false;
Debugger *dbg = Debugger::fromChildJSObject(thisobj);
UpdateFrameIterPc(iter);
return DebuggerGenericEval(cx, "Debugger.Frame.prototype.eval",
@ -4903,7 +4925,8 @@ static bool
DebuggerFrame_evalWithBindings(JSContext *cx, unsigned argc, Value *vp)
{
THIS_FRAME_ITER(cx, argc, vp, "evalWithBindings", args, thisobj, _, iter);
REQUIRE_ARGC("Debugger.Frame.prototype.evalWithBindings", 2);
if (!args.requireAtLeast(cx, "Debugger.Frame.prototype.evalWithBindings", 2))
return false;
Debugger *dbg = Debugger::fromChildJSObject(thisobj);
UpdateFrameIterPc(iter);
return DebuggerGenericEval(cx, "Debugger.Frame.prototype.evalWithBindings",
@ -5298,6 +5321,18 @@ DebuggerObject_getGlobal(JSContext *cx, unsigned argc, Value *vp)
return true;
}
static bool
DebuggerObject_getAllocationSite(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get allocationSite", args, obj);
RootedObject metadata(cx, obj->getMetadata());
if (!cx->compartment()->wrap(cx, &metadata))
return false;
args.rval().setObjectOrNull(metadata);
return true;
}
static bool
DebuggerObject_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp)
{
@ -5383,7 +5418,8 @@ static bool
DebuggerObject_defineProperty(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperty", args, dbg, obj);
REQUIRE_ARGC("Debugger.Object.defineProperty", 2);
if (!args.requireAtLeast(cx, "Debugger.Object.defineProperty", 2))
return false;
RootedId id(cx);
if (!ValueToId<CanGC>(cx, args[0], &id))
@ -5418,7 +5454,8 @@ static bool
DebuggerObject_defineProperties(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "defineProperties", args, dbg, obj);
REQUIRE_ARGC("Debugger.Object.defineProperties", 1);
if (!args.requireAtLeast(cx, "Debugger.Object.defineProperties", 1))
return false;
RootedValue arg(cx, args[0]);
RootedObject props(cx, ToObject(cx, arg));
@ -5666,8 +5703,9 @@ DebuggerObject_call(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerObject_makeDebuggeeValue(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Object.prototype.makeDebuggeeValue", 1);
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "makeDebuggeeValue", args, dbg, referent);
if (!args.requireAtLeast(cx, "Debugger.Object.prototype.makeDebuggeeValue", 1))
return false;
RootedValue arg0(cx, args[0]);
@ -5730,8 +5768,9 @@ RequireGlobalObject(JSContext *cx, HandleValue dbgobj, HandleObject referent)
static bool
DebuggerObject_evalInGlobal(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobal", 1);
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobal", args, dbg, referent);
if (!args.requireAtLeast(cx, "Debugger.Object.prototype.evalInGlobal", 1))
return false;
if (!RequireGlobalObject(cx, args.thisv(), referent))
return false;
@ -5743,8 +5782,9 @@ DebuggerObject_evalInGlobal(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerObject_evalInGlobalWithBindings(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobalWithBindings", 2);
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobalWithBindings", args, dbg, referent);
if (!args.requireAtLeast(cx, "Debugger.Object.prototype.evalInGlobalWithBindings", 2))
return false;
if (!RequireGlobalObject(cx, args.thisv(), referent))
return false;
@ -5797,6 +5837,7 @@ static const JSPropertySpec DebuggerObject_properties[] = {
JS_PSG("boundThis", DebuggerObject_getBoundThis, 0),
JS_PSG("boundArguments", DebuggerObject_getBoundArguments, 0),
JS_PSG("global", DebuggerObject_getGlobal, 0),
JS_PSG("allocationSite", DebuggerObject_getAllocationSite, 0),
JS_PS_END
};
@ -6058,8 +6099,9 @@ DebuggerEnv_names(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Environment.find", 1);
THIS_DEBUGENV_OWNER(cx, argc, vp, "find", args, envobj, env, dbg);
if (!args.requireAtLeast(cx, "Debugger.Environment.find", 1))
return false;
RootedId id(cx);
if (!ValueToIdentifier(cx, args[0], &id))
@ -6087,8 +6129,9 @@ DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerEnv_getVariable(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Environment.getVariable", 1);
THIS_DEBUGENV_OWNER(cx, argc, vp, "getVariable", args, envobj, env, dbg);
if (!args.requireAtLeast(cx, "Debugger.Environment.getVariable", 1))
return false;
RootedId id(cx);
if (!ValueToIdentifier(cx, args[0], &id))
@ -6124,8 +6167,9 @@ DebuggerEnv_getVariable(JSContext *cx, unsigned argc, Value *vp)
static bool
DebuggerEnv_setVariable(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Environment.setVariable", 2);
THIS_DEBUGENV_OWNER(cx, argc, vp, "setVariable", args, envobj, env, dbg);
if (!args.requireAtLeast(cx, "Debugger.Environment.setVariable", 2))
return false;
RootedId id(cx);
if (!ValueToIdentifier(cx, args[0], &id))

View File

@ -158,6 +158,7 @@ typedef JSObject Env;
class Debugger : private mozilla::LinkedListElement<Debugger>
{
friend class Breakpoint;
friend class DebuggerMemory;
friend class mozilla::LinkedListElement<Debugger>;
friend bool (::JS_DefineDebuggerObject)(JSContext *cx, JS::HandleObject obj);
@ -189,6 +190,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
GlobalObjectSet debuggees; /* Debuggee globals. Cross-compartment weak references. */
js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */
bool enabled;
bool trackingAllocationSites;
JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */
/*
@ -243,10 +245,10 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnu);
bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
bool removeDebuggeeGlobal(JSContext *cx, Handle<GlobalObject *> global,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum);
bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
bool removeDebuggeeGlobal(JSContext *cx, Handle<GlobalObject *> global,
AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum);
@ -766,6 +768,8 @@ EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFrameP
mozilla::Range<const jschar> chars, const char *filename, unsigned lineno,
MutableHandleValue rval);
}
bool ReportObjectRequired(JSContext *cx);
} /* namespace js */
#endif /* vm_Debugger_h */

View File

@ -6,8 +6,31 @@
#include "vm/DebuggerMemory.h"
#include "jscompartment.h"
#include "vm/Debugger.h"
#include "vm/GlobalObject.h"
#include "vm/SavedStacks.h"
#include "vm/Debugger-inl.h"
namespace js {
/* static */ DebuggerMemory *
DebuggerMemory::create(JSContext *cx, Debugger *dbg)
{
Value memoryProto = dbg->object->getReservedSlot(Debugger::JSSLOT_DEBUG_MEMORY_PROTO);
RootedObject memory(cx, NewObjectWithGivenProto(cx, &class_,
&memoryProto.toObject(), nullptr));
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>();
}
/* static */ bool
DebuggerMemory::construct(JSContext *cx, unsigned argc, Value *vp)
{
@ -19,7 +42,7 @@ DebuggerMemory::construct(JSContext *cx, unsigned argc, Value *vp)
/* static */ const Class DebuggerMemory::class_ = {
"Memory",
JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGGER_MEMORY_COUNT),
JSCLASS_HAS_RESERVED_SLOTS(DebuggerMemory::JSSLOT_COUNT),
JS_PropertyStub, // addProperty
JS_DeletePropertyStub, // delProperty
@ -36,7 +59,112 @@ DebuggerMemory::construct(JSContext *cx, unsigned argc, Value *vp)
nullptr // trace
};
/* static */ DebuggerMemory *
DebuggerMemory::checkThis(JSContext *cx, CallArgs &args, const char *fnName)
{
const Value &thisValue = args.thisv();
if (!thisValue.isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
return nullptr;
}
JSObject &thisObject = thisValue.toObject();
if (!thisObject.is<DebuggerMemory>()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
DebuggerMemory::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.getReservedSlot(JSSLOT_DEBUGGER).isUndefined()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
DebuggerMemory::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
Debugger *
DebuggerMemory::getDebugger()
{
return Debugger::fromJSObject(&getReservedSlot(JSSLOT_DEBUGGER).toObject());
}
/* 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) {
// Nothing to do here...
args.rval().setUndefined();
return true;
}
if (enabling) {
for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) {
JSCompartment *compartment = r.front()->compartment();
if (compartment->hasObjectMetadataCallback()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET);
return false;
}
}
}
for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) {
if (enabling) {
r.front()->compartment()->setObjectMetadataCallback(SavedStacksMetadataCallback);
} else {
r.front()->compartment()->forgetObjectMetadataCallback();
}
}
dbg->trackingAllocationSites = enabling;
args.rval().setUndefined();
return true;
}
/* 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 */ const JSPropertySpec DebuggerMemory::properties[] = {
JS_PSGS("trackingAllocationSites", DebuggerMemory::getTrackingAllocationSites,
DebuggerMemory::setTrackingAllocationSites, 0),
JS_PS_END
};

View File

@ -16,17 +16,27 @@
namespace js {
class DebuggerMemory : public JSObject {
friend class Debugger;
static DebuggerMemory *checkThis(JSContext *cx, CallArgs &args, const char *fnName);
Debugger *getDebugger();
public:
static DebuggerMemory *create(JSContext *cx, Debugger *dbg);
enum {
JSSLOT_DEBUGGER_MEMORY_COUNT
JSSLOT_DEBUGGER,
JSSLOT_COUNT
};
public:
static bool construct(JSContext *cx, unsigned argc, Value *vp);
static const Class class_;
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
static bool setTrackingAllocationSites(JSContext *cx, unsigned argc, Value *vp);
static bool getTrackingAllocationSites(JSContext *cx, unsigned argc, Value *vp);
};
} /* namespace js */

View File

@ -612,13 +612,13 @@ SavedStacks::createFrameFromLookup(JSContext *cx, const SavedFrame::Lookup &look
if (!frameObj)
return nullptr;
SavedFrame &f = frameObj->as<SavedFrame>();
f.initFromLookup(lookup);
RootedSavedFrame f(cx, &frameObj->as<SavedFrame>());
f->initFromLookup(lookup);
if (!JSObject::freeze(cx, frameObj))
return nullptr;
return &f;
return f.get();
}
/*