blob: 10fe31096a4c433aabad0f8fbfc9616e3904a6f2 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLanguagePch.h"
#include "Types/PropertyIndexRanges.h"
#include "Types/SimpleDictionaryPropertyDescriptor.h"
#include "Types/SimpleDictionaryTypeHandler.h"
#include "Types/NullTypeHandler.h"
#include "ModuleNamespace.h"
#include "ModuleNamespaceEnumerator.h"
namespace Js
{
Js::FunctionInfo ModuleNamespace::EntryInfo::SymbolIterator(FORCE_NO_WRITE_BARRIER_TAG(ModuleNamespace::EntrySymbolIterator));
ModuleNamespace::ModuleNamespace(ModuleRecordBase* moduleRecord, DynamicType* type) :
moduleRecord(moduleRecord), DynamicObject(type), unambiguousNonLocalExports(nullptr),
sortedExportedNames(nullptr), nsSlots(nullptr)
{
}
ModuleNamespace* ModuleNamespace::GetModuleNamespace(ModuleRecordBase* requestModule)
{
Assert(requestModule->IsSourceTextModuleRecord());
SourceTextModuleRecord* moduleRecord = SourceTextModuleRecord::FromHost(requestModule);
ModuleNamespace* nsObject = moduleRecord->GetNamespace();
if (nsObject != nullptr)
{
return nsObject;
}
ScriptContext* scriptContext = moduleRecord->GetRealm()->GetScriptContext();
Recycler* recycler = scriptContext->GetRecycler();
nsObject = RecyclerNew(recycler, ModuleNamespace, moduleRecord, scriptContext->GetLibrary()->GetModuleNamespaceType());
nsObject->Initialize();
moduleRecord->SetNamespace(nsObject);
return nsObject;
}
void ModuleNamespace::Initialize()
{
ScriptContext* scriptContext = moduleRecord->GetRealm()->GetScriptContext();
Recycler* recycler = scriptContext->GetRecycler();
SourceTextModuleRecord* sourceTextModuleRecord = static_cast<SourceTextModuleRecord*>(
static_cast<ModuleRecordBase*>(moduleRecord));
JavascriptLibrary* library = GetLibrary();
if (scriptContext->GetConfig()->IsES6ToStringTagEnabled())
{
DynamicObject::SetPropertyWithAttributes(PropertyIds::_symbolToStringTag, library->GetModuleTypeDisplayString(),
PropertyConfigurable, nullptr);
}
DynamicType* type = library->CreateFunctionWithLengthType(&EntryInfo::SymbolIterator);
RuntimeFunction* iteratorFunction = RecyclerNewEnumClass(scriptContext->GetRecycler(),
JavascriptLibrary::EnumFunctionClass, RuntimeFunction,
type, &EntryInfo::SymbolIterator);
DynamicObject::SetPropertyWithAttributes(PropertyIds::_symbolIterator, iteratorFunction, PropertyBuiltInMethodDefaults, nullptr);
ModuleImportOrExportEntryList* localExportList = sourceTextModuleRecord->GetLocalExportEntryList();
// We don't have a type handler that can handle ModuleNamespace object. We have properties that could be aliased
// like {export foo as foo1, foo2, foo3}, and external properties as reexport from current module. The problem with aliasing
// is that multiple propertyId can be associated with one slotIndex. We need to build from PropertyMap directly here.
// there is one instance of ModuleNamespace per module file; we can always use the BigPropertyIndex for security.
propertyMap = RecyclerNew(recycler, SimplePropertyDescriptorMap, recycler, sourceTextModuleRecord->GetLocalExportCount());
if (localExportList != nullptr)
{
localExportList->Map([=](ModuleImportOrExportEntry exportEntry) {
PropertyId exportNameId = exportEntry.exportName->GetPropertyId();
PropertyId localNameId = exportEntry.localName->GetPropertyId();
const Js::PropertyRecord* exportNameRecord = scriptContext->GetThreadContext()->GetPropertyName(exportNameId);
ModuleNameRecord* importRecord = nullptr;
AssertMsg(exportNameId != Js::Constants::NoProperty, "should have been initialized already");
// ignore local exports that are actually indirect exports.
if (sourceTextModuleRecord->GetImportEntryList() == nullptr ||
!sourceTextModuleRecord->ResolveImport(localNameId, &importRecord))
{
BigPropertyIndex index = sourceTextModuleRecord->GetLocalExportSlotIndexByExportName(exportNameId);
Assert((uint)index < sourceTextModuleRecord->GetLocalExportCount());
SimpleDictionaryPropertyDescriptor<BigPropertyIndex> propertyDescriptor = { index, PropertyModuleNamespaceDefault };
propertyMap->Add(exportNameRecord, propertyDescriptor);
}
});
}
// update the local slot to use the storage for local exports.
SetNSSlotsForModuleNS(sourceTextModuleRecord->GetLocalExportSlots());
// For items that are not in the local export list, we need to resolve them to get it
ExportedNames* exportedNames = sourceTextModuleRecord->GetExportedNames(nullptr);
ModuleNameRecord* moduleNameRecord = nullptr;
#if DBG
uint unresolvableExportsCount = 0;
uint localExportCount = 0;
#endif
if (exportedNames != nullptr)
{
exportedNames->Map([&](PropertyId propertyId) {
if (!moduleRecord->ResolveExport(propertyId, nullptr, nullptr, &moduleNameRecord))
{
// ignore ambigious resolution.
#if DBG
unresolvableExportsCount++;
#endif
return;
}
// non-ambiguous resolution.
if (moduleNameRecord == nullptr)
{
JavascriptError::ThrowSyntaxError(scriptContext, JSERR_ResolveExportFailed, scriptContext->GetPropertyName(propertyId)->GetBuffer());
}
if (moduleNameRecord->module == moduleRecord)
{
// skip local exports as they are covered in the localExportSlots.
#if DBG
localExportCount++;
#endif
return;
}
Assert(moduleNameRecord->module != moduleRecord);
this->AddUnambiguousNonLocalExport(propertyId, moduleNameRecord);
});
}
#if DBG
uint totalExportCount = exportedNames != nullptr ? exportedNames->Count() : 0;
uint unambiguousNonLocalCount = (this->GetUnambiguousNonLocalExports() != nullptr) ? this->GetUnambiguousNonLocalExports()->Count() : 0;
Assert(totalExportCount == localExportCount + unambiguousNonLocalCount + unresolvableExportsCount);
#endif
BOOL result = this->PreventExtensions();
Assert(result);
}
void ModuleNamespace::AddUnambiguousNonLocalExport(PropertyId propertyId, ModuleNameRecord* nonLocalExportNameRecord)
{
Recycler* recycler = GetScriptContext()->GetRecycler();
if (unambiguousNonLocalExports == nullptr)
{
unambiguousNonLocalExports = RecyclerNew(recycler, UnambiguousExportMap, recycler, 4);
}
// keep a local copy of the module/
unambiguousNonLocalExports->AddNew(propertyId, *nonLocalExportNameRecord);
}
PropertyQueryFlags ModuleNamespace::HasPropertyQuery(PropertyId propertyId)
{
SimpleDictionaryPropertyDescriptor<BigPropertyIndex> propertyDescriptor;
const Js::PropertyRecord* propertyRecord = GetScriptContext()->GetThreadContext()->GetPropertyName(propertyId);
if (propertyRecord->IsSymbol())
{
return this->DynamicObject::HasPropertyQuery(propertyId);
}
if (propertyMap != nullptr && propertyMap->TryGetValue(propertyRecord, &propertyDescriptor))
{
return Property_Found;
}
if (unambiguousNonLocalExports != nullptr)
{
return JavascriptConversion::BooleanToPropertyQueryFlags(unambiguousNonLocalExports->ContainsKey(propertyId));
}
return Property_NotFound;
}
BOOL ModuleNamespace::HasOwnProperty(PropertyId propertyId)
{
return HasProperty(propertyId);
}
PropertyQueryFlags ModuleNamespace::GetPropertyQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
SimpleDictionaryPropertyDescriptor<BigPropertyIndex> propertyDescriptor;
const Js::PropertyRecord* propertyRecord = requestContext->GetThreadContext()->GetPropertyName(propertyId);
if (propertyRecord->IsSymbol())
{
return this->DynamicObject::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext);
}
if (propertyMap != nullptr && propertyMap->TryGetValue(propertyRecord, &propertyDescriptor))
{
Assert((uint)propertyDescriptor.propertyIndex < ((SourceTextModuleRecord*)static_cast<ModuleRecordBase*>(moduleRecord))->GetLocalExportCount());
PropertyValueInfo::SetNoCache(info, this); // Disable inlinecache for localexport slot for now.
//if ((PropertyIndex)propertyDescriptor.propertyIndex == propertyDescriptor.propertyIndex)
//{
// PropertyValueInfo::Set(info, this, (PropertyIndex)propertyDescriptor.propertyIndex, propertyDescriptor.Attributes);
//}
*value = this->GetNSSlot(propertyDescriptor.propertyIndex);
return Property_Found;
}
if (unambiguousNonLocalExports != nullptr)
{
ModuleNameRecord moduleNameRecord;
// TODO: maybe we can cache the slot address & offset, instead of looking up everytime? We do need to look up the reference everytime.
if (unambiguousNonLocalExports->TryGetValue(propertyId, &moduleNameRecord))
{
return JavascriptConversion::BooleanToPropertyQueryFlags(moduleNameRecord.module->GetNamespace()->GetProperty(originalInstance, moduleNameRecord.bindingName, value, info, requestContext));
}
}
return Property_NotFound;
}
PropertyQueryFlags ModuleNamespace::GetPropertyQuery(Var originalInstance, JavascriptString* propertyNameString, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
const PropertyRecord* propertyRecord = nullptr;
GetScriptContext()->GetOrAddPropertyRecord(propertyNameString->GetString(), propertyNameString->GetLength(), &propertyRecord);
return JavascriptConversion::BooleanToPropertyQueryFlags(GetProperty(originalInstance, propertyRecord->GetPropertyId(), value, info, requestContext));
}
BOOL ModuleNamespace::GetInternalProperty(Var instance, PropertyId internalPropertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return FALSE;
}
PropertyQueryFlags ModuleNamespace::GetPropertyReferenceQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return JavascriptConversion::BooleanToPropertyQueryFlags(GetProperty(originalInstance, propertyId, value, info, requestContext));
}
BOOL ModuleNamespace::GetEnumerator(JavascriptStaticEnumerator * enumerator, EnumeratorFlags flags, ScriptContext* requestContext, ForInCache * forInCache)
{
ModuleNamespaceEnumerator* moduleEnumerator = ModuleNamespaceEnumerator::New(this, flags, requestContext, forInCache);
if (moduleEnumerator == nullptr)
{
return FALSE;
}
return enumerator->Initialize(moduleEnumerator, nullptr, nullptr, flags, requestContext, nullptr);
}
BOOL ModuleNamespace::DeleteProperty(PropertyId propertyId, PropertyOperationFlags flags)
{
//Assert: IsPropertyKey(P) is true.
//Let exports be O.[[Exports]].
//If P is an element of exports, return false.
//Return true.
return !HasProperty(propertyId);
}
BOOL ModuleNamespace::DeleteProperty(JavascriptString *propertyNameString, PropertyOperationFlags flags)
{
//Assert: IsPropertyKey(P) is true.
//Let exports be O.[[Exports]].
//If P is an element of exports, return false.
//Return true.
PropertyRecord const *propertyRecord = nullptr;
if (JavascriptOperators::ShouldTryDeleteProperty(this, propertyNameString, &propertyRecord))
{
Assert(propertyRecord);
return DeleteProperty(propertyRecord->GetPropertyId(), flags);
}
return TRUE;
}
BOOL ModuleNamespace::GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("{ModuleNamespaceObject}"));
return TRUE;
}
BOOL ModuleNamespace::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("Object, (ModuleNamespaceObject)"));
return TRUE;
}
Var ModuleNamespace::GetNSSlot(BigPropertyIndex propertyIndex)
{
Assert((uint)propertyIndex < static_cast<SourceTextModuleRecord*>(static_cast<ModuleRecordBase*>(moduleRecord))->GetLocalExportCount());
return this->nsSlots[propertyIndex];
}
PropertyId ModuleNamespace::GetPropertyId(BigPropertyIndex index)
{
SimpleDictionaryPropertyDescriptor<BigPropertyIndex> propertyDescriptor;
if (propertyMap->TryGetValueAt(index, &propertyDescriptor))
{
const PropertyRecord* propertyRecord = propertyMap->GetKeyAt(index);
return propertyRecord->GetPropertyId();
}
return Constants::NoProperty;
}
BOOL ModuleNamespace::FindNextProperty(BigPropertyIndex& index, JavascriptString** propertyString, PropertyId* propertyId, PropertyAttributes* attributes, ScriptContext * requestContext) const
{
if (index < propertyMap->Count())
{
SimpleDictionaryPropertyDescriptor<BigPropertyIndex> propertyDescriptor(propertyMap->GetValueAt(index));
Assert(propertyDescriptor.Attributes == PropertyModuleNamespaceDefault);
const PropertyRecord* propertyRecord = propertyMap->GetKeyAt(index);
*propertyId = propertyRecord->GetPropertyId();
if (propertyString != nullptr)
{
*propertyString = requestContext->GetPropertyString(*propertyId);
}
if (attributes != nullptr)
{
*attributes = propertyDescriptor.Attributes;
}
return TRUE;
}
else
{
*propertyId = Constants::NoProperty;
if (propertyString != nullptr)
{
*propertyString = nullptr;
}
}
return FALSE;
}
// We will make sure the iterator will iterate through the exported properties in sorted order.
// There is no such requirement for enumerator (forin).
ListForListIterator* ModuleNamespace::EnsureSortedExportedNames()
{
if (sortedExportedNames == nullptr)
{
ExportedNames* exportedNames = moduleRecord->GetExportedNames(nullptr);
ScriptContext* scriptContext = GetScriptContext();
sortedExportedNames = ListForListIterator::New(scriptContext->GetRecycler());
exportedNames->Map([&](PropertyId propertyId) {
JavascriptString* propertyString = scriptContext->GetPropertyString(propertyId);
sortedExportedNames->Add(propertyString);
});
sortedExportedNames->Sort([](void* context, const void* left, const void* right) ->int {
JavascriptString** leftString = (JavascriptString**) (left);
JavascriptString** rightString = (JavascriptString**) (right);
if (JavascriptString::LessThan(*leftString, *rightString))
{
return -1;
}
if (JavascriptString::LessThan(*rightString, *leftString))
{
return 1;
}
return 0;
}, nullptr);
}
return sortedExportedNames;
}
Var ModuleNamespace::EntrySymbolIterator(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNamespace, _u("Namespace[Symbol.iterator]"));
}
if (JavascriptOperators::GetTypeId(args[0]) != TypeIds_ModuleNamespace)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNamespace, _u("Namespace[Symbol.iterator]"));
}
ModuleNamespace* moduleNamespace = ModuleNamespace::FromVar(args[0]);
ListForListIterator* sortedExportedNames = moduleNamespace->EnsureSortedExportedNames();
return scriptContext->GetLibrary()->CreateListIterator(sortedExportedNames);
}
}