diff --git a/js/src/jsdbg.cpp b/js/src/jsdbg.cpp index 717ace2781e..203f0eb7a9c 100644 --- a/js/src/jsdbg.cpp +++ b/js/src/jsdbg.cpp @@ -115,25 +115,39 @@ CheckThisClass(JSContext *cx, Value *vp, Class *clasp, const char *fnname) Debug::Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment) : object(dbg), debuggeeCompartment(compartment), hooksObject(hooks), - enabled(true), hasDebuggerHandler(false) + uncaughtExceptionHook(NULL), enabled(true), hasDebuggerHandler(false) { } JSTrapStatus -Debug::fireUncaughtExceptionHook(JSContext *cx) +Debug::handleUncaughtException(AutoCompartment &ac, Value *vp, bool callHook) { - // FIXME - JS_ReportPendingException(cx); - JS_ClearPendingException(cx); + JSContext *cx = ac.context; + if (cx->isExceptionPending()) { + if (callHook && uncaughtExceptionHook) { + Value fval = ObjectValue(*uncaughtExceptionHook); + Value exc = cx->getPendingException(); + Value rv; + cx->clearPendingException(); + if (ExternalInvoke(cx, UndefinedValue(), fval, 1, &exc, &rv)) + return parseResumptionValue(ac, true, rv, vp, false); + } + + if (cx->isExceptionPending()) { + JS_ReportPendingException(cx); + cx->clearPendingException(); + } + } return JSTRAP_ERROR; } JSTrapStatus -Debug::parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp) +Debug::parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp, + bool callHook) { vp->setUndefined(); if (!ok) - return fireUncaughtExceptionHook(ac.context); + return handleUncaughtException(ac, vp, callHook); if (rv.isUndefined()) return JSTRAP_CONTINUE; if (rv.isNull()) @@ -153,18 +167,18 @@ Debug::parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value !shape->isDataDescriptor()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_BAD_RESUMPTION); - return fireUncaughtExceptionHook(cx); + return handleUncaughtException(ac, vp, callHook); } if (!js_NativeGet(cx, obj, obj, shape, 0, vp)) - return fireUncaughtExceptionHook(cx); + return handleUncaughtException(ac, vp, callHook); // Throwing or returning objects is not yet supported. It requires // unwrapping. if (vp->isObject()) { vp->setUndefined(); NotImplemented(cx); - return fireUncaughtExceptionHook(cx); + return handleUncaughtException(ac, vp, callHook); } ac.leave(); @@ -281,8 +295,9 @@ void Debug::trace(JSTracer *trc, JSObject *obj) { if (Debug *dbg = (Debug *) obj->getPrivate()) { - if (dbg->hooksObject) - MarkObject(trc, *dbg->hooksObject, "hooks"); + MarkObject(trc, *dbg->hooksObject, "hooks"); + if (dbg->uncaughtExceptionHook) + MarkObject(trc, *dbg->uncaughtExceptionHook, "hooks"); } } @@ -325,7 +340,7 @@ JSBool Debug::getHooks(JSContext *cx, uintN argc, Value *vp) { THISOBJ(cx, vp, Debug, "get hooks", thisobj, dbg); - vp->setObjectOrNull(dbg->hooksObject); + vp->setObject(*dbg->hooksObject); return true; } @@ -366,6 +381,26 @@ Debug::setEnabled(JSContext *cx, uintN argc, Value *vp) return true; } +JSBool +Debug::getUncaughtExceptionHook(JSContext *cx, uintN argc, Value *vp) +{ + THISOBJ(cx, vp, Debug, "get uncaughtExceptionHook", thisobj, dbg); + vp->setObjectOrNull(dbg->uncaughtExceptionHook); + return true; +} + +JSBool +Debug::setUncaughtExceptionHook(JSContext *cx, uintN argc, Value *vp) +{ + REQUIRE_ARGC("Debug.set uncaughtExceptionHook", 1); + THISOBJ(cx, vp, Debug, "set uncaughtExceptionHook", thisobj, dbg); + if (!vp[2].isObjectOrNull()) + return ReportObjectRequired(cx); + dbg->uncaughtExceptionHook = vp[2].toObjectOrNull(); + vp->setUndefined(); + return true; +} + JSBool Debug::construct(JSContext *cx, uintN argc, Value *vp) { @@ -415,6 +450,8 @@ Debug::construct(JSContext *cx, uintN argc, Value *vp) JSPropertySpec Debug::properties[] = { JS_PSGS("hooks", Debug::getHooks, Debug::setHooks, 0), JS_PSGS("enabled", Debug::getEnabled, Debug::setEnabled, 0), + JS_PSGS("uncaughtExceptionHook", Debug::getUncaughtExceptionHook, + Debug::setUncaughtExceptionHook, 0), JS_PS_END }; diff --git a/js/src/jsdbg.h b/js/src/jsdbg.h index 267d2d476c8..57533f2b628 100644 --- a/js/src/jsdbg.h +++ b/js/src/jsdbg.h @@ -56,15 +56,16 @@ class Debug { 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 // property was set. bool hasDebuggerHandler; - JSTrapStatus fireUncaughtExceptionHook(JSContext *cx); - JSTrapStatus parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp); + JSTrapStatus handleUncaughtException(AutoCompartment &ac, Value *vp, bool callHook); + JSTrapStatus parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp, + bool callHook = true); static void trace(JSTracer *trc, JSObject *obj); static void finalize(JSContext *cx, JSObject *obj); @@ -74,6 +75,8 @@ class Debug { static JSBool setHooks(JSContext *cx, uintN argc, Value *vp); static JSBool getEnabled(JSContext *cx, uintN argc, Value *vp); static JSBool setEnabled(JSContext *cx, uintN argc, Value *vp); + static JSBool getUncaughtExceptionHook(JSContext *cx, uintN argc, Value *vp); + static JSBool setUncaughtExceptionHook(JSContext *cx, uintN argc, Value *vp); static JSBool construct(JSContext *cx, uintN argc, Value *vp); static JSPropertySpec properties[]; diff --git a/js/src/tests/js1_8_5/extensions/debug-object-18.js b/js/src/tests/js1_8_5/extensions/debug-object-18.js new file mode 100644 index 00000000000..abbdaece102 --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/debug-object-18.js @@ -0,0 +1,25 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +// Uncaught exceptions in the debugger itself are delivered to the +// uncaughtExceptionHook. + +var g = newGlobal('new-compartment'); +var dbg = new Debug(g); +var log; +dbg.hooks = { + debuggerHandler: function () { + log += 'x'; + throw new TypeError("fail"); + } +}; +dbg.uncaughtExceptionHook = function (exc) { + assertEq(exc instanceof TypeError, true); + log += '!'; +}; + +log = ''; +g.eval("debugger"); +assertEq(log, 'x!'); + +reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/debug-object-19.js b/js/src/tests/js1_8_5/extensions/debug-object-19.js new file mode 100644 index 00000000000..94fe0703bd3 --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/debug-object-19.js @@ -0,0 +1,34 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +// uncaughtExceptionHook returns a resumption value. + +var g = newGlobal('new-compartment'); +var dbg = new Debug(g); +var rv; +dbg.hooks = {debuggerHandler: function () { throw 15; }}; +dbg.uncaughtExceptionHook = function (exc) { + assertEq(exc, 15); + return rv; +}; + +// case 1: undefined +rv = undefined; +g.eval("debugger"); + +// case 2: throw +rv = {throw: 57}; +var result; +try { + g.eval("debugger"); + result = 'no exception thrown'; +} catch (exc) { + result = 'caught ' + exc; +} +assertEq(result, 'caught 57'); + +// case 3: return +rv = {return: 42}; +assertEq(g.eval("debugger;"), 42); + +reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/debug-object-20.js b/js/src/tests/js1_8_5/extensions/debug-object-20.js new file mode 100644 index 00000000000..939bb0a0ba8 --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/debug-object-20.js @@ -0,0 +1,32 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +// uncaughtExceptionHook resumption value other than undefined causes further +// hooks to be skipped. + +var g = newGlobal('new-compartment'); +var log; + +function makeDebug(g, name) { + var dbg = new Debug(g); + dbg.hooks = { + debuggerHandler: function () { + log += name; + throw new Error(name); + } + }; + dbg.uncaughtExceptionHook = function (exc) { + assertEq(exc.message, name); + return name == "2" ? {return: 42} : undefined; + }; +} + +var arr = []; +for (var i = 0; i < 6; i++) + arr[i] = makeDebug(g, "" + i); + +log = ''; +assertEq(g.eval("debugger;"), 42); +assertEq(log, "012"); + +reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/js1_8_5/extensions/jstests.list b/js/src/tests/js1_8_5/extensions/jstests.list index 0ea54c1165a..50c169f78bb 100644 --- a/js/src/tests/js1_8_5/extensions/jstests.list +++ b/js/src/tests/js1_8_5/extensions/jstests.list @@ -58,3 +58,6 @@ skip-if(!xulRuntime.shell) script debug-object-14.js skip-if(!xulRuntime.shell) script debug-object-15.js skip-if(!xulRuntime.shell) script debug-object-16.js skip-if(!xulRuntime.shell) script debug-object-17.js +skip-if(!xulRuntime.shell) script debug-object-18.js +skip-if(!xulRuntime.shell) script debug-object-19.js +skip-if(!xulRuntime.shell) script debug-object-20.js