diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 8d7cfa8601e2..a7adc8603de8 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -3495,6 +3495,40 @@ GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp) return GetProperty(cx, env, env, id, args.rval()); } +static bool +CatchAndReturnPendingExceptionStack(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportError(cx, "Wrong number of arguments, expected exactly 1"); + return false; + } + + if (!args[0].isObject() || !IsCallable(args[0])) { + JS_ReportError(cx, "Argument must be callable"); + return false; + } + + RootedValue thisv(cx, NullValue()); + RootedObject fun(cx, &args[0].toObject()); + RootedValue rval(cx); + if (JS::Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), &rval) || + !cx->isExceptionPending()) + { + JS_ReportError(cx, "Supplied callable did not throw"); + return false; + } + + RootedObject stack(cx); + if (!JS::GetPendingExceptionStack(cx, &stack)) + return false; + cx->clearPendingException(); + + MOZ_ASSERT_IF(stack, stack->is()); + args.rval().setObjectOrNull(stack); + return true; +} + static const JSFunctionSpecWithHelp TestingFunctions[] = { JS_FN_HELP("gc", ::GC, 0, 0, "gc([obj] | 'compartment' [, 'shrinking'])", @@ -4012,6 +4046,11 @@ gc::ZealModeHelpText), "getModuleEnvironmentValue(module, name)", " Get the value of a bound name in a module environment.\n"), + JS_FN_HELP("catchAndReturnPendingExceptionStack", CatchAndReturnPendingExceptionStack, 1, 0, +"catchAndReturnPendingExceptionStack(func)", +" Call `func`, catch its thrown exception, and return the stack captured when\n" +" the exception was thrown.\n"), + JS_FS_HELP_END }; diff --git a/js/src/jit-test/tests/basic/pending-exception-stack-2.js b/js/src/jit-test/tests/basic/pending-exception-stack-2.js new file mode 100644 index 000000000000..5efd5311bc6f --- /dev/null +++ b/js/src/jit-test/tests/basic/pending-exception-stack-2.js @@ -0,0 +1,20 @@ +let err; +const stack = catchAndReturnPendingExceptionStack(function a() { + err = new Error(); + (function b() { + (function c() { + throw err; + }()); + }()); +}); + +assertEq(!!err, true); + +// Yes, they're different! +assertEq(stack.toString() != err.stack, true); + +assertEq(stack.functionDisplayName, "c"); +assertEq(stack.parent.functionDisplayName, "b"); +assertEq(stack.parent.parent.functionDisplayName, "a"); +assertEq(stack.parent.parent.parent.functionDisplayName, null); +assertEq(stack.parent.parent.parent.parent, null); diff --git a/js/src/jit-test/tests/basic/pending-exception-stack.js b/js/src/jit-test/tests/basic/pending-exception-stack.js new file mode 100644 index 000000000000..8e936a47a285 --- /dev/null +++ b/js/src/jit-test/tests/basic/pending-exception-stack.js @@ -0,0 +1,13 @@ +const stack = catchAndReturnPendingExceptionStack(function a() { + (function b() { + (function c() { + throw 42; + }()); + }()); +}); + +assertEq(stack.functionDisplayName, "c"); +assertEq(stack.parent.functionDisplayName, "b"); +assertEq(stack.parent.parent.functionDisplayName, "a"); +assertEq(stack.parent.parent.parent.functionDisplayName, null); +assertEq(stack.parent.parent.parent.parent, null);