* Copyright (c) 2021 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/module/js_module_namespace.h"
#include "ecmascript/global_env.h"
#include "ecmascript/module/js_module_deregister.h"
#include "ecmascript/module/js_shared_module_manager.h"
#include "ecmascript/module/module_path_helper.h"
#include "ecmascript/module/module_value_accessor.h"
#include "ecmascript/object_factory-inl.h"
#include "ecmascript/patch/quick_fix_manager.h"
#include "ecmascript/shared_objects/js_shared_array.h"
namespace panda::ecmascript {
JSHandle<JSTaggedValue> ModuleNamespace::CreateSortedExports(JSThread *thread, const JSHandle<TaggedArray> &exports)
{
auto globalConst = thread->GlobalConstants();
JSHandle<JSArray> exportsArray = JSArray::CreateArrayFromList(thread, exports);
JSHandle<JSTaggedValue> sortedExports = JSHandle<JSTaggedValue>::Cast(exportsArray);
JSHandle<JSTaggedValue> fn = globalConst->GetHandledUndefined();
JSArray::Sort(thread, sortedExports, fn);
return sortedExports;
}
JSHandle<ModuleNamespace> ModuleNamespace::ModuleNamespaceCreate(JSThread *thread,
const JSHandle<JSTaggedValue> &module,
const JSHandle<TaggedArray> &exports)
{
auto globalConst = thread->GlobalConstants();
ASSERT(module->IsModuleRecord());
if (SourceTextModule::IsSharedModule(JSHandle<SourceTextModule>::Cast(module))) {
return SharedModuleManager::GetInstance()->SModuleNamespaceCreate(thread, module, exports);
}
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<ModuleNamespace> mNp = factory->NewModuleNamespace();
mNp->SetModule(thread, module);
JSHandle<JSTaggedValue> sortedExports = CreateSortedExports(thread, exports);
mNp->SetExports(thread, sortedExports);
JSHandle<JSTaggedValue> toStringTag = thread->GlobalConstants()->GetHandledToStringTagSymbol();
JSHandle<JSTaggedValue> moduleString = globalConst->GetHandledModuleString();
PropertyDescriptor des(thread, moduleString, false, false, false);
JSHandle<JSObject> mNpObj = JSHandle<JSObject>::Cast(mNp);
JSObject::DefineOwnProperty(thread, mNpObj, toStringTag, des);
SetModuleDeregisterProcession(thread, mNp, ModuleDeregister::FreeModuleRecord);
JSHandle<ModuleRecord> moduleRecord = JSHandle<ModuleRecord>::Cast(module);
SourceTextModule::Cast(moduleRecord.GetTaggedValue().GetTaggedObject())->SetNamespace(thread,
mNp.GetTaggedValue());
mNp->GetJSHClass()->SetExtensible(false);
return mNp;
}
OperationResult ModuleNamespace::GetProperty(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key)
{
ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key");
if (key->IsSymbol()) {
return JSObject::GetProperty(thread, obj, key);
}
JSMutableHandle<ModuleNamespace> moduleNamespace(thread, JSHandle<ModuleNamespace>::Cast(obj));
JSMutableHandle<SourceTextModule> mm(thread, moduleNamespace->GetModule(thread));
thread->GetEcmaVM()->GetQuickFixManager()->UpdateHotReloadModuleAndNamespace(thread, mm, moduleNamespace);
JSHandle<JSTaggedValue> exports(thread, moduleNamespace->GetExports(thread));
if (exports->IsUndefined()) {
return OperationResult(thread, thread->GlobalConstants()->GetUndefined(), PropertyMetaData(false));
}
if (exports->IsJSArray()) {
if (!JSArray::IncludeInSortedValue(thread, exports, key))
return OperationResult(thread, thread->GlobalConstants()->GetUndefined(), PropertyMetaData(false));
} else if (exports->IsJSSharedArray() && !JSSharedArray::IncludeInSortedValue(thread, exports, key)) {
return OperationResult(thread, thread->GlobalConstants()->GetUndefined(), PropertyMetaData(false));
}
ResolvedMultiMap resolvedMap;
JSHandle<JSTaggedValue> binding = SourceTextModule::ResolveExport(thread, mm, key, resolvedMap);
RETURN_VALUE_IF_ABRUPT_COMPLETION(
thread, OperationResult(thread, JSTaggedValue::Exception(), PropertyMetaData(false)));
if (binding->IsNull() || binding->IsString()) {
CString requestMod = ModulePathHelper::ReformatPath(mm->GetEcmaModuleFilenameString());
LOG_FULL(FATAL) << "Module: '" << requestMod << SourceTextModule::GetResolveErrorReason(binding) <<
ConvertToString(thread, key.GetTaggedValue()) << ".";
}
JSTaggedValue result;
JSType type = binding->GetTaggedObject()->GetClass()->GetObjectType();
switch (type) {
case JSType::RESOLVEDBINDING_RECORD: {
JSHandle<ResolvedBinding> resolvedBind = JSHandle<ResolvedBinding>::Cast(binding);
JSTaggedValue targetModule = resolvedBind->GetModule(thread);
ASSERT(!targetModule.IsUndefined());
JSHandle<SourceTextModule> module(thread, targetModule);
if (module->GetStatus() == ModuleStatus::INSTANTIATED) {
LOG_FULL(ERROR) << "Module is not evaluated, module is :" << module->GetEcmaModuleRecordNameString();
}
ModuleTypes moduleType = module->GetTypes();
if (UNLIKELY(SourceTextModule::IsNativeModule(moduleType))) {
result = ModuleValueAccessor::GetNativeOrCjsModuleValue(thread, module,
resolvedBind->GetBindingName(thread));
RETURN_VALUE_IF_ABRUPT_COMPLETION(
thread, OperationResult(thread, JSTaggedValue::Exception(), PropertyMetaData(false)));
} else {
result = module->GetModuleValue(thread, resolvedBind->GetBindingName(thread), true);
}
break;
}
case JSType::RESOLVEDINDEXBINDING_RECORD: {
JSHandle<ResolvedIndexBinding> resolvedBind = JSHandle<ResolvedIndexBinding>::Cast(binding);
JSTaggedValue targetModule = resolvedBind->GetModule(thread);
ASSERT(!targetModule.IsUndefined());
JSMutableHandle<SourceTextModule> module(thread, targetModule);
thread->GetEcmaVM()->GetQuickFixManager()->UpdateHotReloadModule(thread, module);
if (module->GetStatus() == ModuleStatus::INSTANTIATED) {
LOG_FULL(ERROR) << "Module is not evaluated, module is :" << module->GetEcmaModuleRecordNameString();
}
ModuleTypes moduleType = module->GetTypes();
if (UNLIKELY(SourceTextModule::IsNativeModule(moduleType))) {
result = ModuleValueAccessor::GetNativeOrCjsModuleValue(thread, module, resolvedBind->GetIndex());
RETURN_VALUE_IF_ABRUPT_COMPLETION(
thread, OperationResult(thread, JSTaggedValue::Exception(), PropertyMetaData(false)));
} else {
if (SourceTextModule::IsSharedModule(module)) {
JSHandle<SourceTextModule> sharedModule = SharedModuleManager::GetInstance()->GetSModule(
thread, module->GetEcmaModuleRecordNameString());
if (sharedModule.GetTaggedValue().IsSourceTextModule()) {
module.Update(sharedModule);
}
}
result = module->GetModuleValue(thread, resolvedBind->GetIndex(), true);
}
break;
}
case JSType::RESOLVEDRECORDINDEXBINDING_RECORD:
LOG_FULL(INFO) << "RESOLVEDRECORDINDEXBINDING_RECORD";
break;
case JSType::RESOLVEDRECORDBINDING_RECORD:
LOG_FULL(INFO) << "RESOLVEDRECORDINDEXBINDING_RECORD";
break;
default:
LOG_FULL(FATAL) << "UNREACHABLE";
}
return OperationResult(thread, result, PropertyMetaData(true));
}
JSHandle<TaggedArray> ModuleNamespace::OwnPropertyKeys(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
{
ASSERT(obj->IsModuleNamespace());
JSHandle<ModuleNamespace> moduleNamespace = JSHandle<ModuleNamespace>::Cast(obj);
JSHandle<JSTaggedValue> exports(thread, moduleNamespace->GetExports(thread));
JSHandle<TaggedArray> exportsArray = JSArray::ToTaggedArray(thread, exports);
if (!ModuleNamespace::ValidateKeysAvailable(thread, moduleNamespace, exportsArray)) {
return exportsArray;
}
JSHandle<TaggedArray> symbolKeys = JSObject::GetOwnPropertyKeys(thread, JSHandle<JSObject>(obj));
JSHandle<TaggedArray> result = TaggedArray::Append(thread, exportsArray, symbolKeys);
return result;
}
JSHandle<TaggedArray> ModuleNamespace::OwnEnumPropertyKeys(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
{
ASSERT(obj->IsModuleNamespace());
JSHandle<ModuleNamespace> moduleNamespace = JSHandle<ModuleNamespace>::Cast(obj);
JSHandle<JSTaggedValue> exports(thread, moduleNamespace->GetExports(thread));
JSHandle<TaggedArray> exportsArray = JSArray::ToTaggedArray(thread, exports);
if (!ModuleNamespace::ValidateKeysAvailable(thread, moduleNamespace, exportsArray)) {
return exportsArray;
}
JSHandle<TaggedArray> symbolKeys = JSObject::GetOwnEnumPropertyKeys(thread, JSHandle<JSObject>(obj));
JSHandle<TaggedArray> result = TaggedArray::Append(thread, exportsArray, symbolKeys);
return result;
}
bool ModuleNamespace::PreventExtensions()
{
return true;
}
bool ModuleNamespace::DefineOwnProperty(JSThread *thread,
const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key,
PropertyDescriptor desc)
{
ASSERT(obj->IsModuleNamespace());
if (key->IsSymbol()) {
bool res = JSObject::OrdinaryDefineOwnProperty(thread, JSHandle<JSObject>(obj), key, desc);
return res;
}
PropertyDescriptor current(thread);
if (!GetOwnProperty(thread, obj, key, current)) {
return false;
}
if (desc.IsAccessorDescriptor()) {
return false;
}
if (desc.HasConfigurable() && desc.IsConfigurable()) {
return false;
}
if (desc.HasEnumerable() && !desc.IsEnumerable()) {
return false;
}
if (desc.HasWritable() && !desc.IsWritable()) {
return false;
}
if (desc.HasValue()) {
JSHandle<JSTaggedValue> descValue = desc.GetValue();
JSHandle<JSTaggedValue> currentValue = current.GetValue();
return JSTaggedValue::SameValue(thread, descValue, currentValue);
}
return true;
}
bool ModuleNamespace::HasProperty(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key)
{
ASSERT(obj->IsModuleNamespace());
if (key->IsSymbol()) {
return JSObject::HasProperty(thread, JSHandle<JSObject>(obj), key);
}
JSHandle<ModuleNamespace> moduleNamespace = JSHandle<ModuleNamespace>::Cast(obj);
JSHandle<JSTaggedValue> exports(thread, moduleNamespace->GetExports(thread));
if (exports->IsUndefined()) {
return false;
}
if (JSArray::IncludeInSortedValue(thread, exports, key)) {
return true;
}
return false;
}
bool ModuleNamespace::SetPrototype([[maybe_unused]] const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &proto)
{
ASSERT(obj->IsModuleNamespace());
ASSERT(proto->IsECMAObject() || proto->IsNull());
return proto->IsNull();
}
bool ModuleNamespace::GetOwnProperty(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key, PropertyDescriptor &desc)
{
if (key->IsSymbol()) {
return JSObject::GetOwnProperty(thread, JSHandle<JSObject>(obj), key, desc);
}
JSHandle<ModuleNamespace> moduleNamespace = JSHandle<ModuleNamespace>::Cast(obj);
JSHandle<JSTaggedValue> exports(thread, moduleNamespace->GetExports(thread));
if (exports->IsUndefined()) {
return false;
}
if (!JSArray::IncludeInSortedValue(thread, exports, key)) {
return false;
}
JSHandle<JSTaggedValue> value = ModuleNamespace::GetProperty(thread, obj, key).GetValue();
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
desc.SetValue(value);
desc.SetEnumerable(true);
desc.SetWritable(true);
desc.SetConfigurable(false);
return true;
}
bool ModuleNamespace::SetProperty(JSThread *thread, bool mayThrow)
{
if (mayThrow) {
THROW_TYPE_ERROR_AND_RETURN(thread, "Cannot assign to read only property of Object Module", false);
}
return false;
}
bool ModuleNamespace::DeleteProperty(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key)
{
ASSERT(obj->IsModuleNamespace());
ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key");
if (key->IsSymbol()) {
return JSObject::DeleteProperty(thread, JSHandle<JSObject>(obj), key);
}
JSHandle<ModuleNamespace> moduleNamespace = JSHandle<ModuleNamespace>::Cast(obj);
JSHandle<JSTaggedValue> exports(thread, moduleNamespace->GetExports(thread));
if (exports->IsUndefined()) {
return true;
}
if (JSArray::IncludeInSortedValue(thread, exports, key)) {
return false;
}
return true;
}
bool ModuleNamespace::ValidateKeysAvailable(JSThread *thread, const JSHandle<ModuleNamespace> &moduleNamespace,
const JSHandle<TaggedArray> &exports)
{
JSHandle<SourceTextModule> mm(thread, moduleNamespace->GetModule(thread));
uint32_t exportsLength = exports->GetLength();
for (uint32_t idx = 0; idx < exportsLength; idx++) {
JSHandle<JSTaggedValue> key(thread, exports->Get(thread, idx));
ResolvedMultiMap resolvedMap;
JSHandle<JSTaggedValue> binding = SourceTextModule::ResolveExport(thread, mm, key, resolvedMap);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
ASSERT(binding->IsResolvedBinding() || binding->IsResolvedIndexBinding());
JSTaggedValue targetModule = JSTaggedValue::Undefined();
if (binding->IsResolvedBinding()) {
targetModule = JSHandle<ResolvedBinding>::Cast(binding)->GetModule(thread);
} else if (binding->IsResolvedIndexBinding()) {
targetModule = JSHandle<ResolvedIndexBinding>::Cast(binding)->GetModule(thread);
}
ASSERT(!targetModule.IsUndefined());
JSTaggedValue dictionary = SourceTextModule::Cast(targetModule.GetTaggedObject())->GetNameDictionary(thread);
if (dictionary.IsUndefined()) {
THROW_REFERENCE_ERROR_AND_RETURN(thread, "module environment is undefined", false);
}
}
return true;
}
void ModuleNamespace::SetModuleDeregisterProcession(JSThread *thread, const JSHandle<ModuleNamespace> &nameSpace,
const NativePointerCallback &callback)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<SourceTextModule> module(thread, nameSpace->GetModule(thread));
CString moduleStr = SourceTextModule::GetModuleName(module.GetTaggedValue());
int srcLength = strlen(moduleStr.c_str()) + 1;
auto moduleNameData = thread->GetEcmaVM()->GetNativeAreaAllocator()->AllocateBuffer(srcLength);
if (memcpy_s(moduleNameData, srcLength, moduleStr.c_str(), srcLength) != EOK) {
LOG_ECMA(FATAL) << "Failed to copy module name's data.";
UNREACHABLE();
}
char *tmpData = reinterpret_cast<char *>(moduleNameData);
tmpData[srcLength - 1] = '\0';
ASSERT(nameSpace->GetDeregisterProcession(thread).IsUndefined());
JSHandle<JSNativePointer> registerPointer = factory->NewJSNativePointer(
reinterpret_cast<void *>(moduleNameData), callback, reinterpret_cast<void *>(thread), false, srcLength);
nameSpace->SetDeregisterProcession(thread, registerPointer.GetTaggedValue());
}
}