Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 39 additions & 73 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -10013,6 +10013,35 @@ static int add_fast_array_element(JSContext *ctx, JSObject *p,
return true;
}

/* Allocate a new fast array initialized to JS_UNDEFINED. Its maximum
size is 2^31-1 elements. For convenience, 'len' is a 64 bit
integer. */
static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len)
{
JSValue arr;
JSObject *p;
int i;

if (len > INT32_MAX)
return JS_ThrowRangeError(ctx, "invalid array length");
arr = JS_NewArray(ctx);
if (JS_IsException(arr))
return arr;
if (len > 0) {
p = JS_VALUE_GET_OBJ(arr);
if (expand_fast_array(ctx, p, len) < 0) {
JS_FreeValue(ctx, arr);
return JS_EXCEPTION;
}
p->u.array.count = len;
for(i = 0; i < len; i++)
p->u.array.u.values[i] = JS_UNDEFINED;
/* update the 'length' field */
set_value(ctx, &p->prop[0].u.value, js_int32(len));
}
return arr;
}

static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc)
{
JS_FreeValue(ctx, desc->getter);
Expand Down Expand Up @@ -41608,11 +41637,6 @@ static JSValue js_array_with(JSContext *ctx, JSValueConst this_val,
if (js_get_length64(ctx, &len, obj))
goto exception;

if (len > UINT32_MAX) {
JS_ThrowRangeError(ctx, "invalid array length");
goto exception;
}

if (JS_ToInt64Sat(ctx, &idx, argv[0]))
goto exception;

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

arr = JS_NewArray(ctx);
arr = js_allocate_fast_array(ctx, len);
if (JS_IsException(arr))
goto exception;

p = JS_VALUE_GET_OBJ(arr);
if (expand_fast_array(ctx, p, len) < 0)
goto exception;
p->u.array.count = len;

i = 0;
pval = p->u.array.u.values;
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
Expand All @@ -41644,21 +41664,14 @@ static JSValue js_array_with(JSContext *ctx, JSValueConst this_val,
} else {
for (; i < idx; i++, pval++)
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
goto fill_and_fail;
goto exception;
*pval = js_dup(argv[1]);
for (i++, pval++; i < len; i++, pval++) {
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
fill_and_fail:
for (; i < len; i++, pval++)
*pval = JS_UNDEFINED;
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
goto exception;
}
}
}

if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0)
goto exception;

ret = arr;
arr = JS_UNDEFINED;

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

if (len > UINT32_MAX) {
JS_ThrowRangeError(ctx, "invalid array length");
goto exception;
}

arr = JS_NewArray(ctx);
arr = js_allocate_fast_array(ctx, len);
if (JS_IsException(arr))
goto exception;

if (len > 0) {
p = JS_VALUE_GET_OBJ(arr);
if (expand_fast_array(ctx, p, len) < 0)
goto exception;
p->u.array.count = len;

i = len - 1;
pval = p->u.array.u.values;
Expand All @@ -42591,17 +42596,10 @@ static JSValue js_array_toReversed(JSContext *ctx, JSValueConst this_val,
} else {
// Query order is observable; test262 expects descending order.
for (; i >= 0; i--, pval++) {
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
// Exception; initialize remaining elements.
for (; i >= 0; i--, pval++)
*pval = JS_UNDEFINED;
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
goto exception;
}
}
}

if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0)
goto exception;
}

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

pval = NULL;
last = NULL;
ret = JS_EXCEPTION;
arr = JS_UNDEFINED;

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

newlen = len + add - del;
if (newlen > UINT32_MAX) {
// Per spec: TypeError if newlen >= 2**53, RangeError below
if (newlen > MAX_SAFE_INTEGER) {
JS_ThrowTypeError(ctx, "invalid array length");
} else {
JS_ThrowRangeError(ctx, "invalid array length");
}
if (newlen > MAX_SAFE_INTEGER) {
JS_ThrowTypeError(ctx, "invalid array length");
goto exception;
}

arr = JS_NewArray(ctx);
arr = js_allocate_fast_array(ctx, newlen);
if (JS_IsException(arr))
goto exception;

if (newlen <= 0)
goto done;

p = JS_VALUE_GET_OBJ(arr);
if (expand_fast_array(ctx, p, newlen) < 0)
goto exception;

p->u.array.count = newlen;
pval = &p->u.array.u.values[0];
last = &p->u.array.u.values[newlen];

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

assert(pval == last);

if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(newlen)) < 0)
goto exception;

done:
ret = arr;
arr = JS_UNDEFINED;

exception:
while (pval != last)
*pval++ = JS_UNDEFINED;

JS_FreeValue(ctx, arr);
JS_FreeValue(ctx, obj);
return ret;
Expand Down Expand Up @@ -43141,38 +43122,23 @@ static JSValue js_array_toSorted(JSContext *ctx, JSValueConst this_val,
if (js_get_length64(ctx, &len, obj))
goto exception;

if (len > UINT32_MAX) {
JS_ThrowRangeError(ctx, "invalid array length");
goto exception;
}

arr = JS_NewArray(ctx);
arr = js_allocate_fast_array(ctx, len);
if (JS_IsException(arr))
goto exception;

if (len > 0) {
p = JS_VALUE_GET_OBJ(arr);
if (expand_fast_array(ctx, p, len) < 0)
goto exception;
p->u.array.count = len;

i = 0;
pval = p->u.array.u.values;
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
for (; i < len; i++, pval++)
*pval = js_dup(arrp[i]);
} else {
for (; i < len; i++, pval++) {
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) {
for (; i < len; i++, pval++)
*pval = JS_UNDEFINED;
if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval))
goto exception;
}
}
}

if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0)
goto exception;
}

ret = js_array_sort(ctx, arr, argc, argv);
Expand Down
69 changes: 69 additions & 0 deletions tests/test_array_to_gc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as std from "qjs:std";
import { assert } from "./assert.js";

// The non-mutating array methods (with, toReversed, toSpliced, toSorted)
// allocate a fast array and then populate it by pulling values from the
// source via JS_TryGetPropertyInt64. When the source is an array-like with
// a property getter that triggers GC, the GC must see fully initialized
// slots — otherwise it would walk uninitialized JSValues in the newly
// allocated array. Run under ASAN to catch regressions.

function makeArrayLike(length, getterIndex) {
const obj = { length };
Object.defineProperty(obj, getterIndex, {
configurable: true,
get() {
std.gc();
return 1;
},
});
return obj;
}

// with
{
const obj = makeArrayLike(256, 0);
Object.defineProperty(obj, 1, {
value: 2,
writable: true,
configurable: true,
});
const res = Array.prototype.with.call(obj, 1, 9);
assert(res.length, 256);
assert(res[0], 1);
assert(res[1], 9);
assert(res[2], undefined);
assert(res[255], undefined);
}

// toReversed
{
const obj = makeArrayLike(256, 255);
const res = Array.prototype.toReversed.call(obj);
assert(res.length, 256);
assert(res[0], 1);
assert(res[1], undefined);
assert(res[255], undefined);
}

// toSpliced
{
const obj = makeArrayLike(256, 0);
const res = Array.prototype.toSpliced.call(obj, 1, 0, 7);
assert(res.length, 257);
assert(res[0], 1);
assert(res[1], 7);
assert(res[2], undefined);
assert(res[256], undefined);
}

// toSorted
{
const obj = makeArrayLike(256, 0);
const res = Array.prototype.toSorted.call(obj);
assert(res.length, 256);
// Sort places `undefined` at the end, so the single defined value (1) is first.
assert(res[0], 1);
assert(res[1], undefined);
assert(res[255], undefined);
}
Loading