* Copyright (c) 2021-2025 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/builtins/builtins_promise_job.h"
#include "ecmascript/builtins/builtins_promise_handler.h"
#include "ecmascript/builtins/builtins_promise.h"
#include "ecmascript/interpreter/interpreter.h"
#include "ecmascript/jspandafile/js_pandafile_executor.h"
#include "ecmascript/jspandafile/js_pandafile_manager.h"
#include "ecmascript/js_promise.h"
#include "ecmascript/module/js_dynamic_import.h"
#include "ecmascript/module/js_module_deregister.h"
#include "ecmascript/module/js_module_manager.h"
#include "ecmascript/module/module_path_helper.h"
#include "ecmascript/module/module_tools.h"
#include "ecmascript/module/static/static_module_loader.h"
#include "ecmascript/patch/quick_fix_manager.h"
namespace panda::ecmascript::builtins {
using JSRecordInfo = ecmascript::JSPandaFile::JSRecordInfo;
using ModulePathHelper = ecmascript::ModulePathHelper;
using PathHelper = ecmascript::base::PathHelper;
using StaticModuleLoader = ecmascript::StaticModuleLoader;
#if ENABLE_LATEST_OPTIMIZATION
JSTaggedValue BuiltinsPromiseJob::PromiseReactionJob(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, Reaction);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
ASSERT(value->IsPromiseReaction());
JSHandle<PromiseReaction> reaction = JSHandle<PromiseReaction>::Cast(value);
JSHandle<JSTaggedValue> argument = GetCallArg(argv, 1);
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> promiseOrCapability(thread, reaction->GetPromiseOrCapability(thread));
JSHandle<JSTaggedValue> handler(thread, reaction->GetHandler(thread));
constexpr uint32_t argsLength = 1;
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
JSMutableHandle<JSTaggedValue> callArg(thread, undefined);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
bool isRejected = false;
if (handler->IsString()) {
callArg.Update(argument);
if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(),
JSHandle<EcmaString>(handler), JSHandle<EcmaString>(globalConst->GetHandledThrowerString()))) {
isRejected = true;
}
} else {
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, handler, undefined, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(argument.GetTaggedValue());
JSTaggedValue taggedValue = JSFunction::Call(info);
if (thread->HasPendingException()) {
JSHandle<JSTaggedValue> throwValue = JSPromise::IfThrowGetThrowValue(thread);
callArg.Update(throwValue);
thread->ClearExceptionAndExtraErrorMessage();
isRejected = true;
} else {
callArg.Update(taggedValue);
}
}
if (promiseOrCapability->IsUndefined()) {
return undefined.GetTaggedValue();
}
if (promiseOrCapability->IsJSPromise()) {
if (isRejected) {
return JSPromise::RejectPromise(thread, JSHandle<JSPromise>::Cast(promiseOrCapability), callArg);
}
return BuiltinsPromiseHandler::InnerResolve(thread, JSHandle<JSPromise>::Cast(promiseOrCapability), callArg);
}
JSHandle<PromiseCapability> capability = JSHandle<PromiseCapability>::Cast(promiseOrCapability);
EcmaRuntimeCallInfo *runtimeInfo =
EcmaInterpreter::NewRuntimeCallInfo(thread, undefined, undefined, undefined, argsLength);
if (isRejected) {
runtimeInfo->SetFunction(capability->GetReject(thread));
} else {
runtimeInfo->SetFunction(capability->GetResolve(thread));
}
runtimeInfo->SetCallArg(callArg.GetTaggedValue());
return JSFunction::Call(runtimeInfo);
}
#else
JSTaggedValue BuiltinsPromiseJob::PromiseReactionJob(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, Reaction);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
ASSERT(value->IsPromiseReaction());
JSHandle<PromiseReaction> reaction = JSHandle<PromiseReaction>::Cast(value);
JSHandle<JSTaggedValue> argument = GetCallArg(argv, 1);
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<PromiseCapability> capability(thread, reaction->GetPromiseCapability(thread));
JSHandle<JSTaggedValue> handler(thread, reaction->GetHandler(thread));
JSHandle<JSTaggedValue> call(thread, capability->GetResolve(thread));
const uint32_t argsLength = 1;
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
EcmaRuntimeCallInfo *runtimeInfo =
EcmaInterpreter::NewRuntimeCallInfo(thread, call, undefined, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (handler->IsString()) {
runtimeInfo->SetCallArg(argument.GetTaggedValue());
if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(),
JSHandle<EcmaString>(handler), JSHandle<EcmaString>(globalConst->GetHandledThrowerString()))) {
runtimeInfo->SetFunction(capability->GetReject(thread));
}
} else {
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, handler, undefined, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(argument.GetTaggedValue());
JSTaggedValue taggedValue = JSFunction::Call(info);
if (thread->HasPendingException()) {
JSHandle<JSTaggedValue> throwValue = JSPromise::IfThrowGetThrowValue(thread);
runtimeInfo->SetCallArg(throwValue.GetTaggedValue());
thread->ClearExceptionAndExtraErrorMessage();
runtimeInfo->SetFunction(capability->GetReject(thread));
} else {
runtimeInfo->SetCallArg(taggedValue);
}
}
return JSFunction::Call(runtimeInfo);
}
#endif
JSTaggedValue BuiltinsPromiseJob::PromiseResolveThenableJob(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, ResolveThenableJob);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> promise = GetCallArg(argv, 0);
ASSERT(promise->IsJSPromise());
JSHandle<ResolvingFunctionsRecord> resolvingFunctions =
JSPromise::CreateResolvingFunctions(thread, JSHandle<JSPromise>::Cast(promise));
JSHandle<JSTaggedValue> thenable = GetCallArg(argv, 1);
JSHandle<JSTaggedValue> then = GetCallArg(argv, BuiltinsBase::ArgsPosition::THIRD);
const uint32_t argsLength = 2;
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, then, thenable, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(resolvingFunctions->GetResolveFunction(thread), resolvingFunctions->GetRejectFunction(thread));
JSTaggedValue result = JSFunction::Call(info);
JSHandle<JSTaggedValue> thenResult(thread, result);
if (thread->HasPendingException()) {
thenResult = JSPromise::IfThrowGetThrowValue(thread);
thread->ClearExceptionAndExtraErrorMessage();
JSHandle<JSTaggedValue> reject(thread, resolvingFunctions->GetRejectFunction(thread));
EcmaRuntimeCallInfo *runtimeInfo =
EcmaInterpreter::NewRuntimeCallInfo(thread, reject, undefined, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
runtimeInfo->SetCallArg(thenResult.GetTaggedValue());
return JSFunction::Call(runtimeInfo);
}
return result;
}
JSTaggedValue BuiltinsPromiseJob::DynamicImportJob(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, DynamicImportJob);
JSThread *thread = argv->GetThread();
EcmaVM *vm = thread->GetEcmaVM();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSPromiseReactionsFunction> resolve(GetCallArg(argv, 0));
JSHandle<JSPromiseReactionsFunction> reject(GetCallArg(argv, 1));
JSHandle<EcmaString> dirPath(GetCallArg(argv, 2));
JSHandle<JSTaggedValue> specifier(GetCallArg(argv, 3));
JSHandle<JSTaggedValue> recordName(GetCallArg(argv, 4));
JSHandle<EcmaString> specifierString = JSTaggedValue::ToString(thread, specifier);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
CString entryPoint = JSPandaFile::ENTRY_MAIN_FUNCTION;
CString fileName = ModulePathHelper::Utf8ConvertToString(thread, dirPath.GetTaggedValue());
fileName = vm->GetQuickFixManager()->GetBaseFileNameForHotReload(thread, fileName);
CString requestPath = ModulePathHelper::Utf8ConvertToString(thread, specifierString.GetTaggedValue());
LOG_ECMA(DEBUG) << "Start importing dynamic module : " << requestPath;
ModuleTraceScope moduleTraceScope = ModuleTraceScope::Open(
thread, "BuiltinsPromiseJob::DynamicImport:", requestPath);
std::shared_ptr<JSPandaFile> curJsPandaFile;
CString recordNameStr;
if (!recordName->IsUndefined()) {
recordNameStr = ModulePathHelper::Utf8ConvertToString(thread, recordName.GetTaggedValue());
curJsPandaFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(
thread, fileName, recordNameStr.c_str(), false, ExecuteTypes::STATIC);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
if (curJsPandaFile == nullptr) {
LOG_FULL(FATAL) << "Load current file's panda file failed. Current file is " << recordNameStr;
}
if (vm->IsNormalizedOhmUrlPack()) {
ModulePathHelper::TranslateExpressionToNormalized(thread, curJsPandaFile.get(), fileName, recordNameStr,
requestPath);
LOG_ECMA(DEBUG) << "Exit Translate Normalized OhmUrl for DynamicImport, resultPath: " << requestPath;
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
} else if (ModulePathHelper::NeedTranslate(requestPath)) {
ModulePathHelper::TranslateExpressionInput(curJsPandaFile.get(), requestPath);
LOG_ECMA(DEBUG) << "Exit Translate OhmUrl for DynamicImport, resultPath: " << requestPath;
}
}
ModuleManager *moduleManager = thread->GetModuleManager();
if (SourceTextModule::IsNativeModule(requestPath)) {
return DynamicImport::ExecuteNativeOrJsonModule(thread, requestPath,
SourceTextModule::GetNativeModuleType(requestPath), resolve, reject);
}
bool isExport = ModulePathHelper::CheckExportsWithOhmurl(vm, fileName, recordNameStr, requestPath);
if (!isExport) {
CString normalizeStr = ModulePathHelper::ReformatPath(requestPath);
CString msg = "Cannot find dynamic-import module in oh-exports:'" + normalizeStr;
THROW_REFERENCE_ERROR_AND_RETURN(thread, msg.c_str(),
HandleModuleException(thread, resolve, reject, specifierString));
}
CString moduleName;
if (recordName->IsUndefined()) {
fileName = ResolveFilenameFromNative(thread, fileName, requestPath);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
moduleName = fileName;
} else {
entryPoint =
ModulePathHelper::ConcatFileNameWithMerge(thread, curJsPandaFile.get(),
fileName, recordNameStr, requestPath);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
moduleName = entryPoint;
}
std::shared_ptr<JSPandaFile> jsPandaFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(
thread, fileName, entryPoint, false, ExecuteTypes::STATIC);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
if (jsPandaFile == nullptr) {
LOG_FULL(FATAL) << "Load current file's panda file failed. Current file is " << fileName;
}
JSRecordInfo *recordInfo = jsPandaFile->CheckAndGetRecordInfo(entryPoint);
if (recordInfo == nullptr) {
CString normalizeStr = ModulePathHelper::ReformatPath(entryPoint);
CString msg = "Cannot find dynamic-import module '" + normalizeStr;
THROW_REFERENCE_ERROR_AND_RETURN(thread, msg.c_str(),
HandleModuleException(thread, resolve, reject, specifierString));
}
if (jsPandaFile->IsJson(recordInfo)) {
return DynamicImport::ExecuteNativeOrJsonModule(
thread, entryPoint, ModuleTypes::JSON_MODULE, resolve, reject, jsPandaFile.get());
}
if (!moduleManager->IsModuleLoaded(moduleName) || moduleManager->IsInstantiatedModule(moduleName)) {
if (!JSPandaFileExecutor::ExecuteFromAbcFile(
thread, fileName.c_str(), entryPoint.c_str(), false, ExecuteTypes::DYNAMIC)) {
CString msg = "Cannot execute request dynamic-imported module : " + entryPoint;
THROW_REFERENCE_ERROR_AND_RETURN(thread, msg.c_str(),
HandleModuleException(thread, resolve, reject, specifierString));
}
} else {
JSHandle<SourceTextModule> moduleRecord = moduleManager->GetImportedModule(moduleName);
ModuleDeregister::DisableMultiEntryDeregister(thread, moduleRecord, ExecuteTypes::DYNAMIC);
}
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
JSMutableHandle<JSTaggedValue> moduleNamespace(thread, JSTaggedValue::Undefined());
if (!jsPandaFile->IsModule(recordInfo)) {
moduleNamespace.Update(vm->GetGlobalEnv()->GetExportOfScript());
} else {
JSHandle<SourceTextModule> moduleRecord =
moduleManager->GetImportedModule(moduleName);
moduleRecord->CheckAndThrowModuleError(thread);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
JSHandle<JSTaggedValue> nameSp = SourceTextModule::GetModuleNamespace(thread, moduleRecord);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
moduleNamespace.Update(nameSp);
}
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread,
JSHandle<JSTaggedValue>(resolve),
undefined, undefined, 1);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
HandleModuleException(thread, resolve, reject, specifierString));
info->SetCallArg(moduleNamespace.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return JSFunction::Call(info);
}
JSTaggedValue BuiltinsPromiseJob::CatchException(JSThread *thread, JSHandle<JSPromiseReactionsFunction> reject)
{
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
ASSERT(thread->HasPendingException());
JSHandle<JSTaggedValue> thenResult = JSPromise::IfThrowGetThrowValue(thread);
thread->ClearExceptionAndExtraErrorMessage();
JSHandle<JSTaggedValue> rejectfun(reject);
EcmaRuntimeCallInfo *runtimeInfo =
EcmaInterpreter::NewRuntimeCallInfo(thread, rejectfun, undefined, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
runtimeInfo->SetCallArg(thenResult.GetTaggedValue());
return JSFunction::Call(runtimeInfo);
}
JSTaggedValue BuiltinsPromiseJob::HandleModuleException(JSThread *thread, JSHandle<JSPromiseReactionsFunction> resolve,
JSHandle<JSPromiseReactionsFunction> reject, JSHandle<EcmaString> specifierString)
{
CString requestPath = ModulePathHelper::Utf8ConvertToString(thread, specifierString.GetTaggedValue());
return HandleModuleException(thread, resolve, reject, requestPath);
}
JSTaggedValue BuiltinsPromiseJob::HandleModuleException(JSThread *thread, JSHandle<JSPromiseReactionsFunction> resolve,
JSHandle<JSPromiseReactionsFunction> reject, const CString &requestPath)
{
ASSERT(thread->HasPendingException());
LOG_ECMA(DEBUG) << "start handle module exception " << requestPath;
if (thread->GetEcmaVM()->GetArkTSMode() == ArkTSMode::DYNAMIC) {
return CatchException(thread, reject);
}
if (!StaticModuleLoader::CanTryLoadStaticModulePath(requestPath)) {
LOG_ECMA(DEBUG) << "handle dynamic module exception " << requestPath;
return CatchException(thread, reject);
}
LOG_ECMA(DEBUG) << "try to start load static module: " << requestPath;
JSHandle<JSTaggedValue> errorReuslt = JSPromise::IfThrowGetThrowValue(thread);
thread->ClearExceptionAndExtraErrorMessage();
EcmaVM *vm = thread->GetEcmaVM();
Local<JSValueRef> getEsModule = StaticModuleLoader::GetStaticModuleLoadFunc(vm);
if (!getEsModule->IsFunction(vm)) {
LOG_ECMA(DEBUG) << "napi static module function not found " << requestPath;
thread->SetException(errorReuslt.GetTaggedValue());
return CatchException(thread, reject);
}
Local<FunctionRef> getEsModuleFunc = getEsModule;
ModuleManager *moduleManager = thread->GetModuleManager();
JSHandle<JSTaggedValue> exportObject = StaticModuleLoader::LoadStaticModule(thread, getEsModuleFunc, requestPath);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
LOG_ECMA(DEBUG) << "load static module successfull, requestPath: " << requestPath;
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread,
JSHandle<JSTaggedValue>(resolve),
undefined, undefined, 1);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
ASSERT(info != nullptr);
info->SetCallArg(exportObject.GetTaggedValue());
return JSFunction::Call(info);
}
}