| //------------------------------------------------------------------------------------------------------- |
| // 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 |
| } |