Skip to content

Commit 64e64eb

Browse files
committed
Port js_allocate_fast_array helper for non-mutating array methods
Ported from bellard/quickjs: - bellard/quickjs@feefdb1 - bellard/quickjs@4c722ce
1 parent f269eb7 commit 64e64eb

2 files changed

Lines changed: 108 additions & 73 deletions

File tree

quickjs.c

Lines changed: 39 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10013,6 +10013,35 @@ static int add_fast_array_element(JSContext *ctx, JSObject *p,
1001310013
return true;
1001410014
}
1001510015

10016+
/* Allocate a new fast array initialized to JS_UNDEFINED. Its maximum
10017+
size is 2^31-1 elements. For convenience, 'len' is a 64 bit
10018+
integer. */
10019+
static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len)
10020+
{
10021+
JSValue arr;
10022+
JSObject *p;
10023+
int i;
10024+
10025+
if (len > INT32_MAX)
10026+
return JS_ThrowRangeError(ctx, "invalid array length");
10027+
arr = JS_NewArray(ctx);
10028+
if (JS_IsException(arr))
10029+
return arr;
10030+
if (len > 0) {
10031+
p = JS_VALUE_GET_OBJ(arr);
10032+
if (expand_fast_array(ctx, p, len) < 0) {
10033+
JS_FreeValue(ctx, arr);
10034+
return JS_EXCEPTION;
10035+
}
10036+
p->u.array.count = len;
10037+
for(i = 0; i < len; i++)
10038+
p->u.array.u.values[i] = JS_UNDEFINED;
10039+
/* update the 'length' field */
10040+
set_value(ctx, &p->prop[0].u.value, js_int32(len));
10041+
}
10042+
return arr;
10043+
}
10044+
1001610045
static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc)
1001710046
{
1001810047
JS_FreeValue(ctx, desc->getter);
@@ -41608,11 +41637,6 @@ static JSValue js_array_with(JSContext *ctx, JSValueConst this_val,
4160841637
if (js_get_length64(ctx, &len, obj))
4160941638
goto exception;
4161041639

41611-
if (len > UINT32_MAX) {
41612-
JS_ThrowRangeError(ctx, "invalid array length");
41613-
goto exception;
41614-
}
41615-
4161641640
if (JS_ToInt64Sat(ctx, &idx, argv[0]))
4161741641
goto exception;
4161841642

@@ -41624,15 +41648,11 @@ static JSValue js_array_with(JSContext *ctx, JSValueConst this_val,
4162441648
goto exception;
4162541649
}
4162641650

41627-
arr = JS_NewArray(ctx);
41651+
arr = js_allocate_fast_array(ctx, len);
4162841652
if (JS_IsException(arr))
4162941653
goto exception;
4163041654

4163141655
p = JS_VALUE_GET_OBJ(arr);
41632-
if (expand_fast_array(ctx, p, len) < 0)
41633-
goto exception;
41634-
p->u.array.count = len;
41635-
4163641656
i = 0;
4163741657
pval = p->u.array.u.values;
4163841658
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
@@ -41644,21 +41664,14 @@ static JSValue js_array_with(JSContext *ctx, JSValueConst this_val,
4164441664
} else {
4164541665
for (; i < idx; i++, pval++)
4164641666
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
41647-
goto fill_and_fail;
41667+
goto exception;
4164841668
*pval = js_dup(argv[1]);
4164941669
for (i++, pval++; i < len; i++, pval++) {
41650-
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
41651-
fill_and_fail:
41652-
for (; i < len; i++, pval++)
41653-
*pval = JS_UNDEFINED;
41670+
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
4165441671
goto exception;
41655-
}
4165641672
}
4165741673
}
4165841674

41659-
if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0)
41660-
goto exception;
41661-
4166241675
ret = arr;
4166341676
arr = JS_UNDEFINED;
4166441677

