blob: 267b41efdf916aeda71e5f2c2c13ee8bc2988bd6 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
namespace Js
{
CompileAssert(sizeof(ES5Array) == sizeof(JavascriptArray));
ES5ArrayType::ES5ArrayType(DynamicType* type)
: DynamicType(type->GetScriptContext(), TypeIds_ES5Array, type->GetPrototype(), type->GetEntryPoint(), type->GetTypeHandler(), false, false)
{
}
DynamicType* ES5Array::DuplicateType()
{
return RecyclerNew(GetScriptContext()->GetRecycler(), ES5ArrayType, this->GetDynamicType());
}
bool ES5Array::IsLengthWritable() const
{
return GetTypeHandler()->IsLengthWritable();
}
PropertyQueryFlags ES5Array::HasPropertyQuery(PropertyId propertyId, _Inout_opt_ PropertyValueInfo* info)
{
if (propertyId == PropertyIds::length)
{
return PropertyQueryFlags::Property_Found;
}
// Skip JavascriptArray override
return DynamicObject::HasPropertyQuery(propertyId, info);
}
BOOL ES5Array::IsWritable(PropertyId propertyId)
{
if (propertyId == PropertyIds::length)
{
return IsLengthWritable();
}
return __super::IsWritable(propertyId);
}
BOOL ES5Array::SetEnumerable(PropertyId propertyId, BOOL value)
{
// Skip JavascriptArray override
return DynamicObject::SetEnumerable(propertyId, value);
}
BOOL ES5Array::SetWritable(PropertyId propertyId, BOOL value)
{
// Skip JavascriptArray override
return DynamicObject::SetWritable(propertyId, value);
}
BOOL ES5Array::SetConfigurable(PropertyId propertyId, BOOL value)
{
// Skip JavascriptArray override
return DynamicObject::SetConfigurable(propertyId, value);
}
BOOL ES5Array::SetAttributes(PropertyId propertyId, PropertyAttributes attributes)
{
// Skip JavascriptArray override
return DynamicObject::SetAttributes(propertyId, attributes);
}
PropertyQueryFlags ES5Array::GetPropertyQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
BOOL result;
if (GetPropertyBuiltIns(propertyId, value, &result))
{
return JavascriptConversion::BooleanToPropertyQueryFlags(result);
}
// Skip JavascriptArray override
return DynamicObject::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext);
}
PropertyQueryFlags ES5Array::GetPropertyQuery(Var originalInstance, JavascriptString* propertyNameString, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
BOOL result;
PropertyRecord const* propertyRecord;
this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord);
if (propertyRecord != nullptr && GetPropertyBuiltIns(propertyRecord->GetPropertyId(), value, &result))
{
return JavascriptConversion::BooleanToPropertyQueryFlags(result);
}
// Skip JavascriptArray override
return DynamicObject::GetPropertyQuery(originalInstance, propertyNameString, value, info, requestContext);
}
bool ES5Array::GetPropertyBuiltIns(PropertyId propertyId, Var* value, BOOL* result)
{
if (propertyId == PropertyIds::length)
{
*value = JavascriptNumber::ToVar(this->GetLength(), GetScriptContext());
*result = true;
return true;
}
return false;
}
PropertyQueryFlags ES5Array::GetPropertyReferenceQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return ES5Array::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext);
}
// Convert a Var to array length, throw RangeError if value is not valid for array length.
uint32 ES5Array::ToLengthValue(Var value, ScriptContext* scriptContext)
{
if (TaggedInt::Is(value))
{
int32 newLen = TaggedInt::ToInt32(value);
if (newLen < 0)
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_ArrayLengthAssignIncorrect);
}
return static_cast<uint32>(newLen);
}
else
{
uint32 newLen = JavascriptConversion::ToUInt32(value, scriptContext);
if (newLen != JavascriptConversion::ToNumber(value, scriptContext))
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_ArrayLengthAssignIncorrect);
}
return newLen;
}
}
DescriptorFlags ES5Array::GetSetter(PropertyId propertyId, Var *setterValue, PropertyValueInfo* info, ScriptContext* requestContext)
{
DescriptorFlags result;
if (GetSetterBuiltIns(propertyId, info, &result))
{
return result;
}
return DynamicObject::GetSetter(propertyId, setterValue, info, requestContext);
}
DescriptorFlags ES5Array::GetSetter(JavascriptString* propertyNameString, Var *setterValue, PropertyValueInfo* info, ScriptContext* requestContext)
{
DescriptorFlags result;
PropertyRecord const* propertyRecord;
this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord);
if (propertyRecord != nullptr && GetSetterBuiltIns(propertyRecord->GetPropertyId(), info, &result))
{
return result;
}
return DynamicObject::GetSetter(propertyNameString, setterValue, info, requestContext);
}
bool ES5Array::GetSetterBuiltIns(PropertyId propertyId, PropertyValueInfo* info, DescriptorFlags* result)
{
if (propertyId == PropertyIds::length)
{
PropertyValueInfo::SetNoCache(info, this);
*result = IsLengthWritable() ? WritableData : Data;
return true;
}
return false;
}
BOOL ES5Array::SetProperty(PropertyId propertyId, Var value, PropertyOperationFlags propertyOperationFlags, PropertyValueInfo* info)
{
BOOL result;
if (SetPropertyBuiltIns(propertyId, value, propertyOperationFlags, &result))
{
return result;
}
return __super::SetProperty(propertyId, value, propertyOperationFlags, info);
}
BOOL ES5Array::SetProperty(JavascriptString* propertyNameString, Var value, PropertyOperationFlags propertyOperationFlags, PropertyValueInfo* info)
{
BOOL result;
PropertyRecord const* propertyRecord;
this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord);
if (propertyRecord != nullptr && SetPropertyBuiltIns(propertyRecord->GetPropertyId(), value, propertyOperationFlags, &result))
{
return result;
}
return __super::SetProperty(propertyNameString, value, propertyOperationFlags, info);
}
bool ES5Array::SetPropertyBuiltIns(PropertyId propertyId, Var value, PropertyOperationFlags propertyOperationFlags, BOOL* result)
{
ScriptContext* scriptContext = GetScriptContext();
if (propertyId == PropertyIds::length)
{
if (!GetTypeHandler()->IsLengthWritable())
{
*result = false; // reject
return true;
}
uint32 newLen = ToLengthValue(value, scriptContext);
uint32 assignedLen = GetTypeHandler()->SetLength(this, newLen, propertyOperationFlags);
if (newLen != assignedLen)
{
scriptContext->GetThreadContext()->AddImplicitCallFlags(ImplicitCall_NoOpSet);
}
*result = true;
return true;
}
return false;
}
BOOL ES5Array::SetPropertyWithAttributes(PropertyId propertyId, Var value, PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags, SideEffects possibleSideEffects)
{
if (propertyId == PropertyIds::length)
{
Assert(attributes == PropertyWritable);
Assert(IsWritable(propertyId) && !IsConfigurable(propertyId) && !IsEnumerable(propertyId));
uint32 newLen = ToLengthValue(value, GetScriptContext());
GetTypeHandler()->SetLength(this, newLen, PropertyOperation_None);
return true;
}
return __super::SetPropertyWithAttributes(propertyId, value, attributes, info, flags, possibleSideEffects);
}
BOOL ES5Array::DeleteItem(uint32 index, PropertyOperationFlags flags)
{
// Skip JavascriptArray override
return DynamicObject::DeleteItem(index, flags);
}
PropertyQueryFlags ES5Array::HasItemQuery(uint32 index)
{
// Skip JavascriptArray override
return DynamicObject::HasItemQuery(index);
}
PropertyQueryFlags ES5Array::GetItemQuery(Var originalInstance, uint32 index, Var* value, ScriptContext * requestContext)
{
// Skip JavascriptArray override
return DynamicObject::GetItemQuery(originalInstance, index, value, requestContext);
}
PropertyQueryFlags ES5Array::GetItemReferenceQuery(Var originalInstance, uint32 index, Var* value, ScriptContext * requestContext)
{
// Skip JavascriptArray override
return DynamicObject::GetItemReferenceQuery(originalInstance, index, value, requestContext);
}
DescriptorFlags ES5Array::GetItemSetter(uint32 index, Var* setterValue, ScriptContext* requestContext)
{
// Skip JavascriptArray override
return DynamicObject::GetItemSetter(index, setterValue, requestContext);
}
BOOL ES5Array::SetItem(uint32 index, Var value, PropertyOperationFlags flags)
{
// Skip JavascriptArray override
return DynamicObject::SetItem(index, value, flags);
}
BOOL ES5Array::SetAccessors(PropertyId propertyId, Var getter, Var setter, PropertyOperationFlags flags)
{
// Skip JavascriptArray override
return DynamicObject::SetAccessors(propertyId, getter, setter, flags);
}
BOOL ES5Array::PreventExtensions()
{
// Skip JavascriptArray override
return DynamicObject::PreventExtensions();
}
BOOL ES5Array::Seal()
{
// Skip JavascriptArray override
return DynamicObject::Seal();
}
BOOL ES5Array::Freeze()
{
// Skip JavascriptArray override
return DynamicObject::Freeze();
}
BOOL ES5Array::GetEnumerator(JavascriptStaticEnumerator * enumerator, EnumeratorFlags flags, ScriptContext* requestContext, EnumeratorCache * enumeratorCache)
{
return enumerator->Initialize(nullptr, this, this, flags, requestContext, enumeratorCache);
}
JavascriptEnumerator * ES5Array::GetIndexEnumerator(EnumeratorFlags flags, ScriptContext* requestContext)
{
// ES5Array does not support compat mode, ignore preferSnapshotSemantics
return RecyclerNew(GetScriptContext()->GetRecycler(), ES5ArrayIndexEnumerator, this, flags, requestContext);
}
BOOL ES5Array::IsItemEnumerable(uint32 index)
{
return GetTypeHandler()->IsItemEnumerable(this, index);
}
BOOL ES5Array::SetItemWithAttributes(uint32 index, Var value, PropertyAttributes attributes)
{
return GetTypeHandler()->SetItemWithAttributes(this, index, value, attributes);
}
BOOL ES5Array::SetItemAttributes(uint32 index, PropertyAttributes attributes)
{
return GetTypeHandler()->SetItemAttributes(this, index, attributes);
}
BOOL ES5Array::SetItemAccessors(uint32 index, Var getter, Var setter)
{
return GetTypeHandler()->SetItemAccessors(this, index, getter, setter);
}
BOOL ES5Array::IsObjectArrayFrozen()
{
return GetTypeHandler()->IsObjectArrayFrozen(this);
}
BOOL ES5Array::IsValidDescriptorToken(void * descriptorValidationToken) const
{
return GetTypeHandler()->IsValidDescriptorToken(descriptorValidationToken);
}
uint32 ES5Array::GetNextDescriptor(uint32 key, IndexPropertyDescriptor** descriptor, void ** descriptorValidationToken)
{
return GetTypeHandler()->GetNextDescriptor(key, descriptor, descriptorValidationToken);
}
BOOL ES5Array::GetDescriptor(uint32 index, Js::IndexPropertyDescriptor **ppDescriptor)
{
return GetTypeHandler()->GetDescriptor(index, ppDescriptor);
}
#if ENABLE_TTD
void ES5Array::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
{
this->JavascriptArray::MarkVisitKindSpecificPtrs(extractor);
uint32 length = this->GetLength();
uint32 descriptorIndex = Js::JavascriptArray::InvalidIndex;
IndexPropertyDescriptor* descriptor = nullptr;
void* descriptorValidationToken = nullptr;
do
{
descriptorIndex = this->GetNextDescriptor(descriptorIndex, &descriptor, &descriptorValidationToken);
if(descriptorIndex == Js::JavascriptArray::InvalidIndex || descriptorIndex >= length)
{
break;
}
if((descriptor->Attributes & PropertyDeleted) != PropertyDeleted)
{
if(descriptor->Getter != nullptr)
{
extractor->MarkVisitVar(descriptor->Getter);
}
if(descriptor->Setter != nullptr)
{
extractor->MarkVisitVar(descriptor->Setter);
}
}
} while(true);
}
TTD::NSSnapObjects::SnapObjectType ES5Array::GetSnapTag_TTD() const
{
return TTD::NSSnapObjects::SnapObjectType::SnapES5ArrayObject;
}
void ES5Array::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
TTD::NSSnapObjects::SnapArrayInfo<TTD::TTDVar>* sai = TTD::NSSnapObjects::ExtractArrayValues<TTD::TTDVar>(this, alloc);
TTD::NSSnapObjects::SnapES5ArrayInfo* es5ArrayInfo = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapES5ArrayInfo>();
//
//TODO: reserving memory for entire length might be a problem if we have very large sparse arrays.
//
uint32 length = this->GetLength();
es5ArrayInfo->IsLengthWritable = this->IsLengthWritable();
es5ArrayInfo->GetterSetterCount = 0;
es5ArrayInfo->GetterSetterEntries = alloc.SlabReserveArraySpace<TTD::NSSnapObjects::SnapES5ArrayGetterSetterEntry>(length + 1); //ensure we don't do a 0 reserve
uint32 descriptorIndex = Js::JavascriptArray::InvalidIndex;
IndexPropertyDescriptor* descriptor = nullptr;
void* descriptorValidationToken = nullptr;
do
{
descriptorIndex = this->GetNextDescriptor(descriptorIndex, &descriptor, &descriptorValidationToken);
if(descriptorIndex == Js::JavascriptArray::InvalidIndex || descriptorIndex >= length)
{
break;
}
if((descriptor->Attributes & PropertyDeleted) != PropertyDeleted)
{
TTD::NSSnapObjects::SnapES5ArrayGetterSetterEntry* entry = es5ArrayInfo->GetterSetterEntries + es5ArrayInfo->GetterSetterCount;
es5ArrayInfo->GetterSetterCount++;
entry->Index = (uint32)descriptorIndex;
entry->Attributes = descriptor->Attributes;
entry->Getter = nullptr;
if(descriptor->Getter != nullptr)
{
entry->Getter = descriptor->Getter;
}
entry->Setter = nullptr;
if(descriptor->Setter != nullptr)
{
entry->Setter = descriptor->Setter;
}
}
} while(true);
if(es5ArrayInfo->GetterSetterCount != 0)
{
alloc.SlabCommitArraySpace<TTD::NSSnapObjects::SnapES5ArrayGetterSetterEntry>(es5ArrayInfo->GetterSetterCount, length + 1);
}
else
{
alloc.SlabAbortArraySpace<TTD::NSSnapObjects::SnapES5ArrayGetterSetterEntry>(length + 1);
es5ArrayInfo->GetterSetterEntries = nullptr;
}
es5ArrayInfo->BasicArrayData = sai;
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapES5ArrayInfo*, TTD::NSSnapObjects::SnapObjectType::SnapES5ArrayObject>(objData, es5ArrayInfo);
}
#endif
}