Coverage Report

Created: 2025-08-28 06:26

/src/serenity/Userland/Libraries/LibJS/Runtime/Completion.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
3
 * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <AK/TypeCasts.h>
9
#include <LibJS/Runtime/Completion.h>
10
#include <LibJS/Runtime/NativeFunction.h>
11
#include <LibJS/Runtime/Promise.h>
12
#include <LibJS/Runtime/PromiseCapability.h>
13
#include <LibJS/Runtime/PromiseConstructor.h>
14
#include <LibJS/Runtime/VM.h>
15
#include <LibJS/Runtime/Value.h>
16
17
namespace JS {
18
19
bool g_log_all_js_exceptions = false;
20
21
Completion::Completion(ThrowCompletionOr<Value> const& throw_completion_or_value)
22
0
{
23
0
    if (throw_completion_or_value.is_throw_completion()) {
24
0
        m_type = Type::Throw;
25
0
        m_value = throw_completion_or_value.throw_completion().value();
26
0
    } else {
27
0
        m_type = Type::Normal;
28
0
        m_value = throw_completion_or_value.value();
29
0
    }
30
0
}
31
32
// 6.2.3.1 Await, https://tc39.es/ecma262/#await
33
// FIXME: This no longer matches the spec!
34
ThrowCompletionOr<Value> await(VM& vm, Value value)
35
0
{
36
0
    auto& realm = *vm.current_realm();
37
38
    // 1. Let asyncContext be the running execution context.
39
    // NOTE: This is not needed, as we don't suspend anything.
40
41
    // 2. Let promise be ? PromiseResolve(%Promise%, value).
42
0
    auto* promise_object = TRY(promise_resolve(vm, realm.intrinsics().promise_constructor(), value));
43
44
0
    IGNORE_USE_IN_ESCAPING_LAMBDA Optional<bool> success;
45
0
    IGNORE_USE_IN_ESCAPING_LAMBDA Value result;
46
47
    // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
48
0
    auto fulfilled_closure = [&success, &result](VM& vm) -> ThrowCompletionOr<Value> {
49
        // a. Let prevContext be the running execution context.
50
        // b. Suspend prevContext.
51
        // FIXME: We don't have this concept yet.
52
53
        // NOTE: Since we don't support context suspension, we exfiltrate the result to await()'s scope instead
54
0
        success = true;
55
0
        result = vm.argument(0);
56
57
        // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
58
        // NOTE: This is not done, because we're not suspending anything (see above).
59
60
        // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
61
        // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
62
        // FIXME: We don't have this concept yet.
63
64
        // f. Return undefined.
65
0
        return js_undefined();
66
0
    };
67
68
    // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
69
0
    auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, "");
70
71
    // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called:
72
0
    auto rejected_closure = [&success, &result](VM& vm) -> ThrowCompletionOr<Value> {
73
        // a. Let prevContext be the running execution context.
74
        // b. Suspend prevContext.
75
        // FIXME: We don't have this concept yet.
76
77
        // NOTE: Since we don't support context suspension, we exfiltrate the result to await()'s scope instead
78
0
        success = false;
79
0
        result = vm.argument(0);
80
81
        // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
82
        // NOTE: This is not done, because we're not suspending anything (see above).
83
84
        // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
85
        // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
86
        // FIXME: We don't have this concept yet.
87
88
        // f. Return undefined.
89
0
        return js_undefined();
90
0
    };
91
92
    // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
93
0
    auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, "");
94
95
    // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
96
0
    auto promise = verify_cast<Promise>(promise_object);
97
0
    promise->perform_then(on_fulfilled, on_rejected, {});
98
99
    // FIXME: Since we don't support context suspension, we attempt to "wait" for the promise to resolve
100
    //        by letting the event loop spin until our promise is no longer pending, and then synchronously
101
    //        running all queued promise jobs.
102
    // Note: This is not used by LibJS itself, and is performed for the embedder (i.e. LibWeb).
103
0
    if (auto* custom_data = vm.custom_data()) {
104
0
        custom_data->spin_event_loop_until([&] {
105
0
            return success.has_value();
106
0
        });
107
0
    }
108
109
    // 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
110
    // NOTE: Since we don't push any EC, this step is not performed.
111
112
    // 9. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion Record completion, the following steps of the algorithm that invoked Await will be performed, with completion available.
113
    // 10. Return NormalCompletion(unused).
114
    // 11. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext.
115
116
0
    vm.run_queued_promise_jobs();
117
118
    // Make sure that the promise _actually_ resolved.
119
    // Note that this is checked down the chain (result.is_empty()) anyway, but let's make the source of the issue more clear.
120
0
    VERIFY(success.has_value());
121
122
0
    if (success.value())
123
0
        return result;
124
0
    return throw_completion(result);
125
0
}
126
127
static void log_exception(Value value)
128
0
{
129
0
    if (!value.is_object()) {
130
0
        dbgln("\033[31;1mTHROW!\033[0m {}", value);
131
0
        return;
132
0
    }
133
134
0
    auto& object = value.as_object();
135
0
    auto& vm = object.vm();
136
0
    dbgln("\033[31;1mTHROW!\033[0m {}", object.get(vm.names.message).value());
137
0
    vm.dump_backtrace();
138
0
}
139
140
// 6.2.4.2 ThrowCompletion ( value ), https://tc39.es/ecma262/#sec-throwcompletion
141
Completion throw_completion(Value value)
142
18
{
143
18
    if (g_log_all_js_exceptions)
144
0
        log_exception(value);
145
146
    // 1. Return Completion Record { [[Type]]: throw, [[Value]]: value, [[Target]]: empty }.
147
18
    return { Completion::Type::Throw, value };
148
18
}
149
150
}