Skip to content

Commit 8fb3111

Browse files
committed
[1.6>1.7] [MERGE #3415 @akroshg] slice helper should check for the ES5array or Proxy in the protototype
Merge pull request #3415 from akroshg:nosideeffect This way we can identify if the array's content can be affected. If there is sideeffect expected we can take the slower path to finish the logic.
2 parents 3c659bb + 251abbf commit 8fb3111

File tree

7 files changed

+318
-30
lines changed

7 files changed

+318
-30
lines changed

lib/Runtime/Library/JavascriptArray.cpp

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

lib/Runtime/Library/JavascriptArray.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ namespace Js
625625
template <typename Fn>
626626
static void ForEachOwnMissingArrayIndexOfObject(JavascriptArray *baseArr, JavascriptArray *destArray, RecyclableObject* obj, uint32 startIndex, uint32 limitIndex, uint32 destIndex, Fn fn);
627627

628+
// This helper function is mainly used as a precheck before going to the FillFromPrototype code path.
629+
// Proxy and CustomExternalObject in the prototype chain will be returned as if ES5Array is there.
630+
static bool HasAnyES5ArrayInPrototypeChain(JavascriptArray *arr, bool forceCheckProtoChain = false);
631+
628632
// NativeArrays may change it's content type, but not others
629633
template <typename T> static bool MayChangeType() { return false; }
630634

0 commit comments

Comments
 (0)