/* * 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 "js_cjs_module.h" #include "ecmascript/interpreter/interpreter-inl.h" #include "ecmascript/interpreter/slow_runtime_stub.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 { void JSCjsModule::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 JSCjsModule::SearchFromModuleCache(JSThread *thread, JSHandle &filename) { [[maybe_unused]] EcmaHandleScope handleScope(thread); 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 JSCjsModule::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::PutIfAbsent(thread, moduleCache, filename, moduleHandle); SlowRuntimeStub::StObjByName(thread, moduleObj.GetTaggedValue(), cacheName.GetTaggedValue(), newCache.GetTaggedValue()); } JSHandle JSCjsModule::Load(JSThread *thread, JSHandle &request) { [[maybe_unused]] EcmaHandleScope handleScope(thread); 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); JSRequireManager::ResolveCurrentPath(thread, parent, dirname, jsPandaFile); // Get filename from Callback JSHandle filenameStr = ResolveFilename(thread, dirname.GetTaggedValue(), request.GetTaggedValue()); JSHandle filename = JSHandle::Cast(filenameStr); // 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(); InitializeModule(thread, module, filename, dirname); PutIntoCache(thread, module, filename); // Execute required JSPandaFile RequireExecution(thread, JSHandle::Cast(filename)); // 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 JSCjsModule::RequireExecution(JSThread *thread, const JSHandle &moduleFileName) { CString moduleFilenameStr = ConvertToString(moduleFileName.GetTaggedValue()); const JSPandaFile *jsPandaFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(moduleFilenameStr, JSPandaFile::ENTRY_MAIN_FUNCTION); if (jsPandaFile == nullptr) { LOG_ECMA(ERROR) << "open jsPandaFile " << moduleFilenameStr << " error"; UNREACHABLE(); } JSPandaFileExecutor::Execute(thread, jsPandaFile); } JSHandle JSCjsModule::ResolveFilename(JSThread *thread, JSTaggedValue dirname, JSTaggedValue request) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); ResolvePathCallback resolvePathCallback = thread->GetEcmaVM()->GetResolvePathCallback(); if (resolvePathCallback == nullptr) { JSHandle nativeRequireName = ResolveFilenameFromNative(thread, dirname, request); return nativeRequireName; } std::string modDirname = std::string(ConvertToString(EcmaString::Cast(dirname.GetHeapObject()))); std::string modFile = std::string(ConvertToString(EcmaString::Cast(request.GetHeapObject()))); std::string callbackRequireName = resolvePathCallback(modDirname, modFile); CString fullname = callbackRequireName.c_str(); return factory->NewFromUtf8(fullname); } JSHandle JSCjsModule::ResolveFilenameFromNative(JSThread *thread, JSTaggedValue dirname, JSTaggedValue request) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); CString dirnameStr = ConvertToString(EcmaString::Cast(dirname.GetHeapObject())); CString requestStr = ConvertToString(EcmaString::Cast(request.GetHeapObject())); if (requestStr.find("./") == 0) { requestStr = requestStr.substr(2); // 2 : delete './' } int suffixEnd = static_cast(requestStr.find_last_of('.')); if (suffixEnd == -1) { RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread); } CString fullname; if (requestStr[0] == '/') { // absolute FilePath fullname = requestStr.substr(0, suffixEnd) + ".abc"; } else { int pos = static_cast(dirnameStr.find_last_of('/')); if (pos == -1) { RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread); } fullname = dirnameStr.substr(0, pos + 1) + requestStr.substr(0, suffixEnd) + ".abc"; } return factory->NewFromUtf8(fullname); } JSTaggedValue JSCjsModule::Require(JSThread *thread, JSHandle &request, [[maybe_unused]]JSHandle &parent, [[maybe_unused]]bool isMain) { Load(thread, request); return JSTaggedValue::Undefined(); } } // namespace panda::ecmascript