@@ -42568,20 +42581,12 @@ static JSValue js_array_toReversed(JSContext *ctx, JSValueConst this_val,
4256842581
if (js_get_length64(ctx, &len, obj))
4256942582
goto exception;
4257042583

42571-
if (len > UINT32_MAX) {
42572-
JS_ThrowRangeError(ctx, "invalid array length");
42573-
goto exception;
42574-
}
42575-
42576-
arr = JS_NewArray(ctx);
42584+
arr = js_allocate_fast_array(ctx, len);
4257742585
if (JS_IsException(arr))
4257842586
goto exception;
4257942587

4258042588
if (len > 0) {
4258142589
p = JS_VALUE_GET_OBJ(arr);
42582-
if (expand_fast_array(ctx, p, len) < 0)
42583-
goto exception;
42584-
p->u.array.count = len;
4258542590

4258642591
i = len - 1;
4258742592
pval = p->u.array.u.values;
@@ -42591,17 +42596,10 @@ static JSValue js_array_toReversed(JSContext *ctx, JSValueConst this_val,
4259142596
} else {
4259242597
// Query order is observable; test262 expects descending order.
4259342598
for (; i >= 0; i--, pval++) {
42594-
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
42595-
// Exception; initialize remaining elements.
42596-
for (; i >= 0; i--, pval++)
42597-
*pval = JS_UNDEFINED;
42599+
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
4259842600
goto exception;
42599-
}
4260042601
}
4260142602
}
42602-
42603-
if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0)
42604-
goto exception;
4260542603
}
4260642604

4260742605
ret = arr;
@@ -42727,8 +42725,6 @@ static JSValue js_array_toSpliced(JSContext *ctx, JSValueConst this_val,
4272742725
int64_t i, j, len, newlen, start, add, del;
4272842726
uint32_t count32;
4272942727

42730-
pval = NULL;
42731-
last = NULL;
4273242728
ret = JS_EXCEPTION;
4273342729
arr = JS_UNDEFINED;
4273442730

@@ -42753,28 +42749,19 @@ static JSValue js_array_toSpliced(JSContext *ctx, JSValueConst this_val,
4275342749
add = argc - 2;
4275442750

4275542751
newlen = len + add - del;
42756-
if (newlen > UINT32_MAX) {
42757-
// Per spec: TypeError if newlen >= 2**53, RangeError below
42758-
if (newlen > MAX_SAFE_INTEGER) {
42759-
JS_ThrowTypeError(ctx, "invalid array length");
42760-
} else {
42761-
JS_ThrowRangeError(ctx, "invalid array length");
42762-
}
42752+
if (newlen > MAX_SAFE_INTEGER) {
42753+
JS_ThrowTypeError(ctx, "invalid array length");
4276342754
goto exception;
4276442755
}
4276542756

42766-
arr = JS_NewArray(ctx);
42757+
arr = js_allocate_fast_array(ctx, newlen);
4276742758
if (JS_IsException(arr))
4276842759
goto exception;
4276942760

4277042761
if (newlen <= 0)
4277142762
goto done;
4277242763

4277342764
p = JS_VALUE_GET_OBJ(arr);
42774-
if (expand_fast_array(ctx, p, newlen) < 0)
42775-
goto exception;
42776-
42777-
p->u.array.count = newlen;
4277842765
pval = &p->u.array.u.values[0];
4277942766
last = &p->u.array.u.values[newlen];
4278042767

@@ -42798,17 +42785,11 @@ static JSValue js_array_toSpliced(JSContext *ctx, JSValueConst this_val,
4279842785

4279942786
assert(pval == last);
4280042787

42801-
if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(newlen)) < 0)
42802-
goto exception;
42803-
4280442788
done:
4280542789
ret = arr;
4280642790
arr = JS_UNDEFINED;
4280742791

4280842792
exception:
42809-
while (pval != last)
42810-
*pval++ = JS_UNDEFINED;
42811-
4281242793
JS_FreeValue(ctx, arr);
4281342794
JS_FreeValue(ctx, obj);
4281442795
return ret;
@@ -43141,38 +43122,23 @@ static JSValue js_array_toSorted(JSContext *ctx, JSValueConst this_val,
4314143122
if (js_get_length64(ctx, &len, obj))
4314243123
goto exception;
4314343124

43144-
if (len > UINT32_MAX) {
43145-
JS_ThrowRangeError(ctx, "invalid array length");
43146-
goto exception;
43147-
}
43148-
43149-
arr = JS_NewArray(ctx);
43125+
arr = js_allocate_fast_array(ctx, len);
4315043126
if (JS_IsException(arr))
4315143127
goto exception;
4315243128

4315343129
if (len > 0) {
4315443130
p = JS_VALUE_GET_OBJ(arr);
43155-
if (expand_fast_array(ctx, p, len) < 0)
43156-
goto exception;
43157-
p->u.array.count = len;
43158-
4315943131
i = 0;
4316043132
pval = p->u.array.u.values;
4316143133
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
4316243134
for (; i < len; i++, pval++)
4316343135
*pval = js_dup(arrp[i]);
4316443136
} else {
4316543137
for (; i < len; i++, pval++) {
43166-
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
43167-
for (; i < len; i++, pval++)
43168-
*pval = JS_UNDEFINED;
43138+
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
4316943139
goto exception;
43170-
}
4317143140
}
4317243141
}
43173-
43174-
if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0)
43175-
goto exception;
4317643142
}
4317743143

