Bug 1130214 - Add an .allowUnobservedAsmJS accessor on Debugger instances. (r=jimb)

This commit is contained in:
Shu-yu Guo 2015-02-13 16:53:22 -08:00
parent 8b0b1dcb5c
commit 2d068e7ae5
9 changed files with 218 additions and 103 deletions

View File

@ -9337,7 +9337,7 @@ EstablishPreconditions(ExclusiveContext *cx, AsmJSParser &parser)
if (!parser.options().compileAndGo)
return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Temporarily disabled for event-handler and other cloneable scripts");
if (cx->compartment()->isDebuggee())
if (cx->compartment()->debuggerObservesAsmJS())
return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by debugger");
if (parser.pc->isGenerator())
@ -9396,7 +9396,6 @@ js::IsAsmJSCompilationAvailable(JSContext *cx, unsigned argc, Value *vp)
#else
bool available = cx->jitSupportsFloatingPoint() &&
cx->gcSystemPageSize() == AsmJSPageSize &&
!cx->compartment()->isDebuggee() &&
cx->runtime()->options().asmJS();
#endif

View File

@ -25,6 +25,18 @@ its prototype:
disentangling itself from the debuggee, regardless of what sort of
events or handlers or "points" we add to the interface.
`allowUnobservedAsmJS`
: A boolean value indicating whether asm.js code running inside this
`Debugger` instance's debuggee globals is invisible to Debugger API
handlers and breakpoints. Setting this to `false` inhibits the
ahead-of-time asm.js compiler and forces asm.js code to run as normal
JavaScript. This is an accessor property with a getter and setter. It is
initially `false` in a freshly created `Debugger` instance.
Setting this flag to `true` is intended for uses of subsystems of the
Debugger API (e.g, [`Debugger.Source`][source]) for purposes other than
step debugging a target JavaScript program.
`uncaughtExceptionHook`
: Either `null` or a function that SpiderMonkey calls when a call to a
debug event handler, breakpoint handler, watchpoint handler, or similar

View File

@ -0,0 +1,34 @@
load(libdir + "asm.js");
var g = newGlobal();
g.parent = this;
g.eval("dbg = new Debugger(parent);");
// Initial state is to inhibit asm.js.
assertEq(g.dbg.allowUnobservedAsmJS, false);
var asmFunStr = USE_ASM + 'function f() {} return f';
// With asm.js inhibited, asm.js should fail with a type error about the
// debugger being on.
assertAsmTypeFail(asmFunStr);
// With asm.js uninhibited, asm.js linking should work.
g.dbg.allowUnobservedAsmJS = true;
assertEq(asmLink(asmCompile(asmFunStr))(), undefined);
// Toggling back should inhibit again.
g.dbg.allowUnobservedAsmJS = false;
assertAsmTypeFail(asmFunStr);
// Disabling the debugger should uninhibit.
g.dbg.enabled = false;
assertEq(asmLink(asmCompile(asmFunStr))(), undefined);
// Enabling it should inhibit again.
g.dbg.enabled = true;
assertAsmTypeFail(asmFunStr);
// Removing the global should lift the inhibition.
g.dbg.removeDebuggee(this);
assertEq(asmLink(asmCompile(asmFunStr))(), undefined);

View File

@ -768,15 +768,37 @@ JSCompartment::ensureDelazifyScriptsForDebugger(JSContext *cx)
MOZ_ASSERT(cx->compartment() == this);
if (needsDelazificationForDebugger() && !CreateLazyScriptsForCompartment(cx))
return false;
debugModeBits &= ~DebugNeedsDelazification;
debugModeBits &= ~DebuggerNeedsDelazification;
return true;
}
void
JSCompartment::updateDebuggerObservesFlag(unsigned flag)
{
MOZ_ASSERT(isDebuggee());
MOZ_ASSERT(flag == DebuggerObservesAllExecution ||
flag == DebuggerObservesAsmJS);
const GlobalObject::DebuggerVector *v = maybeGlobal()->getDebuggers();
for (Debugger * const *p = v->begin(); p != v->end(); p++) {
Debugger *dbg = *p;
if (flag == DebuggerObservesAllExecution
? dbg->observesAllExecution()
: dbg->observesAsmJS())
{
debugModeBits |= flag;
return;
}
}
debugModeBits &= ~flag;
}
void
JSCompartment::unsetIsDebuggee()
{
if (isDebuggee()) {
debugModeBits &= ~DebugExecutionMask;
debugModeBits &= ~DebuggerObservesMask;
DebugScopes::onCompartmentUnsetIsDebuggee(this);
}
}

