From 5a699415f8d775f56464bf902b539a4cb9e61fe7 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Fri, 8 May 2026 15:24:53 -0700 Subject: [PATCH] improve T_CFUNC promotion fallback --- src/ant.c | 25 ++++++++++ src/modules/collections.c | 50 ++++++++++---------- src/modules/reflect.c | 27 +++++++++++ tests/test_collection_method_lengths.cjs | 58 ++++++++++++++++++++++++ tests/test_native_method_descriptors.cjs | 13 ++++++ 5 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 tests/test_collection_method_lengths.cjs diff --git a/src/ant.c b/src/ant.c index add769e..170f412 100644 --- a/src/ant.c +++ b/src/ant.c @@ -4539,6 +4539,11 @@ static ant_value_t get_proto(ant_t *js, ant_value_t obj) { } void js_set_proto(ant_value_t obj, ant_value_t proto) { + if (vtype(obj) == T_CFUNC) { + ant_value_t promoted = js_cfunc_promote(rt->js, obj); + if (is_err(promoted)) return; + obj = promoted; + } if (!is_object_type(obj)) return; ant_value_t as_obj = js_as_obj(obj); @@ -4557,6 +4562,11 @@ void js_set_proto_init(ant_value_t obj, ant_value_t proto) { } static void set_proto(ant_t *js, ant_value_t obj, ant_value_t proto) { + if (vtype(obj) == T_CFUNC) { + ant_value_t promoted = js_cfunc_promote(js, obj); + if (is_err(promoted)) return; + obj = promoted; + } ant_object_t *ptr = js_obj_ptr(js_as_obj(obj)); js_set_proto(obj, proto); if (ptr) gc_write_barrier(js, ptr, proto); @@ -5132,6 +5142,11 @@ static ant_value_t check_frozen_sealed(ant_t *js, ant_value_t obj, const char *a } ant_value_t js_delete_prop(ant_t *js, ant_value_t obj, const char *key, size_t len) { + if (vtype(obj) == T_CFUNC) { + ant_value_t promoted = js_cfunc_promote(js, obj); + if (is_err(promoted)) return promoted; + obj = promoted; + } ant_value_t original_obj = obj; obj = js_as_obj(obj); ant_object_t *ptr = js_obj_ptr(obj); @@ -5196,6 +5211,11 @@ ant_value_t js_delete_prop(ant_t *js, ant_value_t obj, const char *key, size_t l } ant_value_t js_delete_sym_prop(ant_t *js, ant_value_t obj, ant_value_t sym) { + if (vtype(obj) == T_CFUNC) { + ant_value_t promoted = js_cfunc_promote(js, obj); + if (is_err(promoted)) return promoted; + obj = promoted; + } obj = js_as_obj(obj); ant_object_t *ptr = js_obj_ptr(obj); if (!ptr) return js_true; @@ -17584,6 +17604,11 @@ static bool js_try_get(ant_t *js, ant_value_t obj, const char *key, ant_value_t *out = accessor_result; return true; } + off = lkp_proto(js, obj, key, key_len); + if (off != 0) { + *out = propref_load(js, off); + return true; + } return false; } diff --git a/src/modules/collections.c b/src/modules/collections.c index 4255d0c..eb5f6b8 100644 --- a/src/modules/collections.c +++ b/src/modules/collections.c @@ -1652,17 +1652,17 @@ void init_collections_module(void) { ant_value_t map_proto = js_mkobj(js); js_set_proto_init(map_proto, object_proto); - js_set(js, map_proto, "set", js_mkfun(map_set)); - js_set(js, map_proto, "get", js_mkfun(map_get)); - js_set(js, map_proto, "has", js_mkfun(map_has)); - js_set(js, map_proto, "upsert", js_mkfun(map_upsert)); - js_set(js, map_proto, "delete", js_mkfun(map_delete)); + js_set(js, map_proto, "set", js_mkfun_arity(map_set, 2)); + js_set(js, map_proto, "get", js_mkfun_arity(map_get, 1)); + js_set(js, map_proto, "has", js_mkfun_arity(map_has, 1)); + js_set(js, map_proto, "upsert", js_mkfun_arity(map_upsert, 3)); + js_set(js, map_proto, "delete", js_mkfun_arity(map_delete, 1)); js_set(js, map_proto, "clear", js_mkfun(map_clear)); js_set_getter_desc(js, map_proto, "size", 4, js_mkfun(map_size), JS_DESC_C); js_set(js, map_proto, "entries", js_mkfun(map_entries)); js_set(js, map_proto, "keys", js_mkfun(map_keys)); js_set(js, map_proto, "values", js_mkfun(map_values)); - js_set(js, map_proto, "forEach", js_mkfun(map_forEach)); + js_set(js, map_proto, "forEach", js_mkfun_arity(map_forEach, 1)); js_set_sym(js, map_proto, iter_sym, js_get(js, map_proto, "entries")); js_set_sym(js, map_proto, tag_sym, js_mkstr(js, "Map", 3)); @@ -1677,22 +1677,22 @@ void init_collections_module(void) { ant_value_t set_proto = js_mkobj(js); js_set_proto_init(set_proto, object_proto); - js_set(js, set_proto, "add", js_mkfun(set_add)); - js_set(js, set_proto, "has", js_mkfun(set_has)); - js_set(js, set_proto, "delete", js_mkfun(set_delete)); + js_set(js, set_proto, "add", js_mkfun_arity(set_add, 1)); + js_set(js, set_proto, "has", js_mkfun_arity(set_has, 1)); + js_set(js, set_proto, "delete", js_mkfun_arity(set_delete, 1)); js_set(js, set_proto, "clear", js_mkfun(set_clear)); js_set_getter_desc(js, set_proto, "size", 4, js_mkfun(set_size), JS_DESC_C); js_set(js, set_proto, "values", js_mkfun(set_values)); js_set_exact(js, set_proto, "keys", js_get(js, set_proto, "values")); js_set(js, set_proto, "entries", js_mkfun(set_entries)); - js_set(js, set_proto, "forEach", js_mkfun(set_forEach)); - js_set(js, set_proto, "union", js_mkfun(set_union)); - js_set(js, set_proto, "intersection", js_mkfun(set_intersection)); - js_set(js, set_proto, "difference", js_mkfun(set_difference)); - js_set(js, set_proto, "symmetricDifference", js_mkfun(set_symmetricDifference)); - js_set(js, set_proto, "isSubsetOf", js_mkfun(set_isSubsetOf)); - js_set(js, set_proto, "isSupersetOf", js_mkfun(set_isSupersetOf)); - js_set(js, set_proto, "isDisjointFrom", js_mkfun(set_isDisjointFrom)); + js_set(js, set_proto, "forEach", js_mkfun_arity(set_forEach, 1)); + js_set(js, set_proto, "union", js_mkfun_arity(set_union, 1)); + js_set(js, set_proto, "intersection", js_mkfun_arity(set_intersection, 1)); + js_set(js, set_proto, "difference", js_mkfun_arity(set_difference, 1)); + js_set(js, set_proto, "symmetricDifference", js_mkfun_arity(set_symmetricDifference, 1)); + js_set(js, set_proto, "isSubsetOf", js_mkfun_arity(set_isSubsetOf, 1)); + js_set(js, set_proto, "isSupersetOf", js_mkfun_arity(set_isSupersetOf, 1)); + js_set(js, set_proto, "isDisjointFrom", js_mkfun_arity(set_isDisjointFrom, 1)); js_set_sym(js, set_proto, iter_sym, js_get(js, set_proto, "values")); js_set_sym(js, set_proto, tag_sym, js_mkstr(js, "Set", 3)); @@ -1706,11 +1706,11 @@ void init_collections_module(void) { ant_value_t weakmap_proto = js_mkobj(js); js_set_proto_init(weakmap_proto, object_proto); - js_set(js, weakmap_proto, "set", js_mkfun(weakmap_set)); - js_set(js, weakmap_proto, "get", js_mkfun(weakmap_get)); - js_set(js, weakmap_proto, "has", js_mkfun(weakmap_has)); - js_set(js, weakmap_proto, "upsert", js_mkfun(weakmap_upsert)); - js_set(js, weakmap_proto, "delete", js_mkfun(weakmap_delete)); + js_set(js, weakmap_proto, "set", js_mkfun_arity(weakmap_set, 2)); + js_set(js, weakmap_proto, "get", js_mkfun_arity(weakmap_get, 1)); + js_set(js, weakmap_proto, "has", js_mkfun_arity(weakmap_has, 1)); + js_set(js, weakmap_proto, "upsert", js_mkfun_arity(weakmap_upsert, 3)); + js_set(js, weakmap_proto, "delete", js_mkfun_arity(weakmap_delete, 1)); js_set_sym(js, weakmap_proto, tag_sym, js_mkstr(js, "WeakMap", 7)); ant_value_t weakmap_ctor = js_mkobj(js); @@ -1722,9 +1722,9 @@ void init_collections_module(void) { ant_value_t weakset_proto = js_mkobj(js); js_set_proto_init(weakset_proto, object_proto); - js_set(js, weakset_proto, "add", js_mkfun(weakset_add)); - js_set(js, weakset_proto, "has", js_mkfun(weakset_has)); - js_set(js, weakset_proto, "delete", js_mkfun(weakset_delete)); + js_set(js, weakset_proto, "add", js_mkfun_arity(weakset_add, 1)); + js_set(js, weakset_proto, "has", js_mkfun_arity(weakset_has, 1)); + js_set(js, weakset_proto, "delete", js_mkfun_arity(weakset_delete, 1)); js_set_sym(js, weakset_proto, tag_sym, js_mkstr(js, "WeakSet", 7)); ant_value_t weakset_ctor = js_mkobj(js); diff --git a/src/modules/reflect.c b/src/modules/reflect.c index dc8f3a1..83418d8 100644 --- a/src/modules/reflect.c +++ b/src/modules/reflect.c @@ -46,6 +46,11 @@ static ant_value_t reflect_set(ant_t *js, ant_value_t *args, int nargs) { ant_value_t value = args[2]; int t = vtype(target); + if (t == T_CFUNC) { + target = js_cfunc_promote(js, target); + if (is_err(target)) return target; + t = T_FUNC; + } if (t != T_OBJ && t != T_FUNC) return js_false; if (vtype(key) != T_STR) return js_false; @@ -64,6 +69,11 @@ static ant_value_t reflect_has(ant_t *js, ant_value_t *args, int nargs) { ant_value_t key = args[1]; int t = vtype(target); + if (t == T_CFUNC) { + target = js_cfunc_promote(js, target); + if (is_err(target)) return target; + t = T_FUNC; + } if (t != T_OBJ && t != T_FUNC) return js_false; if (vtype(key) != T_STR) return js_false; @@ -83,6 +93,11 @@ static ant_value_t reflect_delete_property(ant_t *js, ant_value_t *args, int nar ant_value_t key = args[1]; int t = vtype(target); + if (t == T_CFUNC) { + target = js_cfunc_promote(js, target); + if (is_err(target)) return target; + t = T_FUNC; + } if (t != T_OBJ && t != T_FUNC) return js_false; if (vtype(key) != T_STR) return js_false; @@ -219,6 +234,10 @@ static ant_value_t reflect_get_prototype_of(ant_t *js, ant_value_t *args, int na ant_value_t target = args[0]; + if (vtype(target) == T_CFUNC) { + target = js_cfunc_promote(js, target); + if (is_err(target)) return target; + } if (!is_object_type(target)) { return js_mkerr(js, "Reflect.getPrototypeOf: argument must be an object"); } @@ -232,6 +251,14 @@ static ant_value_t reflect_set_prototype_of(ant_t *js, ant_value_t *args, int na ant_value_t target = args[0]; ant_value_t proto = args[1]; + if (vtype(target) == T_CFUNC) { + target = js_cfunc_promote(js, target); + if (is_err(target)) return target; + } + if (vtype(proto) == T_CFUNC) { + proto = js_cfunc_promote(js, proto); + if (is_err(proto)) return proto; + } if (!is_object_type(target)) return js_false; if (!is_object_type(proto) && vtype(proto) != T_NULL) return js_false; if (vtype(proto) != T_NULL && proto_chain_contains(js, proto, target)) return js_false; diff --git a/tests/test_collection_method_lengths.cjs b/tests/test_collection_method_lengths.cjs new file mode 100644 index 0000000..baef2f7 --- /dev/null +++ b/tests/test_collection_method_lengths.cjs @@ -0,0 +1,58 @@ +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const nativeMethodLengths = [ + [Map.prototype.set, 2, 'Map.prototype.set'], + [Map.prototype.get, 1, 'Map.prototype.get'], + [Map.prototype.has, 1, 'Map.prototype.has'], + [Map.prototype.delete, 1, 'Map.prototype.delete'], + [Map.prototype.clear, 0, 'Map.prototype.clear'], + [Map.prototype.entries, 0, 'Map.prototype.entries'], + [Map.prototype.keys, 0, 'Map.prototype.keys'], + [Map.prototype.values, 0, 'Map.prototype.values'], + [Map.prototype.forEach, 1, 'Map.prototype.forEach'], + [Set.prototype.add, 1, 'Set.prototype.add'], + [Set.prototype.has, 1, 'Set.prototype.has'], + [Set.prototype.delete, 1, 'Set.prototype.delete'], + [Set.prototype.clear, 0, 'Set.prototype.clear'], + [Set.prototype.values, 0, 'Set.prototype.values'], + [Set.prototype.entries, 0, 'Set.prototype.entries'], + [Set.prototype.forEach, 1, 'Set.prototype.forEach'], + [WeakMap.prototype.set, 2, 'WeakMap.prototype.set'], + [WeakMap.prototype.get, 1, 'WeakMap.prototype.get'], + [WeakMap.prototype.has, 1, 'WeakMap.prototype.has'], + [WeakMap.prototype.delete, 1, 'WeakMap.prototype.delete'], + [WeakSet.prototype.add, 1, 'WeakSet.prototype.add'], + [WeakSet.prototype.has, 1, 'WeakSet.prototype.has'], + [WeakSet.prototype.delete, 1, 'WeakSet.prototype.delete'], +]; + +for (const [method, expected, label] of nativeMethodLengths) { + assert(method.length === expected, `${label}.length should be ${expected}, got ${method.length}`); +} + +const uncurryThis = Function.prototype.bind.bind(Function.prototype.call); +const functionCall = uncurryThis(Function.prototype.call); +const mapProbe = new Map(); + +for (const key of Reflect.ownKeys(Map.prototype)) { + if (key === 'constructor') continue; + const desc = Reflect.getOwnPropertyDescriptor(Map.prototype, key); + if ( + typeof desc.value === 'function' && + desc.value.length === 0 && + Symbol.iterator in (functionCall(desc.value, mapProbe) ?? {}) + ) { + const factory = uncurryThis(desc.value); + const next = uncurryThis(factory(mapProbe).next); + assert(typeof next === 'function', 'uncurried safe Map iterator next should be callable'); + } +} + +const mapSet = uncurryThis(Map.prototype.set); +const map = new Map(); +mapSet(map, 'a', 1); +assert(map.get('a') === 1, 'uncurried Map.prototype.set should work'); + +console.log('collection:method-lengths:ok'); diff --git a/tests/test_native_method_descriptors.cjs b/tests/test_native_method_descriptors.cjs index 0b0d6eb..4740151 100644 --- a/tests/test_native_method_descriptors.cjs +++ b/tests/test_native_method_descriptors.cjs @@ -182,6 +182,19 @@ Object.defineProperty(fetch, nativeReproSym, { configurable: true, writable: true, }); + +const originalFetchApply = fetch.apply; +fetch.apply = undefined; +if (fetch.apply !== undefined) { + console.log('FAIL: native function own properties should shadow prototype properties'); + pass = false; +} +delete fetch.apply; +if (fetch.apply !== originalFetchApply || fetch.apply !== Function.prototype.apply) { + console.log('FAIL: deleting native function own properties should restore prototype lookup'); + pass = false; +} + Object.setPrototypeOf(fetch, customFetchProto); if (fetch.extra !== 'value-from-set' || fetch.defined !== 'value-from-defineProperty') { -- 2.51.2