4317843144
ret = js_array_sort(ctx, arr, argc, argv);

tests/test_array_to_gc.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as std from "qjs:std";
2+
import { assert } from "./assert.js";
3+
4+
// The non-mutating array methods (with, toReversed, toSpliced, toSorted)
5+
// allocate a fast array and then populate it by pulling values from the
6+
// source via JS_TryGetPropertyInt64. When the source is an array-like with
7+
// a property getter that triggers GC, the GC must see fully initialized
8+
// slots — otherwise it would walk uninitialized JSValues in the newly
9+
// allocated array. Run under ASAN to catch regressions.
10+
11+
function makeArrayLike(length, getterIndex) {
12+
const obj = { length };
13+
Object.defineProperty(obj, getterIndex, {
14+
configurable: true,
15+
get() {
16+
std.gc();
17+
return 1;
18+
},
19+
});
20+
return obj;
21+
}
22+
23+
// with
24+
{
25+
const obj = makeArrayLike(256, 0);
26+
Object.defineProperty(obj, 1, {
27+
value: 2,
28+
writable: true,
29+
configurable: true,
30+
});
31+
const res = Array.prototype.with.call(obj, 1, 9);
32+
assert(res.length, 256);
33+
assert(res[0], 1);
34+
assert(res[1], 9);
35+
assert(res[2], undefined);
36+
assert(res[255], undefined);
37+
}
38+
39+
// toReversed
40+
{
41+
const obj = makeArrayLike(256, 255);
42+
const res = Array.prototype.toReversed.call(obj);
43+
assert(res.length, 256);
44+
assert(res[0], 1);
45+
assert(res[1], undefined);
46+
assert(res[255], undefined);
47+
}
48+
49+
// toSpliced
50+
{
51+
const obj = makeArrayLike(256, 0);
52+
const res = Array.prototype.toSpliced.call(obj, 1, 0, 7);
53+
assert(res.length, 257);
54+
assert(res[0], 1);
55+
assert(res[1], 7);
56+
assert(res[2], undefined);
57+
assert(res[256], undefined);
58+
}
59+
60+
// toSorted
61+
{
62+
const obj = makeArrayLike(256, 0);
63+
const res = Array.prototype.toSorted.call(obj);
64+
assert(res.length, 256);
65+
// Sort places `undefined` at the end, so the single defined value (1) is first.
66+
assert(res[0], 1);
67+
assert(res[1], undefined);
68+
assert(res[255], undefined);
69+
}

0 commit comments

Comments
 (0)