1/*
2 * Copyright (c) 2022 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/require/js_cjs_module.h"
17
18#include "ecmascript/interpreter/interpreter-inl.h"
19#include "ecmascript/jspandafile/js_pandafile_executor.h"
20#include "ecmascript/module/module_data_extractor.h"
21#include "ecmascript/module/module_path_helper.h"
22#include "ecmascript/require/js_require_manager.h"
23
24namespace panda::ecmascript {
25
26void CjsModule::InitializeModule(JSThread *thread, JSHandle<CjsModule> &module,
27                                 JSHandle<JSTaggedValue> &filename, JSHandle<JSTaggedValue> &dirname)
28{
29    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
30
31    JSHandle<JSTaggedValue> dirKey(factory->NewFromASCII("path"));
32    SlowRuntimeStub::StObjByName(thread, module.GetTaggedValue(), dirKey.GetTaggedValue(),
33                                 dirname.GetTaggedValue());
34    JSHandle<JSTaggedValue> filenameKey(factory->NewFromASCII("filename"));
35    SlowRuntimeStub::StObjByName(thread, module.GetTaggedValue(), filenameKey.GetTaggedValue(),
36                                 filename.GetTaggedValue());
37    module->SetFilename(thread, filename.GetTaggedValue());
38    module->SetPath(thread, dirname.GetTaggedValue());
39    return;
40}
41
42JSHandle<JSTaggedValue> CjsModule::SearchFromModuleCache(JSThread *thread, JSHandle<JSTaggedValue> &filename)
43{
44    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
45    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
46
47    JSHandle<JSTaggedValue> moduleObj(env->GetCjsModuleFunction());
48    JSHandle<JSTaggedValue> cacheName = globalConst->GetHandledCjsCacheString();
49    JSTaggedValue modCache = SlowRuntimeStub::LdObjByName(thread, moduleObj.GetTaggedValue(),
50                                                          cacheName.GetTaggedValue(),
51                                                          false,
52                                                          JSTaggedValue::Undefined());
53    JSHandle<CjsModuleCache> moduleCache = JSHandle<CjsModuleCache>(thread, modCache);
54    if (moduleCache->ContainsModule(filename.GetTaggedValue())) {
55        JSHandle<CjsModule> cachedModule = JSHandle<CjsModule>(thread,
56                                                               moduleCache->GetModule(filename.GetTaggedValue()));
57        JSHandle<JSTaggedValue> exportsName = globalConst->GetHandledCjsExportsString();
58        JSTaggedValue cachedExports = SlowRuntimeStub::LdObjByName(thread, cachedModule.GetTaggedValue(),
59                                                                   exportsName.GetTaggedValue(),
60                                                                   false,
61                                                                   JSTaggedValue::Undefined());
62
63        return JSHandle<JSTaggedValue>(thread, cachedExports);
64    }
65    return JSHandle<JSTaggedValue>(thread, JSTaggedValue::Hole());
66}
67
68void CjsModule::PutIntoCache(JSThread *thread, JSHandle<CjsModule> &module, JSHandle<JSTaggedValue> &filename)
69{
70    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
71    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
72
73    JSHandle<JSTaggedValue> moduleObj(env->GetCjsModuleFunction());
74    JSHandle<JSTaggedValue> cacheName = globalConst->GetHandledCjsCacheString();
75    JSTaggedValue modCache = SlowRuntimeStub::LdObjByName(thread, moduleObj.GetTaggedValue(),
76                                                          cacheName.GetTaggedValue(),
77                                                          false,
78                                                          JSTaggedValue::Undefined());
79    JSHandle<CjsModuleCache> moduleCache = JSHandle<CjsModuleCache>(thread, modCache);
80    JSHandle<JSTaggedValue> moduleHandle = JSHandle<JSTaggedValue>::Cast(module);
81    JSHandle<CjsModuleCache> newCache = CjsModuleCache::PutIfAbsentAndReset(thread, moduleCache, filename,
82        moduleHandle);
83    SlowRuntimeStub::StObjByName(thread, moduleObj.GetTaggedValue(), cacheName.GetTaggedValue(),
84                                 newCache.GetTaggedValue());
85}
86
87JSHandle<JSTaggedValue> CjsModule::Load(JSThread *thread, JSHandle<EcmaString> &request)
88{
89    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
90    // Get local jsPandaFile's dirPath
91    const JSPandaFile *jsPandaFile = EcmaInterpreter::GetNativeCallPandafile(thread);
92    if (jsPandaFile == nullptr) {
93        LOG_ECMA(FATAL) << "CJS REQUIRE FAIL : jsPandaFile is null";
94        UNREACHABLE();
95    }
96    CString filename = jsPandaFile->GetJSPandaFileDesc();
97    CString requestEntryPoint = JSPandaFile::ENTRY_MAIN_FUNCTION;
98    CString requestStr = ModulePathHelper::Utf8ConvertToString(request.GetTaggedValue());
99    CString parent;
100    CString dirname;
101    CString recordName;
102    if (jsPandaFile->IsBundlePack()) {
103        ModulePathHelper::ResolveCurrentPath(parent, dirname, jsPandaFile);
104        recordName = ResolveFilenameFromNative(thread, dirname, requestStr);
105        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
106        filename = recordName;
107    } else {
108        CString currentEntryPoint = EcmaInterpreter::GetCurrentEntryPoint(thread).first;
109        requestEntryPoint = ModulePathHelper::ConcatFileNameWithMerge(thread, jsPandaFile, filename,
110                                                                      currentEntryPoint, requestStr);
111        recordName = requestEntryPoint;
112        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
113    }
114
115    // Search from Module.cache
116    JSHandle<JSTaggedValue> recordNameHdl = JSHandle<JSTaggedValue>::Cast(factory->NewFromUtf8(recordName));
117    JSHandle<JSTaggedValue> maybeCachedExports = SearchFromModuleCache(thread, recordNameHdl);
118    if (!maybeCachedExports->IsHole()) {
119        return maybeCachedExports;
120    }
121
122    // Don't get required exports from cache, execute required JSPandaFile.
123    // module = new Module(), which belongs to required JSPandaFile.
124    JSHandle<CjsModule> module = factory->NewCjsModule();
125    dirname = PathHelper::ResolveDirPath(recordName);
126    JSHandle<JSTaggedValue> dirnameHdl = JSHandle<JSTaggedValue>::Cast(factory->NewFromUtf8(dirname));
127    InitializeModule(thread, module, recordNameHdl, dirnameHdl);
128    PutIntoCache(thread, module, recordNameHdl);
129
130    JSRecordInfo *recordInfo = nullptr;
131    bool hasRecord = jsPandaFile->CheckAndGetRecordInfo(requestEntryPoint, &recordInfo);
132    if (!hasRecord) {
133        JSHandle<JSTaggedValue> exp(thread, JSTaggedValue::Exception());
134        THROW_MODULE_NOT_FOUND_ERROR_WITH_RETURN_VALUE(thread, requestStr, recordName, exp);
135    }
136    if (jsPandaFile->IsJson(recordInfo)) {
137        JSHandle<JSTaggedValue> result = JSHandle<JSTaggedValue>(thread,
138            ModuleDataExtractor::JsonParse(thread, jsPandaFile, requestEntryPoint));
139        // Set module.exports ---> exports
140        JSHandle<JSTaggedValue> exportsKey = thread->GlobalConstants()->GetHandledCjsExportsString();
141        SlowRuntimeStub::StObjByName(thread, module.GetTaggedValue(), exportsKey.GetTaggedValue(),
142                                     result.GetTaggedValue());
143        return result;
144    }
145    // Execute required JSPandaFile
146    RequireExecution(thread, filename, requestEntryPoint);
147    if (thread->HasPendingException()) {
148        thread->GetCurrentEcmaContext()->HandleUncaughtException();
149        return thread->GlobalConstants()->GetHandledUndefined();
150    }
151    // Search from Module.cache after execution.
152    JSHandle<JSTaggedValue> cachedExports = SearchFromModuleCache(thread, recordNameHdl);
153    if (cachedExports->IsHole()) {
154        LOG_ECMA(FATAL) << "CJS REQUIRE FAIL : Can not obtain module, after executing required jsPandaFile";
155        UNREACHABLE();
156    }
157    return cachedExports;
158}
159
160void CjsModule::RequireExecution(JSThread *thread, const CString &mergedFilename, const CString &requestEntryPoint)
161{
162    std::shared_ptr<JSPandaFile> jsPandaFile =
163        JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, mergedFilename, requestEntryPoint);
164    if (jsPandaFile == nullptr) {
165        LOG_FULL(FATAL) << "Load current file's panda file failed. Current file is " <<  mergedFilename;
166    }
167    JSPandaFileExecutor::Execute(thread, jsPandaFile.get(), requestEntryPoint);
168}
169
170JSTaggedValue CjsModule::Require(JSThread *thread, JSHandle<EcmaString> &request,
171                                 [[maybe_unused]] JSHandle<CjsModule> &parent,
172                                 [[maybe_unused]] bool isMain)
173{
174    Load(thread, request);
175    return JSTaggedValue::Undefined();
176}
177} // namespace panda::ecmascript