/* * 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/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" namespace panda::ecmascript::builtins { using JSRecordInfo = ecmascript::JSPandaFile::JSRecordInfo; using ModulePathHelper = ecmascript::ModulePathHelper; using PathHelper = ecmascript::base::PathHelper; using StaticModuleLoader = ecmascript::StaticModuleLoader; JSTaggedValue BuiltinsPromiseJob::PromiseReactionJob(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, Reaction); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); // 1. Assert: reaction is a PromiseReaction Record. JSHandle value = GetCallArg(argv, 0); ASSERT(value->IsPromiseReaction()); JSHandle reaction = JSHandle::Cast(value); JSHandle argument = GetCallArg(argv, 1); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); // 2. Let promiseCapability be reaction.[[Capabilities]]. JSHandle capability(thread, reaction->GetPromiseCapability(thread)); // 3. Let handler be reaction.[[Handler]]. JSHandle handler(thread, reaction->GetHandler(thread)); JSHandle call(thread, capability->GetResolve(thread)); const uint32_t argsLength = 1; JSHandle undefined = globalConst->GetHandledUndefined(); EcmaRuntimeCallInfo *runtimeInfo = EcmaInterpreter::NewRuntimeCallInfo(thread, call, undefined, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (handler->IsString()) { // 4. If handler is "Identity", let handlerResult be NormalCompletion(argument). // 5. Else if handler is "Thrower", let handlerResult be Completion{[[type]]: throw, [[value]]: argument, // [[target]]: empty}. runtimeInfo->SetCallArg(argument.GetTaggedValue()); if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), JSHandle(handler), JSHandle(globalConst->GetHandledThrowerString()))) { runtimeInfo->SetFunction(capability->GetReject(thread)); } } else { // 6. Else, let handlerResult be Call(handler, undefined, «argument»). EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, handler, undefined, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(argument.GetTaggedValue()); JSTaggedValue taggedValue = JSFunction::Call(info); // 7. If handlerResult is an abrupt completion, then // a. Let status be Call(promiseCapability.[[Reject]], undefined, «handlerResult.[[value]]»). // b. NextJob Completion(status). if (thread->HasPendingException()) { JSHandle throwValue = JSPromise::IfThrowGetThrowValue(thread); runtimeInfo->SetCallArg(throwValue.GetTaggedValue()); thread->ClearException(); runtimeInfo->SetFunction(capability->GetReject(thread)); } else { runtimeInfo->SetCallArg(taggedValue); } } // 8. Let status be Call(promiseCapability.[[Resolve]], undefined, «handlerResult.[[value]]»). return JSFunction::Call(runtimeInfo); } JSTaggedValue BuiltinsPromiseJob::PromiseResolveThenableJob(EcmaRuntimeCallInfo *argv) { ASSERT(argv); BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, ResolveThenableJob); JSThread *thread = argv->GetThread(); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle promise = GetCallArg(argv, 0); ASSERT(promise->IsJSPromise()); // 1. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). JSHandle resolvingFunctions = JSPromise::CreateResolvingFunctions(thread, JSHandle::Cast(promise)); JSHandle thenable = GetCallArg(argv, 1); JSHandle then = GetCallArg(argv, BuiltinsBase::ArgsPosition::THIRD); // 2. Let thenCallResult be Call(then, thenable, «resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]]»). const uint32_t argsLength = 2; // 2: «resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]]» JSHandle 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 thenResult(thread, result); // 3. If thenCallResult is an abrupt completion, // a. Let status be Call(resolvingFunctions.[[Reject]], undefined, «thenCallResult.[[value]]»). // b. NextJob Completion(status). if (thread->HasPendingException()) { thenResult = JSPromise::IfThrowGetThrowValue(thread); thread->ClearException(); JSHandle 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); } // 4. NextJob Completion(thenCallResult). 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 resolve(GetCallArg(argv, 0)); JSHandle reject(GetCallArg(argv, 1)); // 1 : reject method JSHandle dirPath(GetCallArg(argv, 2)); // 2 : current file path(containing file name) JSHandle specifier(GetCallArg(argv, 3)); // 3 : request module's path JSHandle recordName(GetCallArg(argv, 4)); // 4 : js recordName or undefined // Let specifierString be Completion(ToString(specifier)) JSHandle specifierString = JSTaggedValue::ToString(thread, specifier); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject)); // Resolve request module's ohmurl CString entryPoint = JSPandaFile::ENTRY_MAIN_FUNCTION; CString fileName = ModulePathHelper::Utf8ConvertToString(thread, dirPath.GetTaggedValue()); CString requestPath = ModulePathHelper::Utf8ConvertToString(thread, specifierString.GetTaggedValue()); LOG_ECMA(DEBUG) << "Start importing dynamic module : " << requestPath; ModuleTraceScope moduleTraceScope(thread, "BuiltinsPromiseJob::DynamicImport:" + requestPath); std::shared_ptr 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; } // translate requestPath to OhmUrl 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::NeedTranstale(requestPath)) { ModulePathHelper::TranstaleExpressionInput(curJsPandaFile.get(), requestPath); LOG_ECMA(DEBUG) << "Exit Translate OhmUrl for DynamicImport, resultPath: " << requestPath; } } // resolve native module ModuleManager *moduleManager = thread->GetModuleManager(); if (SourceTextModule::IsNativeModule(requestPath)) { return DynamicImport::ExecuteNativeOrJsonModule(thread, requestPath, SourceTextModule::GetNativeModuleType(requestPath), resolve, reject); } 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 = 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()); } // Loading request module. thread->GetEcmaVM()->PushToDeregisterModuleList(entryPoint); // IsInstantiatedModule is for lazy module to execute 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 { ModuleDeregister::ReviseLoadedModuleCount(thread, moduleName); } RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, HandleModuleException(thread, resolve, reject, specifierString)); JSMutableHandle moduleNamespace(thread, JSTaggedValue::Undefined()); // only support importing es module, or return a default object. if (!jsPandaFile->IsModule(recordInfo)) { moduleNamespace.Update(vm->GetGlobalEnv()->GetExportOfScript()); } else { // b. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier). JSHandle moduleRecord = moduleManager->GetImportedModule(moduleName); moduleRecord->CheckAndThrowModuleError(thread); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, HandleModuleException(thread, resolve, reject, specifierString)); JSHandle nameSp = SourceTextModule::GetModuleNamespace(thread, moduleRecord); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, HandleModuleException(thread, resolve, reject, specifierString)); // d. Let namespace be ? GetModuleNamespace(moduleRecord). moduleNamespace.Update(nameSp); } JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, JSHandle(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 reject) { BUILTINS_API_TRACE(thread, PromiseJob, CatchException); JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); ASSERT(thread->HasPendingException()); JSHandle thenResult = JSPromise::IfThrowGetThrowValue(thread); thread->ClearException(); JSHandle 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 resolve, JSHandle reject, JSHandle specifierString) { CString requestPath = ModulePathHelper::Utf8ConvertToString(thread, specifierString.GetTaggedValue()); return HandleModuleException(thread, resolve, reject, requestPath); } JSTaggedValue BuiltinsPromiseJob::HandleModuleException(JSThread *thread, JSHandle resolve, JSHandle reject, const CString &requestPath) { ASSERT(thread->HasPendingException()); LOG_ECMA(DEBUG) << "start handle module exception " << requestPath; // If the ohmurl is detected to be in compliance with the 1.0 prefix rule, then throw an exception directly 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 errorReuslt = JSPromise::IfThrowGetThrowValue(thread); thread->ClearException(); EcmaVM *vm = thread->GetEcmaVM(); Local 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); } // try load 1.2 module; Local getEsModuleFunc = getEsModule; ModuleManager *moduleManager = thread->GetModuleManager(); JSHandle 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 undefined = thread->GlobalConstants()->GetHandledUndefined(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, JSHandle(resolve), undefined, undefined, 1); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject)); info->SetCallArg(exportObject.GetTaggedValue()); return JSFunction::Call(info); } } // namespace panda::ecmascript::builtins