/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 | | } |