1 /*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/js_promise.h"
17
18 #include "ecmascript/builtins/builtins_promise_handler.h"
19 #include "ecmascript/global_env.h"
20 #include "ecmascript/interpreter/interpreter.h"
21 #include "ecmascript/jobs/micro_job_queue.h"
22
23 namespace panda::ecmascript {
24 using BuiltinsPromiseHandler = builtins::BuiltinsPromiseHandler;
25
CreateResolvingFunctions(JSThread * thread,const JSHandle<JSPromise> & promise)26 JSHandle<ResolvingFunctionsRecord> JSPromise::CreateResolvingFunctions(JSThread *thread,
27 const JSHandle<JSPromise> &promise)
28 {
29 if (thread->GetEcmaVM()->GetJSOptions().EnablePendingCheak()) {
30 thread->GetEcmaVM()->InsertAsyncStackTrace(promise);
31 }
32 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
33 // 1. Let alreadyResolved be a new Record { [[value]]: false }.
34 JSHandle<PromiseRecord> record = factory->NewPromiseRecord();
35 record->SetValue(thread, JSTaggedValue::False());
36
37 // 2. Let resolve be a new built-in function object as defined in Promise Resolve Functions (25.4.1.3.2).
38 JSHandle<JSPromiseReactionsFunction> resolve = factory->CreateJSPromiseReactionsFunction(
39 MethodIndex::BUILTINS_PROMISE_HANDLER_RESOLVE);
40 // 3. Set the [[Promise]] internal slot of resolve to promise.
41 resolve->SetPromise(thread, promise);
42 // 4. Set the [[AlreadyResolved]] internal slot of resolve to alreadyResolved.
43 resolve->SetAlreadyResolved(thread, record);
44 // 5. Let reject be a new built-in function object as defined in Promise Reject Functions (25.4.1.3.1).
45 JSHandle<JSPromiseReactionsFunction> reject = factory->CreateJSPromiseReactionsFunction(
46 MethodIndex::BUILTINS_PROMISE_HANDLER_REJECT);
47 // 6. Set the [[Promise]] internal slot of reject to promise.
48 reject->SetPromise(thread, promise);
49 // 7. Set the [[AlreadyResolved]] internal slot of reject to alreadyResolved.
50 reject->SetAlreadyResolved(thread, record);
51 // 8. Return a new Record { [[Resolve]]: resolve, [[Reject]]: reject }.
52 JSHandle<ResolvingFunctionsRecord> reactions = factory->NewResolvingFunctionsRecord();
53 reactions->SetResolveFunction(thread, resolve.GetTaggedValue());
54 reactions->SetRejectFunction(thread, reject.GetTaggedValue());
55 return reactions;
56 }
57
FulfillPromise(JSThread * thread,const JSHandle<JSPromise> & promise,const JSHandle<JSTaggedValue> & value)58 JSTaggedValue JSPromise::FulfillPromise(JSThread *thread, const JSHandle<JSPromise> &promise,
59 const JSHandle<JSTaggedValue> &value)
60 {
61 if (thread->GetEcmaVM()->GetJSOptions().EnablePendingCheak()) {
62 thread->GetEcmaVM()->RemoveAsyncStackTrace(promise);
63 }
64 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
65 // 1. Assert: the value of promise's [[PromiseState]] internal slot is "pending".
66 ASSERT_PRINT(promise->GetPromiseState() == PromiseState::PENDING, "FulfillPromise: state must be pending");
67 // 2. Let reactions be the value of promise's [[PromiseFulfillReactions]] internal slot.
68 JSHandle<TaggedQueue> reactions(thread, promise->GetPromiseFulfillReactions());
69 // 3. Set the value of promise's [[PromiseResult]] internal slot to value.
70 promise->SetPromiseResult(thread, value);
71 // 4. Set the value of promise's [[PromiseFulfillReactions]] internal slot to undefined.
72 promise->SetPromiseFulfillReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
73 // 5. Set the value of promise's [[PromiseRejectReactions]] internal slot to undefined.
74 promise->SetPromiseRejectReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
75 // 6. Set the value of promise's [[PromiseState]] internal slot to "fulfilled".
76 promise->SetPromiseState(PromiseState::FULFILLED);
77 // 7. Return TriggerPromiseReactions(reactions, reason).
78 return TriggerPromiseReactions(thread, reactions, value);
79 }
80
NewPromiseCapability(JSThread * thread,const JSHandle<JSTaggedValue> & obj)81 JSHandle<PromiseCapability> JSPromise::NewPromiseCapability(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
82 {
83 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
84 // 1. If IsConstructor(C) is false, throw a TypeError exception.
85 if (!obj->IsConstructor()) {
86 THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: obj is not constructor!",
87 factory->NewPromiseCapability());
88 }
89 // 2. NOTE C is assumed to be a constructor function that supports the parameter conventions of the Promise
90 // constructor (see 25.4.3.1).
91 // 3. Let promiseCapability be a new PromiseCapability { [[Promise]]: undefined, [[Resolve]]: undefined,
92 // [[Reject]]: undefined }.
93 JSHandle<PromiseCapability> promiseCapability = factory->NewPromiseCapability();
94 // 4. Let executor be a new built-in function object as defined in GetCapabilitiesExecutor Functions
95 // (25.4.1.5.1).
96 JSHandle<JSPromiseExecutorFunction> executor = factory->CreateJSPromiseExecutorFunction();
97 // 5. Set the [[Capability]] internal slot of executor to promiseCapability.
98 executor->SetCapability(thread, promiseCapability.GetTaggedValue());
99 // 6. Let promise be Construct(C, «executor»).
100 // 7. ReturnIfAbrupt(promise).
101 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
102 EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, obj, undefined, undefined, 1);
103 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, factory->NewPromiseCapability());
104 info->SetCallArg(executor.GetTaggedValue());
105 JSTaggedValue result = JSFunction::Construct(info);
106 JSHandle<JSPromise> promise(thread, result);
107 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, factory->NewPromiseCapability());
108 // 8. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
109 if (!promiseCapability->GetResolve().IsCallable()) {
110 THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: resolve is not a callable function!",
111 factory->NewPromiseCapability());
112 }
113 // 9. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
114 if (!promiseCapability->GetReject().IsCallable()) {
115 THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: reject is not a callable function!",
116 factory->NewPromiseCapability());
117 }
118 // 10. Set promiseCapability.[[Promise]] to promise.
119 promiseCapability->SetPromise(thread, promise);
120 // 11. Return promiseCapability.
121 return promiseCapability;
122 }
123
IsPromise(const JSHandle<JSTaggedValue> & value)124 bool JSPromise::IsPromise(const JSHandle<JSTaggedValue> &value)
125 {
126 // 1. If Type(x) is not Object, return false.
127 if (!value->IsECMAObject()) {
128 return false;
129 }
130 // 2. If x does not have a [[PromiseState]] internal slot, return false.
131 if (!value->IsJSPromise()) {
132 return false;
133 }
134 // 3. Return true
135 return true;
136 }
137
RejectPromise(JSThread * thread,const JSHandle<JSPromise> & promise,const JSHandle<JSTaggedValue> & reason)138 JSTaggedValue JSPromise::RejectPromise(JSThread *thread, const JSHandle<JSPromise> &promise,
139 const JSHandle<JSTaggedValue> &reason)
140 {
141 if (thread->GetEcmaVM()->GetJSOptions().EnablePendingCheak()) {
142 thread->GetEcmaVM()->RemoveAsyncStackTrace(promise);
143 }
144 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
145 // 1. Assert: the value of promise's [[PromiseState]] internal slot is "pending".
146 ASSERT_PRINT(promise->GetPromiseState() == PromiseState::PENDING, "RejectPromise: state must be pending");
147 // 2. Let reactions be the value of promise's [[PromiseRejectReactions]] internal slot.
148 JSHandle<TaggedQueue> reactions(thread, TaggedQueue::Cast(promise->GetPromiseRejectReactions().GetTaggedObject()));
149 // 3. Set the value of promise's [[PromiseResult]] internal slot to reason.
150 promise->SetPromiseResult(thread, reason);
151 // 4. Set the value of promise's [[PromiseFulfillReactions]] internal slot to undefined.
152 promise->SetPromiseFulfillReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
153 // 5. Set the value of promise's [[PromiseRejectReactions]] internal slot to undefined.
154 promise->SetPromiseRejectReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
155 // 6. Set the value of promise's [[PromiseState]] internal slot to "rejected".
156 promise->SetPromiseState(PromiseState::REJECTED);
157 // 7. When a promise is rejected without any handlers, it is called with its operation argument set to "reject".
158 if (!promise->GetPromiseIsHandled()) {
159 thread->GetCurrentEcmaContext()->PromiseRejectionTracker(promise, reason, PromiseRejectionEvent::REJECT);
160 }
161 // 8. Return TriggerPromiseReactions(reactions, reason).
162 return TriggerPromiseReactions(thread, reactions, reason);
163 }
164
TriggerPromiseReactions(JSThread * thread,const JSHandle<TaggedQueue> & reactions,const JSHandle<JSTaggedValue> & argument)165 JSTaggedValue JSPromise::TriggerPromiseReactions(JSThread *thread, const JSHandle<TaggedQueue> &reactions,
166 const JSHandle<JSTaggedValue> &argument)
167 {
168 // 1. Repeat for each reaction in reactions, in original insertion order
169 // a. Perform EnqueueJob("PromiseJobs", PromiseReactionJob, «reaction, argument»).
170 JSHandle<job::MicroJobQueue> job = thread->GetCurrentEcmaContext()->GetMicroJobQueue();
171 JSHandle<GlobalEnv> globalEnv = thread->GetEcmaVM()->GetGlobalEnv();
172 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
173 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
174 JSHandle<JSFunction> promiseReactionsJob(globalEnv->GetPromiseReactionJob());
175 JSMutableHandle<PromiseReaction> reaction(thread, JSTaggedValue::Undefined());
176 while (!reactions->Empty()) {
177 reaction.Update(reactions->Pop(thread));
178 JSHandle<TaggedArray> arguments = factory->NewTaggedArray(2); // 2 means the length of new array
179 arguments->Set(thread, 0, reaction);
180 arguments->Set(thread, 1, argument);
181 job::MicroJobQueue::EnqueueJob(thread, job, job::QueueType::QUEUE_PROMISE, promiseReactionsJob, arguments);
182 }
183 // 2. Return undefined.
184 return globalConst->GetUndefined();
185 }
186
IfThrowGetThrowValue(JSThread * thread)187 JSHandle<JSTaggedValue> JSPromise::IfThrowGetThrowValue(JSThread *thread)
188 {
189 return JSHandle<JSTaggedValue>(thread, thread->GetException());
190 }
191 } // namespace panda::ecmascript
192