View File

@ -317,17 +317,20 @@ struct JSCompartment
bool gcPreserveJitCode;
enum {
DebugMode = 1 << 0,
DebugObservesAllExecution = 1 << 1,
DebugNeedsDelazification = 1 << 2
IsDebuggee = 1 << 0,
DebuggerObservesAllExecution = 1 << 1,
DebuggerObservesAsmJS = 1 << 2,
DebuggerNeedsDelazification = 1 << 3
};
// DebugObservesAllExecution is a submode of DebugMode, and is only valid
// when DebugMode is also set.
static const unsigned DebugExecutionMask = DebugMode | DebugObservesAllExecution;
unsigned debugModeBits;
static const unsigned DebuggerObservesMask = IsDebuggee |
DebuggerObservesAllExecution |
DebuggerObservesAsmJS;
void updateDebuggerObservesFlag(unsigned flag);
public:
JSCompartment(JS::Zone *zone, const JS::CompartmentOptions &options);
~JSCompartment();
@ -419,14 +422,17 @@ struct JSCompartment
//
// The Debugger observes execution on a frame-by-frame basis. The
// invariants of JSCompartment's debug mode bits, JSScript::isDebuggee,
// InterpreterFrame::isDebuggee, and Baseline::isDebuggee are enumerated
// below.
// InterpreterFrame::isDebuggee, and BaselineFrame::isDebuggee are
// enumerated below.
//
// 1. When a compartment's isDebuggee() == true, relazification, lazy
// parsing, and asm.js are disabled.
// 1. When a compartment's isDebuggee() == true, relazification and lazy
// parsing are disabled.
//
// 2. When a compartment's debugObservesAllExecution() == true, all of the
// compartment's scripts are considered debuggee scripts.
// Whether AOT asm.js is disabled is togglable by the Debugger API. By
// default it is disabled. See debuggerObservesAsmJS below.
//
// 2. When a compartment's debuggerObservesAllExecution() == true, all of
// the compartment's scripts are considered debuggee scripts.
//
// 3. A script is considered a debuggee script either when, per above, its
// compartment is observing all execution, or if it has breakpoints set.
@ -451,32 +457,44 @@ struct JSCompartment
// True if this compartment's global is a debuggee of some Debugger
// object.
bool isDebuggee() const { return !!(debugModeBits & DebugMode); }
void setIsDebuggee() { debugModeBits |= DebugMode; }
bool isDebuggee() const { return !!(debugModeBits & IsDebuggee); }
void setIsDebuggee() { debugModeBits |= IsDebuggee; }
void unsetIsDebuggee();
// True if an this compartment's global is a debuggee of some Debugger
// True if this compartment's global is a debuggee of some Debugger
// object with a live hook that observes all execution; e.g.,
// onEnterFrame.
bool debugObservesAllExecution() const {
return (debugModeBits & DebugExecutionMask) == DebugExecutionMask;
bool debuggerObservesAllExecution() const {
static const unsigned Mask = IsDebuggee | DebuggerObservesAllExecution;
return (debugModeBits & Mask) == Mask;
}
void setDebugObservesAllExecution() {
MOZ_ASSERT(isDebuggee());
debugModeBits |= DebugObservesAllExecution;
}
void unsetDebugObservesAllExecution() {
MOZ_ASSERT(isDebuggee());
debugModeBits &= ~DebugObservesAllExecution;
void updateDebuggerObservesAllExecution() {
updateDebuggerObservesFlag(DebuggerObservesAllExecution);
}
bool needsDelazificationForDebugger() const { return debugModeBits & DebugNeedsDelazification; }
// True if this compartment's global is a debuggee of some Debugger object
// whose allowUnobservedAsmJS flag is false.
//
// Note that since AOT asm.js functions cannot bail out, this flag really
// means "observe asm.js from this point forward". We cannot make
// already-compiled asm.js code observable to Debugger.
bool debuggerObservesAsmJS() const {
static const unsigned Mask = IsDebuggee | DebuggerObservesAsmJS;
return (debugModeBits & Mask) == Mask;
}
void updateDebuggerObservesAsmJS() {
updateDebuggerObservesFlag(DebuggerObservesAsmJS);
}
bool needsDelazificationForDebugger() const {
return debugModeBits & DebuggerNeedsDelazification;
}
/*
* Schedule the compartment to be delazified. Called from
* LazyScript::Create.
*/
void scheduleDelazificationForDebugger() { debugModeBits |= DebugNeedsDelazification; }
void scheduleDelazificationForDebugger() { debugModeBits |= DebuggerNeedsDelazification; }
/*
* If we scheduled delazification for turning on debug mode, delazify all

View File

@ -166,7 +166,7 @@ JSScript::ensureHasAnalyzedArgsUsage(JSContext *cx)
inline bool
JSScript::isDebuggee() const
{
return compartment_->debugObservesAllExecution() || hasDebugScript_;
return compartment_->debuggerObservesAllExecution() || hasDebugScript_;
}
#endif /* jsscriptinlines_h */

View File

@ -357,6 +357,7 @@ Debugger::Debugger(JSContext *cx, NativeObject *dbg)
: object(dbg),
uncaughtExceptionHook(nullptr),
enabled(true),
allowUnobservedAsmJS(false),
trackingAllocationSites(false),
allocationSamplingProbability(1.0),
allocationsLogLength(0),
@ -1993,12 +1994,12 @@ Debugger::ensureExecutionObservabilityOfFrame(JSContext *cx, AbstractFramePtr fr
/* static */ bool
Debugger::ensureExecutionObservabilityOfCompartment(JSContext *cx, JSCompartment *comp)
{
if (comp->debugObservesAllExecution())
if (comp->debuggerObservesAllExecution())
return true;
ExecutionObservableCompartments obs(cx);
if (!obs.init() || !obs.add(comp))
return false;
comp->setDebugObservesAllExecution();
comp->updateDebuggerObservesAllExecution();
return updateExecutionObservability(cx, obs, Observing);
}
@ -2008,66 +2009,64 @@ Debugger::hookObservesAllExecution(Hook which)
return which == OnEnterFrame;
}
bool
Debugger::hasAnyLiveHooksThatObserveAllExecution() const
Debugger::IsObserving
Debugger::observesAllExecution() const
{
if (!enabled)
return false;
return hasAnyHooksThatObserveAllExecution();
if (enabled && !!getHook(OnEnterFrame))
return Observing;
return NotObserving;
}
bool
Debugger::hasAnyHooksThatObserveAllExecution() const
Debugger::IsObserving
Debugger::observesAsmJS() const
{
return !!getHook(OnEnterFrame);
}
/* static */ bool
Debugger::anyOtherDebuggerObservingAllExecution(GlobalObject *global) const
{
// If we are toggling from Observing to NotObserving, add the
// compartment to the observable set only if none of its other
// debuggers are observing all execution.
GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
Debugger *dbg = *p;
if (dbg != this && dbg->hasAnyLiveHooksThatObserveAllExecution())
return true;
}
return false;
if (enabled && !allowUnobservedAsmJS)
return Observing;
return NotObserving;
}
// Toggle whether this Debugger's debuggees observe all execution. This is
// called when a hook that observes all execution is set or unset. See
// hookObservesAllExecution.
bool
Debugger::setObservesAllExecution(JSContext *cx, IsObserving observing)
Debugger::updateObservesAllExecutionOnDebuggees(JSContext *cx, IsObserving observing)
{
ExecutionObservableCompartments obs(cx);
if (!obs.init())
return false;
for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
GlobalObject * global = r.front();
GlobalObject *global = r.front();
JSCompartment *comp = global->compartment();
if (comp->debugObservesAllExecution() == observing)
if (comp->debuggerObservesAllExecution() == observing)
continue;
if (observing) {
if (!obs.add(comp))
return false;
comp->setDebugObservesAllExecution();
} else if (!anyOtherDebuggerObservingAllExecution(global)) {
// It's expensive to eagerly invalidate and recompile a
// compartment, so don't add the compartment to the set.
comp->unsetDebugObservesAllExecution();
}
// It's expensive to eagerly invalidate and recompile a compartment,
// so add the compartment to the set only if we are observing.
if (observing && !obs.add(comp))
return false;
comp->updateDebuggerObservesAllExecution();
}
return updateExecutionObservability(cx, obs, observing);
}
void
Debugger::updateObservesAsmJSOnDebuggees(IsObserving observing)
{
for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
GlobalObject *global = r.front();
JSCompartment *comp = global->compartment();
if (comp->debuggerObservesAsmJS() == observing)
continue;
comp->updateDebuggerObservesAsmJS();
}
}
/*** Debugger JSObjects **************************************************************************/
@ -2393,11 +2392,12 @@ Debugger::setEnabled(JSContext *cx, unsigned argc, Value *vp)
if (!args.requireAtLeast(cx, "Debugger.set enabled", 1))
return false;
bool enabled = ToBoolean(args[0]);
bool wasEnabled = dbg->enabled;
dbg->enabled = ToBoolean(args[0]);
if (enabled != dbg->enabled) {
if (wasEnabled != dbg->enabled) {
for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
if (enabled)
if (!wasEnabled)
bp->site->inc(cx->runtime()->defaultFreeOp());
else
bp->site->dec(cx->runtime()->defaultFreeOp());
@ -2408,7 +2408,7 @@ Debugger::setEnabled(JSContext *cx, unsigned argc, Value *vp)
* that care about new globals.
*/
if (dbg->getHook(OnNewGlobalObject)) {
if (enabled) {
if (!wasEnabled) {
/* If we were not enabled, the link should be a singleton list. */
MOZ_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
@ -2422,18 +2422,12 @@ Debugger::setEnabled(JSContext *cx, unsigned argc, Value *vp)
// Ensure the compartment is observable if we are re-enabling a
// Debugger with hooks that observe all execution.
if (enabled) {
if (dbg->hasAnyHooksThatObserveAllExecution()) {
if (!dbg->setObservesAllExecution(cx, Observing))
return false;
}
} else {
if (!dbg->setObservesAllExecution(cx, NotObserving))
return false;
}
if (!dbg->updateObservesAllExecutionOnDebuggees(cx, dbg->observesAllExecution()))
return false;
dbg->updateObservesAsmJSOnDebuggees(dbg->observesAsmJS());
}
dbg->enabled = enabled;
args.rval().setUndefined();
return true;
}
@ -2463,11 +2457,8 @@ Debugger::setHookImpl(JSContext *cx, unsigned argc, Value *vp, Hook which)
}
dbg->object->setReservedSlot(JSSLOT_DEBUG_HOOK_START + which, args[0]);
if (hookObservesAllExecution(which)) {
if (!dbg->setObservesAllExecution(cx, dbg->hasAnyLiveHooksThatObserveAllExecution()
? Observing : NotObserving))
{
if (!dbg->updateObservesAllExecutionOnDebuggees(cx, dbg->observesAllExecution()))
return false;
}
}
args.rval().setUndefined();
return true;
@ -2605,6 +2596,32 @@ Debugger::setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
return true;
}
/* static */ bool
Debugger::getAllowUnobservedAsmJS(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGGER(cx, argc, vp, "get allowUnobservedAsmJS", args, dbg);
args.rval().setBoolean(dbg->allowUnobservedAsmJS);
return true;
}
/* static */ bool
Debugger::setAllowUnobservedAsmJS(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGGER(cx, argc, vp, "set allowUnobservedAsmJS", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.set allowUnobservedAsmJS", 1))
return false;
dbg->allowUnobservedAsmJS = ToBoolean(args[0]);
for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) {
GlobalObject *global = r.front();
JSCompartment *comp = global->compartment();
comp->updateDebuggerObservesAsmJS();
}
args.rval().setUndefined();
return true;
}
/* static */ bool
Debugger::getMemory(JSContext *cx, unsigned argc, Value *vp)
{
@ -2988,7 +3005,8 @@ Debugger::addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> global)
js_ReportOutOfMemory(cx);
} else {
debuggeeCompartment->setIsDebuggee();
if (!hasAnyLiveHooksThatObserveAllExecution())
debuggeeCompartment->updateDebuggerObservesAsmJS();
if (!observesAllExecution())
return true;
if (ensureExecutionObservabilityOfCompartment(cx, debuggeeCompartment))
return true;
@ -3072,13 +3090,12 @@ Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, GlobalObjectSe
if (trackingAllocationSites)
global->compartment()->forgetObjectMetadataCallback();
/*
* The debuggee needs to be removed from the compartment last, as this can
* trigger GCs if the compartment's debug mode is being changed, and the
* global cannot be rooted on the stack without a cx.
*/
if (global->getDebuggers()->empty())
if (global->getDebuggers()->empty()) {
global->compartment()->unsetIsDebuggee();
} else {
global->compartment()->updateDebuggerObservesAllExecution();
global->compartment()->updateDebuggerObservesAsmJS();
}
}
static inline ScriptSourceObject *GetSourceReferent(JSObject *obj);
@ -4048,6 +4065,8 @@ const JSPropertySpec Debugger::properties[] = {
JS_PSGS("onNewGlobalObject", Debugger::getOnNewGlobalObject, Debugger::setOnNewGlobalObject, 0),
JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook,
Debugger::setUncaughtExceptionHook, 0),
JS_PSGS("allowUnobservedAsmJS", Debugger::getAllowUnobservedAsmJS,
Debugger::setAllowUnobservedAsmJS, 0),
JS_PSG("memory", Debugger::getMemory, 0),
JS_PS_END
};

