Skip to content

Commit 9377f09

Browse files
Skip Array pop fast path if prototype unsafe (#6838)
The Array.prototype.pop fast path does not walk the prototype chain. That’s a problem, if a sparse array has a getter defined in it’s prototype like in issue #6824. There are 4 variants of the fast path: 1. JavascriptNativeArray::PopWithNoDst 2. JavascriptNativeIntArray::Pop 3. JavascriptNativeFloatArray::Pop 4. JavascriptArray::Pop. Behavior: The issue only occurs in the first case, if the result of .pop() is discarded, because in that case only the length of the array is reduced. If the result of a JavascriptNativeArray is stored in a variable (2 and 3), the jit method will bailout and hand over to JavascriptArray::EntryPop which will walk the prototype chain (no issue). The last case explicitly walks the prototype chain (no issue). (Related to #6598) Fix #6824
1 parent d935596 commit 9377f09

File tree

8 files changed

+94
-9
lines changed

8 files changed

+94
-9
lines changed

ContributionAgreement.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ This agreement has been signed by:
4343
|Kevin Cadieux|kevcadieux|
4444
|Aidan Bickford| BickfordA|
4545
|Ryoichi Kaida| camcam-lemon|
46+
|Lukas Kurz| ShortDevelopment|

lib/Backend/JnHelperMethodList.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ HELPERCALLCHK(Array_VarPush, Js::JavascriptArray::Push, 0)
486486
HELPERCALLCHK(Array_NativeIntPush, Js::JavascriptNativeIntArray::Push, 0)
487487
HELPERCALLCHK(Array_NativeFloatPush, Js::JavascriptNativeFloatArray::Push, 0)
488488
HELPERCALLCHK(Array_VarPop, Js::JavascriptArray::Pop, 0)
489-
HELPERCALLCHK(Array_NativePopWithNoDst, Js::JavascriptNativeArray::PopWithNoDst, AttrCanNotBeReentrant)
489+
HELPERCALLCHK(Array_NativePopWithNoDst, Js::JavascriptNativeArray::PopWithNoDst, 0)
490490
HELPERCALLCHK(Array_NativeIntPop, Js::JavascriptNativeIntArray::Pop, AttrCanNotBeReentrant)
491491
HELPERCALLCHK(Array_NativeFloatPop, Js::JavascriptNativeFloatArray::Pop, AttrCanNotBeReentrant)
492492
HELPERCALLCHK(Array_Reverse, Js::JavascriptArray::EntryReverse, 0)

lib/Backend/Lower.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12359,6 +12359,7 @@ Lowerer::GenerateHelperToArrayPopFastPath(IR::Instr * instr, IR::LabelInstr * do
1235912359
IR::JnHelperMethod helperMethod;
1236012360

1236112361
//Decide the helperMethod based on dst availability and nativity of the array.
12362+
// ToDo: Maybe ignore fast path if `JavascriptArray::HasAnyES5ArrayInPrototypeChain`. See #6582 and #6824.
1236212363
if(arrayValueType.IsLikelyNativeArray() && !instr->GetDst())
1236312364
{
1236412365
helperMethod = IR::HelperArray_NativePopWithNoDst;
@@ -12378,11 +12379,7 @@ Lowerer::GenerateHelperToArrayPopFastPath(IR::Instr * instr, IR::LabelInstr * do
1237812379

1237912380
m_lowererMD.LoadHelperArgument(instr, arrayHelperOpnd);
1238012381

12381-
//We do not need scriptContext for HelperArray_NativePopWithNoDst call.
12382-
if(helperMethod != IR::HelperArray_NativePopWithNoDst)
12383-
{
12384-
LoadScriptContext(instr);
12385-
}
12382+
LoadScriptContext(instr);
1238612383

1238712384
IR::Instr * retInstr = m_lowererMD.ChangeToHelperCall(instr, helperMethod, bailOutLabelHelper);
1238812385

lib/Runtime/Library/JavascriptArray.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4968,15 +4968,22 @@ using namespace Js;
49684968
* PopWithNoDst
49694969
* - For pop calls that do not return a value, we only need to decrement the length of the array.
49704970
*/
4971-
void JavascriptNativeArray::PopWithNoDst(Var nativeArray)
4971+
void JavascriptNativeArray::PopWithNoDst(ScriptContext* scriptContext, Var nativeArray)
49724972
{
4973-
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Array_NativePopWithNoDst);
4973+
JIT_HELPER_REENTRANT_HEADER(Array_NativePopWithNoDst);
49744974
Assert(VarIs<JavascriptNativeArray>(nativeArray));
49754975
JavascriptArray * arr = VarTo<JavascriptArray>(nativeArray);
49764976

49774977
// we will bailout on length 0
49784978
Assert(arr->GetLength() != 0);
49794979

4980+
// Check for SparseArray and also has array in prototype chain
4981+
if (JavascriptArray::HasAnyES5ArrayInPrototypeChain(arr, false)) {
4982+
// Pop (walk chain) and discard the result
4983+
EntryPopJavascriptArray(scriptContext, arr);
4984+
return;
4985+
}
4986+
49804987
uint32 index = arr->GetLength() - 1;
49814988
arr->SetLength(index);
49824989
JIT_HELPER_END(Array_NativePopWithNoDst);
@@ -5007,6 +5014,7 @@ using namespace Js;
50075014
{
50085015
arr->SetLength(index);
50095016
}
5017+
50105018
return element;
50115019
JIT_HELPER_END(Array_NativeIntPop);
50125020
}

lib/Runtime/Library/JavascriptArray.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ namespace Js
10331033
Var FindMinOrMax(Js::ScriptContext * scriptContext, bool findMax);
10341034
template<typename T, bool checkNaNAndNegZero> Var FindMinOrMax(Js::ScriptContext * scriptContext, bool findMax); // NativeInt arrays can't have NaNs or -0
10351035

1036-
static void PopWithNoDst(Var nativeArray);
1036+
static void PopWithNoDst(ScriptContext* scriptContext, Var nativeArray);
10371037
};
10381038

10391039
template <> inline bool VarIsImpl<JavascriptNativeArray>(RecyclableObject* obj)

test/Array/pop4.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Copyright (c) 2022 ChakraCore Project Contributors. All rights reserved.
4+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
5+
//-------------------------------------------------------------------------------------------------------
6+
7+
const array = [1, 2, 3];
8+
9+
const testRuns = 10;
10+
let counter = 0;
11+
12+
let proto = [];
13+
Object.defineProperty(proto, 3, {
14+
get: function () {
15+
counter++;
16+
return 42;
17+
}
18+
});
19+
array.__proto__ = proto;
20+
21+
function hotFunction() {
22+
// Discard return value
23+
array.pop();
24+
}
25+
26+
for (let i = 0; i < testRuns; i++) {
27+
array.length = 4;
28+
hotFunction();
29+
}
30+
31+
if (counter < testRuns) {
32+
throw new Error(`Expected ${testRuns} calls to getter, got ${counter}`);
33+
}
34+
print("pass");

test/Array/pop5.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Copyright (c) 2022 ChakraCore Project Contributors. All rights reserved.
4+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
5+
//-------------------------------------------------------------------------------------------------------
6+
7+
const array = [1, 2, 3];
8+
9+
const testRuns = 10;
10+
let counter = 0;
11+
12+
let proto = [];
13+
Object.defineProperty(proto, 3, {
14+
get: function () {
15+
counter++;
16+
return 42;
17+
}
18+
});
19+
array.__proto__ = proto;
20+
21+
let globalCache = null;
22+
function hotFunction() {
23+
// Store return value
24+
globalCache = array.pop();
25+
}
26+
27+
for (let i = 0; i < testRuns; i++) {
28+
array.length = 4;
29+
hotFunction();
30+
}
31+
32+
if (counter < testRuns) {
33+
throw new Error(`Expected ${testRuns} calls to getter, got ${counter}`);
34+
}
35+
print("pass");

test/Array/rlexe.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,16 @@
239239
<baseline>pop3.baseline</baseline>
240240
</default>
241241
</test>
242+
<test>
243+
<default>
244+
<files>pop4.js</files>
245+
</default>
246+
</test>
247+
<test>
248+
<default>
249+
<files>pop5.js</files>
250+
</default>
251+
</test>
242252
<test>
243253
<default>
244254
<files>push1.js</files>

0 commit comments

Comments
 (0)