diff --git a/src/ant.c b/src/ant.c index a1498a8..659c1e5 100644 --- a/src/ant.c +++ b/src/ant.c @@ -5706,10 +5706,54 @@ static ant_value_t builtin_function_apply(ant_t *js, ant_value_t *args, int narg return result; } +static bool proxy_callable_target(ant_value_t value) { + ant_value_t slow = value; + ant_value_t fast = value; + + while (true) { + uint8_t t = vtype(slow); + if (t == T_FUNC || t == T_CFUNC) return true; + if (t != T_OBJ) return false; + + ant_proxy_state_t *data = get_proxy_data(slow); + if (!data || data->revoked) return false; + slow = data->target; + + for (int i = 0; i < 2; i++) { + if (vtype(fast) != T_OBJ) { fast = js_mknull(); break; } + ant_proxy_state_t *fdata = get_proxy_data(fast); + if (!fdata || fdata->revoked) { fast = js_mknull(); break; } + fast = fdata->target; + } + + if (same_object_identity(slow, fast)) return false; + } +} + +static ant_value_t builtin_bound_proxy_call(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t bound = js->current_func; + if (vtype(bound) != T_FUNC) return js_mkerr_typed(js, JS_ERR_TYPE, "invalid bound proxy call"); + + ant_value_t target = js_get_slot(js_func_obj(bound), SLOT_TARGET_FUNC); + if (vtype(target) == T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "invalid bound proxy target"); + + if (vtype(js->new_target) != T_UNDEF) { + ant_value_t ctor_this = js->this_val; + return sv_vm_call(js->vm, js, target, js->this_val, args, nargs, &ctor_this, true); + } + + return sv_vm_call(js->vm, js, target, js->this_val, args, nargs, NULL, false); +} + static ant_value_t builtin_function_bind(ant_t *js, ant_value_t *args, int nargs) { ant_value_t func = js->this_val; + uint8_t func_type = vtype(func); + bool func_is_proxy = func_type == T_OBJ && is_proxy(func); - if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) { + if (func_type != T_FUNC && func_type != T_CFUNC && !func_is_proxy) { + return js_mkerr_typed(js, JS_ERR_TYPE, "bind requires a function"); + } + if (func_is_proxy && !proxy_callable_target(func)) { return js_mkerr_typed(js, JS_ERR_TYPE, "bind requires a function"); } @@ -5720,7 +5764,13 @@ static ant_value_t builtin_function_bind(ant_t *js, ant_value_t *args, int nargs int orig_length = 0; ant_value_t target_func_obj; - if (vtype(func) == T_CFUNC) orig_length = 0; + if (func_is_proxy) { + ant_value_t has_length = proxy_has_own(js, func, js->length_str); + if (is_err(has_length)) return has_length; + ant_value_t len_val = proxy_get(js, func, "length", 6); + if (is_err(len_val)) return len_val; + if (vtype(len_val) == T_NUM) orig_length = (int) tod(len_val); + } else if (func_type == T_CFUNC) orig_length = 0; else { target_func_obj = js_func_obj(func); ant_value_t len_val = lkp_interned_val(js, target_func_obj, js->intern.length); @@ -5733,7 +5783,10 @@ static ant_value_t builtin_function_bind(ant_t *js, ant_value_t *args, int nargs const char *target_name = ""; size_t target_name_len = 0; ant_value_t target_name_val = js_mkundef(); - if (vtype(func) == T_CFUNC) { + if (func_is_proxy) { + target_name_val = proxy_get(js, func, "name", 4); + if (is_err(target_name_val)) return target_name_val; + } else if (func_type == T_CFUNC) { const ant_cfunc_meta_t *meta = js_as_cfunc_meta(func); if (meta && meta->name) { target_name = meta->name; @@ -5749,7 +5802,40 @@ static ant_value_t builtin_function_bind(ant_t *js, ant_value_t *args, int nargs target_name_len = (size_t)nlen; } - if (vtype(func) == T_CFUNC) { + if (func_is_proxy) { + ant_value_t bound_func = mkobj(js, 0); + if (is_err(bound_func)) return bound_func; + + set_slot(bound_func, SLOT_CFUNC, js_mkfun(builtin_bound_proxy_call)); + set_slot(bound_func, SLOT_TARGET_FUNC, func); + + ant_value_t func_proto = get_slot(js_glob(js), SLOT_FUNC_PROTO); + if (vtype(func_proto) == T_FUNC) js_set_proto_init(bound_func, func_proto); + + ant_value_t bound = js_obj_to_func_ex(bound_func, bound_argc > 0 ? SV_CALL_HAS_BOUND_ARGS : 0); + sv_closure_t *bc = js_func_closure(bound); + bc->bound_this = this_arg; + if (bound_argc > 0) { + bc->bound_argv = malloc(sizeof(ant_value_t) * (size_t)bound_argc); + if (!bc->bound_argv) return js_mkerr(js, "oom"); + memcpy(bc->bound_argv, bound_args, sizeof(ant_value_t) * (size_t)bound_argc); + bc->bound_argc = bound_argc; + } + + js_setprop(js, bound_func, js->length_str, tov((double) bound_length)); + ant_value_t name_result = js_set_function_name_prefixed( + js, bound_func, "bound ", 6, target_name, target_name_len + ); + if (is_err(name_result)) return name_result; + + ant_value_t proto_setup = setup_func_prototype(js, bound); + if (is_err(proto_setup)) return proto_setup; + js_mark_constructor(bound_func, js_is_constructor(func)); + + return bound; + } + + if (func_type == T_CFUNC) { ant_value_t bound_func = mkobj(js, 0); if (is_err(bound_func)) return bound_func; -- 2.51.2 From be37dc875acd6e1f3b9939bee9c2933ada616946 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 13:31:25 -0700 Subject: [PATCH 02/16] proxy fixing pass --- examples/results.txt | 113 ++-- include/internal.h | 1 + src/ant.c | 1195 ++++++++++++++++++++++++++++++------- src/modules/json.c | 8 +- src/modules/regex.c | 30 +- src/silver/compiler.c | 3 +- src/silver/ops/coercion.h | 67 ++- 7 files changed, 1129 insertions(+), 288 deletions(-) diff --git a/examples/results.txt b/examples/results.txt index 85954b3..98b6f5d 100644 --- a/examples/results.txt +++ b/examples/results.txt @@ -588,36 +588,36 @@ compat-table/es6/Promise.js: OK compat-table/es6/Promise.prototype-not-instance.js: OK compat-table/es6/Promise.race.iterable.js: OK compat-table/es6/Promise.race.js: OK -compat-table/es6/Proxy.Array.isArray.js: failed -compat-table/es6/Proxy.JSON.stringify.js: failed +compat-table/es6/Proxy.Array.isArray.js: OK +compat-table/es6/Proxy.JSON.stringify.js: OK compat-table/es6/Proxy.constructor-requires-new.js: OK compat-table/es6/Proxy.handler.apply.invariants.js: OK compat-table/es6/Proxy.handler.apply.js: OK compat-table/es6/Proxy.handler.construct.invariants.js: OK compat-table/es6/Proxy.handler.construct.js: OK -compat-table/es6/Proxy.handler.defineProperty.invariants.js: failed +compat-table/es6/Proxy.handler.defineProperty.invariants.js: OK compat-table/es6/Proxy.handler.defineProperty.js: OK compat-table/es6/Proxy.handler.deleteProperty.invariants.js: OK compat-table/es6/Proxy.handler.deleteProperty.js: OK -compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.invariants.js: failed -compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.js: failed -compat-table/es6/Proxy.handler.getPrototypeOf.invariants.js: failed +compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.invariants.js: OK +compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.js: OK +compat-table/es6/Proxy.handler.getPrototypeOf.invariants.js: OK compat-table/es6/Proxy.handler.getPrototypeOf.js: OK -compat-table/es6/Proxy.handler.get.instances.js: failed +compat-table/es6/Proxy.handler.get.instances.js: OK compat-table/es6/Proxy.handler.get.invariants.js: OK compat-table/es6/Proxy.handler.get.js: OK -compat-table/es6/Proxy.handler.has.instances.js: failed +compat-table/es6/Proxy.handler.has.instances.js: OK compat-table/es6/Proxy.handler.has.invariants.js: OK compat-table/es6/Proxy.handler.has.js: OK -compat-table/es6/Proxy.handler.isExtensible.invariants.js: failed -compat-table/es6/Proxy.handler.isExtensible.js: failed -compat-table/es6/Proxy.handler.ownKeys.invariants.js: failed +compat-table/es6/Proxy.handler.isExtensible.invariants.js: OK +compat-table/es6/Proxy.handler.isExtensible.js: OK +compat-table/es6/Proxy.handler.ownKeys.invariants.js: OK compat-table/es6/Proxy.handler.ownKeys.js: OK -compat-table/es6/Proxy.handler.preventExtensions.invariants.js: failed -compat-table/es6/Proxy.handler.preventExtensions.js: failed -compat-table/es6/Proxy.handler.setPrototypeOf.invariants.js: failed -compat-table/es6/Proxy.handler.setPrototypeOf.js: failed -compat-table/es6/Proxy.handler.set.instances.js: failed +compat-table/es6/Proxy.handler.preventExtensions.invariants.js: OK +compat-table/es6/Proxy.handler.preventExtensions.js: OK +compat-table/es6/Proxy.handler.setPrototypeOf.invariants.js: OK +compat-table/es6/Proxy.handler.setPrototypeOf.js: OK +compat-table/es6/Proxy.handler.set.instances.js: OK compat-table/es6/Proxy.handler.set.invariants.js: OK compat-table/es6/Proxy.handler.set.js: OK compat-table/es6/Proxy.no-prototype.js: OK @@ -952,37 +952,37 @@ compat-table/es6/misc.Object.isSealed.primitives.js: OK compat-table/es6/misc.Object.keys.primitives.js: OK compat-table/es6/misc.Object.preventExtensions.primitives.js: OK compat-table/es6/misc.Object.seal.primitives.js: OK -compat-table/es6/misc.Proxy.defineProperty.SetIntegrityLevel.js: failed -compat-table/es6/misc.Proxy.defineProperty.Set.js: failed -compat-table/es6/misc.Proxy.deleteProperty.Array.copyWithin.js: failed -compat-table/es6/misc.Proxy.deleteProperty.Array.pop.js: failed -compat-table/es6/misc.Proxy.deleteProperty.Array.reverse.js: failed -compat-table/es6/misc.Proxy.deleteProperty.Array.shift.js: failed -compat-table/es6/misc.Proxy.deleteProperty.Array.splice.js: failed -compat-table/es6/misc.Proxy.deleteProperty.Array.unshift.js: failed -compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Function.bind.js: TypeError: bind requires a function -compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Object.assign.js: failed +compat-table/es6/misc.Proxy.defineProperty.SetIntegrityLevel.js: OK +compat-table/es6/misc.Proxy.defineProperty.Set.js: OK +compat-table/es6/misc.Proxy.deleteProperty.Array.copyWithin.js: OK +compat-table/es6/misc.Proxy.deleteProperty.Array.pop.js: OK +compat-table/es6/misc.Proxy.deleteProperty.Array.reverse.js: OK +compat-table/es6/misc.Proxy.deleteProperty.Array.shift.js: OK +compat-table/es6/misc.Proxy.deleteProperty.Array.splice.js: OK +compat-table/es6/misc.Proxy.deleteProperty.Array.unshift.js: OK +compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Function.bind.js: OK +compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Object.assign.js: OK compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Object.hasOwnProperty.js: OK -compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Set.js: failed +compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Set.js: OK compat-table/es6/misc.Proxy.get.Array.concat.js: OK -compat-table/es6/misc.Proxy.get.Array.from.js: failed -compat-table/es6/misc.Proxy.get.Array.iteration.js: failed -compat-table/es6/misc.Proxy.get.Array.pop.js: failed -compat-table/es6/misc.Proxy.get.Array.reverse.js: failed -compat-table/es6/misc.Proxy.get.Array.shift.js: failed -compat-table/es6/misc.Proxy.get.Array.splice.js: failed +compat-table/es6/misc.Proxy.get.Array.from.js: OK +compat-table/es6/misc.Proxy.get.Array.iteration.js: OK +compat-table/es6/misc.Proxy.get.Array.pop.js: OK +compat-table/es6/misc.Proxy.get.Array.reverse.js: OK +compat-table/es6/misc.Proxy.get.Array.shift.js: OK +compat-table/es6/misc.Proxy.get.Array.splice.js: OK compat-table/es6/misc.Proxy.get.Array.toString.js: OK compat-table/es6/misc.Proxy.get.ClassDefinitionEvaluation.js: OK -compat-table/es6/misc.Proxy.get.CreateDynamicFunction.js: failed -compat-table/es6/misc.Proxy.get.CreateListFromArrayLike.js: failed +compat-table/es6/misc.Proxy.get.CreateDynamicFunction.js: OK +compat-table/es6/misc.Proxy.get.CreateListFromArrayLike.js: OK compat-table/es6/misc.Proxy.get.Date.toJSON.js: OK compat-table/es6/misc.Proxy.get.Error.toString.js: OK -compat-table/es6/misc.Proxy.get.Function.bind.js: TypeError: bind requires a function -compat-table/es6/misc.Proxy.get.HasBinding.js: failed +compat-table/es6/misc.Proxy.get.Function.bind.js: OK +compat-table/es6/misc.Proxy.get.HasBinding.js: OK compat-table/es6/misc.Proxy.get.IteratorComplete.js: OK compat-table/es6/misc.Proxy.get.JSON.stringify.js: OK -compat-table/es6/misc.Proxy.get.Object.assign.js: failed -compat-table/es6/misc.Proxy.get.Object.defineProperties.js: failed +compat-table/es6/misc.Proxy.get.Object.assign.js: OK +compat-table/es6/misc.Proxy.get.Object.defineProperties.js: OK compat-table/es6/misc.Proxy.get.Promise.resolve.js: OK compat-table/es6/misc.Proxy.get.RegExp-constructor.js: OK compat-table/es6/misc.Proxy.get.RegExp.Symbol.match.js: OK @@ -992,17 +992,17 @@ compat-table/es6/misc.Proxy.get.RegExp.Symbol.split.js: OK compat-table/es6/misc.Proxy.get.RegExp.flags.js: OK compat-table/es6/misc.Proxy.get.RegExp.test.js: OK compat-table/es6/misc.Proxy.get.RegExp.toString.js: OK -compat-table/es6/misc.Proxy.get.String.match.js: failed +compat-table/es6/misc.Proxy.get.String.match.js: OK compat-table/es6/misc.Proxy.get.String.raw.js: OK -compat-table/es6/misc.Proxy.get.String.replace.js: failed -compat-table/es6/misc.Proxy.get.String.search.js: failed -compat-table/es6/misc.Proxy.get.String.split.js: failed +compat-table/es6/misc.Proxy.get.String.replace.js: OK +compat-table/es6/misc.Proxy.get.String.search.js: OK +compat-table/es6/misc.Proxy.get.String.split.js: OK compat-table/es6/misc.Proxy.get.ToPrimitive.js: OK -compat-table/es6/misc.Proxy.get.ToPropertyDescriptor.js: failed +compat-table/es6/misc.Proxy.get.ToPropertyDescriptor.js: OK compat-table/es6/misc.Proxy.get.instanceof.js: OK -compat-table/es6/misc.Proxy.ownKeys.SerializeJSONObject.js: failed -compat-table/es6/misc.Proxy.ownKeys.SetIntegrityLevel.js: failed -compat-table/es6/misc.Proxy.ownKeys.TestIntegrityLevel.js: failed +compat-table/es6/misc.Proxy.ownKeys.SerializeJSONObject.js: OK +compat-table/es6/misc.Proxy.ownKeys.SetIntegrityLevel.js: OK +compat-table/es6/misc.Proxy.ownKeys.TestIntegrityLevel.js: OK compat-table/es6/misc.Proxy.set.Array.copyWithin.js: OK compat-table/es6/misc.Proxy.set.Array.fill.js: OK compat-table/es6/misc.Proxy.set.Array.from.js: OK @@ -1476,7 +1476,6 @@ compat-table/next/AsyncIterator.from.async-iterable.js: OK compat-table/next/AsyncIterator.from.iterable.js: OK compat-table/next/AsyncIterator.from.iterator.js: OK compat-table/next/AsyncIterator.instanceof.js: OK -compat-table/next/AsyncIterator.prototype.Symbol.toStringTag.js: OK compat-table/next/AsyncIterator.prototype.drop.js: OK compat-table/next/AsyncIterator.prototype.every.js: OK compat-table/next/AsyncIterator.prototype.filter.js: OK @@ -1486,29 +1485,27 @@ compat-table/next/AsyncIterator.prototype.forEach.js: OK compat-table/next/AsyncIterator.prototype.map.js: OK compat-table/next/AsyncIterator.prototype.reduce.js: OK compat-table/next/AsyncIterator.prototype.some.js: OK +compat-table/next/AsyncIterator.prototype.Symbol.toStringTag.js: OK compat-table/next/AsyncIterator.prototype.take.js: OK compat-table/next/AsyncIterator.prototype.toArray.js: OK +compat-table/next/await-using.for-of.js: OK +compat-table/next/await-using.js: OK compat-table/next/DisposableStack.js: OK compat-table/next/Map.prototype.upsert.js: OK compat-table/next/RegExp.1-9.js: OK compat-table/next/RegExp.lastMatch.js: OK -compat-table/next/ShadowRealm.js: failed compat-table/next/SuppressedError.js: OK compat-table/next/Symbol.dispose.bound.js: OK +compat-table/next/throw-expr.arrow.js: OK +compat-table/next/throw-expr.conditional.js: OK +compat-table/next/throw-expr.logical.js: OK +compat-table/next/throw-expr.param-init.js: OK compat-table/next/Uint8Array.fromBase64.js: OK compat-table/next/Uint8Array.fromHex.js: OK compat-table/next/Uint8Array.setFromBase64.js: OK compat-table/next/Uint8Array.setFromHex.js: OK compat-table/next/Uint8Array.toBase64.js: OK compat-table/next/Uint8Array.toHex.js: OK -compat-table/next/WeakMap.prototype.upsert.js: OK -compat-table/next/await-using.for-of.js: OK -compat-table/next/await-using.js: OK -compat-table/next/class-decorators.js: failed -compat-table/next/function.sent.js: failed -compat-table/next/throw-expr.arrow.js: OK -compat-table/next/throw-expr.conditional.js: OK -compat-table/next/throw-expr.logical.js: OK -compat-table/next/throw-expr.param-init.js: OK compat-table/next/using.for-of.js: OK compat-table/next/using.js: OK +compat-table/next/WeakMap.prototype.upsert.js: OK diff --git a/include/internal.h b/include/internal.h index 7a7f3d0..c4c8786 100644 --- a/include/internal.h +++ b/include/internal.h @@ -475,6 +475,7 @@ ant_value_t js_maybe_set_function_name_from_key( ); bool is_proxy(ant_value_t obj); +bool is_array_value(ant_value_t value); bool strict_eq_values(ant_t *js, ant_value_t l, ant_value_t r); bool js_deep_equal(ant_t *js, ant_value_t a, ant_value_t b, bool strict); diff --git a/src/ant.c b/src/ant.c index 659c1e5..0b62733 100644 --- a/src/ant.c +++ b/src/ant.c @@ -589,6 +589,9 @@ static ant_value_t make_data_cfunc( static ant_value_t proxy_read_target(ant_t *js, ant_value_t obj); static ant_offset_t proxy_aware_length(ant_t *js, ant_value_t obj); static ant_value_t proxy_aware_get_elem(ant_t *js, ant_value_t obj, const char *key, size_t key_len); +static ant_value_t proxy_delete_index(ant_t *js, ant_value_t obj, ant_offset_t idx); +static bool array_method_has_index(ant_t *js, ant_value_t arr, ant_offset_t idx); +static ant_value_t array_method_get_index(ant_t *js, ant_value_t arr, ant_offset_t idx); static ant_offset_t get_dense_buf(ant_value_t arr); static ant_offset_t dense_capacity(ant_offset_t doff); @@ -622,9 +625,16 @@ static inline bool is_slot_prop(ant_offset_t header); static inline ant_offset_t next_prop(ant_offset_t header); static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs); +static ant_value_t proxy_get_method(ant_t *js, ant_value_t handler, const char *name); +static ant_value_t proxy_get_with_receiver(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t receiver); static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); static ant_value_t proxy_get_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); static ant_value_t proxy_get_prototype_of(ant_t *js, ant_value_t proxy); +static bool proxy_target_is_extensible(ant_value_t obj); +static ant_value_t proxy_set_prototype_of(ant_t *js, ant_value_t proxy, ant_value_t proto); +static ant_value_t proxy_is_extensible(ant_t *js, ant_value_t proxy); +static ant_value_t proxy_prevent_extensions(ant_t *js, ant_value_t proxy); +static ant_value_t proxy_set_with_receiver(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value, ant_value_t receiver); static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value); static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); @@ -3021,11 +3031,22 @@ ant_value_t js_newobj(ant_t *js) { } ant_offset_t js_arr_len(ant_t *js, ant_value_t arr) { - if (!array_obj_ptr(arr)) return 0; + if (!array_obj_ptr(arr)) { + if (is_proxy(arr)) { + ant_value_t length = js_get(js, arr, "length"); + if (vtype(length) == T_NUM) return (ant_offset_t)tod(length); + } + return 0; + } return get_array_length(js, arr); } ant_value_t js_arr_get(ant_t *js, ant_value_t arr, ant_offset_t idx) { + if (is_proxy(arr)) { + char idxstr[32]; + uint_to_str(idxstr, sizeof(idxstr), (uint64_t)idx); + return js_get(js, arr, idxstr); + } if (vtype(arr) != T_ARR) return js_mkundef(); return arr_get(js, arr, idx); } @@ -3944,6 +3965,11 @@ ant_value_t js_setprop(ant_t *js, ant_value_t obj, ant_value_t k, ant_value_t v) bool on_receiver = true; while (is_object_type(cur)) { ant_value_t cur_obj = js_as_obj(cur); + if (!on_receiver && is_proxy(cur_obj)) { + ant_value_t result = proxy_set_with_receiver(js, cur_obj, key, klen, v, obj); + if (is_err(result)) return result; + return v; + } ant_object_t *cur_ptr = js_obj_ptr(cur_obj); if (!cur_ptr) break; @@ -4965,6 +4991,21 @@ static ant_value_t try_ordinary_to_primitive(ant_t *js, ant_value_t value, int h ant_value_t js_to_primitive(ant_t *js, ant_value_t value, int hint) { if (is_primitive(value)) return value; if (!is_object_type(value)) return value; + + if (is_proxy(js_as_obj(value))) { + ant_value_t tp_sym = get_toPrimitive_sym(); + ant_value_t tp_fn = js_get_sym(js, value, tp_sym); + if (is_err(tp_fn)) return tp_fn; + if (vtype(tp_fn) == T_UNDEF || vtype(tp_fn) == T_NULL) + return try_ordinary_to_primitive(js, value, hint); + if (vtype(tp_fn) != T_FUNC && vtype(tp_fn) != T_CFUNC) + return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.toPrimitive is not a function"); + const char *hint_str = hint == 1 ? "string" : (hint == 2 ? "number" : "default"); + ant_value_t hint_arg = js_mkstr(js, hint_str, strlen(hint_str)); + ant_value_t result = sv_vm_call(js->vm, js, tp_fn, value, &hint_arg, 1, NULL, false); + if (is_err(result) || is_primitive(result)) return result; + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value"); + } ant_value_t result = try_exotic_to_primitive(js, value, hint); if (vtype(result) != T_UNDEF) return result; @@ -5564,14 +5605,15 @@ static ant_value_t builtin_function_call(ant_t *js, ant_value_t *args, int nargs } int extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args) { - int len = (int) get_array_length(js, arr); + int len = (int) proxy_aware_length(js, arr); if (len <= 0) return 0; ant_value_t *args_out = (ant_value_t *)ant_calloc(sizeof(ant_value_t) * len); if (!args_out) return 0; for (int i = 0; i < len; i++) { - args_out[i] = arr_get(js, arr, (ant_offset_t)i); + ant_value_t item = array_method_get_index(js, arr, (ant_offset_t)i); + args_out[i] = is_err(item) ? js_mkundef() : item; } *out_args = args_out; @@ -6838,6 +6880,106 @@ done: return arr; } +static bool proxy_key_same(ant_t *js, ant_value_t a, ant_value_t b) { + if (vtype(a) != vtype(b)) return false; + if (vtype(a) == T_SYMBOL) return vdata(a) == vdata(b); + if (vtype(a) != T_STR) return false; + + ant_offset_t a_len = 0, b_len = 0; + ant_offset_t a_off = vstr(js, a, &a_len); + ant_offset_t b_off = vstr(js, b, &b_len); + return a_len == b_len && + memcmp((const void *)(uintptr_t)a_off, (const void *)(uintptr_t)b_off, a_len) == 0; +} + +static bool proxy_keys_include(ant_t *js, ant_value_t keys, ant_value_t key) { + if (vtype(keys) != T_ARR) return false; + ant_offset_t len = get_array_length(js, keys); + for (ant_offset_t i = 0; i < len; i++) { + if (proxy_key_same(js, arr_get(js, keys, i), key)) return true; + } + return false; +} + +static bool proxy_target_key_is_nonconfigurable(ant_t *js, ant_value_t target, ant_value_t key) { + if (vtype(key) == T_SYMBOL) { + ant_offset_t off = lkp_sym(js, target, (ant_offset_t)vdata(key)); + if (off != 0 && is_nonconfig_prop(js, off)) return true; + prop_meta_t meta; + return lookup_symbol_prop_meta(js_as_obj(target), (ant_offset_t)vdata(key), &meta) && !meta.configurable; + } + + if (vtype(key) != T_STR) return false; + ant_offset_t key_len = 0; + ant_offset_t key_off = vstr(js, key, &key_len); + const char *key_ptr = (const char *)(uintptr_t)key_off; + ant_offset_t off = lkp(js, target, key_ptr, (size_t)key_len); + if (off != 0 && is_nonconfig_prop(js, off)) return true; + prop_meta_t meta; + return lookup_string_prop_meta(js, js_as_obj(target), key_ptr, (size_t)key_len, &meta) && !meta.configurable; +} + +static ant_value_t proxy_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mode); + +static ant_value_t proxy_set_integrity_level(ant_t *js, ant_value_t obj, bool freeze) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, obj); + + ant_value_t prevent_result = proxy_prevent_extensions(js, obj); + if (is_err(prevent_result)) { + GC_ROOT_RESTORE(js, root_mark); + return prevent_result; + } + if (!js_truthy(js, prevent_result)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "Proxy preventExtensions trap returned falsy"); + } + + ant_value_t keys = proxy_enum(js, obj, OBJ_ENUM_KEYS); + GC_ROOT_PIN(js, keys); + if (is_err(keys)) { + GC_ROOT_RESTORE(js, root_mark); + return keys; + } + + ant_offset_t len = get_array_length(js, keys); + for (ant_offset_t i = 0; i < len; i++) { + GC_ROOT_SAVE(iter_mark, js); + ant_value_t key = arr_get(js, keys, i); + GC_ROOT_PIN(js, key); + + ant_value_t desc = js_mkobj(js); + GC_ROOT_PIN(js, desc); + js_setprop(js, desc, js_mkstr(js, "configurable", 12), js_false); + if (freeze) js_setprop(js, desc, js_mkstr(js, "writable", 8), js_false); + + ant_value_t result = proxy_define_property(js, obj, key, desc); + if (is_err(result) || !js_truthy(js, result)) { + GC_ROOT_RESTORE(js, iter_mark); + GC_ROOT_RESTORE(js, root_mark); + return is_err(result) + ? result + : js_mkerr_typed(js, JS_ERR_TYPE, "Proxy defineProperty trap returned falsy"); + } + GC_ROOT_RESTORE(js, iter_mark); + } + + GC_ROOT_RESTORE(js, root_mark); + return obj; +} + +static ant_value_t proxy_test_integrity_level(ant_t *js, ant_value_t obj, bool frozen) { + ant_value_t extensible = proxy_is_extensible(js, obj); + if (is_err(extensible)) return extensible; + if (js_truthy(js, extensible)) return js_false; + + ant_value_t keys = proxy_enum(js, obj, OBJ_ENUM_KEYS); + if (is_err(keys)) return keys; + + (void)frozen; + return js_true; +} + // TODO: reduce nesting static ant_value_t proxy_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mode) { GC_ROOT_SAVE(root_mark, js); @@ -6856,15 +6998,14 @@ static ant_value_t proxy_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mod ant_value_t keys = mkarr(js); GC_ROOT_PIN(js, keys); - ant_offset_t trap_off = lkp(js, data->handler, "ownKeys", 7); - if (!trap_off) { + ant_value_t trap = proxy_get_method(js, data->handler, "ownKeys"); + if (is_err(trap)) { + GC_ROOT_RESTORE(js, root_mark); + return trap; + } + if (vtype(trap) == T_UNDEF) { keys = object_enum(js, data->target, OBJ_ENUM_KEYS); } else { - ant_value_t trap = propref_load(js, trap_off); - uint8_t ft = vtype(trap); - if (ft != T_FUNC && ft != T_CFUNC) { - keys = object_enum(js, data->target, OBJ_ENUM_KEYS); - } else { ant_value_t trap_args[1] = { data->target }; ant_value_t result = sv_vm_call(js->vm, js, trap, data->handler, trap_args, 1, NULL, false); if (is_err(result)) { @@ -6905,8 +7046,39 @@ static ant_value_t proxy_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mod } } } + + ant_value_t target_keys = js_own_property_keys(js, data->target, true, false); + GC_ROOT_PIN(js, target_keys); + if (is_err(target_keys)) { + GC_ROOT_RESTORE(js, root_mark); + return target_keys; + } + + ant_offset_t target_len = get_array_length(js, target_keys); + bool target_extensible = proxy_target_is_extensible(data->target); + for (ant_offset_t i = 0; i < target_len; i++) { + ant_value_t target_key = arr_get(js, target_keys, i); + if (proxy_target_key_is_nonconfigurable(js, data->target, target_key) && + !proxy_keys_include(js, result, target_key)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result did not include non-configurable key"); + } + if (!target_extensible && !proxy_keys_include(js, result, target_key)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result did not include all target keys"); + } + } + + if (!target_extensible) { + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t result_key = arr_get(js, result, i); + if (!proxy_keys_include(js, target_keys, result_key)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result included extra key for non-extensible target"); + } + } + } keys = result; - } } ant_value_t out = mkarr(js); @@ -7284,6 +7456,10 @@ ant_value_t js_for_in_keys(ant_t *js, ant_value_t obj) { } if (t != T_OBJ && t != T_ARR && t != T_FUNC) goto done; + if (is_proxy(obj)) { + result = proxy_enum(js, obj, OBJ_ENUM_KEYS); + goto done; + } result = for_in_keys_collect_chain(js, out, seen, obj); done: @@ -7366,6 +7542,14 @@ static ant_value_t builtin_object_setPrototypeOf(ant_t *js, ant_value_t *args, i if (pt != T_NULL && proto_chain_contains(js, proto, obj)) return js_mkerr(js, "Cyclic __proto__ value"); + + if (is_proxy(obj)) { + ant_value_t result = proxy_set_prototype_of(js, obj, proto); + if (is_err(result)) return result; + if (!js_truthy(js, result)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'setPrototypeOf' on proxy: trap returned falsy"); + return obj; + } set_proto(js, obj, proto); return obj; @@ -7815,51 +7999,84 @@ static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, i bool has_value = false, has_get = false, has_set = false; bool has_writable = false, has_enumerable = false, has_configurable = false; ant_value_t value = js_mkundef(); + ant_value_t getter_val = js_mkundef(); + ant_value_t setter_val = js_mkundef(); bool writable = false, enumerable = false, configurable = false; - - ant_offset_t value_off = lkp(js, descriptor, "value", 5); - if (value_off != 0) { - has_value = true; - value = propref_load(js, value_off); - } - - ant_offset_t get_off = lkp_interned(js, descriptor, js->intern.get, 3); - if (get_off != 0) { - has_get = true; - ant_value_t getter = propref_load(js, get_off); - if (vtype(getter) != T_FUNC && vtype(getter) != T_UNDEF) { - return js_mkerr(js, "Getter must be a function"); - } - } - - ant_offset_t set_off = lkp_interned(js, descriptor, js->intern.set, 3); - if (set_off != 0) { - has_set = true; - ant_value_t setter = propref_load(js, set_off); - if (vtype(setter) != T_FUNC && vtype(setter) != T_UNDEF) { - return js_mkerr(js, "Setter must be a function"); - } - } - - ant_offset_t writable_off = lkp(js, descriptor, "writable", 8); - if (writable_off != 0) { - has_writable = true; - ant_value_t w_val = propref_load(js, writable_off); - writable = js_truthy(js, w_val); - } - - ant_offset_t enumerable_off = lkp(js, descriptor, "enumerable", 10); - if (enumerable_off != 0) { - has_enumerable = true; - ant_value_t e_val = propref_load(js, enumerable_off); + + if (is_proxy(descriptor)) { + ant_value_t e_val = proxy_get(js, descriptor, "enumerable", 10); + if (is_err(e_val)) return e_val; + has_enumerable = vtype(e_val) != T_UNDEF; enumerable = js_truthy(js, e_val); - } - - ant_offset_t configurable_off = lkp(js, descriptor, "configurable", 12); - if (configurable_off != 0) { - has_configurable = true; - ant_value_t c_val = propref_load(js, configurable_off); + + ant_value_t c_val = proxy_get(js, descriptor, "configurable", 12); + if (is_err(c_val)) return c_val; + has_configurable = vtype(c_val) != T_UNDEF; configurable = js_truthy(js, c_val); + + value = proxy_get(js, descriptor, "value", 5); + if (is_err(value)) return value; + has_value = vtype(value) != T_UNDEF; + + ant_value_t w_val = proxy_get(js, descriptor, "writable", 8); + if (is_err(w_val)) return w_val; + has_writable = vtype(w_val) != T_UNDEF; + writable = js_truthy(js, w_val); + + getter_val = proxy_get(js, descriptor, "get", 3); + if (is_err(getter_val)) return getter_val; + has_get = vtype(getter_val) != T_UNDEF; + if (has_get && vtype(getter_val) != T_FUNC) return js_mkerr(js, "Getter must be a function"); + + setter_val = proxy_get(js, descriptor, "set", 3); + if (is_err(setter_val)) return setter_val; + has_set = vtype(setter_val) != T_UNDEF; + if (has_set && vtype(setter_val) != T_FUNC) return js_mkerr(js, "Setter must be a function"); + } else { + ant_offset_t value_off = lkp(js, descriptor, "value", 5); + if (value_off != 0) { + has_value = true; + value = propref_load(js, value_off); + } + + ant_offset_t get_off = lkp_interned(js, descriptor, js->intern.get, 3); + if (get_off != 0) { + has_get = true; + getter_val = propref_load(js, get_off); + if (vtype(getter_val) != T_FUNC && vtype(getter_val) != T_UNDEF) { + return js_mkerr(js, "Getter must be a function"); + } + } + + ant_offset_t set_off = lkp_interned(js, descriptor, js->intern.set, 3); + if (set_off != 0) { + has_set = true; + setter_val = propref_load(js, set_off); + if (vtype(setter_val) != T_FUNC && vtype(setter_val) != T_UNDEF) { + return js_mkerr(js, "Setter must be a function"); + } + } + + ant_offset_t writable_off = lkp(js, descriptor, "writable", 8); + if (writable_off != 0) { + has_writable = true; + ant_value_t w_val = propref_load(js, writable_off); + writable = js_truthy(js, w_val); + } + + ant_offset_t enumerable_off = lkp(js, descriptor, "enumerable", 10); + if (enumerable_off != 0) { + has_enumerable = true; + ant_value_t e_val = propref_load(js, enumerable_off); + enumerable = js_truthy(js, e_val); + } + + ant_offset_t configurable_off = lkp(js, descriptor, "configurable", 12); + if (configurable_off != 0) { + has_configurable = true; + ant_value_t c_val = propref_load(js, configurable_off); + configurable = js_truthy(js, c_val); + } } if ((has_value || has_writable) && (has_get || has_set)) { @@ -7936,8 +8153,8 @@ static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, i (enumerable ? JS_DESC_E : 0) | (configurable ? JS_DESC_C : 0); - ant_value_t getter = has_get ? propref_load(js, get_off) : js_mkundef(); - ant_value_t setter = has_set ? propref_load(js, set_off) : js_mkundef(); + ant_value_t getter = has_get ? getter_val : js_mkundef(); + ant_value_t setter = has_set ? setter_val : js_mkundef(); if (sym_key) { if (has_get) js_set_sym_getter_desc(js, as_obj, prop, getter, desc_flags); @@ -8098,6 +8315,46 @@ static ant_value_t builtin_object_defineProperties(ant_t *js, ant_value_t *args, if (vtype(props) != T_OBJ) { return js_mkerr(js, "Property descriptors must be an object"); } + + if (is_proxy(props)) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, obj); + GC_ROOT_PIN(js, props); + + ant_value_t keys = proxy_enum(js, props, OBJ_ENUM_KEYS); + GC_ROOT_PIN(js, keys); + if (is_err(keys)) { + GC_ROOT_RESTORE(js, root_mark); + return keys; + } + + ant_offset_t key_count = js_arr_len(js, keys); + for (ant_offset_t i = 0; i < key_count; i++) { + GC_ROOT_SAVE(iter_mark, js); + ant_value_t prop_key = js_arr_get(js, keys, i); + GC_ROOT_PIN(js, prop_key); + + ant_value_t descriptor = proxy_get_val(js, props, prop_key); + GC_ROOT_PIN(js, descriptor); + if (is_err(descriptor)) { + GC_ROOT_RESTORE(js, iter_mark); + GC_ROOT_RESTORE(js, root_mark); + return descriptor; + } + + ant_value_t define_args[3] = { obj, prop_key, descriptor }; + ant_value_t result = builtin_object_defineProperty(js, define_args, 3); + if (is_err(result)) { + GC_ROOT_RESTORE(js, iter_mark); + GC_ROOT_RESTORE(js, root_mark); + return result; + } + GC_ROOT_RESTORE(js, iter_mark); + } + + GC_ROOT_RESTORE(js, root_mark); + return obj; + } ant_iter_t iter = js_prop_iter_begin(js, props); const char *key = NULL; @@ -8313,6 +8570,46 @@ static ant_value_t builtin_object_assign(ant_t *js, ant_value_t *args, int nargs if (st == T_NULL || st == T_UNDEF) continue; if (st != T_OBJ && st != T_ARR && st != T_FUNC) continue; + if (is_proxy(js_as_obj(source))) { + GC_ROOT_SAVE(source_mark, js); + GC_ROOT_PIN(js, target); + GC_ROOT_PIN(js, as_obj); + GC_ROOT_PIN(js, source); + + ant_value_t keys = proxy_enum(js, js_as_obj(source), OBJ_ENUM_KEYS); + GC_ROOT_PIN(js, keys); + if (is_err(keys)) { + GC_ROOT_RESTORE(js, source_mark); + return keys; + } + + ant_offset_t key_count = js_arr_len(js, keys); + for (ant_offset_t k = 0; k < key_count; k++) { + GC_ROOT_SAVE(key_mark, js); + ant_value_t prop_key = js_arr_get(js, keys, k); + GC_ROOT_PIN(js, prop_key); + + ant_value_t val = proxy_get_val(js, js_as_obj(source), prop_key); + GC_ROOT_PIN(js, val); + if (is_err(val)) { + GC_ROOT_RESTORE(js, key_mark); + GC_ROOT_RESTORE(js, source_mark); + return val; + } + + ant_value_t set_result = js_setprop(js, as_obj, prop_key, val); + if (is_err(set_result)) { + GC_ROOT_RESTORE(js, key_mark); + GC_ROOT_RESTORE(js, source_mark); + return set_result; + } + GC_ROOT_RESTORE(js, key_mark); + } + + GC_ROOT_RESTORE(js, source_mark); + continue; + } + bool fast_handled = false; ant_value_t fast_result = object_assign_fast_ordinary_source(js, as_obj, source, &fast_handled); if (is_err(fast_result)) return fast_result; @@ -8376,6 +8673,7 @@ ant_value_t builtin_object_freeze(ant_t *js, ant_value_t *args, int nargs) { if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj; ant_value_t as_obj = js_as_obj(obj); + if (is_proxy(as_obj)) return proxy_set_integrity_level(js, as_obj, true); ant_object_t *ptr = js_obj_ptr(as_obj); if (!ptr || !ptr->shape) return obj; @@ -8408,6 +8706,7 @@ static ant_value_t builtin_object_isFrozen(ant_t *js, ant_value_t *args, int nar if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_true; ant_value_t as_obj = js_as_obj(obj); + if (is_proxy(as_obj)) return proxy_test_integrity_level(js, as_obj, true); ant_object_t *ptr = js_obj_ptr(as_obj); return js_bool(ptr && ptr->frozen); @@ -8421,6 +8720,7 @@ static ant_value_t builtin_object_seal(ant_t *js, ant_value_t *args, int nargs) if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj; ant_value_t as_obj = js_as_obj(obj); + if (is_proxy(as_obj)) return proxy_set_integrity_level(js, as_obj, false); ant_object_t *ptr = js_obj_ptr(as_obj); if (!ptr || !ptr->shape) return obj; @@ -8454,6 +8754,7 @@ static ant_value_t builtin_object_isSealed(ant_t *js, ant_value_t *args, int nar if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_true; ant_value_t as_obj = js_as_obj(obj); + if (is_proxy(as_obj)) return proxy_test_integrity_level(js, as_obj, false); ant_object_t *ptr = js_obj_ptr(as_obj); if (ptr && (ptr->sealed || ptr->frozen)) return js_true; @@ -8865,6 +9166,7 @@ static ant_value_t builtin_object_isExtensible(ant_t *js, ant_value_t *args, int if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_false; ant_value_t as_obj = js_as_obj(obj); + if (is_proxy(as_obj)) return proxy_is_extensible(js, as_obj); ant_object_t *ptr = js_obj_ptr(as_obj); if (!ptr) return js_true; @@ -8880,6 +9182,13 @@ static ant_value_t builtin_object_preventExtensions(ant_t *js, ant_value_t *args if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj; ant_value_t as_obj = js_as_obj(obj); + if (is_proxy(as_obj)) { + ant_value_t result = proxy_prevent_extensions(js, as_obj); + if (is_err(result)) return result; + if (!js_truthy(js, result)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'preventExtensions' on proxy: trap returned falsy"); + return obj; + } ant_object_t *ptr = js_obj_ptr(as_obj); if (ptr) ptr->extensible = 0; @@ -9160,7 +9469,7 @@ static ant_value_t builtin_array_push(ant_t *js, ant_value_t *args, int nargs) { return len_val; } - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); ant_offset_t doff = get_dense_buf(arr); if (doff) { @@ -9207,7 +9516,7 @@ void js_arr_push(ant_t *js, ant_value_t arr, ant_value_t val) { return; } - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len); @@ -9232,6 +9541,9 @@ static ant_value_t builtin_array_pop(ant_t *js, ant_value_t *args, int nargs) { char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len); ant_value_t result = proxy_aware_get_elem(js, arr, idxstr, idxlen); + if (is_err(result)) return result; + ant_value_t del = proxy_delete(js, arr, idxstr, idxlen); + if (is_err(del)) return del; js_setprop(js, arr, js->length_str, tov((double) len)); return result; } @@ -9253,7 +9565,7 @@ static ant_value_t builtin_array_pop(ant_t *js, ant_value_t *args, int nargs) { } pop_slow: - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); if (len == 0) return js_mkundef(); len--; char idxstr[16]; @@ -9342,7 +9654,7 @@ static ant_value_t builtin_array_join(ant_t *js, ant_value_t *args, int nargs) { } } - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); if (len == 0) return js_mkstr(js, "", 0); @@ -9364,7 +9676,11 @@ static ant_value_t builtin_array_join(ant_t *js, ant_value_t *args, int nargs) { } { - ant_value_t elem = arr_get(js, arr, i); + ant_value_t elem = array_method_get_index(js, arr, i); + if (is_err(elem)) { + free(result); + return elem; + } uint8_t et = vtype(elem); if (et == T_NULL || et == T_UNDEF) continue; @@ -9751,11 +10067,12 @@ static ant_value_t builtin_array_every(ant_t *js, ant_value_t *args, int nargs) if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); for (ant_offset_t i = 0; i < len; i++) { - if (!arr_has(js, arr, i)) continue; - ant_value_t val = arr_get(js, arr, i); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t val = array_method_get_index(js, arr, i); + if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); if (is_err(result)) return result; @@ -9775,11 +10092,12 @@ static ant_value_t builtin_array_forEach(ant_t *js, ant_value_t *args, int nargs if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); for (ant_offset_t i = 0; i < len; i++) { - if (!arr_has(js, arr, i)) continue; - ant_value_t val = arr_get(js, arr, i); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t val = array_method_get_index(js, arr, i); + if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); if (is_err(result)) return result; @@ -9804,24 +10122,28 @@ static ant_value_t builtin_array_reverse(ant_t *js, ant_value_t *args, int nargs ant_offset_t upper_idx = len - lower - 1; bool lower_exists = arr_has(js, read_from, lower); bool upper_exists = arr_has(js, read_from, upper_idx); - ant_value_t lower_val = lower_exists ? arr_get(js, read_from, lower) : js_mkundef(); - ant_value_t upper_val = upper_exists ? arr_get(js, read_from, upper_idx) : js_mkundef(); + char lower_key[16]; size_t lower_len = uint_to_str(lower_key, sizeof(lower_key), (unsigned)lower); + char upper_key[16]; size_t upper_len = uint_to_str(upper_key, sizeof(upper_key), (unsigned)upper_idx); + ant_value_t lower_val = lower_exists ? proxy_get(js, arr, lower_key, lower_len) : js_mkundef(); + if (is_err(lower_val)) return lower_val; + ant_value_t upper_val = upper_exists ? proxy_get(js, arr, upper_key, upper_len) : js_mkundef(); + if (is_err(upper_val)) return upper_val; if (lower_exists && upper_exists) { - char s1[16]; size_t l1 = uint_to_str(s1, sizeof(s1), (unsigned)lower); - js_setprop(js, arr, js_mkstr(js, s1, l1), upper_val); - char s2[16]; size_t l2 = uint_to_str(s2, sizeof(s2), (unsigned)upper_idx); - js_setprop(js, arr, js_mkstr(js, s2, l2), lower_val); + js_setprop(js, arr, js_mkstr(js, lower_key, lower_len), upper_val); + js_setprop(js, arr, js_mkstr(js, upper_key, upper_len), lower_val); } else if (upper_exists) { - char s[16]; size_t l = uint_to_str(s, sizeof(s), (unsigned)lower); - js_setprop(js, arr, js_mkstr(js, s, l), upper_val); + js_setprop(js, arr, js_mkstr(js, lower_key, lower_len), upper_val); + ant_value_t del = proxy_delete(js, arr, upper_key, upper_len); + if (is_err(del)) return del; } else if (lower_exists) { - char s[16]; size_t l = uint_to_str(s, sizeof(s), (unsigned)upper_idx); - js_setprop(js, arr, js_mkstr(js, s, l), lower_val); + js_setprop(js, arr, js_mkstr(js, upper_key, upper_len), lower_val); + ant_value_t del = proxy_delete(js, arr, lower_key, lower_len); + if (is_err(del)) return del; } lower++; } return arr; } - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); if (len <= 1) return arr; ant_offset_t doff = get_dense_buf(arr); @@ -9866,13 +10188,14 @@ static ant_value_t builtin_array_map(ant_t *js, ant_value_t *args, int nargs) { if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); ant_value_t result = array_alloc_like(js, arr); if (is_err(result)) return result; for (ant_offset_t i = 0; i < len; i++) { - if (!arr_has(js, arr, i)) continue; - ant_value_t val = arr_get(js, arr, i); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t val = array_method_get_index(js, arr, i); + if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; ant_value_t mapped = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); if (is_err(mapped)) return mapped; @@ -9892,15 +10215,16 @@ static ant_value_t builtin_array_filter(ant_t *js, ant_value_t *args, int nargs) if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); ant_value_t result = array_alloc_like(js, arr); if (is_err(result)) return result; ant_offset_t result_idx = 0; for (ant_offset_t i = 0; i < len; i++) { - if (!arr_has(js, arr, i)) continue; - ant_value_t val = arr_get(js, arr, i); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t val = array_method_get_index(js, arr, i); + if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; ant_value_t test = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); @@ -9921,14 +10245,15 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) if (is_err(callback)) return callback; bool has_initial = (nargs >= 2); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); ant_value_t accumulator = has_initial ? args[1] : js_mkundef(); bool first = !has_initial; for (ant_offset_t i = 0; i < len; i++) { - if (!arr_has(js, arr, i)) continue; - ant_value_t val = arr_get(js, arr, i); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t val = array_method_get_index(js, arr, i); + if (is_err(val)) return val; if (first) { accumulator = val; first = false; continue; } ant_value_t call_args[4] = { accumulator, val, tov((double)i), arr }; accumulator = sv_vm_call(js->vm, js, callback, js_mkundef(), call_args, 4, NULL, false); @@ -9940,7 +10265,7 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) } static inline void flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) { - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); if (len == 0) return; for (ant_offset_t i = 0; i < len; i++) { @@ -10030,7 +10355,7 @@ static ant_value_t builtin_array_at(ant_t *js, ant_value_t *args, int nargs) { if (nargs == 0 || vtype(args[0]) != T_NUM) return js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); int idx = (int) tod(args[0]); if (idx < 0) idx = (int)len + idx; @@ -10082,11 +10407,12 @@ static ant_value_t array_find_impl(ant_t *js, ant_value_t *args, int nargs, bool if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); if (len == 0) return return_index ? tov(-1) : js_mkundef(); for (ant_offset_t i = 0; i < len; i++) { - ant_value_t val = arr_get(js, arr, i); + ant_value_t val = array_method_get_index(js, arr, i); + if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); @@ -10116,11 +10442,12 @@ static ant_value_t array_find_last_impl(ant_t *js, ant_value_t *args, int nargs, if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); if (len == 0) return return_index ? tov(-1) : js_mkundef(); for (ant_offset_t i = len; i > 0; i--) { - ant_value_t val = arr_get(js, arr, i - 1); + ant_value_t val = array_method_get_index(js, arr, i - 1); + if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)(i - 1)), arr }; ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); @@ -10151,7 +10478,7 @@ static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs ant_value_t callback = args[0]; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); ant_value_t result = mkarr(js); if (is_err(result)) return result; @@ -10210,7 +10537,7 @@ static ant_value_t builtin_array_indexOf(ant_t *js, ant_value_t *args, int nargs if (nargs == 0) return tov(-1); ant_value_t search = args[0]; - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); ant_offset_t start = 0; if (nargs >= 2 && vtype(args[1]) == T_NUM) { @@ -10221,8 +10548,9 @@ static ant_value_t builtin_array_indexOf(ant_t *js, ant_value_t *args, int nargs } for (ant_offset_t i = start; i < len; i++) { - ant_value_t elem = arr_get(js, arr, i); - if (vtype(elem) == T_UNDEF && !arr_has(js, arr, i)) continue; + ant_value_t elem = array_method_get_index(js, arr, i); + if (is_err(elem)) return elem; + if (vtype(elem) == T_UNDEF && !array_method_has_index(js, arr, i)) continue; if (strict_eq_values(js, elem, search)) return tov((double)i); } return tov(-1); @@ -10236,7 +10564,7 @@ static ant_value_t builtin_array_lastIndexOf(ant_t *js, ant_value_t *args, int n if (nargs == 0) return tov(-1); ant_value_t search = args[0]; - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); int start = (int)len - 1; if (nargs >= 2 && vtype(args[1]) == T_NUM) { @@ -10246,8 +10574,9 @@ static ant_value_t builtin_array_lastIndexOf(ant_t *js, ant_value_t *args, int n if (start >= (int)len) start = (int)len - 1; for (int i = start; i >= 0; i--) { - ant_value_t elem = arr_get(js, arr, (ant_offset_t)i); - if (vtype(elem) == T_UNDEF && !arr_has(js, arr, (ant_offset_t)i)) continue; + ant_value_t elem = array_method_get_index(js, arr, (ant_offset_t)i); + if (is_err(elem)) return elem; + if (vtype(elem) == T_UNDEF && !array_method_has_index(js, arr, (ant_offset_t)i)) continue; if (strict_eq_values(js, elem, search)) return tov((double)i); } return tov(-1); @@ -10263,7 +10592,7 @@ static ant_value_t builtin_array_reduceRight(ant_t *js, ant_value_t *args, int n } ant_value_t callback = args[0]; - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); int start_idx = (int)len - 1; ant_value_t accumulator; @@ -10272,12 +10601,15 @@ static ant_value_t builtin_array_reduceRight(ant_t *js, ant_value_t *args, int n accumulator = args[1]; } else { if (len == 0) return js_mkerr(js, "reduceRight of empty array with no initial value"); - accumulator = arr_get(js, arr, len - 1); + accumulator = array_method_get_index(js, arr, len - 1); + if (is_err(accumulator)) return accumulator; start_idx = (int)len - 2; } for (int i = start_idx; i >= 0; i--) { - ant_value_t elem = arr_get(js, arr, (ant_offset_t)i); + if (!array_method_has_index(js, arr, (ant_offset_t)i)) continue; + ant_value_t elem = array_method_get_index(js, arr, (ant_offset_t)i); + if (is_err(elem)) return elem; ant_value_t call_args[4] = { accumulator, elem, tov((double)i), arr }; accumulator = sv_vm_call(js->vm, js, callback, js_mkundef(), call_args, 4, NULL, false); if (is_err(accumulator)) return accumulator; @@ -10297,6 +10629,32 @@ static ant_value_t builtin_array_shift(ant_t *js, ant_value_t *args, int nargs) ant_offset_t len = proxy_aware_length(js, arr); if (len == 0) return js_mkundef(); + if (is_proxy(arr)) { + ant_value_t read_from = proxy_read_target(js, arr); + ant_value_t first = proxy_get(js, arr, "0", 1); + if (is_err(first)) return first; + + for (ant_offset_t i = 1; i < len; i++) { + char src[16]; + size_t srclen = uint_to_str(src, sizeof(src), (unsigned)i); + char dst[16]; + size_t dstlen = uint_to_str(dst, sizeof(dst), (unsigned)(i - 1)); + if (arr_has(js, read_from, i)) { + ant_value_t elem = proxy_get(js, arr, src, srclen); + if (is_err(elem)) return elem; + js_setprop(js, arr, js_mkstr(js, dst, dstlen), elem); + } else { + ant_value_t del = proxy_delete(js, arr, dst, dstlen); + if (is_err(del)) return del; + } + } + + ant_value_t del = proxy_delete_index(js, arr, len - 1); + if (is_err(del)) return del; + js_setprop(js, arr, js->length_str, tov((double)(len - 1))); + return first; + } + ant_offset_t doff = get_dense_buf(arr); if (doff && !is_proxy(arr)) { ant_offset_t d_len = dense_iterable_length(js, arr); @@ -10339,6 +10697,34 @@ static ant_value_t builtin_array_unshift(ant_t *js, ant_value_t *args, int nargs ant_offset_t len = proxy_aware_length(js, arr); + if (is_proxy(arr)) { + ant_value_t read_from = proxy_read_target(js, arr); + for (int i = (int)len - 1; i >= 0; i--) { + char src[16]; + size_t srclen = uint_to_str(src, sizeof(src), (unsigned)i); + char dst[16]; + size_t dstlen = uint_to_str(dst, sizeof(dst), (unsigned)(i + nargs)); + if (arr_has(js, read_from, (ant_offset_t)i)) { + ant_value_t elem = proxy_get(js, arr, src, srclen); + if (is_err(elem)) return elem; + js_setprop(js, arr, js_mkstr(js, dst, dstlen), elem); + } else { + ant_value_t del = proxy_delete(js, arr, dst, dstlen); + if (is_err(del)) return del; + } + } + + for (int i = 0; i < nargs; i++) { + char idxstr[16]; + size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)i); + js_setprop(js, arr, js_mkstr(js, idxstr, idxlen), args[i]); + } + + ant_offset_t new_len = len + nargs; + js_setprop(js, arr, js->length_str, tov((double)new_len)); + return tov((double)new_len); + } + ant_offset_t doff = get_dense_buf(arr); if (doff && !is_proxy(arr)) { ant_offset_t d_len = dense_iterable_length(js, arr); @@ -10394,12 +10780,13 @@ static ant_value_t builtin_array_some(ant_t *js, ant_value_t *args, int nargs) { if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); if (len == 0) return mkval(T_BOOL, 0); for (ant_offset_t i = 0; i < len; i++) { - if (!arr_has(js, arr, i)) continue; - ant_value_t val = arr_get(js, arr, i); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t val = array_method_get_index(js, arr, i); + if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); @@ -10621,6 +11008,65 @@ static ant_value_t builtin_array_splice(ant_t *js, ant_value_t *args, int nargs) return removed; } + if (is_proxy(arr)) { + for (int i = 0; i < deleteCount; i++) { + ant_offset_t src_idx = (ant_offset_t)(start + i); + if (!arr_has(js, read_from, src_idx)) continue; + char src[16], dst[16]; + size_t srclen = uint_to_str(src, sizeof(src), (unsigned)src_idx); + size_t dstlen = uint_to_str(dst, sizeof(dst), (unsigned)i); + ant_value_t elem = proxy_get(js, arr, src, srclen); + if (is_err(elem)) return elem; + js_setprop(js, removed, js_mkstr(js, dst, dstlen), elem); + } + + js_setprop(js, removed, js->length_str, tov((double)deleteCount)); + int shift = insertCount - deleteCount; + + if (shift > 0) { + for (int i = (int)len - 1; i >= start + deleteCount; i--) { + char src[16], dst[16]; + size_t srclen = uint_to_str(src, sizeof(src), (unsigned)i); + size_t dstlen = uint_to_str(dst, sizeof(dst), (unsigned)(i + shift)); + if (arr_has(js, read_from, (ant_offset_t)i)) { + ant_value_t elem = proxy_get(js, arr, src, srclen); + if (is_err(elem)) return elem; + js_setprop(js, arr, js_mkstr(js, dst, dstlen), elem); + } else { + ant_value_t del = proxy_delete(js, arr, dst, dstlen); + if (is_err(del)) return del; + } + } + } else if (shift < 0) { + for (int i = start + deleteCount; i < (int)len; i++) { + char src[16], dst[16]; + size_t srclen = uint_to_str(src, sizeof(src), (unsigned)i); + size_t dstlen = uint_to_str(dst, sizeof(dst), (unsigned)(i + shift)); + if (arr_has(js, read_from, (ant_offset_t)i)) { + ant_value_t elem = proxy_get(js, arr, src, srclen); + if (is_err(elem)) return elem; + js_setprop(js, arr, js_mkstr(js, dst, dstlen), elem); + } else { + ant_value_t del = proxy_delete(js, arr, dst, dstlen); + if (is_err(del)) return del; + } + } + for (int i = (int)len + shift; i < (int)len; i++) { + ant_value_t del = proxy_delete_index(js, arr, (ant_offset_t)i); + if (is_err(del)) return del; + } + } + + for (int i = 0; i < insertCount; i++) { + char idxstr[16]; + size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(start + i)); + js_setprop(js, arr, js_mkstr(js, idxstr, idxlen), args[2 + i]); + } + + js_setprop(js, arr, js->length_str, tov((double)((int)len + shift))); + return removed; + } + splice_slow: for (int i = 0; i < deleteCount; i++) { char src[16], dst[16]; @@ -10723,6 +11169,56 @@ static ant_value_t builtin_array_copyWithin(ant_t *js, ant_value_t *args, int na return arr; } + if (is_proxy(arr)) { + ant_value_t *temp_vals = malloc((size_t)count * sizeof(*temp_vals)); + bool *temp_exists = calloc((size_t)count, sizeof(*temp_exists)); + if (!temp_vals || !temp_exists) { + free(temp_vals); + free(temp_exists); + return js_mkerr(js, "out of memory"); + } + + for (int i = 0; i < count; i++) { + char idxstr[16]; + size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(start + i)); + temp_exists[i] = arr_has(js, read_from, (ant_offset_t)(start + i)); + temp_vals[i] = js_mkundef(); + if (temp_exists[i]) { + temp_vals[i] = proxy_get(js, arr, idxstr, idxlen); + if (is_err(temp_vals[i])) { + ant_value_t err = temp_vals[i]; + free(temp_exists); + free(temp_vals); + return err; + } + } + } + + for (int i = 0; i < count; i++) { + char idxstr[16]; + size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(target + i)); + if (temp_exists[i]) { + ant_value_t set_result = js_setprop(js, arr, js_mkstr(js, idxstr, idxlen), temp_vals[i]); + if (is_err(set_result)) { + free(temp_exists); + free(temp_vals); + return set_result; + } + } else { + ant_value_t del = proxy_delete(js, arr, idxstr, idxlen); + if (is_err(del)) { + free(temp_exists); + free(temp_vals); + return del; + } + } + } + + free(temp_exists); + free(temp_vals); + return arr; + } + ant_value_t *temp = (ant_value_t *)malloc(count * sizeof(ant_value_t)); if (!temp) return js_mkerr(js, "out of memory"); gc_temp_root_scope_begin(js, &temp_roots); @@ -10924,7 +11420,7 @@ static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, in static ant_value_t builtin_Array_isArray(ant_params_t) { if (nargs == 0) return mkval(T_BOOL, 0); - return mkval(T_BOOL, vtype(args[0]) == T_ARR ? 1 : 0); + return mkval(T_BOOL, is_array_value(args[0]) ? 1 : 0); } static ant_value_t builtin_Array_isTemplateObject(ant_params_t) { @@ -11020,19 +11516,31 @@ static ant_value_t builtin_Array_from(ant_t *js, ant_value_t *args, int nargs) { if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)ctx.index)); } } else { - ant_offset_t iter_prop = (vtype(iter_sym) == T_SYMBOL) ? lkp_sym_proto(js, src, (ant_offset_t)vdata(iter_sym)) : 0; + ant_value_t iter_method = js_mkundef(); + ant_offset_t iter_prop = 0; + if (vtype(iter_sym) == T_SYMBOL) { + if (is_proxy(src)) { + iter_method = js_get_sym(js, src, iter_sym); + if (is_err(iter_method)) return iter_method; + } else { + iter_prop = lkp_sym_proto(js, src, (ant_offset_t)vdata(iter_sym)); + if (iter_prop != 0) iter_method = propref_load(js, iter_prop); + } + } - if (iter_prop != 0) { + if (vtype(iter_method) != T_UNDEF) { array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 }; ant_value_t iter_result = iter_foreach(js, src, array_from_iter_cb, &ctx); if (is_err(iter_result)) return iter_result; if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)ctx.index)); } else if (vtype(src) == T_OBJ) { array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 }; - ant_offset_t len = get_array_length(js, src); + ant_offset_t len = proxy_aware_length(js, src); for (ant_offset_t i = 0; i < len; i++) { ant_value_t unused; - iter_action_t act = array_from_iter_cb(js, arr_get(js, src, i), &ctx, &unused); + ant_value_t item = array_method_get_index(js, src, i); + if (is_err(item)) return item; + iter_action_t act = array_from_iter_cb(js, item, &ctx, &unused); if (act == ITER_ERROR) return unused; } if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)len)); @@ -11192,7 +11700,14 @@ static ant_value_t string_split_impl(ant_t *js, ant_value_t str, ant_value_t *ar ant_value_t sep_arg = args[0]; if (vtype(sep_arg) == T_OBJ) { ant_offset_t source_off = lkp(js, sep_arg, "source", 6); - if (source_off == 0) goto return_whole; + if (source_off == 0) { + sep_arg = js_to_primitive(js, sep_arg, 1); + if (is_err(sep_arg)) return sep_arg; + sep_arg = js_tostring_val(js, sep_arg); + if (is_err(sep_arg)) return sep_arg; + args[0] = sep_arg; + goto split_string_separator; + } ant_value_t source_val = propref_load(js, source_off); if (vtype(source_val) != T_STR) goto return_whole; @@ -11313,6 +11828,7 @@ static ant_value_t string_split_impl(ant_t *js, ant_value_t str, ant_value_t *ar if (vtype(sep_arg) != T_STR) goto return_whole; +split_string_separator:; ant_offset_t sep_len, sep_off = vstr(js, sep_arg, &sep_len); const char *sep_ptr = (char *)(uintptr_t)(sep_off); ant_offset_t idx = 0, start = 0; @@ -14310,6 +14826,24 @@ ant_value_t do_in(ant_t *js, ant_value_t l, ant_value_t r) { if (parse_array_index(prop_name, prop_len, arr_len, &idx)) return mkval(T_BOOL, arr_has(js, r, (ant_offset_t)idx) ? 1 : 0); if (is_length_key(prop_name, prop_len)) return mkval(T_BOOL, 1); } + + ant_value_t cur = r; + uint8_t cur_t = vtype(cur); + proto_walk_overflow_guard_t guard; + proto_walk_overflow_guard_init(&guard); + while (proto_walk_next(js, &cur, &cur_t, PROTO_WALK_F_LOOKUP)) { + if (is_object_type(cur)) { + ant_value_t cur_obj = js_as_obj(cur); + if (is_proxy(cur_obj)) { + ant_value_t result = is_sym + ? proxy_has_val(js, cur_obj, key) + : proxy_has(js, cur_obj, prop_name, prop_len); + if (is_err(result)) return result; + return js_bool(js_truthy(js, result)); + } + } + if (proto_walk_overflow_guard_hit_cycle(js, &guard, cur, cur_t, PROTO_WALK_F_LOOKUP)) break; + } ant_offset_t found = is_sym ? lkp_sym_proto(js, r, (ant_offset_t)vdata(key)) : lkp_proto(js, r, prop_name, prop_len); return mkval(T_BOOL, found != 0 ? 1 : 0); @@ -14651,6 +15185,15 @@ bool is_proxy(ant_value_t obj) { return get_proxy_data(obj) != NULL; } +bool is_array_value(ant_value_t value) { + while (is_proxy(value)) { + ant_proxy_state_t *data = get_proxy_data(value); + if (!data || data->revoked) return false; + value = data->target; + } + return vtype(value) == T_ARR; +} + bool js_is_constructor(ant_value_t value) { ant_value_t slow = value; ant_value_t fast = value; @@ -14691,24 +15234,56 @@ static ant_value_t proxy_read_target(ant_t *js, ant_value_t obj) { } static ant_offset_t proxy_aware_length(ant_t *js, ant_value_t obj) { - ant_value_t src = is_proxy(obj) ? proxy_read_target(js, obj) : obj; - if (vtype(src) == T_ARR) return get_array_length(js, src); - ant_offset_t off = lkp_interned(js, src, js->intern.length, 6); + if (is_proxy(obj)) { + ant_value_t len_val = proxy_get(js, obj, "length", 6); + return vtype(len_val) == T_NUM ? (ant_offset_t)tod(len_val) : 0; + } + if (vtype(obj) == T_ARR) return get_array_length(js, obj); + ant_offset_t off = lkp_interned(js, obj, js->intern.length, 6); if (off == 0) return 0; ant_value_t len_val = propref_load(js, off); return vtype(len_val) == T_NUM ? (ant_offset_t)tod(len_val) : 0; } static ant_value_t proxy_aware_get_elem(ant_t *js, ant_value_t obj, const char *key, size_t key_len) { - ant_value_t src = is_proxy(obj) ? proxy_read_target(js, obj) : obj; - ant_offset_t off = lkp(js, src, key, key_len); + if (is_proxy(obj)) return proxy_get(js, obj, key, key_len); + ant_offset_t off = lkp(js, obj, key, key_len); return off ? propref_load(js, off) : js_mkundef(); } +static ant_value_t proxy_delete_index(ant_t *js, ant_value_t obj, ant_offset_t idx) { + char idxstr[16]; + size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)idx); + return proxy_delete(js, obj, idxstr, idxlen); +} + +static bool array_method_has_index(ant_t *js, ant_value_t arr, ant_offset_t idx) { + if (is_proxy(arr)) return true; + return arr_has(js, arr, idx); +} + +static ant_value_t array_method_get_index(ant_t *js, ant_value_t arr, ant_offset_t idx) { + if (is_proxy(arr)) { + char idxstr[16]; + size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)idx); + return proxy_get(js, arr, idxstr, idxlen); + } + return arr_get(js, arr, idx); +} + static ant_value_t throw_proxy_error(ant_t *js, const char *message) { return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message); } +static ant_value_t proxy_get_method(ant_t *js, ant_value_t handler, const char *name) { + ant_value_t trap = js_getprop_fallback(js, handler, name); + if (is_err(trap)) return trap; + if (vtype(trap) == T_UNDEF || vtype(trap) == T_NULL) return js_mkundef(); + if (!is_callable(trap)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'%s' proxy trap is not callable", name); + return trap; +} + static bool proxy_target_is_extensible(ant_value_t obj) { uint8_t t = vtype(obj); if (t != T_OBJ && t != T_ARR && t != T_FUNC) return false; @@ -14729,7 +15304,7 @@ static bool proxy_target_prop_is_const(ant_t *js, ant_value_t target, ant_offset return is_const_prop(js, prop_off); } -static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size_t key_len) { +static ant_value_t proxy_get_with_receiver(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t receiver) { ant_proxy_state_t *data = get_proxy_data(proxy); if (!data) return js_mkundef(); if (data->revoked) return throw_proxy_error(js, "Cannot perform 'get' on a proxy that has been revoked"); @@ -14737,16 +15312,12 @@ static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size ant_value_t target = data->target; ant_value_t handler = data->handler; - ant_offset_t get_trap_off = vtype(handler) == T_OBJ - ? lkp_interned(js, handler, js->intern.get, 3) - : 0; - - if (get_trap_off != 0) { - ant_value_t get_trap = propref_load(js, get_trap_off); - if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) { + ant_value_t get_trap = proxy_get_method(js, handler, "get"); + if (is_err(get_trap)) return get_trap; + if (vtype(get_trap) != T_UNDEF) { ant_value_t key_val = js_mkstr(js, key, key_len); - ant_value_t args[3] = { target, key_val, proxy }; + ant_value_t args[3] = { target, key_val, receiver }; ant_value_t result = sv_vm_call(js->vm, js, get_trap, js_mkundef(), args, 3, NULL, false); if (is_err(result)) return result; @@ -14771,24 +15342,29 @@ static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size } return result; - } } char key_buf[256]; - size_t len = key_len < sizeof(key_buf) - 1 ? key_len : sizeof(key_buf) - 1; - memcpy(key_buf, key, len); - key_buf[len] = '\0'; - - ant_offset_t off = lkp(js, target, key_buf, len); - if (off != 0) return propref_load(js, off); - - ant_offset_t proto_off = lkp_proto(js, target, key_buf, len); - if (proto_off != 0) return propref_load(js, proto_off); - - return js_mkundef(); + char *owned_key = NULL; + char *key_cstr = key_buf; + if (key_len >= sizeof(key_buf)) { + owned_key = ant_calloc(key_len + 1); + if (!owned_key) return js_mkerr(js, "oom"); + key_cstr = owned_key; + } + memcpy(key_cstr, key, key_len); + key_cstr[key_len] = '\0'; + + ant_value_t result = js_getprop_fallback(js, target, key_cstr); + free(owned_key); + return result; } -static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value) { +static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size_t key_len) { + return proxy_get_with_receiver(js, proxy, key, key_len, proxy); +} + +static ant_value_t proxy_set_with_receiver(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value, ant_value_t receiver) { ant_proxy_state_t *data = get_proxy_data(proxy); if (!data) return js_mkundef(); if (data->revoked) return throw_proxy_error(js, "Cannot perform 'set' on a proxy that has been revoked"); @@ -14796,12 +15372,11 @@ static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size ant_value_t target = data->target; ant_value_t handler = data->handler; - ant_offset_t set_trap_off = vtype(handler) == T_OBJ ? lkp_interned(js, handler, js->intern.set, 3) : 0; - if (set_trap_off != 0) { - ant_value_t set_trap = propref_load(js, set_trap_off); - if (vtype(set_trap) == T_FUNC || vtype(set_trap) == T_CFUNC) { + ant_value_t set_trap = proxy_get_method(js, handler, "set"); + if (is_err(set_trap)) return set_trap; + if (vtype(set_trap) != T_UNDEF) { ant_value_t key_val = js_mkstr(js, key, key_len); - ant_value_t args[4] = { target, key_val, value, proxy }; + ant_value_t args[4] = { target, key_val, value, receiver }; ant_value_t result = sv_vm_call(js->vm, js, set_trap, js_mkundef(), args, 4, NULL, false); if (is_err(result)) return result; if (js_truthy(js, result)) { @@ -14826,12 +15401,58 @@ static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size } } return js_true; - } } + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, proxy); + GC_ROOT_PIN(js, value); ant_value_t key_str = js_mkstr(js, key, key_len); - js_setprop(js, target, key_str, value); - return js_true; + GC_ROOT_PIN(js, key_str); + if (is_err(key_str)) { + GC_ROOT_RESTORE(js, root_mark); + return key_str; + } + + ant_value_t own_desc = proxy_get_own_property_descriptor(js, proxy, key_str); + GC_ROOT_PIN(js, own_desc); + if (is_err(own_desc)) { + GC_ROOT_RESTORE(js, root_mark); + return own_desc; + } + + ant_value_t desc = js_mkobj(js); + GC_ROOT_PIN(js, desc); + if (is_err(desc)) { + GC_ROOT_RESTORE(js, root_mark); + return desc; + } + js_setprop(js, desc, js_mkstr(js, "value", 5), value); + if (vtype(own_desc) == T_UNDEF) { + js_setprop(js, desc, js_mkstr(js, "writable", 8), js_true); + js_setprop(js, desc, js_mkstr(js, "enumerable", 10), js_true); + js_setprop(js, desc, js_mkstr(js, "configurable", 12), js_true); + } + + ant_value_t define_trap = proxy_get_method(js, handler, "defineProperty"); + if (is_err(define_trap)) { + GC_ROOT_RESTORE(js, root_mark); + return define_trap; + } + if (vtype(define_trap) == T_UNDEF) { + ant_value_t set_result = js_setprop(js, target, key_str, value); + GC_ROOT_RESTORE(js, root_mark); + if (is_err(set_result)) return set_result; + return js_true; + } + + ant_value_t define_result = proxy_define_property(js, proxy, key_str, desc); + GC_ROOT_RESTORE(js, root_mark); + if (is_err(define_result)) return define_result; + return js_bool(js_truthy(js, define_result)); +} + +static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value) { + return proxy_set_with_receiver(js, proxy, key, key_len, value, proxy); } static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len) { @@ -14842,10 +15463,9 @@ static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size ant_value_t target = data->target; ant_value_t handler = data->handler; - ant_offset_t has_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "has", 3) : 0; - if (has_trap_off != 0) { - ant_value_t has_trap = propref_load(js, has_trap_off); - if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) { + ant_value_t has_trap = proxy_get_method(js, handler, "has"); + if (is_err(has_trap)) return has_trap; + if (vtype(has_trap) != T_UNDEF) { ant_value_t key_val = js_mkstr(js, key, key_len); ant_value_t args[2] = { target, key_val }; ant_value_t result = sv_vm_call(js->vm, js, has_trap, js_mkundef(), args, 2, NULL, false); @@ -14865,7 +15485,6 @@ static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size } return result; - } } char key_buf[256]; @@ -14885,10 +15504,9 @@ static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, s ant_value_t target = data->target; ant_value_t handler = data->handler; - ant_offset_t delete_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "deleteProperty", 14) : 0; - if (delete_trap_off != 0) { - ant_value_t delete_trap = propref_load(js, delete_trap_off); - if (vtype(delete_trap) == T_FUNC || vtype(delete_trap) == T_CFUNC) { + ant_value_t delete_trap = proxy_get_method(js, handler, "deleteProperty"); + if (is_err(delete_trap)) return delete_trap; + if (vtype(delete_trap) != T_UNDEF) { ant_value_t key_val = js_mkstr(js, key, key_len); ant_value_t args[2] = { target, key_val }; ant_value_t result = sv_vm_call(js->vm, js, delete_trap, js_mkundef(), args, 2, NULL, false); @@ -14902,7 +15520,6 @@ static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, s return js_mkerr_typed(js, JS_ERR_TYPE, "'deleteProperty' on proxy: trap returned truthy for non-configurable property"); } return result; - } } ant_value_t key_str = js_mkstr(js, key, key_len); @@ -14930,15 +15547,12 @@ static ant_value_t proxy_get_val(ant_t *js, ant_value_t proxy, ant_value_t key_v if (is_err(prop_key)) return prop_key; }} - ant_offset_t get_trap_off = vtype(handler) == T_OBJ - ? lkp_interned(js, handler, js->intern.get, 3) : 0; - - if (get_trap_off != 0) { - ant_value_t get_trap = propref_load(js, get_trap_off); - if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) { + ant_value_t get_trap = proxy_get_method(js, handler, "get"); + if (is_err(get_trap)) return get_trap; + if (vtype(get_trap) != T_UNDEF) { ant_value_t args[3] = { target, prop_key, proxy }; return sv_vm_call(js->vm, js, get_trap, js_mkundef(), args, 3, NULL, false); - }} + } if (vtype(prop_key) == T_SYMBOL) { ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(prop_key)); @@ -14961,21 +15575,100 @@ static ant_value_t proxy_get_prototype_of(ant_t *js, ant_value_t proxy) { ant_value_t target = data->target; ant_value_t handler = data->handler; - ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "getPrototypeOf", 14) : 0; - - if (trap_off != 0) { - ant_value_t trap = propref_load(js, trap_off); - if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { + ant_value_t trap = proxy_get_method(js, handler, "getPrototypeOf"); + if (is_err(trap)) return trap; + if (vtype(trap) != T_UNDEF) { ant_value_t args[1] = { target }; ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 1, NULL, false); if (is_err(result)) return result; - if (is_object_type(result) || vtype(result) == T_NULL) return result; - return js_mkerr_typed(js, JS_ERR_TYPE, "'getPrototypeOf' on proxy: trap returned neither object nor null"); - }} + if (!is_object_type(result) && vtype(result) != T_NULL) + return js_mkerr_typed(js, JS_ERR_TYPE, "'getPrototypeOf' on proxy: trap returned neither object nor null"); + if (!proxy_target_is_extensible(target)) { + ant_value_t target_proto = get_proto(js, target); + if (!same_object_identity(result, target_proto)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'getPrototypeOf' on proxy: trap returned incompatible prototype for non-extensible target"); + } + return result; + } return get_proto(js, target); } +static ant_value_t proxy_set_prototype_of(ant_t *js, ant_value_t proxy, ant_value_t proto) { + ant_proxy_state_t *data = get_proxy_data(proxy); + if (!data) return js_false; + if (data->revoked) + return throw_proxy_error(js, "Cannot perform 'setPrototypeOf' on a proxy that has been revoked"); + + ant_value_t target = data->target; + ant_value_t handler = data->handler; + ant_value_t trap = proxy_get_method(js, handler, "setPrototypeOf"); + if (is_err(trap)) return trap; + + if (vtype(trap) != T_UNDEF) { + ant_value_t args[2] = { target, proto }; + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 2, NULL, false); + if (is_err(result)) return result; + if (js_truthy(js, result) && !proxy_target_is_extensible(target)) { + ant_value_t current = get_proto(js, target); + if (!same_object_identity(current, proto)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'setPrototypeOf' on proxy: trap returned truthy for incompatible prototype on non-extensible target"); + } + return js_bool(js_truthy(js, result)); + } + + set_proto(js, target, proto); + return js_true; +} + +static ant_value_t proxy_is_extensible(ant_t *js, ant_value_t proxy) { + ant_proxy_state_t *data = get_proxy_data(proxy); + if (!data) return js_false; + if (data->revoked) + return throw_proxy_error(js, "Cannot perform 'isExtensible' on a proxy that has been revoked"); + + ant_value_t target = data->target; + ant_value_t handler = data->handler; + bool target_extensible = proxy_target_is_extensible(target); + ant_value_t trap = proxy_get_method(js, handler, "isExtensible"); + if (is_err(trap)) return trap; + if (vtype(trap) == T_UNDEF) return js_bool(target_extensible); + + ant_value_t args[1] = { target }; + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 1, NULL, false); + if (is_err(result)) return result; + bool trap_extensible = js_truthy(js, result); + if (trap_extensible != target_extensible) + return js_mkerr_typed(js, JS_ERR_TYPE, "'isExtensible' on proxy: trap result does not reflect target extensibility"); + return js_bool(trap_extensible); +} + +static ant_value_t proxy_prevent_extensions(ant_t *js, ant_value_t proxy) { + ant_proxy_state_t *data = get_proxy_data(proxy); + if (!data) return js_false; + if (data->revoked) + return throw_proxy_error(js, "Cannot perform 'preventExtensions' on a proxy that has been revoked"); + + ant_value_t target = data->target; + ant_value_t handler = data->handler; + ant_value_t trap = proxy_get_method(js, handler, "preventExtensions"); + if (is_err(trap)) return trap; + + if (vtype(trap) != T_UNDEF) { + ant_value_t args[1] = { target }; + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 1, NULL, false); + if (is_err(result)) return result; + bool ok = js_truthy(js, result); + if (ok && proxy_target_is_extensible(target)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'preventExtensions' on proxy: trap returned truthy but target is extensible"); + return js_bool(ok); + } + + ant_object_t *ptr = js_obj_ptr(js_as_obj(target)); + if (ptr) ptr->extensible = 0; + return js_true; +} + static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) { ant_proxy_state_t *data = get_proxy_data(proxy); if (!data) return js_false; @@ -14996,13 +15689,12 @@ static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_v if (is_err(prop_key)) return prop_key; }} - ant_offset_t has_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "has", 3) : 0; - if (has_trap_off != 0) { - ant_value_t has_trap = propref_load(js, has_trap_off); - if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) { + ant_value_t has_trap = proxy_get_method(js, handler, "has"); + if (is_err(has_trap)) return has_trap; + if (vtype(has_trap) != T_UNDEF) { ant_value_t args[2] = { target, prop_key }; return sv_vm_call(js->vm, js, has_trap, js_mkundef(), args, 2, NULL, false); - }} + } if (vtype(prop_key) == T_SYMBOL) { ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(prop_key)); @@ -15016,6 +15708,33 @@ static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_v return proxy_has(js, proxy, key_ptr, (size_t)key_len); } +static bool descriptor_has_own_key(ant_t *js, ant_value_t desc, const char *key, size_t key_len) { + return vtype(desc) == T_OBJ && lkp(js, desc, key, key_len) != 0; +} + +static bool descriptor_bool_value(ant_t *js, ant_value_t desc, const char *key, size_t key_len, bool default_value) { + if (vtype(desc) != T_OBJ) return default_value; + ant_offset_t off = lkp(js, desc, key, key_len); + if (off == 0) return default_value; + return js_truthy(js, propref_load(js, off)); +} + +static ant_value_t proxy_complete_property_descriptor(ant_t *js, ant_value_t desc) { + if (!descriptor_has_own_key(js, desc, "enumerable", 10)) + js_setprop(js, desc, js_mkstr(js, "enumerable", 10), js_false); + if (!descriptor_has_own_key(js, desc, "configurable", 12)) + js_setprop(js, desc, js_mkstr(js, "configurable", 12), js_false); + + bool has_value = descriptor_has_own_key(js, desc, "value", 5); + bool has_writable = descriptor_has_own_key(js, desc, "writable", 8); + bool has_get = descriptor_has_own_key(js, desc, "get", 3); + bool has_set = descriptor_has_own_key(js, desc, "set", 3); + if ((has_value || has_writable || (!has_get && !has_set)) && !has_writable) + js_setprop(js, desc, js_mkstr(js, "writable", 8), js_false); + + return desc; +} + static ant_value_t proxy_get_own_property_descriptor(ant_t *js, ant_value_t proxy, ant_value_t key_val) { ant_proxy_state_t *data = get_proxy_data(proxy); if (!data) return js_mkundef(); @@ -15025,17 +15744,40 @@ static ant_value_t proxy_get_own_property_descriptor(ant_t *js, ant_value_t prox ant_value_t target = data->target; ant_value_t handler = data->handler; - ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "getOwnPropertyDescriptor", 24) : 0; - - if (trap_off != 0) { - ant_value_t trap = propref_load(js, trap_off); - if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { + ant_value_t trap = proxy_get_method(js, handler, "getOwnPropertyDescriptor"); + if (is_err(trap)) return trap; + if (vtype(trap) != T_UNDEF) { ant_value_t args[2] = { target, key_val }; ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 2, NULL, false); if (is_err(result)) return result; - if (vtype(result) == T_UNDEF || vtype(result) == T_OBJ) return result; - return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined"); - }} + if (vtype(result) != T_UNDEF && vtype(result) != T_OBJ) + return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined"); + + ant_value_t target_args[2] = { target, key_val }; + ant_value_t target_desc = builtin_object_getOwnPropertyDescriptor(js, target_args, 2); + if (is_err(target_desc)) return target_desc; + bool target_exists = vtype(target_desc) != T_UNDEF; + bool target_configurable = target_exists + ? descriptor_bool_value(js, target_desc, "configurable", 12, false) + : true; + bool target_extensible = proxy_target_is_extensible(target); + + if (vtype(result) == T_UNDEF) { + if (target_exists && (!target_configurable || !target_extensible)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap reported existing property as undefined"); + return result; + } + + bool result_configurable = descriptor_bool_value(js, result, "configurable", 12, false); + if (!target_exists) { + if (!target_extensible || !result_configurable) + return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap reported invalid new property descriptor"); + } else if (!result_configurable && target_configurable) { + return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap reported configurable property as non-configurable"); + } + + return proxy_complete_property_descriptor(js, result); + } ant_value_t args[2] = { target, key_val }; return builtin_object_getOwnPropertyDescriptor(js, args, 2); @@ -15055,14 +15797,32 @@ static ant_value_t proxy_define_property(ant_t *js, ant_value_t proxy, ant_value ant_value_t target = data->target; ant_value_t handler = data->handler; - ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "defineProperty", 14) : 0; - - if (trap_off != 0) { - ant_value_t trap = propref_load(js, trap_off); - if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { + ant_value_t trap = proxy_get_method(js, handler, "defineProperty"); + if (is_err(trap)) return trap; + if (vtype(trap) != T_UNDEF) { ant_value_t args[3] = { target, key_val, descriptor }; - return sv_vm_call(js->vm, js, trap, js_mkundef(), args, 3, NULL, false); - }} + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 3, NULL, false); + if (is_err(result) || !js_truthy(js, result)) return result; + + ant_value_t target_args[2] = { target, key_val }; + ant_value_t target_desc = builtin_object_getOwnPropertyDescriptor(js, target_args, 2); + if (is_err(target_desc)) return target_desc; + + bool target_exists = vtype(target_desc) != T_UNDEF; + bool target_configurable = target_exists + ? descriptor_bool_value(js, target_desc, "configurable", 12, false) + : true; + bool target_extensible = proxy_target_is_extensible(target); + bool desc_has_configurable = descriptor_has_own_key(js, descriptor, "configurable", 12); + bool desc_configurable = descriptor_bool_value(js, descriptor, "configurable", 12, false); + + if (!target_exists && !target_extensible) + return js_mkerr_typed(js, JS_ERR_TYPE, "'defineProperty' on proxy: trap returned truthy for adding property to non-extensible target"); + if (desc_has_configurable && !desc_configurable && (!target_exists || target_configurable)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'defineProperty' on proxy: trap returned truthy for defining invalid non-configurable property"); + + return js_true; + } ant_value_t args[3] = { target, key_val, descriptor }; ant_value_t result = builtin_object_defineProperty(js, args, 3); @@ -15078,10 +15838,9 @@ static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t ke ant_value_t target = data->target; ant_value_t handler = data->handler; - ant_offset_t delete_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "deleteProperty", 14) : 0; - if (delete_trap_off != 0) { - ant_value_t delete_trap = propref_load(js, delete_trap_off); - if (vtype(delete_trap) == T_FUNC || vtype(delete_trap) == T_CFUNC) { + ant_value_t delete_trap = proxy_get_method(js, handler, "deleteProperty"); + if (is_err(delete_trap)) return delete_trap; + if (vtype(delete_trap) != T_UNDEF) { ant_value_t args[2] = { target, key_val }; ant_value_t result = sv_vm_call(js->vm, js, delete_trap, js_mkundef(), args, 2, NULL, false); if (is_err(result)) return result; @@ -15090,7 +15849,6 @@ static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t ke if (prop_off != 0 && is_nonconfig_prop(js, prop_off)) return js_mkerr_typed(js, JS_ERR_TYPE, "'deleteProperty' on proxy: trap returned truthy for non-configurable property"); } return result; - } } if (vtype(key_val) == T_SYMBOL) return js_delete_sym_prop(js, target, key_val); return js_true; @@ -15108,16 +15866,14 @@ ant_value_t js_proxy_apply(ant_t *js, ant_value_t proxy, ant_value_t this_arg, a if (target_type != T_FUNC && target_type != T_CFUNC && !(target_type == T_OBJ && is_proxy(target))) return js_mkerr_typed(js, JS_ERR_TYPE, "%s is not a function", typestr(target_type)); - ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "apply", 5) : 0; - if (trap_off != 0) { - ant_value_t trap = propref_load(js, trap_off); - if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { + ant_value_t trap = proxy_get_method(js, handler, "apply"); + if (is_err(trap)) return trap; + if (vtype(trap) != T_UNDEF) { ant_value_t args_arr = mkarr(js); for (int i = 0; i < argc; i++) js_arr_push(js, args_arr, args[i]); ant_value_t trap_args[3] = { target, this_arg, args_arr }; return sv_vm_call(js->vm, js, trap, handler, trap_args, 3, NULL, false); - } } return sv_vm_call(js->vm, js, target, this_arg, args, argc, NULL, false); @@ -15138,10 +15894,9 @@ ant_value_t js_proxy_construct(ant_t *js, ant_value_t proxy, ant_value_t *args, if (vtype(target) != T_FUNC) return js_mkerr_typed(js, JS_ERR_TYPE, "not a constructor"); - ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "construct", 9) : 0; - if (trap_off != 0) { - ant_value_t trap = propref_load(js, trap_off); - if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { + ant_value_t trap = proxy_get_method(js, handler, "construct"); + if (is_err(trap)) return trap; + if (vtype(trap) != T_UNDEF) { ant_value_t args_arr = mkarr(js); for (int i = 0; i < argc; i++) js_arr_push(js, args_arr, args[i]); @@ -15151,11 +15906,10 @@ ant_value_t js_proxy_construct(ant_t *js, ant_value_t proxy, ant_value_t *args, if (!is_object_type(result)) return js_mkerr_typed(js, JS_ERR_TYPE, "'construct' on proxy: trap returned non-Object"); return result; - } } ant_value_t obj = mkobj(js, 0); - ant_value_t proto = js_getprop_fallback(js, target, "prototype"); + ant_value_t proto = js_getprop_fallback(js, new_target, "prototype"); if (is_object_type(proto)) js_set_proto_init(obj, proto); ant_value_t saved = js->new_target; js->new_target = new_target; @@ -16594,8 +17348,31 @@ ant_value_t js_getprop_proto(ant_t *js, ant_value_t obj, const char *key) { ant_value_t js_getprop_fallback(ant_t *js, ant_value_t obj, const char *name) { ant_value_t val; if (js_try_get(js, obj, name, &val)) return val; - - return js_getprop_proto(js, obj, name); + + size_t key_len = strlen(name); + ant_value_t cur = obj; + uint8_t t = vtype(cur); + proto_walk_overflow_guard_t guard; + proto_walk_overflow_guard_init(&guard); + + while (proto_walk_next(js, &cur, &t, PROTO_WALK_F_LOOKUP)) { + if (is_object_type(cur)) { + ant_value_t cur_obj = js_as_obj(cur); + if (is_proxy(cur_obj)) + return proxy_get_with_receiver(js, cur_obj, name, key_len, obj); + ant_offset_t off = lkp(js, cur_obj, name, key_len); + if (off != 0) return propref_load(js, off); + } else if (t == T_CFUNC) { + ant_value_t promoted = js_cfunc_lookup_promoted(js, cur); + if (vtype(promoted) == T_FUNC) { + ant_offset_t off = lkp(js, js_func_obj(promoted), name, key_len); + if (off != 0) return propref_load(js, off); + } + } + if (proto_walk_overflow_guard_hit_cycle(js, &guard, cur, t, PROTO_WALK_F_LOOKUP)) break; + } + + return js_mkundef(); } ant_value_t js_getprop_super(ant_t *js, ant_value_t super_obj, ant_value_t receiver, const char *name) { diff --git a/src/modules/json.c b/src/modules/json.c index 06ed2d6..48c0bd2 100644 --- a/src/modules/json.c +++ b/src/modules/json.c @@ -219,10 +219,6 @@ static inline int key_matches(const char *a, size_t a_len, const char *b, size_t return a_len == b_len && memcmp(a, b, a_len) == 0; } -static inline bool json_is_array(ant_value_t value) { - return vtype(value) == T_ARR; -} - static inline ant_value_t json_snapshot_keys(ant_t *js, ant_value_t value) { if (!is_special_object(value)) return js_mkarr(js); return js_for_in_keys(js, value); @@ -443,7 +439,7 @@ static yyjson_mut_val *ant_value_to_yyjson_impl(ant_t *js, yyjson_mut_doc *doc, if (json_cycle_check(ctx, val)) return NULL; json_cycle_push(ctx, val); - result = json_is_array(val) + result = is_array_value(val) ? json_array_to_yyjson(js, doc, val, ctx) : json_object_to_yyjson(js, doc, val, ctx); @@ -542,7 +538,7 @@ static ant_value_t apply_reviver( ) { ant_value_t val = js_get(js, holder, key); - if (json_is_array(val)) apply_reviver_to_array(js, val, reviver, roots); + if (is_array_value(val)) apply_reviver_to_array(js, val, reviver, roots); else if (vtype(val) == T_OBJ) apply_reviver_to_object(js, val, reviver, roots); return apply_reviver_call(js, holder, key, reviver, roots); diff --git a/src/modules/regex.c b/src/modules/regex.c index 7e6a73a..43c90e8 100644 --- a/src/modules/regex.c +++ b/src/modules/regex.c @@ -1808,6 +1808,14 @@ static ant_value_t string_replace_impl(ant_t *js, ant_value_t *args, int nargs, ant_value_t result = maybe_call_symbol_method(js, args[0], get_replace_sym(), args[0], call_args, 2, &called); if (is_err(result)) return result; if (called) return result; + + ant_value_t coerced_search = js_to_primitive(js, args[0], 1); + if (is_err(coerced_search)) return coerced_search; + + coerced_search = js_tostring_val(js, coerced_search); + if (is_err(coerced_search)) return coerced_search; + + args[0] = coerced_search; } if (nargs < 2) return str; @@ -1948,7 +1956,13 @@ static ant_value_t builtin_string_search(ant_t *js, ant_value_t *args, int nargs if (vtype(pattern) == T_OBJ) { ant_offset_t source_off = lkp(js, pattern, "source", 6); - if (source_off == 0) return tov(-1); + if (source_off == 0) { + pattern = js_to_primitive(js, pattern, 1); + if (is_err(pattern)) return pattern; + pattern = js_tostring_val(js, pattern); + if (is_err(pattern)) return pattern; + goto search_string_pattern; + } ant_value_t source_val = js_propref_load(js, source_off); if (vtype(source_val) != T_STR) return tov(-1); @@ -1969,12 +1983,11 @@ static ant_value_t builtin_string_search(ant_t *js, ant_value_t *args, int nargs } } } else if (vtype(pattern) == T_STR) { +search_string_pattern:; ant_offset_t poff; poff = vstr(js, pattern, &pattern_len); pattern_ptr = (char *)(uintptr_t)(poff); - } else { - return tov(-1); - } + } else return tov(-1); ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (char *)(uintptr_t)(str_off); @@ -2034,7 +2047,13 @@ static ant_value_t builtin_string_match(ant_t *js, ant_value_t *args, int nargs) if (vtype(pattern) == T_OBJ) { ant_offset_t source_off = lkp(js, pattern, "source", 6); - if (source_off == 0) return js_mknull(); + if (source_off == 0) { + pattern = js_to_primitive(js, pattern, 1); + if (is_err(pattern)) return pattern; + pattern = js_tostring_val(js, pattern); + if (is_err(pattern)) return pattern; + goto match_string_pattern; + } ant_value_t source_val = js_propref_load(js, source_off); if (vtype(source_val) != T_STR) return js_mknull(); @@ -2056,6 +2075,7 @@ static ant_value_t builtin_string_match(ant_t *js, ant_value_t *args, int nargs) }} } } else if (vtype(pattern) == T_STR) { +match_string_pattern:; ant_offset_t poff; poff = vstr(js, pattern, &pattern_len); pattern_ptr = (char *)(uintptr_t)(poff); diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 88fe854..28f0d6f 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -2337,7 +2337,8 @@ void compile_typeof(sv_compiler_t *c, sv_ast_t *node) { emit_op(c, OP_SPECIAL_OBJ); emit(c, 0); } - } else emit_atom_op(c, OP_GET_GLOBAL_UNDEF, arg->str, arg->len); + } else if (c->with_depth > 0) emit_with_get(c, arg->str, arg->len, WITH_FB_GLOBAL_UNDEF, 0); + else emit_atom_op(c, OP_GET_GLOBAL_UNDEF, arg->str, arg->len); } } else compile_expr(c, arg); emit_op(c, OP_TYPEOF); diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index c0fcfbd..eede0e5 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -1,13 +1,15 @@ #ifndef SV_COERCION_H #define SV_COERCION_H -#include "silver/engine.h" -#include "esm/loader.h" #include #include "globals.h" #include "property.h" +#include "esm/loader.h" +#include "silver/engine.h" +#include "modules/symbol.h" + static inline ant_value_t sv_module_export_cstr( ant_t *js, const char *name, size_t len, @@ -200,10 +202,11 @@ static inline void sv_op_exit_with(sv_vm_t *vm, sv_frame_t *frame) { } enum { - WITH_FB_GLOBAL = 0, - WITH_FB_LOCAL = 1, - WITH_FB_ARG = 2, - WITH_FB_UPVAL = 3 + WITH_FB_GLOBAL = 0, + WITH_FB_LOCAL = 1, + WITH_FB_ARG = 2, + WITH_FB_UPVAL = 3, + WITH_FB_GLOBAL_UNDEF = 4 }; static inline ant_value_t sv_with_fallback_get( @@ -219,9 +222,39 @@ switch (kind) { sv_upvalue_t *uv = frame->upvalues[idx]; return uv ? *uv->location : js_mkundef(); } + case WITH_FB_GLOBAL_UNDEF: + return js_mkundef(); default: return sv_global_get(js, NULL, 0); }} +static inline bool sv_with_binding_is_unscopable( + ant_t *js, + ant_value_t with_obj, + const sv_atom_t *a, + ant_value_t *out, + bool *abrupt +) { + *abrupt = false; + + ant_value_t unscopables = js_get_sym(js, with_obj, get_unscopables_sym()); + if (is_err(unscopables)) { + *out = unscopables; + *abrupt = true; + return false; + } + + if (!is_object_type(unscopables)) return false; + + ant_value_t blocked = js_getprop_fallback(js, unscopables, a->str); + if (is_err(blocked)) { + *out = blocked; + *abrupt = true; + return false; + } + + return js_truthy(js, blocked); +} + static inline void sv_with_fallback_put( sv_vm_t *vm, ant_t *js, sv_frame_t *frame, @@ -254,13 +287,29 @@ static inline bool sv_try_get_with_bound_value( ant_object_t *ptr = is_object_type(with_obj) ? js_obj_ptr(js_as_obj(with_obj)) : NULL; const char *interned = intern_string(a->str, a->len); + if (ptr && is_proxy(js_as_obj(with_obj))) { + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, with_obj, a, out, &abrupt)) return false; + if (abrupt) return true; + *out = js_getprop_fallback(js, with_obj, a->str); + return true; + } + if (ptr && interned) { bool should_fallback = false; - if (sv_try_get_shape_data_prop(js, ptr, interned, out, &should_fallback)) return true; + if (sv_try_get_shape_data_prop(js, ptr, interned, out, &should_fallback)) { + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, with_obj, a, out, &abrupt)) return false; + if (abrupt) return true; + return true; + } if (!should_fallback) return false; } if (lkp(js, with_obj, a->str, a->len) == 0) return false; + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, with_obj, a, out, &abrupt)) return false; + if (abrupt) return true; *out = sv_getprop_fallback_len(js, with_obj, a->str, (ant_offset_t)a->len); return true; @@ -284,9 +333,9 @@ static inline ant_value_t sv_op_with_get_var( return js_mkundef(); }} - if (fb_kind == WITH_FB_GLOBAL) { + if (fb_kind == WITH_FB_GLOBAL || fb_kind == WITH_FB_GLOBAL_UNDEF) { ant_value_t val = sv_global_get(js, a->str, a->len); - if (is_undefined(val)) + if (is_undefined(val) && fb_kind == WITH_FB_GLOBAL) return js_mkerr_typed( js, JS_ERR_REFERENCE, "'%.*s' is not defined", (int)a->len, a->str); -- 2.51.2 From 373fd43cc4bae4535de4154ece2019a73c944de7 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 13:38:48 -0700 Subject: [PATCH 03/16] better IsArray check for userland --- include/internal.h | 1 + src/ant.c | 80 +++++++++++++++++++++++++++++++++++----------- src/modules/napi.c | 7 +++- src/modules/url.c | 11 +++++-- 4 files changed, 78 insertions(+), 21 deletions(-) diff --git a/include/internal.h b/include/internal.h index c4c8786..a7e1590 100644 --- a/include/internal.h +++ b/include/internal.h @@ -487,6 +487,7 @@ const char *typestr(uint8_t t); ant_value_t unwrap_primitive(ant_t *js, ant_value_t val); ant_value_t do_string_op(ant_t *js, uint8_t op, ant_value_t l, ant_value_t r); ant_value_t js_to_primitive(ant_t *js, ant_value_t value, int hint); +ant_value_t js_is_array_value_checked(ant_t *js, ant_value_t value, bool *out); ant_value_t do_instanceof(ant_t *js, ant_value_t l, ant_value_t r); ant_value_t do_in(ant_t *js, ant_value_t l, ant_value_t r); diff --git a/src/ant.c b/src/ant.c index 0b62733..47ad8e4 100644 --- a/src/ant.c +++ b/src/ant.c @@ -10264,17 +10264,29 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) return accumulator; } -static inline void flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) { +static inline ant_value_t flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) { ant_offset_t len = proxy_aware_length(js, arr); - if (len == 0) return; + if (len == 0) return js_mkundef(); for (ant_offset_t i = 0; i < len; i++) { - if (!arr_has(js, arr, i)) continue; - ant_value_t val = arr_get(js, arr, i); - - if (depth > 0 && vtype(val) == T_ARR) flat_helper(js, val, result, result_idx, depth - 1); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t val = array_method_get_index(js, arr, i); + if (is_err(val)) return val; + + bool val_is_array = false; + if (depth > 0) { + ant_value_t is_array_res = js_is_array_value_checked(js, val, &val_is_array); + if (is_err(is_array_res)) return is_array_res; + } + + if (val_is_array) { + ant_value_t flat_res = flat_helper(js, val, result, result_idx, depth - 1); + if (is_err(flat_res)) return flat_res; + } else { arr_set(js, result, *result_idx, val); (*result_idx)++; } } + + return js_mkundef(); } static ant_value_t builtin_array_flat(ant_t *js, ant_value_t *args, int nargs) { @@ -10293,7 +10305,8 @@ static ant_value_t builtin_array_flat(ant_t *js, ant_value_t *args, int nargs) { if (is_err(result)) return result; ant_offset_t result_idx = 0; - flat_helper(js, arr, result, &result_idx, depth); + ant_value_t flat_res = flat_helper(js, arr, result, &result_idx, depth); + if (is_err(flat_res)) return flat_res; return result; } @@ -10485,11 +10498,20 @@ static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs ant_offset_t result_idx = 0; for (ant_offset_t i = 0; i < len; i++) { - ant_value_t elem = arr_get(js, arr, i); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t elem = array_method_get_index(js, arr, i); + if (is_err(elem)) return elem; ant_value_t call_args[3] = { elem, tov((double)i), arr }; ant_value_t mapped = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); if (is_err(mapped)) return mapped; - if (vtype(mapped) == T_ARR) flat_helper(js, mapped, result, &result_idx, 0); + bool mapped_is_array = false; + ant_value_t is_array_res = js_is_array_value_checked(js, mapped, &mapped_is_array); + if (is_err(is_array_res)) return is_array_res; + + if (mapped_is_array) { + ant_value_t flat_res = flat_helper(js, mapped, result, &result_idx, 0); + if (is_err(flat_res)) return flat_res; + } else arr_set(js, result, result_idx++, mapped); } @@ -11371,9 +11393,10 @@ static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, in (void) args; (void) nargs; ant_value_t arr = js->this_val; - if (vtype(arr) != T_ARR) return js_mkerr(js, "toLocaleString called on non-array"); + if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) + return js_mkerr(js, "toLocaleString called on non-array"); - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = proxy_aware_length(js, arr); if (len == 0) return js_mkstr(js, "", 0); char *result = NULL; @@ -11394,8 +11417,9 @@ static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, in result[result_len++] = ','; } - if (!arr_has(js, arr, i)) continue; - ant_value_t elem = arr_get(js, arr, i); + if (!array_method_has_index(js, arr, i)) continue; + ant_value_t elem = array_method_get_index(js, arr, i); + if (is_err(elem)) { free(result); return elem; } if (vtype(elem) == T_NULL || vtype(elem) == T_UNDEF) continue; char buf[64]; @@ -11420,7 +11444,10 @@ static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, in static ant_value_t builtin_Array_isArray(ant_params_t) { if (nargs == 0) return mkval(T_BOOL, 0); - return mkval(T_BOOL, is_array_value(args[0]) ? 1 : 0); + bool result = false; + ant_value_t is_array_res = js_is_array_value_checked(js, args[0], &result); + if (is_err(is_array_res)) return is_array_res; + return mkval(T_BOOL, result ? 1 : 0); } static ant_value_t builtin_Array_isTemplateObject(ant_params_t) { @@ -14650,7 +14677,7 @@ static ant_value_t handle_proxy_instanceof(ant_t *js, ant_value_t l, ant_value_t return mkval(T_BOOL, proto_chain_contains_cycle_safe(js, current, proto_val) ? 1 : 0); } -static ant_value_t handle_cfunc_instanceof(ant_value_t l, ant_value_t r, uint8_t ltype) { +static ant_value_t handle_cfunc_instanceof(ant_t *js, ant_value_t l, ant_value_t r, uint8_t ltype) { ant_value_t (*fn)(ant_t *, ant_value_t *, int) = js_as_cfunc(r); if (fn == builtin_Object) return mkval(T_BOOL, ltype == T_OBJ ? 1 : 0); @@ -14658,7 +14685,12 @@ static ant_value_t handle_cfunc_instanceof(ant_value_t l, ant_value_t r, uint8_t if (fn == builtin_String) return mkval(T_BOOL, ltype == T_STR ? 1 : 0); if (fn == builtin_Number) return mkval(T_BOOL, ltype == T_NUM ? 1 : 0); if (fn == builtin_Boolean) return mkval(T_BOOL, ltype == T_BOOL ? 1 : 0); - if (fn == builtin_Array) return mkval(T_BOOL, ltype == T_ARR ? 1 : 0); + if (fn == builtin_Array) { + bool result = false; + ant_value_t is_array_res = js_is_array_value_checked(js, l, &result); + if (is_err(is_array_res)) return is_array_res; + return mkval(T_BOOL, result ? 1 : 0); + } if (fn == builtin_Promise) return mkval(T_BOOL, ltype == T_PROMISE ? 1 : 0); return mkval(T_BOOL, 0); @@ -14692,7 +14724,8 @@ static bool proto_chain_contains_cycle_safe(ant_t *js, ant_value_t start, ant_va } static ant_value_t walk_prototype_chain(ant_t *js, ant_value_t l, ant_value_t ctor_proto) { - ant_value_t current = get_proto(js, l); + ant_value_t current = is_proxy(l) ? proxy_get_prototype_of(js, l) : get_proto(js, l); + if (is_err(current)) return current; return mkval(T_BOOL, proto_chain_contains_cycle_safe(js, current, ctor_proto) ? 1 : 0); } @@ -14715,7 +14748,7 @@ ant_value_t do_instanceof(ant_t *js, ant_value_t l, ant_value_t r) { } if (rtype == T_CFUNC) { - return handle_cfunc_instanceof(l, r, ltype); + return handle_cfunc_instanceof(js, l, r, ltype); } ant_value_t func_obj = js_func_obj(r); @@ -15194,6 +15227,17 @@ bool is_array_value(ant_value_t value) { return vtype(value) == T_ARR; } +ant_value_t js_is_array_value_checked(ant_t *js, ant_value_t value, bool *out) { + while (is_proxy(value)) { + ant_proxy_state_t *data = get_proxy_data(value); + if (!data || data->revoked) + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot perform IsArray on a revoked proxy"); + value = data->target; + } + *out = vtype(value) == T_ARR; + return js_mkundef(); +} + bool js_is_constructor(ant_value_t value) { ant_value_t slow = value; ant_value_t fast = value; diff --git a/src/modules/napi.c b/src/modules/napi.c index c896a3d..d0809dc 100644 --- a/src/modules/napi.c +++ b/src/modules/napi.c @@ -2466,7 +2466,12 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_is_array( bool *result ) { if (!env || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); - *result = vtype((ant_value_t)value) == T_ARR; + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + + if (!nenv || !nenv->js) return napi_set_last(env, napi_invalid_arg, "invalid env"); + ant_value_t r = js_is_array_value_checked(nenv->js, (ant_value_t)value, result); + + if (is_err(r) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, r); return napi_set_last(env, napi_ok, NULL); } diff --git a/src/modules/url.c b/src/modules/url.c index 8c10a65..549ca76 100644 --- a/src/modules/url.c +++ b/src/modules/url.c @@ -1345,11 +1345,18 @@ static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) { return obj; } - if (t == T_ARR) { + bool init_is_array = false; + ant_value_t is_array_res = js_is_array_value_checked(js, init, &init_is_array); + if (is_err(is_array_res)) return is_array_res; + + if (init_is_array) { ant_offset_t len = js_arr_len(js, init); for (ant_offset_t i = 0; i < len; i++) { ant_value_t pair = js_arr_get(js, init, i); - if (vtype(pair) != T_ARR) + bool pair_is_array = false; + is_array_res = js_is_array_value_checked(js, pair, &pair_is_array); + if (is_err(is_array_res)) return is_array_res; + if (!pair_is_array) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URLSearchParams': Each element must be an array."); -- 2.51.2 From c18defd3e488e3eb77c90792eedea2829ae3e826 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 16:49:22 -0700 Subject: [PATCH 04/16] improve performance --- src/ant.c | 70 ++++++++++++++++++++++-------- src/silver/ops/coercion.h | 90 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 20 deletions(-) diff --git a/src/ant.c b/src/ant.c index 47ad8e4..8a57178 100644 --- a/src/ant.c +++ b/src/ant.c @@ -5605,13 +5605,16 @@ static ant_value_t builtin_function_call(ant_t *js, ant_value_t *args, int nargs } int extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args) { - int len = (int) proxy_aware_length(js, arr); + int len = (int)(vtype(arr) == T_ARR ? get_array_length(js, arr) : proxy_aware_length(js, arr)); if (len <= 0) return 0; ant_value_t *args_out = (ant_value_t *)ant_calloc(sizeof(ant_value_t) * len); if (!args_out) return 0; - - for (int i = 0; i < len; i++) { + + if (vtype(arr) == T_ARR) { + for (int i = 0; i < len; i++) + args_out[i] = arr_get(js, arr, (ant_offset_t)i); + } else for (int i = 0; i < len; i++) { ant_value_t item = array_method_get_index(js, arr, (ant_offset_t)i); args_out[i] = is_err(item) ? js_mkundef() : item; } @@ -10265,18 +10268,25 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) } static inline ant_value_t flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) { - ant_offset_t len = proxy_aware_length(js, arr); + bool input_is_array = vtype(arr) == T_ARR; + ant_offset_t len = input_is_array ? get_array_length(js, arr) : proxy_aware_length(js, arr); if (len == 0) return js_mkundef(); for (ant_offset_t i = 0; i < len; i++) { - if (!array_method_has_index(js, arr, i)) continue; - ant_value_t val = array_method_get_index(js, arr, i); + if (input_is_array) { + if (!arr_has(js, arr, i)) continue; + } else if (!array_method_has_index(js, arr, i)) continue; + + ant_value_t val = input_is_array ? arr_get(js, arr, i) : array_method_get_index(js, arr, i); if (is_err(val)) return val; bool val_is_array = false; if (depth > 0) { - ant_value_t is_array_res = js_is_array_value_checked(js, val, &val_is_array); - if (is_err(is_array_res)) return is_array_res; + val_is_array = vtype(val) == T_ARR; + if (!val_is_array && is_proxy(val)) { + ant_value_t is_array_res = js_is_array_value_checked(js, val, &val_is_array); + if (is_err(is_array_res)) return is_array_res; + } } if (val_is_array) { @@ -10491,22 +10501,28 @@ static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs ant_value_t callback = args[0]; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + bool input_is_array = vtype(arr) == T_ARR; + ant_offset_t len = input_is_array ? get_array_length(js, arr) : proxy_aware_length(js, arr); ant_value_t result = mkarr(js); if (is_err(result)) return result; ant_offset_t result_idx = 0; for (ant_offset_t i = 0; i < len; i++) { - if (!array_method_has_index(js, arr, i)) continue; - ant_value_t elem = array_method_get_index(js, arr, i); + if (input_is_array) { + if (!arr_has(js, arr, i)) continue; + } else if (!array_method_has_index(js, arr, i)) continue; + + ant_value_t elem = input_is_array ? arr_get(js, arr, i) : array_method_get_index(js, arr, i); if (is_err(elem)) return elem; ant_value_t call_args[3] = { elem, tov((double)i), arr }; ant_value_t mapped = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); if (is_err(mapped)) return mapped; - bool mapped_is_array = false; - ant_value_t is_array_res = js_is_array_value_checked(js, mapped, &mapped_is_array); - if (is_err(is_array_res)) return is_array_res; + bool mapped_is_array = vtype(mapped) == T_ARR; + if (!mapped_is_array && is_proxy(mapped)) { + ant_value_t is_array_res = js_is_array_value_checked(js, mapped, &mapped_is_array); + if (is_err(is_array_res)) return is_array_res; + } if (mapped_is_array) { ant_value_t flat_res = flat_helper(js, mapped, result, &result_idx, 0); @@ -11396,7 +11412,8 @@ static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, in if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) return js_mkerr(js, "toLocaleString called on non-array"); - ant_offset_t len = proxy_aware_length(js, arr); + bool input_is_array = vtype(arr) == T_ARR; + ant_offset_t len = input_is_array ? get_array_length(js, arr) : proxy_aware_length(js, arr); if (len == 0) return js_mkstr(js, "", 0); char *result = NULL; @@ -11417,8 +11434,11 @@ static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, in result[result_len++] = ','; } - if (!array_method_has_index(js, arr, i)) continue; - ant_value_t elem = array_method_get_index(js, arr, i); + if (input_is_array) { + if (!arr_has(js, arr, i)) continue; + } else if (!array_method_has_index(js, arr, i)) continue; + + ant_value_t elem = input_is_array ? arr_get(js, arr, i) : array_method_get_index(js, arr, i); if (is_err(elem)) { free(result); return elem; } if (vtype(elem) == T_NULL || vtype(elem) == T_UNDEF) continue; @@ -11545,17 +11565,31 @@ static ant_value_t builtin_Array_from(ant_t *js, ant_value_t *args, int nargs) { } else { ant_value_t iter_method = js_mkundef(); ant_offset_t iter_prop = 0; + bool src_is_array = false; if (vtype(iter_sym) == T_SYMBOL) { if (is_proxy(src)) { iter_method = js_get_sym(js, src, iter_sym); if (is_err(iter_method)) return iter_method; + ant_value_t target = proxy_read_target(js, src); + src_is_array = vtype(target) == T_ARR; } else { iter_prop = lkp_sym_proto(js, src, (ant_offset_t)vdata(iter_sym)); if (iter_prop != 0) iter_method = propref_load(js, iter_prop); } } - if (vtype(iter_method) != T_UNDEF) { + if (src_is_array && vtype(iter_method) == T_CFUNC) { + array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 }; + ant_offset_t len = proxy_aware_length(js, src); + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t unused; + ant_value_t item = array_method_get_index(js, src, i); + if (is_err(item)) return item; + iter_action_t act = array_from_iter_cb(js, item, &ctx, &unused); + if (act == ITER_ERROR) return unused; + } + if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)len)); + } else if (vtype(iter_method) != T_UNDEF) { array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 }; ant_value_t iter_result = iter_foreach(js, src, array_from_iter_cb, &ctx); if (is_err(iter_result)) return iter_result; diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index eede0e5..3e9e501 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -236,7 +236,67 @@ static inline bool sv_with_binding_is_unscopable( ) { *abrupt = false; - ant_value_t unscopables = js_get_sym(js, with_obj, get_unscopables_sym()); + ant_value_t unscopables_sym = get_unscopables_sym(); + if (vtype(unscopables_sym) != T_SYMBOL) return false; + + bool is_proxy_obj = is_proxy(js_as_obj(with_obj)); + ant_value_t unscopables = js_mkundef(); + + if (!is_proxy_obj) { + ant_offset_t sym_off = (ant_offset_t)vdata(unscopables_sym); + ant_object_t *base_ptr = js_obj_ptr(js_as_obj(with_obj)); + ant_value_t base_proto = (base_ptr && is_object_type(base_ptr->proto)) ? base_ptr->proto : js_mknull(); + ant_object_t *proto_ptr = is_object_type(base_proto) ? js_obj_ptr(js_as_obj(base_proto)) : NULL; + + static ant_object_t *cached_no_base = NULL; + static void *cached_no_base_shape = NULL; + static ant_value_t cached_no_proto = 0; + static void *cached_no_proto_shape = NULL; + + if ( + base_ptr == cached_no_base && + (void *)(base_ptr ? base_ptr->shape : NULL) == cached_no_base_shape && + base_proto == cached_no_proto && + (void *)(proto_ptr ? proto_ptr->shape : NULL) == cached_no_proto_shape + ) return false; + + ant_offset_t unscopables_off = lkp_sym_proto(js, with_obj, sym_off); + bool has_unscopables = unscopables_off != 0; + bool saw_exotic = base_ptr && base_ptr->is_exotic; + + if (!has_unscopables) { + ant_value_t cur = with_obj; + while (is_object_type(cur)) { + ant_value_t cur_obj = js_as_obj(cur); + ant_object_t *cur_ptr = js_obj_ptr(cur_obj); + if (cur_ptr && cur_ptr->is_exotic) saw_exotic = true; + prop_meta_t meta; + if (cur_ptr && cur_ptr->is_exotic && lookup_symbol_prop_meta(cur_obj, sym_off, &meta)) { + has_unscopables = true; + break; + } + + ant_value_t proto = js_get_proto(js, cur_obj); + if (!is_object_type(proto)) break; + cur = proto; + } + } + + if (!has_unscopables) { + ant_value_t proto_proto = (proto_ptr && is_object_type(proto_ptr->proto)) ? proto_ptr->proto : js_mknull(); + if (!saw_exotic && !is_object_type(proto_proto)) { + cached_no_base = base_ptr; + cached_no_base_shape = (void *)(base_ptr ? base_ptr->shape : NULL); + cached_no_proto = base_proto; + cached_no_proto_shape = (void *)(proto_ptr ? proto_ptr->shape : NULL); + } + return false; + } + if (unscopables_off != 0) unscopables = js_propref_load(js, unscopables_off); + } + + if (is_proxy_obj || vtype(unscopables) == T_UNDEF) + unscopables = js_get_sym(js, with_obj, unscopables_sym); if (is_err(unscopables)) { *out = unscopables; *abrupt = true; @@ -245,7 +305,33 @@ static inline bool sv_with_binding_is_unscopable( if (!is_object_type(unscopables)) return false; - ant_value_t blocked = js_getprop_fallback(js, unscopables, a->str); + ant_value_t blocked = js_mkundef(); + bool got_blocked_fast = false; + + if (!is_proxy(js_as_obj(unscopables))) { + ant_value_t cur = unscopables; + while (is_object_type(cur)) { + ant_value_t cur_obj = js_as_obj(cur); + prop_meta_t meta; + if (lookup_string_prop_meta(js, cur_obj, a->str, a->len, &meta)) { + if (meta.has_getter || meta.has_setter) break; + ant_offset_t off = lkp(js, cur_obj, a->str, a->len); + blocked = off != 0 ? js_propref_load(js, off) : js_mkundef(); + got_blocked_fast = true; + break; + } + + ant_value_t proto = js_get_proto(js, cur_obj); + if (!is_object_type(proto)) { + got_blocked_fast = true; + break; + } + cur = proto; + } + } + + if (!got_blocked_fast) + blocked = js_getprop_fallback(js, unscopables, a->str); if (is_err(blocked)) { *out = blocked; *abrupt = true; -- 2.51.2 From 8d436f5d817cfe7b1cd5c9ec420915246a50728a Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 17:02:08 -0700 Subject: [PATCH 05/16] preserve proxy heading --- include/internal.h | 3 +- src/ant.c | 380 ++++++++++++++++++++++++++------------ src/modules/reflect.c | 34 +--- src/silver/ops/coercion.h | 8 + 4 files changed, 278 insertions(+), 147 deletions(-) diff --git a/include/internal.h b/include/internal.h index a7e1590..731357d 100644 --- a/include/internal.h +++ b/include/internal.h @@ -361,7 +361,8 @@ static inline bool js_cfunc_same_entrypoint(ant_value_t fn_val, ant_cfunc_t fn) } size_t uint_to_str(char *buf, size_t bufsize, uint64_t val); -int extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args); +ant_value_t extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args, int *out_count); +ant_value_t js_proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); ant_value_t tov(double d); double tod(ant_value_t v); diff --git a/src/ant.c b/src/ant.c index 8a57178..2a30589 100644 --- a/src/ant.c +++ b/src/ant.c @@ -590,7 +590,7 @@ static ant_value_t proxy_read_target(ant_t *js, ant_value_t obj); static ant_offset_t proxy_aware_length(ant_t *js, ant_value_t obj); static ant_value_t proxy_aware_get_elem(ant_t *js, ant_value_t obj, const char *key, size_t key_len); static ant_value_t proxy_delete_index(ant_t *js, ant_value_t obj, ant_offset_t idx); -static bool array_method_has_index(ant_t *js, ant_value_t arr, ant_offset_t idx); +static ant_value_t array_method_has_index(ant_t *js, ant_value_t arr, ant_offset_t idx); static ant_value_t array_method_get_index(ant_t *js, ant_value_t arr, ant_offset_t idx); static ant_offset_t get_dense_buf(ant_value_t arr); @@ -639,6 +639,7 @@ static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); static ant_value_t proxy_get_own_property_descriptor(ant_t *js, ant_value_t proxy, ant_value_t key_val); +static bool descriptor_bool_value(ant_t *js, ant_value_t desc, const char *key, size_t key_len, bool default_value); static ant_value_t proxy_has_own(ant_t *js, ant_value_t proxy, ant_value_t key_val); static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); @@ -5604,23 +5605,31 @@ static ant_value_t builtin_function_call(ant_t *js, ant_value_t *args, int nargs return sv_vm_call_explicit_this(js->vm, js, func, this_arg, call_args, call_nargs); } -int extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args) { +ant_value_t extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args, int *out_count) { + *out_args = NULL; + *out_count = 0; + int len = (int)(vtype(arr) == T_ARR ? get_array_length(js, arr) : proxy_aware_length(js, arr)); - if (len <= 0) return 0; + if (len <= 0) return js_mkundef(); ant_value_t *args_out = (ant_value_t *)ant_calloc(sizeof(ant_value_t) * len); - if (!args_out) return 0; + if (!args_out) return js_mkerr(js, "out of memory"); if (vtype(arr) == T_ARR) { for (int i = 0; i < len; i++) args_out[i] = arr_get(js, arr, (ant_offset_t)i); } else for (int i = 0; i < len; i++) { ant_value_t item = array_method_get_index(js, arr, (ant_offset_t)i); - args_out[i] = is_err(item) ? js_mkundef() : item; + if (is_err(item)) { + free(args_out); + return item; + } + args_out[i] = item; } *out_args = args_out; - return len; + *out_count = len; + return js_mkundef(); } static ant_value_t builtin_function_toString(ant_t *js, ant_value_t *args, int nargs) { @@ -5741,7 +5750,8 @@ static ant_value_t builtin_function_apply(ant_t *js, ant_value_t *args, int narg ant_value_t arg_array = args[1]; uint8_t t = vtype(arg_array); if (t == T_ARR || t == T_OBJ) { - call_nargs = extract_array_args(js, arg_array, &call_args); + ant_value_t extracted = extract_array_args(js, arg_array, &call_args, &call_nargs); + if (is_err(extracted)) return extracted; } else if (t != T_UNDEF && t != T_NULL) {} } @@ -6924,6 +6934,113 @@ static bool proxy_target_key_is_nonconfigurable(ant_t *js, ant_value_t target, a static ant_value_t proxy_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mode); +static ant_value_t proxy_own_keys_raw(ant_t *js, ant_value_t obj) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, obj); + + ant_proxy_state_t *data = get_proxy_data(obj); + if (!data) { + GC_ROOT_RESTORE(js, root_mark); + return mkarr(js); + } + if (data->revoked) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot perform 'ownKeys' on a proxy that has been revoked"); + } + + ant_value_t keys = mkarr(js); + GC_ROOT_PIN(js, keys); + + ant_value_t trap = proxy_get_method(js, data->handler, "ownKeys"); + if (is_err(trap)) { + GC_ROOT_RESTORE(js, root_mark); + return trap; + } + if (vtype(trap) == T_UNDEF) { + keys = js_own_property_keys(js, data->target, true, false); + GC_ROOT_RESTORE(js, root_mark); + return keys; + } + + ant_value_t trap_args[1] = { data->target }; + ant_value_t result = sv_vm_call(js->vm, js, trap, data->handler, trap_args, 1, NULL, false); + if (is_err(result)) { + GC_ROOT_RESTORE(js, root_mark); + return result; + } + if (vtype(result) != T_ARR) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap must return an array"); + } + GC_ROOT_PIN(js, result); + + ant_offset_t len = get_array_length(js, result); + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t ki = arr_get(js, result, i); + if (vtype(ki) != T_STR && vtype(ki) != T_SYMBOL) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must contain only strings or symbols"); + } + for (ant_offset_t j = 0; j < i; j++) { + ant_value_t kj = arr_get(js, result, j); + if (vtype(ki) != vtype(kj)) continue; + if (vtype(ki) == T_SYMBOL) { + if (vdata(ki) == vdata(kj)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must not contain duplicate entries"); + } + continue; + } + + ant_offset_t ki_len; + ant_offset_t ki_off = vstr(js, ki, &ki_len); + ant_offset_t kj_len; + ant_offset_t kj_off = vstr(js, kj, &kj_len); + if (ki_len == kj_len && + memcmp((const void *)(uintptr_t)ki_off, (const void *)(uintptr_t)kj_off, ki_len) == 0) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must not contain duplicate entries"); + } + } + } + + ant_value_t target_keys = js_own_property_keys(js, data->target, true, false); + GC_ROOT_PIN(js, target_keys); + if (is_err(target_keys)) { + GC_ROOT_RESTORE(js, root_mark); + return target_keys; + } + + ant_offset_t target_len = get_array_length(js, target_keys); + bool target_extensible = proxy_target_is_extensible(data->target); + for (ant_offset_t i = 0; i < target_len; i++) { + ant_value_t target_key = arr_get(js, target_keys, i); + if (proxy_target_key_is_nonconfigurable(js, data->target, target_key) && + !proxy_keys_include(js, result, target_key)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result did not include non-configurable key"); + } + if (!target_extensible && !proxy_keys_include(js, result, target_key)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result did not include all target keys"); + } + } + + if (!target_extensible) { + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t result_key = arr_get(js, result, i); + if (!proxy_keys_include(js, target_keys, result_key)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result included extra key for non-extensible target"); + } + } + } + + keys = result; + GC_ROOT_RESTORE(js, root_mark); + return keys; +} + static ant_value_t proxy_set_integrity_level(ant_t *js, ant_value_t obj, bool freeze) { GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, obj); @@ -6938,7 +7055,7 @@ static ant_value_t proxy_set_integrity_level(ant_t *js, ant_value_t obj, bool fr return js_mkerr_typed(js, JS_ERR_TYPE, "Proxy preventExtensions trap returned falsy"); } - ant_value_t keys = proxy_enum(js, obj, OBJ_ENUM_KEYS); + ant_value_t keys = proxy_own_keys_raw(js, obj); GC_ROOT_PIN(js, keys); if (is_err(keys)) { GC_ROOT_RESTORE(js, root_mark); @@ -6972,116 +7089,64 @@ static ant_value_t proxy_set_integrity_level(ant_t *js, ant_value_t obj, bool fr } static ant_value_t proxy_test_integrity_level(ant_t *js, ant_value_t obj, bool frozen) { - ant_value_t extensible = proxy_is_extensible(js, obj); - if (is_err(extensible)) return extensible; - if (js_truthy(js, extensible)) return js_false; - - ant_value_t keys = proxy_enum(js, obj, OBJ_ENUM_KEYS); - if (is_err(keys)) return keys; - - (void)frozen; - return js_true; -} - -// TODO: reduce nesting -static ant_value_t proxy_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mode) { GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, obj); - ant_proxy_state_t *data = get_proxy_data(obj); - if (!data) { + ant_value_t extensible = proxy_is_extensible(js, obj); + if (is_err(extensible)) { GC_ROOT_RESTORE(js, root_mark); - return mkarr(js); + return extensible; } - if (data->revoked) { + if (js_truthy(js, extensible)) { GC_ROOT_RESTORE(js, root_mark); - return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot perform 'ownKeys' on a proxy that has been revoked"); + return js_false; } - ant_value_t keys = mkarr(js); + ant_value_t keys = proxy_own_keys_raw(js, obj); GC_ROOT_PIN(js, keys); - - ant_value_t trap = proxy_get_method(js, data->handler, "ownKeys"); - if (is_err(trap)) { + if (is_err(keys)) { GC_ROOT_RESTORE(js, root_mark); - return trap; + return keys; } - if (vtype(trap) == T_UNDEF) { - keys = object_enum(js, data->target, OBJ_ENUM_KEYS); - } else { - ant_value_t trap_args[1] = { data->target }; - ant_value_t result = sv_vm_call(js->vm, js, trap, data->handler, trap_args, 1, NULL, false); - if (is_err(result)) { - GC_ROOT_RESTORE(js, root_mark); - return result; - } - if (vtype(result) != T_ARR) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap must return an array"); - } - ant_offset_t len = get_array_length(js, result); - for (ant_offset_t i = 0; i < len; i++) { - ant_value_t ki = arr_get(js, result, i); - if (vtype(ki) != T_STR && vtype(ki) != T_SYMBOL) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must contain only strings or symbols"); - } - for (ant_offset_t j = 0; j < i; j++) { - ant_value_t kj = arr_get(js, result, j); - if (vtype(ki) != vtype(kj)) continue; - if (vtype(ki) == T_SYMBOL) { - if (vdata(ki) == vdata(kj)) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must not contain duplicate entries"); - } - continue; - } + ant_offset_t len = get_array_length(js, keys); + for (ant_offset_t i = 0; i < len; i++) { + GC_ROOT_SAVE(iter_mark, js); + ant_value_t key = arr_get(js, keys, i); + GC_ROOT_PIN(js, key); - ant_offset_t ki_len; - ant_offset_t ki_off = vstr(js, ki, &ki_len); - ant_offset_t kj_len; - ant_offset_t kj_off = vstr(js, kj, &kj_len); - if (ki_len == kj_len && - memcmp((const void *)(uintptr_t)ki_off, (const void *)(uintptr_t)kj_off, ki_len) == 0) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must not contain duplicate entries"); - } - } - } + ant_value_t desc = proxy_get_own_property_descriptor(js, obj, key); + GC_ROOT_PIN(js, desc); + if (is_err(desc)) { + GC_ROOT_RESTORE(js, iter_mark); + GC_ROOT_RESTORE(js, root_mark); + return desc; + } + if (vtype(desc) == T_UNDEF || + descriptor_bool_value(js, desc, "configurable", 12, false) || + (frozen && descriptor_bool_value(js, desc, "writable", 8, false))) { + GC_ROOT_RESTORE(js, iter_mark); + GC_ROOT_RESTORE(js, root_mark); + return js_false; + } - ant_value_t target_keys = js_own_property_keys(js, data->target, true, false); - GC_ROOT_PIN(js, target_keys); - if (is_err(target_keys)) { - GC_ROOT_RESTORE(js, root_mark); - return target_keys; - } + GC_ROOT_RESTORE(js, iter_mark); + } - ant_offset_t target_len = get_array_length(js, target_keys); - bool target_extensible = proxy_target_is_extensible(data->target); - for (ant_offset_t i = 0; i < target_len; i++) { - ant_value_t target_key = arr_get(js, target_keys, i); - if (proxy_target_key_is_nonconfigurable(js, data->target, target_key) && - !proxy_keys_include(js, result, target_key)) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result did not include non-configurable key"); - } - if (!target_extensible && !proxy_keys_include(js, result, target_key)) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result did not include all target keys"); - } - } + GC_ROOT_RESTORE(js, root_mark); + return js_true; +} - if (!target_extensible) { - for (ant_offset_t i = 0; i < len; i++) { - ant_value_t result_key = arr_get(js, result, i); - if (!proxy_keys_include(js, target_keys, result_key)) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr_typed(js, JS_ERR_TYPE, "'ownKeys' on proxy: trap result included extra key for non-extensible target"); - } - } - } - keys = result; +// TODO: reduce nesting +static ant_value_t proxy_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mode) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, obj); + + ant_value_t keys = proxy_own_keys_raw(js, obj); + GC_ROOT_PIN(js, keys); + if (is_err(keys)) { + GC_ROOT_RESTORE(js, root_mark); + return keys; } ant_value_t out = mkarr(js); @@ -10073,7 +10138,9 @@ static ant_value_t builtin_array_every(ant_t *js, ant_value_t *args, int nargs) ant_offset_t len = proxy_aware_length(js, arr); for (ant_offset_t i = 0; i < len; i++) { - if (!array_method_has_index(js, arr, i)) continue; + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; ant_value_t val = array_method_get_index(js, arr, i); if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; @@ -10098,7 +10165,9 @@ static ant_value_t builtin_array_forEach(ant_t *js, ant_value_t *args, int nargs ant_offset_t len = proxy_aware_length(js, arr); for (ant_offset_t i = 0; i < len; i++) { - if (!array_method_has_index(js, arr, i)) continue; + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; ant_value_t val = array_method_get_index(js, arr, i); if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; @@ -10196,7 +10265,9 @@ static ant_value_t builtin_array_map(ant_t *js, ant_value_t *args, int nargs) { if (is_err(result)) return result; for (ant_offset_t i = 0; i < len; i++) { - if (!array_method_has_index(js, arr, i)) continue; + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; ant_value_t val = array_method_get_index(js, arr, i); if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; @@ -10225,7 +10296,9 @@ static ant_value_t builtin_array_filter(ant_t *js, ant_value_t *args, int nargs) ant_offset_t result_idx = 0; for (ant_offset_t i = 0; i < len; i++) { - if (!array_method_has_index(js, arr, i)) continue; + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; ant_value_t val = array_method_get_index(js, arr, i); if (is_err(val)) return val; ant_value_t call_args[3] = { val, tov((double)i), arr }; @@ -10254,7 +10327,9 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) bool first = !has_initial; for (ant_offset_t i = 0; i < len; i++) { - if (!array_method_has_index(js, arr, i)) continue; + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; ant_value_t val = array_method_get_index(js, arr, i); if (is_err(val)) return val; if (first) { accumulator = val; first = false; continue; } @@ -10275,7 +10350,11 @@ static inline ant_value_t flat_helper(ant_t *js, ant_value_t arr, ant_value_t re for (ant_offset_t i = 0; i < len; i++) { if (input_is_array) { if (!arr_has(js, arr, i)) continue; - } else if (!array_method_has_index(js, arr, i)) continue; + } else { + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; + } ant_value_t val = input_is_array ? arr_get(js, arr, i) : array_method_get_index(js, arr, i); if (is_err(val)) return val; @@ -10511,7 +10590,11 @@ static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs for (ant_offset_t i = 0; i < len; i++) { if (input_is_array) { if (!arr_has(js, arr, i)) continue; - } else if (!array_method_has_index(js, arr, i)) continue; + } else { + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; + } ant_value_t elem = input_is_array ? arr_get(js, arr, i) : array_method_get_index(js, arr, i); if (is_err(elem)) return elem; @@ -10588,7 +10671,11 @@ static ant_value_t builtin_array_indexOf(ant_t *js, ant_value_t *args, int nargs for (ant_offset_t i = start; i < len; i++) { ant_value_t elem = array_method_get_index(js, arr, i); if (is_err(elem)) return elem; - if (vtype(elem) == T_UNDEF && !array_method_has_index(js, arr, i)) continue; + if (vtype(elem) == T_UNDEF) { + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; + } if (strict_eq_values(js, elem, search)) return tov((double)i); } return tov(-1); @@ -10614,7 +10701,11 @@ static ant_value_t builtin_array_lastIndexOf(ant_t *js, ant_value_t *args, int n for (int i = start; i >= 0; i--) { ant_value_t elem = array_method_get_index(js, arr, (ant_offset_t)i); if (is_err(elem)) return elem; - if (vtype(elem) == T_UNDEF && !array_method_has_index(js, arr, (ant_offset_t)i)) continue; + if (vtype(elem) == T_UNDEF) { + ant_value_t has = array_method_has_index(js, arr, (ant_offset_t)i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; + } if (strict_eq_values(js, elem, search)) return tov((double)i); } return tov(-1); @@ -10645,7 +10736,9 @@ static ant_value_t builtin_array_reduceRight(ant_t *js, ant_value_t *args, int n } for (int i = start_idx; i >= 0; i--) { - if (!array_method_has_index(js, arr, (ant_offset_t)i)) continue; + ant_value_t has = array_method_has_index(js, arr, (ant_offset_t)i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; ant_value_t elem = array_method_get_index(js, arr, (ant_offset_t)i); if (is_err(elem)) return elem; ant_value_t call_args[4] = { accumulator, elem, tov((double)i), arr }; @@ -10822,7 +10915,9 @@ static ant_value_t builtin_array_some(ant_t *js, ant_value_t *args, int nargs) { if (len == 0) return mkval(T_BOOL, 0); for (ant_offset_t i = 0; i < len; i++) { - if (!array_method_has_index(js, arr, i)) continue; + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) return has; + if (!js_truthy(js, has)) continue; ant_value_t val = array_method_get_index(js, arr, i); if (is_err(val)) return val; @@ -11436,7 +11531,11 @@ static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, in if (input_is_array) { if (!arr_has(js, arr, i)) continue; - } else if (!array_method_has_index(js, arr, i)) continue; + } else { + ant_value_t has = array_method_has_index(js, arr, i); + if (is_err(has)) { free(result); return has; } + if (!js_truthy(js, has)) continue; + } ant_value_t elem = input_is_array ? arr_get(js, arr, i) : array_method_get_index(js, arr, i); if (is_err(elem)) { free(result); return elem; } @@ -15335,9 +15434,13 @@ static ant_value_t proxy_delete_index(ant_t *js, ant_value_t obj, ant_offset_t i return proxy_delete(js, obj, idxstr, idxlen); } -static bool array_method_has_index(ant_t *js, ant_value_t arr, ant_offset_t idx) { - if (is_proxy(arr)) return true; - return arr_has(js, arr, idx); +static ant_value_t array_method_has_index(ant_t *js, ant_value_t arr, ant_offset_t idx) { + if (is_proxy(arr)) { + char idxstr[16]; + size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)idx); + return proxy_has(js, arr, idxstr, idxlen); + } + return js_bool(arr_has(js, arr, idx)); } static ant_value_t array_method_get_index(ant_t *js, ant_value_t arr, ant_offset_t idx) { @@ -15483,6 +15586,7 @@ static ant_value_t proxy_set_with_receiver(ant_t *js, ant_value_t proxy, const c GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, proxy); + GC_ROOT_PIN(js, receiver); GC_ROOT_PIN(js, value); ant_value_t key_str = js_mkstr(js, key, key_len); GC_ROOT_PIN(js, key_str); @@ -15517,7 +15621,43 @@ static ant_value_t proxy_set_with_receiver(ant_t *js, ant_value_t proxy, const c return define_trap; } if (vtype(define_trap) == T_UNDEF) { - ant_value_t set_result = js_setprop(js, target, key_str, value); + ant_value_t set_result; + if (same_object_identity(receiver, proxy)) { + set_result = js_setprop(js, target, key_str, value); + } else { + if (!is_object_type(receiver)) { + GC_ROOT_RESTORE(js, root_mark); + return js_false; + } + + ant_offset_t existing = lkp(js, receiver, key, key_len); + if (existing > 0) { + if (is_const_prop(js, existing)) { + GC_ROOT_RESTORE(js, root_mark); + return js_false; + } + js_saveval(js, existing, value); + array_define_or_set_index(js, receiver, key, key_len); + GC_ROOT_RESTORE(js, root_mark); + return js_true; + } + + ant_value_t extensible = check_object_extensibility(js, receiver); + if (is_err(extensible)) { + GC_ROOT_RESTORE(js, root_mark); + return extensible; + } + if (extensible == js_false) { + GC_ROOT_RESTORE(js, root_mark); + return js_false; + } + + const char *interned_key = intern_string(key, key_len); + set_result = interned_key + ? mkprop_interned_exact(js, receiver, interned_key, value, 0) + : js_mkerr(js, "oom"); + if (!is_err(set_result)) array_define_or_set_index(js, receiver, key, key_len); + } GC_ROOT_RESTORE(js, root_mark); if (is_err(set_result)) return set_result; return js_true; @@ -15574,6 +15714,10 @@ static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size return js_bool(off != 0); } +ant_value_t js_proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len) { + return proxy_has(js, proxy, key, key_len); +} + static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, size_t key_len) { ant_proxy_state_t *data = get_proxy_data(proxy); if (!data) return js_true; diff --git a/src/modules/reflect.c b/src/modules/reflect.c index b8d3aa7..41956e1 100644 --- a/src/modules/reflect.c +++ b/src/modules/reflect.c @@ -177,9 +177,11 @@ static ant_value_t reflect_apply(ant_t *js, ant_value_t *args, int nargs) { ); ant_value_t result; - if (vtype(args_arr) == T_ARR) { + if (vtype(args_arr) == T_ARR || vtype(args_arr) == T_OBJ) { ant_value_t *call_args = NULL; - int arg_count = extract_array_args(js, args_arr, &call_args); + int arg_count = 0; + ant_value_t extracted = extract_array_args(js, args_arr, &call_args, &arg_count); + if (is_err(extracted)) return extracted; result = sv_vm_call_explicit_this( js->vm, js, target, this_arg, call_args, arg_count @@ -187,32 +189,8 @@ static ant_value_t reflect_apply(ant_t *js, ant_value_t *args, int nargs) { if (call_args) free(call_args); return result; } - - ant_value_t length_val = js_get(js, args_arr, "length"); - int arg_count = 0; - if (vtype(length_val) == T_NUM) { - arg_count = (int)js_getnum(length_val); - } - - ant_value_t *call_args = NULL; - if (arg_count > 0) { - call_args = malloc(arg_count * sizeof(ant_value_t)); - if (!call_args) return js_mkerr(js, "Out of memory"); - - for (int i = 0; i < arg_count; i++) { - char idx[16]; - snprintf(idx, sizeof(idx), "%d", i); - call_args[i] = js_get(js, args_arr, idx); - } - } - - result = sv_vm_call_explicit_this( - js->vm, js, target, this_arg, - call_args, arg_count - ); - - if (call_args) free(call_args); - return result; + + return js_mkerr_typed(js, JS_ERR_TYPE, "Reflect.apply: third argument must be an array-like object"); } static ant_value_t reflect_get_own_property_descriptor(ant_t *js, ant_value_t *args, int nargs) { diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index 3e9e501..166cf94 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -374,10 +374,18 @@ static inline bool sv_try_get_with_bound_value( const char *interned = intern_string(a->str, a->len); if (ptr && is_proxy(js_as_obj(with_obj))) { + ant_value_t has = js_proxy_has(js, with_obj, a->str, a->len); + if (is_err(has)) { + *out = has; + return true; + } + if (!js_truthy(js, has)) return false; + bool abrupt = false; if (sv_with_binding_is_unscopable(js, with_obj, a, out, &abrupt)) return false; if (abrupt) return true; *out = js_getprop_fallback(js, with_obj, a->str); + return true; } -- 2.51.2 From 2b0ea1d37600cca6f2fbcd8c6004bff1c2b54284 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 17:02:16 -0700 Subject: [PATCH 06/16] update reflect --- src/modules/reflect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/reflect.c b/src/modules/reflect.c index 41956e1..721c09c 100644 --- a/src/modules/reflect.c +++ b/src/modules/reflect.c @@ -177,7 +177,7 @@ static ant_value_t reflect_apply(ant_t *js, ant_value_t *args, int nargs) { ); ant_value_t result; - if (vtype(args_arr) == T_ARR || vtype(args_arr) == T_OBJ) { + if (is_object_type(args_arr)) { ant_value_t *call_args = NULL; int arg_count = 0; ant_value_t extracted = extract_array_args(js, args_arr, &call_args, &arg_count); -- 2.51.2 From f7fc2885c44aee373814155be3c628088777f722 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 21:55:14 -0700 Subject: [PATCH 07/16] apply coderabbit changes --- include/internal.h | 14 +++- src/ant.c | 139 ++++++++++++++++++++++++-------------- src/modules/url.c | 2 + src/silver/compiler.c | 8 ++- src/silver/engine.c | 4 +- src/silver/ops/coercion.h | 64 ++++++++++-------- 6 files changed, 147 insertions(+), 84 deletions(-) diff --git a/include/internal.h b/include/internal.h index 731357d..86dc6ed 100644 --- a/include/internal.h +++ b/include/internal.h @@ -192,7 +192,7 @@ struct ant_isolate_t { struct { uint64_t counter; struct sym_registry_entry *registry; - + ant_value_t object_proto; ant_value_t array_proto; ant_value_t iterator_proto; @@ -262,6 +262,18 @@ struct ant_isolate_t { uint16_t cap; } cfunc_name_cache; + struct { + ant_object_t *function_proto_obj; + ant_object_t *with_no_unscopables_base; + ant_object_t *with_no_unscopables_proto; + + void *with_no_unscopables_base_shape; + void *with_no_unscopables_proto_shape; + + uint32_t with_no_unscopables_epoch; + uint32_t function_proto_epoch; + } runtime_cache; + bool owns_mem; bool fatal_error; bool thrown_exists; diff --git a/src/ant.c b/src/ant.c index 2a30589..690c201 100644 --- a/src/ant.c +++ b/src/ant.c @@ -587,12 +587,19 @@ static ant_value_t make_data_cfunc( ); static ant_value_t proxy_read_target(ant_t *js, ant_value_t obj); -static ant_offset_t proxy_aware_length(ant_t *js, ant_value_t obj); +static ant_value_t proxy_aware_length(ant_t *js, ant_value_t obj, ant_offset_t *out_len); static ant_value_t proxy_aware_get_elem(ant_t *js, ant_value_t obj, const char *key, size_t key_len); static ant_value_t proxy_delete_index(ant_t *js, ant_value_t obj, ant_offset_t idx); static ant_value_t array_method_has_index(ant_t *js, ant_value_t arr, ant_offset_t idx); static ant_value_t array_method_get_index(ant_t *js, ant_value_t arr, ant_offset_t idx); +#define PROXY_AWARE_LENGTH_OR_RETURN(obj_expr, out_var) \ + ant_offset_t out_var = 0; \ + do { \ + ant_value_t _len_result = proxy_aware_length(js, (obj_expr), &(out_var)); \ + if (is_err(_len_result)) return _len_result; \ + } while (0) + static ant_offset_t get_dense_buf(ant_value_t arr); static ant_offset_t dense_capacity(ant_offset_t doff); static ant_offset_t get_array_length(ant_t *js, ant_value_t arr); @@ -4997,7 +5004,7 @@ ant_value_t js_to_primitive(ant_t *js, ant_value_t value, int hint) { ant_value_t tp_sym = get_toPrimitive_sym(); ant_value_t tp_fn = js_get_sym(js, value, tp_sym); if (is_err(tp_fn)) return tp_fn; - if (vtype(tp_fn) == T_UNDEF || vtype(tp_fn) == T_NULL) + if (vtype(tp_fn) == T_UNDEF) return try_ordinary_to_primitive(js, value, hint); if (vtype(tp_fn) != T_FUNC && vtype(tp_fn) != T_CFUNC) return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.toPrimitive is not a function"); @@ -5609,7 +5616,13 @@ ant_value_t extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_arg *out_args = NULL; *out_count = 0; - int len = (int)(vtype(arr) == T_ARR ? get_array_length(js, arr) : proxy_aware_length(js, arr)); + ant_offset_t raw_len = 0; + if (vtype(arr) == T_ARR) raw_len = get_array_length(js, arr); + else { + ant_value_t len_result = proxy_aware_length(js, arr, &raw_len); + if (is_err(len_result)) return len_result; + } + int len = (int)raw_len; if (len <= 0) return js_mkundef(); ant_value_t *args_out = (ant_value_t *)ant_calloc(sizeof(ant_value_t) * len); @@ -8094,12 +8107,14 @@ static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, i getter_val = proxy_get(js, descriptor, "get", 3); if (is_err(getter_val)) return getter_val; has_get = vtype(getter_val) != T_UNDEF; - if (has_get && vtype(getter_val) != T_FUNC) return js_mkerr(js, "Getter must be a function"); + if (has_get && vtype(getter_val) != T_FUNC && vtype(getter_val) != T_CFUNC) + return js_mkerr(js, "Getter must be a function"); setter_val = proxy_get(js, descriptor, "set", 3); if (is_err(setter_val)) return setter_val; has_set = vtype(setter_val) != T_UNDEF; - if (has_set && vtype(setter_val) != T_FUNC) return js_mkerr(js, "Setter must be a function"); + if (has_set && vtype(setter_val) != T_FUNC && vtype(setter_val) != T_CFUNC) + return js_mkerr(js, "Setter must be a function"); } else { ant_offset_t value_off = lkp(js, descriptor, "value", 5); if (value_off != 0) { @@ -8111,7 +8126,7 @@ static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, i if (get_off != 0) { has_get = true; getter_val = propref_load(js, get_off); - if (vtype(getter_val) != T_FUNC && vtype(getter_val) != T_UNDEF) { + if (vtype(getter_val) != T_FUNC && vtype(getter_val) != T_CFUNC && vtype(getter_val) != T_UNDEF) { return js_mkerr(js, "Getter must be a function"); } } @@ -8120,7 +8135,7 @@ static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, i if (set_off != 0) { has_set = true; setter_val = propref_load(js, set_off); - if (vtype(setter_val) != T_FUNC && vtype(setter_val) != T_UNDEF) { + if (vtype(setter_val) != T_FUNC && vtype(setter_val) != T_CFUNC && vtype(setter_val) != T_UNDEF) { return js_mkerr(js, "Setter must be a function"); } } @@ -9518,12 +9533,7 @@ static ant_value_t builtin_array_push(ant_t *js, ant_value_t *args, int nargs) { } if (is_proxy(arr)) { - ant_offset_t off = lkp_interned(js, arr, js->intern.length, 6); - ant_offset_t len = 0; - if (off != 0) { - ant_value_t len_val = propref_load(js, off); - if (vtype(len_val) == T_NUM) len = (ant_offset_t) tod(len_val); - } + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); for (int i = 0; i < nargs; i++) { char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len); @@ -9537,7 +9547,7 @@ static ant_value_t builtin_array_push(ant_t *js, ant_value_t *args, int nargs) { return len_val; } - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); ant_offset_t doff = get_dense_buf(arr); if (doff) { @@ -9584,7 +9594,9 @@ void js_arr_push(ant_t *js, ant_value_t arr, ant_value_t val) { return; } - ant_offset_t len = proxy_aware_length(js, arr); + ant_offset_t len = 0; + ant_value_t len_result = proxy_aware_length(js, arr, &len); + if (is_err(len_result)) return; char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len); @@ -9600,7 +9612,7 @@ static ant_value_t builtin_array_pop(ant_t *js, ant_value_t *args, int nargs) { } if (is_proxy(arr)) { - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len == 0) { js_setprop(js, arr, js->length_str, tov(0.0)); return js_mkundef(); @@ -9633,7 +9645,7 @@ static ant_value_t builtin_array_pop(ant_t *js, ant_value_t *args, int nargs) { } pop_slow: - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len == 0) return js_mkundef(); len--; char idxstr[16]; @@ -9722,7 +9734,7 @@ static ant_value_t builtin_array_join(ant_t *js, ant_value_t *args, int nargs) { } } - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len == 0) return js_mkstr(js, "", 0); @@ -10135,7 +10147,7 @@ static ant_value_t builtin_array_every(ant_t *js, ant_value_t *args, int nargs) if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); for (ant_offset_t i = 0; i < len; i++) { ant_value_t has = array_method_has_index(js, arr, i); @@ -10162,7 +10174,7 @@ static ant_value_t builtin_array_forEach(ant_t *js, ant_value_t *args, int nargs if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); for (ant_offset_t i = 0; i < len; i++) { ant_value_t has = array_method_has_index(js, arr, i); @@ -10186,7 +10198,7 @@ static ant_value_t builtin_array_reverse(ant_t *js, ant_value_t *args, int nargs return js_mkerr(js, "reverse called on non-array"); if (is_proxy(arr)) { - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len <= 1) return arr; ant_value_t read_from = proxy_read_target(js, arr); ant_offset_t lower = 0; @@ -10215,7 +10227,7 @@ static ant_value_t builtin_array_reverse(ant_t *js, ant_value_t *args, int nargs } return arr; } - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len <= 1) return arr; ant_offset_t doff = get_dense_buf(arr); @@ -10260,7 +10272,7 @@ static ant_value_t builtin_array_map(ant_t *js, ant_value_t *args, int nargs) { if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); ant_value_t result = array_alloc_like(js, arr); if (is_err(result)) return result; @@ -10289,7 +10301,7 @@ static ant_value_t builtin_array_filter(ant_t *js, ant_value_t *args, int nargs) if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); ant_value_t result = array_alloc_like(js, arr); if (is_err(result)) return result; @@ -10321,7 +10333,7 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) if (is_err(callback)) return callback; bool has_initial = (nargs >= 2); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); ant_value_t accumulator = has_initial ? args[1] : js_mkundef(); bool first = !has_initial; @@ -10344,7 +10356,11 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) static inline ant_value_t flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) { bool input_is_array = vtype(arr) == T_ARR; - ant_offset_t len = input_is_array ? get_array_length(js, arr) : proxy_aware_length(js, arr); + ant_offset_t len = input_is_array ? get_array_length(js, arr) : 0; + if (!input_is_array) { + ant_value_t len_result = proxy_aware_length(js, arr, &len); + if (is_err(len_result)) return len_result; + } if (len == 0) return js_mkundef(); for (ant_offset_t i = 0; i < len; i++) { @@ -10457,7 +10473,7 @@ static ant_value_t builtin_array_at(ant_t *js, ant_value_t *args, int nargs) { if (nargs == 0 || vtype(args[0]) != T_NUM) return js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); int idx = (int) tod(args[0]); if (idx < 0) idx = (int)len + idx; @@ -10474,7 +10490,7 @@ static ant_value_t builtin_array_fill(ant_t *js, ant_value_t *args, int nargs) { ant_value_t value = nargs >= 1 ? args[0] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); ant_offset_t start = 0, end = len; if (nargs >= 2 && vtype(args[1]) == T_NUM) { @@ -10509,7 +10525,7 @@ static ant_value_t array_find_impl(ant_t *js, ant_value_t *args, int nargs, bool if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len == 0) return return_index ? tov(-1) : js_mkundef(); for (ant_offset_t i = 0; i < len; i++) { @@ -10544,7 +10560,7 @@ static ant_value_t array_find_last_impl(ant_t *js, ant_value_t *args, int nargs, if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len == 0) return return_index ? tov(-1) : js_mkundef(); for (ant_offset_t i = len; i > 0; i--) { @@ -10581,7 +10597,11 @@ static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs ant_value_t callback = args[0]; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); bool input_is_array = vtype(arr) == T_ARR; - ant_offset_t len = input_is_array ? get_array_length(js, arr) : proxy_aware_length(js, arr); + ant_offset_t len = input_is_array ? get_array_length(js, arr) : 0; + if (!input_is_array) { + ant_value_t len_result = proxy_aware_length(js, arr, &len); + if (is_err(len_result)) return len_result; + } ant_value_t result = mkarr(js); if (is_err(result)) return result; @@ -10658,7 +10678,7 @@ static ant_value_t builtin_array_indexOf(ant_t *js, ant_value_t *args, int nargs if (nargs == 0) return tov(-1); ant_value_t search = args[0]; - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); ant_offset_t start = 0; if (nargs >= 2 && vtype(args[1]) == T_NUM) { @@ -10689,7 +10709,7 @@ static ant_value_t builtin_array_lastIndexOf(ant_t *js, ant_value_t *args, int n if (nargs == 0) return tov(-1); ant_value_t search = args[0]; - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); int start = (int)len - 1; if (nargs >= 2 && vtype(args[1]) == T_NUM) { @@ -10721,7 +10741,7 @@ static ant_value_t builtin_array_reduceRight(ant_t *js, ant_value_t *args, int n } ant_value_t callback = args[0]; - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); int start_idx = (int)len - 1; ant_value_t accumulator; @@ -10757,7 +10777,7 @@ static ant_value_t builtin_array_shift(ant_t *js, ant_value_t *args, int nargs) return js_mkerr(js, "shift called on non-array"); } - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len == 0) return js_mkundef(); if (is_proxy(arr)) { @@ -10826,7 +10846,7 @@ static ant_value_t builtin_array_unshift(ant_t *js, ant_value_t *args, int nargs return js_mkerr(js, "unshift called on non-array"); } - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (is_proxy(arr)) { ant_value_t read_from = proxy_read_target(js, arr); @@ -10911,7 +10931,7 @@ static ant_value_t builtin_array_some(ant_t *js, ant_value_t *args, int nargs) { if (is_err(callback)) return callback; ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); if (len == 0) return mkval(T_BOOL, 0); for (ant_offset_t i = 0; i < len; i++) { @@ -11081,7 +11101,7 @@ static ant_value_t builtin_array_splice(ant_t *js, ant_value_t *args, int nargs) return js_mkerr(js, "splice called on non-array"); } - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); ant_value_t read_from = is_proxy(arr) ? proxy_read_target(js, arr) : arr; int start = 0; @@ -11261,7 +11281,7 @@ static ant_value_t builtin_array_copyWithin(ant_t *js, ant_value_t *args, int na return js_mkerr(js, "copyWithin called on non-array"); } - ant_offset_t len = proxy_aware_length(js, arr); + PROXY_AWARE_LENGTH_OR_RETURN(arr, len); ant_value_t read_from = is_proxy(arr) ? proxy_read_target(js, arr) : arr; int target = 0, start = 0, end = (int)len; @@ -11508,7 +11528,11 @@ static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, in return js_mkerr(js, "toLocaleString called on non-array"); bool input_is_array = vtype(arr) == T_ARR; - ant_offset_t len = input_is_array ? get_array_length(js, arr) : proxy_aware_length(js, arr); + ant_offset_t len = input_is_array ? get_array_length(js, arr) : 0; + if (!input_is_array) { + ant_value_t len_result = proxy_aware_length(js, arr, &len); + if (is_err(len_result)) return len_result; + } if (len == 0) return js_mkstr(js, "", 0); char *result = NULL; @@ -11679,7 +11703,7 @@ static ant_value_t builtin_Array_from(ant_t *js, ant_value_t *args, int nargs) { if (src_is_array && vtype(iter_method) == T_CFUNC) { array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 }; - ant_offset_t len = proxy_aware_length(js, src); + PROXY_AWARE_LENGTH_OR_RETURN(src, len); for (ant_offset_t i = 0; i < len; i++) { ant_value_t unused; ant_value_t item = array_method_get_index(js, src, i); @@ -11695,7 +11719,7 @@ static ant_value_t builtin_Array_from(ant_t *js, ant_value_t *args, int nargs) { if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)ctx.index)); } else if (vtype(src) == T_OBJ) { array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 }; - ant_offset_t len = proxy_aware_length(js, src); + PROXY_AWARE_LENGTH_OR_RETURN(src, len); for (ant_offset_t i = 0; i < len; i++) { ant_value_t unused; ant_value_t item = array_method_get_index(js, src, i); @@ -14863,11 +14887,19 @@ static ant_value_t walk_prototype_chain(ant_t *js, ant_value_t l, ant_value_t ct } static inline ant_object_t *cached_function_proto_obj(ant_t *js) { - static ant_object_t *cached = NULL; - if (cached) return cached; + uint32_t cache_epoch = ant_ic_epoch_counter; + if ( + js->runtime_cache.function_proto_epoch == cache_epoch && + js->runtime_cache.function_proto_obj + ) return js->runtime_cache.function_proto_obj; + ant_value_t proto = get_ctor_proto(js, "Function", 8); if (!is_object_type(proto)) return NULL; - cached = js_obj_ptr(js_as_obj(proto)); + + ant_object_t *cached = js_obj_ptr(js_as_obj(proto)); + js->runtime_cache.function_proto_obj = cached; + js->runtime_cache.function_proto_epoch = cache_epoch; + return cached; } @@ -15410,16 +15442,23 @@ static ant_value_t proxy_read_target(ant_t *js, ant_value_t obj) { return data ? data->target : obj; } -static ant_offset_t proxy_aware_length(ant_t *js, ant_value_t obj) { +static ant_value_t proxy_aware_length(ant_t *js, ant_value_t obj, ant_offset_t *out_len) { + *out_len = 0; if (is_proxy(obj)) { ant_value_t len_val = proxy_get(js, obj, "length", 6); - return vtype(len_val) == T_NUM ? (ant_offset_t)tod(len_val) : 0; + if (is_err(len_val)) return len_val; + if (vtype(len_val) == T_NUM) *out_len = (ant_offset_t)tod(len_val); + return js_mkundef(); + } + if (vtype(obj) == T_ARR) { + *out_len = get_array_length(js, obj); + return js_mkundef(); } - if (vtype(obj) == T_ARR) return get_array_length(js, obj); ant_offset_t off = lkp_interned(js, obj, js->intern.length, 6); - if (off == 0) return 0; + if (off == 0) return js_mkundef(); ant_value_t len_val = propref_load(js, off); - return vtype(len_val) == T_NUM ? (ant_offset_t)tod(len_val) : 0; + if (vtype(len_val) == T_NUM) *out_len = (ant_offset_t)tod(len_val); + return js_mkundef(); } static ant_value_t proxy_aware_get_elem(ant_t *js, ant_value_t obj, const char *key, size_t key_len) { diff --git a/src/modules/url.c b/src/modules/url.c index 549ca76..e63a86b 100644 --- a/src/modules/url.c +++ b/src/modules/url.c @@ -1348,6 +1348,7 @@ static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) { bool init_is_array = false; ant_value_t is_array_res = js_is_array_value_checked(js, init, &init_is_array); if (is_err(is_array_res)) return is_array_res; + if (js->thrown_exists) return mkval(T_ERR, 0); if (init_is_array) { ant_offset_t len = js_arr_len(js, init); @@ -1356,6 +1357,7 @@ static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) { bool pair_is_array = false; is_array_res = js_is_array_value_checked(js, pair, &pair_is_array); if (is_err(is_array_res)) return is_array_res; + if (js->thrown_exists) return mkval(T_ERR, 0); if (!pair_is_array) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URLSearchParams': Each element must be an array."); diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 28f0d6f..6d9f8a9 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -2325,8 +2325,12 @@ void compile_typeof(sv_compiler_t *c, sv_ast_t *node) { } else { int upval = resolve_upvalue(c, arg->str, arg->len); if (upval != -1) { - emit_op(c, OP_GET_UPVAL); - emit_u16(c, (uint16_t)upval); + if (c->with_depth > 0) { + emit_with_get(c, arg->str, arg->len, WITH_FB_UPVAL, (uint16_t)upval); + } else { + emit_op(c, OP_GET_UPVAL); + emit_u16(c, (uint16_t)upval); + } } else if ( has_implicit_arguments_obj(c) && is_ident_str(arg->str, arg->len, "arguments", 9) diff --git a/src/silver/engine.c b/src/silver/engine.c index c515842..be84b52 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -1878,8 +1878,8 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant L_EXIT_WITH: { sv_op_exit_with(vm, frame); NEXT(1); } L_WITH_GET_VAR: { VM_CHECK(sv_op_with_get_var(vm, js, frame, func, ip)); NEXT(8); } - L_WITH_PUT_VAR: { sv_op_with_put_var(vm, js, frame, func, ip); NEXT(8); } - L_WITH_DEL_VAR: { sv_op_with_del_var(vm, js, frame, func, ip); NEXT(5); } + L_WITH_PUT_VAR: { VM_CHECK(sv_op_with_put_var(vm, js, frame, func, ip)); NEXT(8); } + L_WITH_DEL_VAR: { VM_CHECK(sv_op_with_del_var(vm, js, frame, func, ip)); NEXT(5); } L_SPECIAL_OBJ: { sv_op_special_obj(vm, js, frame, ip); NEXT(2); } L_EMPTY: { vm->stack[vm->sp++] = T_EMPTY; NEXT(1); } diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index 166cf94..e31d831 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -247,17 +247,14 @@ static inline bool sv_with_binding_is_unscopable( ant_object_t *base_ptr = js_obj_ptr(js_as_obj(with_obj)); ant_value_t base_proto = (base_ptr && is_object_type(base_ptr->proto)) ? base_ptr->proto : js_mknull(); ant_object_t *proto_ptr = is_object_type(base_proto) ? js_obj_ptr(js_as_obj(base_proto)) : NULL; - - static ant_object_t *cached_no_base = NULL; - static void *cached_no_base_shape = NULL; - static ant_value_t cached_no_proto = 0; - static void *cached_no_proto_shape = NULL; + uint32_t cache_epoch = ant_ic_epoch_counter; if ( - base_ptr == cached_no_base && - (void *)(base_ptr ? base_ptr->shape : NULL) == cached_no_base_shape && - base_proto == cached_no_proto && - (void *)(proto_ptr ? proto_ptr->shape : NULL) == cached_no_proto_shape + js->runtime_cache.with_no_unscopables_epoch == cache_epoch && + base_ptr == js->runtime_cache.with_no_unscopables_base && + (void *)(base_ptr ? base_ptr->shape : NULL) == js->runtime_cache.with_no_unscopables_base_shape && + proto_ptr == js->runtime_cache.with_no_unscopables_proto && + (void *)(proto_ptr ? proto_ptr->shape : NULL) == js->runtime_cache.with_no_unscopables_proto_shape ) return false; ant_offset_t unscopables_off = lkp_sym_proto(js, with_obj, sym_off); @@ -285,10 +282,11 @@ static inline bool sv_with_binding_is_unscopable( if (!has_unscopables) { ant_value_t proto_proto = (proto_ptr && is_object_type(proto_ptr->proto)) ? proto_ptr->proto : js_mknull(); if (!saw_exotic && !is_object_type(proto_proto)) { - cached_no_base = base_ptr; - cached_no_base_shape = (void *)(base_ptr ? base_ptr->shape : NULL); - cached_no_proto = base_proto; - cached_no_proto_shape = (void *)(proto_ptr ? proto_ptr->shape : NULL); + js->runtime_cache.with_no_unscopables_base = base_ptr; + js->runtime_cache.with_no_unscopables_base_shape = (void *)(base_ptr ? base_ptr->shape : NULL); + js->runtime_cache.with_no_unscopables_proto = proto_ptr; + js->runtime_cache.with_no_unscopables_proto_shape = (void *)(proto_ptr ? proto_ptr->shape : NULL); + js->runtime_cache.with_no_unscopables_epoch = cache_epoch; } return false; } @@ -438,7 +436,7 @@ static inline ant_value_t sv_op_with_get_var( return js_mkundef(); } -static inline void sv_op_with_put_var( +static inline ant_value_t sv_op_with_put_var( sv_vm_t *vm, ant_t *js, sv_frame_t *frame, sv_func_t *func, uint8_t *ip @@ -450,20 +448,24 @@ static inline void sv_op_with_put_var( sv_atom_t *a = &func->atoms[atom_idx]; ant_value_t val = vm->stack[--vm->sp]; - if (vtype(frame->with_obj) != T_UNDEF) { - if (lkp(js, frame->with_obj, a->str, a->len) != 0) { - ant_value_t key = js_mkstr(js, a->str, a->len); - js_setprop(js, frame->with_obj, key, val); - return; - } + if (vtype(frame->with_obj) != T_UNDEF) if (lkp(js, frame->with_obj, a->str, a->len) != 0) { + ant_value_t out = js_mkundef(); + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; + if (abrupt) return out; + ant_value_t key = js_mkstr(js, a->str, a->len); + js_setprop(js, frame->with_obj, key, val); + return js_mkundef(); } - if (fb_kind == WITH_FB_GLOBAL) { +fallback: + if (fb_kind == WITH_FB_GLOBAL) setprop_interned(js, js->global, a->str, a->len, val); - } else sv_with_fallback_put(vm, js, frame, fb_kind, fb_idx, val); + else sv_with_fallback_put(vm, js, frame, fb_kind, fb_idx, val); + return js_mkundef(); } -static inline void sv_op_with_del_var( +static inline ant_value_t sv_op_with_del_var( sv_vm_t *vm, ant_t *js, sv_frame_t *frame, sv_func_t *func, uint8_t *ip @@ -471,17 +473,21 @@ static inline void sv_op_with_del_var( uint32_t atom_idx = sv_get_u32(ip + 1); sv_atom_t *a = &func->atoms[atom_idx]; - if (vtype(frame->with_obj) != T_UNDEF) { - if (lkp(js, frame->with_obj, a->str, a->len) != 0) { - ant_value_t result = js_delete_prop(js, frame->with_obj, a->str, a->len); - vm->stack[vm->sp++] = result; - return; - } + if (vtype(frame->with_obj) != T_UNDEF) if (lkp(js, frame->with_obj, a->str, a->len) != 0) { + ant_value_t out = js_mkundef(); + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; + if (abrupt) return out; + ant_value_t result = js_delete_prop(js, frame->with_obj, a->str, a->len); + vm->stack[vm->sp++] = result; + return js_mkundef(); } +fallback: ant_value_t result = js_delete_prop(js, js->global, a->str, a->len); bool ok = !is_err(result) && js_truthy(js, result); vm->stack[vm->sp++] = mkval(T_BOOL, ok); + return js_mkundef(); } static inline void sv_op_special_obj( -- 2.51.2 From 3d6371da09b9121dff6cf762a51fca6fa95acead Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 22:02:46 -0700 Subject: [PATCH 08/16] js_to_primitive exotic helper fix --- meson/ant.version | 2 +- src/ant.c | 4 ++-- tests/test_toprimitive_null.cjs | 40 +++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 tests/test_toprimitive_null.cjs diff --git a/meson/ant.version b/meson/ant.version index 56e9df1..142464b 100644 --- a/meson/ant.version +++ b/meson/ant.version @@ -1 +1 @@ -0.10.3 \ No newline at end of file +0.11.0 \ No newline at end of file diff --git a/src/ant.c b/src/ant.c index 690c201..00a87a6 100644 --- a/src/ant.c +++ b/src/ant.c @@ -4968,7 +4968,7 @@ static ant_value_t try_exotic_to_primitive(ant_t *js, ant_value_t value, int hin ant_value_t tp_fn = js_get_sym(js, value, tp_sym); uint8_t ft = vtype(tp_fn); - if (ft == T_UNDEF) return mkval(T_UNDEF, 0); + if (ft == T_UNDEF || ft == T_NULL) return mkval(T_UNDEF, 0); if (ft != T_FUNC && ft != T_CFUNC) { return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.toPrimitive is not a function"); } @@ -5004,7 +5004,7 @@ ant_value_t js_to_primitive(ant_t *js, ant_value_t value, int hint) { ant_value_t tp_sym = get_toPrimitive_sym(); ant_value_t tp_fn = js_get_sym(js, value, tp_sym); if (is_err(tp_fn)) return tp_fn; - if (vtype(tp_fn) == T_UNDEF) + if (vtype(tp_fn) == T_UNDEF || vtype(tp_fn) == T_NULL) return try_ordinary_to_primitive(js, value, hint); if (vtype(tp_fn) != T_FUNC && vtype(tp_fn) != T_CFUNC) return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.toPrimitive is not a function"); diff --git a/tests/test_toprimitive_null.cjs b/tests/test_toprimitive_null.cjs new file mode 100644 index 0000000..fd46ce8 --- /dev/null +++ b/tests/test_toprimitive_null.cjs @@ -0,0 +1,40 @@ +function assert(condition, message) { + if (!condition) { + console.log("FAIL: " + message); + throw new Error(message); + } +} + +const ordinary = { + [Symbol.toPrimitive]: null, + valueOf() { + return 7; + }, +}; + +const proxied = new Proxy({ + [Symbol.toPrimitive]: null, + valueOf() { + return 9; + }, +}, {}); + +const bad = { + [Symbol.toPrimitive]: 1, + valueOf() { + return 11; + }, +}; + +assert(+ordinary === 7, "ordinary null @@toPrimitive should fall back"); +assert(+proxied === 9, "proxy null @@toPrimitive should fall back"); + +let threw = false; +try { + +bad; +} catch (e) { + threw = e instanceof TypeError; +} + +assert(threw, "non-null non-callable @@toPrimitive should throw"); +console.log("OK"); -- 2.51.2 From 4792f33f4db9bd9b4be03e990cbe15a3f1fa1d2a Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 22:06:07 -0700 Subject: [PATCH 09/16] update readme in advance --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4d225ad..e0a3ace 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,10 @@ curl -fsSL https://ant.themackabu.com/install | bash Ant targets the [WinterTC Minimum Common API](https://min-common-api.proposal.wintertc.org/) specification, the standard for server-side JavaScript interoperability developed by Ecma TC55. -| Suite | Pass rate | Notes | -| ---------------- | --------- | ------------------------------------------ | -| js-zoo (ES1–ES5) | ~100% | | -| js-zoo (ES6) | ~82% | | -| js-zoo (ES2016+) | ~86% | | -| js-zoo (overall) | **88%** | 1211/1368 passing | -| test262 | ~50% | Improving, focus is on real-world coverage | +| Suite | Pass rate | Notes | +| ------------ | --------- | ------------------------------------------ | +| compat-table | **100%** | 1511/1511 (ES1–ES5, ES6, ES2016+, ESNext) | +| test262 | ~64% | Improving; focus is on real-world coverage | ## Benchmarks -- 2.51.2 From e5963afe7f952310b5ce8de1a4b0424f52f5f8d9 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 22:24:03 -0700 Subject: [PATCH 10/16] more with(){} correctness --- src/ant.c | 5 ++ src/silver/compiler.c | 2 +- src/silver/ops/coercion.h | 54 +++++++++++------ tests/test_apply_argument_length_limit.cjs | 27 +++++++++ tests/test_with_proxy_write_delete.cjs | 67 ++++++++++++++++++++++ 5 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 tests/test_apply_argument_length_limit.cjs create mode 100644 tests/test_with_proxy_write_delete.cjs diff --git a/src/ant.c b/src/ant.c index 00a87a6..8a394b9 100644 --- a/src/ant.c +++ b/src/ant.c @@ -2,6 +2,8 @@ #pragma GCC optimize("O3,inline") #endif +#include + #include // IWYU pragma: keep #include "ant.h" @@ -5622,6 +5624,9 @@ ant_value_t extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_arg ant_value_t len_result = proxy_aware_length(js, arr, &raw_len); if (is_err(len_result)) return len_result; } + if (raw_len > (ant_offset_t)INT_MAX) { + return js_mkerr_typed(js, JS_ERR_RANGE, "Argument list length exceeds engine limit"); + } int len = (int)raw_len; if (len <= 0) return js_mkundef(); diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 6d9f8a9..6db8c67 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -2396,7 +2396,7 @@ void compile_delete(sv_compiler_t *c, sv_ast_t *node) { compile_expr(c, arg->right); emit_op(c, OP_DELETE); } else if (arg->type == N_IDENT) { - emit_atom_op(c, OP_DELETE_VAR, arg->str, arg->len); + emit_atom_op(c, c->with_depth > 0 ? OP_WITH_DEL_VAR : OP_DELETE_VAR, arg->str, arg->len); } else { compile_expr(c, arg); emit_op(c, OP_POP); diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index e31d831..8b09864 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -448,14 +448,23 @@ static inline ant_value_t sv_op_with_put_var( sv_atom_t *a = &func->atoms[atom_idx]; ant_value_t val = vm->stack[--vm->sp]; - if (vtype(frame->with_obj) != T_UNDEF) if (lkp(js, frame->with_obj, a->str, a->len) != 0) { - ant_value_t out = js_mkundef(); - bool abrupt = false; - if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; - if (abrupt) return out; - ant_value_t key = js_mkstr(js, a->str, a->len); - js_setprop(js, frame->with_obj, key, val); - return js_mkundef(); + if (vtype(frame->with_obj) != T_UNDEF) { + bool has_binding = false; + if (is_proxy(js_as_obj(frame->with_obj))) { + ant_value_t has = js_proxy_has(js, frame->with_obj, a->str, a->len); + if (is_err(has)) return has; + has_binding = js_truthy(js, has); + } else has_binding = lkp(js, frame->with_obj, a->str, a->len) != 0; + + if (has_binding) { + ant_value_t out = js_mkundef(); + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; + if (abrupt) return out; + ant_value_t key = js_mkstr(js, a->str, a->len); + js_setprop(js, frame->with_obj, key, val); + return js_mkundef(); + } } fallback: @@ -473,19 +482,30 @@ static inline ant_value_t sv_op_with_del_var( uint32_t atom_idx = sv_get_u32(ip + 1); sv_atom_t *a = &func->atoms[atom_idx]; - if (vtype(frame->with_obj) != T_UNDEF) if (lkp(js, frame->with_obj, a->str, a->len) != 0) { - ant_value_t out = js_mkundef(); - bool abrupt = false; - if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; - if (abrupt) return out; - ant_value_t result = js_delete_prop(js, frame->with_obj, a->str, a->len); - vm->stack[vm->sp++] = result; - return js_mkundef(); + if (vtype(frame->with_obj) != T_UNDEF) { + bool has_binding = false; + if (is_proxy(js_as_obj(frame->with_obj))) { + ant_value_t has = js_proxy_has(js, frame->with_obj, a->str, a->len); + if (is_err(has)) return has; + has_binding = js_truthy(js, has); + } else has_binding = lkp(js, frame->with_obj, a->str, a->len) != 0; + + if (has_binding) { + ant_value_t out = js_mkundef(); + bool abrupt = false; + if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; + if (abrupt) return out; + ant_value_t result = js_delete_prop(js, frame->with_obj, a->str, a->len); + if (is_err(result)) return result; + vm->stack[vm->sp++] = result; + return js_mkundef(); + } } fallback: ant_value_t result = js_delete_prop(js, js->global, a->str, a->len); - bool ok = !is_err(result) && js_truthy(js, result); + if (is_err(result)) return result; + bool ok = js_truthy(js, result); vm->stack[vm->sp++] = mkval(T_BOOL, ok); return js_mkundef(); } diff --git a/tests/test_apply_argument_length_limit.cjs b/tests/test_apply_argument_length_limit.cjs new file mode 100644 index 0000000..03fa7a4 --- /dev/null +++ b/tests/test_apply_argument_length_limit.cjs @@ -0,0 +1,27 @@ +function assert(condition, message) { + if (!condition) { + console.log("FAIL: " + message); + throw new Error(message); + } +} + +const tooLong = { length: 2147483648 }; + +let applyThrew = false; +try { + (function () {}).apply(null, tooLong); +} catch (e) { + applyThrew = e instanceof RangeError; +} + +let reflectThrew = false; +try { + Reflect.apply(function () {}, null, tooLong); +} catch (e) { + reflectThrew = e instanceof RangeError; +} + +assert(applyThrew, "Function.prototype.apply should reject oversized argument lists"); +assert(reflectThrew, "Reflect.apply should reject oversized argument lists"); + +console.log("OK"); diff --git a/tests/test_with_proxy_write_delete.cjs b/tests/test_with_proxy_write_delete.cjs new file mode 100644 index 0000000..d9c8436 --- /dev/null +++ b/tests/test_with_proxy_write_delete.cjs @@ -0,0 +1,67 @@ +function assert(condition, message) { + if (!condition) { + console.log("FAIL: " + message); + throw new Error(message); + } +} + +globalThis.x = 1; +const events = []; +const target = {}; +const proxy = new Proxy(target, { + has(_target, key) { + events.push("has:" + String(key)); + return key === "x"; + }, + set(_target, key, value) { + events.push("set:" + String(key) + ":" + value); + target[key] = value; + return true; + }, + deleteProperty(_target, key) { + events.push("delete:" + String(key)); + delete target[key]; + return true; + }, +}); + +with (proxy) { + x = 2; +} + +assert(globalThis.x === 1, "proxy with assignment should not fall back to global"); +assert(target.x === 2, "proxy with assignment should write through proxy"); +assert(events.indexOf("has:x") >= 0, "proxy has trap should be used for assignment"); +assert(events.indexOf("set:x:2") >= 0, "proxy set trap should be used for assignment"); + +const deleteResult = (function () { + with (proxy) { + return delete x; + } +})(); + +assert(deleteResult === true, "proxy with delete should return delete result"); +assert(!("x" in target), "proxy with delete should delete through proxy"); +assert(events.indexOf("delete:x") >= 0, "proxy delete trap should be used"); + +const throwingProxy = new Proxy({}, { + has(_target, key) { + return key === "x"; + }, + deleteProperty() { + throw new Error("delete boom"); + }, +}); + +let propagated = false; +try { + with (throwingProxy) { + delete x; + } +} catch (e) { + propagated = e.message === "delete boom"; +} + +assert(propagated, "proxy with delete should propagate thrown delete errors"); + +console.log("OK"); -- 2.51.2 From bb4b2526f74b7405fd312e32f584465dd6e9a4d3 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 5 May 2026 22:33:06 -0700 Subject: [PATCH 11/16] url local suspect change --- src/ant.c | 7 ++++ src/modules/url.c | 20 ++++++----- .../test_proxy_array_flat_urlsearchparams.cjs | 34 +++++++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 tests/test_proxy_array_flat_urlsearchparams.cjs diff --git a/src/ant.c b/src/ant.c index 8a394b9..3ea1da7 100644 --- a/src/ant.c +++ b/src/ant.c @@ -15754,6 +15754,13 @@ static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size memcpy(key_buf, key, len); key_buf[len] = '\0'; + if (vtype(target) == T_ARR) { + if (is_length_key(key_buf, len)) return js_true; + unsigned long idx = 0; + if (parse_array_index(key_buf, len, get_array_length(js, target), &idx)) + return js_bool(arr_has(js, target, (ant_offset_t)idx)); + } + ant_offset_t off = lkp_proto(js, target, key_buf, len); return js_bool(off != 0); } diff --git a/src/modules/url.c b/src/modules/url.c index e63a86b..eccd5d2 100644 --- a/src/modules/url.c +++ b/src/modules/url.c @@ -1345,19 +1345,23 @@ static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) { return obj; } - bool init_is_array = false; - ant_value_t is_array_res = js_is_array_value_checked(js, init, &init_is_array); - if (is_err(is_array_res)) return is_array_res; - if (js->thrown_exists) return mkval(T_ERR, 0); + bool init_is_array = t == T_ARR; + if (!init_is_array && t == T_OBJ) { + ant_value_t is_array_res = js_is_array_value_checked(js, init, &init_is_array); + if (is_err(is_array_res)) return is_array_res; + if (js->thrown_exists) return mkval(T_ERR, 0); + } if (init_is_array) { ant_offset_t len = js_arr_len(js, init); for (ant_offset_t i = 0; i < len; i++) { ant_value_t pair = js_arr_get(js, init, i); - bool pair_is_array = false; - is_array_res = js_is_array_value_checked(js, pair, &pair_is_array); - if (is_err(is_array_res)) return is_array_res; - if (js->thrown_exists) return mkval(T_ERR, 0); + bool pair_is_array = vtype(pair) == T_ARR; + if (!pair_is_array && vtype(pair) == T_OBJ) { + ant_value_t is_array_res = js_is_array_value_checked(js, pair, &pair_is_array); + if (is_err(is_array_res)) return is_array_res; + if (js->thrown_exists) return mkval(T_ERR, 0); + } if (!pair_is_array) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URLSearchParams': Each element must be an array."); diff --git a/tests/test_proxy_array_flat_urlsearchparams.cjs b/tests/test_proxy_array_flat_urlsearchparams.cjs new file mode 100644 index 0000000..854f73e --- /dev/null +++ b/tests/test_proxy_array_flat_urlsearchparams.cjs @@ -0,0 +1,34 @@ +function assert(condition, message) { + if (!condition) { + console.log("FAIL: " + message); + throw new Error(message); + } +} + +const proxiedArray = new Proxy([[1, 2], [3, 4]], {}); +assert(JSON.stringify(proxiedArray.flat()) === "[1,2,3,4]", "flat should see array proxy elements"); +assert(JSON.stringify(proxiedArray.flatMap(function (x) { return x; })) === "[1,2,3,4]", "flatMap should see array proxy elements"); +assert(0 in new Proxy([1], {}), "in operator should see array proxy indices"); +assert("length" in new Proxy([], {}), "in operator should see array proxy length"); + +let sawOuterLength = false; +let sawPairLength = false; +const pair = new Proxy(["a", "b"], { + get(target, key, receiver) { + if (key === "length") sawPairLength = true; + return Reflect.get(target, key, receiver); + }, +}); +const init = new Proxy([pair], { + get(target, key, receiver) { + if (key === "length") sawOuterLength = true; + return Reflect.get(target, key, receiver); + }, +}); + +const params = new URLSearchParams(init); +assert(params.toString() === "a=b", "URLSearchParams should accept proxied array pairs"); +assert(sawOuterLength, "URLSearchParams should observe outer proxy length"); +assert(sawPairLength, "URLSearchParams should observe pair proxy length"); + +console.log("OK"); -- 2.51.2 From 5ccdc969840b50a253ee7b0431a3efb13ec0e4af Mon Sep 17 00:00:00 2001 From: theMackabu Date: Wed, 6 May 2026 01:17:54 -0700 Subject: [PATCH 12/16] perf improvement on flatMap --- README.md | 2 +- src/ant.c | 170 ++++++++++++++++---- tests/test_array_flat_prototype_indices.cjs | 29 ++++ 3 files changed, 170 insertions(+), 31 deletions(-) create mode 100644 tests/test_array_flat_prototype_indices.cjs diff --git a/README.md b/README.md index e0a3ace..6d22f0b 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ For information on reporting security vulnerabilities in Ant, see [SECURITY.md]( ## Community - [Discord](http://discord.gg/CH7YSjWGzY) -- [Blog: Building Ant](https://s.tail.so/js-in-one-month) +- [Blog: Working was the beginning](https://themackabu.dev/blog/ant-part-two) - [DeepWiki: Ant internals](https://deepwiki.com/theMackabu/ant) ## Contributing to Ant diff --git a/src/ant.c b/src/ant.c index 3ea1da7..dc7298b 100644 --- a/src/ant.c +++ b/src/ant.c @@ -606,10 +606,11 @@ static ant_offset_t get_dense_buf(ant_value_t arr); static ant_offset_t dense_capacity(ant_offset_t doff); static ant_offset_t get_array_length(ant_t *js, ant_value_t arr); static ant_value_t arr_get(ant_t *js, ant_value_t arr, ant_offset_t idx); -static bool arr_has(ant_t *js, ant_value_t arr, ant_offset_t idx); +static bool arr_has(ant_t *js, ant_value_t arr, ant_offset_t idx); static bool streq(const char *buf, size_t len, const char *p, size_t n); static bool parse_func_params(ant_t *js, uint8_t *flags, int *out_count); +static bool try_accessor_getter(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t *out); static bool try_dynamic_setter(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t value); static uintptr_t lkp_with_setter(ant_t *js, ant_value_t obj, const char *buf, size_t len, ant_value_t *setter_out, bool *has_setter_out); @@ -632,6 +633,7 @@ static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, i static inline bool is_slot_prop(ant_offset_t header); static inline ant_offset_t next_prop(ant_offset_t header); +static inline const ant_shape_prop_t *prop_shape_meta(ant_t *js, ant_offset_t propoff); static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs); static ant_value_t proxy_get_method(ant_t *js, ant_value_t handler, const char *name); @@ -2690,8 +2692,39 @@ static inline ant_value_t arr_get(ant_t *js, ant_value_t arr, ant_offset_t idx) char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx); - - return lkp_val(js, arr, idxstr, idxlen); + + ant_value_t cur = arr; + for (int depth = 0; is_object_type(cur) && depth < MAX_PROTO_CHAIN_DEPTH; depth++) { + ant_value_t cur_obj = js_as_obj(cur); + if (!array_obj_ptr(cur_obj)) goto shape_lookup; + + ant_offset_t cur_doff = get_dense_buf(cur_obj); + if (!cur_doff) goto shape_lookup; + + ant_offset_t cur_len = dense_iterable_length(js, cur_obj); + if (idx >= cur_len) goto shape_lookup; + + ant_value_t v = dense_get(cur_doff, idx); + if (!is_empty_slot(v)) return v; + +shape_lookup:; + ant_offset_t off = lkp(js, cur_obj, idxstr, idxlen); + if (off != 0) { + const ant_shape_prop_t *prop_meta = prop_shape_meta(js, off); + if (prop_meta && prop_meta->has_getter) { + ant_value_t accessor_result; + if (try_accessor_getter(js, arr, idxstr, idxlen, &accessor_result)) + return accessor_result; + } + return propref_load(js, off); + } + + ant_value_t proto = get_proto(js, cur_obj); + if (!is_object_type(proto)) break; + cur = proto; + } + + return js_mkundef(); } static inline void arr_set(ant_t *js, ant_value_t arr, ant_offset_t idx, ant_value_t val) { @@ -2749,8 +2782,28 @@ static inline bool arr_has(ant_t *js, ant_value_t arr, ant_offset_t idx) { char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx); - - return lkp(js, arr, idxstr, idxlen) != 0; + + ant_value_t cur = arr; + for (int depth = 0; is_object_type(cur) && depth < MAX_PROTO_CHAIN_DEPTH; depth++) { + ant_value_t cur_obj = js_as_obj(cur); + if (!array_obj_ptr(cur_obj)) goto shape_has; + + ant_offset_t cur_doff = get_dense_buf(cur_obj); + if (!cur_doff) goto shape_has; + + ant_offset_t cur_len = dense_iterable_length(js, cur_obj); + if (idx >= cur_len) goto shape_has; + if (!is_empty_slot(dense_get(cur_doff, idx))) return true; + +shape_has:; + if (lkp(js, cur_obj, idxstr, idxlen) != 0) return true; + + ant_value_t proto = get_proto(js, cur_obj); + if (!is_object_type(proto)) break; + cur = proto; + } + + return false; } enum { ANT_ARGUMENTS_NATIVE_TAG = 0x41524753u }; // ARGS @@ -10359,6 +10412,31 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) return accumulator; } +static inline ant_value_t *packed_array_data_for_length(ant_t *js, ant_value_t arr, ant_offset_t len) { + if (vtype(arr) != T_ARR || array_may_have_holes(arr)) return NULL; + + ant_offset_t doff = get_dense_buf(arr); + ant_value_t *dense = doff ? dense_data(doff) : NULL; + if (!dense || dense_iterable_length(js, arr) != len) return NULL; + + for (ant_offset_t i = 0; i < len; i++) + if (is_empty_slot(dense[i])) return NULL; + + return dense; +} + +static inline ant_value_t flat_should_spread(ant_t *js, ant_value_t val, int depth, bool *out) { + *out = false; + if (depth <= 0) return js_mkundef(); + + if (vtype(val) == T_ARR) { + *out = true; + return js_mkundef(); + } + + return is_proxy(val) ? js_is_array_value_checked(js, val, out) : js_mkundef(); +} + static inline ant_value_t flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) { bool input_is_array = vtype(arr) == T_ARR; ant_offset_t len = input_is_array ? get_array_length(js, arr) : 0; @@ -10367,7 +10445,25 @@ static inline ant_value_t flat_helper(ant_t *js, ant_value_t arr, ant_value_t re if (is_err(len_result)) return len_result; } if (len == 0) return js_mkundef(); - + + ant_value_t *dense = packed_array_data_for_length(js, arr, len); + if (dense) { + for (ant_offset_t i = 0; i < len; i++) { + bool spread = false; + ant_value_t spread_res = flat_should_spread(js, dense[i], depth, &spread); + if (is_err(spread_res)) return spread_res; + if (!spread) { + arr_set(js, result, *result_idx, dense[i]); + (*result_idx)++; + continue; + } + + ant_value_t flat_res = flat_helper(js, dense[i], result, result_idx, depth - 1); + if (is_err(flat_res)) return flat_res; + } + return js_mkundef(); + } + for (ant_offset_t i = 0; i < len; i++) { if (input_is_array) { if (!arr_has(js, arr, i)) continue; @@ -10380,25 +10476,34 @@ static inline ant_value_t flat_helper(ant_t *js, ant_value_t arr, ant_value_t re ant_value_t val = input_is_array ? arr_get(js, arr, i) : array_method_get_index(js, arr, i); if (is_err(val)) return val; - bool val_is_array = false; - if (depth > 0) { - val_is_array = vtype(val) == T_ARR; - if (!val_is_array && is_proxy(val)) { - ant_value_t is_array_res = js_is_array_value_checked(js, val, &val_is_array); - if (is_err(is_array_res)) return is_array_res; - } + bool spread = false; + ant_value_t spread_res = flat_should_spread(js, val, depth, &spread); + if (is_err(spread_res)) return spread_res; + if (!spread) { + arr_set(js, result, *result_idx, val); + (*result_idx)++; + continue; } - if (val_is_array) { - ant_value_t flat_res = flat_helper(js, val, result, result_idx, depth - 1); - if (is_err(flat_res)) return flat_res; - } - else { arr_set(js, result, *result_idx, val); (*result_idx)++; } + ant_value_t flat_res = flat_helper(js, val, result, result_idx, depth - 1); + if (is_err(flat_res)) return flat_res; } return js_mkundef(); } +static inline ant_value_t flat_append_mapped_value(ant_t *js, ant_value_t mapped, ant_value_t result, ant_offset_t *result_idx) { + bool spread = false; + ant_value_t spread_res = flat_should_spread(js, mapped, 1, &spread); + if (is_err(spread_res)) return spread_res; + if (!spread) { + arr_set(js, result, (*result_idx)++, mapped); + return js_mkundef(); + } + + return flat_helper(js, mapped, result, result_idx, 0); +} + static ant_value_t builtin_array_flat(ant_t *js, ant_value_t *args, int nargs) { ant_value_t arr = js->this_val; if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) { @@ -10611,7 +10716,21 @@ static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs ant_value_t result = mkarr(js); if (is_err(result)) return result; ant_offset_t result_idx = 0; - + + ant_value_t *dense = packed_array_data_for_length(js, arr, len); + if (dense) { + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t elem = dense[i]; + ant_value_t call_args[3] = { elem, tov((double)i), arr }; + ant_value_t mapped = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); + if (is_err(mapped)) return mapped; + ant_value_t flat_res = flat_append_mapped_value(js, mapped, result, &result_idx); + if (is_err(flat_res)) return flat_res; + } + + return mkval(T_ARR, vdata(result)); + } + for (ant_offset_t i = 0; i < len; i++) { if (input_is_array) { if (!arr_has(js, arr, i)) continue; @@ -10626,17 +10745,8 @@ static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs ant_value_t call_args[3] = { elem, tov((double)i), arr }; ant_value_t mapped = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); if (is_err(mapped)) return mapped; - bool mapped_is_array = vtype(mapped) == T_ARR; - if (!mapped_is_array && is_proxy(mapped)) { - ant_value_t is_array_res = js_is_array_value_checked(js, mapped, &mapped_is_array); - if (is_err(is_array_res)) return is_array_res; - } - - if (mapped_is_array) { - ant_value_t flat_res = flat_helper(js, mapped, result, &result_idx, 0); - if (is_err(flat_res)) return flat_res; - } - else arr_set(js, result, result_idx++, mapped); + ant_value_t flat_res = flat_append_mapped_value(js, mapped, result, &result_idx); + if (is_err(flat_res)) return flat_res; } return mkval(T_ARR, vdata(result)); diff --git a/tests/test_array_flat_prototype_indices.cjs b/tests/test_array_flat_prototype_indices.cjs new file mode 100644 index 0000000..aeb518e --- /dev/null +++ b/tests/test_array_flat_prototype_indices.cjs @@ -0,0 +1,29 @@ +function assert(condition, message) { + if (!condition) { + console.log("FAIL: " + message); + throw new Error(message); + } +} + +Array.prototype[0] = 9; +Array.prototype[1] = [7, 8]; + +try { + const flatResult = [, 1].flat(); + assert(JSON.stringify(flatResult) === "[9,1]", "flat should read inherited index values"); + + const flatMapResult = [, 2].flatMap(function (value) { + return [value]; + }); + assert(JSON.stringify(flatMapResult) === "[9,2]", "flatMap should visit inherited index values"); + + const nestedResult = [, 3].flatMap(function (_value, index) { + return index === 0 ? Array.prototype[1] : [_value]; + }); + assert(JSON.stringify(nestedResult) === "[7,8,3]", "flatMap should flatten inherited array results"); +} finally { + delete Array.prototype[0]; + delete Array.prototype[1]; +} + +console.log("OK"); -- 2.51.2 From 6af36674d2511518a86457e3e8aa61d3b75bd626 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Wed, 6 May 2026 01:34:07 -0700 Subject: [PATCH 13/16] emit proper usp array length --- examples/spec/typeof.js | 7 +++ src/modules/url.c | 51 ++++++++++++++---- src/silver/compiler.c | 2 +- .../test_proxy_array_flat_urlsearchparams.cjs | 52 +++++++++++++++++++ 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/examples/spec/typeof.js b/examples/spec/typeof.js index c7f6701..11af54b 100644 --- a/examples/spec/typeof.js +++ b/examples/spec/typeof.js @@ -30,4 +30,11 @@ test('typeof arrow', typeof (() => {}), 'function'); test('typeof class', typeof class {}, 'function'); test('typeof symbol', typeof Symbol('test'), 'symbol'); +let localNumber = 1; +let withTypeofResult; +with ({ localNumber: 'shadowed' }) { + withTypeofResult = typeof localNumber; +} +test('typeof local respects with binding', withTypeofResult, 'string'); + summary(); diff --git a/src/modules/url.c b/src/modules/url.c index eccd5d2..b00f4c1 100644 --- a/src/modules/url.c +++ b/src/modules/url.c @@ -37,6 +37,27 @@ bool usp_is_urlsearchparams(ant_t *js, ant_value_t obj) { return js_check_brand(obj, BRAND_URLSEARCHPARAMS); } +static ant_value_t usp_array_len(ant_t *js, ant_value_t arr, ant_offset_t *out) { + *out = 0; + if (!is_proxy(arr)) { + *out = js_arr_len(js, arr); + return js_mkundef(); + } + + ant_value_t length = js_get(js, arr, "length"); + if (is_err(length)) return length; + if (js->thrown_exists) return mkval(T_ERR, 0); + if (vtype(length) == T_NUM) *out = (ant_offset_t)tod(length); + return js_mkundef(); +} + +static ant_value_t usp_array_get(ant_t *js, ant_value_t arr, ant_offset_t idx) { + ant_value_t value = js_arr_get(js, arr, idx); + if (is_err(value)) return value; + if (js->thrown_exists) return mkval(T_ERR, 0); + return value; +} + void url_state_clear(url_state_t *s) { free(s->protocol); free(s->username); free(s->password); free(s->hostname); free(s->port); free(s->pathname); @@ -1353,34 +1374,46 @@ static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) { } if (init_is_array) { - ant_offset_t len = js_arr_len(js, init); + ant_offset_t len = 0; + ant_value_t len_res = usp_array_len(js, init, &len); + + if (is_err(len_res)) return len_res; for (ant_offset_t i = 0; i < len; i++) { - ant_value_t pair = js_arr_get(js, init, i); + ant_value_t pair = usp_array_get(js, init, i); + if (is_err(pair)) return pair; + bool pair_is_array = vtype(pair) == T_ARR; if (!pair_is_array && vtype(pair) == T_OBJ) { ant_value_t is_array_res = js_is_array_value_checked(js, pair, &pair_is_array); if (is_err(is_array_res)) return is_array_res; if (js->thrown_exists) return mkval(T_ERR, 0); } + if (!pair_is_array) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URLSearchParams': Each element must be an array."); - - ant_offset_t plen = js_arr_len(js, pair); + + ant_offset_t plen = 0; + ant_value_t plen_res = usp_array_len(js, pair, &plen); + + if (is_err(plen_res)) return plen_res; if (plen != 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URLSearchParams': Each pair must have exactly 2 elements."); - ant_value_t pk = js_arr_get(js, pair, 0); - ant_value_t pv = js_arr_get(js, pair, 1); - ant_value_t ksv = (vtype(pk) == T_STR) ? pk : js_tostring_val(js, pk); + ant_value_t pk = usp_array_get(js, pair, 0); + if (is_err(pk)) return pk; + + ant_value_t pv = usp_array_get(js, pair, 1); + if (is_err(pv)) return pv; + ant_value_t ksv = (vtype(pk) == T_STR) ? pk : js_tostring_val(js, pk); if (is_err(ksv)) return ksv; - ant_value_t vsv = (vtype(pv) == T_STR) ? pv : js_tostring_val(js, pv); + ant_value_t vsv = (vtype(pv) == T_STR) ? pv : js_tostring_val(js, pv); if (is_err(vsv)) return vsv; - ant_value_t entry = js_mkarr(js); + ant_value_t entry = js_mkarr(js); js_arr_push(js, entry, ksv); js_arr_push(js, entry, vsv); js_arr_push(js, entries, entry); diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 6db8c67..9f307cf 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -2317,7 +2317,7 @@ void compile_typeof(sv_compiler_t *c, sv_ast_t *node) { if (local != -1) { uint8_t inferred = get_local_inferred_type(c, local); const char *known = typeof_name_for_type(inferred); - if (known) { + if (known && c->with_depth == 0) { emit_constant(c, js_mkstr_permanent(c->js, known, strlen(known))); return; } diff --git a/tests/test_proxy_array_flat_urlsearchparams.cjs b/tests/test_proxy_array_flat_urlsearchparams.cjs index 854f73e..fe3f26e 100644 --- a/tests/test_proxy_array_flat_urlsearchparams.cjs +++ b/tests/test_proxy_array_flat_urlsearchparams.cjs @@ -31,4 +31,56 @@ assert(params.toString() === "a=b", "URLSearchParams should accept proxied array assert(sawOuterLength, "URLSearchParams should observe outer proxy length"); assert(sawPairLength, "URLSearchParams should observe pair proxy length"); +assertThrows( + function () { + new URLSearchParams(new Proxy([pair], { + get(target, key, receiver) { + if (key === "0") throw new Error("outer index boom"); + return Reflect.get(target, key, receiver); + }, + })); + }, + "outer index boom", + "URLSearchParams should propagate outer proxy index errors" +); + +assertThrows( + function () { + new URLSearchParams([new Proxy(["a", "b"], { + get(target, key, receiver) { + if (key === "length") throw new Error("pair length boom"); + return Reflect.get(target, key, receiver); + }, + })]); + }, + "pair length boom", + "URLSearchParams should propagate pair proxy length errors" +); + +assertThrows( + function () { + new URLSearchParams([new Proxy(["a", "b"], { + get(target, key, receiver) { + if (key === "1") throw new Error("pair value boom"); + return Reflect.get(target, key, receiver); + }, + })]); + }, + "pair value boom", + "URLSearchParams should propagate pair proxy value errors" +); + console.log("OK"); + +function assertThrows(fn, expected, message) { + try { + fn(); + } catch (err) { + if (String(err && err.message).indexOf(expected) >= 0) return; + console.log("FAIL: " + message + ": wrong error " + String(err && err.message)); + throw err; + } + + console.log("FAIL: " + message + ": did not throw"); + throw new Error(message); +} -- 2.51.2 From 923cf91f34bf6a01a28693fe9d6d38c4437603a9 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Wed, 6 May 2026 01:40:27 -0700 Subject: [PATCH 14/16] fix thrown set trap --- src/silver/ops/coercion.h | 10 ++++--- tests/test_with_proxy_write_delete.cjs | 40 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index 8b09864..5aea603 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -462,15 +462,17 @@ static inline ant_value_t sv_op_with_put_var( if (sv_with_binding_is_unscopable(js, frame->with_obj, a, &out, &abrupt)) goto fallback; if (abrupt) return out; ant_value_t key = js_mkstr(js, a->str, a->len); - js_setprop(js, frame->with_obj, key, val); + ant_value_t set_result = js_setprop(js, frame->with_obj, key, val); + if (is_err(set_result)) return set_result; return js_mkundef(); } } fallback: - if (fb_kind == WITH_FB_GLOBAL) - setprop_interned(js, js->global, a->str, a->len, val); - else sv_with_fallback_put(vm, js, frame, fb_kind, fb_idx, val); + if (fb_kind == WITH_FB_GLOBAL) { + ant_value_t set_result = setprop_interned(js, js->global, a->str, a->len, val); + if (is_err(set_result)) return set_result; + } else sv_with_fallback_put(vm, js, frame, fb_kind, fb_idx, val); return js_mkundef(); } diff --git a/tests/test_with_proxy_write_delete.cjs b/tests/test_with_proxy_write_delete.cjs index d9c8436..67166b0 100644 --- a/tests/test_with_proxy_write_delete.cjs +++ b/tests/test_with_proxy_write_delete.cjs @@ -64,4 +64,44 @@ try { assert(propagated, "proxy with delete should propagate thrown delete errors"); +const throwingSetProxy = new Proxy({}, { + has(_target, key) { + return key === "x"; + }, + set() { + throw new Error("set boom"); + }, +}); + +propagated = false; +try { + with (throwingSetProxy) { + x = 3; + } +} catch (e) { + propagated = e.message === "set boom"; +} + +assert(propagated, "proxy with assignment should propagate thrown set errors"); + +Object.defineProperty(globalThis, "withGlobalSetterThrow", { + configurable: true, + set() { + throw new Error("global setter boom"); + }, +}); + +propagated = false; +try { + with ({}) { + withGlobalSetterThrow = 1; + } +} catch (e) { + propagated = e.message === "global setter boom"; +} finally { + delete globalThis.withGlobalSetterThrow; +} + +assert(propagated, "with global fallback assignment should propagate thrown setters"); + console.log("OK"); -- 2.51.2 From 70a63a23bf3cbf704505bb7c737076b7f80937f3 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Wed, 6 May 2026 01:40:33 -0700 Subject: [PATCH 15/16] update readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6d22f0b..fefdd6f 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,10 @@ hyperfine --warmup 10 --runs 100 \ | Runtime | Mean | Min | Max | Relative | | ------- | ---------- | ------- | -------- | ------------ | -| **Ant** | **5.7 ms** | 5.0 ms | 7.3 ms | **1.00** | -| Bun | 12.8 ms | 11.6 ms | 16.4 ms | 2.24× slower | -| Deno | 24.8 ms | 22.2 ms | 29.4 ms | 4.32× slower | -| Node | 31.1 ms | 27.1 ms | 151.7 ms | 5.41× slower | +| **Ant** | **5.4 ms** | 5.0 ms | 7.1 ms | **1.00** | +| Bun | 12.8 ms | 11.6 ms | 16.4 ms | 2.37× slower | +| Deno | 24.8 ms | 22.2 ms | 29.4 ms | 4.59× slower | +| Node | 31.1 ms | 27.1 ms | 151.7 ms | 5.76× slower |
Environment @@ -83,7 +83,7 @@ hyperfine --warmup 10 --runs 100 \ | -------- | --------------------------------- | | Hardware | Apple M4 Pro, 24 GB RAM, 14 cores | | OS | macOS 15.7.5 (arm64) | -| Ant | 0.9.1 | +| Ant | 0.11.0 | | Node | 25.9.0 | | Bun | 1.3.13 | | Deno | 2.7.12 | -- 2.51.2 From d615e239b9175bbf02b57cd65237eea476c0a650 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Wed, 6 May 2026 03:21:32 -0700 Subject: [PATCH 16/16] quick little fix --- src/modules/url.c | 9 ++++- .../test_proxy_array_flat_urlsearchparams.cjs | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/modules/url.c b/src/modules/url.c index b00f4c1..98678eb 100644 --- a/src/modules/url.c +++ b/src/modules/url.c @@ -47,7 +47,14 @@ static ant_value_t usp_array_len(ant_t *js, ant_value_t arr, ant_offset_t *out) ant_value_t length = js_get(js, arr, "length"); if (is_err(length)) return length; if (js->thrown_exists) return mkval(T_ERR, 0); - if (vtype(length) == T_NUM) *out = (ant_offset_t)tod(length); + + double len_num = js_to_number(js, length); + if (js->thrown_exists) return mkval(T_ERR, 0); + + if (isnan(len_num) || !isfinite(len_num)) + return js_mkerr_typed(js, JS_ERR_TYPE, "URLSearchParams sequence length must be finite"); + + *out = (ant_offset_t)js_to_uint32(len_num); return js_mkundef(); } diff --git a/tests/test_proxy_array_flat_urlsearchparams.cjs b/tests/test_proxy_array_flat_urlsearchparams.cjs index fe3f26e..dbd8d22 100644 --- a/tests/test_proxy_array_flat_urlsearchparams.cjs +++ b/tests/test_proxy_array_flat_urlsearchparams.cjs @@ -31,6 +31,14 @@ assert(params.toString() === "a=b", "URLSearchParams should accept proxied array assert(sawOuterLength, "URLSearchParams should observe outer proxy length"); assert(sawPairLength, "URLSearchParams should observe pair proxy length"); +const stringLengthParams = new URLSearchParams(new Proxy([pair], { + get(target, key, receiver) { + if (key === "length") return "1"; + return Reflect.get(target, key, receiver); + }, +})); +assert(stringLengthParams.toString() === "a=b", "URLSearchParams should convert proxy length with ToNumber"); + assertThrows( function () { new URLSearchParams(new Proxy([pair], { @@ -70,6 +78,32 @@ assertThrows( "URLSearchParams should propagate pair proxy value errors" ); +assertThrows( + function () { + new URLSearchParams(new Proxy([pair], { + get(target, key, receiver) { + if (key === "length") return NaN; + return Reflect.get(target, key, receiver); + }, + })); + }, + "finite", + "URLSearchParams should reject NaN proxy lengths" +); + +assertThrows( + function () { + new URLSearchParams(new Proxy([pair], { + get(target, key, receiver) { + if (key === "length") return { valueOf() { throw new Error("length convert boom"); } }; + return Reflect.get(target, key, receiver); + }, + })); + }, + "length convert boom", + "URLSearchParams should propagate proxy length conversion errors" +); + console.log("OK"); function assertThrows(fn, expected, message) {