| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "RuntimeTypePch.h" |
| |
| namespace Js |
| { |
| template <typename T> |
| DictionaryTypeHandlerBase<T>* DictionaryTypeHandlerBase<T>::New(Recycler * recycler, int initialCapacity, uint16 inlineSlotCapacity, uint16 offsetOfInlineSlots) |
| { |
| return NewTypeHandler<DictionaryTypeHandlerBase>(recycler, initialCapacity, inlineSlotCapacity, offsetOfInlineSlots); |
| } |
| |
| template <typename T> |
| DictionaryTypeHandlerBase<T>::DictionaryTypeHandlerBase(Recycler* recycler) : |
| DynamicTypeHandler(1), |
| nextPropertyIndex(0) |
| #if ENABLE_FIXED_FIELDS |
| , singletonInstance(nullptr) |
| #endif |
| { |
| SetIsInlineSlotCapacityLocked(); |
| propertyMap = RecyclerNew(recycler, PropertyDescriptorMap, recycler, this->GetSlotCapacity()); |
| } |
| |
| template <typename T> |
| DictionaryTypeHandlerBase<T>::DictionaryTypeHandlerBase(Recycler* recycler, int slotCapacity, uint16 inlineSlotCapacity, uint16 offsetOfInlineSlots) : |
| // Do not RoundUp passed in slotCapacity. This may be called by ConvertTypeHandler for an existing DynamicObject and should use the real existing slotCapacity. |
| DynamicTypeHandler(slotCapacity, inlineSlotCapacity, offsetOfInlineSlots), |
| nextPropertyIndex(0) |
| #if ENABLE_FIXED_FIELDS |
| , singletonInstance(nullptr) |
| #endif |
| { |
| SetIsInlineSlotCapacityLocked(); |
| Assert(GetSlotCapacity() <= MaxPropertyIndexSize); |
| propertyMap = RecyclerNew(recycler, PropertyDescriptorMap, recycler, slotCapacity); |
| } |
| |
| // |
| // Takes over a given dictionary typeHandler. Used only by subclass. |
| // |
| template <typename T> |
| DictionaryTypeHandlerBase<T>::DictionaryTypeHandlerBase(DictionaryTypeHandlerBase* typeHandler) : |
| DynamicTypeHandler(typeHandler->GetSlotCapacity(), typeHandler->GetInlineSlotCapacity(), typeHandler->GetOffsetOfInlineSlots()), |
| propertyMap(typeHandler->propertyMap), nextPropertyIndex(typeHandler->nextPropertyIndex) |
| #if ENABLE_FIXED_FIELDS |
| , singletonInstance(typeHandler->singletonInstance) |
| #endif |
| { |
| Assert(typeHandler->GetIsInlineSlotCapacityLocked()); |
| CopyPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesInlineSlotCapacityLocked | PropertyTypesHasSpecialProperties, typeHandler->GetPropertyTypes()); |
| } |
| |
| template <typename T> |
| DictionaryTypeHandlerBase<T>::DictionaryTypeHandlerBase(Recycler* recycler, DictionaryTypeHandlerBase * typeHandler) : |
| DynamicTypeHandler(typeHandler), |
| nextPropertyIndex(typeHandler->nextPropertyIndex) |
| #if ENABLE_FIXED_FIELDS |
| , singletonInstance(nullptr) |
| #endif |
| { |
| Assert(this->GetIsInlineSlotCapacityLocked() == typeHandler->GetIsInlineSlotCapacityLocked()); |
| propertyMap = typeHandler->propertyMap->Clone(); |
| } |
| |
| template <typename T> |
| DynamicTypeHandler * DictionaryTypeHandlerBase<T>::Clone(Recycler * recycler) |
| { |
| return RecyclerNew(recycler, DictionaryTypeHandlerBase, recycler, this); |
| } |
| |
| template <typename T> |
| int DictionaryTypeHandlerBase<T>::GetPropertyCount() |
| { |
| return propertyMap->Count(); |
| } |
| |
| template <typename T> |
| PropertyId DictionaryTypeHandlerBase<T>::GetPropertyId(ScriptContext* scriptContext, PropertyIndex index) |
| { |
| if (index < propertyMap->Count()) |
| { |
| DictionaryPropertyDescriptor<T> descriptor = propertyMap->GetValueAt(index); |
| if (!(descriptor.Attributes & PropertyDeleted) && descriptor.HasNonLetConstGlobal()) |
| { |
| return propertyMap->GetKeyAt(index)->GetPropertyId(); |
| } |
| } |
| return Constants::NoProperty; |
| } |
| |
| template <typename T> |
| PropertyId DictionaryTypeHandlerBase<T>::GetPropertyId(ScriptContext* scriptContext, BigPropertyIndex index) |
| { |
| if (index < propertyMap->Count()) |
| { |
| DictionaryPropertyDescriptor<T> descriptor = propertyMap->GetValueAt(index); |
| if (!(descriptor.Attributes & PropertyDeleted) && descriptor.HasNonLetConstGlobal()) |
| { |
| return propertyMap->GetKeyAt(index)->GetPropertyId(); |
| } |
| } |
| return Constants::NoProperty; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyStringName, |
| PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) |
| { |
| Assert(propertyStringName); |
| Assert(propertyId); |
| Assert(type); |
| |
| for (; index < propertyMap->Count(); ++index) |
| { |
| DictionaryPropertyDescriptor<T> descriptor = propertyMap->GetValueAt(index); |
| PropertyAttributes attribs = descriptor.Attributes; |
| |
| if (!(attribs & PropertyDeleted) && (!!(flags & EnumeratorFlags::EnumNonEnumerable) || (attribs & PropertyEnumerable)) && |
| (!(attribs & PropertyLetConstGlobal) || descriptor.HasNonLetConstGlobal())) |
| { |
| const PropertyRecord* propertyRecord = propertyMap->GetKeyAt(index); |
| |
| // Skip this property if it is a symbol and we are not including symbol properties |
| if (!(flags & EnumeratorFlags::EnumSymbols) && propertyRecord->IsSymbol()) |
| { |
| continue; |
| } |
| |
| // Pass back attributes of this property so caller can use them if it needs |
| if (attributes != nullptr) |
| { |
| *attributes = attribs; |
| } |
| |
| *propertyId = propertyRecord->GetPropertyId(); |
| PropertyString* propertyString = scriptContext->GetPropertyString(*propertyId); |
| *propertyStringName = propertyString; |
| T dataSlot = descriptor.template GetDataPropertyIndex<false>(); |
| if (dataSlot != NoSlots && (attribs & PropertyWritable) && type == typeToEnumerate) |
| { |
| PropertyValueInfo::SetCacheInfo(info, propertyString, propertyString->GetLdElemInlineCache(), false); |
| SetPropertyValueInfo(info, instance, dataSlot, &descriptor); |
| } |
| else |
| { |
| PropertyValueInfo::SetNoCache(info, instance); |
| } |
| return TRUE; |
| } |
| } |
| PropertyValueInfo::SetNoCache(info, instance); |
| |
| return FALSE; |
| } |
| |
| template <> |
| BOOL DictionaryTypeHandlerBase<BigPropertyIndex>::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, |
| PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) |
| { |
| Assert(false); |
| Throw::InternalError(); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, |
| PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) |
| { |
| PropertyIndex local = (PropertyIndex)index; |
| Assert(index <= Constants::UShortMaxValue || index == Constants::NoBigSlot); |
| BOOL result = this->FindNextProperty(scriptContext, local, propertyString, propertyId, attributes, type, typeToEnumerate, flags, instance, info); |
| index = local; |
| return result; |
| } |
| |
| template <> |
| BOOL DictionaryTypeHandlerBase<BigPropertyIndex>::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyStringName, |
| PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) |
| { |
| Assert(propertyStringName); |
| Assert(propertyId); |
| Assert(type); |
| |
| for (; index < propertyMap->Count(); ++index) |
| { |
| DictionaryPropertyDescriptor<BigPropertyIndex> descriptor = propertyMap->GetValueAt(index); |
| PropertyAttributes attribs = descriptor.Attributes; |
| if (!(attribs & PropertyDeleted) && (!!(flags & EnumeratorFlags::EnumNonEnumerable) || (attribs & PropertyEnumerable)) && |
| (!(attribs & PropertyLetConstGlobal) || descriptor.HasNonLetConstGlobal())) |
| { |
| const PropertyRecord* propertyRecord = propertyMap->GetKeyAt(index); |
| |
| // Skip this property if it is a symbol and we are not including symbol properties |
| if (!(flags & EnumeratorFlags::EnumSymbols) && propertyRecord->IsSymbol()) |
| { |
| continue; |
| } |
| |
| if (attributes != nullptr) |
| { |
| *attributes = attribs; |
| } |
| |
| *propertyId = propertyRecord->GetPropertyId(); |
| *propertyStringName = scriptContext->GetPropertyString(*propertyId); |
| |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| template <typename T> |
| PropertyIndex DictionaryTypeHandlerBase<T>::GetPropertyIndex(PropertyRecord const* propertyRecord) |
| { |
| return GetPropertyIndex_Internal<false>(propertyRecord); |
| } |
| |
| template <typename T> |
| PropertyIndex DictionaryTypeHandlerBase<T>::GetRootPropertyIndex(PropertyRecord const* propertyRecord) |
| { |
| return GetPropertyIndex_Internal<true>(propertyRecord); |
| } |
| |
| #if ENABLE_NATIVE_CODEGEN |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::GetPropertyEquivalenceInfo(PropertyRecord const* propertyRecord, PropertyEquivalenceInfo& info) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| if (this->propertyMap->TryGetReference(propertyRecord, &descriptor) && !(descriptor->Attributes & PropertyDeleted)) |
| { |
| AssertMsg(descriptor->template GetDataPropertyIndex<false>() != Constants::NoSlot, "We don't support equivalent object type spec on accessors."); |
| AssertMsg(descriptor->template GetDataPropertyIndex<false>() <= Constants::PropertyIndexMax, "We don't support equivalent object type spec on big property indexes."); |
| T propertyIndex = descriptor->template GetDataPropertyIndex<false>(); |
| info.slotIndex = propertyIndex <= Constants::PropertyIndexMax ? |
| AdjustValidSlotIndexForInlineSlots(static_cast<PropertyIndex>(propertyIndex)) : Constants::NoSlot; |
| info.isAuxSlot = propertyIndex >= GetInlineSlotCapacity(); |
| info.isWritable = !!(descriptor->Attributes & PropertyWritable); |
| } |
| else |
| { |
| info.slotIndex = Constants::NoSlot; |
| info.isAuxSlot = false; |
| info.isWritable = false; |
| } |
| return info.slotIndex != Constants::NoSlot; |
| } |
| |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::IsObjTypeSpecEquivalent(const Type* type, const TypeEquivalenceRecord& record, uint& failedPropertyIndex) |
| { |
| uint propertyCount = record.propertyCount; |
| EquivalentPropertyEntry* properties = record.properties; |
| for (uint pi = 0; pi < propertyCount; pi++) |
| { |
| const EquivalentPropertyEntry* refInfo = &properties[pi]; |
| if (!this->IsObjTypeSpecEquivalentImpl<false>(type, refInfo)) |
| { |
| failedPropertyIndex = pi; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::IsObjTypeSpecEquivalent(const Type* type, const EquivalentPropertyEntry *entry) |
| { |
| return this->IsObjTypeSpecEquivalentImpl<true>(type, entry); |
| } |
| |
| template <typename T> |
| template <bool doLock> |
| bool DictionaryTypeHandlerBase<T>::IsObjTypeSpecEquivalentImpl(const Type* type, const EquivalentPropertyEntry *entry) |
| { |
| ScriptContext* scriptContext = type->GetScriptContext(); |
| |
| T absSlotIndex = Constants::NoSlot; |
| PropertyIndex relSlotIndex = Constants::NoSlot; |
| |
| const PropertyRecord* propertyRecord = |
| doLock ? scriptContext->GetPropertyNameLocked(entry->propertyId) : scriptContext->GetPropertyName(entry->propertyId); |
| DictionaryPropertyDescriptor<T>* descriptor; |
| if (this->propertyMap->TryGetReference(propertyRecord, &descriptor) && !(descriptor->Attributes & PropertyDeleted)) |
| { |
| // We don't object type specialize accessors at this point, so if we see an accessor on an object we must have a mismatch. |
| // When we add support for accessors we will need another bit on EquivalentPropertyEntry indicating whether we expect |
| // a data or accessor property. |
| if (descriptor->GetIsAccessor()) |
| { |
| return false; |
| } |
| |
| absSlotIndex = descriptor->template GetDataPropertyIndex<false>(); |
| if (absSlotIndex <= Constants::PropertyIndexMax) |
| { |
| relSlotIndex = AdjustValidSlotIndexForInlineSlots(static_cast<PropertyIndex>(absSlotIndex)); |
| } |
| } |
| |
| if (relSlotIndex != Constants::NoSlot) |
| { |
| if (relSlotIndex != entry->slotIndex || ((absSlotIndex >= GetInlineSlotCapacity()) != entry->isAuxSlot)) |
| { |
| return false; |
| } |
| |
| if (entry->mustBeWritable && (!(descriptor->Attributes & PropertyWritable) || descriptor->IsOrMayBecomeFixed())) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| if (entry->slotIndex != Constants::NoSlot || entry->mustBeWritable) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| #endif |
| |
| template <typename T> |
| template <bool allowLetConstGlobal> |
| PropertyIndex DictionaryTypeHandlerBase<T>::GetPropertyIndex_Internal(PropertyRecord const* propertyRecord) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor) && !(descriptor->Attributes & PropertyDeleted)) |
| { |
| return descriptor->template GetDataPropertyIndex<allowLetConstGlobal>(); |
| } |
| else |
| { |
| return NoSlots; |
| } |
| } |
| |
| template <> |
| template <bool allowLetConstGlobal> |
| PropertyIndex DictionaryTypeHandlerBase<BigPropertyIndex>::GetPropertyIndex_Internal(PropertyRecord const* propertyRecord) |
| { |
| DictionaryPropertyDescriptor<BigPropertyIndex>* descriptor; |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor) && !(descriptor->Attributes & PropertyDeleted)) |
| { |
| BigPropertyIndex dataPropertyIndex = descriptor->GetDataPropertyIndex<allowLetConstGlobal>(); |
| if (dataPropertyIndex < Constants::NoSlot) |
| { |
| return (PropertyIndex)dataPropertyIndex; |
| } |
| } |
| return Constants::NoSlot; |
| } |
| |
| template <> |
| PropertyIndex DictionaryTypeHandlerBase<BigPropertyIndex>::GetRootPropertyIndex(PropertyRecord const* propertyRecord) |
| { |
| return Constants::NoSlot; |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::Add( |
| const PropertyRecord* propertyRecord, |
| PropertyAttributes attributes, |
| ScriptContext *const scriptContext) |
| { |
| return Add(propertyRecord, attributes, true, false, false, scriptContext); |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::Add( |
| const PropertyRecord* propertyRecord, |
| PropertyAttributes attributes, |
| bool isInitialized, bool isFixed, bool usedAsFixed, |
| ScriptContext *const scriptContext) |
| { |
| Assert(this->GetSlotCapacity() <= MaxPropertyIndexSize); // slotCapacity should never exceed MaxPropertyIndexSize |
| Assert(nextPropertyIndex < this->GetSlotCapacity()); // nextPropertyIndex must be ready |
| T index = ::Math::PostInc(nextPropertyIndex); |
| |
| DictionaryPropertyDescriptor<T> descriptor(index, attributes); |
| #if ENABLE_FIXED_FIELDS |
| Assert((!isFixed && !usedAsFixed) || (!IsInternalPropertyId(propertyRecord->GetPropertyId()) && this->singletonInstance != nullptr)); |
| descriptor.SetIsInitialized(isInitialized); |
| descriptor.SetIsFixed(isFixed); |
| descriptor.SetUsedAsFixed(usedAsFixed); |
| #endif |
| propertyMap->Add(propertyRecord, descriptor); |
| |
| scriptContext->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, attributes, propertyRecord, scriptContext); |
| scriptContext->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(this, attributes, propertyRecord, scriptContext); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::HasProperty(DynamicObject* instance, PropertyId propertyId, bool *noRedecl, _Inout_opt_ PropertyValueInfo* info) |
| { |
| return HasProperty_Internal<false>(instance, propertyId, noRedecl, info, nullptr, nullptr); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::HasRootProperty(DynamicObject* instance, PropertyId propertyId, bool *noRedecl, bool *pDeclaredProperty, bool *pNonconfigurableProperty) |
| { |
| return HasProperty_Internal<true>(instance, propertyId, noRedecl, nullptr /*info*/, pDeclaredProperty, pNonconfigurableProperty); |
| } |
| |
| template <typename T> |
| template <bool allowLetConstGlobal> |
| BOOL DictionaryTypeHandlerBase<T>::HasProperty_Internal(DynamicObject* instance, PropertyId propertyId, bool *noRedecl, _Inout_opt_ PropertyValueInfo* info, bool *pDeclaredProperty, bool *pNonconfigurableProperty) |
| { |
| // HasProperty is called with NoProperty in JavascriptDispatch.cpp to for undeferral of the |
| // deferred type system that DOM objects use. Allow NoProperty for this reason, but only |
| // here in HasProperty. |
| if (propertyId == Constants::NoProperty) |
| { |
| return false; |
| } |
| |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if ((descriptor->Attributes & PropertyDeleted) || (!allowLetConstGlobal && !descriptor->HasNonLetConstGlobal())) |
| { |
| return false; |
| } |
| if (noRedecl && descriptor->Attributes & PropertyNoRedecl) |
| { |
| *noRedecl = true; |
| } |
| if (pDeclaredProperty && descriptor->Attributes & (PropertyNoRedecl | PropertyDeclaredGlobal)) |
| { |
| *pDeclaredProperty = true; |
| } |
| if (pNonconfigurableProperty && !(descriptor->Attributes & PropertyConfigurable)) |
| { |
| *pNonconfigurableProperty = true; |
| } |
| if (info) |
| { |
| T dataSlot = descriptor->template GetDataPropertyIndex<allowLetConstGlobal>(); |
| if (dataSlot != NoSlots) |
| { |
| SetPropertyValueInfo(info, instance, dataSlot, descriptor); |
| } |
| else if (descriptor->GetGetterPropertyIndex() != NoSlots) |
| { |
| // PropertyAttributes is only one byte so it can't carry out data about whether this is an accessor. |
| // Accessors must be cached differently than normal properties, so if we want to cache this we must |
| // do so here rather than in the caller. However, caching here would require passing originalInstance and |
| // requestContext through a wide variety of call paths to this point (like we do for GetProperty), for |
| // very little improvement. For now, just block caching this case. |
| PropertyValueInfo::SetNoCache(info, instance); |
| } |
| } |
| return true; |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (instance->HasObjectArray() && propertyRecord->IsNumeric()) |
| { |
| return DictionaryTypeHandlerBase<T>::HasItem(instance, propertyRecord->GetNumericValue()); |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::HasProperty(DynamicObject* instance, JavascriptString* propertyNameString) |
| { |
| AssertMsg(!PropertyRecord::IsPropertyNameNumeric(propertyNameString->GetString(), propertyNameString->GetLength()), |
| "Numeric property names should have been converted to uint or PropertyRecord* before calling GetSetter"); |
| |
| JsUtil::CharacterBuffer<WCHAR> propertyName(propertyNameString->GetString(), propertyNameString->GetLength()); |
| DictionaryPropertyDescriptor<T>* descriptor; |
| if (propertyMap->TryGetReference(propertyName, &descriptor)) |
| { |
| if ((descriptor->Attributes & PropertyDeleted) || !descriptor->HasNonLetConstGlobal()) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::GetRootProperty(DynamicObject* instance, Var originalInstance, PropertyId propertyId, |
| Var* value, PropertyValueInfo* info, ScriptContext* requestContext) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "Instance must be a root object!"); |
| return GetProperty_Internal<true>(instance, originalInstance, propertyId, value, info, requestContext); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::GetProperty(DynamicObject* instance, Var originalInstance, PropertyId propertyId, |
| Var* value, PropertyValueInfo* info, ScriptContext* requestContext) |
| { |
| return GetProperty_Internal<false>(instance, originalInstance, propertyId, value, info, requestContext); |
| } |
| |
| template <typename T> |
| template <bool allowLetConstGlobal, typename PropertyType> |
| BOOL DictionaryTypeHandlerBase<T>::GetPropertyFromDescriptor(DynamicObject* instance, Var originalInstance, |
| DictionaryPropertyDescriptor<T>* descriptor, Var* value, PropertyValueInfo* info, PropertyType propertyT, ScriptContext* requestContext) |
| { |
| bool const isLetConstGlobal = (descriptor->Attributes & PropertyLetConstGlobal) != 0; |
| AssertMsg(!isLetConstGlobal || VarIs<RootObjectBase>(instance), "object must be a global object if letconstglobal is set"); |
| if (allowLetConstGlobal) |
| { |
| // GetRootProperty: false if not global |
| if (!(descriptor->Attributes & PropertyLetConstGlobal) && (descriptor->Attributes & PropertyDeleted)) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| // GetProperty: don't count deleted or global. |
| if (descriptor->Attributes & (PropertyDeleted | (descriptor->GetIsShadowed() ? 0 : PropertyLetConstGlobal))) |
| { |
| return false; |
| } |
| } |
| |
| T dataSlot = descriptor->template GetDataPropertyIndex<allowLetConstGlobal>(); |
| if (dataSlot != NoSlots) |
| { |
| *value = instance->GetSlot(dataSlot); |
| SetPropertyValueInfo(info, instance, dataSlot, descriptor); |
| } |
| else if (descriptor->GetGetterPropertyIndex() != NoSlots) |
| { |
| // We must update cache before calling a getter, because it can invalidate something. Bug# 593815 |
| SetPropertyValueInfoNonFixed(info, instance, descriptor->GetGetterPropertyIndex(), descriptor->Attributes); |
| CacheOperators::CachePropertyReadForGetter(info, originalInstance, propertyT, requestContext); |
| PropertyValueInfo::SetNoCache(info, instance); // we already cached getter, so we don't have to do it once more |
| |
| RecyclableObject* func = UnsafeVarTo<RecyclableObject>(instance->GetSlot(descriptor->GetGetterPropertyIndex())); |
| *value = JavascriptOperators::CallGetter(func, originalInstance, requestContext); |
| return true; |
| } |
| else |
| { |
| *value = instance->GetLibrary()->GetUndefined(); |
| return true; |
| } |
| return true; |
| } |
| |
| template <typename T> |
| template <bool allowLetConstGlobal> |
| BOOL DictionaryTypeHandlerBase<T>::GetProperty_Internal(DynamicObject* instance, Var originalInstance, PropertyId propertyId, |
| Var* value, PropertyValueInfo* info, ScriptContext* requestContext) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| return GetPropertyFromDescriptor<allowLetConstGlobal>(instance, originalInstance, descriptor, value, info, propertyId, requestContext); |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (instance->HasObjectArray() && propertyRecord->IsNumeric()) |
| { |
| return DictionaryTypeHandlerBase<T>::GetItem(instance, originalInstance, propertyRecord->GetNumericValue(), value, requestContext); |
| } |
| |
| *value = requestContext->GetMissingPropertyResult(); |
| return false; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::GetProperty(DynamicObject* instance, Var originalInstance, JavascriptString* propertyNameString, |
| Var* value, PropertyValueInfo* info, ScriptContext* requestContext) |
| { |
| AssertMsg(!PropertyRecord::IsPropertyNameNumeric(propertyNameString->GetString(), propertyNameString->GetLength()), |
| "Numeric property names should have been converted to uint or PropertyRecord* before calling GetSetter"); |
| |
| JsUtil::CharacterBuffer<WCHAR> propertyName(propertyNameString->GetString(), propertyNameString->GetLength()); |
| DictionaryPropertyDescriptor<T>* descriptor; |
| if (propertyMap->TryGetReference(propertyName, &descriptor)) |
| { |
| return GetPropertyFromDescriptor<false>(instance, originalInstance, descriptor, value, info, propertyName, requestContext); |
| } |
| |
| *value = requestContext->GetMissingPropertyResult(); |
| return false; |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::SetPropertyValueInfo(PropertyValueInfo* info, RecyclableObject* instance, T propIndex, DictionaryPropertyDescriptor<T>* descriptor) |
| { |
| SetPropertyValueInfoNonFixed(info, instance, propIndex, descriptor->Attributes); |
| if (descriptor->IsOrMayBecomeFixed()) |
| { |
| PropertyValueInfo::DisableStoreFieldCache(info); |
| } |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| // letconst shadowing a deleted property. don't bother to cache |
| PropertyValueInfo::SetNoCache(info, instance); |
| } |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::SetPropertyValueInfoNonFixed(PropertyValueInfo* info, RecyclableObject* instance, T propIndex, PropertyAttributes attributes, InlineCacheFlags flags) |
| { |
| PropertyValueInfo::Set(info, instance, propIndex, attributes, flags); |
| } |
| |
| template <> |
| void DictionaryTypeHandlerBase<BigPropertyIndex>::SetPropertyValueInfoNonFixed(PropertyValueInfo* info, RecyclableObject* instance, BigPropertyIndex propIndex, PropertyAttributes attributes, InlineCacheFlags flags) |
| { |
| PropertyValueInfo::SetNoCache(info, instance); |
| } |
| |
| template <typename T> |
| DescriptorFlags DictionaryTypeHandlerBase<T>::GetSetter(DynamicObject* instance, PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) |
| { |
| return GetSetter_Internal<false>(instance, propertyId, setterValue, info, requestContext); |
| } |
| |
| template <typename T> |
| DescriptorFlags DictionaryTypeHandlerBase<T>::GetRootSetter(DynamicObject* instance, PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "Instance must be a root object!"); |
| return GetSetter_Internal<true>(instance, propertyId, setterValue, info, requestContext); |
| } |
| |
| template <typename T> |
| template <bool allowLetConstGlobal> |
| DescriptorFlags DictionaryTypeHandlerBase<T>::GetSetter_Internal(DynamicObject* instance, PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| return GetSetterFromDescriptor<allowLetConstGlobal>(instance, descriptor, setterValue, info); |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (instance->HasObjectArray() && propertyRecord->IsNumeric()) |
| { |
| return DictionaryTypeHandlerBase<T>::GetItemSetter(instance, propertyRecord->GetNumericValue(), setterValue, requestContext); |
| } |
| |
| return None; |
| } |
| |
| template <typename T> |
| template <bool allowLetConstGlobal> |
| DescriptorFlags DictionaryTypeHandlerBase<T>::GetSetterFromDescriptor(DynamicObject* instance, DictionaryPropertyDescriptor<T> * descriptor, Var* setterValue, PropertyValueInfo* info) |
| { |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| return None; |
| } |
| if (descriptor->template GetDataPropertyIndex<allowLetConstGlobal>() != NoSlots) |
| { |
| // not a setter but shadows |
| if (allowLetConstGlobal && (descriptor->Attributes & PropertyLetConstGlobal)) |
| { |
| return (descriptor->Attributes & PropertyConst) ? (DescriptorFlags)(Const | Data) : WritableData; |
| } |
| if (descriptor->Attributes & PropertyWritable) |
| { |
| return WritableData; |
| } |
| if (descriptor->Attributes & PropertyConst) |
| { |
| return (DescriptorFlags)(Const | Data); |
| } |
| return Data; |
| } |
| else if (descriptor->GetSetterPropertyIndex() != NoSlots) |
| { |
| *setterValue = ((DynamicObject*)instance)->GetSlot(descriptor->GetSetterPropertyIndex()); |
| SetPropertyValueInfoNonFixed(info, instance, descriptor->GetSetterPropertyIndex(), descriptor->Attributes, InlineCacheSetterFlag); |
| return Accessor; |
| } |
| return None; |
| } |
| |
| template <typename T> |
| DescriptorFlags DictionaryTypeHandlerBase<T>::GetSetter(DynamicObject* instance, JavascriptString* propertyNameString, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) |
| { |
| AssertMsg(!PropertyRecord::IsPropertyNameNumeric(propertyNameString->GetString(), propertyNameString->GetLength()), |
| "Numeric property names should have been converted to uint or PropertyRecord* before calling GetSetter"); |
| |
| JsUtil::CharacterBuffer<WCHAR> propertyName(propertyNameString->GetString(), propertyNameString->GetLength()); |
| DictionaryPropertyDescriptor<T>* descriptor; |
| |
| if (propertyMap->TryGetReference(propertyName, &descriptor)) |
| { |
| return GetSetterFromDescriptor<false>(instance, descriptor, setterValue, info); |
| } |
| |
| return None; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetRootProperty(DynamicObject* instance, PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "Instance must be a root object!"); |
| return SetProperty_Internal<true>(instance, propertyId, value, flags, info); |
| } |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::InitProperty(DynamicObject* instance, PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) |
| { |
| return SetProperty_Internal<false>(instance, propertyId, value, flags, info, true /* IsInit */); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetProperty(DynamicObject* instance, PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) |
| { |
| return SetProperty_Internal<false>(instance, propertyId, value, flags, info); |
| } |
| |
| template <typename T> |
| template <bool allowLetConstGlobal> |
| void DictionaryTypeHandlerBase<T>::SetPropertyWithDescriptor( |
| _In_ DynamicObject* instance, |
| _In_ PropertyRecord const* propertyRecord, |
| _Inout_ DictionaryPropertyDescriptor<T> ** pdescriptor, |
| _In_ Var value, |
| _In_ PropertyOperationFlags flags, |
| _Inout_opt_ PropertyValueInfo* info) |
| { |
| Assert(pdescriptor && *pdescriptor); |
| DictionaryPropertyDescriptor<T> * descriptor = *pdescriptor; |
| PropertyId propertyId = propertyRecord->GetPropertyId(); |
| Assert(instance); |
| Assert((descriptor->Attributes & PropertyDeleted) == 0 || (allowLetConstGlobal && descriptor->GetIsShadowed())); |
| |
| // DictionaryTypeHandlers are not supposed to be shared. |
| Assert(!GetIsOrMayBecomeShared()); |
| #if ENABLE_FIXED_FIELDS |
| DynamicObject* localSingletonInstance = this->singletonInstance != nullptr ? this->singletonInstance->Get() : nullptr; |
| Assert(this->singletonInstance == nullptr || localSingletonInstance == instance); |
| #endif |
| T dataSlotAllowLetConstGlobal = descriptor->template GetDataPropertyIndex<allowLetConstGlobal>(); |
| if (dataSlotAllowLetConstGlobal != NoSlots) |
| { |
| if (allowLetConstGlobal |
| && (descriptor->Attributes & PropertyNoRedecl) |
| && !(flags & PropertyOperation_AllowUndecl)) |
| { |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| if (scriptContext->IsUndeclBlockVar(instance->GetSlot(dataSlotAllowLetConstGlobal))) |
| { |
| JavascriptError::ThrowReferenceError(scriptContext, JSERR_UseBeforeDeclaration); |
| } |
| } |
| #if ENABLE_FIXED_FIELDS |
| if (!descriptor->GetIsInitialized()) |
| { |
| if ((flags & PropertyOperation_PreInit) == 0) |
| { |
| descriptor->SetIsInitialized(true); |
| if (localSingletonInstance == instance && !IsInternalPropertyId(propertyId) && |
| (flags & (PropertyOperation_NonFixedValue | PropertyOperation_SpecialValue)) == 0) |
| { |
| Assert(value != nullptr); |
| // We don't want fixed properties on external objects. See DynamicObject::ResetObject for more information. |
| Assert(!instance->IsExternal()); |
| descriptor->SetIsFixed(VarIs<JavascriptFunction>(value) ? ShouldFixMethodProperties() : (ShouldFixDataProperties() && CheckHeuristicsForFixedDataProps(instance, propertyId, value))); |
| } |
| } |
| } |
| else |
| { |
| InvalidateFixedField(instance, propertyId, descriptor); |
| } |
| #endif |
| SetSlotUnchecked(instance, dataSlotAllowLetConstGlobal, value); |
| // If we just added a fixed method, don't populate the inline cache so that we always take the slow path |
| // when overwriting this property and correctly invalidate any JIT-ed code that hard-coded this method. |
| if (!descriptor->IsOrMayBecomeFixed()) |
| { |
| SetPropertyValueInfoNonFixed(info, instance, dataSlotAllowLetConstGlobal, GetLetConstGlobalPropertyAttributes<allowLetConstGlobal>(descriptor->Attributes)); |
| } |
| else |
| { |
| PropertyValueInfo::SetNoCache(info, instance); |
| } |
| } |
| else if (descriptor->GetSetterPropertyIndex() != NoSlots) |
| { |
| RecyclableObject* func = VarTo<RecyclableObject>(instance->GetSlot(descriptor->GetSetterPropertyIndex())); |
| JavascriptOperators::CallSetter(func, instance, value, NULL); |
| |
| // Wait for the setter to return before setting up the inline cache info, as the setter may change |
| // the attributes |
| |
| if (propertyMap->TryGetReference(propertyRecord, pdescriptor)) |
| { |
| descriptor = *pdescriptor; |
| T dataSlot = descriptor->template GetDataPropertyIndex<false>(); |
| if (dataSlot != NoSlots) |
| { |
| SetPropertyValueInfoNonFixed(info, instance, dataSlot, descriptor->Attributes); |
| } |
| else if (descriptor->GetSetterPropertyIndex() != NoSlots) |
| { |
| SetPropertyValueInfoNonFixed(info, instance, descriptor->GetSetterPropertyIndex(), descriptor->Attributes, InlineCacheSetterFlag); |
| } |
| } |
| else |
| { |
| *pdescriptor = nullptr; |
| } |
| } |
| if (NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(propertyId)) |
| { |
| this->SetHasSpecialProperties(); |
| if (GetFlags() & IsPrototypeFlag) |
| { |
| instance->GetScriptContext()->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear(); |
| } |
| } |
| SetPropertyUpdateSideEffect(instance, propertyId, value, SideEffects_Any); |
| } |
| |
| template <typename T> |
| template <bool allowLetConstGlobal> |
| BOOL DictionaryTypeHandlerBase<T>::SetProperty_Internal(DynamicObject* instance, PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info, bool isInit) |
| { |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| DictionaryPropertyDescriptor<T>* descriptor; |
| bool throwIfNotExtensible = (flags & (PropertyOperation_ThrowIfNotExtensible | PropertyOperation_StrictMode)) != 0; |
| bool isForce = (flags & PropertyOperation_Force) != 0; |
| |
| JavascriptLibrary::CheckAndInvalidateIsConcatSpreadableCache(propertyId, scriptContext); |
| |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| #if ENABLE_FIXED_FIELDS |
| Assert(descriptor->SanityCheckFixedBits()); |
| #endif |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| if (!isForce) |
| { |
| if (!this->VerifyIsExtensible(scriptContext, throwIfNotExtensible)) |
| { |
| return false; |
| } |
| } |
| scriptContext->InvalidateProtoCaches(propertyId); |
| if (descriptor->Attributes & PropertyLetConstGlobal) |
| { |
| descriptor->Attributes = PropertyDynamicTypeDefaults | (descriptor->Attributes & (PropertyLetConstGlobal | PropertyNoRedecl)); |
| } |
| else |
| { |
| descriptor->Attributes = PropertyDynamicTypeDefaults; |
| } |
| instance->SetHasNoEnumerableProperties(false); |
| descriptor->ConvertToData(); |
| } |
| else if (!allowLetConstGlobal && descriptor->HasNonLetConstGlobal() && !(descriptor->Attributes & PropertyWritable)) |
| { |
| if (!isForce) |
| { |
| JavascriptError::ThrowCantAssignIfStrictMode(flags, scriptContext); |
| } |
| |
| // Since we separate LdFld and StFld caches there is no point in caching for StFld with non-writable properties, except perhaps |
| // to prepopulate the type property cache (which we do share between LdFld and StFld), for potential future field loads. This |
| // would require additional handling in CacheOperators::CachePropertyWrite, such that for !info-IsWritable() we don't populate |
| // the local cache (that would be illegal), but still populate the type's property cache. |
| PropertyValueInfo::SetNoCache(info, instance); |
| return false; |
| } |
| else if (isInit && descriptor->GetIsAccessor()) |
| { |
| descriptor->ConvertToData(); |
| } |
| SetPropertyWithDescriptor<allowLetConstGlobal>(instance, propertyRecord, &descriptor, value, flags, info); |
| return true; |
| } |
| |
| // Always check numeric propertyRecord. This may create objectArray. |
| if (propertyRecord->IsNumeric()) |
| { |
| // Calls this or subclass implementation |
| return SetItem(instance, propertyRecord->GetNumericValue(), value, flags); |
| } |
| return this->AddProperty(instance, propertyRecord, value, PropertyDynamicTypeDefaults, info, flags, throwIfNotExtensible, SideEffects_Any); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetProperty(DynamicObject* instance, JavascriptString* propertyNameString, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) |
| { |
| // Either the property exists in the dictionary, in which case a PropertyRecord for it exists, |
| // or we have to add it to the dictionary, in which case we need to get or create a PropertyRecord. |
| // Thus, just get or create one and call the PropertyId overload of SetProperty. |
| PropertyRecord const * propertyRecord; |
| instance->GetScriptContext()->GetOrAddPropertyRecord(propertyNameString, &propertyRecord); |
| return DictionaryTypeHandlerBase<T>::SetProperty(instance, propertyRecord->GetPropertyId(), value, flags, info); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetInternalProperty(DynamicObject* instance, PropertyId propertyId, Var value, PropertyOperationFlags flags) |
| { |
| return SetPropertyWithAttributes(instance, propertyId, value, PropertyInternalDefaults, nullptr, flags); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::DeleteProperty(DynamicObject* instance, PropertyId propertyId, PropertyOperationFlags propertyOperationFlags) |
| { |
| return DeleteProperty_Internal<false>(instance, propertyId, propertyOperationFlags); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::DeleteProperty(DynamicObject *instance, JavascriptString *propertyNameString, PropertyOperationFlags propertyOperationFlags) |
| { |
| AssertMsg(!PropertyRecord::IsPropertyNameNumeric(propertyNameString->GetString(), propertyNameString->GetLength()), |
| "Numeric property names should have been converted to uint or PropertyRecord* "); |
| |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| JavascriptLibrary* library = scriptContext->GetLibrary(); |
| DictionaryPropertyDescriptor<T>* descriptor; |
| JsUtil::CharacterBuffer<WCHAR> propertyName(propertyNameString->GetString(), propertyNameString->GetLength()); |
| |
| if (propertyMap->TryGetReference(propertyName, &descriptor)) |
| { |
| if (!this->GetHasSpecialProperties() && NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(propertyNameString)) |
| { |
| // If you are deleting a valueOf/toString and the flag wasn't set, it means you are deleting the default |
| // implementation off of Object.prototype |
| this->SetHasSpecialProperties(); |
| if (GetFlags() & IsPrototypeFlag) |
| { |
| library->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear(); |
| } |
| } |
| #if ENABLE_FIXED_FIELDS |
| Assert(descriptor->SanityCheckFixedBits()); |
| #endif |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| return true; |
| } |
| else if (!(descriptor->Attributes & PropertyConfigurable)) |
| { |
| // Let/const properties do not have attributes and they cannot be deleted |
| JavascriptError::ThrowCantDeleteIfStrictModeOrNonconfigurable( |
| propertyOperationFlags, scriptContext, propertyNameString->GetString()); |
| |
| return false; |
| } |
| |
| Var undefined = library->GetUndefined(); |
| |
| if (descriptor->HasNonLetConstGlobal()) |
| { |
| T dataSlot = descriptor->template GetDataPropertyIndex<false>(); |
| if (dataSlot != NoSlots) |
| { |
| SetSlotUnchecked(instance, dataSlot, undefined); |
| } |
| else |
| { |
| Assert(descriptor->GetIsAccessor()); |
| SetSlotUnchecked(instance, descriptor->GetGetterPropertyIndex(), undefined); |
| SetSlotUnchecked(instance, descriptor->GetSetterPropertyIndex(), undefined); |
| } |
| |
| if (this->GetFlags() & IsPrototypeFlag) |
| { |
| scriptContext->InvalidateProtoCaches(scriptContext->GetOrAddPropertyIdTracked(propertyNameString->GetString(), propertyNameString->GetLength())); |
| } |
| |
| if ((descriptor->Attributes & PropertyLetConstGlobal) == 0) |
| { |
| Assert(!descriptor->GetIsShadowed()); |
| descriptor->Attributes = PropertyDeletedDefaults; |
| } |
| else |
| { |
| descriptor->Attributes &= ~PropertyDynamicTypeDefaults; |
| descriptor->Attributes |= PropertyDeletedDefaults; |
| } |
| #if ENABLE_FIXED_FIELDS |
| InvalidateFixedField(instance, propertyNameString, descriptor); |
| #endif |
| |
| // Change the type so as we can invalidate the cache in fast path jit |
| if (instance->GetType()->HasBeenCached()) |
| { |
| instance->ChangeType(); |
| } |
| SetPropertyUpdateSideEffect(instance, propertyName, nullptr, SideEffects_Any); |
| return true; |
| } |
| |
| Assert(descriptor->Attributes & PropertyLetConstGlobal); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::DeleteRootProperty(DynamicObject* instance, PropertyId propertyId, PropertyOperationFlags propertyOperationFlags) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "Instance must be a root object!"); |
| return DeleteProperty_Internal<true>(instance, propertyId, propertyOperationFlags); |
| } |
| |
| template <typename T> |
| template <bool allowLetConstGlobal> |
| BOOL DictionaryTypeHandlerBase<T>::DeleteProperty_Internal(DynamicObject* instance, PropertyId propertyId, PropertyOperationFlags propertyOperationFlags) |
| { |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (!this->GetHasSpecialProperties() && NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(propertyId)) |
| { |
| this->SetHasSpecialProperties(); |
| if (GetFlags() & IsPrototypeFlag) |
| { |
| scriptContext->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear(); |
| } |
| } |
| #if ENABLE_FIXED_FIELDS |
| Assert(descriptor->SanityCheckFixedBits()); |
| #endif |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| // If PropertyDeleted and PropertyLetConstGlobal are set then we have both |
| // a deleted global property and let/const variable in this descriptor. |
| // If allowLetConstGlobal is true then the let/const shadows the property |
| // and we should return false for a failed delete by going into the else |
| // if branch below. |
| if (allowLetConstGlobal && (descriptor->Attributes & PropertyLetConstGlobal)) |
| { |
| JavascriptError::ThrowCantDeleteIfStrictMode(propertyOperationFlags, scriptContext, propertyRecord->GetBuffer()); |
| |
| return false; |
| } |
| return true; |
| } |
| else if (!(descriptor->Attributes & PropertyConfigurable) || |
| (allowLetConstGlobal && (descriptor->Attributes & PropertyLetConstGlobal))) |
| { |
| // Let/const properties do not have attributes and they cannot be deleted |
| JavascriptError::ThrowCantDeleteIfStrictModeOrNonconfigurable( |
| propertyOperationFlags, scriptContext, scriptContext->GetPropertyName(propertyId)->GetBuffer()); |
| |
| return false; |
| } |
| |
| Var undefined = scriptContext->GetLibrary()->GetUndefined(); |
| |
| if (descriptor->HasNonLetConstGlobal()) |
| { |
| T dataSlot = descriptor->template GetDataPropertyIndex<false>(); |
| if (dataSlot != NoSlots) |
| { |
| SetSlotUnchecked(instance, dataSlot, undefined); |
| } |
| else |
| { |
| Assert(descriptor->GetIsAccessor()); |
| SetSlotUnchecked(instance, descriptor->GetGetterPropertyIndex(), undefined); |
| SetSlotUnchecked(instance, descriptor->GetSetterPropertyIndex(), undefined); |
| } |
| |
| if (this->GetFlags() & IsPrototypeFlag) |
| { |
| scriptContext->InvalidateProtoCaches(propertyId); |
| } |
| |
| if ((descriptor->Attributes & PropertyLetConstGlobal) == 0) |
| { |
| Assert(!descriptor->GetIsShadowed()); |
| descriptor->Attributes = PropertyDeletedDefaults; |
| } |
| else |
| { |
| descriptor->Attributes &= ~PropertyDynamicTypeDefaults; |
| descriptor->Attributes |= PropertyDeletedDefaults; |
| } |
| #if ENABLE_FIXED_FIELDS |
| InvalidateFixedField(instance, propertyId, descriptor); |
| #endif |
| |
| // Change the type so as we can invalidate the cache in fast path jit |
| if (instance->GetType()->HasBeenCached()) |
| { |
| instance->ChangeType(); |
| } |
| SetPropertyUpdateSideEffect(instance, propertyId, nullptr, SideEffects_Any); |
| return true; |
| } |
| |
| Assert(descriptor->Attributes & PropertyLetConstGlobal); |
| return false; |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (instance->HasObjectArray() && propertyRecord->IsNumeric()) |
| { |
| return DictionaryTypeHandlerBase<T>::DeleteItem(instance, propertyRecord->GetNumericValue(), propertyOperationFlags); |
| } |
| |
| return true; |
| } |
| |
| #if ENABLE_FIXED_FIELDS |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::IsFixedProperty(const DynamicObject* instance, PropertyId propertyId) |
| { |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| DictionaryPropertyDescriptor<T> descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId); |
| if (propertyMap->TryGetValue(propertyRecord, &descriptor)) |
| { |
| return descriptor.GetIsFixed(); |
| } |
| else |
| { |
| AssertMsg(false, "Asking about a property this type handler doesn't know about?"); |
| return false; |
| } |
| } |
| #endif |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetItem(DynamicObject* instance, uint32 index, Var value, PropertyOperationFlags flags) |
| { |
| if (!(this->GetFlags() & IsExtensibleFlag) && !instance->HasObjectArray()) |
| { |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| JavascriptError::ThrowCantExtendIfStrictMode(flags, scriptContext); |
| return false; |
| } |
| return __super::SetItem(instance, index, value, flags); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetItemWithAttributes(DynamicObject* instance, uint32 index, Var value, PropertyAttributes attributes) |
| { |
| return instance->SetObjectArrayItemWithAttributes(index, value, attributes); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetItemAttributes(DynamicObject* instance, uint32 index, PropertyAttributes attributes) |
| { |
| return instance->SetObjectArrayItemAttributes(index, attributes); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetItemAccessors(DynamicObject* instance, uint32 index, Var getter, Var setter) |
| { |
| return instance->SetObjectArrayItemAccessors(index, getter, setter); |
| } |
| |
| template <typename T> |
| DescriptorFlags DictionaryTypeHandlerBase<T>::GetItemSetter(DynamicObject* instance, uint32 index, Var* setterValue, ScriptContext* requestContext) |
| { |
| if (instance->HasObjectArray()) |
| { |
| return instance->GetObjectArrayItemSetter(index, setterValue, requestContext); |
| } |
| return __super::GetItemSetter(instance, index, setterValue, requestContext); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::IsEnumerable(DynamicObject* instance, PropertyId propertyId) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (!descriptor->HasNonLetConstGlobal()) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "object must be a global object if letconstglobal is set"); |
| |
| return true; |
| } |
| return descriptor->Attributes & PropertyEnumerable; |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (propertyRecord->IsNumeric()) |
| { |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray != nullptr) |
| { |
| return objectArray->IsEnumerable(propertyId); |
| } |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::IsWritable(DynamicObject* instance, PropertyId propertyId) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (!descriptor->HasNonLetConstGlobal()) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "object must be a global object if letconstglobal is set"); |
| return !(descriptor->Attributes & PropertyConst); |
| } |
| return descriptor->Attributes & PropertyWritable; |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (propertyRecord->IsNumeric()) |
| { |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray != nullptr) |
| { |
| return objectArray->IsWritable(propertyId); |
| } |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::IsConfigurable(DynamicObject* instance, PropertyId propertyId) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (!descriptor->HasNonLetConstGlobal()) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "object must be a global object if letconstglobal is set"); |
| return true; |
| } |
| return descriptor->Attributes & PropertyConfigurable; |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (propertyRecord->IsNumeric()) |
| { |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray != nullptr) |
| { |
| return objectArray->IsConfigurable(propertyId); |
| } |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetEnumerable(DynamicObject* instance, PropertyId propertyId, BOOL value) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| return false; |
| } |
| |
| if (!descriptor->HasNonLetConstGlobal()) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "object must be a global object if letconstglobal is set"); |
| return false; |
| } |
| |
| if (value) |
| { |
| descriptor->Attributes |= PropertyEnumerable; |
| instance->SetHasNoEnumerableProperties(false); |
| } |
| else |
| { |
| descriptor->Attributes &= (~PropertyEnumerable); |
| } |
| return true; |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (propertyRecord->IsNumeric()) |
| { |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray != nullptr) |
| { |
| return objectArray->SetEnumerable(propertyId, value); |
| } |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetWritable(DynamicObject* instance, PropertyId propertyId, BOOL value) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| return false; |
| } |
| |
| if (!descriptor->HasNonLetConstGlobal()) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "object must be a global object if letconstglobal is set"); |
| return false; |
| } |
| |
| if (value) |
| { |
| descriptor->Attributes |= PropertyWritable; |
| } |
| else |
| { |
| descriptor->Attributes &= (~PropertyWritable); |
| |
| instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptor->Attributes, propertyId, scriptContext); |
| } |
| instance->ChangeType(); |
| return true; |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (instance->HasObjectArray() && propertyRecord->IsNumeric()) |
| { |
| return instance->SetObjectArrayItemWritable(propertyId, value); |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetConfigurable(DynamicObject* instance, PropertyId propertyId, BOOL value) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| return false; |
| } |
| |
| if (!descriptor->HasNonLetConstGlobal()) |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "object must be a global object if letconstglobal is set"); |
| return false; |
| } |
| |
| if (value) |
| { |
| descriptor->Attributes |= PropertyConfigurable; |
| } |
| else |
| { |
| descriptor->Attributes &= (~PropertyConfigurable); |
| } |
| return true; |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (propertyRecord->IsNumeric()) |
| { |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray != nullptr) |
| { |
| return objectArray->SetConfigurable(propertyId, value); |
| } |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::PreventExtensions(DynamicObject* instance) |
| { |
| this->ClearFlags(IsExtensibleFlag); |
| |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray) |
| { |
| objectArray->PreventExtensions(); |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::Seal(DynamicObject* instance) |
| { |
| this->ClearFlags(IsExtensibleFlag); |
| |
| // Set [[Configurable]] flag of each property to false |
| DictionaryPropertyDescriptor<T> *descriptor = nullptr; |
| for (T index = 0; index < propertyMap->Count(); index++) |
| { |
| descriptor = propertyMap->GetReferenceAt(index); |
| if (descriptor->HasNonLetConstGlobal()) |
| { |
| descriptor->Attributes &= (~PropertyConfigurable); |
| } |
| } |
| |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray) |
| { |
| objectArray->Seal(); |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::FreezeImpl(DynamicObject* instance, bool isConvertedType) |
| { |
| this->ClearFlags(IsExtensibleFlag); |
| |
| // Set [[Writable]] flag of each property to false except for setter\getters |
| // Set [[Configurable]] flag of each property to false |
| DictionaryPropertyDescriptor<T> *descriptor = nullptr; |
| for (T index = 0; index < propertyMap->Count(); index++) |
| { |
| descriptor = propertyMap->GetReferenceAt(index); |
| if (descriptor->HasNonLetConstGlobal()) |
| { |
| if (descriptor->template GetDataPropertyIndex<false>() != NoSlots) |
| { |
| // Only data descriptor has Writable property |
| descriptor->Attributes &= ~(PropertyWritable | PropertyConfigurable); |
| } |
| else |
| { |
| descriptor->Attributes &= ~(PropertyConfigurable); |
| } |
| } |
| #if DBG |
| else |
| { |
| AssertMsg(VarIs<RootObjectBase>(instance), "instance needs to be global object when letconst global is set"); |
| } |
| #endif |
| } |
| if (!isConvertedType) |
| { |
| // Change of [[Writable]] property requires cache invalidation, hence ChangeType |
| instance->ChangeType(); |
| } |
| |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray) |
| { |
| objectArray->Freeze(); |
| } |
| |
| this->ClearHasOnlyWritableDataProperties(); |
| if (GetFlags() & IsPrototypeFlag) |
| { |
| InvalidateStoreFieldCachesForAllProperties(instance->GetScriptContext()); |
| instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear(); |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::IsSealed(DynamicObject* instance) |
| { |
| if (this->GetFlags() & IsExtensibleFlag) |
| { |
| return false; |
| } |
| |
| DictionaryPropertyDescriptor<T> *descriptor = nullptr; |
| for (T index = 0; index < propertyMap->Count(); index++) |
| { |
| descriptor = propertyMap->GetReferenceAt(index); |
| if ((!(descriptor->Attributes & PropertyDeleted) && !(descriptor->Attributes & PropertyLetConstGlobal))) |
| { |
| if (descriptor->Attributes & PropertyConfigurable) |
| { |
| // [[Configurable]] must be false for all (existing) properties. |
| // IE9 compatibility: keep IE9 behavior (also check deleted properties) |
| return false; |
| } |
| } |
| } |
| |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray && !objectArray->IsSealed()) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::IsFrozen(DynamicObject* instance) |
| { |
| if (this->GetFlags() & IsExtensibleFlag) |
| { |
| return false; |
| } |
| |
| DictionaryPropertyDescriptor<T> *descriptor = nullptr; |
| for (T index = 0; index < propertyMap->Count(); index++) |
| { |
| descriptor = propertyMap->GetReferenceAt(index); |
| if ((!(descriptor->Attributes & PropertyDeleted) && !(descriptor->Attributes & PropertyLetConstGlobal))) |
| { |
| if (descriptor->Attributes & PropertyConfigurable) |
| { |
| return false; |
| } |
| |
| if (descriptor->template GetDataPropertyIndex<false>() != NoSlots && (descriptor->Attributes & PropertyWritable)) |
| { |
| // Only data descriptor has [[Writable]] property |
| return false; |
| } |
| } |
| } |
| |
| // Use IsObjectArrayFrozen() to skip "length" [[Writable]] check |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray && !objectArray->IsObjectArrayFrozen()) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| _Check_return_ _Success_(return) BOOL DictionaryTypeHandlerBase<T>::GetAccessors(DynamicObject* instance, PropertyId propertyId, _Outptr_result_maybenull_ Var* getter, _Outptr_result_maybenull_ Var* setter) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| AssertMsg(nullptr != getter && nullptr != setter, "Getter/Setter must be a valid pointer"); |
| |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| return false; |
| } |
| |
| if (descriptor->template GetDataPropertyIndex<false>() == NoSlots) |
| { |
| bool getset = false; |
| if (descriptor->GetGetterPropertyIndex() != NoSlots) |
| { |
| *getter = instance->GetSlot(descriptor->GetGetterPropertyIndex()); |
| *setter = nullptr; |
| getset = true; |
| } |
| if (descriptor->GetSetterPropertyIndex() != NoSlots) |
| { |
| *setter = instance->GetSlot(descriptor->GetSetterPropertyIndex()); |
| if(!getset) { |
| // if we didn't set the getter above, we need to set it here |
| *getter = nullptr; |
| } |
| getset = true; |
| } |
| return getset; |
| } |
| } |
| |
| // Check numeric propertyRecord only if objectArray available |
| if (propertyRecord->IsNumeric()) |
| { |
| ArrayObject * objectArray = instance->GetObjectArray(); |
| if (objectArray != nullptr) |
| { |
| return objectArray->GetAccessors(propertyId, getter, setter, scriptContext); |
| } |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetAccessors(DynamicObject* instance, PropertyId propertyId, Var getter, Var setter, PropertyOperationFlags flags) |
| { |
| Assert(instance); |
| JavascriptLibrary* library = instance->GetLibrary(); |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| |
| Assert(this->VerifyIsExtensible(scriptContext, false) || this->HasProperty(instance, propertyId) |
| || JavascriptFunction::IsBuiltinProperty(instance, propertyId)); |
| |
| // We could potentially need 2 new slots to hold getter/setter, try pre-reserve |
| if (this->GetSlotCapacity() - 2 < nextPropertyIndex) |
| { |
| if (this->GetSlotCapacity() > MaxPropertyIndexSize - 2) |
| { |
| return ConvertToBigDictionaryTypeHandler(instance) |
| ->SetAccessors(instance, propertyId, getter, setter, flags); |
| } |
| |
| this->EnsureSlotCapacity(instance, 2); |
| } |
| |
| DictionaryPropertyDescriptor<T>* descriptor; |
| if (this->GetFlags() & IsPrototypeFlag) |
| { |
| scriptContext->InvalidateProtoCaches(propertyId); |
| } |
| |
| bool isGetterSet = true; |
| bool isSetterSet = true; |
| if (!getter || getter == library->GetDefaultAccessorFunction()) |
| { |
| isGetterSet = false; |
| } |
| if (!setter || setter == library->GetDefaultAccessorFunction()) |
| { |
| isSetterSet = false; |
| } |
| |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| #if ENABLE_FIXED_FIELDS |
| Assert(descriptor->SanityCheckFixedBits()); |
| #endif |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| if (descriptor->Attributes & PropertyLetConstGlobal) |
| { |
| descriptor->Attributes = PropertyDynamicTypeDefaults | (descriptor->Attributes & (PropertyLetConstGlobal | PropertyNoRedecl)); |
| } |
| else |
| { |
| descriptor->Attributes = PropertyDynamicTypeDefaults; |
| } |
| } |
| |
| if (!descriptor->GetIsAccessor()) |
| { |
| // New getter/setter, make sure both values are not null and set to the slots |
| getter = CanonicalizeAccessor(getter, library); |
| setter = CanonicalizeAccessor(setter, library); |
| } |
| |
| // conversion from data-property to accessor property |
| if (descriptor->ConvertToGetterSetter(nextPropertyIndex)) |
| { |
| AssertOrFailFast(this->GetSlotCapacity() >= nextPropertyIndex); // pre-reserved 2 at entry |
| } |
| |
| // DictionaryTypeHandlers are not supposed to be shared. |
| Assert(!GetIsOrMayBecomeShared()); |
| #if ENABLE_FIXED_FIELDS |
| DynamicObject* localSingletonInstance = this->singletonInstance != nullptr ? this->singletonInstance->Get() : nullptr; |
| Assert(this->singletonInstance == nullptr || localSingletonInstance == instance); |
| |
| // Although we don't actually have CreateTypeForNewScObject on DictionaryTypeHandler, we could potentially |
| // transition to a DictionaryTypeHandler with some properties uninitialized. |
| if (!descriptor->GetIsInitialized()) |
| { |
| descriptor->SetIsInitialized(true); |
| if (localSingletonInstance == instance && !IsInternalPropertyId(propertyId)) |
| { |
| // We don't want fixed properties on external objects. See DynamicObject::ResetObject for more information. |
| Assert(!instance->IsExternal() || (flags & PropertyOperation_NonFixedValue) != 0); |
| descriptor->SetIsFixed((flags & PropertyOperation_NonFixedValue) == 0 && ShouldFixAccessorProperties()); |
| } |
| if (!isGetterSet || !isSetterSet) |
| { |
| descriptor->SetIsOnlyOneAccessorInitialized(true); |
| } |
| } |
| else if (descriptor->GetIsOnlyOneAccessorInitialized()) |
| { |
| // Only one of getter/setter was initialized, allow the isFixed to stay if we are defining the other one. |
| Var oldGetter = GetSlot(instance, descriptor->GetGetterPropertyIndex()); |
| Var oldSetter = GetSlot(instance, descriptor->GetSetterPropertyIndex()); |
| |
| if (((getter == oldGetter || !isGetterSet) && oldSetter == library->GetDefaultAccessorFunction()) || |
| ((setter == oldSetter || !isSetterSet) && oldGetter == library->GetDefaultAccessorFunction())) |
| { |
| descriptor->SetIsOnlyOneAccessorInitialized(false); |
| } |
| else |
| { |
| InvalidateFixedField(instance, propertyId, descriptor); |
| } |
| } |
| else |
| { |
| InvalidateFixedField(instance, propertyId, descriptor); |
| } |
| #endif |
| |
| // don't overwrite an existing accessor with null |
| if (getter != nullptr) |
| { |
| getter = CanonicalizeAccessor(getter, library); |
| SetSlotUnchecked(instance, descriptor->GetGetterPropertyIndex(), getter); |
| } |
| if (setter != nullptr) |
| { |
| setter = CanonicalizeAccessor(setter, library); |
| SetSlotUnchecked(instance, descriptor->GetSetterPropertyIndex(), setter); |
| } |
| instance->ChangeType(); |
| library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, PropertyNone, propertyRecord, scriptContext); |
| library->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(this, PropertyNone, propertyRecord, scriptContext); |
| |
| SetPropertyUpdateSideEffect(instance, propertyId, nullptr, SideEffects_Any); |
| |
| // Let's make sure we always have a getter and a setter |
| Assert(instance->GetSlot(descriptor->GetGetterPropertyIndex()) != nullptr && instance->GetSlot(descriptor->GetSetterPropertyIndex()) != nullptr); |
| |
| return true; |
| } |
| |
| // Always check numeric propertyRecord. This may create objectArray. |
| if (propertyRecord->IsNumeric()) |
| { |
| // Calls this or subclass implementation |
| return SetItemAccessors(instance, propertyRecord->GetNumericValue(), getter, setter); |
| } |
| |
| getter = CanonicalizeAccessor(getter, library); |
| setter = CanonicalizeAccessor(setter, library); |
| T getterIndex = ::Math::PostInc(nextPropertyIndex); |
| T setterIndex = ::Math::PostInc(nextPropertyIndex); |
| DictionaryPropertyDescriptor<T> newDescriptor(getterIndex, setterIndex); |
| AssertOrFailFast(this->GetSlotCapacity() >= nextPropertyIndex); // pre-reserved 2 at entry |
| |
| // DictionaryTypeHandlers are not supposed to be shared. |
| Assert(!GetIsOrMayBecomeShared()); |
| #if ENABLE_FIXED_FIELDS |
| DynamicObject* localSingletonInstance = this->singletonInstance != nullptr ? this->singletonInstance->Get() : nullptr; |
| Assert(this->singletonInstance == nullptr || localSingletonInstance == instance); |
| newDescriptor.SetIsInitialized(true); |
| if (localSingletonInstance == instance && !IsInternalPropertyId(propertyId)) |
| { |
| // We don't want fixed properties on external objects. See DynamicObject::ResetObject for more information. |
| Assert(!instance->IsExternal() || (flags & PropertyOperation_NonFixedValue) != 0); |
| |
| // Even if one (or both?) accessors are the default functions obtained through canonicalization, |
| // they are still legitimate functions, so it's ok to mark the whole property as fixed. |
| newDescriptor.SetIsFixed((flags & PropertyOperation_NonFixedValue) == 0 && ShouldFixAccessorProperties()); |
| if (!isGetterSet || !isSetterSet) |
| { |
| newDescriptor.SetIsOnlyOneAccessorInitialized(true); |
| } |
| } |
| #endif |
| |
| propertyMap->Add(propertyRecord, newDescriptor); |
| |
| SetSlotUnchecked(instance, newDescriptor.GetGetterPropertyIndex(), getter); |
| SetSlotUnchecked(instance, newDescriptor.GetSetterPropertyIndex(), setter); |
| |
| library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, PropertyNone, propertyRecord, scriptContext); |
| library->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(this, PropertyNone, propertyRecord, scriptContext); |
| |
| SetPropertyUpdateSideEffect(instance, propertyId, nullptr, SideEffects_Any); |
| |
| // Let's make sure we always have a getter and a setter |
| Assert(instance->GetSlot(newDescriptor.GetGetterPropertyIndex()) != nullptr && instance->GetSlot(newDescriptor.GetSetterPropertyIndex()) != nullptr); |
| |
| return true; |
| } |
| |
| // If this type is not extensible and the property being set does not already exist, |
| // if throwIfNotExtensible is |
| // * true, a type error will be thrown |
| // * false, FALSE will be returned (unless strict mode is enabled, in which case a type error will be thrown). |
| // Either way, the property will not be set. |
| // |
| // This is used to ensure that we throw in the following scenario, in accordance with |
| // section 10.2.1.2.2 of the Errata to the ES5 spec: |
| // Object.preventExtension(this); // make the global object non-extensible |
| // var x = 4; |
| // |
| // throwIfNotExtensible should always be false for non-numeric properties. |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetPropertyWithAttributes(DynamicObject* instance, PropertyId propertyId, Var value, |
| PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags, SideEffects possibleSideEffects) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| bool isForce = (flags & PropertyOperation_Force) != 0; |
| bool throwIfNotExtensible = (flags & PropertyOperation_ThrowIfNotExtensible) != 0; |
| |
| #ifdef DEBUG |
| uint32 debugIndex; |
| Assert(!(throwIfNotExtensible && scriptContext->IsNumericPropertyId(propertyId, &debugIndex))); |
| #endif |
| Assert(propertyId != Constants::NoProperty); |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| #if ENABLE_FIXED_FIELDS |
| Assert(descriptor->SanityCheckFixedBits()); |
| #endif |
| if (attributes & descriptor->Attributes & PropertyLetConstGlobal) |
| { |
| // Do not need to change the descriptor or its attributes if setting the initial value of a LetConstGlobal |
| } |
| else if (descriptor->Attributes & PropertyDeleted && !(attributes & PropertyLetConstGlobal)) |
| { |
| if (!isForce) |
| { |
| if (!this->VerifyIsExtensible(scriptContext, throwIfNotExtensible)) |
| { |
| return FALSE; |
| } |
| } |
| |
| scriptContext->InvalidateProtoCaches(propertyId); |
| if (descriptor->Attributes & PropertyLetConstGlobal) |
| { |
| descriptor->Attributes = attributes | (descriptor->Attributes & (PropertyLetConstGlobal | PropertyNoRedecl)); |
| } |
| else |
| { |
| descriptor->Attributes = attributes; |
| } |
| descriptor->ConvertToData(); |
| } |
| else if (descriptor->GetIsShadowed()) |
| { |
| descriptor->Attributes = attributes | (descriptor->Attributes & (PropertyLetConstGlobal | PropertyNoRedecl)); |
| } |
| else if ((descriptor->Attributes & PropertyLetConstGlobal) != (attributes & PropertyLetConstGlobal)) |
| { |
| // We could potentially need 1 new slot by AddShadowedData(), try pre-reserve |
| if (this->GetSlotCapacity() <= nextPropertyIndex) |
| { |
| if (this->GetSlotCapacity() >= MaxPropertyIndexSize) |
| { |
| return ConvertToBigDictionaryTypeHandler(instance)->SetPropertyWithAttributes( |
| instance, propertyId, value, attributes, info, flags, possibleSideEffects); |
| } |
| |
| this->EnsureSlotCapacity(instance); |
| } |
| |
| bool addingLetConstGlobal = (attributes & PropertyLetConstGlobal) != 0; |
| |
| if (addingLetConstGlobal) |
| { |
| descriptor->Attributes = descriptor->Attributes | (attributes & PropertyNoRedecl); |
| } |
| else |
| { |
| descriptor->Attributes = attributes | (descriptor->Attributes & PropertyNoRedecl); |
| } |
| |
| descriptor->AddShadowedData(nextPropertyIndex, addingLetConstGlobal); |
| |
| AssertOrFailFast(this->GetSlotCapacity() >= nextPropertyIndex); // pre-reserved above |
| |
| if (addingLetConstGlobal) |
| { |
| // If shadowing a global property with a let/const, need to invalidate |
| // JIT fast path cache since look up could now go to the let/const instead |
| // of the global property. |
| // |
| // Do not need to invalidate when adding a global property that gets shadowed |
| // by an existing let/const, since all caches will still be correct. |
| instance->ChangeType(); |
| } |
| } |
| else |
| { |
| if (descriptor->GetIsAccessor() && !(attributes & PropertyLetConstGlobal)) |
| { |
| #if DEBUG |
| Var ctor = JavascriptOperators::GetProperty(instance, PropertyIds::constructor, scriptContext); |
| #endif |
| AssertMsg(VarIs<RootObjectBase>(instance) || JavascriptFunction::IsBuiltinProperty(instance, propertyId) || |
| // ValidateAndApplyPropertyDescriptor says to preserve Configurable and Enumerable flags |
| |
| // For InitRootFld, which is equivalent to |
| // CreateGlobalFunctionBinding called from GlobalDeclarationInstantiation in the spec, |
| // we can assume that the attributes specified include enumerable and writable. Thus |
| // we don't need to preserve the original values of these two attributes and therefore |
| // do not need to change InitRootFld from being a SetPropertyWithAttributes API call to |
| // something else. All we need to do is convert the descriptor to a data descriptor. |
| // Built-in Function.prototype properties 'length', 'arguments', and 'caller' are special cases. |
| |
| ((JavascriptOperators::IsClassConstructor(instance) // Static method |
| || JavascriptOperators::IsClassConstructor(ctor) |
| || JavascriptOperators::IsClassMethod(ctor)) |
| && (attributes & PropertyClassMemberDefaults) == PropertyClassMemberDefaults), |
| // 14.3.9: InitClassMember sets property descriptor to {writable:true, enumerable:false, configurable:true} |
| |
| "Expect to only come down this path for InitClassMember or InitRootFld (on the global object) overwriting existing accessor property"); |
| if (!(descriptor->Attributes & PropertyConfigurable)) |
| { |
| if (scriptContext && scriptContext->GetThreadContext()->RecordImplicitException()) |
| { |
| JavascriptError::ThrowTypeError(scriptContext, JSERR_DefineProperty_NotConfigurable, scriptContext->GetThreadContext()->GetPropertyName(propertyId)->GetBuffer()); |
| } |
| return FALSE; |
| } |
| descriptor->ConvertToData(); |
| instance->ChangeType(); |
| } |
| |
| // Make sure to keep the PropertyLetConstGlobal bit as is while taking the new attributes. |
| descriptor->Attributes = attributes | (descriptor->Attributes & PropertyLetConstGlobal); |
| } |
| |
| if (attributes & PropertyLetConstGlobal) |
| { |
| SetPropertyWithDescriptor<true>(instance, propertyRecord, &descriptor, value, flags, info); |
| } |
| else |
| { |
| SetPropertyWithDescriptor<false>(instance, propertyRecord, &descriptor, value, flags, info); |
| } |
| if (descriptor != nullptr) //descriptor can dissappear, so this reference may not exist. |
| { |
| if (descriptor->Attributes & PropertyEnumerable) |
| { |
| instance->SetHasNoEnumerableProperties(false); |
| } |
| instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptor->Attributes, propertyId, scriptContext); |
| } |
| |
| SetPropertyUpdateSideEffect(instance, propertyId, value, possibleSideEffects); |
| return true; |
| } |
| |
| // Always check numeric propertyRecord. This may create objectArray. |
| if (propertyRecord->IsNumeric()) |
| { |
| // Calls this or subclass implementation |
| return SetItemWithAttributes(instance, propertyRecord->GetNumericValue(), value, attributes); |
| } |
| |
| return this->AddProperty(instance, propertyRecord, value, attributes, info, flags, throwIfNotExtensible, possibleSideEffects); |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::EnsureSlotCapacity(DynamicObject * instance, T increment /*= 1*/) |
| { |
| Assert(this->GetSlotCapacity() < MaxPropertyIndexSize); // Otherwise we can't grow this handler's capacity. We should've evolved to Bigger handler or OOM. |
| |
| // A Dictionary type is expected to have more properties |
| // grow exponentially rather linearly to avoid the realloc and moves, |
| // however use a small exponent to avoid waste |
| int newSlotCapacity = ::Math::Add(nextPropertyIndex, increment); |
| newSlotCapacity = ::Math::Add(newSlotCapacity, newSlotCapacity >> 2); |
| if (newSlotCapacity > MaxPropertyIndexSize) |
| { |
| newSlotCapacity = MaxPropertyIndexSize; |
| } |
| newSlotCapacity = RoundUpSlotCapacity(newSlotCapacity, GetInlineSlotCapacity()); |
| Assert(newSlotCapacity <= MaxPropertyIndexSize); |
| |
| instance->EnsureSlots(this->GetSlotCapacity(), newSlotCapacity, instance->GetScriptContext(), this); |
| this->SetSlotCapacity(newSlotCapacity); |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::SetAttributes(DynamicObject* instance, PropertyId propertyId, PropertyAttributes attributes) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(propertyId != Constants::NoProperty); |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| return false; |
| } |
| |
| descriptor->Attributes = (descriptor->Attributes & ~PropertyDynamicTypeDefaults) | (attributes & PropertyDynamicTypeDefaults); |
| |
| if (descriptor->Attributes & PropertyEnumerable) |
| { |
| instance->SetHasNoEnumerableProperties(false); |
| } |
| |
| instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptor->Attributes, propertyId, scriptContext); |
| |
| return true; |
| } |
| |
| // Check numeric propertyId only if objectArray available |
| if (instance->HasObjectArray() && propertyRecord->IsNumeric()) |
| { |
| return DictionaryTypeHandlerBase<T>::SetItemAttributes(instance, propertyRecord->GetNumericValue(), attributes); |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::GetAttributesWithPropertyIndex(DynamicObject * instance, PropertyId propertyId, BigPropertyIndex index, PropertyAttributes * attributes) |
| { |
| // this might get value that are deleted from the dictionary, but that should be nulled out |
| DictionaryPropertyDescriptor<T> * descriptor; |
| // We can't look it up using the slot index, as one propertyId might have multiple slots, do the propertyId map lookup |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (!propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| return false; |
| } |
| // This function is only used by LdRootFld, so the index will allow let const globals |
| Assert(descriptor->template GetDataPropertyIndex<true>() == index); |
| if (descriptor->Attributes & PropertyDeleted) |
| { |
| return false; |
| } |
| *attributes = descriptor->Attributes & PropertyDynamicTypeDefaults; |
| return true; |
| } |
| |
| template <typename T> |
| BigDictionaryTypeHandler* DictionaryTypeHandlerBase<T>::ConvertToBigDictionaryTypeHandler(DynamicObject* instance) |
| { |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| Recycler* recycler = scriptContext->GetRecycler(); |
| |
| BigDictionaryTypeHandler* newTypeHandler = NewBigDictionaryTypeHandler(recycler, GetSlotCapacity(), GetInlineSlotCapacity(), GetOffsetOfInlineSlots()); |
| // We expect the new type handler to start off marked as having only writable data properties. |
| Assert(newTypeHandler->GetHasOnlyWritableDataProperties()); |
| |
| #if ENABLE_FIXED_FIELDS |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| DynamicType* oldType = instance->GetDynamicType(); |
| RecyclerWeakReference<DynamicObject>* oldSingletonInstance = GetSingletonInstance(); |
| TraceFixedFieldsBeforeTypeHandlerChange(_u("DictionaryTypeHandler"), _u("BigDictionaryTypeHandler"), instance, this, oldType, oldSingletonInstance); |
| #endif |
| |
| CopySingletonInstance(instance, newTypeHandler); |
| #endif |
| |
| DictionaryPropertyDescriptor<T> descriptor; |
| DictionaryPropertyDescriptor<BigPropertyIndex> bigDescriptor; |
| |
| const PropertyRecord* propertyId; |
| for (int i = 0; i < propertyMap->Count(); i++) |
| { |
| descriptor = propertyMap->GetValueAt(i); |
| propertyId = propertyMap->GetKeyAt(i); |
| |
| bigDescriptor.CopyFrom(descriptor); |
| newTypeHandler->propertyMap->Add(propertyId, bigDescriptor); |
| } |
| |
| newTypeHandler->nextPropertyIndex = nextPropertyIndex; |
| |
| #if ENABLE_FIXED_FIELDS |
| ClearSingletonInstance(); |
| #endif |
| |
| AssertMsg((newTypeHandler->GetFlags() & IsPrototypeFlag) == 0, "Why did we create a brand new type handler with a prototype flag set?"); |
| newTypeHandler->SetFlags(IsPrototypeFlag, this->GetFlags()); |
| newTypeHandler->ChangeFlags(IsExtensibleFlag, this->GetFlags()); |
| // Any new type handler we expect to see here should have inline slot capacity locked. If this were to change, we would need |
| // to update our shrinking logic (see PathTypeHandlerBase::ShrinkSlotAndInlineSlotCapacity). |
| Assert(newTypeHandler->GetIsInlineSlotCapacityLocked()); |
| newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, this->GetPropertyTypes()); |
| newTypeHandler->SetInstanceTypeHandler(instance); |
| |
| #if ENABLE_FIXED_FIELDS |
| // Unlike for SimpleDictionaryTypeHandler or PathTypeHandler, the DictionaryTypeHandler copies usedAsFixed indiscriminately above. |
| // Therefore, we don't care if we changed the type or not, and don't need the assert below. |
| // We assumed that we don't need to transfer used as fixed bits unless we are a prototype, which is only valid if we also changed the type. |
| // Assert(instance->GetType() != oldType); |
| Assert(!newTypeHandler->HasSingletonInstance() || !instance->HasSharedType()); |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| TraceFixedFieldsAfterTypeHandlerChange(instance, this, newTypeHandler, oldType, oldSingletonInstance); |
| #endif |
| #endif |
| |
| return newTypeHandler; |
| } |
| |
| template <typename T> |
| BigDictionaryTypeHandler* DictionaryTypeHandlerBase<T>::NewBigDictionaryTypeHandler(Recycler* recycler, int slotCapacity, uint16 inlineSlotCapacity, uint16 offsetOfInlineSlots) |
| { |
| return RecyclerNew(recycler, BigDictionaryTypeHandler, recycler, slotCapacity, inlineSlotCapacity, offsetOfInlineSlots); |
| } |
| |
| template <> |
| BigDictionaryTypeHandler* DictionaryTypeHandlerBase<BigPropertyIndex>::ConvertToBigDictionaryTypeHandler(DynamicObject* instance) |
| { |
| Throw::OutOfMemory(); |
| } |
| |
| template<> |
| BOOL DictionaryTypeHandlerBase<PropertyIndex>::IsBigDictionaryTypeHandler() |
| { |
| return FALSE; |
| } |
| |
| template<> |
| BOOL DictionaryTypeHandlerBase<BigPropertyIndex>::IsBigDictionaryTypeHandler() |
| { |
| return TRUE; |
| } |
| |
| template <typename T> |
| BOOL DictionaryTypeHandlerBase<T>::AddProperty(DynamicObject* instance, const PropertyRecord* propertyRecord, Var value, |
| PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags, bool throwIfNotExtensible, SideEffects possibleSideEffects) |
| { |
| AnalysisAssert(instance); |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| bool isForce = (flags & PropertyOperation_Force) != 0; |
| PropertyId propertyId = propertyRecord->GetPropertyId(); |
| #if DBG |
| DictionaryPropertyDescriptor<T>* descriptor; |
| Assert(!propertyMap->TryGetReference(propertyRecord, &descriptor)); |
| Assert(!propertyRecord->IsNumeric()); |
| #endif |
| |
| if (!isForce) |
| { |
| if (!this->VerifyIsExtensible(scriptContext, throwIfNotExtensible)) |
| { |
| return FALSE; |
| } |
| } |
| |
| if (this->GetSlotCapacity() <= nextPropertyIndex) |
| { |
| if (this->GetSlotCapacity() >= MaxPropertyIndexSize || |
| (this->GetSlotCapacity() >= CONFIG_FLAG(BigDictionaryTypeHandlerThreshold) && !this->IsBigDictionaryTypeHandler())) |
| { |
| BigDictionaryTypeHandler* newTypeHandler = ConvertToBigDictionaryTypeHandler(instance); |
| |
| return newTypeHandler->AddProperty(instance, propertyRecord, value, attributes, info, flags, false, possibleSideEffects); |
| } |
| this->EnsureSlotCapacity(instance); |
| } |
| |
| T index = ::Math::PostInc(nextPropertyIndex); |
| DictionaryPropertyDescriptor<T> newDescriptor(index, attributes); |
| |
| // DictionaryTypeHandlers are not supposed to be shared. |
| Assert(!GetIsOrMayBecomeShared()); |
| #if ENABLE_FIXED_FIELDS |
| DynamicObject* localSingletonInstance = this->singletonInstance != nullptr ? this->singletonInstance->Get() : nullptr; |
| Assert(this->singletonInstance == nullptr || localSingletonInstance == instance); |
| |
| if ((flags & PropertyOperation_PreInit) == 0) |
| { |
| newDescriptor.SetIsInitialized(true); |
| if (localSingletonInstance == instance && !IsInternalPropertyId(propertyId) && |
| (flags & (PropertyOperation_NonFixedValue | PropertyOperation_SpecialValue)) == 0) |
| { |
| Assert(value != nullptr); |
| // We don't want fixed properties on external objects. See DynamicObject::ResetObject for more information. |
| Assert(!instance->IsExternal()); |
| newDescriptor.SetIsFixed(VarIs<JavascriptFunction>(value) ? ShouldFixMethodProperties() : (ShouldFixDataProperties() & CheckHeuristicsForFixedDataProps(instance, propertyRecord, value))); |
| } |
| } |
| #endif |
| |
| propertyMap->Add(propertyRecord, newDescriptor); |
| |
| if (attributes & PropertyEnumerable) |
| { |
| instance->SetHasNoEnumerableProperties(false); |
| } |
| JavascriptLibrary* library = scriptContext->GetLibrary(); |
| |
| library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, attributes, propertyId, scriptContext); |
| if (NoSpecialPropertyCache::IsSpecialProperty(propertyId) && !this->GetHasSpecialProperties()) |
| { |
| if (!NoSpecialPropertyCache::IsDefaultSpecialProperty(instance, library, propertyId)) |
| { |
| this->SetHasSpecialProperties(); |
| if (GetFlags() & IsPrototypeFlag) |
| { |
| library->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear(); |
| } |
| } |
| else |
| { |
| PropertyValueInfo::SetNoCache(info, instance); |
| } |
| } |
| |
| SetSlotUnchecked(instance, index, value); |
| |
| #if ENABLE_FIXED_FIELDS |
| // If we just added a fixed method, don't populate the inline cache so that we always take the |
| // slow path when overwriting this property and correctly invalidate any JIT-ed code that hard-coded |
| // this method. |
| if (newDescriptor.GetIsFixed()) |
| { |
| PropertyValueInfo::SetNoCache(info, instance); |
| } |
| else |
| #endif |
| { |
| SetPropertyValueInfoNonFixed(info, instance, index, attributes); |
| } |
| |
| // Always invalidate prototype caches when we add a property. Previously, we only did this if the current |
| // type is used as a prototype, or if the new property is also found on the prototype chain (because |
| // adding a new field doesn't create a new dictionary type). However, if the new property is already in |
| // the cache as a missing property, we have to invalidate the prototype caches. |
| scriptContext->InvalidateProtoCaches(propertyRecord->GetPropertyId()); |
| |
| SetPropertyUpdateSideEffect(instance, propertyRecord->GetPropertyId(), value, possibleSideEffects); |
| return true; |
| } |
| |
| // |
| // Converts (upgrades) this dictionary type handler to an ES5 array type handler. The new handler takes |
| // over all members of this handler including the property map. |
| // |
| template <typename T> |
| ES5ArrayTypeHandlerBase<T>* DictionaryTypeHandlerBase<T>::ConvertToES5ArrayType(DynamicObject *instance) |
| { |
| Recycler* recycler = instance->GetRecycler(); |
| |
| ES5ArrayTypeHandlerBase<T>* newTypeHandler = RecyclerNew(recycler, ES5ArrayTypeHandlerBase<T>, recycler, this); |
| // Don't need to transfer the singleton instance, because the new handler takes over this handler. |
| AssertMsg((newTypeHandler->GetFlags() & IsPrototypeFlag) == 0, "Why did we create a brand new type handler with a prototype flag set?"); |
| newTypeHandler->SetFlags(IsPrototypeFlag, this->GetFlags()); |
| // Property types were copied in the constructor. |
| //newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesInlineSlotCapacityLocked, this->GetPropertyTypes()); |
| newTypeHandler->SetInstanceTypeHandler(instance); |
| return newTypeHandler; |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::SetAllPropertiesToUndefined(DynamicObject* instance, bool invalidateFixedFields) |
| { |
| // The Var for window is reused across navigation. we shouldn't preserve the IsExtensibleFlag when we don't keep |
| // the expandos. Reset the IsExtensibleFlag in cleanup scenario should be good enough |
| // to cover all the preventExtension/Freeze/Seal scenarios. |
| // Note that we don't change the flag for keepProperties scenario: the flags should be preserved and that's consistent |
| // with other browsers. |
| ChangeFlags(IsExtensibleFlag | IsSealedOnceFlag | IsFrozenOnceFlag, IsExtensibleFlag); |
| |
| // Note: This method is currently only called from ResetObject, which in turn only applies to external objects. |
| // Before using for other purposes, make sure the assumptions made here make sense in the new context. In particular, |
| // the invalidateFixedFields == false is only correct if a) the object is known not to have any, or b) the type of the |
| // object has changed and/or property guards have already been invalidated through some other means. |
| int propertyCount = this->propertyMap->Count(); |
| |
| #if ENABLE_FIXED_FIELDS |
| if (invalidateFixedFields) |
| { |
| for (int propertyIndex = 0; propertyIndex < propertyCount; propertyIndex++) |
| { |
| const PropertyRecord* propertyRecord = this->propertyMap->GetKeyAt(propertyIndex); |
| DictionaryPropertyDescriptor<T>* descriptor = this->propertyMap->GetReferenceAt(propertyIndex); |
| InvalidateFixedField(instance, propertyRecord->GetPropertyId(), descriptor); |
| } |
| } |
| #endif |
| |
| Js::RecyclableObject* undefined = instance->GetLibrary()->GetUndefined(); |
| Js::JavascriptFunction* defaultAccessor = instance->GetLibrary()->GetDefaultAccessorFunction(); |
| for (int propertyIndex = 0; propertyIndex < propertyCount; propertyIndex++) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor = this->propertyMap->GetReferenceAt(propertyIndex); |
| |
| T dataPropertyIndex = descriptor->template GetDataPropertyIndex<false>(); |
| if (dataPropertyIndex != NoSlots) |
| { |
| SetSlotUnchecked(instance, dataPropertyIndex, undefined); |
| } |
| else |
| { |
| SetSlotUnchecked(instance, descriptor->GetGetterPropertyIndex(), defaultAccessor); |
| SetSlotUnchecked(instance, descriptor->GetSetterPropertyIndex(), defaultAccessor); |
| } |
| } |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::MarshalAllPropertiesToScriptContext(DynamicObject* instance, ScriptContext* targetScriptContext, bool invalidateFixedFields) |
| { |
| #if ENABLE_FIXED_FIELDS |
| // Note: This method is currently only called from ResetObject, which in turn only applies to external objects. |
| // Before using for other purposes, make sure the assumptions made here make sense in the new context. In particular, |
| // the invalidateFixedFields == false is only correct if a) the object is known not to have any, or b) the type of the |
| // object has changed and/or property guards have already been invalidated through some other means. |
| if (invalidateFixedFields) |
| { |
| int propertyCount = this->propertyMap->Count(); |
| for (int propertyIndex = 0; propertyIndex < propertyCount; propertyIndex++) |
| { |
| const PropertyRecord* propertyRecord = this->propertyMap->GetKeyAt(propertyIndex); |
| DictionaryPropertyDescriptor<T>* descriptor = this->propertyMap->GetReferenceAt(propertyIndex); |
| InvalidateFixedField(instance, propertyRecord->GetPropertyId(), descriptor); |
| } |
| } |
| #endif |
| |
| int slotCount = this->nextPropertyIndex; |
| for (int slotIndex = 0; slotIndex < slotCount; slotIndex++) |
| { |
| SetSlotUnchecked(instance, slotIndex, CrossSite::MarshalVar(targetScriptContext, GetSlot(instance, slotIndex))); |
| } |
| } |
| |
| template <typename T> |
| DynamicTypeHandler* DictionaryTypeHandlerBase<T>::ConvertToTypeWithItemAttributes(DynamicObject* instance) |
| { |
| return JavascriptArray::IsNonES5Array(instance) ? ConvertToES5ArrayType(instance) : this; |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::SetIsPrototype(DynamicObject* instance) |
| { |
| // Don't return if IsPrototypeFlag is set, because we may still need to do a type transition and |
| // set fixed bits. If this handler were to be shared, this instance may not be a prototype yet. |
| // We might need to convert to a non-shared type handler and/or change type. |
| if (!ChangeTypeOnProto() && !(GetIsOrMayBecomeShared() && IsolatePrototypes())) |
| { |
| SetFlags(IsPrototypeFlag); |
| return; |
| } |
| |
| // DictionaryTypeHandlers are never shared. If we allow sharing, we will have to handle this case |
| // just like SimpleDictionaryTypeHandler. |
| Assert(!GetIsOrMayBecomeShared()); |
| |
| #if ENABLE_FIXED_FIELDS |
| Assert(!GetIsShared() || this->singletonInstance == nullptr); |
| Assert(this->singletonInstance == nullptr || this->singletonInstance->Get() == instance); |
| |
| // Review (jedmiad): Why isn't this getting inlined? |
| const auto setFixedFlags = [instance](const PropertyRecord* propertyRecord, DictionaryPropertyDescriptor<T>* const descriptor, bool hasNewType) |
| { |
| if (IsInternalPropertyId(propertyRecord->GetPropertyId())) |
| { |
| return; |
| } |
| if (!(descriptor->Attributes & PropertyDeleted)) |
| { |
| // See PathTypeHandlerBase::ConvertToSimpleDictionaryType for rules governing fixed field bits during type |
| // handler transitions. In addition, we know that the current instance is not yet a prototype. |
| |
| Assert(descriptor->SanityCheckFixedBits()); |
| if (descriptor->GetIsInitialized()) |
| { |
| // Since DictionaryTypeHandlers are never shared, we can set fixed fields and clear used as fixed as long |
| // as we have changed the type. Otherwise populated load field caches would still be valid and would need |
| // to be explicitly invalidated if the property value changes. |
| if (hasNewType) |
| { |
| T dataSlot = descriptor->template GetDataPropertyIndex<false>(); |
| if (dataSlot != NoSlots) |
| { |
| Var value = instance->GetSlot(dataSlot); |
| // Because DictionaryTypeHandlers are never shared we should always have a property value if the handler |
| // says it's initialized. |
| Assert(value != nullptr); |
| descriptor->SetIsFixed(VarIs<JavascriptFunction>(value) ? ShouldFixMethodProperties() : (ShouldFixDataProperties() && CheckHeuristicsForFixedDataProps(instance, propertyRecord, value))); |
| } |
| else if (descriptor->GetIsAccessor()) |
| { |
| Assert(descriptor->GetGetterPropertyIndex() != NoSlots && descriptor->GetSetterPropertyIndex() != NoSlots); |
| descriptor->SetIsFixed(ShouldFixAccessorProperties()); |
| } |
| |
| // Since we have a new type we can clear all used as fixed bits. That's because any instance field loads |
| // will have been invalidated by the type transition, and there are no proto fields loads from this object |
| // because it is just now becoming a proto. |
| descriptor->SetUsedAsFixed(false); |
| } |
| } |
| else |
| { |
| Assert(!descriptor->GetIsFixed() && !descriptor->GetUsedAsFixed()); |
| } |
| Assert(descriptor->SanityCheckFixedBits()); |
| } |
| }; |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| DynamicType* oldType = instance->GetDynamicType(); |
| RecyclerWeakReference<DynamicObject>* oldSingletonInstance = GetSingletonInstance(); |
| TraceFixedFieldsBeforeSetIsProto(instance, this, oldType, oldSingletonInstance); |
| #endif |
| #endif |
| |
| bool hasNewType = false; |
| if (ChangeTypeOnProto()) |
| { |
| // Forcing a type transition allows us to fix all fields (even those that were previously marked as non-fixed). |
| instance->ChangeType(); |
| Assert(!instance->HasSharedType()); |
| hasNewType = true; |
| } |
| |
| // Currently there is no way to become the prototype if you are a stack instance |
| Assert(!ThreadContext::IsOnStack(instance)); |
| #if ENABLE_FIXED_FIELDS |
| if (AreSingletonInstancesNeeded() && this->singletonInstance == nullptr) |
| { |
| this->singletonInstance = instance->CreateWeakReferenceToSelf(); |
| } |
| |
| // We don't want fixed properties on external objects. See DynamicObject::ResetObject for more information. |
| if (!instance->IsExternal()) |
| { |
| // The propertyMap dictionary is guaranteed to have contiguous entries because we never remove entries from it. |
| for (int i = 0; i < propertyMap->Count(); i++) |
| { |
| const PropertyRecord* propertyRecord = propertyMap->GetKeyAt(i); |
| DictionaryPropertyDescriptor<T>* const descriptor = propertyMap->GetReferenceAt(i); |
| setFixedFlags(propertyRecord, descriptor, hasNewType); |
| } |
| } |
| #endif |
| |
| SetFlags(IsPrototypeFlag); |
| |
| #if ENABLE_FIXED_FIELDS |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| TraceFixedFieldsAfterSetIsProto(instance, this, this, oldType, oldSingletonInstance); |
| #endif |
| #endif |
| } |
| |
| #if DBG |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::CanStorePropertyValueDirectly(const DynamicObject* instance, PropertyId propertyId, bool allowLetConst) |
| { |
| ScriptContext* scriptContext = instance->GetScriptContext(); |
| DictionaryPropertyDescriptor<T> descriptor; |
| |
| // We pass Constants::NoProperty for ActivationObjects for functions with same named formals. |
| if (propertyId == Constants::NoProperty) |
| { |
| return true; |
| } |
| |
| PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId); |
| if (propertyMap->TryGetValue(propertyRecord, &descriptor)) |
| { |
| if (allowLetConst && (descriptor.Attributes & PropertyLetConstGlobal)) |
| { |
| return true; |
| } |
| else |
| { |
| return !descriptor.IsOrMayBecomeFixed(); |
| } |
| } |
| else |
| { |
| AssertMsg(false, "Asking about a property this type handler doesn't know about?"); |
| return false; |
| } |
| } |
| |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::IsLetConstGlobal(DynamicObject* instance, PropertyId propertyId) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId); |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor) && (descriptor->Attributes & PropertyLetConstGlobal)) |
| { |
| return true; |
| } |
| return false; |
| } |
| #endif |
| |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::NextLetConstGlobal(int& index, RootObjectBase* instance, const PropertyRecord** propertyRecord, Var* value, bool* isConst) |
| { |
| for (; index < propertyMap->Count(); index++) |
| { |
| DictionaryPropertyDescriptor<T> descriptor = propertyMap->GetValueAt(index); |
| |
| if (descriptor.Attributes & PropertyLetConstGlobal) |
| { |
| *propertyRecord = propertyMap->GetKeyAt(index); |
| *value = instance->GetSlot(descriptor.template GetDataPropertyIndex<true>()); |
| *isConst = (descriptor.Attributes & PropertyConst) != 0; |
| |
| index += 1; |
| |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| #if ENABLE_FIXED_FIELDS |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::HasSingletonInstance() const |
| { |
| return this->singletonInstance != nullptr; |
| } |
| |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::TryUseFixedProperty(PropertyRecord const * propertyRecord, Var * pProperty, FixedPropertyKind propertyType, ScriptContext * requestContext) |
| { |
| bool result = TryGetFixedProperty<false, true>(propertyRecord, pProperty, propertyType, requestContext); |
| TraceUseFixedProperty(propertyRecord, pProperty, result, _u("DictionaryTypeHandler"), requestContext); |
| return result; |
| } |
| |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::TryUseFixedAccessor(PropertyRecord const * propertyRecord, Var * pAccessor, FixedPropertyKind propertyType, bool getter, ScriptContext * requestContext) |
| { |
| bool result = TryGetFixedAccessor<false, true>(propertyRecord, pAccessor, propertyType, getter, requestContext); |
| TraceUseFixedProperty(propertyRecord, pAccessor, result, _u("DictionaryTypeHandler"), requestContext); |
| return result; |
| } |
| |
| #if DBG |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::CheckFixedProperty(PropertyRecord const * propertyRecord, Var * pProperty, ScriptContext * requestContext) |
| { |
| return TryGetFixedProperty<true, false>(propertyRecord, pProperty, (Js::FixedPropertyKind)(Js::FixedPropertyKind::FixedMethodProperty | Js::FixedPropertyKind::FixedDataProperty), requestContext); |
| } |
| |
| template <typename T> |
| bool DictionaryTypeHandlerBase<T>::HasAnyFixedProperties() const |
| { |
| for (int i = 0; i < propertyMap->Count(); i++) |
| { |
| DictionaryPropertyDescriptor<T> descriptor = propertyMap->GetValueAt(i); |
| if (descriptor.GetIsFixed()) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| #endif |
| |
| template <typename T> |
| template <bool allowNonExistent, bool markAsUsed> |
| bool DictionaryTypeHandlerBase<T>::TryGetFixedProperty(PropertyRecord const * propertyRecord, Var * pProperty, FixedPropertyKind propertyType, ScriptContext * requestContext) |
| { |
| // Note: This function is not thread-safe and cannot be called from the JIT thread. That's why we collect and |
| // cache any fixed function instances during work item creation on the main thread. |
| DynamicObject* localSingletonInstance = this->singletonInstance != nullptr ? this->singletonInstance->Get() : nullptr; |
| if (localSingletonInstance != nullptr && localSingletonInstance->GetScriptContext() == requestContext) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (descriptor->Attributes & PropertyDeleted || !descriptor->GetIsFixed()) |
| { |
| return false; |
| } |
| T dataSlot = descriptor->template GetDataPropertyIndex<false>(); |
| if (dataSlot != NoSlots) |
| { |
| Assert(!IsInternalPropertyId(propertyRecord->GetPropertyId())); |
| Var value = localSingletonInstance->GetSlot(dataSlot); |
| if (value && ((IsFixedMethodProperty(propertyType) && VarIs<JavascriptFunction>(value)) || IsFixedDataProperty(propertyType))) |
| { |
| *pProperty = value; |
| if (markAsUsed) |
| { |
| descriptor->SetUsedAsFixed(true); |
| } |
| return true; |
| } |
| } |
| } |
| else |
| { |
| AssertMsg(allowNonExistent, "Trying to get a fixed function instance for a non-existent property?"); |
| } |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| template <bool allowNonExistent, bool markAsUsed> |
| bool DictionaryTypeHandlerBase<T>::TryGetFixedAccessor(PropertyRecord const * propertyRecord, Var * pAccessor, FixedPropertyKind propertyType, bool getter, ScriptContext * requestContext) |
| { |
| // Note: This function is not thread-safe and cannot be called from the JIT thread. That's why we collect and |
| // cache any fixed function instances during work item creation on the main thread. |
| DynamicObject* localSingletonInstance = this->singletonInstance != nullptr ? this->singletonInstance->Get() : nullptr; |
| if (localSingletonInstance != nullptr && localSingletonInstance->GetScriptContext() == requestContext) |
| { |
| DictionaryPropertyDescriptor<T>* descriptor; |
| if (propertyMap->TryGetReference(propertyRecord, &descriptor)) |
| { |
| if (descriptor->Attributes & PropertyDeleted || !descriptor->GetIsAccessor() || !descriptor->GetIsFixed()) |
| { |
| return false; |
| } |
| |
| T accessorSlot = getter ? descriptor->GetGetterPropertyIndex() : descriptor->GetSetterPropertyIndex(); |
| if (accessorSlot != NoSlots) |
| { |
| Assert(!IsInternalPropertyId(propertyRecord->GetPropertyId())); |
| Var value = localSingletonInstance->GetSlot(accessorSlot); |
| if (value && IsFixedAccessorProperty(propertyType) && VarIs<JavascriptFunction>(value)) |
| { |
| *pAccessor = value; |
| if (markAsUsed) |
| { |
| descriptor->SetUsedAsFixed(true); |
| } |
| return true; |
| } |
| } |
| } |
| else |
| { |
| AssertMsg(allowNonExistent, "Trying to get a fixed function instance for a non-existent property?"); |
| } |
| } |
| |
| return false; |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::CopySingletonInstance(DynamicObject* instance, DynamicTypeHandler* typeHandler) |
| { |
| if (this->singletonInstance != nullptr) |
| { |
| Assert(AreSingletonInstancesNeeded()); |
| Assert(this->singletonInstance->Get() == instance); |
| typeHandler->SetSingletonInstanceUnchecked(this->singletonInstance); |
| } |
| } |
| |
| template <typename T> |
| template <typename TPropertyKey> |
| void DictionaryTypeHandlerBase<T>::InvalidateFixedField(DynamicObject* instance, TPropertyKey propertyKey, DictionaryPropertyDescriptor<T>* descriptor) |
| { |
| // DictionaryTypeHandlers are never shared, but if they were we would need to invalidate even if |
| // there wasn't a singleton instance. See SimpleDictionaryTypeHandler::InvalidateFixedFields. |
| Assert(!GetIsOrMayBecomeShared()); |
| if (this->singletonInstance != nullptr) |
| { |
| Assert(this->singletonInstance->Get() == instance); |
| |
| // Even if we wrote a new value into this property (overwriting a previously fixed one), we don't |
| // consider the new one fixed. This also means that it's ok to populate the inline caches for |
| // this property from now on. |
| descriptor->SetIsFixed(false); |
| |
| if (descriptor->GetUsedAsFixed()) |
| { |
| // Invalidate any JIT-ed code that hard coded this method. No need to invalidate |
| // any store field inline caches, because they have never been populated. |
| PropertyId propertyId = TMapKey_GetPropertyId(instance->GetScriptContext(), propertyKey); |
| instance->GetScriptContext()->GetThreadContext()->InvalidatePropertyGuards(propertyId); |
| descriptor->SetUsedAsFixed(false); |
| } |
| } |
| } |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::DumpFixedFields() const { |
| for (int i = 0; i < propertyMap->Count(); i++) |
| { |
| DictionaryPropertyDescriptor<T> descriptor = propertyMap->GetValueAt(i); |
| |
| const PropertyRecord* propertyRecord = propertyMap->GetKeyAt(i); |
| Output::Print(_u(" %s %d%d%d,"), propertyRecord->GetBuffer(), |
| descriptor.GetIsInitialized() ? 1 : 0, |
| descriptor.GetIsFixed() ? 1 : 0, |
| descriptor.GetUsedAsFixed() ? 1 : 0); |
| } |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::TraceFixedFieldsBeforeTypeHandlerChange( |
| const char16* oldTypeHandlerName, const char16* newTypeHandlerName, |
| DynamicObject* instance, DynamicTypeHandler* oldTypeHandler, |
| DynamicType* oldType, RecyclerWeakReference<DynamicObject>* oldSingletonInstanceBefore) |
| { |
| if (PHASE_VERBOSE_TRACE1(FixMethodPropsPhase)) |
| { |
| Output::Print(_u("FixedFields: converting 0x%p from %s to %s:\n"), instance, oldTypeHandlerName, newTypeHandlerName); |
| Output::Print(_u(" before: type = 0x%p, type handler = 0x%p, old singleton = 0x%p(0x%p)\n"), |
| oldType, oldTypeHandler, oldSingletonInstanceBefore, oldSingletonInstanceBefore != nullptr ? oldSingletonInstanceBefore->Get() : nullptr); |
| Output::Print(_u(" fixed fields:")); |
| oldTypeHandler->DumpFixedFields(); |
| Output::Print(_u("\n")); |
| } |
| if (PHASE_VERBOSE_TESTTRACE1(FixMethodPropsPhase)) |
| { |
| Output::Print(_u("FixedFields: converting instance from %s to %s:\n"), oldTypeHandlerName, newTypeHandlerName); |
| Output::Print(_u(" old singleton before %s null \n"), oldSingletonInstanceBefore == nullptr ? _u("==") : _u("!=")); |
| Output::Print(_u(" fixed fields before:")); |
| oldTypeHandler->DumpFixedFields(); |
| Output::Print(_u("\n")); |
| } |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::TraceFixedFieldsAfterTypeHandlerChange( |
| DynamicObject* instance, DynamicTypeHandler* oldTypeHandler, DynamicTypeHandler* newTypeHandler, |
| DynamicType* oldType, RecyclerWeakReference<DynamicObject>* oldSingletonInstanceBefore) |
| { |
| if (PHASE_VERBOSE_TRACE1(FixMethodPropsPhase)) |
| { |
| RecyclerWeakReference<DynamicObject>* oldSingletonInstanceAfter = oldTypeHandler->GetSingletonInstance(); |
| RecyclerWeakReference<DynamicObject>* newSingletonInstanceAfter = newTypeHandler->GetSingletonInstance(); |
| Output::Print(_u(" after: type = 0x%p, type handler = 0x%p, old singleton = 0x%p(0x%p), new singleton = 0x%p(0x%p)\n"), |
| instance->GetType(), newTypeHandler, |
| oldSingletonInstanceAfter, oldSingletonInstanceAfter != nullptr ? oldSingletonInstanceAfter->Get() : nullptr, |
| newSingletonInstanceAfter, newSingletonInstanceAfter != nullptr ? newSingletonInstanceAfter->Get() : nullptr); |
| Output::Print(_u(" fixed fields after:")); |
| newTypeHandler->DumpFixedFields(); |
| Output::Print(_u("\n")); |
| Output::Flush(); |
| } |
| if (PHASE_VERBOSE_TESTTRACE1(FixMethodPropsPhase)) |
| { |
| Output::Print(_u(" type %s, typeHandler %s, old singleton after %s null (%s), new singleton after %s null\n"), |
| oldTypeHandler != newTypeHandler ? _u("changed") : _u("unchanged"), |
| oldType != instance->GetType() ? _u("changed") : _u("unchanged"), |
| oldSingletonInstanceBefore == nullptr ? _u("==") : _u("!="), |
| oldSingletonInstanceBefore != oldTypeHandler->GetSingletonInstance() ? _u("changed") : _u("unchanged"), |
| newTypeHandler->GetSingletonInstance() == nullptr ? _u("==") : _u("!=")); |
| Output::Print(_u(" fixed fields after:")); |
| newTypeHandler->DumpFixedFields(); |
| Output::Print(_u("\n")); |
| Output::Flush(); |
| } |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::TraceFixedFieldsBeforeSetIsProto( |
| DynamicObject* instance, DynamicTypeHandler* oldTypeHandler, DynamicType* oldType, RecyclerWeakReference<DynamicObject>* oldSingletonInstanceBefore) |
| { |
| if (PHASE_VERBOSE_TRACE1(FixMethodPropsPhase)) |
| { |
| Output::Print(_u("FixedFields: PathTypeHandler::SetIsPrototype(0x%p):\n"), instance); |
| Output::Print(_u(" before: type = 0x%p, old singleton = 0x%p(0x%p)\n"), |
| oldType, oldSingletonInstanceBefore, oldSingletonInstanceBefore != nullptr ? oldSingletonInstanceBefore->Get() : nullptr); |
| Output::Print(_u(" fixed fields:")); |
| oldTypeHandler->DumpFixedFields(); |
| Output::Print(_u("\n")); |
| } |
| if (PHASE_VERBOSE_TESTTRACE1(FixMethodPropsPhase)) |
| { |
| Output::Print(_u("FixedFields: PathTypeHandler::SetIsPrototype():\n")); |
| Output::Print(_u(" old singleton before %s null \n"), oldSingletonInstanceBefore == nullptr ? _u("==") : _u("!=")); |
| Output::Print(_u(" fixed fields before:")); |
| oldTypeHandler->DumpFixedFields(); |
| Output::Print(_u("\n")); |
| } |
| } |
| |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::TraceFixedFieldsAfterSetIsProto( |
| DynamicObject* instance, DynamicTypeHandler* oldTypeHandler, DynamicTypeHandler* newTypeHandler, |
| DynamicType* oldType, RecyclerWeakReference<DynamicObject>* oldSingletonInstanceBefore) |
| { |
| if (PHASE_VERBOSE_TRACE1(FixMethodPropsPhase)) |
| { |
| RecyclerWeakReference<DynamicObject>* oldSingletonInstanceAfter = oldTypeHandler->GetSingletonInstance(); |
| RecyclerWeakReference<DynamicObject>* newSingletonInstanceAfter = newTypeHandler->GetSingletonInstance(); |
| Output::Print(_u(" after: type = 0x%p, type handler = 0x%p, old singleton = 0x%p(0x%p), new singleton = 0x%p(0x%p)\n"), |
| instance->GetType(), newTypeHandler, |
| oldSingletonInstanceAfter, oldSingletonInstanceAfter != nullptr ? oldSingletonInstanceAfter->Get() : nullptr, |
| newSingletonInstanceAfter, newSingletonInstanceAfter != nullptr ? newSingletonInstanceAfter->Get() : nullptr); |
| Output::Print(_u(" fixed fields:")); |
| newTypeHandler->DumpFixedFields(); |
| Output::Print(_u("\n")); |
| Output::Flush(); |
| } |
| if (PHASE_VERBOSE_TESTTRACE1(FixMethodPropsPhase)) |
| { |
| Output::Print(_u(" type %s, old singleton after %s null (%s)\n"), |
| oldType != instance->GetType() ? _u("changed") : _u("unchanged"), |
| oldSingletonInstanceBefore == nullptr ? _u("==") : _u("!="), |
| oldSingletonInstanceBefore != oldTypeHandler->GetSingletonInstance() ? _u("changed") : _u("unchanged")); |
| Output::Print(_u(" fixed fields after:")); |
| newTypeHandler->DumpFixedFields(); |
| Output::Print(_u("\n")); |
| Output::Flush(); |
| } |
| } |
| #endif |
| #endif // ENABLE_FIXED_FIELDS |
| |
| #if ENABLE_TTD |
| template <typename T> |
| void DictionaryTypeHandlerBase<T>::MarkObjectSlots_TTD(TTD::SnapshotExtractor* extractor, DynamicObject* obj) const |
| { |
| for (auto iter = this->propertyMap->GetIterator(); iter.IsValid(); iter.MoveNext()) |
| { |
| DictionaryPropertyDescriptor<T> descriptor = iter.CurrentValue(); |
| |
| // |
| //TODO: not sure about relationship with PropertyLetConstGlobal here need to -- check how GetProperty works |
| // maybe we need to template this with allowLetGlobalConst as well |
| // |
| |
| Js::PropertyId pid = iter.CurrentKey()->GetPropertyId(); |
| #if ENABLE_FIXED_FIELDS |
| if ((!DynamicTypeHandler::ShouldMarkPropertyId_TTD(pid)) | (!descriptor.GetIsInitialized()) | (descriptor.Attributes & PropertyDeleted)) |
| #else |
| if ((!DynamicTypeHandler::ShouldMarkPropertyId_TTD(pid)) | (descriptor.Attributes & PropertyDeleted)) |
| #endif |
| { |
| continue; |
| } |
| |
| T dIndex = descriptor.template GetDataPropertyIndex<false>(); |
| if (dIndex != NoSlots) |
| { |
| Js::Var dValue = obj->GetSlot(dIndex); |
| extractor->MarkVisitVar(dValue); |
| } |
| else |
| { |
| T gIndex = descriptor.GetGetterPropertyIndex(); |
| if (gIndex != NoSlots) |
| { |
| Js::Var gValue = obj->GetSlot(gIndex); |
| extractor->MarkVisitVar(gValue); |
| } |
| |
| T sIndex = descriptor.GetSetterPropertyIndex(); |
| if (sIndex != NoSlots) |
| { |
| Js::Var sValue = obj->GetSlot(sIndex); |
| extractor->MarkVisitVar(sValue); |
| } |
| } |
| } |
| } |
| |
| template <typename T> |
| uint32 DictionaryTypeHandlerBase<T>::ExtractSlotInfo_TTD(TTD::NSSnapType::SnapHandlerPropertyEntry* entryInfo, ThreadContext* threadContext, TTD::SlabAllocator& alloc) const |
| { |
| T maxSlot = 0; |
| |
| for (auto iter = this->propertyMap->GetIterator(); iter.IsValid(); iter.MoveNext()) |
| { |
| DictionaryPropertyDescriptor<T> descriptor = iter.CurrentValue(); |
| Js::PropertyId pid = iter.CurrentKey()->GetPropertyId(); |
| |
| T dIndex = descriptor.template GetDataPropertyIndex<false>(); |
| if (dIndex != NoSlots) |
| { |
| maxSlot = max(maxSlot, dIndex); |
| |
| #if ENABLE_FIXED_FIELDS |
| TTD::NSSnapType::SnapEntryDataKindTag tag = descriptor.GetIsInitialized() ? TTD::NSSnapType::SnapEntryDataKindTag::Data : TTD::NSSnapType::SnapEntryDataKindTag::Uninitialized; |
| #else |
| TTD::NSSnapType::SnapEntryDataKindTag tag = TTD::NSSnapType::SnapEntryDataKindTag::Data; |
| #endif |
| TTD::NSSnapType::ExtractSnapPropertyEntryInfo(entryInfo + dIndex, pid, descriptor.Attributes, tag); |
| } |
| else |
| { |
| #if ENABLE_FIXED_FIELDS |
| TTDAssert(descriptor.GetIsInitialized(), "How can this not be initialized?"); |
| #endif |
| |
| T gIndex = descriptor.GetGetterPropertyIndex(); |
| if (gIndex != NoSlots) |
| { |
| maxSlot = max(maxSlot, gIndex); |
| |
| TTD::NSSnapType::SnapEntryDataKindTag tag = TTD::NSSnapType::SnapEntryDataKindTag::Getter; |
| TTD::NSSnapType::ExtractSnapPropertyEntryInfo(entryInfo + gIndex, pid, descriptor.Attributes, tag); |
| } |
| |
| T sIndex = descriptor.GetSetterPropertyIndex(); |
| if (sIndex != NoSlots) |
| { |
| maxSlot = max(maxSlot, sIndex); |
| |
| TTD::NSSnapType::SnapEntryDataKindTag tag = TTD::NSSnapType::SnapEntryDataKindTag::Setter; |
| TTD::NSSnapType::ExtractSnapPropertyEntryInfo(entryInfo + sIndex, pid, descriptor.Attributes, tag); |
| } |
| } |
| } |
| |
| if (this->propertyMap->Count() == 0) |
| { |
| return 0; |
| } |
| else |
| { |
| return (uint32)(maxSlot + 1); |
| } |
| } |
| |
| template <typename T> |
| Js::BigPropertyIndex DictionaryTypeHandlerBase<T>::GetPropertyIndex_EnumerateTTD(const Js::PropertyRecord* pRecord) |
| { |
| for (Js::BigPropertyIndex index = 0; index < this->propertyMap->Count(); index++) |
| { |
| Js::PropertyId pid = this->propertyMap->GetKeyAt(index)->GetPropertyId(); |
| const DictionaryPropertyDescriptor<T>& idescriptor = propertyMap->GetValueAt(index); |
| |
| if (pid == pRecord->GetPropertyId() && !(idescriptor.Attributes & PropertyDeleted)) |
| { |
| return index; |
| } |
| } |
| |
| TTDAssert(false, "We found this and not accessor but NoBigSlot for index?"); |
| return Js::Constants::NoBigSlot; |
| } |
| #endif |
| |
| #if DBG_DUMP |
| template<typename T> void DictionaryTypeHandlerBase<T>::Dump(unsigned indent) const { |
| const auto padding(_u("")); |
| const unsigned fieldIndent(indent + 2); |
| const unsigned mapLabelIndent(indent + 4); |
| const unsigned mapValueIndent(indent + 6); |
| |
| Output::Print(_u("%*sDictionaryTypeHandlerBase (0x%p):\n"), indent, padding, this); |
| DynamicTypeHandler::Dump(indent + 2); |
| if (this->propertyMap == nullptr) |
| { |
| Output::Print(_u("%*spropertyMap: <null>\n"), fieldIndent, padding); |
| } |
| else |
| { |
| Output::Print(_u("%*spropertyMap: 0x%p\n"), fieldIndent, padding, static_cast<void*>(this->propertyMap)); |
| this->propertyMap->Map([&](const PropertyRecord *key, const DictionaryPropertyDescriptor<T> &value) |
| { |
| Output::Print(_u("%*sKey:\n"), mapLabelIndent, padding); |
| if (key == nullptr) |
| { |
| Output::Print(_u("%*s<null>\n"), mapValueIndent, padding); |
| } |
| else |
| { |
| key->Dump(mapValueIndent); |
| } |
| Output::Print(_u("%*sValue\n"), mapLabelIndent, padding); |
| value.Dump(mapValueIndent); |
| }); |
| } |
| Output::Print(_u("%*snextPropertyIndex: %d\n"), fieldIndent, padding, static_cast<int32>(this->nextPropertyIndex)); |
| } |
| |
| #endif |
| |
| template class DictionaryTypeHandlerBase<PropertyIndex>; |
| template class DictionaryTypeHandlerBase<BigPropertyIndex>; |
| |
| template <bool allowLetConstGlobal> |
| PropertyAttributes GetLetConstGlobalPropertyAttributes(PropertyAttributes attributes) |
| { |
| return (allowLetConstGlobal && (attributes & PropertyLetConstGlobal) != 0) ? (attributes | PropertyWritable) : attributes; |
| } |
| } |