Open Bug 1844895 Opened 1 year ago Updated 1 year ago

Assertion failure: frame.isDebuggee(), at gecko-dev/js/src/debugger/DebugAPI-inl.h:77

Categories

(Core :: JavaScript Engine: JIT, defect, P3)

Firefox 117
defect

Tracking

()

People

(Reporter: anbu1024.me, Unassigned)

References

(Blocks 1 open bug)

Details

Steps to reproduce:

SpiderMonkey version:
commit 920be8b5eee004fc10a2785fab49b860be4d4ba3

SpiderMonkey build cmd:

/bin/sh ../../gecko-dev/js/src/configure --enable-debug --disable-optimize --disable-shared-js --disable-tests

Exec:

./js --baseline-warmup-threshold=10 --ion-warmup-threshold=100 --ion-check-range-analysis --ion-extra-checks ./test.js

Test case:

for (let i = 512, j = 10;
    (() => {
        const x = i < j;
        
        function bob() {
            this.sameZoneAs = bob;
        }
        
        const alice = new bob();
        const t = this.newGlobal(alice).Debugger;
        
        class T extends t {}
        const ttt = new T();
        ttt.addAllGlobalsAsDebuggees();

        return x;
    })();
    
    (() => {j--;})()
    
    ) 
{}

async function foo(a) {
    return a;
}

const f = foo();
const ppp = f.then(foo);

for (let i = 0, j = 10;
    (() => {
        const x = i !== j;
        return x;
    })();
    (() => {
        function black(arg) {

            async function* melon(arg1) {
                try { arg1.finally(melon); } catch (e) {}
                return arg;
            }
            melon(f).next(arg);
        }

        const apple = new black(ppp);
        const banana = apple?.constructor;
        try { new banana(apple, Float32Array); } catch (e) {}
        j--;
    })()) {
    ;
}

const dbg = this.Debugger;
const obj = this.wasmIntrinsicI8VecMul(this, foo, this);

function test() {
    const a = dbg();
    const b = a.findAllGlobals();
    return b;
}

Object.defineProperty(obj, "constructor", { writable: true, configurable: true, value: test });

const cons = obj.constructor;

const x = cons();

const y = x[1];

const z = y.makeDebuggeeValue(ppp);

z.getPromiseReactions();

gc();

Actual results:

Error msg:

Assertion failure: frame.isDebuggee(), at gecko-dev/js/src/debugger/DebugAPI-inl.h:77

Thank you for reporting!

Here's reduced testcase for --baselin-eager option:

function f1() {}

const g1 = this.newGlobal({ sameZoneAs: f1 });
{
  const dbg1 = new g1.Debugger();
  dbg1.addAllGlobalsAsDebuggees();
}

const p1 = Promise.resolve();
const p2 = p1.then(() => {});

async function* f2() {
  return p2;
}
p1.finally(f2);
f2().next();

const dbg2 = new Debugger();
const globals = dbg2.findAllGlobals();
const g2 = globals[1];
const q2 = g2.makeDebuggeeValue(p2);

q2.getPromiseReactions();

gc();

What's happening here is the following:

  1. debugger dbg1 is created and it adds all globals as debuggee
  2. after leaving the block around dbg1, it's has no reference and will be collected on the next GC
  3. async generator f2 is called and gets baseline-compiled
  4. during the baseline-compilation, given the global is debugee, debug epilogue is generated [1]
  5. f2 gets suspended on implicit await in return p2
  6. DebuggerFrame is created for the suspended f2's frame in getPromiseReactions, and it creates DebugScript
  7. the debugger dbg1 gets GC-ed in gc() call, and all globals are no longer debugeee
  8. when GC-ing the debugger, the DebugScript doesn't get removed due to the DebuggerFrame above (not sure why it's not GC-ed in this case tho, explicitly keeping a reference can cause this)
  9. after the top-level script finishes, the job queue is drained
  10. f2 gets resumed, and it reaches the debug epilogue
  11. the debug epilogue hits the assertion failure because the frame isn't marked as debuggee, but the script still has the DebugScript [2]

[1] https://searchfox.org/mozilla-central/rev/920be8b5eee004fc10a2785fab49b860be4d4ba3/js/src/jit/BaselineCodeGen.cpp#4869-4870,4887,4898-4901

template <typename Handler>
bool BaselineCodeGen<Handler>::emitDebugEpilogue() {
...
    if (!callVM<Fn, jit::DebugEpilogueOnBaselineReturn>(kind)) {
...
bool BaselineCodeGen<Handler>::emitReturn() {
  if (handler.shouldEmitDebugEpilogueAtReturnOp()) {
    if (!emitDebugEpilogue()) {
      return false;

[2] https://searchfox.org/mozilla-central/rev/920be8b5eee004fc10a2785fab49b860be4d4ba3/js/src/debugger/DebugAPI-inl.h#76-77

MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(),
              frame.isDebuggee());

Anyway, if a DebugScript can be kept after the Debugger gets GC-ed, logic around that should expect that case.
So far this is just an assertion failure and it doesn't go wrong on non-debug build.

Blocks: js-debugger
Severity: -- → S4
Status: UNCONFIRMED → NEW
Ever confirmed: true
Priority: -- → P3
You need to log in before you can comment on or make changes to this bug.