View File

@ -253,6 +253,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
};
typedef mozilla::LinkedList<AllocationSite> AllocationSiteList;
bool allowUnobservedAsmJS;
bool trackingAllocationSites;
double allocationSamplingProbability;
AllocationSiteList allocationsLog;
@ -403,6 +405,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static bool setOnPromiseSettled(JSContext *cx, unsigned argc, Value *vp);
static bool getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp);
static bool setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp);
static bool getAllowUnobservedAsmJS(JSContext *cx, unsigned argc, Value *vp);
static bool setAllowUnobservedAsmJS(JSContext *cx, unsigned argc, Value *vp);
static bool getMemory(JSContext *cx, unsigned argc, Value *vp);
static bool addDebuggee(JSContext *cx, unsigned argc, Value *vp);
static bool addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp);
@ -440,15 +444,22 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
// Public for DebuggerScript_setBreakpoint.
static bool ensureExecutionObservabilityOfScript(JSContext *cx, JSScript *script);
// Whether the Debugger instance needs to observe all non-AOT JS
// execution of its debugees.
IsObserving observesAllExecution() const;
// Whether the Debugger instance needs to observe AOT-compiled asm.js
// execution of its debuggees.
IsObserving observesAsmJS() const;
private:
static bool ensureExecutionObservabilityOfFrame(JSContext *cx, AbstractFramePtr frame);
static bool ensureExecutionObservabilityOfCompartment(JSContext *cx, JSCompartment *comp);
static bool hookObservesAllExecution(Hook which);
bool anyOtherDebuggerObservingAllExecution(GlobalObject *global) const;
bool hasAnyLiveHooksThatObserveAllExecution() const;
bool hasAnyHooksThatObserveAllExecution() const;
bool setObservesAllExecution(JSContext *cx, IsObserving observing);
bool updateObservesAllExecutionOnDebuggees(JSContext *cx, IsObserving observing);
void updateObservesAsmJSOnDebuggees(IsObserving observing);
JSObject *getHook(Hook hook) const;
bool hasAnyLiveHooks() const;

View File

@ -212,11 +212,11 @@ ParseTask::init(JSContext *cx, const ReadOnlyCompileOptions &options)
if (!this->options.copy(cx, options))
return false;
// If the main-thread global is a debuggee, disable asm.js
// compilation. This is preferred to marking the task compartment as a
// debuggee, as the task compartment is (1) invisible to Debugger and (2)
// cannot have any Debuggers.
if (cx->compartment()->isDebuggee())
// If the main-thread global is a debuggee that observes asm.js, disable
// asm.js compilation. This is preferred to marking the task compartment
// as a debuggee, as the task compartment is (1) invisible to Debugger and
// (2) cannot have any Debuggers.
if (cx->compartment()->debuggerObservesAsmJS())
this->options.asmJSOption = false;
return true;