Completion.cpp 6.4 KB

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