* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/js_async_generator_object.h"
#include "ecmascript/async_generator_helper.h"
#include "ecmascript/builtins/builtins_promise.h"
#include "ecmascript/global_env.h"
#include "ecmascript/interpreter/interpreter.h"
#include "ecmascript/js_iterator.h"
namespace panda::ecmascript {
using BuiltinsPromise = builtins::BuiltinsPromise;
void JSAsyncGeneratorObject::AsyncGeneratorValidate(JSThread *thread, const JSHandle<JSTaggedValue> &gen,
const JSTaggedValue &val)
{
if (!gen->IsAsyncGeneratorObject()) {
THROW_TYPE_ERROR(thread, "Not a asyncgenerator object");
}
JSHandle<JSObject> obj = JSTaggedValue::ToObject(thread, gen);
RETURN_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSAsyncGeneratorObject> generator = JSHandle<JSAsyncGeneratorObject>::Cast(obj);
if (!JSTaggedValue::SameValue(thread, generator->GetGeneratorBrand(thread), val)) {
THROW_TYPE_ERROR(thread, "Results are not equal");
}
}
JSTaggedValue JSAsyncGeneratorObject::AsyncGeneratorResolve(JSThread *thread,
const JSHandle<JSAsyncGeneratorObject> &generator,
const JSHandle<JSTaggedValue> value, bool done)
{
ASSERT(generator->IsAsyncGeneratorObject());
JSHandle<TaggedQueue> queue(thread, generator->GetAsyncGeneratorQueue(thread));
ASSERT(!(queue->Empty(thread)));
JSHandle<AsyncGeneratorRequest> next(thread, queue->Front(thread));
queue->Pop(thread);
JSHandle<PromiseCapability> capability(thread, next->GetCapability(thread));
JSHandle<JSObject> iteratorResult = JSIterator::CreateIterResultObject(thread, value, done);
JSHandle<JSTaggedValue> its = JSHandle<JSTaggedValue>::Cast(iteratorResult);
JSHandle<JSTaggedValue> resolve(thread, capability->GetResolve(thread));
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo* info =
EcmaInterpreter::NewRuntimeCallInfo(thread, resolve, undefined, undefined, 1, StackCheck::NO);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(its.GetTaggedValue());
[[maybe_unused]] JSTaggedValue res = JSFunction::Call(info);
if (thread->HasPendingException()) {
[[maybe_unused]] JSType errorType = thread->GetException().GetTaggedObject()->GetClass()->GetObjectType();
ASSERT(errorType == JSType::JS_RANGE_ERROR);
thread->ClearException();
return JSTaggedValue::Undefined();
}
AsyncGeneratorResumeNext(thread, generator);
return JSTaggedValue::Undefined();
}
JSTaggedValue JSAsyncGeneratorObject::AsyncGeneratorReject(JSThread *thread,
const JSHandle<JSAsyncGeneratorObject> &generator,
const JSHandle<JSTaggedValue> value)
{
ASSERT(generator->IsAsyncGeneratorObject());
JSHandle<TaggedQueue> queue(thread, generator->GetAsyncGeneratorQueue(thread));
ASSERT(!(queue->Empty(thread)));
JSHandle<JSTaggedValue> val(thread, queue->Front(thread));
JSHandle<AsyncGeneratorRequest> next = JSHandle<AsyncGeneratorRequest>::Cast(val);
queue->Pop(thread);
JSHandle<PromiseCapability> capability(thread, next->GetCapability(thread));
JSHandle<JSTaggedValue> reject(thread, capability->GetReject(thread));
const GlobalEnvConstants *constants = thread->GlobalConstants();
const JSHandle<JSTaggedValue> thisArg = constants->GetHandledUndefined();
const JSHandle<JSTaggedValue> undefined = constants->GetHandledUndefined();
EcmaRuntimeCallInfo* info =
EcmaInterpreter::NewRuntimeCallInfo(thread, reject, thisArg, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(value.GetTaggedValue());
[[maybe_unused]] JSTaggedValue res = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
AsyncGeneratorResumeNext(thread, generator);
return JSTaggedValue::Undefined();
}
JSTaggedValue JSAsyncGeneratorObject::AsyncGeneratorResumeNext(JSThread *thread,
const JSHandle<JSAsyncGeneratorObject> &generator)
{
ASSERT(generator->IsAsyncGeneratorObject());
JSAsyncGeneratorState state = generator->GetAsyncGeneratorState();
ASSERT(state != JSAsyncGeneratorState::EXECUTING);
if (state == JSAsyncGeneratorState::AWAITING_RETURN) {
return JSTaggedValue::Undefined();
}
JSHandle<TaggedQueue> queue(thread, generator->GetAsyncGeneratorQueue(thread));
if (queue->Empty(thread)) {
return JSTaggedValue::Undefined();
}
JSHandle<AsyncGeneratorRequest> next(thread, queue->Front(thread));
ASSERT(next->GetClass()->IsAsyncGeneratorRequest());
JSTaggedValue rcd = next->GetCompletion(thread);
JSHandle<CompletionRecord> completion(thread, rcd);
CompletionRecordType type = completion->GetType();
if (thread->HasPendingException() || type != CompletionRecordType::NORMAL) {
if (state == JSAsyncGeneratorState::SUSPENDED_START) {
state = JSAsyncGeneratorState::COMPLETED;
generator->SetAsyncGeneratorState(state);
}
if (state == JSAsyncGeneratorState::COMPLETED) {
if (completion->GetType() == CompletionRecordType::RETURN) {
generator->SetAsyncGeneratorState(JSAsyncGeneratorState::AWAITING_RETURN);
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<JSTaggedValue> val(thread, completion->GetValue(thread));
JSTaggedValue promise = PromiseResolve(thread,
JSHandle<JSTaggedValue>::Cast(env->GetPromiseFunction()), val);
JSHandle<JSPromise> handPromise(thread, promise);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSAsyncGeneratorResNextRetProRstFtn> onFulfilled =
factory->NewJSAsyGenResNextRetProRstFulfilledFtn();
onFulfilled->SetAsyncGeneratorObject(thread, generator);
JSHandle<JSAsyncGeneratorResNextRetProRstFtn> onFulRejected =
factory->NewJSAsyGenResNextRetProRstRejectedFtn();
onFulRejected->SetAsyncGeneratorObject(thread, generator);
#ifdef ENABLE_NEXT_OPTIMIZATION
[[maybe_unused]] JSTaggedValue pres = BuiltinsPromise::PerformPromiseThen(
thread, handPromise, JSHandle<JSTaggedValue>::Cast(onFulfilled),
JSHandle<JSTaggedValue>::Cast(onFulRejected), thread->GlobalConstants()->GetHandledUndefined());
#else
JSHandle<PromiseCapability> tcap =
JSPromise::NewPromiseCapability(thread, JSHandle<JSTaggedValue>::Cast(env->GetPromiseFunction()));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
[[maybe_unused]] JSTaggedValue pres = BuiltinsPromise::PerformPromiseThen(
thread, handPromise, JSHandle<JSTaggedValue>::Cast(onFulfilled),
JSHandle<JSTaggedValue>::Cast(onFulRejected), tcap);
#endif
return JSTaggedValue::Undefined();
} else {
ASSERT(completion->GetType() == CompletionRecordType::THROW);
JSHandle<JSTaggedValue> comVal(thread, completion->GetValue(thread));
AsyncGeneratorReject(thread, generator, comVal);
return JSTaggedValue::Undefined();
}
}
} else if (state == JSAsyncGeneratorState::COMPLETED) {
JSHandle<JSTaggedValue> comVal(thread, JSTaggedValue::Undefined());
return AsyncGeneratorResolve(thread, generator, comVal, true);
}
ASSERT((state == JSAsyncGeneratorState::SUSPENDED_START) ||
(state == JSAsyncGeneratorState::SUSPENDED_YIELD));
JSTaggedValue val = generator->GetGeneratorContext(thread);
JSHandle<GeneratorContext> genContext(thread, val);
generator->SetAsyncGeneratorState(JSAsyncGeneratorState::EXECUTING);
if (completion->GetType() == CompletionRecordType::NORMAL) {
AsyncGeneratorHelper::Next(thread, genContext, completion->GetValue(thread));
}
if (completion->GetType() == CompletionRecordType::RETURN) {
AsyncGeneratorHelper::Return(thread, genContext, completion);
}
if (completion->GetType() == CompletionRecordType::THROW) {
AsyncGeneratorHelper::Throw(thread, genContext, completion);
}
return JSTaggedValue::Undefined();
}
JSTaggedValue JSAsyncGeneratorObject::AsyncGeneratorEnqueue(JSThread *thread, const JSHandle<JSTaggedValue> &gen,
const JSHandle<CompletionRecord> completionRecord)
{
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<PromiseCapability> pcap =
JSPromise::NewPromiseCapability(thread, JSHandle<JSTaggedValue>::Cast(env->GetPromiseFunction()));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
AsyncGeneratorValidate(thread, gen, JSTaggedValue::Undefined());
if (thread->HasPendingException()) {
thread->ClearException();
JSHandle<JSObject> resolutionError = factory->GetJSError(ErrorType::TYPE_ERROR,
"Resolve: The promise and resolution cannot be the same.", StackCheck::NO);
const GlobalEnvConstants *constants = thread->GlobalConstants();
JSHandle<JSTaggedValue> rstErr = JSHandle<JSTaggedValue>::Cast(resolutionError);
JSHandle<JSTaggedValue> reject(thread, pcap->GetReject(thread));
JSHandle<JSTaggedValue> thisArg = constants->GetHandledUndefined();
JSHandle<JSTaggedValue> undefined = constants->GetHandledUndefined();
EcmaRuntimeCallInfo* info =
EcmaInterpreter::NewRuntimeCallInfo(thread, reject, thisArg, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(rstErr.GetTaggedValue());
[[maybe_unused]] JSTaggedValue res = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSObject> promise(thread, pcap->GetPromise(thread));
return promise.GetTaggedValue();
}
JSHandle<JSObject> obj = JSTaggedValue::ToObject(thread, gen);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSAsyncGeneratorObject> generator = JSHandle<JSAsyncGeneratorObject>::Cast(obj);
JSHandle<TaggedQueue> queue(thread, generator->GetAsyncGeneratorQueue(thread));
ObjectFactory *fty = thread->GetEcmaVM()->GetFactory();
JSHandle<AsyncGeneratorRequest> asyncGeneratorRst = fty->NewAsyncGeneratorRequest();
asyncGeneratorRst->SetCompletion(thread, completionRecord);
asyncGeneratorRst->SetCapability(thread, pcap);
TaggedQueue *newQueue = TaggedQueue::Push(thread, queue, JSHandle<JSTaggedValue>::Cast(asyncGeneratorRst));
generator->SetAsyncGeneratorQueue(thread, JSTaggedValue(newQueue));
JSAsyncGeneratorState state = generator->GetAsyncGeneratorState();
if (state != JSAsyncGeneratorState::EXECUTING) {
AsyncGeneratorResumeNext(thread, generator);
}
JSHandle<JSObject> promise(thread, pcap->GetPromise(thread));
return promise.GetTaggedValue();
}
JSTaggedValue JSAsyncGeneratorObject::PromiseResolve(JSThread *thread, const JSHandle<JSTaggedValue> promise,
const JSHandle<JSTaggedValue> value)
{
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
ASSERT(promise->IsECMAObject());
if (value->IsJSPromise()) {
JSHandle<JSTaggedValue> ctorKey(globalConst->GetHandledConstructorString());
JSHandle<JSTaggedValue> ctorValue = JSObject::GetProperty(thread, value, ctorKey).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (JSTaggedValue::SameValue(thread, ctorValue.GetTaggedValue(), promise.GetTaggedValue())) {
return value.GetTaggedValue();
}
}
JSHandle<PromiseCapability> promiseCapability = JSPromise::NewPromiseCapability(thread, promise);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> resolve(thread, promiseCapability->GetResolve(thread));
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
JSHandle<JSTaggedValue> thisArg = globalConst->GetHandledUndefined();
EcmaRuntimeCallInfo* info =
EcmaInterpreter::NewRuntimeCallInfo(thread, resolve, thisArg, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(value.GetTaggedValue());
[[maybe_unused]] JSTaggedValue res = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSPromise> promiseObj(thread, promiseCapability->GetPromise(thread));
return promiseObj.GetTaggedValue();
}
JSTaggedValue JSAsyncGeneratorObject::ProcessorFulfilledFunc(EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
JSHandle<JSAsyncGeneratorResNextRetProRstFtn> asyncResNextRtnPro =
JSHandle<JSAsyncGeneratorResNextRetProRstFtn>::Cast(base::BuiltinsBase::GetConstructor(argv));
JSHandle<JSAsyncGeneratorObject> asyncGen(thread, asyncResNextRtnPro->GetAsyncGeneratorObject(thread));
asyncGen->SetAsyncGeneratorState(JSAsyncGeneratorState::COMPLETED);
JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, 0);
return AsyncGeneratorResolve(thread, asyncGen, value, true);
}
JSTaggedValue JSAsyncGeneratorObject::ProcessorRejectedFunc(EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
JSHandle<JSAsyncGeneratorResNextRetProRstFtn> asyncResNextRtnPro =
JSHandle<JSAsyncGeneratorResNextRetProRstFtn>::Cast(base::BuiltinsBase::GetConstructor(argv));
JSHandle<JSAsyncGeneratorObject> asyncGen(thread, asyncResNextRtnPro->GetAsyncGeneratorObject(thread));
asyncGen->SetAsyncGeneratorState(JSAsyncGeneratorState::COMPLETED);
JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, 0);
return AsyncGeneratorReject(thread, asyncGen, value);
}
}