Commit 7b1b91a4 authored by Julian Descottes's avatar Julian Descottes
Browse files

Bug 1834990 - Add per-realm option to allow more than 50 stacktraces r=arai

This is mostly adapted from the similar patch created to enable/disable stack traces at D150061

Differential Revision: https://phabricator.services.mozilla.com/D179243
parent dbd4f1c9
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -4144,6 +4144,8 @@ struct MOZ_STACK_CLASS Debugger::CallData {
  bool adoptSource();
  bool enableAsyncStack();
  bool disableAsyncStack();
  bool enableUnlimitedStacksCapturing();
  bool disableUnlimitedStacksCapturing();

  using Method = bool (CallData::*)();

@@ -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),
@@ -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[]{
+8 −0
Original line number Diff line number Diff line
@@ -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.
+37 −28
Original line number Diff line number Diff line
@@ -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();
+3 −2
Original line number Diff line number Diff line
@@ -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;
  }

+8 −0
Original line number Diff line number Diff line
@@ -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);