summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/debug/Frame-onStep-async-02.js
blob: 33dcdf9ba0492ab09057c8890378b30154958d47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// With enough hackery, stepping in and out of async functions can be made to
// work as users expect.
//
// This test exercises the common case when we have syntactically `await
// $ASYNC_FN($ARGS)` so that the calls nest as if they were synchronous
// calls. It works, but there's a problem.
//
// onStep fires in extra places that end users would find very confusing--see
// the comment marked (!) below. As a result, Debugger API consumers must do
// some extra work to skip pausing there. This test is a proof of concept that
// shows what sort of effort is needed. It maintains a single `asyncStack` and
// skips the onStep hook if we're not running the function at top of the async
// stack. Real debuggers would have to maintain multiple async stacks.

// Set up debuggee.
var g = newGlobal({newCompartment: true});
g.eval(`\
async function outer() {                                // line 1
    return (await inner()) + (await inner()) + "!";     // 2
}                                                       // 3
async function inner() {                                // 4
    return (await leaf()) + (await leaf());             // 5
}                                                       // 6
async function leaf() {                                 // 7
    return (await Promise.resolve("m"));                // 8
}                                                       // 9
`);

// Set up debugger.
let previousLine = -1;
let dbg = new Debugger(g);
let log = "";
let asyncStack = [];

dbg.onEnterFrame = frame => {
    assertEq(frame.type, "call");

    // If we're entering this frame for the first time, push it to the async
    // stack.
    if (!frame.seen) {
        frame.seen = true;
        asyncStack.push(frame);
        log += "(";
    }

    frame.onStep = () => {
        // When stepping, we sometimes pause at opcodes in older frames (!)
        // where all that's happening is async function administrivia.
        //
        // For example, the first time `leaf()` yields, `inner()` and
        // `outer()` are still on the stack; they haven't awaited yet because
        // control has not returned from `leaf()` to them yet. So stepping will
        // hop from line 8 to line 5 to line 2 as we unwind the stack, then
        // resume on line 8.
        //
        // Anyway: skip that noise.
        if (frame !== asyncStack[asyncStack.length - 1])
            return;

        let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
        if (previousLine != line) {
            log += line; // We stepped to a new line.
            previousLine = line;
        }
    };

    frame.onPop = completion => {
        // Popping the frame. But async function frames are popped multiple
        // times: for the "initial suspend", at each await, and on return. The
        // debugger offers no easy way to distinguish them (bug 1470558).
        // For now there's an "await" property, but bug 1470558 may come up
        // with a different solution, so don't rely on it!
        if (!completion.await) {
            // Returning (not awaiting or at initial suspend).
            assertEq(asyncStack.pop(), frame);
            log += ")";
        }
    };
};

// Run.
let result;
g.outer().then(v => { result = v; });
drainJobQueue();

assertEq(result, "mmmm!");
assertEq(log, "(12(45(789)5(789)56)2(45(789)5(789)56)23)");