1/*
2 * Copyright (c) 2021 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/builtins/builtins_promise_job.h"
17
18#include "ecmascript/interpreter/interpreter.h"
19#include "ecmascript/jspandafile/js_pandafile_executor.h"
20#include "ecmascript/jspandafile/js_pandafile_manager.h"
21#include "ecmascript/js_promise.h"
22#include "ecmascript/module/js_dynamic_import.h"
23#include "ecmascript/module/js_module_deregister.h"
24#include "ecmascript/module/js_module_manager.h"
25#include "ecmascript/module/module_path_helper.h"
26
27namespace panda::ecmascript::builtins {
28using JSRecordInfo = ecmascript::JSPandaFile::JSRecordInfo;
29using ModulePathHelper = ecmascript::ModulePathHelper;
30using PathHelper = ecmascript::base::PathHelper;
31
32JSTaggedValue BuiltinsPromiseJob::PromiseReactionJob(EcmaRuntimeCallInfo *argv)
33{
34    ASSERT(argv);
35    BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, Reaction);
36    JSThread *thread = argv->GetThread();
37    [[maybe_unused]] EcmaHandleScope handleScope(thread);
38    // 1. Assert: reaction is a PromiseReaction Record.
39    JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
40    ASSERT(value->IsPromiseReaction());
41    JSHandle<PromiseReaction> reaction = JSHandle<PromiseReaction>::Cast(value);
42    JSHandle<JSTaggedValue> argument = GetCallArg(argv, 1);
43
44    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
45    // 2. Let promiseCapability be reaction.[[Capabilities]].
46    JSHandle<PromiseCapability> capability(thread, reaction->GetPromiseCapability());
47    // 3. Let handler be reaction.[[Handler]].
48    JSHandle<JSTaggedValue> handler(thread, reaction->GetHandler());
49    JSHandle<JSTaggedValue> call(thread, capability->GetResolve());
50    const uint32_t argsLength = 1;
51    JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
52    EcmaRuntimeCallInfo *runtimeInfo =
53        EcmaInterpreter::NewRuntimeCallInfo(thread, call, undefined, undefined, argsLength);
54    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
55    if (handler->IsString()) {
56        // 4. If handler is "Identity", let handlerResult be NormalCompletion(argument).
57        // 5. Else if handler is "Thrower", let handlerResult be Completion{[[type]]: throw, [[value]]: argument,
58        // [[target]]: empty}.
59        runtimeInfo->SetCallArg(argument.GetTaggedValue());
60        if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(),
61            JSHandle<EcmaString>(handler), JSHandle<EcmaString>(globalConst->GetHandledThrowerString()))) {
62            runtimeInfo->SetFunction(capability->GetReject());
63        }
64    } else {
65        // 6. Else, let handlerResult be Call(handler, undefined, «argument»).
66        EcmaRuntimeCallInfo *info =
67            EcmaInterpreter::NewRuntimeCallInfo(thread, handler, undefined, undefined, argsLength);
68        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
69        info->SetCallArg(argument.GetTaggedValue());
70        JSTaggedValue taggedValue = JSFunction::Call(info);
71        // 7. If handlerResult is an abrupt completion, then
72        // a. Let status be Call(promiseCapability.[[Reject]], undefined, «handlerResult.[[value]]»).
73        // b. NextJob Completion(status).
74        if (thread->HasPendingException()) {
75            JSHandle<JSTaggedValue> throwValue = JSPromise::IfThrowGetThrowValue(thread);
76            runtimeInfo->SetCallArg(throwValue.GetTaggedValue());
77            thread->ClearException();
78            runtimeInfo->SetFunction(capability->GetReject());
79        } else {
80            runtimeInfo->SetCallArg(taggedValue);
81        }
82    }
83    // 8. Let status be Call(promiseCapability.[[Resolve]], undefined, «handlerResult.[[value]]»).
84    return JSFunction::Call(runtimeInfo);
85}
86
87JSTaggedValue BuiltinsPromiseJob::PromiseResolveThenableJob(EcmaRuntimeCallInfo *argv)
88{
89    ASSERT(argv);
90    BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, ResolveThenableJob);
91    JSThread *thread = argv->GetThread();
92    [[maybe_unused]] EcmaHandleScope handleScope(thread);
93    JSHandle<JSTaggedValue> promise = GetCallArg(argv, 0);
94    ASSERT(promise->IsJSPromise());
95    // 1. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve).
96    JSHandle<ResolvingFunctionsRecord> resolvingFunctions =
97        JSPromise::CreateResolvingFunctions(thread, JSHandle<JSPromise>::Cast(promise));
98    JSHandle<JSTaggedValue> thenable = GetCallArg(argv, 1);
99    JSHandle<JSTaggedValue> then = GetCallArg(argv, BuiltinsBase::ArgsPosition::THIRD);
100
101    // 2. Let thenCallResult be Call(then, thenable, «resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]]»).
102    const uint32_t argsLength = 2; // 2: «resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]]»
103    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
104    EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, then, thenable, undefined, argsLength);
105    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
106    info->SetCallArg(resolvingFunctions->GetResolveFunction(), resolvingFunctions->GetRejectFunction());
107    JSTaggedValue result = JSFunction::Call(info);
108    JSHandle<JSTaggedValue> thenResult(thread, result);
109    // 3. If thenCallResult is an abrupt completion,
110    // a. Let status be Call(resolvingFunctions.[[Reject]], undefined, «thenCallResult.[[value]]»).
111    // b. NextJob Completion(status).
112    if (thread->HasPendingException()) {
113        thenResult = JSPromise::IfThrowGetThrowValue(thread);
114        thread->ClearException();
115        JSHandle<JSTaggedValue> reject(thread, resolvingFunctions->GetRejectFunction());
116        EcmaRuntimeCallInfo *runtimeInfo =
117            EcmaInterpreter::NewRuntimeCallInfo(thread, reject, undefined, undefined, 1);
118        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
119        runtimeInfo->SetCallArg(thenResult.GetTaggedValue());
120        return JSFunction::Call(runtimeInfo);
121    }
122    // 4. NextJob Completion(thenCallResult).
123    return result;
124}
125
126JSTaggedValue BuiltinsPromiseJob::DynamicImportJob(EcmaRuntimeCallInfo *argv)
127{
128    ASSERT(argv);
129    BUILTINS_API_TRACE(argv->GetThread(), PromiseJob, DynamicImportJob);
130    JSThread *thread = argv->GetThread();
131    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
132    EcmaVM *vm = thread->GetEcmaVM();
133    [[maybe_unused]] EcmaHandleScope handleScope(thread);
134
135    JSHandle<JSPromiseReactionsFunction> resolve(GetCallArg(argv, 0));
136    JSHandle<JSPromiseReactionsFunction> reject(GetCallArg(argv, 1));   // 1 : reject method
137    JSHandle<EcmaString> dirPath(GetCallArg(argv, 2));                  // 2 : current file path(containing file name)
138    JSHandle<JSTaggedValue> specifier(GetCallArg(argv, 3));             // 3 : request module's path
139    JSHandle<JSTaggedValue> recordName(GetCallArg(argv, 4));            // 4 : js recordName or undefined
140
141    // Let specifierString be Completion(ToString(specifier))
142    JSHandle<EcmaString> specifierString = JSTaggedValue::ToString(thread, specifier);
143    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
144
145    // Resolve request module's ohmurl
146    CString entryPoint = JSPandaFile::ENTRY_MAIN_FUNCTION;
147    CString fileName = ModulePathHelper::Utf8ConvertToString(dirPath.GetTaggedValue());
148    CString requestPath = ModulePathHelper::Utf8ConvertToString(specifierString.GetTaggedValue());
149    LOG_ECMA(DEBUG) << "Start importing dynamic module : " << requestPath;
150    bool enableESMTrace = thread->GetEcmaVM()->GetJSOptions().EnableESMTrace();
151    if (enableESMTrace) {
152        CString traceInfo = "DynamicImportJob: " + requestPath;
153        ECMA_BYTRACE_START_TRACE(HITRACE_TAG_ARK, traceInfo.c_str());
154    }
155    std::shared_ptr<JSPandaFile> curJsPandaFile;
156    CString recordNameStr;
157    if (!recordName->IsUndefined()) {
158        recordNameStr = ModulePathHelper::Utf8ConvertToString(recordName.GetTaggedValue());
159        curJsPandaFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, fileName, recordNameStr.c_str());
160        if (curJsPandaFile == nullptr) {
161            LOG_FULL(FATAL) << "Load current file's panda file failed. Current file is " << recordNameStr;
162        }
163        // translate requestPath to OhmUrl
164        if (vm->IsNormalizedOhmUrlPack()) {
165            ModulePathHelper::TranslateExpressionToNormalized(thread, curJsPandaFile.get(), fileName, recordNameStr,
166                requestPath);
167            LOG_ECMA(DEBUG) << "Exit Translate Normalized OhmUrl for DynamicImport, resultPath: " << requestPath;
168            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
169        } else if (ModulePathHelper::NeedTranstale(requestPath)) {
170            ModulePathHelper::TranstaleExpressionInput(curJsPandaFile.get(), requestPath);
171            LOG_ECMA(DEBUG) << "Exit Translate OhmUrl for DynamicImport, resultPath: " << requestPath;
172        }
173    }
174    // resolve native module
175    auto [isNative, moduleType] = SourceTextModule::CheckNativeModule(requestPath);
176    ModuleManager *moduleManager = thread->GetCurrentEcmaContext()->GetModuleManager();
177    if (isNative) {
178        return DynamicImport::ExecuteNativeOrJsonModule(thread, requestPath, moduleType, resolve, reject);
179    }
180
181    CString moduleName;
182    if (recordName->IsUndefined()) {
183        fileName = ResolveFilenameFromNative(thread, fileName, requestPath);
184        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
185        moduleName = fileName;
186    } else {
187        entryPoint =
188            ModulePathHelper::ConcatFileNameWithMerge(thread, curJsPandaFile.get(),
189                fileName, recordNameStr, requestPath);
190        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
191        moduleName = entryPoint;
192    }
193    std::shared_ptr<JSPandaFile> jsPandaFile =
194        JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, fileName, entryPoint);
195    if (jsPandaFile == nullptr) {
196        LOG_FULL(FATAL) << "Load current file's panda file failed. Current file is " << fileName;
197    }
198
199    JSRecordInfo *recordInfo = nullptr;
200    bool hasRecord = jsPandaFile->CheckAndGetRecordInfo(entryPoint, &recordInfo);
201    if (!hasRecord) {
202        CString normalizeStr = ModulePathHelper::ReformatPath(entryPoint);
203        CString msg = "Cannot find dynamic-import module '" + normalizeStr;
204        JSTaggedValue error = factory->GetJSError(ErrorType::REFERENCE_ERROR,
205            msg.c_str(), StackCheck::NO).GetTaggedValue();
206        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, CatchException(thread, reject));
207    }
208    if (jsPandaFile->IsJson(recordInfo)) {
209        return DynamicImport::ExecuteNativeOrJsonModule(
210            thread, entryPoint, ModuleTypes::JSON_MODULE, resolve, reject, jsPandaFile.get());
211    }
212    // Loading request module.
213    thread->GetEcmaVM()->PushToDeregisterModuleList(entryPoint);
214    // IsInstantiatedModule is for lazy module to execute
215    if (!moduleManager->IsModuleLoaded(moduleName) || moduleManager->IsInstantiatedModule(moduleName)) {
216        if (!JSPandaFileExecutor::ExecuteFromAbcFile(thread, fileName.c_str(), entryPoint.c_str(), false, true)) {
217            CString msg = "Cannot execute request dynamic-imported module : " + entryPoint;
218            JSTaggedValue error = factory->GetJSError(ErrorType::REFERENCE_ERROR, msg.c_str(),
219                                                      StackCheck::NO).GetTaggedValue();
220            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, CatchException(thread, reject));
221        }
222    } else {
223        ModuleDeregister::ReviseLoadedModuleCount(thread, moduleName);
224    }
225
226    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
227    JSMutableHandle<JSTaggedValue> moduleNamespace(thread, JSTaggedValue::Undefined());
228    // only support importing es module, or return a default object.
229    if (!jsPandaFile->IsModule(recordInfo)) {
230        moduleNamespace.Update(vm->GetGlobalEnv()->GetExportOfScript());
231    } else {
232        // b. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier).
233        JSHandle<SourceTextModule> moduleRecord =
234            moduleManager->GetImportedModule(moduleName);
235        JSHandle<JSTaggedValue> nameSp = SourceTextModule::GetModuleNamespace(thread, moduleRecord);
236        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
237        // d. Let namespace be ? GetModuleNamespace(moduleRecord).
238        moduleNamespace.Update(nameSp);
239    }
240    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
241    EcmaRuntimeCallInfo *info =
242        EcmaInterpreter::NewRuntimeCallInfo(thread,
243                                            JSHandle<JSTaggedValue>(resolve),
244                                            undefined, undefined, 1);
245    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CatchException(thread, reject));
246    info->SetCallArg(moduleNamespace.GetTaggedValue());
247    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
248    if (enableESMTrace) {
249        ECMA_BYTRACE_FINISH_TRACE(HITRACE_TAG_ARK);
250    }
251    return JSFunction::Call(info);
252}
253
254JSTaggedValue BuiltinsPromiseJob::CatchException(JSThread *thread, JSHandle<JSPromiseReactionsFunction> reject)
255{
256    BUILTINS_API_TRACE(thread, PromiseJob, CatchException);
257    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
258    ASSERT(thread->HasPendingException());
259    JSHandle<JSTaggedValue> thenResult = JSPromise::IfThrowGetThrowValue(thread);
260    thread->ClearException();
261    JSHandle<JSTaggedValue> rejectfun(reject);
262    EcmaRuntimeCallInfo *runtimeInfo =
263        EcmaInterpreter::NewRuntimeCallInfo(thread, rejectfun, undefined, undefined, 1);
264    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
265    runtimeInfo->SetCallArg(thenResult.GetTaggedValue());
266    return JSFunction::Call(runtimeInfo);
267}
268}  // namespace panda::ecmascript::builtins
269