blob: 998cf594aff3dcb52a55b9714b06a2ebb8675f54 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
#include "Library/ArgumentsObjectEnumerator.h"
namespace Js
{
BOOL ArgumentsObject::GetEnumerator(JavascriptStaticEnumerator * enumerator, EnumeratorFlags flags, ScriptContext* requestContext, ForInCache * forInCache)
{
return GetEnumeratorWithPrefix(
RecyclerNew(GetScriptContext()->GetRecycler(), ArgumentsObjectPrefixEnumerator, this, flags, requestContext),
enumerator, flags, requestContext, forInCache);
}
BOOL ArgumentsObject::GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("{...}"));
return TRUE;
}
BOOL ArgumentsObject::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("Object, (Arguments)"));
return TRUE;
}
Var ArgumentsObject::GetCaller(ScriptContext * scriptContext)
{
JavascriptStackWalker walker(scriptContext);
if (!this->AdvanceWalkerToArgsFrame(&walker))
{
return scriptContext->GetLibrary()->GetNull();
}
return ArgumentsObject::GetCaller(scriptContext, &walker, false);
}
Var ArgumentsObject::GetCaller(ScriptContext * scriptContext, JavascriptStackWalker *walker, bool skipGlobal)
{
// The arguments.caller property is equivalent to callee.caller.arguments - that is, it's the
// caller's arguments object (if any). Just fetch the caller and compute its arguments.
JavascriptFunction* funcCaller = nullptr;
while (walker->GetCaller(&funcCaller))
{
if (walker->IsCallerGlobalFunction())
{
// Caller is global/eval. If we're in IE9 mode, and the caller is eval,
// keep looking. Otherwise, caller is null.
if (skipGlobal || walker->IsEvalCaller())
{
continue;
}
funcCaller = nullptr;
}
break;
}
if (funcCaller == nullptr || JavascriptOperators::GetTypeId(funcCaller) == TypeIds_Null)
{
return scriptContext->GetLibrary()->GetNull();
}
AssertMsg(JavascriptOperators::GetTypeId(funcCaller) == TypeIds_Function, "non function caller");
const CallInfo callInfo = walker->GetCallInfo();
uint32 paramCount = callInfo.Count;
CallFlags flags = callInfo.Flags;
if (paramCount == 0 || (flags & CallFlags_Eval))
{
// The caller is the "global function" or eval, so we return "null".
return scriptContext->GetLibrary()->GetNull();
}
if (!walker->GetCurrentFunction()->IsScriptFunction())
{
// builtin function do not have an argument object - return null.
return scriptContext->GetLibrary()->GetNull();
}
// Create new arguments object, everytime this is requested for, with the actuals value.
Var args = nullptr;
args = JavascriptOperators::LoadHeapArguments(
funcCaller,
paramCount - 1,
walker->GetJavascriptArgs(),
scriptContext->GetLibrary()->GetNull(),
scriptContext->GetLibrary()->GetNull(),
scriptContext,
/* formalsAreLetDecls */ false);
return args;
}
bool ArgumentsObject::Is(Var aValue)
{
return JavascriptOperators::GetTypeId(aValue) == TypeIds_Arguments;
}
HeapArgumentsObject::HeapArgumentsObject(DynamicType * type) : ArgumentsObject(type), frameObject(nullptr), formalCount(0),
numOfArguments(0), callerDeleted(false), deletedArgs(nullptr)
{
}
HeapArgumentsObject::HeapArgumentsObject(Recycler *recycler, ActivationObject* obj, uint32 formalCount, DynamicType * type)
: ArgumentsObject(type), frameObject(obj), formalCount(formalCount), numOfArguments(0), callerDeleted(false), deletedArgs(nullptr)
{
}
void HeapArgumentsObject::SetNumberOfArguments(uint32 len)
{
numOfArguments = len;
}
uint32 HeapArgumentsObject::GetNumberOfArguments() const
{
return numOfArguments;
}
HeapArgumentsObject* HeapArgumentsObject::As(Var aValue)
{
if (ArgumentsObject::Is(aValue) && static_cast<ArgumentsObject*>(RecyclableObject::FromVar(aValue))->GetHeapArguments() == aValue)
{
return static_cast<HeapArgumentsObject*>(RecyclableObject::FromVar(aValue));
}
return NULL;
}
BOOL HeapArgumentsObject::AdvanceWalkerToArgsFrame(JavascriptStackWalker *walker)
{
// Walk until we find this arguments object on the frame.
// Note that each frame may have a HeapArgumentsObject
// associated with it. Look for the HeapArgumentsObject.
while (walker->Walk())
{
if (walker->IsJavascriptFrame() && walker->GetCurrentFunction()->IsScriptFunction())
{
Var args = walker->GetPermanentArguments();
if (args == this)
{
return TRUE;
}
}
}
return FALSE;
}
//
// Get the next valid formal arg index held in this object.
//
uint32 HeapArgumentsObject::GetNextFormalArgIndex(uint32 index, BOOL enumNonEnumerable, PropertyAttributes* attributes) const
{
if (attributes != nullptr)
{
*attributes = PropertyEnumerable;
}
if ( ++index < formalCount )
{
// None of the arguments deleted
if ( deletedArgs == nullptr )
{
return index;
}
while ( index < formalCount )
{
if (!this->deletedArgs->Test(index))
{
return index;
}
index++;
}
}
return JavascriptArray::InvalidIndex;
}
BOOL HeapArgumentsObject::HasItemAt(uint32 index)
{
// If this arg index is bound to a named formal argument, get it from the local frame.
// If not, report this fact to the caller, which will defer to the normal get-value-by-index means.
if (IsFormalArgument(index) && (this->deletedArgs == nullptr || !this->deletedArgs->Test(index)) )
{
return true;
}
return false;
}
BOOL HeapArgumentsObject::GetItemAt(uint32 index, Var* value, ScriptContext* requestContext)
{
// If this arg index is bound to a named formal argument, get it from the local frame.
// If not, report this fact to the caller, which will defer to the normal get-value-by-index means.
if (HasItemAt(index))
{
*value = this->frameObject->GetSlot(index);
return true;
}
return false;
}
BOOL HeapArgumentsObject::SetItemAt(uint32 index, Var value)
{
// If this arg index is bound to a named formal argument, set it in the local frame.
// If not, report this fact to the caller, which will defer to the normal set-value-by-index means.
if (HasItemAt(index))
{
this->frameObject->SetSlot(SetSlotArguments(Constants::NoProperty, index, value));
return true;
}
return false;
}
BOOL HeapArgumentsObject::DeleteItemAt(uint32 index)
{
if (index < formalCount)
{
if (this->deletedArgs == nullptr)
{
Recycler *recycler = GetScriptContext()->GetRecycler();
deletedArgs = RecyclerNew(recycler, BVSparse<Recycler>, recycler);
}
if (!this->deletedArgs->Test(index))
{
this->deletedArgs->Set(index);
return true;
}
}
return false;
}
BOOL HeapArgumentsObject::IsFormalArgument(uint32 index)
{
return index < this->numOfArguments && index < formalCount;
}
BOOL HeapArgumentsObject::IsFormalArgument(PropertyId propertyId)
{
uint32 index;
return IsFormalArgument(propertyId, &index);
}
BOOL HeapArgumentsObject::IsFormalArgument(PropertyId propertyId, uint32* pIndex)
{
return
this->GetScriptContext()->IsNumericPropertyId(propertyId, pIndex) &&
IsFormalArgument(*pIndex);
}
BOOL HeapArgumentsObject::IsArgumentDeleted(uint32 index) const
{
return this->deletedArgs != NULL && this->deletedArgs->Test(index);
}
#if ENABLE_TTD
void HeapArgumentsObject::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
{
if(this->frameObject != nullptr)
{
extractor->MarkVisitVar(this->frameObject);
}
}
TTD::NSSnapObjects::SnapObjectType HeapArgumentsObject::GetSnapTag_TTD() const
{
return TTD::NSSnapObjects::SnapObjectType::SnapHeapArgumentsObject;
}
void HeapArgumentsObject::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
this->ExtractSnapObjectDataInto_Helper<TTD::NSSnapObjects::SnapObjectType::SnapHeapArgumentsObject>(objData, alloc);
}
template <TTD::NSSnapObjects::SnapObjectType argsKind>
void HeapArgumentsObject::ExtractSnapObjectDataInto_Helper(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
TTD::NSSnapObjects::SnapHeapArgumentsInfo* argsInfo = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapHeapArgumentsInfo>();
TTDAssert(this->callerDeleted == 0, "This never seems to be set but I want to assert just to be safe.");
argsInfo->NumOfArguments = this->numOfArguments;
argsInfo->FormalCount = this->formalCount;
uint32 depOnCount = 0;
TTD_PTR_ID* depOnArray = nullptr;
argsInfo->IsFrameNullPtr = false;
argsInfo->IsFrameJsNull = false;
argsInfo->FrameObject = TTD_INVALID_PTR_ID;
if(this->frameObject == nullptr)
{
argsInfo->IsFrameNullPtr = true;
}
else if(Js::JavascriptOperators::GetTypeId(this->frameObject) == TypeIds_Null)
{
argsInfo->IsFrameJsNull = true;
}
else
{
argsInfo->FrameObject = TTD_CONVERT_VAR_TO_PTR_ID(
static_cast<ActivationObject*>(this->frameObject));
//Primitive kinds always inflated first so we only need to deal with complex kinds as depends on
if(TTD::JsSupport::IsVarComplexKind(this->frameObject))
{
depOnCount = 1;
depOnArray = alloc.SlabAllocateArray<TTD_PTR_ID>(depOnCount);
depOnArray[0] = argsInfo->FrameObject;
}
}
argsInfo->DeletedArgFlags = (this->formalCount != 0) ? alloc.SlabAllocateArrayZ<byte>(argsInfo->FormalCount) : nullptr;
if(this->deletedArgs != nullptr)
{
for(uint32 i = 0; i < this->formalCount; ++i)
{
if(this->deletedArgs->Test(i))
{
argsInfo->DeletedArgFlags[i] = true;
}
}
}
if(depOnCount == 0)
{
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapHeapArgumentsInfo*, argsKind>(objData, argsInfo);
}
else
{
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapHeapArgumentsInfo*, argsKind>(objData, argsInfo, alloc, depOnCount, depOnArray);
}
}
ES5HeapArgumentsObject* HeapArgumentsObject::ConvertToES5HeapArgumentsObject_TTD()
{
Assert(VirtualTableInfo<HeapArgumentsObject>::HasVirtualTable(this));
VirtualTableInfo<ES5HeapArgumentsObject>::SetVirtualTable(this);
return static_cast<ES5HeapArgumentsObject*>(this);
}
#endif
ES5HeapArgumentsObject* HeapArgumentsObject::ConvertToUnmappedArgumentsObject(bool overwriteArgsUsingFrameObject)
{
ES5HeapArgumentsObject* es5ArgsObj = ConvertToES5HeapArgumentsObject(overwriteArgsUsingFrameObject);
for (uint i=0; i<this->formalCount && i<numOfArguments; ++i)
{
es5ArgsObj->DisconnectFormalFromNamedArgument(i);
}
return es5ArgsObj;
}
ES5HeapArgumentsObject* HeapArgumentsObject::ConvertToES5HeapArgumentsObject(bool overwriteArgsUsingFrameObject)
{
if (!CrossSite::IsCrossSiteObjectTyped(this))
{
Assert(VirtualTableInfo<HeapArgumentsObject>::HasVirtualTable(this));
VirtualTableInfo<ES5HeapArgumentsObject>::SetVirtualTable(this);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<HeapArgumentsObject>>::HasVirtualTable(this));
VirtualTableInfo<CrossSiteObject<ES5HeapArgumentsObject>>::SetVirtualTable(this);
}
ES5HeapArgumentsObject* es5This = static_cast<ES5HeapArgumentsObject*>(this);
if (overwriteArgsUsingFrameObject)
{
// Copy existing items to the array so that they are there before the user can call Object.preventExtensions,
// after which we can no longer add new items to the objectArray.
for (uint32 i = 0; i < this->formalCount && i < this->numOfArguments; ++i)
{
AssertMsg(this->IsFormalArgument(i), "this->IsFormalArgument(i)");
if (!this->IsArgumentDeleted(i))
{
// At this time the value doesn't matter, use one from slots.
this->SetObjectArrayItem(i, this->frameObject->GetSlot(i), PropertyOperation_None);
}
}
}
return es5This;
}
PropertyQueryFlags HeapArgumentsObject::HasPropertyQuery(PropertyId id)
{
ScriptContext *scriptContext = GetScriptContext();
// Try to get a numbered property that maps to an actual argument.
uint32 index;
if (scriptContext->IsNumericPropertyId(id, &index) && index < this->HeapArgumentsObject::GetNumberOfArguments())
{
return HeapArgumentsObject::HasItemQuery(index);
}
return DynamicObject::HasPropertyQuery(id);
}
PropertyQueryFlags HeapArgumentsObject::GetPropertyQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
ScriptContext *scriptContext = GetScriptContext();
// Try to get a numbered property that maps to an actual argument.
uint32 index;
if (scriptContext->IsNumericPropertyId(propertyId, &index) && index < this->HeapArgumentsObject::GetNumberOfArguments())
{
if (this->GetItemAt(index, value, requestContext))
{
return Property_Found;
}
}
if (JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext)))
{
// Property has been pre-set and not deleted. Use it.
return Property_Found;
}
*value = requestContext->GetMissingPropertyResult();
return Property_NotFound;
}
PropertyQueryFlags HeapArgumentsObject::GetPropertyQuery(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*");
if (JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetPropertyQuery(originalInstance, propertyNameString, value, info, requestContext)))
{
// Property has been pre-set and not deleted. Use it.
return Property_Found;
}
*value = requestContext->GetMissingPropertyResult();
return Property_NotFound;
}
PropertyQueryFlags HeapArgumentsObject::GetPropertyReferenceQuery(Var originalInstance, PropertyId id, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return HeapArgumentsObject::GetPropertyQuery(originalInstance, id, value, info, requestContext);
}
BOOL HeapArgumentsObject::SetProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
{
// Try to set a numbered property that maps to an actual argument.
ScriptContext *scriptContext = GetScriptContext();
uint32 index;
if (scriptContext->IsNumericPropertyId(propertyId, &index) && index < this->HeapArgumentsObject::GetNumberOfArguments())
{
if (this->SetItemAt(index, value))
{
return true;
}
}
// TODO: In strict mode, "callee" and "caller" cannot be set.
// length is also set in the dynamic object
return DynamicObject::SetProperty(propertyId, value, flags, info);
}
BOOL HeapArgumentsObject::SetProperty(JavascriptString* propertyNameString, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
{
AssertMsg(!PropertyRecord::IsPropertyNameNumeric(propertyNameString->GetSz(), propertyNameString->GetLength()),
"Numeric property names should have been converted to uint or PropertyRecord*");
// TODO: In strict mode, "callee" and "caller" cannot be set.
// length is also set in the dynamic object
return DynamicObject::SetProperty(propertyNameString, value, flags, info);
}
PropertyQueryFlags HeapArgumentsObject::HasItemQuery(uint32 index)
{
if (this->HasItemAt(index))
{
return Property_Found;
}
return DynamicObject::HasItemQuery(index);
}
PropertyQueryFlags HeapArgumentsObject::GetItemQuery(Var originalInstance, uint32 index, Var* value, ScriptContext* requestContext)
{
if (this->GetItemAt(index, value, requestContext))
{
return Property_Found;
}
return DynamicObject::GetItemQuery(originalInstance, index, value, requestContext);
}
PropertyQueryFlags HeapArgumentsObject::GetItemReferenceQuery(Var originalInstance, uint32 index, Var* value, ScriptContext* requestContext)
{
return HeapArgumentsObject::GetItemQuery(originalInstance, index, value, requestContext);
}
BOOL HeapArgumentsObject::SetItem(uint32 index, Var value, PropertyOperationFlags flags)
{
if (this->SetItemAt(index, value))
{
return true;
}
return DynamicObject::SetItem(index, value, flags);
}
BOOL HeapArgumentsObject::DeleteItem(uint32 index, PropertyOperationFlags flags)
{
if (this->DeleteItemAt(index))
{
return true;
}
return DynamicObject::DeleteItem(index, flags);
}
BOOL HeapArgumentsObject::SetConfigurable(PropertyId propertyId, BOOL value)
{
// Try to set a numbered property that maps to an actual argument.
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
// From now on, make sure that frame object is ES5HeapArgumentsObject.
return this->ConvertToES5HeapArgumentsObject()->SetConfigurableForFormal(index, propertyId, value);
}
// Use 'this' dynamic object.
// This will use type handler and convert its objectArray to ES5Array is not already converted.
return __super::SetConfigurable(propertyId, value);
}
BOOL HeapArgumentsObject::SetEnumerable(PropertyId propertyId, BOOL value)
{
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return this->ConvertToES5HeapArgumentsObject()->SetEnumerableForFormal(index, propertyId, value);
}
return __super::SetEnumerable(propertyId, value);
}
BOOL HeapArgumentsObject::SetWritable(PropertyId propertyId, BOOL value)
{
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return this->ConvertToES5HeapArgumentsObject()->SetWritableForFormal(index, propertyId, value);
}
return __super::SetWritable(propertyId, value);
}
BOOL HeapArgumentsObject::SetAccessors(PropertyId propertyId, Var getter, Var setter, PropertyOperationFlags flags)
{
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return this->ConvertToES5HeapArgumentsObject()->SetAccessorsForFormal(index, propertyId, getter, setter, flags);
}
return __super::SetAccessors(propertyId, getter, setter, flags);
}
BOOL HeapArgumentsObject::SetPropertyWithAttributes(PropertyId propertyId, Var value, PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags, SideEffects possibleSideEffects)
{
// This is called by defineProperty in order to change the value in objectArray.
// We have to intercept this call because
// in case when we are connected (objectArray item is not used) we need to update the slot value (which is actually used).
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return this->ConvertToES5HeapArgumentsObject()->SetPropertyWithAttributesForFormal(
index, propertyId, value, attributes, info, flags, possibleSideEffects);
}
return __super::SetPropertyWithAttributes(propertyId, value, attributes, info, flags, possibleSideEffects);
}
// This disables adding new properties to the object.
BOOL HeapArgumentsObject::PreventExtensions()
{
// It's possible that after call to preventExtensions, the user can change the attributes;
// In this case if we don't covert to ES5 version now, later we would not be able to add items to objectArray,
// which would cause not being able to change attributes.
// So, convert to ES5 now which will make sure the items are there.
return this->ConvertToES5HeapArgumentsObject()->PreventExtensions();
}
// This is equivalent to .preventExtensions semantics with addition of setting configurable to false for all properties.
BOOL HeapArgumentsObject::Seal()
{
// Same idea as with PreventExtensions: we have to make sure that items in objectArray for formals
// are there before seal, otherwise we will not be able to add them later.
return this->ConvertToES5HeapArgumentsObject()->Seal();
}
// This is equivalent to .seal semantics with addition of setting writable to false for all properties.
BOOL HeapArgumentsObject::Freeze()
{
// Same idea as with PreventExtensions: we have to make sure that items in objectArray for formals
// are there before seal, otherwise we will not be able to add them later.
return this->ConvertToES5HeapArgumentsObject()->Freeze();
}
//---------------------- ES5HeapArgumentsObject -------------------------------
BOOL ES5HeapArgumentsObject::SetConfigurable(PropertyId propertyId, BOOL value)
{
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return this->SetConfigurableForFormal(index, propertyId, value);
}
return this->DynamicObject::SetConfigurable(propertyId, value);
}
BOOL ES5HeapArgumentsObject::SetEnumerable(PropertyId propertyId, BOOL value)
{
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return this->SetEnumerableForFormal(index, propertyId, value);
}
return this->DynamicObject::SetEnumerable(propertyId, value);
}
BOOL ES5HeapArgumentsObject::SetWritable(PropertyId propertyId, BOOL value)
{
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return this->SetWritableForFormal(index, propertyId, value);
}
return this->DynamicObject::SetWritable(propertyId, value);
}
BOOL ES5HeapArgumentsObject::SetAccessors(PropertyId propertyId, Var getter, Var setter, PropertyOperationFlags flags)
{
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return SetAccessorsForFormal(index, propertyId, getter, setter);
}
return this->DynamicObject::SetAccessors(propertyId, getter, setter, flags);
}
BOOL ES5HeapArgumentsObject::SetPropertyWithAttributes(PropertyId propertyId, Var value, PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags, SideEffects possibleSideEffects)
{
// This is called by defineProperty in order to change the value in objectArray.
// We have to intercept this call because
// in case when we are connected (objectArray item is not used) we need to update the slot value (which is actually used).
uint32 index;
if (this->IsFormalArgument(propertyId, &index))
{
return this->SetPropertyWithAttributesForFormal(index, propertyId, value, attributes, info, flags, possibleSideEffects);
}
return this->DynamicObject::SetPropertyWithAttributes(propertyId, value, attributes, info, flags, possibleSideEffects);
}
BOOL ES5HeapArgumentsObject::GetEnumerator(JavascriptStaticEnumerator * enumerator, EnumeratorFlags flags, ScriptContext* requestContext, ForInCache * forInCache)
{
ES5ArgumentsObjectEnumerator * es5HeapArgumentsObjectEnumerator = ES5ArgumentsObjectEnumerator::New(this, flags, requestContext, forInCache);
if (es5HeapArgumentsObjectEnumerator == nullptr)
{
return false;
}
return enumerator->Initialize(es5HeapArgumentsObjectEnumerator, nullptr, nullptr, flags, requestContext, nullptr);
}
BOOL ES5HeapArgumentsObject::PreventExtensions()
{
return this->DynamicObject::PreventExtensions();
}
BOOL ES5HeapArgumentsObject::Seal()
{
return this->DynamicObject::Seal();
}
BOOL ES5HeapArgumentsObject::Freeze()
{
return this->DynamicObject::Freeze();
}
BOOL ES5HeapArgumentsObject::GetItemAt(uint32 index, Var* value, ScriptContext* requestContext)
{
return this->IsFormalDisconnectedFromNamedArgument(index) ?
JavascriptConversion::PropertyQueryFlagsToBoolean(this->DynamicObject::GetItemQuery(this, index, value, requestContext)) :
__super::GetItemAt(index, value, requestContext);
}
BOOL ES5HeapArgumentsObject::SetItemAt(uint32 index, Var value)
{
return this->IsFormalDisconnectedFromNamedArgument(index) ?
this->DynamicObject::SetItem(index, value, PropertyOperation_None) :
__super::SetItemAt(index, value);
}
BOOL ES5HeapArgumentsObject::DeleteItemAt(uint32 index)
{
BOOL result = __super::DeleteItemAt(index);
if (result && IsFormalArgument(index))
{
AssertMsg(this->IsFormalDisconnectedFromNamedArgument(index), "__super::DeleteItemAt must perform the disconnect.");
// Make sure that objectArray does not have the item ().
if (this->HasObjectArrayItem(index))
{
this->DeleteObjectArrayItem(index, PropertyOperation_None);
}
}
return result;
}
//
// Get the next valid formal arg index held in this object.
//
uint32 ES5HeapArgumentsObject::GetNextFormalArgIndex(uint32 index, BOOL enumNonEnumerable, PropertyAttributes* attributes) const
{
return GetNextFormalArgIndexHelper(index, enumNonEnumerable, attributes);
}
uint32 ES5HeapArgumentsObject::GetNextFormalArgIndexHelper(uint32 index, BOOL enumNonEnumerable, PropertyAttributes* attributes) const
{
// Formals:
// - deleted => not in objectArray && not connected -- do not enum, do not advance.
// - connected, if in objectArray -- if (enumerable) enum it, advance objectEnumerator.
// - disconnected =>in objectArray -- if (enumerable) enum it, advance objectEnumerator.
// We use GetFormalCount and IsEnumerableByIndex which don't change the object
// but are not declared as const, so do a const cast.
ES5HeapArgumentsObject* mutableThis = const_cast<ES5HeapArgumentsObject*>(this);
uint32 formalCount = this->GetFormalCount();
while (++index < formalCount)
{
bool isDeleted = mutableThis->IsFormalDisconnectedFromNamedArgument(index) &&
!mutableThis->HasObjectArrayItem(index);
if (!isDeleted)
{
BOOL isEnumerable = mutableThis->IsEnumerableByIndex(index);
if (enumNonEnumerable || isEnumerable)
{
if (attributes != nullptr && isEnumerable)
{
*attributes = PropertyEnumerable;
}
return index;
}
}
}
return JavascriptArray::InvalidIndex;
}
// Disconnects indexed argument from named argument for frame object property.
// Remove association from them map. From now on (or still) for this argument,
// named argument's value is no longer associated with arguments[] item.
void ES5HeapArgumentsObject::DisconnectFormalFromNamedArgument(uint32 index)
{
AssertMsg(this->IsFormalArgument(index), "SetAccessorsForFormal: called for non-formal");
if (!IsFormalDisconnectedFromNamedArgument(index))
{
__super::DeleteItemAt(index);
}
}
BOOL ES5HeapArgumentsObject::IsFormalDisconnectedFromNamedArgument(uint32 index)
{
return this->IsArgumentDeleted(index);
}
// Wrapper over IsEnumerable by uint32 index.
BOOL ES5HeapArgumentsObject::IsEnumerableByIndex(uint32 index)
{
ScriptContext* scriptContext = this->GetScriptContext();
Var indexNumber = JavascriptNumber::New(index, scriptContext);
JavascriptString* indexPropertyName = JavascriptConversion::ToString(indexNumber, scriptContext);
PropertyRecord const * propertyRecord;
scriptContext->GetOrAddPropertyRecord(indexPropertyName->GetString(), indexPropertyName->GetLength(), &propertyRecord);
return this->IsEnumerable(propertyRecord->GetPropertyId());
}
// Helper method, just to avoid code duplication.
BOOL ES5HeapArgumentsObject::SetConfigurableForFormal(uint32 index, PropertyId propertyId, BOOL value)
{
AssertMsg(this->IsFormalArgument(index), "SetAccessorsForFormal: called for non-formal");
// In order for attributes to work, the item must exist. Make sure that's the case.
// If we were connected, we have to copy the value from frameObject->slots, otherwise which value doesn't matter.
AutoObjectArrayItemExistsValidator autoItemAddRelease(this, index);
BOOL result = this->DynamicObject::SetConfigurable(propertyId, value);
autoItemAddRelease.m_isReleaseItemNeeded = !result;
return result;
}
// Helper method, just to avoid code duplication.
BOOL ES5HeapArgumentsObject::SetEnumerableForFormal(uint32 index, PropertyId propertyId, BOOL value)
{
AssertMsg(this->IsFormalArgument(index), "SetAccessorsForFormal: called for non-formal");
AutoObjectArrayItemExistsValidator autoItemAddRelease(this, index);
BOOL result = this->DynamicObject::SetEnumerable(propertyId, value);
autoItemAddRelease.m_isReleaseItemNeeded = !result;
return result;
}
// Helper method, just to avoid code duplication.
BOOL ES5HeapArgumentsObject::SetWritableForFormal(uint32 index, PropertyId propertyId, BOOL value)
{
AssertMsg(this->IsFormalArgument(index), "SetAccessorsForFormal: called for non-formal");
AutoObjectArrayItemExistsValidator autoItemAddRelease(this, index);
BOOL isDisconnected = IsFormalDisconnectedFromNamedArgument(index);
if (!isDisconnected && !value)
{
// Settings writable to false causes disconnect.
// It will be too late to copy the value after setting writable = false, as we would not be able to.
// Since we are connected, it does not matter the value is, so it's safe (no matter if SetWritable fails) to copy it here.
this->SetObjectArrayItem(index, this->frameObject->GetSlot(index), PropertyOperation_None);
}
BOOL result = this->DynamicObject::SetWritable(propertyId, value); // Note: this will convert objectArray to ES5Array.
if (result && !value && !isDisconnected)
{
this->DisconnectFormalFromNamedArgument(index);
}
autoItemAddRelease.m_isReleaseItemNeeded = !result;
return result;
}
// Helper method for SetPropertyWithAttributes, just to avoid code duplication.
BOOL ES5HeapArgumentsObject::SetAccessorsForFormal(uint32 index, PropertyId propertyId, Var getter, Var setter, PropertyOperationFlags flags)
{
AssertMsg(this->IsFormalArgument(index), "SetAccessorsForFormal: called for non-formal");
AutoObjectArrayItemExistsValidator autoItemAddRelease(this, index);
BOOL result = this->DynamicObject::SetAccessors(propertyId, getter, setter, flags);
if (result)
{
this->DisconnectFormalFromNamedArgument(index);
}
autoItemAddRelease.m_isReleaseItemNeeded = !result;
return result;
}
// Helper method for SetPropertyWithAttributes, just to avoid code duplication.
BOOL ES5HeapArgumentsObject::SetPropertyWithAttributesForFormal(uint32 index, PropertyId propertyId, Var value, PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags, SideEffects possibleSideEffects)
{
AssertMsg(this->IsFormalArgument(propertyId), "SetPropertyWithAttributesForFormal: called for non-formal");
BOOL result = this->DynamicObject::SetPropertyWithAttributes(propertyId, value, attributes, info, flags, possibleSideEffects);
if (result)
{
if ((attributes & PropertyWritable) == 0)
{
// Setting writable to false should cause disconnect.
this->DisconnectFormalFromNamedArgument(index);
}
if (!this->IsFormalDisconnectedFromNamedArgument(index))
{
// Update the (connected with named param) value, as above call could cause change of the value.
BOOL tempResult = this->SetItemAt(index, value); // Update the value in frameObject.
AssertMsg(tempResult, "this->SetItem(index, value)");
}
}
return result;
}
//---------------------- ES5HeapArgumentsObject::AutoObjectArrayItemExistsValidator -------------------------------
ES5HeapArgumentsObject::AutoObjectArrayItemExistsValidator::AutoObjectArrayItemExistsValidator(ES5HeapArgumentsObject* args, uint32 index)
: m_args(args), m_index(index), m_isReleaseItemNeeded(false)
{
AssertMsg(args, "args");
AssertMsg(args->IsFormalArgument(index), "AutoObjectArrayItemExistsValidator: called for non-formal");
if (!args->HasObjectArrayItem(index))
{
m_isReleaseItemNeeded = args->SetObjectArrayItem(index, args->frameObject->GetSlot(index), PropertyOperation_None) != FALSE;
}
}
ES5HeapArgumentsObject::AutoObjectArrayItemExistsValidator::~AutoObjectArrayItemExistsValidator()
{
if (m_isReleaseItemNeeded)
{
m_args->DeleteObjectArrayItem(m_index, PropertyOperation_None);
}
}
#if ENABLE_TTD
TTD::NSSnapObjects::SnapObjectType ES5HeapArgumentsObject::GetSnapTag_TTD() const
{
return TTD::NSSnapObjects::SnapObjectType::SnapES5HeapArgumentsObject;
}
void ES5HeapArgumentsObject::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
this->ExtractSnapObjectDataInto_Helper<TTD::NSSnapObjects::SnapObjectType::SnapES5HeapArgumentsObject>(objData, alloc);
}
#endif
}