/* * 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/require/js_cjs_module.h" #include "ecmascript/aot_file_manager.h" #include "ecmascript/base/path_helper.h" #include "ecmascript/builtins/builtins_json.h" #include "ecmascript/interpreter/interpreter-inl.h" #include "ecmascript/interpreter/slow_runtime_stub.h" #include "ecmascript/platform/file.h" #include "ecmascript/require/js_cjs_module_cache.h" #include "ecmascript/require/js_require_manager.h" #include "ecmascript/jspandafile/js_pandafile.h" #include "ecmascript/jspandafile/js_pandafile_executor.h" #include "ecmascript/jspandafile/js_pandafile_manager.h" namespace panda::ecmascript { using BuiltinsJson = builtins::BuiltinsJson; using PathHelper = base::PathHelper; void CjsModule::InitializeModule(JSThread *thread, JSHandle &module, JSHandle &filename, JSHandle &dirname) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle dirKey(factory->NewFromASCII("path")); SlowRuntimeStub::StObjByName(thread, module.GetTaggedValue(), dirKey.GetTaggedValue(), dirname.GetTaggedValue()); JSHandle filenameKey(factory->NewFromASCII("filename")); SlowRuntimeStub::StObjByName(thread, module.GetTaggedValue(), filenameKey.GetTaggedValue(), filename.GetTaggedValue()); module->SetFilename(thread, filename.GetTaggedValue()); module->SetPath(thread, dirname.GetTaggedValue()); return; } JSHandle CjsModule::SearchFromModuleCache(JSThread *thread, JSHandle &filename) { JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle moduleObj(env->GetCjsModuleFunction()); JSHandle cacheName = globalConst->GetHandledCjsCacheString(); JSTaggedValue modCache = SlowRuntimeStub::LdObjByName(thread, moduleObj.GetTaggedValue(), cacheName.GetTaggedValue(), false, JSTaggedValue::Undefined()); JSHandle moduleCache = JSHandle(thread, modCache); if (moduleCache->ContainsModule(filename.GetTaggedValue())) { JSHandle cachedModule = JSHandle(thread, moduleCache->GetModule(filename.GetTaggedValue())); JSHandle exportsName = globalConst->GetHandledCjsExportsString(); JSTaggedValue cachedExports = SlowRuntimeStub::LdObjByName(thread, cachedModule.GetTaggedValue(), exportsName.GetTaggedValue(), false, JSTaggedValue::Undefined()); return JSHandle(thread, cachedExports); } return JSHandle(thread, JSTaggedValue::Hole()); } void CjsModule::PutIntoCache(JSThread *thread, JSHandle &module, JSHandle &filename) { JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSHandle moduleObj(env->GetCjsModuleFunction()); JSHandle cacheName = globalConst->GetHandledCjsCacheString(); JSTaggedValue modCache = SlowRuntimeStub::LdObjByName(thread, moduleObj.GetTaggedValue(), cacheName.GetTaggedValue(), false, JSTaggedValue::Undefined()); JSHandle moduleCache = JSHandle(thread, modCache); JSHandle moduleHandle = JSHandle::Cast(module); JSHandle newCache = CjsModuleCache::PutIfAbsentAndReset(thread, moduleCache, filename, moduleHandle); SlowRuntimeStub::StObjByName(thread, moduleObj.GetTaggedValue(), cacheName.GetTaggedValue(), newCache.GetTaggedValue()); } JSHandle CjsModule::Load(JSThread *thread, JSHandle &request) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); // Get local jsPandaFile's dirPath JSMutableHandle parent(thread, JSTaggedValue::Undefined()); JSMutableHandle dirname(thread, JSTaggedValue::Undefined()); const JSPandaFile *jsPandaFile = EcmaInterpreter::GetNativeCallPandafile(thread); JSHandle entrypointVal(thread, EcmaInterpreter::GetCurrentEntryPoint(thread)); CString mergedFilename = jsPandaFile->GetJSPandaFileDesc(); CString requestEntryPoint = JSPandaFile::ENTRY_MAIN_FUNCTION; JSMutableHandle filename(thread, JSTaggedValue::Undefined()); if (jsPandaFile->IsBundlePack()) { PathHelper::ResolveCurrentPath(thread, parent, dirname, jsPandaFile); filename.Update(ResolveFilenameFromNative(thread, dirname.GetTaggedValue(), request.GetTaggedValue())); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); mergedFilename = ConvertToString(filename.GetTaggedValue()); } else { CString currentEntryPoint = ConvertToString(entrypointVal.GetTaggedValue()); CString requestStr = ConvertToString(request.GetTaggedValue()); requestEntryPoint = PathHelper::ConcatFileNameWithMerge(thread, jsPandaFile, mergedFilename, currentEntryPoint, requestStr); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); filename.Update(factory->NewFromUtf8(requestEntryPoint)); } // Search from Module.cache JSHandle maybeCachedExports = SearchFromModuleCache(thread, filename); if (!maybeCachedExports->IsHole()) { return maybeCachedExports; } // Don't get required exports from cache, execute required JSPandaFile. // module = new Module(), which belongs to required JSPandaFile. JSHandle module = factory->NewCjsModule(); dirname.Update(PathHelper::ResolveDirPath(thread, filename)); InitializeModule(thread, module, filename, dirname); PutIntoCache(thread, module, filename); if (jsPandaFile->IsJson(thread, requestEntryPoint)) { JSHandle result = JSHandle(thread, ModuleManager::JsonParse(thread, jsPandaFile, requestEntryPoint)); // Set module.exports ---> exports JSHandle exportsKey = thread->GlobalConstants()->GetHandledCjsExportsString(); SlowRuntimeStub::StObjByName(thread, module.GetTaggedValue(), exportsKey.GetTaggedValue(), result.GetTaggedValue()); return result; } // Execute required JSPandaFile RequireExecution(thread, mergedFilename, requestEntryPoint); if (thread->HasPendingException()) { thread->GetEcmaVM()->HandleUncaughtException(thread->GetException().GetTaggedObject()); return thread->GlobalConstants()->GetHandledUndefined(); } // Search from Module.cache after execution. JSHandle cachedExports = SearchFromModuleCache(thread, filename); if (cachedExports->IsHole()) { LOG_ECMA(ERROR) << "CJS REQUIRE FAIL : Can not obtain module, after executing required jsPandaFile"; UNREACHABLE(); } return cachedExports; } void CjsModule::RequireExecution(JSThread *thread, CString mergedFilename, CString requestEntryPoint) { const JSPandaFile *jsPandaFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, mergedFilename, requestEntryPoint); if (jsPandaFile == nullptr) { CString msg = "Load file with filename '" + mergedFilename + "' failed, recordName '" + requestEntryPoint + "'"; THROW_ERROR(thread, ErrorType::REFERENCE_ERROR, msg.c_str()); } JSPandaFileExecutor::Execute(thread, jsPandaFile, requestEntryPoint); } JSTaggedValue CjsModule::Require(JSThread *thread, JSHandle &request, [[maybe_unused]]JSHandle &parent, [[maybe_unused]]bool isMain) { Load(thread, request); return JSTaggedValue::Undefined(); } } // namespace panda::ecmascript