/* * 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\""); /* 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) { JSObject *p; int64_t i, from, to, len; JSValue val; int fromPresent; p = NULL; if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { p = JS_VALUE_GET_OBJ(obj); if (p->class_id != JS_CLASS_ARRAY || !p->fast_array) { p = NULL; } } for(i = 0; i < count; ) { if (dir < 0) { from = from_pos + count - i - 1; to = to_pos + count - i - 1; } else { from = from_pos + i; to = to_pos + i; } if (p && p->fast_array && from >= 0 && from < (len = p->u.array.count) && to >= 0 && to < len) { int64_t l, j; /* Fast path for fast arrays. Since we don't look at the prototype chain, we can optimize only the cases where all the elements are present in the array. */ l = count - i; if (dir < 0) { l = min_int64(l, from + 1); l = min_int64(l, to + 1); for(j = 0; j < l; j++) { set_value(ctx, &p->u.array.u.values[to - j], JS_DupValue(ctx, p->u.array.u.values[from - j])); } } else { l = min_int64(l, len - from); l = min_int64(l, len - to); for(j = 0; j < l; j++) { set_value(ctx, &p->u.array.u.values[to + j], JS_DupValue(ctx, p->u.array.u.values[from + j])); } } i += l; } else { 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; } i++; } } 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_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); } }