Add support for dbg.hooks.throw.
Add support for dbg.hooks.throw.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/hooks-throw-01.js
@@ -0,0 +1,27 @@
+// |jit-test| debug
+// Basic throw hook test.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal('new-compartment');
+var dbg = Debug(g);
+var hit = false;
+dbg.hooks = {
+ throw: function (frame, exc) {
+ // hooks.throw is called multiple times as the stack is unwound.
+ // Only check the first hit.
+ assertEq(arguments.length, 2);
+ assertEq(frame instanceof Debug.Frame, true);
+ if (!hit) {
+ assertEq(exc, 101);
+ assertEq(frame.type, "call");
+ assertEq(frame.callee.name, "f");
+ assertEq(frame.older.type, "eval");
+ hit = true;
+ }
+ }
+};
+
+g.eval("function f() { throw 101; }");
+assertThrowsValue(function () { g.eval("f();"); }, 101);
+assertEq(hit, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/hooks-throw-02.js
@@ -0,0 +1,60 @@
+// |jit-test| debug
+// The throw hook is called multiple times as the stack unwinds.
+
+var g = newGlobal('new-compartment');
+g.debuggeeGlobal = this;
+g.dbg = null;
+g.eval("(" + function () {
+ dbg = new Debug(debuggeeGlobal);
+ dbg.hooks = {
+ throw: function (frame, exc) {
+ assertEq(frame instanceof Debug.Frame, true);
+ assertEq(exc instanceof Debug.Object, true);
+ var s = '!';
+ for (var f = frame; f; f = f.older)
+ if (f.type === "call")
+ s += f.callee.name;
+ s += ', ';
+ debuggeeGlobal.log += s;
+ }
+ };
+ } + ")();");
+
+var log;
+
+function k() {
+ try {
+ throw new Error("oops"); // hook call 1
+ } finally {
+ log += 'k-finally, ';
+ } // hook call 2
+}
+
+function j() {
+ k(); // hook call 3
+ log += 'j-unreached, ';
+}
+
+function h() {
+ try {
+ j(); // hook call 4
+ log += 'h-unreached, ';
+ } catch (exc) {
+ log += 'h-catch, ';
+ throw exc; // hook call 5
+ }
+}
+
+function f() {
+ try {
+ h(); // hook call 6
+ } catch (exc) {
+ log += 'f-catch, ';
+ }
+ log += 'f-after, ';
+}
+
+log = '';
+f();
+g.dbg.enabled = false;
+assertEq(log, '!kjhf, k-finally, !kjhf, !jhf, !hf, h-catch, !hf, !f, f-catch, f-after, ');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/hooks-throw-03.js
@@ -0,0 +1,21 @@
+// |jit-test| debug
+// hooks.throw is not called for exceptions thrown and handled in the debugger.
+
+var g = newGlobal('new-compartment');
+var dbg = Debug(g);
+g.log = '';
+dbg.hooks = {
+ debuggerHandler: function (frame) {
+ try {
+ throw new Error("oops");
+ } catch (exc) {
+ g.log += exc.message;
+ }
+ },
+ throw: function (frame) {
+ g.log += 'BAD';
+ }
+};
+
+g.eval("debugger; log += ' ok';");
+assertEq(g.log, 'oops ok');
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -132,17 +132,18 @@ CheckThisClass(JSContext *cx, Value *vp,
enum {
JSSLOT_DEBUG_FRAME_PROTO,
JSSLOT_DEBUG_OBJECT_PROTO,
JSSLOT_DEBUG_COUNT
};
Debug::Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment)
: object(dbg), debuggeeCompartment(compartment), hooksObject(hooks),
- uncaughtExceptionHook(NULL), enabled(true), hasDebuggerHandler(false)
+ uncaughtExceptionHook(NULL), enabled(true), hasDebuggerHandler(false),
+ hasThrowHandler(false)
{
}
bool
Debug::init()
{
return frames.init() && objects.init();
}
@@ -378,16 +379,22 @@ CallMethodIfPresent(JSContext *cx, JSObj
JSAtom *atom = js_Atomize(cx, name, strlen(name), 0);
Value fval;
return atom &&
js_GetMethod(cx, obj, ATOM_TO_JSID(atom), JSGET_NO_METHOD_BARRIER, &fval) &&
(!js_IsCallable(fval) ||
ExternalInvoke(cx, ObjectValue(*obj), fval, argc, argv, rval));
}
+bool
+Debug::observesDebuggerStatement() const
+{
+ return enabled && hasDebuggerHandler;
+}
+
JSTrapStatus
Debug::handleDebuggerStatement(JSContext *cx, Value *vp)
{
// Grab cx->fp() before pushing a dummy frame.
StackFrame *fp = cx->fp();
JS_ASSERT(hasDebuggerHandler);
AutoCompartment ac(cx, hooksObject);
@@ -398,41 +405,74 @@ Debug::handleDebuggerStatement(JSContext
if (!getScriptFrame(cx, fp, argv))
return JSTRAP_ERROR;
Value rv;
bool ok = CallMethodIfPresent(cx, hooksObject, "debuggerHandler", 1, argv, &rv);
return parseResumptionValue(ac, ok, rv, vp);
}
+bool
+Debug::observesThrow() const
+{
+ return enabled && hasThrowHandler;
+}
+
JSTrapStatus
-Debug::dispatchDebuggerStatement(JSContext *cx, js::Value *vp)
+Debug::handleThrow(JSContext *cx, Value *vp)
+{
+ // Grab cx->fp() and the exception value before preparing to call the hook.
+ StackFrame *fp = cx->fp();
+ Value exc = cx->getPendingException();
+
+ cx->clearPendingException();
+ JS_ASSERT(hasThrowHandler);
+ AutoCompartment ac(cx, hooksObject);
+ if (!ac.enter())
+ return JSTRAP_ERROR;
+
+ Value argv[2];
+ argv[1] = exc;
+ if (!getScriptFrame(cx, fp, &argv[0]) || !wrapDebuggeeValue(cx, &argv[1]))
+ return JSTRAP_ERROR;
+
+ Value rv;
+ bool ok = CallMethodIfPresent(cx, hooksObject, "throw", 2, argv, &rv);
+ JSTrapStatus st = parseResumptionValue(ac, ok, rv, vp);
+ if (st == JSTRAP_CONTINUE)
+ cx->setPendingException(argv[1]);
+ return st;
+}
+
+JSTrapStatus
+Debug::dispatchHook(JSContext *cx, js::Value *vp, DebugObservesMethod observesEvent,
+ DebugHandleMethod handleEvent)
{
// Determine which debuggers will receive this event, and in what order.
// Make a copy of the list, since the original is mutable and we will be
// calling into arbitrary JS.
// Note: In the general case, 'triggered' contains references to objects in
// different compartments--every compartment *except* this one.
AutoValueVector triggered(cx);
JSCompartment *compartment = cx->compartment;
const JSCompartment::DebugVector &debuggers = compartment->getDebuggers();
for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) {
Debug *dbg = *p;
- if (dbg->observesDebuggerStatement()) {
+ if ((dbg->*observesEvent)()) {
if (!triggered.append(ObjectValue(*dbg->toJSObject())))
return JSTRAP_ERROR;
}
}
// Deliver the event to each debugger, checking again to make sure it
// should still be delivered.
for (Value *p = triggered.begin(); p != triggered.end(); p++) {
Debug *dbg = Debug::fromJSObject(&p->toObject());
- if (dbg->observesCompartment(compartment) && dbg->observesDebuggerStatement()) {
- JSTrapStatus st = dbg->handleDebuggerStatement(cx, vp);
+ if (dbg->observesCompartment(compartment) && (dbg->*observesEvent)()) {
+ JSTrapStatus st = (dbg->*handleEvent)(cx, vp);
if (st != JSTRAP_CONTINUE)
return st;
}
}
return JSTRAP_CONTINUE;
}
// === Debug JSObjects
@@ -587,22 +627,28 @@ JSBool
Debug::setHooks(JSContext *cx, uintN argc, Value *vp)
{
REQUIRE_ARGC("Debug.set hooks", 1);
THISOBJ(cx, vp, Debug, "set hooks", thisobj, dbg);
if (!vp[2].isObject())
return ReportObjectRequired(cx);
JSObject *hooksobj = &vp[2].toObject();
+ bool hasDebuggerHandler, hasThrow;
JSBool found;
if (!JS_HasProperty(cx, hooksobj, "debuggerHandler", &found))
return false;
- dbg->hasDebuggerHandler = !!found;
+ hasDebuggerHandler = !!found;
+ if (!JS_HasProperty(cx, hooksobj, "throw", &found))
+ return false;
+ hasThrow = !!found;
dbg->hooksObject = hooksobj;
+ dbg->hasDebuggerHandler = hasDebuggerHandler;
+ dbg->hasThrowHandler = hasThrow;
vp->setUndefined();
return true;
}
JSBool
Debug::getEnabled(JSContext *cx, uintN argc, Value *vp)
{
THISOBJ(cx, vp, Debug, "get enabled", thisobj, dbg);
--- a/js/src/jsdbg.h
+++ b/js/src/jsdbg.h
@@ -56,19 +56,20 @@ class Debug {
private:
JSObject *object; // The Debug object. Strong reference.
JSCompartment *debuggeeCompartment; // Weak reference.
JSObject *hooksObject; // See Debug.prototype.hooks. Strong reference.
JSObject *uncaughtExceptionHook; // Strong reference.
bool enabled;
- // True if hooksObject had a debuggerHandler property when the hooks
+ // True if hooksObject had a property of the respective name when the hooks
// property was set.
- bool hasDebuggerHandler;
+ bool hasDebuggerHandler; // hooks.debuggerHandler
+ bool hasThrowHandler; // hooks.throw
typedef HashMap<StackFrame *, JSObject *, DefaultHasher<StackFrame *>, SystemAllocPolicy>
FrameMap;
FrameMap frames;
typedef HashMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, SystemAllocPolicy>
ObjectMap;
ObjectMap objects;
@@ -89,20 +90,28 @@ class Debug {
static JSBool setUncaughtExceptionHook(JSContext *cx, uintN argc, Value *vp);
static JSBool construct(JSContext *cx, uintN argc, Value *vp);
static JSPropertySpec properties[];
inline bool hasAnyLiveHooks() const;
static void slowPathLeaveStackFrame(JSContext *cx);
- inline bool observesDebuggerStatement() const;
- static JSTrapStatus dispatchDebuggerStatement(JSContext *cx, Value *vp);
+ typedef bool (Debug::*DebugObservesMethod)() const;
+ typedef JSTrapStatus (Debug::*DebugHandleMethod)(JSContext *, Value *) const;
+ static JSTrapStatus dispatchHook(JSContext *cx, js::Value *vp,
+ DebugObservesMethod observesEvent,
+ DebugHandleMethod handleEvent);
+
+ bool observesDebuggerStatement() const;
JSTrapStatus handleDebuggerStatement(JSContext *cx, Value *vp);
+ bool observesThrow() const;
+ JSTrapStatus handleThrow(JSContext *cx, Value *vp);
+
public:
Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment);
bool init();
inline JSObject *toJSObject() const;
static inline Debug *fromJSObject(JSObject *obj);
static Debug *fromChildJSObject(JSObject *obj);
/*********************************** Methods for interaction with the GC. */
@@ -126,16 +135,17 @@ class Debug {
static void sweepAll(JSRuntime *rt);
static void sweepCompartment(JSCompartment *compartment);
inline bool observesCompartment(JSCompartment *c) const;
void detachFrom(JSCompartment *c);
static inline void leaveStackFrame(JSContext *cx);
static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
+ static inline JSTrapStatus onThrow(JSContext *cx, js::Value *vp);
/**************************************** Functions for use by jsdbg.cpp. */
// Precondition: *vp is a value from a debuggee compartment and cx is in
// the debugger's compartment.
//
// Wrap *vp for the debugger compartment, wrap it in a Debug.Object if it's
// an object, store the result in *vp, and return true.
@@ -202,25 +212,31 @@ Debug::fromJSObject(JSObject *obj)
void
Debug::leaveStackFrame(JSContext *cx)
{
if (!cx->compartment->getDebuggers().empty())
slowPathLeaveStackFrame(cx);
}
-bool
-Debug::observesDebuggerStatement() const
-{
- return enabled && hasDebuggerHandler;
-}
-
JSTrapStatus
Debug::onDebuggerStatement(JSContext *cx, js::Value *vp)
{
return cx->compartment->getDebuggers().empty()
? JSTRAP_CONTINUE
- : dispatchDebuggerStatement(cx, vp);
+ : dispatchHook(cx, vp,
+ DebugObservesMethod(&Debug::observesDebuggerStatement),
+ DebugHandleMethod(&Debug::handleDebuggerStatement));
+}
+
+JSTrapStatus
+Debug::onThrow(JSContext *cx, js::Value *vp)
+{
+ return cx->compartment->getDebuggers().empty()
+ ? JSTRAP_CONTINUE
+ : dispatchHook(cx, vp,
+ DebugObservesMethod(&Debug::observesThrow),
+ DebugHandleMethod(&Debug::handleThrow));
}
}
#endif /* jsdbg_h__ */
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -6631,33 +6631,38 @@ END_CASE(JSOP_ARRAYPUSH)
JSThrowHook handler;
JSTryNote *tn, *tnlimit;
uint32 offset;
/* Restore atoms local in case we will resume. */
atoms = script->atomMap.vector;
/* Call debugger throw hook if set. */
- handler = cx->debugHooks->throwHook;
- if (handler) {
+ if (cx->debugHooks->throwHook || !cx->compartment->getDebuggers().empty()) {
Value rval;
- switch (handler(cx, script, regs.pc, Jsvalify(&rval),
- cx->debugHooks->throwHookData)) {
- case JSTRAP_ERROR:
+ JSTrapStatus st = Debug::onThrow(cx, &rval);
+ if (st == JSTRAP_CONTINUE) {
+ handler = cx->debugHooks->throwHook;
+ if (handler)
+ st = handler(cx, script, regs.pc, Jsvalify(&rval), cx->debugHooks->throwHookData);
+ }
+
+ switch (st) {
+ case JSTRAP_ERROR:
cx->clearPendingException();
goto error;
- case JSTRAP_RETURN:
+ case JSTRAP_RETURN:
cx->clearPendingException();
regs.fp()->setReturnValue(rval);
interpReturnOK = JS_TRUE;
goto forced_return;
- case JSTRAP_THROW:
+ case JSTRAP_THROW:
cx->setPendingException(rval);
- case JSTRAP_CONTINUE:
- default:;
+ case JSTRAP_CONTINUE:
+ default:;
}
CHECK_INTERRUPT_HANDLER();
}
/*
* Look for a try block in script that can catch this exception.
*/
if (!JSScript::isValidOffset(script->trynotesOffset))