@@ -5318,6 +5318,58 @@ namespace Js
53185318 JS_REENTRANT_UNLOCK (jsReentLock, return JavascriptArray::ReverseHelper (pArr, nullptr , obj, length.GetBigIndex (), scriptContext));
53195319 }
53205320
5321+ bool JavascriptArray::HasAnyES5ArrayInPrototypeChain (JavascriptArray *arr, bool forceCheckProtoChain)
5322+ {
5323+ Assert (arr != nullptr );
5324+
5325+ #if ENABLE_COPYONACCESS_ARRAY
5326+ JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(arr);
5327+ #endif
5328+
5329+ bool hasAnyES5Array = false ;
5330+
5331+ // If there is no gap (unless forced) we are not filling from the prototype - so no point checking for ES5Array.
5332+ if (forceCheckProtoChain || arr->IsFillFromPrototypes ())
5333+ {
5334+ RecyclableObject* prototype = arr->GetPrototype ();
5335+
5336+ while (JavascriptOperators::GetTypeId (prototype) != TypeIds_Null)
5337+ {
5338+ RecyclableObject* protoObj = prototype;
5339+
5340+ if (!(DynamicObject::IsAnyArray (protoObj) || JavascriptOperators::IsObject (protoObj))
5341+ || JavascriptProxy::Is (protoObj)
5342+ || protoObj->IsExternal ())
5343+ {
5344+ hasAnyES5Array = true ;
5345+ break ;
5346+ }
5347+
5348+ if (DynamicObject::IsAnyArray (protoObj))
5349+ {
5350+ if (ES5Array::Is (protoObj))
5351+ {
5352+ hasAnyES5Array = true ;
5353+ break ;
5354+ }
5355+ }
5356+ else if (DynamicType::Is (protoObj->GetTypeId ()))
5357+ {
5358+ DynamicObject* dynobj = DynamicObject::FromVar (protoObj);
5359+ ArrayObject* objectArray = dynobj->GetObjectArray ();
5360+ if (objectArray != nullptr && ES5Array::Is (objectArray))
5361+ {
5362+ hasAnyES5Array = true ;
5363+ break ;
5364+ }
5365+ }
5366+
5367+ prototype = prototype->GetPrototype ();
5368+ }
5369+ }
5370+ return hasAnyES5Array;
5371+ }
5372+
53215373 // Array.prototype.reverse as described in ES6.0 (draft 22) Section 22.1.3.20
53225374 template <typename T>
53235375 Var JavascriptArray::ReverseHelper (JavascriptArray* pArr, Js::TypedArrayBase* typedArrayBase, RecyclableObject* obj, T length, ScriptContext* scriptContext)
@@ -5347,7 +5399,9 @@ namespace Js
53475399
53485400 ThrowTypeErrorOnFailureHelper h (scriptContext, methodName);
53495401
5350- if (pArr)
5402+ bool useNoSideEffectReverse = pArr != nullptr && !HasAnyES5ArrayInPrototypeChain (pArr);
5403+
5404+ if (useNoSideEffectReverse)
53515405 {
53525406 Recycler * recycler = scriptContext->GetRecycler ();
53535407
@@ -5373,6 +5427,12 @@ namespace Js
53735427 }
53745428 }
53755429
5430+ // As we have already established that the FillFromPrototype should not change the bound of the array.
5431+ if (length != (T)pArr->length )
5432+ {
5433+ Js::Throw::FatalInternalError ();
5434+ }
5435+
53765436 if (pArr->HasNoMissingValues () && pArr->head && pArr->head ->next )
53775437 {
53785438 // This function currently does not track missing values in the head segment if there are multiple segments
@@ -5679,23 +5739,33 @@ namespace Js
56795739 {
56805740 return res;
56815741 }
5682- if (JavascriptArray::Is (args[0 ]))
5742+
5743+ bool useNoSideEffectShift = JavascriptArray::Is (args[0 ])
5744+ && !JavascriptArray::FromVar (args[0 ])->IsCrossSiteObject ()
5745+ && !HasAnyES5ArrayInPrototypeChain (JavascriptArray::FromVar (args[0 ]));
5746+
5747+ if (useNoSideEffectShift)
56835748 {
56845749 JavascriptArray * pArr = JavascriptArray::FromVar (args[0 ]);
5685- #if ENABLE_COPYONACCESS_ARRAY
5686- JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(pArr);
5687- #endif
56885750
56895751 if (pArr->length == 0 )
56905752 {
56915753 return res;
56925754 }
56935755
5756+ uint32 length = pArr->length ;
5757+
56945758 if (pArr->IsFillFromPrototypes ())
56955759 {
56965760 JS_REENTRANT (jsReentLock, pArr->FillFromPrototypes (0 , pArr->length )); // We need find all missing value from [[proto]] object
56975761 }
56985762
5763+ // As we have already established that the FillFromPrototype should not change the bound of the array.
5764+ if (length != pArr->length )
5765+ {
5766+ Js::Throw::FatalInternalError ();
5767+ }
5768+
56995769 if (pArr->HasNoMissingValues () && pArr->head && pArr->head ->next )
57005770 {
57015771 // This function currently does not track missing values in the head segment if there are multiple segments
@@ -6137,6 +6207,11 @@ namespace Js
61376207
61386208 if (pArr)
61396209 {
6210+ if (HasAnyES5ArrayInPrototypeChain (pArr))
6211+ {
6212+ JS_REENTRANT_UNLOCK (jsReentLock, return JavascriptArray::SliceObjectHelper (obj, start, 0u , newArr, newObj, newLen, scriptContext));
6213+ }
6214+
61406215 // If we constructed a new Array object, we have some nice helpers here
61416216 if (newArr && isBuiltinArrayCtor)
61426217 {
@@ -6662,24 +6737,32 @@ namespace Js
66626737 }
66636738 }
66646739
6665- if (JavascriptArray::Is (args[0 ]))
6666- {
6667- #if ENABLE_COPYONACCESS_ARRAY
6668- JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(args[0 ]);
6669- #endif
6740+ bool useNoSideEffectSort = JavascriptArray::Is (args[0 ])
6741+ && !JavascriptArray::FromVar (args[0 ])->IsCrossSiteObject ()
6742+ && !HasAnyES5ArrayInPrototypeChain (JavascriptArray::FromVar (args[0 ]));
66706743
6744+ if (useNoSideEffectSort)
6745+ {
66716746 JavascriptArray *arr = JavascriptArray::FromVar (args[0 ]);
66726747
66736748 if (arr->length <= 1 )
66746749 {
66756750 return args[0 ];
66766751 }
66776752
6753+ uint32 length = arr->length ;
6754+
66786755 if (arr->IsFillFromPrototypes ())
66796756 {
66806757 JS_REENTRANT (jsReentLock, arr->FillFromPrototypes (0 , arr->length )); // We need find all missing value from [[proto]] object
66816758 }
66826759
6760+ // As we have already established that the FillFromPrototype should not change the bound of the array.
6761+ if (length != arr->length )
6762+ {
6763+ Js::Throw::FatalInternalError ();
6764+ }
6765+
66836766 // Maintain nativity of the array only for the following cases (To favor inplace conversions - keeps the conversion cost less):
66846767 // - int cases for X86 and
66856768 // - FloatArray for AMD64
@@ -7677,22 +7760,38 @@ namespace Js
76777760 {
76787761 return res;
76797762 }
7680- if (JavascriptArray::Is (args[0 ]) && !JavascriptArray::FromVar (args[0 ])->IsCrossSiteObject ())
7763+
7764+ JavascriptArray * pArr = nullptr ;
7765+ if (JavascriptArray::Is (args[0 ])
7766+ && !JavascriptArray::FromVar (args[0 ])->IsCrossSiteObject ())
76817767 {
76827768#if ENABLE_COPYONACCESS_ARRAY
76837769 JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(args[0 ]);
76847770#endif
7685- JavascriptArray * pArr = JavascriptArray::FromVar (args[0 ]);
7771+ pArr = JavascriptArray::FromVar (args[0 ]);
7772+ }
76867773
76877774 uint32 unshiftElements = args.Info .Count - 1 ;
76887775
7776+ // forceCheckProtoChain - since the array expand to accommodate new items thus we have to check if we have accessor on the proto chain.
7777+ bool useNoSideEffectUnshift = pArr != nullptr && (unshiftElements == 0 || !HasAnyES5ArrayInPrototypeChain (pArr, true /* forceCheckProtoChain*/ ));
7778+
7779+ if (useNoSideEffectUnshift)
7780+ {
76897781 if (unshiftElements > 0 )
76907782 {
7783+ uint32 length = pArr->length ;
76917784 if (pArr->IsFillFromPrototypes ())
76927785 {
76937786 JS_REENTRANT (jsReentLock, pArr->FillFromPrototypes (0 , pArr->length )); // We need find all missing value from [[proto]] object
76947787 }
76957788
7789+ // As we have already established that the FillFromPrototype should not change the bound of the array.
7790+ if (length != pArr->length )
7791+ {
7792+ Js::Throw::FatalInternalError ();
7793+ }
7794+
76967795 // Pre-process: truncate overflowing elements to properties
76977796 bool newLenOverflowed = false ;
76987797 uint32 maxLen = MaxArrayLength - unshiftElements;
@@ -7795,7 +7894,6 @@ namespace Js
77957894 }
77967895
77977896 JS_REENTRANT (jsReentLock, BigIndex length = OP_GetLength (dynamicObject, scriptContext));
7798- uint32 unshiftElements = args.Info .Count - 1 ;
77997897 if (unshiftElements > 0 )
78007898 {
78017899 uint32 MaxSpaceUint32 = MaxArrayLength - unshiftElements;
0 commit comments