diff --git a/include/ant.h b/include/ant.h index 7bd18230..e33842f6 100644 --- a/include/ant.h +++ b/include/ant.h @@ -16,6 +16,7 @@ #define STR_PROTO "__proto__" #define STR_PROTO_LEN 9 +#define CFUNC_HAS_PROTOTYPE 0x01u #define ANT_ASSERT(cond, msg) ({ \ if (!(cond)) { \ @@ -128,6 +129,16 @@ ant_offset_t js_mkprop_fast_off(ant_t *js, ant_value_t obj, const char *key, siz js_mkfun_meta(&_ant_cfunc_meta); \ }) +#define js_mkfun_flags(fn, flags) ({ \ + static const ant_cfunc_meta_t _ant_cfunc_meta = { (fn), NULL, 0, (flags) }; \ + js_mkfun_meta(&_ant_cfunc_meta); \ +}) + +#define js_mkfun_arity_flags(fn, arity, flags) ({ \ + static const ant_cfunc_meta_t _ant_cfunc_meta = { (fn), NULL, (arity), (flags) }; \ + js_mkfun_meta(&_ant_cfunc_meta); \ +}) + void js_set(ant_t *, ant_value_t, const char *, ant_value_t); void js_set_exact(ant_t *, ant_value_t, const char *, ant_value_t); void js_set_sym(ant_t *, ant_value_t obj, ant_value_t sym, ant_value_t val); diff --git a/src/ant.c b/src/ant.c index 775a40ac..6c8b3bdb 100644 --- a/src/ant.c +++ b/src/ant.c @@ -578,6 +578,8 @@ static uintptr_t lkp_with_setter(ant_t *js, ant_value_t obj, const char *buf, si static ant_value_t get_prototype_for_type(ant_t *js, uint8_t type); static ant_value_t js_cfunc_name_value(ant_t *js, ant_value_t cfunc); static ant_value_t js_cfunc_length_value(ant_value_t cfunc); +static bool js_cfunc_has_prototype(ant_value_t cfunc); +static ant_value_t setup_func_prototype_property(ant_t *js, ant_value_t func, bool mark_constructor); static ant_value_t js_expose_cfunc_for_key(ant_t *js, ant_value_t value, const char *key, size_t key_len); static inline ant_value_t lkp_val(ant_t *js, ant_value_t obj, const char *buf, size_t len); @@ -977,6 +979,16 @@ static bool js_cfunc_try_get_own(ant_t *js, ant_value_t cfunc, const char *key, return true; } + if (key_len == 9 && memcmp(key, "prototype", 9) == 0 && js_cfunc_has_prototype(cfunc)) { + ant_value_t promoted = js_cfunc_promote(js, cfunc); + if (is_err(promoted)) { + *out = promoted; + return true; + } + *out = js_get(js, promoted, "prototype"); + return true; + } + return false; } @@ -3326,31 +3338,7 @@ double js_to_number(ant_t *js, ant_value_t arg) { } static ant_value_t setup_func_prototype(ant_t *js, ant_value_t func) { - ant_value_t proto_obj = mkobj(js, 0); - if (is_err(proto_obj)) return proto_obj; - - ant_value_t object_proto = js->sym.object_proto; - if (vtype(object_proto) == T_OBJ) { - js_set_proto_init(proto_obj, object_proto); - } - - ant_value_t constructor_key = js_mkstr(js, "constructor", 11); - if (is_err(constructor_key)) return constructor_key; - - ant_value_t res = mkprop(js, proto_obj, constructor_key, func, 0); - if (is_err(res)) return res; - js_set_descriptor(js, proto_obj, "constructor", 11, JS_DESC_W | JS_DESC_C); - - ant_value_t prototype_key = js_mkstr(js, "prototype", 9); - if (is_err(prototype_key)) return prototype_key; - - res = js_setprop(js, func, prototype_key, proto_obj); - if (is_err(res)) return res; - js_set_descriptor(js, js_as_obj(func), "prototype", 9, JS_DESC_W); - - js_mark_constructor(func, true); - - return js_mkundef(); + return setup_func_prototype_property(js, func, true); } static inline bool same_object_identity(ant_value_t a, ant_value_t b) { @@ -7633,9 +7621,10 @@ static ant_value_t builtin_object_getOwnPropertyNames(ant_t *js, ant_value_t *ar ant_value_t arr = mkarr(js); ant_offset_t idx = 0; arr_set(js, arr, idx++, js->length_str); - if (vtype(js_cfunc_name_value(js, obj)) == T_STR) { - arr_set(js, arr, idx++, ANT_STRING("name")); - } + + if (vtype(js_cfunc_name_value(js, obj)) == T_STR) arr_set(js, arr, idx++, ANT_STRING("name")); + if (js_cfunc_has_prototype(obj)) arr_set(js, arr, idx++, ANT_STRING("prototype")); + return mkval(T_ARR, vdata(arr)); } @@ -14518,6 +14507,37 @@ static ant_value_t js_cfunc_length_value(ant_value_t cfunc) { return tov((double)js_cfunc_length(cfunc)); } +static bool js_cfunc_has_prototype(ant_value_t cfunc) { + const ant_cfunc_meta_t *meta = js_as_cfunc_meta(cfunc); + return meta && (meta->flags & CFUNC_HAS_PROTOTYPE) != 0; +} + +static ant_value_t setup_func_prototype_property(ant_t *js, ant_value_t func, bool mark_constructor) { + ant_value_t proto_obj = mkobj(js, 0); + if (is_err(proto_obj)) return proto_obj; + + ant_value_t object_proto = js->sym.object_proto; + if (vtype(object_proto) == T_OBJ) js_set_proto_init(proto_obj, object_proto); + + ant_value_t constructor_key = js_mkstr(js, "constructor", 11); + if (is_err(constructor_key)) return constructor_key; + + ant_value_t res = mkprop(js, proto_obj, constructor_key, func, 0); + if (is_err(res)) return res; + js_set_descriptor(js, proto_obj, "constructor", 11, JS_DESC_W | JS_DESC_C); + + ant_value_t prototype_key = js_mkstr(js, "prototype", 9); + if (is_err(prototype_key)) return prototype_key; + + res = js_setprop(js, func, prototype_key, proto_obj); + if (is_err(res)) return res; + js_set_descriptor(js, js_as_obj(func), "prototype", 9, JS_DESC_W); + + if (mark_constructor) js_mark_constructor(func, true); + + return js_mkundef(); +} + ant_value_t js_cfunc_expose_named(ant_t *js, ant_value_t cfunc, const char *name, size_t name_len) { if (vtype(cfunc) != T_CFUNC || !name || is_internal_prop(name, (ant_offset_t)name_len)) return cfunc; @@ -14612,6 +14632,11 @@ ant_value_t js_cfunc_promote(ant_t *js, ant_value_t cfunc) { ant_value_t promoted = js_obj_to_func(fn_obj); + if (js_cfunc_has_prototype(cfunc)) { + ant_value_t proto_result = setup_func_prototype_property(js, promoted, false); + if (is_err(proto_result)) return proto_result; + } + if (js->cfunc_promote_cache.len >= js->cfunc_promote_cache.cap) { uint8_t new_cap = js->cfunc_promote_cache.cap ? js->cfunc_promote_cache.cap * 2 : 4; uintptr_t *new_ptrs = realloc(js->cfunc_promote_cache.cfunc_ptr, new_cap * sizeof(uintptr_t)); diff --git a/src/modules/fetch.c b/src/modules/fetch.c index dafa778b..4e0f738f 100644 --- a/src/modules/fetch.c +++ b/src/modules/fetch.c @@ -903,7 +903,7 @@ static ant_value_t js_fetch(ant_t *js, ant_value_t *args, int nargs) { void init_fetch_module() { utarray_new(pending_requests, &ut_ptr_icd); - js_set(rt->js, rt->js->global, "fetch", js_mkfun(js_fetch)); + js_set(rt->js, rt->js->global, "fetch", js_mkfun_flags(js_fetch, CFUNC_HAS_PROTOTYPE)); } int has_pending_fetches(void) { diff --git a/src/modules/io.c b/src/modules/io.c index 28c61bb1..d0c770fa 100644 --- a/src/modules/io.c +++ b/src/modules/io.c @@ -849,7 +849,25 @@ void inspect_value(ant_t *js, ant_value_t val, FILE *stream, int depth, inspect_ } if (t == T_CFUNC) { - fprintf(stream, "", (uint64_t)vdata(val)); + ant_value_t promoted = js_cfunc_lookup_promoted(js, val); + if (vtype(promoted) == T_FUNC) { + if (depth > 10) fprintf(stream, "", (uint64_t)vdata(js_as_obj(promoted))); + else inspect_object(js, promoted, stream, depth, visited); + return; + } + + const ant_cfunc_meta_t *meta = js_as_cfunc_meta(val); + const char *name = (meta && meta->name) ? meta->name : NULL; + uint32_t length = js_cfunc_length(val); + bool has_prototype = meta && (meta->flags & CFUNC_HAS_PROTOTYPE) != 0; + + if (name) fprintf(stream, "[Function: %s]", name); + else fprintf(stream, "[Function]"); + + fprintf(stream, " "); + return; } diff --git a/src/modules/timer.c b/src/modules/timer.c index 09431faf..8740a6e8 100644 --- a/src/modules/timer.c +++ b/src/modules/timer.c @@ -779,12 +779,12 @@ int has_pending_microtasks(void) { } static void timers_define_common(ant_t *js, ant_value_t obj) { - js_set(js, obj, "setTimeout", js_mkfun(js_set_timeout)); - js_set(js, obj, "clearTimeout", js_mkfun(js_clear_timeout)); - js_set(js, obj, "setInterval", js_mkfun(js_set_interval)); - js_set(js, obj, "clearInterval", js_mkfun(js_clear_timeout)); - js_set(js, obj, "setImmediate", js_mkfun(js_set_immediate)); - js_set(js, obj, "clearImmediate", js_mkfun(js_clear_immediate)); + js_set(js, obj, "setTimeout", js_mkfun_flags(js_set_timeout, CFUNC_HAS_PROTOTYPE)); + js_set(js, obj, "clearTimeout", js_mkfun_flags(js_clear_timeout, CFUNC_HAS_PROTOTYPE)); + js_set(js, obj, "setInterval", js_mkfun_flags(js_set_interval, CFUNC_HAS_PROTOTYPE)); + js_set(js, obj, "clearInterval", js_mkfun_flags(js_clear_timeout, CFUNC_HAS_PROTOTYPE)); + js_set(js, obj, "setImmediate", js_mkfun_flags(js_set_immediate, CFUNC_HAS_PROTOTYPE)); + js_set(js, obj, "clearImmediate", js_mkfun_flags(js_clear_immediate, CFUNC_HAS_PROTOTYPE)); js_set(js, obj, "queueMicrotask", js_mkfun(js_queue_microtask)); } diff --git a/tests/test_native_method_descriptors.cjs b/tests/test_native_method_descriptors.cjs index 0d5d2126..0b0d6ebf 100644 --- a/tests/test_native_method_descriptors.cjs +++ b/tests/test_native_method_descriptors.cjs @@ -202,6 +202,11 @@ if (Object.getPrototypeOf(fetch) !== customFetchProto || fetch.fromCustomProto ! const fetchOwnNames = Object.getOwnPropertyNames(fetch); const fetchOwnSymbols = Object.getOwnPropertySymbols(fetch); +if (!fetchOwnNames.includes('prototype')) { + console.log('FAIL: native functions with prototype metadata should expose prototype in own property names'); + pass = false; +} + if (!fetchOwnNames.includes('extra') || !fetchOwnNames.includes('defined')) { console.log('FAIL: native function own property names should include promoted writes'); pass = false; @@ -215,6 +220,7 @@ if (!fetchOwnSymbols.includes(nativeReproSym)) { const extraDesc = Object.getOwnPropertyDescriptor(fetch, 'extra'); const definedDesc = Object.getOwnPropertyDescriptor(fetch, 'defined'); const symDesc = Object.getOwnPropertyDescriptor(fetch, nativeReproSym); +const prototypeDesc = Object.getOwnPropertyDescriptor(fetch, 'prototype'); if (!extraDesc || extraDesc.value !== 'value-from-set' || !extraDesc.enumerable || !extraDesc.configurable || !extraDesc.writable) { console.log('FAIL: promoted native string properties should keep their descriptors'); @@ -231,6 +237,11 @@ if (!symDesc || symDesc.value !== 'symbol-value' || !symDesc.enumerable || !symD pass = false; } +if (!prototypeDesc || prototypeDesc.enumerable || prototypeDesc.configurable || !prototypeDesc.writable) { + console.log('FAIL: native function prototype descriptors should match built-in function defaults'); + pass = false; +} + delete fetch.extra; delete fetch.defined; delete fetch[nativeReproSym];