cosmopolitan/third_party/quickjs/array.c
Justine Tunney ae5d06dc53 Unbloat build config
- 10.5% reduction of o//depend dependency graph
- 8.8% reduction in latency of make command
- Fix issue with temporary file cleanup

There's a new -w option in compile.com that turns off the recent
Landlock output path workaround for "good commands" which do not
unlink() the output file like GNU tooling does.

Our new GNU Make unveil sandboxing appears to have zero overhead
in the grand scheme of things. Full builds are pretty fast since
the only thing that's actually slowed us down is probably libcxx

    make -j16 MODE=rel
    RL: took 85,732,063µs wall time
    RL: ballooned to 323,612kb in size
    RL: needed 828,560,521µs cpu (11% kernel)
    RL: caused 39,080,670 page faults (99% memcpy)
    RL: 350,073 context switches (72% consensual)
    RL: performed 0 reads and 11,494,960 write i/o operations

pledge() and unveil() no longer consider ENOSYS to be an error.
These functions have also been added to Python's cosmo module.

This change also removes some WIN32 APIs and System Five magnums
which we're not using and it's doubtful anyone else would be too
2022-08-10 04:43:09 -07:00

1953 lines
63 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "libc/str/str.h"
#include "third_party/quickjs/internal.h"
asm(".ident\t\"\\n\\n\
QuickJS (MIT License)\\n\
Copyright (c) 2017-2021 Fabrice Bellard\\n\
Copyright (c) 2017-2021 Charlie Gordon\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */
/* Check if an object has a generalized numeric property. Return value:
-1 for exception,
TRUE if property exists, stored into *pval,
FALSE if proprty does not exist.
*/
static int JS_TryGetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, JSValue *pval)
{
JSValue val = JS_UNDEFINED;
JSAtom prop;
int present;
if (LIKELY((uint64_t)idx <= JS_ATOM_MAX_INT)) {
/* fast path */
present = JS_HasProperty(ctx, obj, __JS_AtomFromUInt32(idx));
if (present > 0) {
val = JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx));
if (UNLIKELY(JS_IsException(val)))
present = -1;
}
} else {
prop = JS_NewAtomInt64(ctx, idx);
present = -1;
if (LIKELY(prop != JS_ATOM_NULL)) {
present = JS_HasProperty(ctx, obj, prop);
if (present > 0) {
val = JS_GetProperty(ctx, obj, prop);
if (UNLIKELY(JS_IsException(val)))
present = -1;
}
JS_FreeAtom(ctx, prop);
}
}
*pval = val;
return present;
}
static int JS_DeletePropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, int flags)
{
JSAtom prop;
int res;
if ((uint64_t)idx <= JS_ATOM_MAX_INT) {
/* fast path for fast arrays */
return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags);
}
prop = JS_NewAtomInt64(ctx, idx);
if (prop == JS_ATOM_NULL)
return -1;
res = JS_DeleteProperty(ctx, obj, prop, flags);
JS_FreeAtom(ctx, prop);
return res;
}
static int JS_CopySubArray(JSContext *ctx,
JSValueConst obj, int64_t to_pos,
int64_t from_pos, int64_t count, int dir)
{
int64_t i, from, to;
JSValue val;
int fromPresent;
/* XXX: should special case fast arrays */
for (i = 0; i < count; i++) {
if (dir < 0) {
from = from_pos + count - i - 1;
to = to_pos + count - i - 1;
} else {
from = from_pos + i;
to = to_pos + i;
}
fromPresent = JS_TryGetPropertyInt64(ctx, obj, from, &val);
if (fromPresent < 0)
goto exception;
if (fromPresent) {
if (JS_SetPropertyInt64(ctx, obj, to, val) < 0)
goto exception;
} else {
if (JS_DeletePropertyInt64(ctx, obj, to, JS_PROP_THROW) < 0)
goto exception;
}
}
return 0;
exception:
return -1;
}
JSValue js_array_constructor(JSContext *ctx, JSValueConst new_target,
int argc, JSValueConst *argv)
{
JSValue obj;
int i;
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_ARRAY);
if (JS_IsException(obj))
return obj;
if (argc == 1 && JS_IsNumber(argv[0])) {
uint32_t len;
if (JS_ToArrayLengthFree(ctx, &len, JS_DupValue(ctx, argv[0]), TRUE))
goto fail;
if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewUint32(ctx, len)) < 0)
goto fail;
} else {
for(i = 0; i < argc; i++) {
if (JS_SetPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i])) < 0)
goto fail;
}
}
return obj;
fail:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
JSValue js_array_from(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// from(items, mapfn = void 0, this_arg = void 0)
JSValueConst items = argv[0], mapfn, this_arg;
JSValueConst args[2];
JSValue stack[2];
JSValue iter, r, v, v2, arrayLike;
int64_t k, len;
int done, mapping;
mapping = FALSE;
mapfn = JS_UNDEFINED;
this_arg = JS_UNDEFINED;
r = JS_UNDEFINED;
arrayLike = JS_UNDEFINED;
stack[0] = JS_UNDEFINED;
stack[1] = JS_UNDEFINED;
if (argc > 1) {
mapfn = argv[1];
if (!JS_IsUndefined(mapfn)) {
if (check_function(ctx, mapfn))
goto exception;
mapping = 1;
if (argc > 2)
this_arg = argv[2];
}
}
iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator);
if (JS_IsException(iter))
goto exception;
if (!JS_IsUndefined(iter)) {
JS_FreeValue(ctx, iter);
if (JS_IsConstructor(ctx, this_val))
r = JS_CallConstructor(ctx, this_val, 0, NULL);
else
r = JS_NewArray(ctx);
if (JS_IsException(r))
goto exception;
stack[0] = JS_DupValue(ctx, items);
if (js_for_of_start(ctx, &stack[1], FALSE))
goto exception;
for (k = 0;; k++) {
v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done);
if (JS_IsException(v))
goto exception_close;
if (done)
break;
if (mapping) {
args[0] = v;
args[1] = JS_NewInt32(ctx, k);
v2 = JS_Call(ctx, mapfn, this_arg, 2, args);
JS_FreeValue(ctx, v);
v = v2;
if (JS_IsException(v))
goto exception_close;
}
if (JS_DefinePropertyValueInt64(ctx, r, k, v,
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
goto exception_close;
}
} else {
arrayLike = JS_ToObject(ctx, items);
if (JS_IsException(arrayLike))
goto exception;
if (js_get_length64(ctx, &len, arrayLike) < 0)
goto exception;
v = JS_NewInt64(ctx, len);
args[0] = v;
if (JS_IsConstructor(ctx, this_val)) {
r = JS_CallConstructor(ctx, this_val, 1, args);
} else {
r = js_array_constructor(ctx, JS_UNDEFINED, 1, args);
}
JS_FreeValue(ctx, v);
if (JS_IsException(r))
goto exception;
for(k = 0; k < len; k++) {
v = JS_GetPropertyInt64(ctx, arrayLike, k);
if (JS_IsException(v))
goto exception;
if (mapping) {
args[0] = v;
args[1] = JS_NewInt32(ctx, k);
v2 = JS_Call(ctx, mapfn, this_arg, 2, args);
JS_FreeValue(ctx, v);
v = v2;
if (JS_IsException(v))
goto exception;
}
if (JS_DefinePropertyValueInt64(ctx, r, k, v,
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
goto exception;
}
}
if (JS_SetProperty(ctx, r, JS_ATOM_length, JS_NewUint32(ctx, k)) < 0)
goto exception;
goto done;
exception_close:
if (!JS_IsUndefined(stack[0]))
JS_IteratorClose(ctx, stack[0], TRUE);
exception:
JS_FreeValue(ctx, r);
r = JS_EXCEPTION;
done:
JS_FreeValue(ctx, arrayLike);
JS_FreeValue(ctx, stack[0]);
JS_FreeValue(ctx, stack[1]);
return r;
}
static JSValue js_array_of(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj, args[1];
int i;
if (JS_IsConstructor(ctx, this_val)) {
args[0] = JS_NewInt32(ctx, argc);
obj = JS_CallConstructor(ctx, this_val, 1, (JSValueConst *)args);
} else {
obj = JS_NewArray(ctx);
}
if (JS_IsException(obj))
return JS_EXCEPTION;
for(i = 0; i < argc; i++) {
if (JS_CreateDataPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i]),
JS_PROP_THROW) < 0) {
goto fail;
}
}
if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewUint32(ctx, argc)) < 0) {
fail:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
return obj;
}
static JSValue js_array_isArray(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
int ret;
ret = JS_IsArray(ctx, argv[0]);
if (ret < 0)
return JS_EXCEPTION;
else
return JS_NewBool(ctx, ret);
}
static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj,
JSValueConst len_val)
{
JSValue ctor, ret, species;
int res;
JSContext *realm;
res = JS_IsArray(ctx, obj);
if (res < 0)
return JS_EXCEPTION;
if (!res)
return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val);
ctor = JS_GetProperty(ctx, obj, JS_ATOM_constructor);
if (JS_IsException(ctor))
return ctor;
if (JS_IsConstructor(ctx, ctor)) {
/* legacy web compatibility */
realm = JS_GetFunctionRealm(ctx, ctor);
if (!realm) {
JS_FreeValue(ctx, ctor);
return JS_EXCEPTION;
}
if (realm != ctx &&
js_same_value(ctx, ctor, realm->array_ctor)) {
JS_FreeValue(ctx, ctor);
ctor = JS_UNDEFINED;
}
}
if (JS_IsObject(ctor)) {
species = JS_GetProperty(ctx, ctor, JS_ATOM_Symbol_species);
JS_FreeValue(ctx, ctor);
if (JS_IsException(species))
return species;
ctor = species;
if (JS_IsNull(ctor))
ctor = JS_UNDEFINED;
}
if (JS_IsUndefined(ctor)) {
return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val);
} else {
ret = JS_CallConstructor(ctx, ctor, 1, &len_val);
JS_FreeValue(ctx, ctor);
return ret;
}
}
static const JSCFunctionListEntry js_array_funcs[] = {
JS_CFUNC_DEF("isArray", 1, js_array_isArray ),
JS_CFUNC_DEF("from", 1, js_array_from ),
JS_CFUNC_DEF("of", 0, js_array_of ),
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
};
static int JS_isConcatSpreadable(JSContext *ctx, JSValueConst obj)
{
JSValue val;
if (!JS_IsObject(obj))
return FALSE;
val = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_isConcatSpreadable);
if (JS_IsException(val))
return -1;
if (!JS_IsUndefined(val))
return JS_ToBoolFree(ctx, val);
return JS_IsArray(ctx, obj);
}
static JSValue js_array_concat(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj, arr, val;
JSValueConst e;
int64_t len, k, n;
int i, res;
arr = JS_UNDEFINED;
obj = JS_ToObject(ctx, this_val);
if (JS_IsException(obj))
goto exception;
arr = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0));
if (JS_IsException(arr))
goto exception;
n = 0;
for (i = -1; i < argc; i++) {
if (i < 0)
e = obj;
else
e = argv[i];
res = JS_isConcatSpreadable(ctx, e);
if (res < 0)
goto exception;
if (res) {
if (js_get_length64(ctx, &len, e))
goto exception;
if (n + len > MAX_SAFE_INTEGER) {
JS_ThrowTypeError(ctx, "Array loo long");
goto exception;
}
for (k = 0; k < len; k++, n++) {
res = JS_TryGetPropertyInt64(ctx, e, k, &val);
if (res < 0)
goto exception;
if (res) {
if (JS_DefinePropertyValueInt64(ctx, arr, n, val,
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
goto exception;
}
}
} else {
if (n >= MAX_SAFE_INTEGER) {
JS_ThrowTypeError(ctx, "Array loo long");
goto exception;
}
if (JS_DefinePropertyValueInt64(ctx, arr, n, JS_DupValue(ctx, e),
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
goto exception;
n++;
}
}
if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0)
goto exception;
JS_FreeValue(ctx, obj);
return arr;
exception:
JS_FreeValue(ctx, arr);
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
JSValue js_array_every(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int special)
{
JSValue obj, val, index_val, res, ret;
JSValueConst args[3];
JSValueConst func, this_arg;
int64_t len, k, n;
int present;
ret = JS_UNDEFINED;
val = JS_UNDEFINED;
if (special & special_TA) {
obj = JS_DupValue(ctx, this_val);
len = js_typed_array_get_length_internal(ctx, obj);
if (len < 0)
goto exception;
} else {
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
}
func = argv[0];
this_arg = JS_UNDEFINED;
if (argc > 1)
this_arg = argv[1];
if (check_function(ctx, func))
goto exception;
switch (special) {
case special_every:
case special_every | special_TA:
ret = JS_TRUE;
break;
case special_some:
case special_some | special_TA:
ret = JS_FALSE;
break;
case special_map:
/* XXX: JS_ArraySpeciesCreate should take int64_t */
ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt64(ctx, len));
if (JS_IsException(ret))
goto exception;
break;
case special_filter:
ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0));
if (JS_IsException(ret))
goto exception;
break;
case special_map | special_TA:
args[0] = obj;
args[1] = JS_NewInt32(ctx, len);
ret = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
if (JS_IsException(ret))
goto exception;
break;
case special_filter | special_TA:
ret = JS_NewArray(ctx);
if (JS_IsException(ret))
goto exception;
break;
}
n = 0;
for(k = 0; k < len; k++) {
if (special & special_TA) {
val = JS_GetPropertyInt64(ctx, obj, k);
if (JS_IsException(val))
goto exception;
present = TRUE;
} else {
present = JS_TryGetPropertyInt64(ctx, obj, k, &val);
if (present < 0)
goto exception;
}
if (present) {
index_val = JS_NewInt64(ctx, k);
if (JS_IsException(index_val))
goto exception;
args[0] = val;
args[1] = index_val;
args[2] = obj;
res = JS_Call(ctx, func, this_arg, 3, args);
JS_FreeValue(ctx, index_val);
if (JS_IsException(res))
goto exception;
switch (special) {
case special_every:
case special_every | special_TA:
if (!JS_ToBoolFree(ctx, res)) {
ret = JS_FALSE;
goto done;
}
break;
case special_some:
case special_some | special_TA:
if (JS_ToBoolFree(ctx, res)) {
ret = JS_TRUE;
goto done;
}
break;
case special_map:
if (JS_DefinePropertyValueInt64(ctx, ret, k, res,
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
goto exception;
break;
case special_map | special_TA:
if (JS_SetPropertyValue(ctx, ret, JS_NewInt32(ctx, k), res, JS_PROP_THROW) < 0)
goto exception;
break;
case special_filter:
case special_filter | special_TA:
if (JS_ToBoolFree(ctx, res)) {
if (JS_DefinePropertyValueInt64(ctx, ret, n++, JS_DupValue(ctx, val),
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
goto exception;
}
break;
default:
JS_FreeValue(ctx, res);
break;
}
JS_FreeValue(ctx, val);
val = JS_UNDEFINED;
}
}
done:
if (special == (special_filter | special_TA)) {
JSValue arr;
args[0] = obj;
args[1] = JS_NewInt32(ctx, n);
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
if (JS_IsException(arr))
goto exception;
args[0] = ret;
res = JS_Invoke(ctx, arr, JS_ATOM_set, 1, args);
if (check_exception_free(ctx, res))
goto exception;
JS_FreeValue(ctx, ret);
ret = arr;
}
JS_FreeValue(ctx, val);
JS_FreeValue(ctx, obj);
return ret;
exception:
JS_FreeValue(ctx, ret);
JS_FreeValue(ctx, val);
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
JSValue js_array_reduce(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int special)
{
JSValue obj, val, index_val, acc, acc1;
JSValueConst args[4];
JSValueConst func;
int64_t len, k, k1;
int present;
acc = JS_UNDEFINED;
val = JS_UNDEFINED;
if (special & special_TA) {
obj = JS_DupValue(ctx, this_val);
len = js_typed_array_get_length_internal(ctx, obj);
if (len < 0)
goto exception;
} else {
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
}
func = argv[0];
if (check_function(ctx, func))
goto exception;
k = 0;
if (argc > 1) {
acc = JS_DupValue(ctx, argv[1]);
} else {
for(;;) {
if (k >= len) {
JS_ThrowTypeError(ctx, "empty array");
goto exception;
}
k1 = (special & special_reduceRight) ? len - k - 1 : k;
k++;
if (special & special_TA) {
acc = JS_GetPropertyInt64(ctx, obj, k1);
if (JS_IsException(acc))
goto exception;
break;
} else {
present = JS_TryGetPropertyInt64(ctx, obj, k1, &acc);
if (present < 0)
goto exception;
if (present)
break;
}
}
}
for (; k < len; k++) {
k1 = (special & special_reduceRight) ? len - k - 1 : k;
if (special & special_TA) {
val = JS_GetPropertyInt64(ctx, obj, k1);
if (JS_IsException(val))
goto exception;
present = TRUE;
} else {
present = JS_TryGetPropertyInt64(ctx, obj, k1, &val);
if (present < 0)
goto exception;
}
if (present) {
index_val = JS_NewInt64(ctx, k1);
if (JS_IsException(index_val))
goto exception;
args[0] = acc;
args[1] = val;
args[2] = index_val;
args[3] = obj;
acc1 = JS_Call(ctx, func, JS_UNDEFINED, 4, args);
JS_FreeValue(ctx, index_val);
JS_FreeValue(ctx, val);
val = JS_UNDEFINED;
if (JS_IsException(acc1))
goto exception;
JS_FreeValue(ctx, acc);
acc = acc1;
}
}
JS_FreeValue(ctx, obj);
return acc;
exception:
JS_FreeValue(ctx, acc);
JS_FreeValue(ctx, val);
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static JSValue js_array_fill(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj;
int64_t len, start, end;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
start = 0;
if (argc > 1 && !JS_IsUndefined(argv[1])) {
if (JS_ToInt64Clamp(ctx, &start, argv[1], 0, len, len))
goto exception;
}
end = len;
if (argc > 2 && !JS_IsUndefined(argv[2])) {
if (JS_ToInt64Clamp(ctx, &end, argv[2], 0, len, len))
goto exception;
}
/* XXX: should special case fast arrays */
while (start < end) {
if (JS_SetPropertyInt64(ctx, obj, start,
JS_DupValue(ctx, argv[0])) < 0)
goto exception;
start++;
}
return obj;
exception:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
/* Access an Array's internal JSValue array if available */
BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj,
JSValue **arrpp, uint32_t *countp)
{
/* Try and handle fast arrays explicitly */
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
JSObject *p = JS_VALUE_GET_OBJ(obj);
if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
*countp = p->u.array.count;
*arrpp = p->u.array.u.values;
return TRUE;
}
}
return FALSE;
}
JSValue js_array_includes(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj, val;
int64_t len, n, res;
JSValue *arrp;
uint32_t count;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
res = FALSE;
if (len > 0) {
n = 0;
if (argc > 1) {
if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len))
goto exception;
}
if (js_get_fast_array(ctx, obj, &arrp, &count)) {
for (; n < count; n++) {
if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]),
JS_DupValue(ctx, arrp[n]),
JS_EQ_SAME_VALUE_ZERO)) {
res = TRUE;
goto done;
}
}
}
for (; n < len; n++) {
val = JS_GetPropertyInt64(ctx, obj, n);
if (JS_IsException(val))
goto exception;
if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val,
JS_EQ_SAME_VALUE_ZERO)) {
res = TRUE;
break;
}
}
}
done:
JS_FreeValue(ctx, obj);
return JS_NewBool(ctx, res);
exception:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static JSValue js_array_indexOf(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj, val;
int64_t len, n, res;
JSValue *arrp;
uint32_t count;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
res = -1;
if (len > 0) {
n = 0;
if (argc > 1) {
if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len))
goto exception;
}
if (js_get_fast_array(ctx, obj, &arrp, &count)) {
for (; n < count; n++) {
if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]),
JS_DupValue(ctx, arrp[n]), JS_EQ_STRICT)) {
res = n;
goto done;
}
}
}
for (; n < len; n++) {
int present = JS_TryGetPropertyInt64(ctx, obj, n, &val);
if (present < 0)
goto exception;
if (present) {
if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) {
res = n;
break;
}
}
}
}
done:
JS_FreeValue(ctx, obj);
return JS_NewInt64(ctx, res);
exception:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static JSValue js_array_lastIndexOf(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj, val;
int64_t len, n, res;
int present;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
res = -1;
if (len > 0) {
n = len - 1;
if (argc > 1) {
if (JS_ToInt64Clamp(ctx, &n, argv[1], -1, len - 1, len))
goto exception;
}
/* XXX: should special case fast arrays */
for (; n >= 0; n--) {
present = JS_TryGetPropertyInt64(ctx, obj, n, &val);
if (present < 0)
goto exception;
if (present) {
if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) {
res = n;
break;
}
}
}
}
JS_FreeValue(ctx, obj);
return JS_NewInt64(ctx, res);
exception:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static JSValue js_array_find(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int findIndex)
{
JSValueConst func, this_arg;
JSValueConst args[3];
JSValue obj, val, index_val, res;
int64_t len, k;
index_val = JS_UNDEFINED;
val = JS_UNDEFINED;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
func = argv[0];
if (check_function(ctx, func))
goto exception;
this_arg = JS_UNDEFINED;
if (argc > 1)
this_arg = argv[1];
for(k = 0; k < len; k++) {
index_val = JS_NewInt64(ctx, k);
if (JS_IsException(index_val))
goto exception;
val = JS_GetPropertyValue(ctx, obj, index_val);
if (JS_IsException(val))
goto exception;
args[0] = val;
args[1] = index_val;
args[2] = this_val;
res = JS_Call(ctx, func, this_arg, 3, args);
if (JS_IsException(res))
goto exception;
if (JS_ToBoolFree(ctx, res)) {
if (findIndex) {
JS_FreeValue(ctx, val);
JS_FreeValue(ctx, obj);
return index_val;
} else {
JS_FreeValue(ctx, index_val);
JS_FreeValue(ctx, obj);
return val;
}
}
JS_FreeValue(ctx, val);
JS_FreeValue(ctx, index_val);
}
JS_FreeValue(ctx, obj);
if (findIndex)
return JS_NewInt32(ctx, -1);
else
return JS_UNDEFINED;
exception:
JS_FreeValue(ctx, index_val);
JS_FreeValue(ctx, val);
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static JSValue js_array_toString(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj, method, ret;
obj = JS_ToObject(ctx, this_val);
if (JS_IsException(obj))
return JS_EXCEPTION;
method = JS_GetProperty(ctx, obj, JS_ATOM_join);
if (JS_IsException(method)) {
ret = JS_EXCEPTION;
} else
if (!JS_IsFunction(ctx, method)) {
/* Use intrinsic Object.prototype.toString */
JS_FreeValue(ctx, method);
ret = js_object_toString(ctx, obj, 0, NULL);
} else {
ret = JS_CallFree(ctx, method, obj, 0, NULL);
}
JS_FreeValue(ctx, obj);
return ret;
}
static JSValue js_array_join(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int toLocaleString)
{
JSValue obj, sep = JS_UNDEFINED, el;
StringBuffer b_s, *b = &b_s;
JSString *p = NULL;
int64_t i, n;
int c;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &n, obj))
goto exception;
c = ','; /* default separator */
if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) {
sep = JS_ToString(ctx, argv[0]);
if (JS_IsException(sep))
goto exception;
p = JS_VALUE_GET_STRING(sep);
if (p->len == 1 && !p->is_wide_char)
c = p->u.str8[0];
else
c = -1;
}
string_buffer_init(ctx, b, 0);
for(i = 0; i < n; i++) {
if (i > 0) {
if (c >= 0) {
string_buffer_putc8(b, c);
} else {
string_buffer_concat(b, p, 0, p->len);
}
}
el = JS_GetPropertyUint32(ctx, obj, i);
if (JS_IsException(el))
goto fail;
if (!JS_IsNull(el) && !JS_IsUndefined(el)) {
if (toLocaleString) {
el = JS_ToLocaleStringFree(ctx, el);
}
if (string_buffer_concat_value_free(b, el))
goto fail;
}
}
JS_FreeValue(ctx, sep);
JS_FreeValue(ctx, obj);
return string_buffer_end(b);
fail:
string_buffer_free(b);
JS_FreeValue(ctx, sep);
exception:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
JSValue js_array_pop(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int shift)
{
JSValue obj, res = JS_UNDEFINED;
int64_t len, newLen;
JSValue *arrp;
uint32_t count32;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
newLen = 0;
if (len > 0) {
newLen = len - 1;
/* Special case fast arrays */
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
JSObject *p = JS_VALUE_GET_OBJ(obj);
if (shift) {
res = arrp[0];
memmove(arrp, arrp + 1, (count32 - 1) * sizeof(*arrp));
p->u.array.count--;
} else {
res = arrp[count32 - 1];
p->u.array.count--;
}
} else {
if (shift) {
res = JS_GetPropertyInt64(ctx, obj, 0);
if (JS_IsException(res))
goto exception;
if (JS_CopySubArray(ctx, obj, 0, 1, len - 1, +1))
goto exception;
} else {
res = JS_GetPropertyInt64(ctx, obj, newLen);
if (JS_IsException(res))
goto exception;
}
if (JS_DeletePropertyInt64(ctx, obj, newLen, JS_PROP_THROW) < 0)
goto exception;
}
}
if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0)
goto exception;
JS_FreeValue(ctx, obj);
return res;
exception:
JS_FreeValue(ctx, res);
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
/* return -1 if exception */
static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len)
{
uint32_t new_size;
size_t slack;
JSValue *new_array_prop;
/* XXX: potential arithmetic overflow */
new_size = max_int(new_len, p->u.array.u1.size * 3 / 2);
new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack);
if (!new_array_prop)
return -1;
new_size += slack / sizeof(*new_array_prop);
p->u.array.u.values = new_array_prop;
p->u.array.u1.size = new_size;
return 0;
}
/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array =
TRUE and p->extensible = TRUE */
int add_fast_array_element(JSContext *ctx, JSObject *p, JSValue val, int flags)
{
uint32_t new_len, array_len;
/* extend the array by one */
/* XXX: convert to slow array if new_len > 2^31-1 elements */
new_len = p->u.array.count + 1;
/* update the length if necessary. We assume that if the length is
not an integer, then if it >= 2^31. */
if (LIKELY(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) {
array_len = JS_VALUE_GET_INT(p->prop[0].u.value);
if (new_len > array_len) {
if (UNLIKELY(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) {
JS_FreeValue(ctx, val);
return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
}
p->prop[0].u.value = JS_NewInt32(ctx, new_len);
}
}
if (UNLIKELY(new_len > p->u.array.u1.size)) {
if (expand_fast_array(ctx, p, new_len)) {
JS_FreeValue(ctx, val);
return -1;
}
}
p->u.array.u.values[new_len - 1] = val;
p->u.array.count = new_len;
return TRUE;
}
JSValue js_array_push(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int unshift)
{
JSValue obj;
int i;
int64_t len, from, newLen;
obj = JS_ToObject(ctx, this_val);
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
JSObject *p = JS_VALUE_GET_OBJ(obj);
if (p->class_id != JS_CLASS_ARRAY ||
!p->fast_array || !p->extensible)
goto generic_case;
/* length must be writable */
if (UNLIKELY(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE)))
goto generic_case;
/* check the length */
if (UNLIKELY(JS_VALUE_GET_TAG(p->prop[0].u.value) != JS_TAG_INT))
goto generic_case;
len = JS_VALUE_GET_INT(p->prop[0].u.value);
/* we don't support holes */
if (UNLIKELY(len != p->u.array.count))
goto generic_case;
newLen = len + argc;
if (UNLIKELY(newLen > INT32_MAX))
goto generic_case;
if (newLen > p->u.array.u1.size) {
if (expand_fast_array(ctx, p, newLen))
goto exception;
}
if (unshift && argc > 0) {
memmove(p->u.array.u.values + argc, p->u.array.u.values,
len * sizeof(p->u.array.u.values[0]));
from = 0;
} else {
from = len;
}
for(i = 0; i < argc; i++) {
p->u.array.u.values[from + i] = JS_DupValue(ctx, argv[i]);
}
p->u.array.count = newLen;
p->prop[0].u.value = JS_NewInt32(ctx, newLen);
} else {
generic_case:
if (js_get_length64(ctx, &len, obj))
goto exception;
newLen = len + argc;
if (newLen > MAX_SAFE_INTEGER) {
JS_ThrowTypeError(ctx, "Array loo long");
goto exception;
}
from = len;
if (unshift && argc > 0) {
if (JS_CopySubArray(ctx, obj, argc, 0, len, -1))
goto exception;
from = 0;
}
for(i = 0; i < argc; i++) {
if (JS_SetPropertyInt64(ctx, obj, from + i,
JS_DupValue(ctx, argv[i])) < 0)
goto exception;
}
if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0)
goto exception;
}
JS_FreeValue(ctx, obj);
return JS_NewInt64(ctx, newLen);
exception:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static JSValue js_array_reverse(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj, lval, hval;
JSValue *arrp;
int64_t len, l, h;
int l_present, h_present;
uint32_t count32;
lval = JS_UNDEFINED;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
/* Special case fast arrays */
if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
uint32_t ll, hh;
if (count32 > 1) {
for (ll = 0, hh = count32 - 1; ll < hh; ll++, hh--) {
lval = arrp[ll];
arrp[ll] = arrp[hh];
arrp[hh] = lval;
}
}
return obj;
}
for (l = 0, h = len - 1; l < h; l++, h--) {
l_present = JS_TryGetPropertyInt64(ctx, obj, l, &lval);
if (l_present < 0)
goto exception;
h_present = JS_TryGetPropertyInt64(ctx, obj, h, &hval);
if (h_present < 0)
goto exception;
if (h_present) {
if (JS_SetPropertyInt64(ctx, obj, l, hval) < 0)
goto exception;
if (l_present) {
if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) {
lval = JS_UNDEFINED;
goto exception;
}
lval = JS_UNDEFINED;
} else {
if (JS_DeletePropertyInt64(ctx, obj, h, JS_PROP_THROW) < 0)
goto exception;
}
} else {
if (l_present) {
if (JS_DeletePropertyInt64(ctx, obj, l, JS_PROP_THROW) < 0)
goto exception;
if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) {
lval = JS_UNDEFINED;
goto exception;
}
lval = JS_UNDEFINED;
}
}
}
return obj;
exception:
JS_FreeValue(ctx, lval);
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static BOOL js_is_fast_array(JSContext *ctx, JSValueConst obj)
{
/* Try and handle fast arrays explicitly */
if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) {
JSObject *p = JS_VALUE_GET_OBJ(obj);
if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
return TRUE;
}
}
return FALSE;
}
static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int splice)
{
JSValue obj, arr, val, len_val;
int64_t len, start, k, final, n, count, del_count, new_len;
int kPresent;
JSValue *arrp;
uint32_t count32, i, item_count;
arr = JS_UNDEFINED;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len))
goto exception;
if (splice) {
if (argc == 0) {
item_count = 0;
del_count = 0;
} else
if (argc == 1) {
item_count = 0;
del_count = len - start;
} else {
item_count = argc - 2;
if (JS_ToInt64Clamp(ctx, &del_count, argv[1], 0, len - start, 0))
goto exception;
}
if (len + item_count - del_count > MAX_SAFE_INTEGER) {
JS_ThrowTypeError(ctx, "Array loo long");
goto exception;
}
count = del_count;
} else {
item_count = 0; /* avoid warning */
final = len;
if (!JS_IsUndefined(argv[1])) {
if (JS_ToInt64Clamp(ctx, &final, argv[1], 0, len, len))
goto exception;
}
count = max_int64(final - start, 0);
}
len_val = JS_NewInt64(ctx, count);
arr = JS_ArraySpeciesCreate(ctx, obj, len_val);
JS_FreeValue(ctx, len_val);
if (JS_IsException(arr))
goto exception;
k = start;
final = start + count;
n = 0;
/* The fast array test on arr ensures that
JS_CreateDataPropertyUint32() won't modify obj in case arr is
an exotic object */
/* Special case fast arrays */
if (js_get_fast_array(ctx, obj, &arrp, &count32) &&
js_is_fast_array(ctx, arr)) {
/* XXX: should share code with fast array constructor */
for (; k < final && k < count32; k++, n++) {
if (JS_CreateDataPropertyUint32(ctx, arr, n, JS_DupValue(ctx, arrp[k]), JS_PROP_THROW) < 0)
goto exception;
}
}
/* Copy the remaining elements if any (handle case of inherited properties) */
for (; k < final; k++, n++) {
kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val);
if (kPresent < 0)
goto exception;
if (kPresent) {
if (JS_CreateDataPropertyUint32(ctx, arr, n, val, JS_PROP_THROW) < 0)
goto exception;
}
}
if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0)
goto exception;
if (splice) {
new_len = len + item_count - del_count;
if (item_count != del_count) {
if (JS_CopySubArray(ctx, obj, start + item_count,
start + del_count, len - (start + del_count),
item_count <= del_count ? +1 : -1) < 0)
goto exception;
for (k = len; k-- > new_len; ) {
if (JS_DeletePropertyInt64(ctx, obj, k, JS_PROP_THROW) < 0)
goto exception;
}
}
for (i = 0; i < item_count; i++) {
if (JS_SetPropertyInt64(ctx, obj, start + i, JS_DupValue(ctx, argv[i + 2])) < 0)
goto exception;
}
if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, new_len)) < 0)
goto exception;
}
JS_FreeValue(ctx, obj);
return arr;
exception:
JS_FreeValue(ctx, obj);
JS_FreeValue(ctx, arr);
return JS_EXCEPTION;
}
static JSValue js_array_copyWithin(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj;
int64_t len, from, to, final, count;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
if (JS_ToInt64Clamp(ctx, &to, argv[0], 0, len, len))
goto exception;
if (JS_ToInt64Clamp(ctx, &from, argv[1], 0, len, len))
goto exception;
final = len;
if (argc > 2 && !JS_IsUndefined(argv[2])) {
if (JS_ToInt64Clamp(ctx, &final, argv[2], 0, len, len))
goto exception;
}
count = min_int64(final - from, len - to);
if (JS_CopySubArray(ctx, obj, to, from, count,
(from < to && to < from + count) ? -1 : +1))
goto exception;
return obj;
exception:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static int64_t JS_FlattenIntoArray(JSContext *ctx, JSValueConst target,
JSValueConst source, int64_t sourceLen,
int64_t targetIndex, int depth,
JSValueConst mapperFunction,
JSValueConst thisArg)
{
JSValue element;
int64_t sourceIndex, elementLen;
int present, is_array;
if (js_check_stack_overflow(ctx->rt, 0)) {
JS_ThrowStackOverflow(ctx);
return -1;
}
for (sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) {
present = JS_TryGetPropertyInt64(ctx, source, sourceIndex, &element);
if (present < 0)
return -1;
if (!present)
continue;
if (!JS_IsUndefined(mapperFunction)) {
JSValueConst args[3] = { element, JS_NewInt64(ctx, sourceIndex), source };
element = JS_Call(ctx, mapperFunction, thisArg, 3, args);
JS_FreeValue(ctx, (JSValue)args[0]);
JS_FreeValue(ctx, (JSValue)args[1]);
if (JS_IsException(element))
return -1;
}
if (depth > 0) {
is_array = JS_IsArray(ctx, element);
if (is_array < 0)
goto fail;
if (is_array) {
if (js_get_length64(ctx, &elementLen, element) < 0)
goto fail;
targetIndex = JS_FlattenIntoArray(ctx, target, element,
elementLen, targetIndex,
depth - 1,
JS_UNDEFINED, JS_UNDEFINED);
if (targetIndex < 0)
goto fail;
JS_FreeValue(ctx, element);
continue;
}
}
if (targetIndex >= MAX_SAFE_INTEGER) {
JS_ThrowTypeError(ctx, "Array too long");
goto fail;
}
if (JS_DefinePropertyValueInt64(ctx, target, targetIndex, element,
JS_PROP_C_W_E | JS_PROP_THROW) < 0)
return -1;
targetIndex++;
}
return targetIndex;
fail:
JS_FreeValue(ctx, element);
return -1;
}
static JSValue js_array_flatten(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int map)
{
JSValue obj, arr;
JSValueConst mapperFunction, thisArg;
int64_t sourceLen;
int depthNum;
arr = JS_UNDEFINED;
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &sourceLen, obj))
goto exception;
depthNum = 1;
mapperFunction = JS_UNDEFINED;
thisArg = JS_UNDEFINED;
if (map) {
mapperFunction = argv[0];
if (argc > 1) {
thisArg = argv[1];
}
if (check_function(ctx, mapperFunction))
goto exception;
} else {
if (argc > 0 && !JS_IsUndefined(argv[0])) {
if (JS_ToInt32Sat(ctx, &depthNum, argv[0]) < 0)
goto exception;
}
}
arr = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0));
if (JS_IsException(arr))
goto exception;
if (JS_FlattenIntoArray(ctx, arr, obj, sourceLen, 0, depthNum,
mapperFunction, thisArg) < 0)
goto exception;
JS_FreeValue(ctx, obj);
return arr;
exception:
JS_FreeValue(ctx, obj);
JS_FreeValue(ctx, arr);
return JS_EXCEPTION;
}
typedef struct ValueSlot {
JSValue val;
JSString *str;
int64_t pos;
} ValueSlot;
struct array_sort_context {
JSContext *ctx;
int exception;
int has_method;
JSValueConst method;
};
static int js_array_cmp_generic(const void *a, const void *b, void *opaque) {
struct array_sort_context *psc = opaque;
JSContext *ctx = psc->ctx;
JSValueConst argv[2];
JSValue res;
ValueSlot *ap = (ValueSlot *)(void *)a;
ValueSlot *bp = (ValueSlot *)(void *)b;
int cmp;
if (psc->exception)
return 0;
if (psc->has_method) {
/* custom sort function is specified as returning 0 for identical
* objects: avoid method call overhead.
*/
if (!memcmp(&ap->val, &bp->val, sizeof(ap->val)))
goto cmp_same;
argv[0] = ap->val;
argv[1] = bp->val;
res = JS_Call(ctx, psc->method, JS_UNDEFINED, 2, argv);
if (JS_IsException(res))
goto exception;
if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) {
int val = JS_VALUE_GET_INT(res);
cmp = (val > 0) - (val < 0);
} else {
double val;
if (JS_ToFloat64Free(ctx, &val, res) < 0)
goto exception;
cmp = (val > 0) - (val < 0);
}
} else {
/* Not supposed to bypass ToString even for identical objects as
* tested in test262/test/built-ins/Array/prototype/sort/bug_596_1.js
*/
if (!ap->str) {
JSValue str = JS_ToString(ctx, ap->val);
if (JS_IsException(str))
goto exception;
ap->str = JS_VALUE_GET_STRING(str);
}
if (!bp->str) {
JSValue str = JS_ToString(ctx, bp->val);
if (JS_IsException(str))
goto exception;
bp->str = JS_VALUE_GET_STRING(str);
}
cmp = js_string_compare(ctx, ap->str, bp->str);
}
if (cmp != 0)
return cmp;
cmp_same:
/* make sort stable: compare array offsets */
return (ap->pos > bp->pos) - (ap->pos < bp->pos);
exception:
psc->exception = 1;
return 0;
}
static JSValue js_array_sort(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
struct array_sort_context asc = { ctx, 0, 0, argv[0] };
JSValue obj = JS_UNDEFINED;
ValueSlot *array = NULL;
size_t array_size = 0, pos = 0, n = 0;
int64_t i, len, undefined_count = 0;
int present;
if (!JS_IsUndefined(asc.method)) {
if (check_function(ctx, asc.method))
goto exception;
asc.has_method = 1;
}
obj = JS_ToObject(ctx, this_val);
if (js_get_length64(ctx, &len, obj))
goto exception;
/* XXX: should special case fast arrays */
for (i = 0; i < len; i++) {
if (pos >= array_size) {
size_t new_size, slack;
ValueSlot *new_array;
new_size = (array_size + (array_size >> 1) + 31) & ~15;
new_array = js_realloc2(ctx, array, new_size * sizeof(*array), &slack);
if (new_array == NULL)
goto exception;
new_size += slack / sizeof(*new_array);
array = new_array;
array_size = new_size;
}
present = JS_TryGetPropertyInt64(ctx, obj, i, &array[pos].val);
if (present < 0)
goto exception;
if (present == 0)
continue;
if (JS_IsUndefined(array[pos].val)) {
undefined_count++;
continue;
}
array[pos].str = NULL;
array[pos].pos = i;
pos++;
}
rqsort(array, pos, sizeof(*array), js_array_cmp_generic, &asc);
if (asc.exception)
goto exception;
/* XXX: should special case fast arrays */
while (n < pos) {
if (array[n].str)
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str));
if (array[n].pos == n) {
JS_FreeValue(ctx, array[n].val);
} else {
if (JS_SetPropertyInt64(ctx, obj, n, array[n].val) < 0) {
n++;
goto exception;
}
}
n++;
}
js_free(ctx, array);
for (i = n; undefined_count-- > 0; i++) {
if (JS_SetPropertyInt64(ctx, obj, i, JS_UNDEFINED) < 0)
goto fail;
}
for (; i < len; i++) {
if (JS_DeletePropertyInt64(ctx, obj, i, JS_PROP_THROW) < 0)
goto fail;
}
return obj;
exception:
for (; n < pos; n++) {
JS_FreeValue(ctx, array[n].val);
if (array[n].str)
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str));
}
js_free(ctx, array);
fail:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
void js_array_iterator_finalizer(JSRuntime *rt, JSValue val)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
JSArrayIteratorData *it = p->u.array_iterator_data;
if (it) {
JS_FreeValueRT(rt, it->obj);
js_free_rt(rt, it);
}
}
void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
JSArrayIteratorData *it = p->u.array_iterator_data;
if (it) {
JS_MarkValue(rt, it->obj, mark_func);
}
}
JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab)
{
JSValue obj;
int i;
obj = JS_NewArray(ctx);
if (JS_IsException(obj))
return JS_EXCEPTION;
for(i = 0; i < len; i++) {
if (JS_CreateDataPropertyUint32(ctx, obj, i, JS_DupValue(ctx, tab[i]), 0) < 0) {
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
}
return obj;
}
JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic)
{
JSValue enum_obj, arr;
JSArrayIteratorData *it;
JSIteratorKindEnum kind;
int class_id;
kind = magic & 3;
if (magic & 4) {
/* string iterator case */
arr = JS_ToStringCheckObject(ctx, this_val);
class_id = JS_CLASS_STRING_ITERATOR;
} else {
arr = JS_ToObject(ctx, this_val);
class_id = JS_CLASS_ARRAY_ITERATOR;
}
if (JS_IsException(arr))
goto fail;
enum_obj = JS_NewObjectClass(ctx, class_id);
if (JS_IsException(enum_obj))
goto fail;
it = js_malloc(ctx, sizeof(*it));
if (!it)
goto fail1;
it->obj = arr;
it->kind = kind;
it->idx = 0;
JS_SetOpaque(enum_obj, it);
return enum_obj;
fail1:
JS_FreeValue(ctx, enum_obj);
fail:
JS_FreeValue(ctx, arr);
return JS_EXCEPTION;
}
JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
BOOL *pdone, int magic)
{
JSArrayIteratorData *it;
uint32_t len, idx;
JSValue val, obj;
JSObject *p;
it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_ITERATOR);
if (!it)
goto fail1;
if (JS_IsUndefined(it->obj))
goto done;
p = JS_VALUE_GET_OBJ(it->obj);
if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
if (typed_array_is_detached(ctx, p)) {
JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
goto fail1;
}
len = p->u.array.count;
} else {
if (js_get_length32(ctx, &len, it->obj)) {
fail1:
*pdone = FALSE;
return JS_EXCEPTION;
}
}
idx = it->idx;
if (idx >= len) {
JS_FreeValue(ctx, it->obj);
it->obj = JS_UNDEFINED;
done:
*pdone = TRUE;
return JS_UNDEFINED;
}
it->idx = idx + 1;
*pdone = FALSE;
if (it->kind == JS_ITERATOR_KIND_KEY) {
return JS_NewUint32(ctx, idx);
} else {
val = JS_GetPropertyUint32(ctx, it->obj, idx);
if (JS_IsException(val))
return JS_EXCEPTION;
if (it->kind == JS_ITERATOR_KIND_VALUE) {
return val;
} else {
JSValueConst args[2];
JSValue num;
num = JS_NewUint32(ctx, idx);
args[0] = num;
args[1] = val;
obj = js_create_array(ctx, 2, args);
JS_FreeValue(ctx, val);
JS_FreeValue(ctx, num);
return obj;
}
}
}
static const JSCFunctionListEntry js_array_proto_funcs[] = {
JS_CFUNC_DEF("concat", 1, js_array_concat ),
JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every ),
JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some ),
JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach ),
JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map ),
JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter ),
JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce ),
JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight ),
JS_CFUNC_DEF("fill", 1, js_array_fill ),
JS_CFUNC_MAGIC_DEF("find", 1, js_array_find, 0 ),
JS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, 1 ),
JS_CFUNC_DEF("indexOf", 1, js_array_indexOf ),
JS_CFUNC_DEF("lastIndexOf", 1, js_array_lastIndexOf ),
JS_CFUNC_DEF("includes", 1, js_array_includes ),
JS_CFUNC_MAGIC_DEF("join", 1, js_array_join, 0 ),
JS_CFUNC_DEF("toString", 0, js_array_toString ),
JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_array_join, 1 ),
JS_CFUNC_MAGIC_DEF("pop", 0, js_array_pop, 0 ),
JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ),
JS_CFUNC_MAGIC_DEF("shift", 0, js_array_pop, 1 ),
JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ),
JS_CFUNC_DEF("reverse", 0, js_array_reverse ),
JS_CFUNC_DEF("sort", 1, js_array_sort ),
JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ),
JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ),
JS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin ),
JS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1 ),
JS_CFUNC_MAGIC_DEF("flat", 0, js_array_flatten, 0 ),
JS_CFUNC_MAGIC_DEF("values", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE ),
JS_ALIAS_DEF("[Symbol.iterator]", "values" ),
JS_CFUNC_MAGIC_DEF("keys", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY ),
JS_CFUNC_MAGIC_DEF("entries", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ),
};
void JS_AddIntrinsicArray(JSContext *ctx) {
JSValueConst obj;
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY],
js_array_proto_funcs,
countof(js_array_proto_funcs));
obj = JS_NewGlobalCConstructor(ctx, "Array", js_array_constructor, 1,
ctx->class_proto[JS_CLASS_ARRAY]);
ctx->array_ctor = JS_DupValue(ctx, obj);
JS_SetPropertyFunctionList(ctx, obj, js_array_funcs,
countof(js_array_funcs));
}
static const JSCFunctionListEntry js_array_iterator_proto_funcs[] = {
JS_ITERATOR_NEXT_DEF("next", 0, js_array_iterator_next, 0 ),
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Array Iterator", JS_PROP_CONFIGURABLE ),
};
void JS_AddArrayIteratorProto(JSContext *ctx) {
/* needed to initialize arguments[Symbol.iterator] */
ctx->array_proto_values =
JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_values);
ctx->class_proto[JS_CLASS_ARRAY_ITERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto);
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_ITERATOR],
js_array_iterator_proto_funcs,
countof(js_array_iterator_proto_funcs));
}
int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, JSValue val, BOOL is_array_ctor)
{
uint32_t tag, len;
tag = JS_VALUE_GET_TAG(val);
switch(tag) {
case JS_TAG_INT:
case JS_TAG_BOOL:
case JS_TAG_NULL:
{
int v;
v = JS_VALUE_GET_INT(val);
if (v < 0)
goto fail;
len = v;
}
break;
#ifdef CONFIG_BIGNUM
case JS_TAG_BIG_INT:
case JS_TAG_BIG_FLOAT:
{
JSBigFloat *p = JS_VALUE_GET_PTR(val);
bf_t a;
BOOL res;
bf_get_int32((int32_t *)&len, &p->num, BF_GET_INT_MOD);
bf_init(ctx->bf_ctx, &a);
bf_set_ui(&a, len);
res = bf_cmp_eq(&a, &p->num);
bf_delete(&a);
JS_FreeValue(ctx, val);
if (!res)
goto fail;
}
break;
#endif
default:
if (JS_TAG_IS_FLOAT64(tag)) {
double d;
d = JS_VALUE_GET_FLOAT64(val);
len = (uint32_t)d;
if (len != d)
goto fail;
} else {
uint32_t len1;
if (is_array_ctor) {
val = JS_ToNumberFree(ctx, val);
if (JS_IsException(val))
return -1;
/* cannot recurse because val is a number */
if (JS_ToArrayLengthFree(ctx, &len, val, TRUE))
return -1;
} else {
/* legacy behavior: must do the conversion twice and compare */
if (JS_ToUint32(ctx, &len, val)) {
JS_FreeValue(ctx, val);
return -1;
}
val = JS_ToNumberFree(ctx, val);
if (JS_IsException(val))
return -1;
/* cannot recurse because val is a number */
if (JS_ToArrayLengthFree(ctx, &len1, val, FALSE))
return -1;
if (len1 != len) {
fail:
JS_ThrowRangeError(ctx, "invalid array length");
return -1;
}
}
}
break;
}
*plen = len;
return 0;
}
/* set the array length and remove the array elements if necessary. */
int set_array_length(JSContext *ctx, JSObject *p, JSValue val, int flags)
{
uint32_t len, idx, cur_len;
int i, ret;
/* Note: this call can reallocate the properties of 'p' */
ret = JS_ToArrayLengthFree(ctx, &len, val, FALSE);
if (ret)
return -1;
/* JS_ToArrayLengthFree() must be done before the read-only test */
if (UNLIKELY(!(p->shape->prop[0].flags & JS_PROP_WRITABLE)))
return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
if (LIKELY(p->fast_array)) {
uint32_t old_len = p->u.array.count;
if (len < old_len) {
for(i = len; i < old_len; i++) {
JS_FreeValue(ctx, p->u.array.u.values[i]);
}
p->u.array.count = len;
}
p->prop[0].u.value = JS_NewUint32(ctx, len);
} else {
/* Note: length is always a uint32 because the object is an
array */
JS_ToUint32(ctx, &cur_len, p->prop[0].u.value);
if (len < cur_len) {
uint32_t d;
JSShape *sh;
JSShapeProperty *pr;
d = cur_len - len;
sh = p->shape;
if (d <= sh->prop_count) {
JSAtom atom;
/* faster to iterate */
while (cur_len > len) {
atom = JS_NewAtomUInt32(ctx, cur_len - 1);
ret = delete_property(ctx, p, atom);
JS_FreeAtom(ctx, atom);
if (UNLIKELY(!ret)) {
/* UNLIKELY case: property is not
configurable */
break;
}
cur_len--;
}
} else {
/* faster to iterate thru all the properties. Need two
passes in case one of the property is not
configurable */
cur_len = len;
for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count;
i++, pr++) {
if (pr->atom != JS_ATOM_NULL &&
JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) {
if (idx >= cur_len &&
!(pr->flags & JS_PROP_CONFIGURABLE)) {
cur_len = idx + 1;
}
}
}
for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count;
i++, pr++) {
if (pr->atom != JS_ATOM_NULL &&
JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) {
if (idx >= cur_len) {
/* remove the property */
delete_property(ctx, p, pr->atom);
/* WARNING: the shape may have been modified */
sh = p->shape;
pr = get_shape_prop(sh) + i;
}
}
}
}
} else {
cur_len = len;
}
set_value(ctx, &p->prop[0].u.value, JS_NewUint32(ctx, cur_len));
if (UNLIKELY(cur_len > len)) {
return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable");
}
}
return TRUE;
}
void js_array_finalizer(JSRuntime *rt, JSValue val)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
int i;
for(i = 0; i < p->u.array.count; i++) {
JS_FreeValueRT(rt, p->u.array.u.values[i]);
}
js_free_rt(rt, p->u.array.u.values);
}
void js_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
int i;
for(i = 0; i < p->u.array.count; i++) {
JS_MarkValue(rt, p->u.array.u.values[i], mark_func);
}
}