Loading js/src/debugger/Debugger.cpp +36 −0 Original line number Diff line number Diff line Loading @@ -4144,6 +4144,8 @@ struct MOZ_STACK_CLASS Debugger::CallData { bool adoptSource(); bool enableAsyncStack(); bool disableAsyncStack(); bool enableUnlimitedStacksCapturing(); bool disableUnlimitedStacksCapturing(); using Method = bool (CallData::*)(); Loading Loading @@ -6332,6 +6334,36 @@ bool Debugger::CallData::disableAsyncStack() { return true; } bool Debugger::CallData::enableUnlimitedStacksCapturing() { if (!args.requireAtLeast(cx, "Debugger.enableUnlimitedStacksCapturing", 1)) { return false; } Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); if (!global) { return false; } global->realm()->isUnlimitedStacksCapturingEnabled = true; args.rval().setUndefined(); return true; } bool Debugger::CallData::disableUnlimitedStacksCapturing() { if (!args.requireAtLeast(cx, "Debugger.disableUnlimitedStacksCapturing", 1)) { return false; } Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); if (!global) { return false; } global->realm()->isUnlimitedStacksCapturingEnabled = false; args.rval().setUndefined(); return true; } const JSPropertySpec Debugger::properties[] = { JS_DEBUG_PSGS("onDebuggerStatement", getOnDebuggerStatement, setOnDebuggerStatement), Loading Loading @@ -6376,6 +6408,10 @@ const JSFunctionSpec Debugger::methods[] = { JS_DEBUG_FN("adoptSource", adoptSource, 1), JS_DEBUG_FN("enableAsyncStack", enableAsyncStack, 1), JS_DEBUG_FN("disableAsyncStack", disableAsyncStack, 1), JS_DEBUG_FN("enableUnlimitedStacksCapturing", enableUnlimitedStacksCapturing, 1), JS_DEBUG_FN("disableUnlimitedStacksCapturing", disableUnlimitedStacksCapturing, 1), JS_FS_END}; const JSFunctionSpec Debugger::static_methods[]{ Loading js/src/doc/Debugger/Debugger.md +8 −0 Original line number Diff line number Diff line Loading @@ -526,6 +526,14 @@ Enable async stack capturing for the realm for the global object designated by Disable async stack capturing for the realm for the global object designated by <i>global</i>. ### `enableUnlimitedStacksCapturing(global)` Allow to capture more than 50 stacktraces for the realm for the global object designated by <i>global</i>, even if it is not a debuggee. ### `disableUnlimitedStacksCapturing(global)` Disallow to capture more than 50 stacktraces for the realm for the global object designated by <i>global</i>, unless it is a debuggee. ## Static methods of the Debugger Object The functions described below are not called with a `this` value. Loading js/src/jit-test/tests/basic/throw-exception-stack.js +37 −28 Original line number Diff line number Diff line Loading @@ -18,50 +18,59 @@ function testTestingFunction() { } testTestingFunction(); // Debuggee globals always get an exception stack. function testDebuggee() { let g = newGlobal({newCompartment: true}); let dbg = new Debugger(g); g.evaluate("(" + function() { /** * Check that the expected number of stack traces are generated for a given * global where 100 "throws" are generated */ function assertStacksCount(global, expectedStacksCount) { global.evaluate("(" + function(_expectedStacksCount) { let thrower = () => { throw 123; }; for (let i = 0; i < 100; i++) { let info = getExceptionInfo(thrower); assertEq(info.exception, 123); // NOTE: if this ever gets increased, update the tests above too! if (i <= _expectedStacksCount) { assertEq(info.stack.includes("thrower@"), true); } else { assertEq(info.stack, null); } } } + `)(${expectedStacksCount})`); } } + ")()"); // Debuggee globals always get an exception stack. function testDebuggee() { let g = newGlobal({newCompartment: true}); let dbg = new Debugger(g); assertStacksCount(g, 100); } testDebuggee(); // Globals with trusted principals always get an exception stack. function testTrustedPrincipals() { let g = newGlobal({newCompartment: true, systemPrincipal: true}); g.evaluate("(" + function() { let thrower = () => { throw 123; }; for (let i = 0; i < 100; i++) { let info = getExceptionInfo(thrower); assertEq(info.exception, 123); assertEq(info.stack.includes("thrower@"), true); } } + ")()"); assertStacksCount(g, 100); } testTrustedPrincipals(); // In normal cases, a stack is captured only for the first 50 exceptions per realm. function testNormal() { let g = newGlobal(); g.evaluate("(" + function() { let thrower = () => { throw 123; }; for (let i = 0; i < 100; i++) { let info = getExceptionInfo(thrower); assertEq(info.exception, 123); // NOTE: if this ever gets increased, update the tests above too! if (i <= 50) { assertEq(info.stack.includes("thrower@"), true); } else { assertEq(info.stack, null); } } } + ")()"); assertStacksCount(g, 50); } testNormal(); // Non debuggee with unlimited stacks capturing enabled should always get a stack. function testEnableUnlimitedStacksCapturing() { let dbg = new Debugger(); let g = newGlobal(); dbg.enableUnlimitedStacksCapturing(g); assertStacksCount(g, 100); dbg.disableUnlimitedStacksCapturing(g); assertStacksCount(g, 50); dbg.enableUnlimitedStacksCapturing(g); assertStacksCount(g, 100); } testEnableUnlimitedStacksCapturing(); js/src/vm/Realm.cpp +3 −2 Original line number Diff line number Diff line Loading @@ -558,8 +558,9 @@ bool Realm::shouldCaptureStackForThrow() { // relevant for uncaught exceptions that are not Error objects. // To match other browsers, we always capture a stack trace if the realm is a // debuggee (this includes the devtools console being open). if (isDebuggee()) { // debuggee (this includes the devtools console being open) or if unlimited // stack traces have been enabled for this realm (used in automation). if (isDebuggee() || isUnlimitedStacksCapturingEnabled) { return true; } Loading js/src/vm/Realm.h +8 −0 Original line number Diff line number Diff line Loading @@ -381,6 +381,14 @@ class JS::Realm : public JS::shadow::Realm { // features are used. bool isAsyncStackCapturingEnabled = false; // Allow to collect more than 50 stack traces for throw even if the global is // not a debuggee. // // Similarly to isAsyncStackCapturingEnabled, this is a lightweight // alternative for making the global a debuggee, when no actual debugging // features are required. bool isUnlimitedStacksCapturingEnabled = false; private: void updateDebuggerObservesFlag(unsigned flag); Loading Loading
js/src/debugger/Debugger.cpp +36 −0 Original line number Diff line number Diff line Loading @@ -4144,6 +4144,8 @@ struct MOZ_STACK_CLASS Debugger::CallData { bool adoptSource(); bool enableAsyncStack(); bool disableAsyncStack(); bool enableUnlimitedStacksCapturing(); bool disableUnlimitedStacksCapturing(); using Method = bool (CallData::*)(); Loading Loading @@ -6332,6 +6334,36 @@ bool Debugger::CallData::disableAsyncStack() { return true; } bool Debugger::CallData::enableUnlimitedStacksCapturing() { if (!args.requireAtLeast(cx, "Debugger.enableUnlimitedStacksCapturing", 1)) { return false; } Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); if (!global) { return false; } global->realm()->isUnlimitedStacksCapturingEnabled = true; args.rval().setUndefined(); return true; } bool Debugger::CallData::disableUnlimitedStacksCapturing() { if (!args.requireAtLeast(cx, "Debugger.disableUnlimitedStacksCapturing", 1)) { return false; } Rooted<GlobalObject*> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); if (!global) { return false; } global->realm()->isUnlimitedStacksCapturingEnabled = false; args.rval().setUndefined(); return true; } const JSPropertySpec Debugger::properties[] = { JS_DEBUG_PSGS("onDebuggerStatement", getOnDebuggerStatement, setOnDebuggerStatement), Loading Loading @@ -6376,6 +6408,10 @@ const JSFunctionSpec Debugger::methods[] = { JS_DEBUG_FN("adoptSource", adoptSource, 1), JS_DEBUG_FN("enableAsyncStack", enableAsyncStack, 1), JS_DEBUG_FN("disableAsyncStack", disableAsyncStack, 1), JS_DEBUG_FN("enableUnlimitedStacksCapturing", enableUnlimitedStacksCapturing, 1), JS_DEBUG_FN("disableUnlimitedStacksCapturing", disableUnlimitedStacksCapturing, 1), JS_FS_END}; const JSFunctionSpec Debugger::static_methods[]{ Loading
js/src/doc/Debugger/Debugger.md +8 −0 Original line number Diff line number Diff line Loading @@ -526,6 +526,14 @@ Enable async stack capturing for the realm for the global object designated by Disable async stack capturing for the realm for the global object designated by <i>global</i>. ### `enableUnlimitedStacksCapturing(global)` Allow to capture more than 50 stacktraces for the realm for the global object designated by <i>global</i>, even if it is not a debuggee. ### `disableUnlimitedStacksCapturing(global)` Disallow to capture more than 50 stacktraces for the realm for the global object designated by <i>global</i>, unless it is a debuggee. ## Static methods of the Debugger Object The functions described below are not called with a `this` value. Loading
js/src/jit-test/tests/basic/throw-exception-stack.js +37 −28 Original line number Diff line number Diff line Loading @@ -18,50 +18,59 @@ function testTestingFunction() { } testTestingFunction(); // Debuggee globals always get an exception stack. function testDebuggee() { let g = newGlobal({newCompartment: true}); let dbg = new Debugger(g); g.evaluate("(" + function() { /** * Check that the expected number of stack traces are generated for a given * global where 100 "throws" are generated */ function assertStacksCount(global, expectedStacksCount) { global.evaluate("(" + function(_expectedStacksCount) { let thrower = () => { throw 123; }; for (let i = 0; i < 100; i++) { let info = getExceptionInfo(thrower); assertEq(info.exception, 123); // NOTE: if this ever gets increased, update the tests above too! if (i <= _expectedStacksCount) { assertEq(info.stack.includes("thrower@"), true); } else { assertEq(info.stack, null); } } } + `)(${expectedStacksCount})`); } } + ")()"); // Debuggee globals always get an exception stack. function testDebuggee() { let g = newGlobal({newCompartment: true}); let dbg = new Debugger(g); assertStacksCount(g, 100); } testDebuggee(); // Globals with trusted principals always get an exception stack. function testTrustedPrincipals() { let g = newGlobal({newCompartment: true, systemPrincipal: true}); g.evaluate("(" + function() { let thrower = () => { throw 123; }; for (let i = 0; i < 100; i++) { let info = getExceptionInfo(thrower); assertEq(info.exception, 123); assertEq(info.stack.includes("thrower@"), true); } } + ")()"); assertStacksCount(g, 100); } testTrustedPrincipals(); // In normal cases, a stack is captured only for the first 50 exceptions per realm. function testNormal() { let g = newGlobal(); g.evaluate("(" + function() { let thrower = () => { throw 123; }; for (let i = 0; i < 100; i++) { let info = getExceptionInfo(thrower); assertEq(info.exception, 123); // NOTE: if this ever gets increased, update the tests above too! if (i <= 50) { assertEq(info.stack.includes("thrower@"), true); } else { assertEq(info.stack, null); } } } + ")()"); assertStacksCount(g, 50); } testNormal(); // Non debuggee with unlimited stacks capturing enabled should always get a stack. function testEnableUnlimitedStacksCapturing() { let dbg = new Debugger(); let g = newGlobal(); dbg.enableUnlimitedStacksCapturing(g); assertStacksCount(g, 100); dbg.disableUnlimitedStacksCapturing(g); assertStacksCount(g, 50); dbg.enableUnlimitedStacksCapturing(g); assertStacksCount(g, 100); } testEnableUnlimitedStacksCapturing();
js/src/vm/Realm.cpp +3 −2 Original line number Diff line number Diff line Loading @@ -558,8 +558,9 @@ bool Realm::shouldCaptureStackForThrow() { // relevant for uncaught exceptions that are not Error objects. // To match other browsers, we always capture a stack trace if the realm is a // debuggee (this includes the devtools console being open). if (isDebuggee()) { // debuggee (this includes the devtools console being open) or if unlimited // stack traces have been enabled for this realm (used in automation). if (isDebuggee() || isUnlimitedStacksCapturingEnabled) { return true; } Loading
js/src/vm/Realm.h +8 −0 Original line number Diff line number Diff line Loading @@ -381,6 +381,14 @@ class JS::Realm : public JS::shadow::Realm { // features are used. bool isAsyncStackCapturingEnabled = false; // Allow to collect more than 50 stack traces for throw even if the global is // not a debuggee. // // Similarly to isAsyncStackCapturingEnabled, this is a lightweight // alternative for making the global a debuggee, when no actual debugging // features are required. bool isUnlimitedStacksCapturingEnabled = false; private: void updateDebuggerObservesFlag(unsigned flag); Loading