MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve runtime compatibility for modern SSR/RSC workloads and builtin modules

fix JSON/promise/abort/collection edge cases while simplifying server, import-meta, path, and builtin lookup internals

+3707 -671
+4 -4
examples/results.txt
··· 460 460 compat-table/es6/Function.name.variable.js: failed 461 461 compat-table/es6/Map.Symbol.species.js: OK 462 462 compat-table/es6/Map.constructor-accepts-null.js: OK 463 - compat-table/es6/Map.constructor-arguments.js: failed 463 + compat-table/es6/Map.constructor-arguments.js: OK 464 464 compat-table/es6/Map.constructor-invokes-set.js: failed 465 465 compat-table/es6/Map.constructor-requires-new.js: OK 466 466 compat-table/es6/Map.iterator-closing.js: failed ··· 527 527 compat-table/es6/Proxy.handler.deleteProperty.invariants.js: OK 528 528 compat-table/es6/Proxy.handler.deleteProperty.js: OK 529 529 compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.invariants.js: failed 530 - compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.js: TypeError: Cannot read properties of undefined (reading 'value') 530 + compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.js: failed 531 531 compat-table/es6/Proxy.handler.getPrototypeOf.invariants.js: failed 532 532 compat-table/es6/Proxy.handler.getPrototypeOf.js: failed 533 533 compat-table/es6/Proxy.handler.get.instances.js: failed ··· 887 887 compat-table/es6/misc.Proxy.deleteProperty.Array.unshift.js: failed 888 888 compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Function.bind.js: TypeError: bind requires a function 889 889 compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Object.assign.js: failed 890 - compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Object.hasOwnProperty.js: failed 890 + compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Object.hasOwnProperty.js: OK 891 891 compat-table/es6/misc.Proxy.getOwnPropertyDescriptor.Set.js: failed 892 892 compat-table/es6/misc.Proxy.get.Array.concat.js: OK 893 893 compat-table/es6/misc.Proxy.get.Array.from.js: failed ··· 1072 1072 compat-table/es6/typed-arrays.prototype.findIndex.js: failed 1073 1073 compat-table/es6/typed-arrays.prototype.find.js: failed 1074 1074 compat-table/es6/typed-arrays.prototype.forEach.js: failed 1075 - compat-table/es6/typed-arrays.prototype.indexOf.js: failed 1075 + compat-table/es6/typed-arrays.prototype.indexOf.js: OK 1076 1076 compat-table/es6/typed-arrays.prototype.join.js: OK 1077 1077 compat-table/es6/typed-arrays.prototype.keys.js: OK 1078 1078 compat-table/es6/typed-arrays.prototype.lastIndexOf.js: failed
+3 -1
include/ant.h
··· 125 125 126 126 uint8_t vtype(ant_value_t val); 127 127 size_t vdata(ant_value_t val); 128 - ant_object_t *js_obj_ptr(ant_value_t val); 129 128 bool js_is_constructor(ant_t *js, ant_value_t value); 129 + 130 + ant_object_t *js_obj_ptr(ant_value_t val); 131 + ant_value_t js_obj_from_ptr(ant_object_t *obj); 130 132 131 133 double js_getnum(ant_value_t val); 132 134 char *js_getstr(ant_t *js, ant_value_t val, size_t *len);
+1
include/common.h
··· 75 75 SLOT_HEADERS_GUARD, 76 76 SLOT_REQUEST_HEADERS, 77 77 SLOT_REQUEST_SIGNAL, 78 + SLOT_REQUEST_ABORT_REASON, 78 79 SLOT_REQUEST_BODY_STREAM, 79 80 SLOT_RESPONSE_HEADERS, 80 81 SLOT_RESPONSE_BODY_STREAM,
+12 -5
include/esm/builtin_bundle.h
··· 8 8 #include <stdint.h> 9 9 10 10 typedef struct { 11 - const char *specifier; 12 - const char *source_name; 13 11 const uint8_t *code; 14 12 size_t code_len; 15 13 ant_module_format_t format; 16 - } ant_builtin_bundle_entry_t; 14 + } ant_builtin_bundle_module_t; 15 + 16 + typedef struct { 17 + const char *specifier; 18 + size_t specifier_len; 19 + const char *source_name; 20 + size_t module_id; 21 + } ant_builtin_bundle_alias_t; 17 22 18 - bool esm_is_builtin_specifier(const char *specifier); 19 - const ant_builtin_bundle_entry_t *esm_lookup_builtin_bundle(const char *specifier, size_t spec_len); 23 + bool esm_has_builtin_scheme(const char *specifier); 24 + 25 + const ant_builtin_bundle_alias_t *esm_lookup_builtin_alias(const char *specifier, size_t spec_len); 26 + const ant_builtin_bundle_module_t *esm_lookup_builtin_module(size_t module_id); 20 27 21 28 #endif
+1 -1
include/esm/loader.h
··· 36 36 ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spec_len); 37 37 ant_value_t js_esm_resolve_specifier(ant_t *js, ant_value_t specifier, const char *base_path); 38 38 ant_value_t js_esm_import_sync_cstr_from(ant_t *js, const char *specifier, size_t spec_len, const char *base_path); 39 - ant_value_t js_esm_import_dynamic(ant_t *js, ant_value_t specifier, ant_value_t *out_tla_promise); 39 + ant_value_t js_esm_import_dynamic(ant_t *js, ant_value_t specifier, const char *base_path, ant_value_t *out_tla_promise); 40 40 41 41 #endif
+1
include/gc/modules.h
··· 29 29 void gc_mark_codec_streams(ant_t *js, gc_mark_fn mark); 30 30 void gc_mark_compression_streams(ant_t *js, gc_mark_fn mark); 31 31 void gc_mark_zlib(ant_t *js, gc_mark_fn mark); 32 + void gc_mark_abort_signal_object(ant_t *js, ant_value_t signal, gc_mark_fn mark); 32 33 33 34 #endif
+1 -1
include/gc/roots.h
··· 5 5 #include <assert.h> 6 6 #include <stdbool.h> 7 7 8 - #define GC_MAX_STATIC_ROOTS 64 8 + #define GC_MAX_STATIC_ROOTS 128 9 9 10 10 typedef void (*gc_root_visitor_t)(ant_t *js, ant_value_t v); 11 11
+4 -2
include/modules/collections.h
··· 6 6 #include "modules/symbol.h" 7 7 8 8 typedef struct map_entry { 9 - char *key; 9 + unsigned char *key; 10 + size_t key_len; 10 11 ant_value_t key_val; 11 12 ant_value_t value; 12 13 UT_hash_handle hh; ··· 14 15 15 16 typedef struct set_entry { 16 17 ant_value_t value; 17 - char *key; 18 + unsigned char *key; 19 + size_t key_len; 18 20 UT_hash_handle hh; 19 21 } set_entry_t; 20 22
+2
include/modules/timer.h
··· 3 3 4 4 #include "types.h" 5 5 6 + ant_value_t timers_promises_library(ant_t *js); 7 + 6 8 void init_timer_module(void); 7 9 void process_microtasks(ant_t *js); 8 10 void process_immediates(ant_t *js);
+8
include/modules/tls.h
··· 1 + #ifndef ANT_INTERNAL_TLS_MODULE_H 2 + #define ANT_INTERNAL_TLS_MODULE_H 3 + 4 + #include "types.h" 5 + 6 + ant_value_t internal_tls_library(ant_t *js); 7 + 8 + #endif
+8
include/modules/uri.h
··· 1 1 #ifndef URI_H 2 2 #define URI_H 3 3 4 + #include "types.h" 5 + 4 6 void init_uri_module(void); 7 + 8 + ant_value_t js_encodeURI(ant_t *js, ant_value_t *args, int nargs); 9 + ant_value_t js_decodeURI(ant_t *js, ant_value_t *args, int nargs); 10 + 11 + ant_value_t js_encodeURIComponent(ant_t *js, ant_value_t *args, int nargs); 12 + ant_value_t js_decodeURIComponent(ant_t *js, ant_value_t *args, int nargs); 5 13 6 14 #endif
+30 -1
include/net/connection.h
··· 9 9 typedef struct ant_listener_s ant_listener_t; 10 10 typedef struct ant_conn_s ant_conn_t; 11 11 12 + typedef enum { 13 + ANT_CONN_KIND_TCP = 0, 14 + ANT_CONN_KIND_PIPE, 15 + } ant_conn_kind_t; 16 + 12 17 struct ant_conn_s { 13 - uv_tcp_t handle; 18 + ant_conn_kind_t kind; 19 + 20 + union { 21 + uv_tcp_t tcp; 22 + uv_pipe_t pipe; 23 + } handle; 24 + 14 25 uv_timer_t timer; 15 26 ant_listener_t *listener; 27 + 16 28 void *user_data; 17 29 char *buffer; 30 + 18 31 size_t buffer_len; 19 32 size_t buffer_cap; 33 + size_t buffer_offset; 34 + 20 35 uint64_t timeout_ms; 21 36 uint64_t bytes_read; 22 37 uint64_t bytes_written; 38 + 23 39 int close_handles; 24 40 bool closing; 25 41 bool read_paused; 26 42 bool read_eof; 27 43 bool has_local_addr; 28 44 bool has_remote_addr; 45 + 29 46 char local_addr[64]; 30 47 char remote_addr[64]; 31 48 char local_family[8]; 32 49 char remote_family[8]; 50 + 33 51 int local_port; 34 52 int remote_port; 35 53 struct ant_conn_s *next; 36 54 }; 37 55 56 + static inline uv_stream_t *ant_conn_stream(ant_conn_t *conn) { 57 + if (!conn) return NULL; 58 + switch (conn->kind) { 59 + case ANT_CONN_KIND_PIPE: return (uv_stream_t *)&conn->handle.pipe; 60 + case ANT_CONN_KIND_TCP: 61 + default: return (uv_stream_t *)&conn->handle.tcp; 62 + } 63 + } 64 + 38 65 void ant_conn_start(ant_conn_t *conn); 39 66 int ant_conn_accept(ant_conn_t *conn, uv_stream_t *server_stream); 67 + 40 68 ant_conn_t *ant_conn_create_tcp(ant_listener_t *listener, uint64_t timeout_ms); 69 + ant_conn_t *ant_conn_create_pipe(ant_listener_t *listener, uint64_t timeout_ms); 41 70 42 71 #endif
+38 -1
include/net/listener.h
··· 9 9 typedef struct ant_listener_s ant_listener_t; 10 10 typedef struct ant_conn_s ant_conn_t; 11 11 12 + typedef enum { 13 + ANT_LISTENER_KIND_TCP = 0, 14 + ANT_LISTENER_KIND_PIPE, 15 + } ant_listener_kind_t; 16 + 12 17 typedef void (*ant_conn_write_cb)( 13 18 ant_conn_t *conn, 14 19 int status, ··· 27 32 28 33 struct ant_listener_s { 29 34 uv_loop_t *loop; 30 - uv_tcp_t handle; 35 + ant_listener_kind_t kind; 36 + 37 + union { 38 + uv_tcp_t tcp; 39 + uv_pipe_t pipe; 40 + } handle; 41 + 31 42 ant_conn_t *connections; 32 43 ant_listener_callbacks_t callbacks; 44 + 33 45 void *user_data; 34 46 uint64_t idle_timeout_ms; 47 + 35 48 int port; 36 49 int backlog; 50 + 51 + char *path; 37 52 bool started; 38 53 bool closing; 39 54 bool closed; 40 55 }; 41 56 57 + static inline uv_stream_t *ant_listener_stream(ant_listener_t *listener) { 58 + if (!listener) return NULL; 59 + switch (listener->kind) { 60 + case ANT_LISTENER_KIND_PIPE: return (uv_stream_t *)&listener->handle.pipe; 61 + case ANT_LISTENER_KIND_TCP: 62 + default: return (uv_stream_t *)&listener->handle.tcp; 63 + } 64 + } 65 + 42 66 int ant_listener_listen_tcp( 43 67 ant_listener_t *listener, 44 68 uv_loop_t *loop, ··· 50 74 void *user_data 51 75 ); 52 76 77 + int ant_listener_listen_pipe( 78 + ant_listener_t *listener, 79 + uv_loop_t *loop, 80 + const char *path, 81 + int backlog, 82 + uint64_t idle_timeout_ms, 83 + const ant_listener_callbacks_t *callbacks, 84 + void *user_data 85 + ); 86 + 53 87 void ant_listener_stop(ant_listener_t *listener, bool force); 54 88 void *ant_listener_get_user_data(const ant_listener_t *listener); 89 + void ant_listener_ref(ant_listener_t *listener); 90 + void ant_listener_unref(ant_listener_t *listener); 55 91 56 92 int ant_conn_local_port(const ant_conn_t *conn); 57 93 int ant_listener_port(const ant_listener_t *listener); ··· 82 118 const char *ant_conn_remote_addr(const ant_conn_t *conn); 83 119 const char *ant_conn_local_family(const ant_conn_t *conn); 84 120 const char *ant_conn_remote_family(const ant_conn_t *conn); 121 + const char *ant_listener_path(const ant_listener_t *listener); 85 122 86 123 size_t ant_conn_buffer_len(const ant_conn_t *conn); 87 124 ant_listener_t *ant_conn_listener(const ant_conn_t *conn);
+2 -2
meson/deps/meson.build
··· 211 211 libffi_dep, uuid_dep, crprintf_dep, 212 212 llhttp, pcre2_dep, libuv_dep, base64_dep, 213 213 argtable3_dep, tlsuv_dep, libsodium_dep, 214 - yyjson_dep, minicoro_dep, uuidv7_dep, 214 + yyjson_dep, minicoro_dep, uuidv7_dep, 215 215 uriparser_dep, utf8proc_dep, openssl_dep, 216 216 zlib_dep, brotli_common_dep, brotli_dec_dep, 217 - brotli_enc_dep, uthash_dep, lmdb_dep, 217 + brotli_enc_dep, uthash_dep, lmdb_dep, nghttp2_dep, 218 218 ] + mbedtls_dep + win_deps 219 219 220 220 if get_option('jit')
+260 -130
src/ant.c
··· 253 253 return (ant_object_t *)(uintptr_t)vdata(as_obj); 254 254 } 255 255 256 + ant_value_t js_obj_from_ptr(ant_object_t *obj) { 257 + if (!obj) return js_mkundef(); 258 + return mkval(T_OBJ, (uintptr_t)obj); 259 + } 260 + 256 261 void js_mark_constructor(ant_value_t value, bool is_constructor) { 257 262 ant_object_t *obj = js_obj_ptr(value); 258 263 if (obj) obj->is_constructor = is_constructor ? 1u : 0u; ··· 613 618 static inline ant_value_t lkp_sym_proto_val(ant_t *js, ant_value_t obj, ant_offset_t sym_off); 614 619 static size_t tostr(ant_t *js, ant_value_t value, char *buf, size_t len); 615 620 static size_t strpromise(ant_t *js, ant_value_t value, char *buf, size_t len); 621 + 616 622 static ant_value_t js_call_valueOf(ant_t *js, ant_value_t value); 617 623 static ant_value_t js_call_toString(ant_t *js, ant_value_t value); 618 624 static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, size_t method_len, ant_value_t *args, int nargs); ··· 620 626 static inline bool is_slot_prop(ant_offset_t header); 621 627 static inline ant_offset_t next_prop(ant_offset_t header); 622 628 623 - static ant_value_t builtin_Object(ant_t *js, ant_value_t *args, int nargs); 624 629 static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs); 625 - static ant_value_t string_split_impl(ant_t *js, ant_value_t str, ant_value_t *args, int nargs); 626 - 627 630 static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); 628 631 static ant_value_t proxy_get_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); 629 632 static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value); 630 633 static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); 631 634 static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); 635 + static ant_value_t proxy_get_own_property_descriptor(ant_t *js, ant_value_t proxy, ant_value_t key_val); 636 + static ant_value_t proxy_has_own(ant_t *js, ant_value_t proxy, ant_value_t key_val); 632 637 static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); 633 638 static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); 634 639 635 - static ant_value_t get_prototype_for_type(ant_t *js, uint8_t type); 636 640 static ant_value_t get_ctor_proto(ant_t *js, const char *name, size_t len); 637 - ant_offset_t lkp_interned(ant_t *js, ant_value_t obj, const char *search_intern, size_t len); 638 - static ant_offset_t get_array_length(ant_t *js, ant_value_t arr); 639 641 static inline void array_len_set(ant_t *js, ant_value_t obj, ant_offset_t new_len); 640 642 641 643 typedef struct { ant_value_t handle; bool is_new; } ctor_t; ··· 872 874 } 873 875 874 876 const char *get_str_prop(ant_t *js, ant_value_t obj, const char *key, ant_offset_t klen, ant_offset_t *out_len) { 877 + GC_ROOT_SAVE(root_mark, js); 878 + GC_ROOT_PIN(js, obj); 875 879 ant_value_t v = lkp_val(js, obj, key, klen); 876 - if (vtype(v) != T_STR) return NULL; 877 - return (const char *)(uintptr_t)(vstr(js, v, out_len)); 880 + 881 + GC_ROOT_PIN(js, v); 882 + if (vtype(v) != T_STR) { 883 + GC_ROOT_RESTORE(js, root_mark); 884 + return NULL; 885 + } 886 + 887 + const char *str = (const char *)(uintptr_t)(vstr(js, v, out_len)); 888 + GC_ROOT_RESTORE(js, root_mark); 889 + 890 + return str; 878 891 } 879 892 880 893 static bool is_small_array(ant_t *js, ant_value_t obj, int *elem_count) { ··· 1717 1730 ant_value_t rope_flatten(ant_t *js, ant_value_t rope) { 1718 1731 assert(vtype(rope) == T_STR); 1719 1732 if (!str_is_heap_rope(rope)) return rope; 1733 + 1734 + GC_ROOT_SAVE(root_mark, js); 1735 + GC_ROOT_PIN(js, rope); 1720 1736 1721 1737 ant_value_t cached = rope_cached_flat(rope); 1722 - if (vtype(cached) == T_STR && !str_is_heap_rope(cached)) return cached; 1738 + GC_ROOT_PIN(js, cached); 1739 + if (vtype(cached) == T_STR && !str_is_heap_rope(cached)) { 1740 + GC_ROOT_RESTORE(js, root_mark); 1741 + return cached; 1742 + } 1723 1743 1724 1744 ant_offset_t total_len = rope_len(rope); 1745 + char *buf = (char *)ant_calloc(total_len + 1); 1725 1746 1726 - char *buf = (char *)ant_calloc(total_len + 1); 1727 - if (!buf) return js_mkerr(js, "oom"); 1747 + if (!buf) { 1748 + GC_ROOT_RESTORE(js, root_mark); 1749 + return js_mkerr(js, "oom"); 1750 + } 1728 1751 1729 1752 ant_offset_t pos = 0; 1730 1753 rope_flatten_into(js, rope, buf, &pos); 1731 1754 buf[pos] = '\0'; 1732 1755 1733 1756 ant_value_t flat = js_mkstr(js, buf, pos); 1757 + GC_ROOT_PIN(js, flat); 1734 1758 free(buf); 1735 1759 1736 1760 if (!is_err(flat)) { 1737 1761 rope_set_cached_flat(rope, flat); 1738 1762 } 1739 1763 1764 + GC_ROOT_RESTORE(js, root_mark); 1740 1765 return flat; 1741 1766 } 1742 1767 ··· 4074 4099 4075 4100 typedef enum { ITER_CONTINUE, ITER_BREAK, ITER_ERROR } iter_action_t; 4076 4101 typedef iter_action_t (*iter_callback_t)(ant_t *js, ant_value_t value, void *ctx, ant_value_t *out); 4077 - static ant_value_t iter_foreach(ant_t *js, ant_value_t iterable, iter_callback_t cb, void *ctx); 4078 - 4079 4102 4080 4103 static bool js_try_call_method(ant_t *js, ant_value_t obj, const char *method, size_t method_len, ant_value_t *args, int nargs, ant_value_t *out_result) { 4081 4104 ant_value_t getter = js_mkundef(); bool has_getter = false; ··· 4906 4929 4907 4930 bound_closure->func = orig->func; 4908 4931 bound_closure->call_flags = orig->call_flags; 4909 - bound_closure->upvalues = orig->upvalues; 4910 - if (orig->upvalues) bound_closure->call_flags |= SV_CALL_BORROWED_UPVALS; 4932 + bound_closure->upvalues = NULL; 4911 4933 bound_closure->bound_this = this_arg; 4912 4934 bound_closure->bound_args = js_mkundef(); 4913 4935 bound_closure->super_val = orig->super_val; 4914 4936 bound_closure->func_obj = bound_func; 4937 + 4938 + if (orig->func && orig->func->upvalue_count > 0 && orig->upvalues) { 4939 + size_t upvalue_bytes = sizeof(sv_upvalue_t *) * (size_t)orig->func->upvalue_count; 4940 + bound_closure->upvalues = malloc(upvalue_bytes); 4941 + if (!bound_closure->upvalues) return js_mkerr(js, "oom"); 4942 + memcpy(bound_closure->upvalues, orig->upvalues, upvalue_bytes); 4943 + } 4915 4944 4916 4945 if (bound_argc > 0) 4917 4946 bound_closure->call_flags |= SV_CALL_HAS_BOUND_ARGS; ··· 5324 5353 } 5325 5354 5326 5355 static ant_value_t for_in_keys_add(ant_t *js, ant_value_t out, ant_value_t seen, ant_value_t key) { 5356 + GC_ROOT_SAVE(root_mark, js); 5357 + GC_ROOT_PIN(js, out); 5358 + GC_ROOT_PIN(js, seen); 5359 + GC_ROOT_PIN(js, key); 5360 + 5327 5361 if (vtype(key) != T_STR) return js_mkundef(); 5328 5362 5329 5363 ant_offset_t key_len = 0; 5330 5364 ant_offset_t key_off = vstr(js, key, &key_len); 5331 5365 const char *key_ptr = (const char *)(uintptr_t)(key_off); 5332 5366 5333 - if (is_internal_prop(key_ptr, key_len)) 5334 - return js_mkundef(); 5335 - if (lkp(js, seen, key_ptr, key_len) != 0) 5336 - return js_mkundef(); 5367 + if (is_internal_prop(key_ptr, key_len)) goto done; 5368 + if (lkp(js, seen, key_ptr, key_len) != 0) goto done; 5337 5369 5338 5370 ant_value_t mark = setprop_cstr(js, seen, key_ptr, key_len, js_true); 5339 - if (is_err(mark)) return mark; 5371 + if (is_err(mark)) { 5372 + GC_ROOT_RESTORE(js, root_mark); 5373 + return mark; 5374 + } 5340 5375 5341 5376 js_arr_push(js, out, key); 5377 + done: 5378 + GC_ROOT_RESTORE(js, root_mark); 5342 5379 return js_mkundef(); 5343 5380 } 5344 5381 5345 5382 static ant_value_t for_in_keys_add_string_indices(ant_t *js, ant_value_t out, ant_value_t seen, ant_value_t str) { 5383 + GC_ROOT_SAVE(root_mark, js); 5384 + GC_ROOT_PIN(js, out); 5385 + GC_ROOT_PIN(js, seen); 5386 + GC_ROOT_PIN(js, str); 5387 + 5346 5388 ant_offset_t slen = vstrlen(js, str); 5347 5389 for (ant_offset_t i = 0; i < slen; i++) { 5348 5390 char idx[16]; 5349 5391 size_t idx_len = uint_to_str(idx, sizeof(idx), (uint64_t)i); 5392 + 5350 5393 ant_value_t key = js_mkstr(js, idx, idx_len); 5394 + GC_ROOT_PIN(js, key); 5395 + 5351 5396 ant_value_t r = for_in_keys_add(js, out, seen, key); 5352 - if (is_err(r)) return r; 5397 + if (is_err(r)) { GC_ROOT_RESTORE(js, root_mark); return r; } 5353 5398 } 5399 + 5400 + GC_ROOT_RESTORE(js, root_mark); 5354 5401 return js_mkundef(); 5355 5402 } 5356 5403 5357 5404 ant_value_t js_for_in_keys(ant_t *js, ant_value_t obj) { 5405 + GC_ROOT_SAVE(root_mark, js); 5358 5406 uint8_t t = vtype(obj); 5359 5407 ant_value_t out = mkarr(js); 5360 - if (t == T_NULL || t == T_UNDEF) return out; 5408 + ant_value_t result = out; 5409 + 5410 + GC_ROOT_PIN(js, obj); 5411 + GC_ROOT_PIN(js, out); 5412 + 5413 + if (t == T_NULL || t == T_UNDEF) goto done; 5361 5414 5362 5415 ant_value_t seen = mkobj(js, 0); 5363 - ant_value_t result = js_mkundef(); 5416 + GC_ROOT_PIN(js, seen); 5364 5417 5365 5418 if (t == T_STR) { 5366 5419 result = for_in_keys_add_string_indices(js, out, seen, obj); 5367 - if (is_err(result)) return result; 5368 - return out; 5420 + if (is_err(result)) goto done; 5421 + result = out; 5422 + goto done; 5369 5423 } 5370 5424 5371 5425 if (t == T_OBJ) { 5372 5426 ant_value_t prim = get_slot(obj, SLOT_PRIMITIVE); 5427 + GC_ROOT_PIN(js, prim); 5373 5428 if (vtype(prim) == T_STR) { 5374 5429 result = for_in_keys_add_string_indices(js, out, seen, prim); 5375 - if (is_err(result)) return result; 5430 + if (is_err(result)) goto done; 5431 + result = out; 5376 5432 } 5377 5433 } 5378 5434 5379 - if (t == T_OBJ || t == T_ARR || t == T_FUNC) { 5380 - ant_value_t own_keys = object_enum(js, obj, OBJ_ENUM_KEYS); 5381 - if (is_err(own_keys)) return own_keys; 5382 - if (vtype(own_keys) == T_ARR) { 5383 - ant_offset_t own_len = js_arr_len(js, own_keys); 5384 - for (ant_offset_t i = 0; i < own_len; i++) { 5385 - ant_value_t key = js_arr_get(js, own_keys, i); 5386 - result = for_in_keys_add(js, out, seen, key); 5387 - if (is_err(result)) return result; 5388 - } 5389 - } 5435 + if (t != T_OBJ && t != T_ARR && t != T_FUNC) goto done; 5436 + ant_value_t own_keys = object_enum(js, obj, OBJ_ENUM_KEYS); 5437 + 5438 + GC_ROOT_PIN(js, own_keys); 5439 + if (is_err(own_keys)) { 5440 + result = own_keys; 5441 + goto done; 5442 + } 5443 + 5444 + if (vtype(own_keys) != T_ARR) goto done; 5445 + ant_offset_t own_len = js_arr_len(js, own_keys); 5446 + 5447 + for (ant_offset_t i = 0; i < own_len; i++) { 5448 + ant_value_t key = js_arr_get(js, own_keys, i); 5449 + GC_ROOT_PIN(js, key); 5450 + result = for_in_keys_add(js, out, seen, key); 5451 + if (is_err(result)) goto done; 5452 + result = out; 5390 5453 } 5391 5454 5392 - return out; 5455 + done: 5456 + GC_ROOT_RESTORE(js, root_mark); 5457 + return result; 5393 5458 } 5394 5459 5395 5460 static ant_value_t builtin_object_values(ant_t *js, ant_value_t *args, int nargs) { ··· 5501 5566 if (nargs >= 2 && vtype(args[1]) == T_OBJ) { 5502 5567 ant_value_t props = args[1]; 5503 5568 ant_iter_t iter = js_prop_iter_begin(js, props); 5569 + 5504 5570 const char *key = NULL; 5505 5571 size_t klen = 0; 5506 5572 ant_value_t descriptor = js_mkundef(); 5573 + 5507 5574 while (js_prop_iter_next(&iter, &key, &klen, &descriptor)) { 5508 - if (vtype(descriptor) == T_OBJ) { 5509 - ant_offset_t val_off = lkp(js, descriptor, "value", 5); 5510 - if (val_off != 0) { 5511 - ant_value_t val = propref_load(js, val_off); 5512 - ant_value_t key_str = js_mkstr(js, key, klen); 5513 - js_setprop(js, obj, key_str, val); 5514 - } 5515 - } 5575 + if (vtype(descriptor) != T_OBJ) continue; 5576 + ant_offset_t val_off = lkp(js, descriptor, "value", 5); 5577 + if (val_off == 0) continue; 5578 + ant_value_t val = propref_load(js, val_off); 5579 + ant_value_t key_str = js_mkstr(js, key, klen); 5580 + js_setprop(js, obj, key_str, val); 5516 5581 } 5582 + 5517 5583 js_prop_iter_end(&iter); 5518 5584 } 5519 5585 ··· 5537 5603 } 5538 5604 5539 5605 if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkval(T_BOOL, 0); 5606 + ant_value_t as_obj = js_as_obj(obj); 5607 + if (is_proxy(as_obj)) return proxy_has_own(js, as_obj, key); 5540 5608 5541 - ant_value_t as_obj = js_as_obj(obj); 5542 5609 ant_offset_t key_len, key_off = vstr(js, key, &key_len); 5543 5610 const char *key_str = (char *)(uintptr_t)(key_off); 5544 5611 ··· 6124 6191 } 6125 6192 6126 6193 ant_value_t as_obj = js_as_obj(obj); 6194 + if (is_proxy(as_obj)) { 6195 + return proxy_get_own_property_descriptor(js, as_obj, key); 6196 + } 6127 6197 6128 6198 ant_offset_t sym_off = is_sym ? (ant_offset_t)vdata(key) : 0; 6129 - prop_meta_t sym_meta; 6130 - prop_meta_t str_meta; 6199 + prop_meta_t sym_meta; prop_meta_t str_meta; 6200 + 6131 6201 bool has_sym_meta = is_sym ? lookup_symbol_prop_meta(as_obj, sym_off, &sym_meta) : false; 6132 6202 bool has_str_meta = is_sym ? false : lookup_string_prop_meta(js, as_obj, key_str, (size_t)key_len, &str_meta); 6133 6203 ··· 6135 6205 if (prop_off == 0 && !(is_sym ? has_sym_meta : has_str_meta)) { 6136 6206 return js_mkundef(); 6137 6207 } 6138 - 6139 - ant_value_t result = js_mkobj(js); 6140 6208 6141 6209 bool has_getter = false; 6142 6210 bool has_setter = false; 6211 + 6212 + ant_value_t result = js_mkobj(js); 6143 6213 ant_value_t getter = js_mkundef(); 6144 6214 ant_value_t setter = js_mkundef(); 6215 + 6145 6216 bool writable = true; 6146 6217 bool enumerable = true; 6147 6218 bool configurable = true; ··· 6165 6236 } 6166 6237 6167 6238 if (has_getter || has_setter) { 6168 - if (has_getter) { 6169 - js_setprop(js, result, js_mkstr(js, "get", 3), getter); 6170 - } 6171 - if (has_setter) { 6172 - js_setprop(js, result, js_mkstr(js, "set", 3), setter); 6173 - } 6239 + if (has_getter) js_setprop(js, result, js_mkstr(js, "get", 3), getter); 6240 + if (has_setter) js_setprop(js, result, js_mkstr(js, "set", 3), setter); 6174 6241 js_setprop(js, result, js_mkstr(js, "enumerable", 10), js_bool(enumerable)); 6175 6242 js_setprop(js, result, js_mkstr(js, "configurable", 12), js_bool(configurable)); 6176 6243 } else { ··· 6313 6380 6314 6381 if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkval(T_BOOL, 0); 6315 6382 ant_value_t as_obj = js_as_obj(obj); 6383 + 6384 + if (is_proxy(as_obj)) return proxy_has_own(js, as_obj, key); 6316 6385 bool is_arr_obj = array_obj_ptr(as_obj) != NULL; 6317 6386 6318 6387 if (vtype(key) == T_SYMBOL) { ··· 6459 6528 } 6460 6529 6461 6530 if (!tag) { 6462 - if (is_object_type(obj) && get_slot(obj, SLOT_ERROR_BRAND) == js_true) { 6463 - tag = "Error"; tag_len = 5; 6464 - } else switch (t) { 6465 - case T_UNDEF: tag = "Undefined"; tag_len = 9; break; 6466 - case T_NULL: tag = "Null"; tag_len = 4; break; 6467 - case T_BOOL: tag = "Boolean"; tag_len = 7; break; 6468 - case T_NUM: tag = "Number"; tag_len = 6; break; 6469 - case T_STR: tag = "String"; tag_len = 6; break; 6470 - case T_ARR: tag = "Array"; tag_len = 5; break; 6471 - case T_FUNC: tag = "Function"; tag_len = 8; break; 6472 - case T_CFUNC: tag = "Function"; tag_len = 8; break; 6473 - case T_ERR: tag = "Error"; tag_len = 5; break; 6474 - case T_BIGINT: tag = "BigInt"; tag_len = 6; break; 6475 - case T_PROMISE: tag = "Promise"; tag_len = 7; break; 6476 - case T_OBJ: tag = "Object"; tag_len = 6; break; 6477 - default: tag = "Unknown"; tag_len = 7; break; 6478 - } 6479 - } 6531 + if (is_object_type(obj) && get_slot(obj, SLOT_ERROR_BRAND) == js_true) { 6532 + tag = "Error"; tag_len = 5; 6533 + } else switch (t) { 6534 + case T_UNDEF: tag = "Undefined"; tag_len = 9; break; 6535 + case T_NULL: tag = "Null"; tag_len = 4; break; 6536 + case T_BOOL: tag = "Boolean"; tag_len = 7; break; 6537 + case T_NUM: tag = "Number"; tag_len = 6; break; 6538 + case T_STR: tag = "String"; tag_len = 6; break; 6539 + case T_ARR: tag = "Array"; tag_len = 5; break; 6540 + case T_FUNC: tag = "Function"; tag_len = 8; break; 6541 + case T_CFUNC: tag = "Function"; tag_len = 8; break; 6542 + case T_ERR: tag = "Error"; tag_len = 5; break; 6543 + case T_BIGINT: tag = "BigInt"; tag_len = 6; break; 6544 + case T_PROMISE: tag = "Promise"; tag_len = 7; break; 6545 + case T_OBJ: tag = "Object"; tag_len = 6; break; 6546 + default: tag = "Unknown"; tag_len = 7; break; 6547 + }} 6480 6548 6481 6549 char static_buf[64]; 6482 6550 string_builder_t sb; ··· 9870 9938 return result; 9871 9939 } 9872 9940 9941 + static inline ant_value_t js_get_thenable_then(ant_t *js, ant_value_t value) { 9942 + if (!is_object_type(value)) return js_mkundef(); 9943 + return js_getprop_fallback(js, value, "then"); 9944 + } 9945 + 9873 9946 js_await_result_t js_promise_await_coroutine(ant_t *js, ant_value_t promise, coroutine_t *coro) { 9874 9947 js_await_result_t result = { 9875 9948 .state = JS_AWAIT_PENDING, ··· 9877 9950 }; 9878 9951 9879 9952 if (vtype(promise) != T_PROMISE || !coro) return result; 9880 - 9881 9953 ant_promise_state_t *pd = get_promise_data(js, promise, false); 9954 + 9882 9955 if (!pd) { 9883 9956 result.state = JS_AWAIT_FULFILLED; 9884 9957 return result; 9885 9958 } 9886 9959 9887 - if (pd->state == 1) { 9888 - result.state = JS_AWAIT_FULFILLED; 9889 - result.value = pd->value; 9890 - return result; 9891 - } 9892 - 9893 - if (pd->state == 2) { 9894 - if (pd->unhandled_reported) js_fire_rejection_handled(js, promise, pd->value); 9895 - pd->has_rejection_handler = true; 9896 - pd->unhandled_reported = false; 9897 - result.state = JS_AWAIT_REJECTED; 9898 - result.value = pd->value; 9899 - return result; 9900 - } 9960 + // if (pd->state == 1) { 9961 + // result.state = JS_AWAIT_FULFILLED; 9962 + // result.value = pd->value; 9963 + // return result; 9964 + // } 9965 + // 9966 + // if (pd->state == 2) { 9967 + // if (pd->unhandled_reported) js_fire_rejection_handled(js, promise, pd->value); 9968 + // pd->has_rejection_handler = true; 9969 + // pd->unhandled_reported = false; 9970 + // result.state = JS_AWAIT_REJECTED; 9971 + // result.value = pd->value; 9972 + // return result; 9973 + // } 9901 9974 9902 9975 promise_handler_t h = { js_mkundef(), js_mkundef(), js_mkundef(), coro }; 9903 9976 if (!promise_handler_append(pd, &h)) { ··· 9910 9983 pd->has_rejection_handler = true; 9911 9984 pd->unhandled_reported = false; 9912 9985 9913 - gc_root_pending_promise(js_obj_ptr(js_as_obj(promise))); 9986 + if (pd->state == 0) gc_root_pending_promise(js_obj_ptr(js_as_obj(promise))); 9987 + else queue_promise_trigger(js, promise); 9988 + 9914 9989 return result; 9915 9990 } 9916 9991 ··· 9924 9999 9925 10000 uint32_t len = promise_handler_count(pd); 9926 10001 if (len == 0) return; 10002 + 9927 10003 gc_root_pending_promise(pobj); 9928 10004 pd->processing = true; 9929 10005 9930 10006 for (uint32_t i = 0; i < len; i++) { 9931 10007 promise_handler_t *h = promise_handler_at(pd, i); 9932 10008 if (!h) continue; 10009 + 9933 10010 if (h->await_coro) { 9934 10011 settle_and_resume_coroutine(js, h->await_coro, val, state != 1); 9935 10012 continue; 9936 10013 } 9937 - 9938 - ant_value_t handler = (state == 1) ? h->onFulfilled : h->onRejected; 9939 10014 9940 - if (vtype(handler) == T_FUNC || vtype(handler) == T_CFUNC) { 9941 - ant_value_t res; 9942 - if (vtype(handler) == T_CFUNC) { 9943 - ant_value_t (*fn)(ant_t *, ant_value_t *, int) = (ant_value_t(*)(ant_t *, ant_value_t *, int)) vdata(handler); 9944 - res = fn(js, &val, 1); 9945 - } else { 9946 - ant_value_t call_args[] = { val }; 9947 - res = sv_vm_call(js->vm, js, handler, js_mkundef(), call_args, 1, NULL, false); 9948 - } 9949 - 9950 - if (is_err(res)) { 9951 - ant_value_t reject_val = js->thrown_value; 9952 - if (vtype(reject_val) == T_UNDEF) reject_val = res; 9953 - js->thrown_exists = false; 9954 - js->thrown_value = js_mkundef(); 9955 - js->thrown_stack = js_mkundef(); 9956 - js_reject_promise(js, h->nextPromise, reject_val); 9957 - } else js_resolve_promise(js, h->nextPromise, res); 9958 - } else { 10015 + ant_value_t handler = (state == 1) ? h->onFulfilled : h->onRejected; 10016 + if (vtype(handler) != T_FUNC && vtype(handler) != T_CFUNC) { 9959 10017 if (state == 1) js_resolve_promise(js, h->nextPromise, val); 9960 10018 else js_reject_promise(js, h->nextPromise, val); 10019 + continue; 9961 10020 } 10021 + 10022 + ant_value_t res = js_mkundef(); 10023 + if (vtype(handler) == T_CFUNC) { 10024 + ant_value_t (*fn)(ant_t *, ant_value_t *, int) = (ant_value_t(*)(ant_t *, ant_value_t *, int))vdata(handler); 10025 + res = fn(js, &val, 1); 10026 + } else { 10027 + ant_value_t call_args[] = { val }; 10028 + res = sv_vm_call(js->vm, js, handler, js_mkundef(), call_args, 1, NULL, false); 10029 + } 10030 + 10031 + if (!is_err(res)) { 10032 + js_resolve_promise(js, h->nextPromise, res); 10033 + continue; 10034 + } 10035 + 10036 + ant_value_t reject_val = js->thrown_value; 10037 + if (vtype(reject_val) == T_UNDEF) reject_val = res; 10038 + 10039 + js->thrown_exists = false; 10040 + js->thrown_value = js_mkundef(); 10041 + js->thrown_stack = js_mkundef(); 10042 + js_reject_promise(js, h->nextPromise, reject_val); 9962 10043 } 9963 10044 9964 10045 pd->processing = false; ··· 10039 10120 return; 10040 10121 } 10041 10122 10042 - ant_value_t then_prop = js_get(js, val, "then"); 10123 + ant_value_t then_prop = js_get_thenable_then(js, val); 10043 10124 GC_ROOT_PIN(js, then_prop); 10044 10125 10045 10126 if (vtype(then_prop) == T_FUNC || vtype(then_prop) == T_CFUNC) { ··· 10335 10416 return ret; 10336 10417 } 10337 10418 10338 - if (vtype(result) == T_OBJ) { 10339 - ant_value_t then_fn = js_get(js, result, "then"); 10419 + if (is_object_type(result)) { 10420 + ant_value_t then_fn = js_get_thenable_then(js, result); 10340 10421 GC_ROOT_PIN(js, then_fn); 10341 10422 10342 10423 if (vtype(then_fn) == T_FUNC || vtype(then_fn) == T_CFUNC) { ··· 10393 10474 return ret; 10394 10475 } 10395 10476 10396 - if (vtype(result) == T_OBJ) { 10397 - ant_value_t then_prop = js_get(js, result, "then"); 10477 + if (is_object_type(result)) { 10478 + ant_value_t then_prop = js_get_thenable_then(js, result); 10398 10479 GC_ROOT_PIN(js, then_prop); 10399 10480 10400 10481 if (vtype(then_prop) == T_FUNC || vtype(then_prop) == T_CFUNC) { ··· 11154 11235 return get_slot(me, SLOT_DATA); 11155 11236 } 11156 11237 11238 + static inline bool js_has_module_filename(const char *filename) { 11239 + return filename && filename[0]; 11240 + } 11241 + 11242 + static const char *js_get_current_module_filename(ant_t *js) { 11243 + const char *active = js_module_eval_active_filename(js); 11244 + if (js_has_module_filename(active)) return active; 11245 + return js->filename; 11246 + } 11247 + 11157 11248 static ant_value_t builtin_import(ant_t *js, ant_value_t *args, int nargs) { 11158 11249 if (nargs < 1) return js_mkerr(js, "import() requires a string specifier"); 11250 + const char *base_path = js_get_current_module_filename(js); 11159 11251 11160 11252 ant_value_t tla_promise = js_mkundef(); 11161 - ant_value_t ns = js_esm_import_dynamic(js, args[0], &tla_promise); 11253 + ant_value_t ns = js_esm_import_dynamic(js, args[0], base_path, &tla_promise); 11162 11254 if (is_err(ns)) return builtin_Promise_reject(js, &ns, 1); 11163 11255 11164 11256 if (vtype(tla_promise) == T_PROMISE) { ··· 11207 11299 static ant_value_t builtin_import_meta_resolve(ant_t *js, ant_value_t *args, int nargs) { 11208 11300 if (nargs < 1) return js_mkerr(js, "import.meta.resolve() requires a string specifier"); 11209 11301 11210 - ant_value_t import_meta = js_get_current_import_meta(js); 11211 - if (vtype(import_meta) == T_OBJ) { 11212 - ant_value_t filename = js_get(js, import_meta, "filename"); 11302 + const char *base_path = NULL; 11303 + ant_value_t import_meta = js_getthis(js); 11213 11304 11214 - if (vtype(filename) == T_STR) { 11215 - ant_offset_t n = 0; ant_offset_t off = vstr(js, filename, &n); 11216 - return js_esm_resolve_specifier(js, args[0], (const char *)(uintptr_t)(off)); 11217 - }} 11218 - 11219 - return js_esm_resolve_specifier(js, args[0], NULL); 11305 + if (vtype(import_meta) == T_OBJ) { 11306 + ant_value_t filename = js_get(js, import_meta, "filename"); 11307 + if (vtype(filename) == T_STR) base_path = js_getstr(js, filename, NULL); 11308 + } 11309 + 11310 + if (!js_has_module_filename(base_path)) base_path = js_get_current_module_filename(js); 11311 + return js_esm_resolve_specifier(js, args[0], base_path); 11220 11312 } 11221 11313 11222 11314 static inline void js_set_import_meta_special_dirname( ··· 11264 11356 if (is_err(import_meta)) return import_meta; 11265 11357 11266 11358 bool is_url = esm_is_url(filename); 11267 - bool is_builtin = esm_is_builtin_specifier(filename); 11359 + bool is_builtin = esm_has_builtin_scheme(filename); 11268 11360 11269 11361 ant_value_t url_val = (is_url || is_builtin) 11270 11362 ? js_mkstr(js, filename, strlen(filename)) ··· 11637 11729 return js_bool(off != 0); 11638 11730 } 11639 11731 return js_false; 11732 + } 11733 + 11734 + static ant_value_t proxy_get_own_property_descriptor(ant_t *js, ant_value_t proxy, ant_value_t key_val) { 11735 + ant_proxy_state_t *data = get_proxy_data(proxy); 11736 + if (!data) return js_mkundef(); 11737 + 11738 + if (data->revoked) 11739 + return throw_proxy_error(js, "Cannot perform 'getOwnPropertyDescriptor' on a proxy that has been revoked"); 11740 + 11741 + ant_value_t target = data->target; 11742 + ant_value_t handler = data->handler; 11743 + ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "getOwnPropertyDescriptor", 24) : 0; 11744 + 11745 + if (trap_off != 0) { 11746 + ant_value_t trap = propref_load(js, trap_off); 11747 + if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { 11748 + ant_value_t args[2] = { target, key_val }; 11749 + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 2, NULL, false); 11750 + if (is_err(result)) return result; 11751 + if (vtype(result) == T_UNDEF || vtype(result) == T_OBJ) return result; 11752 + return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined"); 11753 + } 11754 + } 11755 + 11756 + ant_value_t args[2] = { target, key_val }; 11757 + return builtin_object_getOwnPropertyDescriptor(js, args, 2); 11758 + } 11759 + 11760 + static ant_value_t proxy_has_own(ant_t *js, ant_value_t proxy, ant_value_t key_val) { 11761 + ant_value_t desc = proxy_get_own_property_descriptor(js, proxy, key_val); 11762 + if (is_err(desc)) return desc; 11763 + return js_bool(vtype(desc) != T_UNDEF); 11640 11764 } 11641 11765 11642 11766 static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) { ··· 12864 12988 print_uncaught_throw(js); 12865 12989 js_destroy(js); exit(1); 12866 12990 } 12991 + 12992 + GC_ROOT_SAVE(root_mark, js); 12993 + ant_value_t reason = pd->value; 12994 + GC_ROOT_PIN(js, p); GC_ROOT_PIN(js, reason); 12867 12995 12868 - if (!js_fire_unhandled_rejection(js, p, pd->value)) 12869 - print_unhandled_promise_rejection(js, pd->value); 12996 + if (!js_fire_unhandled_rejection(js, p, reason)) 12997 + print_unhandled_promise_rejection(js, reason); 12998 + 12999 + GC_ROOT_RESTORE(js, root_mark); 12870 13000 pd->unhandled_reported = true; 12871 13001 } 12872 13002
+49 -19
src/builtins/node/http.cjs src/builtins/node/http.mjs
··· 1 - const net = require('node:net'); 2 - const httpMetadata = require('ant:internal/http_metadata'); 3 - const httpParser = require('ant:internal/http_parser'); 4 - const httpWriter = require('ant:internal/http_writer'); 1 + import net from 'node:net'; 5 2 6 - const METHODS = httpMetadata.METHODS; 7 - const STATUS_CODES = httpMetadata.STATUS_CODES; 3 + import httpParser from 'ant:internal/http_parser'; 4 + import httpWriter from 'ant:internal/http_writer'; 5 + 6 + import { EventEmitter } from 'node:events'; 7 + import { STATUS_CODES } from 'ant:internal/http_metadata'; 8 8 9 9 function createHeadersObject() { 10 10 return Object.create(null); ··· 106 106 } 107 107 } 108 108 109 - class IncomingMessage extends EventEmitter { 109 + export class IncomingMessage extends EventEmitter { 110 110 constructor(socket, parsed) { 111 111 super(); 112 112 this.socket = socket; ··· 124 124 this.destroyed = false; 125 125 this.readableEnded = false; 126 126 this._body = bufferFrom(parsed.body); 127 + this._bodyConsumed = false; 128 + } 129 + 130 + _takeBody() { 131 + if (this._bodyConsumed) return Buffer.alloc(0); 132 + this._bodyConsumed = true; 133 + return this._body; 127 134 } 128 135 129 136 _deliverBody() { 130 137 if (this.destroyed || this.complete) return; 131 138 132 - if (this._body.length > 0) this.emit('data', this._body); 139 + const body = this._takeBody(); 140 + if (body.length > 0) this.emit('data', body); 133 141 this.complete = true; 134 142 this.readableEnded = true; 135 143 this.emit('end'); 136 144 this.emit('close'); 137 145 } 138 146 147 + [Symbol.asyncIterator]() { 148 + const body = this._takeBody(); 149 + let emitted = false; 150 + let finished = false; 151 + 152 + return { 153 + next: async () => { 154 + if (finished) return { done: true, value: undefined }; 155 + if (!emitted && body.length > 0) { 156 + emitted = true; 157 + return { done: false, value: body }; 158 + } 159 + finished = true; 160 + this.complete = true; 161 + this.readableEnded = true; 162 + return { done: true, value: undefined }; 163 + }, 164 + return: async () => { 165 + finished = true; 166 + this.complete = true; 167 + this.readableEnded = true; 168 + return { done: true, value: undefined }; 169 + }, 170 + [Symbol.asyncIterator]() { 171 + return this; 172 + } 173 + }; 174 + } 175 + 139 176 destroy(error) { 140 177 if (this.destroyed) return this; 141 178 this.destroyed = true; ··· 150 187 } 151 188 } 152 189 153 - class ServerResponse extends EventEmitter { 190 + export class ServerResponse extends EventEmitter { 154 191 constructor(req, socket, socketState) { 155 192 super(); 156 193 this.req = req; ··· 311 348 } 312 349 } 313 350 314 - class Server extends net.Server { 351 + export class Server extends net.Server { 315 352 constructor(options, requestListener) { 316 353 const serverOptions = typeof options === 'function' ? undefined : options; 317 354 const onRequest = typeof options === 'function' ? options : requestListener; ··· 395 432 } 396 433 } 397 434 398 - function createServer(options, requestListener) { 435 + export function createServer(options, requestListener) { 399 436 return new Server(options, requestListener); 400 437 } 401 438 402 - module.exports = { 403 - METHODS, 404 - STATUS_CODES, 405 - IncomingMessage, 406 - Server, 407 - ServerResponse, 408 - createServer 409 - }; 439 + export { METHODS, STATUS_CODES } from 'ant:internal/http_metadata';
+132
src/builtins/node/https.mjs
··· 1 + import * as http from 'node:http'; 2 + import tls from 'ant:internal/tls'; 3 + 4 + const kSecureContext = Symbol('ant.internal.https.secureContext'); 5 + 6 + const TLS_CONTEXT_OPTION_KEYS = ['allowPartialChain', 'ca', 'cert', 'key']; 7 + 8 + const TLS_SERVER_OPTION_KEYS = [ 9 + 'ALPNProtocols', 10 + 'SNICallback', 11 + 'allowPartialChain', 12 + 'ca', 13 + 'cert', 14 + 'ciphers', 15 + 'clientCertEngine', 16 + 'crl', 17 + 'dhparam', 18 + 'ecdhCurve', 19 + 'honorCipherOrder', 20 + 'key', 21 + 'maxVersion', 22 + 'minVersion', 23 + 'passphrase', 24 + 'pfx', 25 + 'rejectUnauthorized', 26 + 'requestCert', 27 + 'secureOptions', 28 + 'secureProtocol', 29 + 'sessionIdContext', 30 + 'sigalgs', 31 + 'ticketKeys' 32 + ]; 33 + 34 + function createPlainObject() { 35 + return Object.create(null); 36 + } 37 + 38 + function splitServerOptions(options) { 39 + const httpOptions = createPlainObject(); 40 + const tlsOptions = createPlainObject(); 41 + let hasTlsOptions = false; 42 + 43 + if (!options || typeof options !== 'object') { 44 + return { httpOptions: undefined, tlsOptions: undefined, hasTlsOptions: false }; 45 + } 46 + 47 + Object.keys(options).forEach(key => { 48 + if (TLS_SERVER_OPTION_KEYS.includes(key)) { 49 + tlsOptions[key] = options[key]; 50 + hasTlsOptions = true; 51 + return; 52 + } 53 + 54 + httpOptions[key] = options[key]; 55 + }); 56 + 57 + return { 58 + httpOptions, 59 + tlsOptions: hasTlsOptions ? tlsOptions : undefined, 60 + hasTlsOptions 61 + }; 62 + } 63 + 64 + function toContextOptions(options) { 65 + const contextOptions = createPlainObject(); 66 + 67 + if (!options) return contextOptions; 68 + TLS_CONTEXT_OPTION_KEYS.forEach(key => { 69 + if (options[key] !== undefined) contextOptions[key] = options[key]; 70 + }); 71 + return contextOptions; 72 + } 73 + 74 + function maybeCreateSecureContext(options) { 75 + if (!options) return undefined; 76 + return tls.createContext(toContextOptions(options)); 77 + } 78 + 79 + function normalizeCreateServerArgs(options, requestListener) { 80 + if (typeof options === 'function') { 81 + return { 82 + serverOptions: undefined, 83 + requestListener: options 84 + }; 85 + } 86 + 87 + return { 88 + serverOptions: options, 89 + requestListener 90 + }; 91 + } 92 + 93 + function clientNotImplemented() { 94 + throw new Error('node:https client transport is not implemented yet'); 95 + } 96 + 97 + // compatibility stub only 98 + export class Server extends http.Server { 99 + constructor(options, requestListener) { 100 + const normalized = normalizeCreateServerArgs(options, requestListener); 101 + const split = splitServerOptions(normalized.serverOptions); 102 + 103 + super(split.httpOptions, normalized.requestListener); 104 + this[kSecureContext] = split.hasTlsOptions ? maybeCreateSecureContext(split.tlsOptions) : undefined; 105 + } 106 + } 107 + 108 + export function createSecureContext(options) { 109 + return tls.createContext(options); 110 + } 111 + 112 + export function isSecureContext(value) { 113 + return tls.isContext(value); 114 + } 115 + 116 + export function setConfigPath(path) { 117 + return tls.setConfigPath(path); 118 + } 119 + 120 + export function createServer(options, requestListener) { 121 + return new Server(options, requestListener); 122 + } 123 + 124 + export function request() { 125 + clientNotImplemented(); 126 + } 127 + 128 + export function get() { 129 + clientNotImplemented(); 130 + } 131 + 132 + export { METHODS, STATUS_CODES } from 'ant:internal/http_metadata';
+80
src/builtins/node/querystring.mjs
··· 1 + const plusPattern = /\+/g; 2 + const defaultMaxKeys = 1000; 3 + 4 + export const escape = value => encodeURIComponent(String(value)); 5 + export const normalizeComponent = value => String(value).replace(plusPattern, '%20'); 6 + 7 + export function unescape(value) { 8 + const input = normalizeComponent(value); 9 + 10 + try { 11 + return decodeURIComponent(input); 12 + } catch { 13 + return input; 14 + } 15 + } 16 + 17 + const getOptions = options => (options && typeof options === 'object' ? options : null); 18 + 19 + const appendValue = (target, key, value) => { 20 + if (target[key] === undefined) { 21 + target[key] = value; 22 + return; 23 + } 24 + 25 + target[key] = Array.isArray(target[key]) ? [...target[key], value] : [target[key], value]; 26 + }; 27 + 28 + export function parse(input, sep = '&', eq = '=', options = undefined) { 29 + const result = Object.create(null); 30 + const source = String(input || ''); 31 + const parsedOptions = getOptions(options); 32 + const maxKeys = typeof parsedOptions?.maxKeys === 'number' ? parsedOptions.maxKeys : defaultMaxKeys; 33 + const decode = typeof parsedOptions?.decodeURIComponent === 'function' ? parsedOptions.decodeURIComponent : unescape; 34 + 35 + if (source === '') return result; 36 + 37 + let count = 0; 38 + for (const entry of source.split(sep)) { 39 + if (entry === '') continue; 40 + if (maxKeys > 0 && count >= maxKeys) break; 41 + 42 + const index = entry.indexOf(eq); 43 + const rawKey = index >= 0 ? entry.slice(0, index) : entry; 44 + const rawValue = index >= 0 ? entry.slice(index + eq.length) : ''; 45 + const key = decode(normalizeComponent(rawKey)); 46 + const value = decode(normalizeComponent(rawValue)); 47 + 48 + appendValue(result, key, value); 49 + count += 1; 50 + } 51 + 52 + return result; 53 + } 54 + 55 + export function stringify(obj, sep = '&', eq = '=', options = undefined) { 56 + const parsedOptions = getOptions(options); 57 + const encode = typeof parsedOptions?.encodeURIComponent === 'function' ? parsedOptions.encodeURIComponent : escape; 58 + 59 + if (obj === null || obj === undefined) return ''; 60 + const parts = []; 61 + 62 + for (const key of Object.keys(obj)) { 63 + const value = obj[key]; 64 + const encodedKey = encode(key); 65 + 66 + if (Array.isArray(value)) { 67 + for (const item of value) parts.push(encodedKey + eq + encode(item ?? '')); 68 + continue; 69 + } 70 + 71 + parts.push(encodedKey + eq + encode(value ?? '')); 72 + } 73 + 74 + return parts.join(sep); 75 + } 76 + 77 + export const encode = stringify; 78 + export const decode = parse; 79 + 80 + export default { parse, stringify, escape, unescape, encode, decode };
+11 -7
src/errors.c
··· 22 22 typedef struct { char *buf; size_t size; } errbuf_t; 23 23 24 24 static void print_error_value(ant_t *js, ant_value_t value, ant_value_t fallback_stack, const char *prefix) { 25 + ant_value_t obj = is_err(value) ? js_as_obj(value) : value; 25 26 const char *stack = NULL; 26 27 27 - if (vtype(value) == T_OBJ) 28 - stack = get_str_prop(js, value, "stack", 5, NULL); 28 + if (vtype(obj) == T_OBJ) 29 + stack = get_str_prop(js, obj, "stack", 5, NULL); 29 30 30 31 if (!stack && vtype(fallback_stack) == T_STR) { 31 32 ant_offset_t slen; ··· 39 40 fputs(stack, stderr); 40 41 size_t n = strlen(stack); 41 42 if (n == 0 || stack[n - 1] != '\n') fputc('\n', stderr); 42 - } else if (vtype(value) == T_OBJ) { 43 - const char *name = get_str_prop(js, value, "name", 4, NULL); 44 - const char *msg = get_str_prop(js, value, "message", 7, NULL); 43 + } else if (vtype(obj) == T_OBJ) { 44 + const char *name = get_str_prop(js, obj, "name", 4, NULL); 45 + const char *msg = get_str_prop(js, obj, "message", 7, NULL); 46 + 45 47 if (name && msg) fprintf(stderr, "%s%s%s: %s%s%s\n", C_RED, name, C_RESET, C_BOLD, msg, C_RESET); 46 48 else if (name) fprintf(stderr, "%s%s%s\n", C_RED, name, C_RESET); 47 - else fprintf(stderr, "%s\n", js_str(js, value)); 48 - } else fprintf(stderr, "%s\n", js_str(js, value)); 49 + else fprintf(stderr, "[object Error]\n"); 50 + } 51 + 52 + else fprintf(stderr, "%s\n", js_str(js, value)); 49 53 } 50 54 51 55 bool print_uncaught_throw(ant_t *js) {
+19 -11
src/esm/builtin_bundle.c
··· 3 3 #include "esm/builtin_bundle.h" 4 4 #include "builtin_bundle_data.h" 5 5 6 - bool esm_is_builtin_specifier(const char *specifier) { 6 + bool esm_has_builtin_scheme(const char *specifier) { 7 7 if (!specifier) return false; 8 - return 9 - strncmp(specifier, "node:", 5) == 0 || 10 - strncmp(specifier, "ant:", 4) == 0; 8 + 9 + if (specifier[0] == 'n') return strncmp(specifier, "node:", 5) == 0; 10 + if (specifier[0] == 'a') return strncmp(specifier, "ant:" , 4) == 0; 11 + 12 + return false; 11 13 } 12 14 13 - const ant_builtin_bundle_entry_t *esm_lookup_builtin_bundle(const char *specifier, size_t spec_len) { 14 - size_t i = 0; 15 + 16 + const ant_builtin_bundle_alias_t *esm_lookup_builtin_alias(const char *specifier, size_t spec_len) { 15 17 if (!specifier) return NULL; 16 - 17 - for (i = 0; i < ant_builtin_bundle_data_count; i++) { 18 - const ant_builtin_bundle_entry_t *entry = &ant_builtin_bundle_data[i]; 19 - size_t entry_len = strlen(entry->specifier); 20 - if (entry_len == spec_len && memcmp(entry->specifier, specifier, spec_len) == 0) return entry; 18 + 19 + for (size_t i = 0; i < ant_builtin_bundle_alias_count; i++) { 20 + const ant_builtin_bundle_alias_t *alias = &ant_builtin_bundle_aliases[i]; 21 + if (alias->specifier_len != spec_len) continue; 22 + if (memcmp(alias->specifier, specifier, spec_len) != 0) continue; 23 + return alias; 21 24 } 22 25 23 26 return NULL; 24 27 } 28 + 29 + const ant_builtin_bundle_module_t *esm_lookup_builtin_module(size_t module_id) { 30 + if (module_id >= ant_builtin_bundle_module_count) return NULL; 31 + return &ant_builtin_bundle_modules[module_id]; 32 + }
+43 -24
src/esm/loader.c
··· 8 8 9 9 #include "modules/json.h" 10 10 #include "modules/napi.h" 11 + #include "modules/uri.h" 11 12 12 13 #include "errors.h" 13 14 #include "gc/modules.h" ··· 73 74 static char *esm_resolve_node_module(const char *specifier, const char *base_path); 74 75 static char *esm_canonicalize_path(const char *path); 75 76 76 - static char *esm_file_url_to_path(const char *specifier) { 77 + static char *esm_file_url_to_path(ant_t *js, const char *specifier) { 77 78 if (!specifier || strncmp(specifier, "file:", 5) != 0) return NULL; 78 79 79 80 const char *p = specifier + 5; ··· 81 82 else if (strncmp(p, "//localhost/", 12) == 0) p += 11; 82 83 83 84 if (*p == '\0') return NULL; 84 - return strdup(p); 85 + 86 + ant_value_t encoded = js_mkstr(js, p, strlen(p)); 87 + ant_value_t decoded = js_decodeURI(js, &encoded, 1); 88 + 89 + size_t len = 0; 90 + char *str = js_getstr(js, decoded, &len); 91 + 92 + return str ? strndup(str, len) : NULL; 85 93 } 86 94 87 95 static char *esm_make_cache_key(const char *module_key) { 88 96 if (!module_key) return NULL; 89 - if (esm_is_builtin_specifier(module_key)) return strdup(module_key); 97 + if (esm_has_builtin_scheme(module_key)) return strdup(module_key); 90 98 if (esm_is_data_url(module_key)) return strdup(module_key); 91 99 if (esm_is_url(module_key)) return strdup(module_key); 92 100 return esm_canonicalize_path(module_key); ··· 1176 1184 size_t spec_len, 1177 1185 const char *base_path 1178 1186 ) { 1179 - const ant_builtin_bundle_entry_t *bundle = NULL; 1187 + const ant_builtin_bundle_alias_t *bundle = NULL; 1188 + const ant_builtin_bundle_module_t *module = NULL; 1189 + 1180 1190 char *spec_copy = strndup(specifier, spec_len); 1181 1191 if (!spec_copy) return js_mkerr(js, "oom"); 1182 1192 1183 - char *file_url_path = esm_file_url_to_path(spec_copy); 1193 + char *file_url_path = esm_file_url_to_path(js, spec_copy); 1184 1194 if (file_url_path) { 1185 1195 free(spec_copy); 1186 1196 spec_copy = file_url_path; 1187 1197 spec_len = strlen(spec_copy); 1188 1198 } 1189 1199 1190 - bundle = esm_lookup_builtin_bundle(spec_copy, spec_len); 1200 + bundle = esm_lookup_builtin_alias(spec_copy, spec_len); 1191 1201 if (bundle) { 1202 + module = esm_lookup_builtin_module(bundle->module_id); 1203 + if (!module) { 1204 + free(spec_copy); 1205 + return js_mkerr(js, "Invalid builtin module id"); 1206 + } 1207 + 1192 1208 ant_value_t ns = esm_get_or_load( 1193 - js, 1194 - spec_copy, 1209 + js, spec_copy, 1210 + bundle->source_name, 1195 1211 bundle->source_name, 1196 - bundle->specifier, 1197 - bundle->format, 1198 - bundle->code, 1199 - bundle->code_len 1212 + module->format, 1213 + module->code, 1214 + module->code_len 1200 1215 ); 1216 + 1201 1217 free(spec_copy); 1202 1218 return ns; 1203 1219 } ··· 1250 1266 return js_esm_import_sync_from(js, specifier, NULL); 1251 1267 } 1252 1268 1253 - ant_value_t js_esm_import_dynamic(ant_t *js, ant_value_t specifier, ant_value_t *out_tla_promise) { 1269 + ant_value_t js_esm_import_dynamic(ant_t *js, ant_value_t specifier, const char *base_path, ant_value_t *out_tla_promise) { 1254 1270 *out_tla_promise = js_mkundef(); 1255 1271 esm_dynamic_import_depth++; 1256 1272 1257 - ant_value_t ns = js_esm_import_sync(js, specifier); 1273 + ant_value_t ns = js_esm_import_sync_from(js, specifier, base_path); 1258 1274 esm_dynamic_import_depth--; 1259 1275 if (is_err(ns)) return ns; 1260 1276 ··· 1271 1287 } 1272 1288 1273 1289 ant_value_t js_esm_make_file_url(ant_t *js, const char *path) { 1274 - size_t url_len = strlen(path) + 8; 1275 - char *url = malloc(url_len); 1276 - if (!url) return js_mkerr(js, "oom"); 1290 + size_t path_len = strlen(path); 1291 + size_t raw_len = 7 + path_len; 1292 + 1293 + char *raw = malloc(raw_len + 1); 1294 + if (!raw) return js_mkerr(js, "oom"); 1277 1295 1278 - snprintf(url, url_len, "file://%s", path); 1279 - ant_value_t val = js_mkstr(js, url, strlen(url)); 1280 - free(url); 1281 - return val; 1296 + snprintf(raw, raw_len + 1, "file://%s", path); 1297 + ant_value_t raw_val = js_mkstr(js, raw, raw_len); 1298 + free(raw); 1299 + 1300 + return js_encodeURI(js, &raw_val, 1); 1282 1301 } 1283 1302 1284 1303 void gc_mark_esm(ant_t *js, gc_mark_fn mark) { ··· 1290 1309 }} 1291 1310 1292 1311 ant_value_t js_esm_resolve_specifier(ant_t *js, ant_value_t specifier, const char *base_path) { 1293 - const ant_builtin_bundle_entry_t *bundle = NULL; 1312 + const ant_builtin_bundle_alias_t *bundle = NULL; 1294 1313 if (vtype(specifier) != T_STR) { 1295 1314 return js_mkerr(js, "import.meta.resolve() requires a string specifier"); 1296 1315 } ··· 1301 1320 char *spec_copy = strndup(spec_str, (size_t)spec_len); 1302 1321 if (!spec_copy) return js_mkerr(js, "oom"); 1303 1322 1304 - bundle = esm_lookup_builtin_bundle(spec_copy, (size_t)spec_len); 1323 + bundle = esm_lookup_builtin_alias(spec_copy, (size_t)spec_len); 1305 1324 if (bundle) { 1306 - ant_value_t result = js_mkstr(js, bundle->specifier, strlen(bundle->specifier)); 1325 + ant_value_t result = js_mkstr(js, bundle->source_name, strlen(bundle->source_name)); 1307 1326 free(spec_copy); 1308 1327 return result; 1309 1328 }
+4
src/gc/objects.c
··· 296 296 gc_mark_value(js, obj->proxy_state->target); 297 297 gc_mark_value(js, obj->proxy_state->handler); 298 298 } 299 + 300 + gc_mark_abort_signal_object( 301 + js, js_obj_from_ptr(obj), gc_mark_value 302 + ); 299 303 } 300 304 301 305 static void gc_drain_mark_stack(ant_t *js) {
+6 -2
src/main.c
··· 70 70 #include "modules/util.h" 71 71 #include "modules/async_hooks.h" 72 72 #include "modules/net.h" 73 + #include "modules/tls.h" 73 74 #include "modules/http_metadata.h" 74 75 #include "modules/http_parser.h" 75 76 #include "modules/http_writer.h" ··· 615 616 ant_register_library(ffi_library, "ant:ffi", NULL); 616 617 ant_register_library(lmdb_library, "ant:lmdb", NULL); 617 618 619 + ant_register_library(internal_tls_library, "ant:internal/tls", NULL); 618 620 ant_register_library(internal_http_parser_library, "ant:internal/http_parser", NULL); 619 621 ant_register_library(internal_http_writer_library, "ant:internal/http_writer", NULL); 620 622 ant_register_library(internal_http_metadata_library, "ant:internal/http_metadata", NULL); ··· 627 629 ant_standard_library("buffer", buffer_library); 628 630 ant_standard_library("path", path_library); 629 631 ant_standard_library("fs", fs_library); 630 - ant_standard_library("fs/promises", fs_promises_library); 631 632 ant_standard_library("os", os_library); 632 633 ant_standard_library("url", url_library); 633 634 ant_standard_library("perf_hooks", perf_hooks_library); ··· 636 637 ant_standard_library("events", events_library); 637 638 ant_standard_library("tty", tty_library); 638 639 ant_standard_library("readline", readline_library); 639 - ant_standard_library("readline/promises", readline_promises_library); 640 640 ant_standard_library("child_process", child_process_library); 641 641 ant_standard_library("worker_threads", worker_threads_library); 642 642 ant_standard_library("async_hooks", async_hooks_library); 643 643 ant_standard_library("v8", v8_library); 644 644 ant_standard_library("zlib", zlib_library); 645 + 646 + ant_standard_library("fs/promises", fs_promises_library); 647 + ant_standard_library("timers/promises", timers_promises_library); 648 + ant_standard_library("readline/promises", readline_promises_library); 645 649 646 650 ant_value_t snapshot_result = ant_load_snapshot(js); 647 651 if (vtype(snapshot_result) == T_ERR) {
+27
src/modules/abort.c
··· 50 50 return (abort_signal_data_t *)(uintptr_t)js_getnum(slot); 51 51 } 52 52 53 + static abort_signal_data_t *get_signal_data_if_signal_object(ant_value_t obj) { 54 + if (!g_initialized || !is_object_type(obj)) return NULL; 55 + if (js_get_slot(obj, SLOT_PROTO) != g_signal_proto) return NULL; 56 + return get_signal_data(obj); 57 + } 58 + 53 59 static ant_value_t make_abort_error(ant_t *js) { 54 60 return make_dom_exception(js, "signal is aborted without reason", "AbortError"); 55 61 } ··· 416 422 if (g_initialized) mark(js, g_signal_proto); 417 423 for (abort_timeout_entry_t *e = timeout_entries; e; e = e->next) 418 424 if (!e->closed) mark(js, e->signal); 425 + } 426 + 427 + void gc_mark_abort_signal_object(ant_t *js, ant_value_t signal, gc_mark_fn mark) { 428 + abort_signal_data_t *data = get_signal_data_if_signal_object(signal); 429 + 430 + if (!data) return; 431 + mark(js, data->reason); 432 + 433 + unsigned int listener_count = utarray_len(data->listeners); 434 + for (unsigned int i = 0; i < listener_count; i++) { 435 + abort_listener_t *entry = (abort_listener_t *)utarray_eltptr(data->listeners, i); 436 + if (!entry) continue; 437 + mark(js, entry->callback); 438 + } 439 + 440 + unsigned int follower_count = utarray_len(data->followers); 441 + for (unsigned int i = 0; i < follower_count; i++) { 442 + ant_value_t *follower = (ant_value_t *)utarray_eltptr(data->followers, i); 443 + if (!follower) continue; 444 + mark(js, *follower); 445 + } 419 446 } 420 447 421 448 bool abort_signal_is_aborted(ant_value_t signal) {
+46
src/modules/buffer.c
··· 1858 1858 return typedarray_join_with(js, js_getthis(js), sep, sep_len); 1859 1859 } 1860 1860 1861 + static ant_value_t js_typedarray_indexOf(ant_t *js, ant_value_t *args, int nargs) { 1862 + ant_value_t this_val = js_getthis(js); 1863 + ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER); 1864 + 1865 + TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val); 1866 + if (!ta_data || !ta_data->buffer || ta_data->buffer->is_detached) return js_mknum(-1); 1867 + 1868 + size_t len = ta_data->length; 1869 + if (len == 0 || nargs < 1) return js_mknum(-1); 1870 + 1871 + int64_t from_index = 0; 1872 + if (nargs > 1 && vtype(args[1]) != T_UNDEF) { 1873 + from_index = (int64_t)js_to_number(js, args[1]); 1874 + if (from_index < 0) { 1875 + from_index += (int64_t)len; 1876 + if (from_index < 0) from_index = 0; 1877 + }} 1878 + 1879 + if ((size_t)from_index >= len) return js_mknum(-1); 1880 + uint8_t *data = ta_data->buffer->data + ta_data->byte_offset; 1881 + double needle_num = js_to_number(js, args[0]); 1882 + 1883 + for (size_t i = (size_t)from_index; i < len; i++) { 1884 + bool match = false; 1885 + switch (ta_data->type) { 1886 + case TYPED_ARRAY_INT8: match = ((int8_t *)data)[i] == (int8_t)needle_num; break; 1887 + case TYPED_ARRAY_UINT8: 1888 + case TYPED_ARRAY_UINT8_CLAMPED: match = data[i] == (uint8_t)needle_num; break; 1889 + case TYPED_ARRAY_INT16: match = ((int16_t *)data)[i] == (int16_t)needle_num; break; 1890 + case TYPED_ARRAY_UINT16: match = ((uint16_t *)data)[i] == (uint16_t)needle_num; break; 1891 + case TYPED_ARRAY_INT32: match = ((int32_t *)data)[i] == (int32_t)needle_num; break; 1892 + case TYPED_ARRAY_UINT32: match = ((uint32_t *)data)[i] == (uint32_t)needle_num; break; 1893 + case TYPED_ARRAY_FLOAT16: match = half_to_double(((uint16_t *)data)[i]) == needle_num; break; 1894 + case TYPED_ARRAY_FLOAT32: match = ((float *)data)[i] == (float)needle_num; break; 1895 + case TYPED_ARRAY_FLOAT64: match = ((double *)data)[i] == needle_num; break; 1896 + case TYPED_ARRAY_BIGINT64: match = ((int64_t *)data)[i] == (int64_t)needle_num; break; 1897 + case TYPED_ARRAY_BIGUINT64: match = ((uint64_t *)data)[i] == (uint64_t)needle_num; break; 1898 + default: break; 1899 + } 1900 + if (match) return js_mknum((double)i); 1901 + } 1902 + 1903 + return js_mknum(-1); 1904 + } 1905 + 1861 1906 // Buffer.prototype.toString(encoding) 1862 1907 static ant_value_t js_buffer_slice(ant_t *js, ant_value_t *args, int nargs) { 1863 1908 return js_typedarray_subarray(js, args, nargs); ··· 2174 2219 js_set(js, typedarray_proto, "with", js_mkfun(js_typedarray_with)); 2175 2220 js_set(js, typedarray_proto, "toString", js_mkfun(js_typedarray_toString)); 2176 2221 js_set(js, typedarray_proto, "join", js_mkfun(js_typedarray_join)); 2222 + js_set(js, typedarray_proto, "indexOf", js_mkfun(js_typedarray_indexOf)); 2177 2223 js_set_sym(js, typedarray_proto, get_toStringTag_sym(), js_mkstr(js, "TypedArray", 10)); 2178 2224 2179 2225 g_typedarray_iter_proto = js_mkobj(js);
+185 -111
src/modules/collections.c
··· 10 10 #include "silver/engine.h" 11 11 #include "descriptors.h" 12 12 13 + #include "modules/bigint.h" 13 14 #include "modules/collections.h" 14 15 #include "modules/symbol.h" 15 16 16 17 ant_value_t g_map_iter_proto = 0; 17 18 ant_value_t g_set_iter_proto = 0; 18 19 20 + typedef struct { 21 + unsigned char stack[32]; 22 + unsigned char *bytes; 23 + size_t len; 24 + } collection_key_t; 25 + 19 26 static ant_value_t normalize_map_key(ant_value_t key) { 20 27 if (vtype(key) == T_NUM) { 21 28 double d = tod(key); ··· 24 31 return key; 25 32 } 26 33 27 - static const char *ant_value_to_key(ant_t *js, ant_value_t val) { 28 - if (vtype(val) == T_STR) { 29 - ant_offset_t len; 30 - ant_offset_t off = vstr(js, val, &len); 31 - return (char *)(uintptr_t)(off); 34 + static void collection_key_reset(collection_key_t *key) { 35 + key->bytes = key->stack; 36 + key->len = 0; 37 + } 38 + 39 + static void collection_key_free(collection_key_t *key) { 40 + if (key->bytes != key->stack) free(key->bytes); 41 + collection_key_reset(key); 42 + } 43 + 44 + static bool collection_key_reserve(collection_key_t *key, size_t len) { 45 + if (len <= sizeof(key->stack)) return true; 46 + unsigned char *heap = malloc(len); 47 + if (!heap) return false; 48 + key->bytes = heap; 49 + return true; 50 + } 51 + 52 + static bool collection_key_init(ant_t *js, ant_value_t input, collection_key_t *out) { 53 + collection_key_reset(out); 54 + 55 + ant_value_t key = normalize_map_key(input); 56 + uint8_t tag = (uint8_t)vtype(key); 57 + 58 + if (vtype(key) == T_STR) { 59 + size_t str_len = 0; 60 + const char *str = js_getstr(js, key, &str_len); 61 + out->len = 1 + str_len; 62 + if (!collection_key_reserve(out, out->len)) return false; 63 + out->bytes[0] = tag; 64 + if (str_len > 0) memcpy(out->bytes + 1, str, str_len); 65 + return true; 32 66 } 33 - return js_str(js, val); 67 + 68 + if (vtype(key) == T_BIGINT) { 69 + size_t str_len = bigint_digits_len(js, key) + (bigint_is_negative(js, key) ? 1 : 0); 70 + out->len = 1 + str_len; 71 + if (!collection_key_reserve(out, out->len)) return false; 72 + out->bytes[0] = tag; 73 + if (str_len > 0) strbigint(js, key, (char *)(out->bytes + 1), str_len + 1); 74 + return true; 75 + } 76 + 77 + out->len = 1 + sizeof(ant_value_t); 78 + if (!collection_key_reserve(out, out->len)) return false; 79 + out->bytes[0] = tag; 80 + memcpy(out->bytes + 1, &key, sizeof(ant_value_t)); 81 + 82 + return true; 83 + } 84 + 85 + static map_entry_t *map_find_entry(ant_t *js, map_entry_t **map_ptr, ant_value_t key_val) { 86 + collection_key_t key; 87 + if (!collection_key_init(js, key_val, &key)) return NULL; 88 + 89 + map_entry_t *entry = NULL; 90 + HASH_FIND(hh, *map_ptr, key.bytes, key.len, entry); 91 + collection_key_free(&key); 92 + 93 + return entry; 94 + } 95 + 96 + static set_entry_t *set_find_entry(ant_t *js, set_entry_t **set_ptr, ant_value_t value) { 97 + collection_key_t key; 98 + if (!collection_key_init(js, value, &key)) return NULL; 99 + 100 + set_entry_t *entry = NULL; 101 + HASH_FIND(hh, *set_ptr, key.bytes, key.len, entry); 102 + collection_key_free(&key); 103 + 104 + return entry; 105 + } 106 + 107 + static bool map_store_entry( 108 + ant_t *js, 109 + map_entry_t **map_ptr, 110 + ant_value_t raw_key, 111 + ant_value_t key_val, 112 + ant_value_t value 113 + ) { 114 + collection_key_t key; 115 + if (!collection_key_init(js, raw_key, &key)) return false; 116 + 117 + map_entry_t *entry = NULL; 118 + HASH_FIND(hh, *map_ptr, key.bytes, key.len, entry); 119 + if (entry) { 120 + entry->key_val = key_val; 121 + entry->value = value; 122 + collection_key_free(&key); 123 + return true; 124 + } 125 + 126 + entry = ant_calloc(sizeof(map_entry_t)); 127 + if (!entry) { 128 + collection_key_free(&key); 129 + return false; 130 + } 131 + 132 + entry->key = malloc(key.len); 133 + if (!entry->key) { 134 + collection_key_free(&key); 135 + free(entry); 136 + return false; 137 + } 138 + 139 + memcpy(entry->key, key.bytes, key.len); 140 + entry->key_len = key.len; 141 + entry->key_val = key_val; 142 + entry->value = value; 143 + 144 + HASH_ADD_KEYPTR(hh, *map_ptr, entry->key, entry->key_len, entry); 145 + collection_key_free(&key); 146 + 147 + return true; 148 + } 149 + 150 + static bool set_store_entry(ant_t *js, set_entry_t **set_ptr, ant_value_t value) { 151 + collection_key_t key; 152 + if (!collection_key_init(js, value, &key)) return false; 153 + 154 + set_entry_t *entry = NULL; 155 + HASH_FIND(hh, *set_ptr, key.bytes, key.len, entry); 156 + if (entry) { 157 + collection_key_free(&key); 158 + return true; 159 + } 160 + 161 + entry = ant_calloc(sizeof(set_entry_t)); 162 + if (!entry) { 163 + collection_key_free(&key); 164 + return false; 165 + } 166 + 167 + entry->key = malloc(key.len); 168 + if (!entry->key) { 169 + collection_key_free(&key); 170 + free(entry); 171 + return false; 172 + } 173 + 174 + memcpy(entry->key, key.bytes, key.len); 175 + entry->key_len = key.len; 176 + entry->value = value; 177 + 178 + HASH_ADD_KEYPTR(hh, *set_ptr, entry->key, entry->key_len, entry); 179 + collection_key_free(&key); 180 + 181 + return true; 34 182 } 35 183 36 184 map_entry_t **get_map_from_obj(ant_t *js, ant_value_t obj) { ··· 79 227 ant_value_t this_val = js->this_val; 80 228 map_entry_t **map_ptr = get_map_from_obj(js, this_val); 81 229 if (!map_ptr) return js_mkerr(js, "Invalid Map object"); 82 - const char *key_str = ant_value_to_key(js, args[0]); 83 230 84 231 ant_value_t key_val = normalize_map_key(args[0]); 85 - map_entry_t *entry; 86 - 87 - HASH_FIND_STR(*map_ptr, key_str, entry); 88 - if (entry) { 89 - entry->key_val = key_val; 90 - entry->value = args[1]; 91 - } else { 92 - entry = ant_calloc(sizeof(map_entry_t)); 93 - if (!entry) return js_mkerr(js, "out of memory"); 94 - entry->key = strdup(key_str); 95 - entry->key_val = key_val; 96 - entry->value = args[1]; 97 - HASH_ADD_STR(*map_ptr, key, entry); 98 - } 232 + if (!map_store_entry(js, map_ptr, args[0], key_val, args[1])) 233 + return js_mkerr(js, "out of memory"); 99 234 100 235 return this_val; 101 236 } ··· 106 241 ant_value_t this_val = js->this_val; 107 242 map_entry_t **map_ptr = get_map_from_obj(js, this_val); 108 243 if (!map_ptr) return js_mkundef(); 109 - 110 - const char *key_str = ant_value_to_key(js, args[0]); 111 - 112 - map_entry_t *entry; 113 - HASH_FIND_STR(*map_ptr, key_str, entry); 244 + 245 + map_entry_t *entry = map_find_entry(js, map_ptr, args[0]); 114 246 return entry ? entry->value : js_mkundef(); 115 247 } 116 248 ··· 121 253 map_entry_t **map_ptr = get_map_from_obj(js, this_val); 122 254 123 255 if (!map_ptr) return js_false; 124 - const char *key_str = ant_value_to_key(js, args[0]); 125 - 126 - map_entry_t *entry; 127 - HASH_FIND_STR(*map_ptr, key_str, entry); 256 + map_entry_t *entry = map_find_entry(js, map_ptr, args[0]); 128 257 return js_bool(entry != NULL); 129 258 } 130 259 ··· 135 264 map_entry_t **map_ptr = get_map_from_obj(js, this_val); 136 265 137 266 if (!map_ptr) return js_false; 138 - const char *key_str = ant_value_to_key(js, args[0]); 139 - 140 - map_entry_t *entry; 141 - HASH_FIND_STR(*map_ptr, key_str, entry); 267 + map_entry_t *entry = map_find_entry(js, map_ptr, args[0]); 142 268 if (entry) { 143 269 HASH_DEL(*map_ptr, entry); 144 270 free(entry->key); ··· 308 434 set_entry_t **set_ptr = get_set_from_obj(js, this_val); 309 435 if (!set_ptr) return js_mkerr(js, "Invalid Set object"); 310 436 311 - const char *key_str = ant_value_to_key(js, args[0]); 312 - 313 - set_entry_t *entry; 314 - HASH_FIND_STR(*set_ptr, key_str, entry); 315 - 316 - if (!entry) { 317 - entry = ant_calloc(sizeof(set_entry_t)); 318 - if (!entry) return js_mkerr(js, "out of memory"); 319 - entry->value = args[0]; 320 - entry->key = strdup(key_str); 321 - HASH_ADD_KEYPTR(hh, *set_ptr, entry->key, strlen(entry->key), entry); 322 - } 437 + if (!set_store_entry(js, set_ptr, args[0])) 438 + return js_mkerr(js, "out of memory"); 323 439 324 440 return this_val; 325 441 } ··· 330 446 ant_value_t this_val = js->this_val; 331 447 set_entry_t **set_ptr = get_set_from_obj(js, this_val); 332 448 if (!set_ptr) return js_false; 333 - 334 - const char *key_str = ant_value_to_key(js, args[0]); 335 - 336 - set_entry_t *entry; 337 - HASH_FIND_STR(*set_ptr, key_str, entry); 449 + 450 + set_entry_t *entry = set_find_entry(js, set_ptr, args[0]); 338 451 return js_bool(entry != NULL); 339 452 } 340 453 ··· 344 457 ant_value_t this_val = js->this_val; 345 458 set_entry_t **set_ptr = get_set_from_obj(js, this_val); 346 459 if (!set_ptr) return js_false; 347 - 348 - const char *key_str = ant_value_to_key(js, args[0]); 349 - set_entry_t *entry; 350 - HASH_FIND_STR(*set_ptr, key_str, entry); 460 + 461 + set_entry_t *entry = set_find_entry(js, set_ptr, args[0]); 351 462 352 463 if (entry) { 353 464 HASH_DEL(*set_ptr, entry); ··· 427 538 weakmap_entry_t **wm_ptr = get_weakmap_from_obj(js, this_val); 428 539 if (!wm_ptr) return js_mkerr(js, "Invalid WeakMap object"); 429 540 430 - if (vtype(args[0]) != T_OBJ) 541 + if (!is_object_type(args[0])) 431 542 return js_mkerr(js, "WeakMap key must be an object"); 432 543 433 544 ant_value_t key_obj = args[0]; ··· 453 564 ant_value_t this_val = js->this_val; 454 565 weakmap_entry_t **wm_ptr = get_weakmap_from_obj(js, this_val); 455 566 if (!wm_ptr) return js_mkundef(); 456 - 457 - if (vtype(args[0]) != T_OBJ) return js_mkundef(); 567 + if (!is_object_type(args[0])) return js_mkundef(); 458 568 459 569 ant_value_t key_obj = args[0]; 460 570 weakmap_entry_t *entry; ··· 468 578 ant_value_t this_val = js->this_val; 469 579 weakmap_entry_t **wm_ptr = get_weakmap_from_obj(js, this_val); 470 580 if (!wm_ptr) return js_false; 471 - 472 - if (vtype(args[0]) != T_OBJ) return js_false; 581 + if (!is_object_type(args[0])) return js_false; 473 582 474 583 ant_value_t key_obj = args[0]; 475 584 weakmap_entry_t *entry; ··· 483 592 ant_value_t this_val = js->this_val; 484 593 weakmap_entry_t **wm_ptr = get_weakmap_from_obj(js, this_val); 485 594 if (!wm_ptr) return js_false; 486 - 487 - if (vtype(args[0]) != T_OBJ) return js_false; 595 + if (!is_object_type(args[0])) return js_false; 488 596 489 597 ant_value_t key_obj = args[0]; 490 598 weakmap_entry_t *entry; ··· 504 612 weakset_entry_t **ws_ptr = get_weakset_from_obj(js, this_val); 505 613 if (!ws_ptr) return js_mkerr(js, "Invalid WeakSet object"); 506 614 507 - if (vtype(args[0]) != T_OBJ) 615 + if (!is_object_type(args[0])) 508 616 return js_mkerr(js, "WeakSet value must be an object"); 509 617 510 618 ant_value_t value_obj = args[0]; ··· 528 636 ant_value_t this_val = js->this_val; 529 637 weakset_entry_t **ws_ptr = get_weakset_from_obj(js, this_val); 530 638 if (!ws_ptr) return js_false; 531 - 532 - if (vtype(args[0]) != T_OBJ) return js_false; 639 + if (!is_object_type(args[0])) return js_false; 533 640 534 641 ant_value_t value_obj = args[0]; 535 642 weakset_entry_t *entry; ··· 543 650 ant_value_t this_val = js->this_val; 544 651 weakset_entry_t **ws_ptr = get_weakset_from_obj(js, this_val); 545 652 if (!ws_ptr) return js_false; 546 - 547 - if (vtype(args[0]) != T_OBJ) return js_false; 653 + if (!is_object_type(args[0])) return js_false; 548 654 549 655 ant_value_t value_obj = args[0]; 550 656 weakset_entry_t *entry; ··· 559 665 } 560 666 561 667 static ant_value_t builtin_WeakRef(ant_t *js, ant_value_t *args, int nargs) { 562 - if (nargs < 1 || vtype(args[0]) != T_OBJ) { 668 + if (nargs < 1 || !is_object_type(args[0])) { 563 669 return js_mkerr(js, "WeakRef target must be an object"); 564 670 } 565 671 ··· 695 801 js_mkundef(), cb_args, 2, NULL, false) 696 802 ); 697 803 698 - map_entry_t *entry; 699 - ant_value_t group; 700 - 701 804 if (is_err(key)) return key; 702 - const char *key_str = ant_value_to_key(js, key); 703 - HASH_FIND_STR(*map_head, key_str, entry); 704 - 805 + map_entry_t *entry = map_find_entry(js, map_head, key); 806 + ant_value_t group; 807 + 705 808 if (entry) group = entry->value; else { 706 809 group = js_mkarr(js); 707 - entry = ant_calloc(sizeof(map_entry_t)); 708 - if (!entry) return js_mkerr(js, "out of memory"); 709 - entry->key = strdup(key_str); 710 - entry->key_val = key; 711 - entry->value = group; 712 - HASH_ADD_STR(*map_head, key, entry); 810 + if (!map_store_entry(js, map_head, key, key, group)) return js_mkerr(js, "out of memory"); 713 811 } 714 812 715 813 js_arr_push(js, group, val); ··· 756 854 757 855 ant_value_t key = normalize_map_key(js_arr_get(js, entry, 0)); 758 856 ant_value_t value = js_arr_get(js, entry, 1); 759 - const char *key_str = ant_value_to_key(js, key); 760 - 761 - map_entry_t *map_entry; 762 - HASH_FIND_STR(*map_head, key_str, map_entry); 763 - if (map_entry) { 764 - map_entry->key_val = key; 765 - map_entry->value = value; 766 - continue; 767 - } 768 - 769 - map_entry = ant_calloc(sizeof(map_entry_t)); 770 - if (!map_entry) return js_mkerr(js, "out of memory"); 771 - map_entry->key = strdup(key_str); 772 - map_entry->key_val = key; 773 - map_entry->value = value; 774 - HASH_ADD_STR(*map_head, key, map_entry); 857 + if (!map_store_entry(js, map_head, key, key, value)) 858 + return js_mkerr(js, "out of memory"); 775 859 } 776 860 777 861 return map_obj; ··· 808 892 809 893 for (ant_offset_t i = 0; i < length; i++) { 810 894 ant_value_t value = js_arr_get(js, iterable, i); 811 - const char *key_str = ant_value_to_key(js, value); 812 - 813 - set_entry_t *entry; 814 - HASH_FIND_STR(*set_head, key_str, entry); 815 - if (entry) continue; 816 - 817 - entry = ant_calloc(sizeof(set_entry_t)); 818 - if (!entry) return js_mkerr(js, "out of memory"); 819 - entry->value = value; 820 - entry->key = strdup(key_str); 821 - HASH_ADD_KEYPTR(hh, *set_head, entry->key, strlen(entry->key), entry); 895 + if (!set_store_entry(js, set_head, value)) return js_mkerr(js, "out of memory"); 822 896 } 823 897 824 898 return set_obj; ··· 862 936 863 937 ant_value_t key = js_arr_get(js, entry, 0); 864 938 ant_value_t value = js_arr_get(js, entry, 1); 865 - 866 - if (vtype(key) != T_OBJ) return js_mkerr(js, "WeakMap key must be an object"); 939 + if (!is_object_type(key)) return js_mkerr(js, "WeakMap key must be an object"); 867 940 868 941 weakmap_entry_t *wm_entry; 869 942 HASH_FIND(hh, *wm_head, &key, sizeof(ant_value_t), wm_entry); 943 + 870 944 if (wm_entry) { 871 945 wm_entry->value = value; 872 946 continue; ··· 874 948 875 949 wm_entry = ant_calloc(sizeof(weakmap_entry_t)); 876 950 if (!wm_entry) return js_mkerr(js, "out of memory"); 951 + 877 952 wm_entry->key_obj = key; 878 953 wm_entry->value = value; 879 954 HASH_ADD(hh, *wm_head, key_obj, sizeof(ant_value_t), wm_entry); ··· 913 988 914 989 for (ant_offset_t i = 0; i < length; i++) { 915 990 ant_value_t value = js_arr_get(js, iterable, i); 916 - 917 - if (vtype(value) != T_OBJ) return js_mkerr(js, "WeakSet value must be an object"); 991 + if (!is_object_type(value)) return js_mkerr(js, "WeakSet value must be an object"); 918 992 919 993 weakset_entry_t *entry; 920 994 HASH_FIND(hh, *ws_head, &value, sizeof(ant_value_t), entry);
+87 -1
src/modules/fs.c
··· 148 148 FS_OP_EXISTS, 149 149 FS_OP_READDIR, 150 150 FS_OP_ACCESS, 151 + FS_OP_REALPATH, 151 152 FS_OP_WRITE_FD, 152 153 FS_OP_OPEN, 153 154 FS_OP_CLOSE ··· 221 222 result = encode_data(req->js, req->data, req->data_len, req->encoding); 222 223 else result = fs_read_to_uint8array(req->js, req->data, req->data_len); 223 224 } else if (req->op_type == FS_OP_READ_BYTES && req->data) 225 + result = js_mkstr(req->js, req->data, req->data_len); 226 + else if (req->op_type == FS_OP_REALPATH && req->data) 224 227 result = js_mkstr(req->js, req->data, req->data_len); 225 228 js_resolve_promise(req->js, req->promise, result); 226 229 } ··· 420 423 js_resolve_promise(req->js, req->promise, stat_obj); 421 424 remove_pending_request(req); 422 425 free_fs_request(req); 426 + } 427 + 428 + static void on_realpath_complete(uv_fs_t *uv_req) { 429 + fs_request_t *req = (fs_request_t *)uv_req->data; 430 + 431 + if (uv_req->result < 0 || !uv_req->ptr) { 432 + req->failed = 1; 433 + req->error_msg = strdup(uv_strerror((int)uv_req->result)); 434 + req->completed = 1; 435 + complete_request(req); 436 + return; 437 + } 438 + 439 + req->data = strdup((const char *)uv_req->ptr); 440 + if (!req->data) { 441 + req->failed = 1; 442 + req->error_msg = strdup("Out of memory"); 443 + req->completed = 1; 444 + complete_request(req); 445 + return; 446 + } 447 + 448 + req->data_len = strlen(req->data); 449 + req->completed = 1; 450 + complete_request(req); 423 451 } 424 452 425 453 static void on_exists_complete(uv_fs_t *uv_req) { ··· 1304 1332 return req->promise; 1305 1333 } 1306 1334 1335 + static ant_value_t builtin_fs_realpathSync(ant_t *js, ant_value_t *args, int nargs) { 1336 + if (nargs < 1) return js_mkerr(js, "realpathSync() requires a path argument"); 1337 + 1338 + ant_value_t path_val = fs_coerce_path(js, args[0]); 1339 + if (vtype(path_val) != T_STR) return js_mkerr(js, "realpathSync() path must be a string"); 1340 + 1341 + size_t path_len = 0; 1342 + const char *path = js_getstr(js, path_val, &path_len); 1343 + if (!path) return js_mkerr(js, "Failed to get path string"); 1344 + 1345 + char *resolved = realpath(path, NULL); 1346 + if (!resolved) return js_mkerr(js, "realpathSync failed for '%s'", path); 1347 + 1348 + ant_value_t result = js_mkstr(js, resolved, strlen(resolved)); 1349 + free(resolved); 1350 + return result; 1351 + } 1352 + 1353 + static ant_value_t builtin_fs_realpath(ant_t *js, ant_value_t *args, int nargs) { 1354 + if (nargs < 1) return js_mkerr(js, "realpath() requires a path argument"); 1355 + 1356 + ant_value_t path_val = fs_coerce_path(js, args[0]); 1357 + if (vtype(path_val) != T_STR) return js_mkerr(js, "realpath() path must be a string"); 1358 + 1359 + size_t path_len = 0; 1360 + const char *path = js_getstr(js, path_val, &path_len); 1361 + if (!path) return js_mkerr(js, "Failed to get path string"); 1362 + 1363 + fs_request_t *req = calloc(1, sizeof(fs_request_t)); 1364 + if (!req) return js_mkerr(js, "Out of memory"); 1365 + 1366 + req->js = js; 1367 + req->op_type = FS_OP_REALPATH; 1368 + req->promise = js_mkpromise(js); 1369 + req->path = strndup(path, path_len); 1370 + req->uv_req.data = req; 1371 + 1372 + if (!req->path) { 1373 + free_fs_request(req); 1374 + return js_mkerr(js, "Out of memory"); 1375 + } 1376 + 1377 + utarray_push_back(pending_requests, &req); 1378 + int result = uv_fs_realpath(uv_default_loop(), &req->uv_req, req->path, on_realpath_complete); 1379 + 1380 + if (result < 0) { 1381 + req->failed = 1; 1382 + req->error_msg = strdup(uv_strerror(result)); 1383 + req->completed = 1; 1384 + complete_request(req); 1385 + } 1386 + 1387 + return req->promise; 1388 + } 1389 + 1307 1390 static ant_value_t builtin_fs_readdirSync(ant_t *js, ant_value_t *args, int nargs) { 1308 1391 if (nargs < 1) return js_mkerr(js, "readdirSync() requires a path argument"); 1309 1392 ··· 1873 1956 js_set(js, lib, "exists", js_mkfun(builtin_fs_exists)); 1874 1957 js_set(js, lib, "access", js_mkfun(builtin_fs_access)); 1875 1958 js_set(js, lib, "readdir", js_mkfun(builtin_fs_readdir)); 1959 + js_set(js, lib, "realpath", js_mkfun(builtin_fs_realpath)); 1876 1960 } 1877 1961 1878 1962 static ant_value_t fs_make_constants(ant_t *js) { ··· 1903 1987 1904 1988 ant_value_t fs_library(ant_t *js) { 1905 1989 ant_value_t lib = js_mkobj(js); 1990 + ant_value_t realpath_sync = js_mkfun(builtin_fs_realpathSync); 1906 1991 fs_set_promise_methods(js, lib); 1907 1992 1908 1993 js_set(js, lib, "readFileSync", js_mkfun(builtin_fs_readFileSync)); ··· 1923 2008 js_set(js, lib, "existsSync", js_mkfun(builtin_fs_existsSync)); 1924 2009 js_set(js, lib, "accessSync", js_mkfun(builtin_fs_accessSync)); 1925 2010 js_set(js, lib, "readdirSync", js_mkfun(builtin_fs_readdirSync)); 2011 + js_set(js, lib, "realpathSync", realpath_sync); 2012 + js_set(js, realpath_sync, "native", realpath_sync); 1926 2013 1927 2014 js_set_getter_desc( 1928 2015 js, lib, ··· 1959 2046 if (reqp && *reqp) { mark(js, (*reqp)->promise); } 1960 2047 } 1961 2048 } 1962 -
-1
src/modules/globals.c
··· 106 106 107 107 js_set(js, global, "reportError", js_mkfun(js_report_error)); 108 108 js_set_descriptor(js, global, "reportError", 11, JS_DESC_W | JS_DESC_C); 109 - 110 109 }
+126 -56
src/modules/json.c
··· 5 5 #include <yyjson.h> 6 6 #include <uthash.h> 7 7 8 + #include "gc.h" 8 9 #include "utf8.h" 9 10 #include "errors.h" 10 11 #include "runtime.h" ··· 40 41 size_t idx, max; 41 42 yyjson_val *item; 42 43 43 - yyjson_arr_foreach(val, idx, max, item) 44 - js_arr_push(js, arr, yyjson_to_jsval(js, item)); 45 - 44 + yyjson_arr_foreach(val, idx, max, item) { 45 + ant_value_t elem = yyjson_to_jsval(js, item); 46 + js_arr_push(js, arr, elem); 47 + } 48 + 46 49 return arr; 47 50 } 48 51 ··· 53 56 json_key_entry_t *hash = NULL, *entry, *tmp; 54 57 55 58 yyjson_obj_foreach(val, idx, max, key, item) { 56 - const char *k = yyjson_get_str(key); 57 - size_t klen = yyjson_get_len(key); 58 - ant_value_t v = yyjson_to_jsval(js, item); 59 - 60 - HASH_FIND(hh, hash, k, klen, entry); 61 - if (entry) js_saveval(js, entry->prop_off, v); else { 62 - ant_offset_t off = js_mkprop_fast_off(js, obj, k, klen, v); 63 - entry = malloc(sizeof(json_key_entry_t)); 64 - entry->key = k; entry->key_len = klen; entry->prop_off = off; 65 - HASH_ADD_KEYPTR(hh, hash, entry->key, entry->key_len, entry); 66 - } 67 - } 59 + const char *k = yyjson_get_str(key); 60 + 61 + size_t klen = yyjson_get_len(key); 62 + ant_value_t v = yyjson_to_jsval(js, item); 63 + 64 + HASH_FIND(hh, hash, k, klen, entry); 65 + if (entry) js_saveval(js, entry->prop_off, v); else { 66 + ant_offset_t off = js_mkprop_fast_off(js, obj, k, klen, v); 67 + entry = malloc(sizeof(json_key_entry_t)); 68 + entry->key = k; entry->key_len = klen; entry->prop_off = off; 69 + HASH_ADD_KEYPTR(hh, hash, entry->key, entry->key_len, entry); 70 + }} 68 71 69 72 HASH_ITER(hh, hash, entry, tmp) { 70 73 HASH_DEL(hash, entry); free(entry); ··· 188 191 const char *key, ant_value_t reviver 189 192 ); 190 193 194 + static ant_value_t json_apply_tojson( 195 + ant_t *js, 196 + const char *key, 197 + ant_value_t val, 198 + json_cycle_ctx *ctx 199 + ) { 200 + if (!is_special_object(val)) return val; 201 + ant_value_t toJSON = js_get(js, val, "toJSON"); 202 + 203 + if (is_err(toJSON)) { 204 + json_capture_error(ctx, toJSON); 205 + return js_mkundef(); 206 + } 207 + 208 + if (!is_callable(toJSON)) return val; 209 + ant_value_t args[1] = { js_mkstr(js, key, strlen(key)) }; 210 + 211 + ant_value_t transformed = sv_vm_call( 212 + js->vm, js, 213 + toJSON, val, 214 + args, 1, NULL, false 215 + ); 216 + 217 + if (is_err(transformed)) { 218 + json_capture_error(ctx, transformed); 219 + return js_mkundef(); 220 + } 221 + 222 + return transformed; 223 + } 224 + 225 + static ant_value_t json_apply_replacer( 226 + ant_t *js, 227 + const char *key, 228 + ant_value_t val, 229 + json_cycle_ctx *ctx 230 + ) { 231 + if (!is_callable(ctx->replacer_func)) return val; 232 + ant_value_t args[2] = { js_mkstr(js, key, strlen(key)), val }; 233 + 234 + ant_value_t transformed = sv_vm_call( 235 + js->vm, js, 236 + ctx->replacer_func, ctx->holder, 237 + args, 2, NULL, false 238 + ); 239 + 240 + if (is_err(transformed)) { 241 + json_capture_error(ctx, transformed); 242 + return js_mkundef(); 243 + } 244 + 245 + return transformed; 246 + } 247 + 248 + static inline ant_value_t json_create_root_holder(ant_t *js, ant_value_t value) { 249 + ant_value_t holder = js_mkobj(js); 250 + if (!is_err(holder)) js_set(js, holder, "", value); 251 + return holder; 252 + } 253 + 191 254 static yyjson_mut_val *json_array_to_yyjson( 192 255 ant_t *js, yyjson_mut_doc *doc, ant_value_t val, json_cycle_ctx *ctx 193 256 ) { ··· 241 304 ctx->holder = saved_holder; 242 305 return NULL; 243 306 } 244 - int ptype = vtype(prop); 245 - if (ptype == T_UNDEF || ptype == T_FUNC) continue; 246 - 307 + 247 308 yyjson_mut_val *jval = ant_value_to_yyjson_with_key(js, doc, key, prop, ctx, 0); 248 309 if (json_has_abort(ctx)) { 249 310 ctx->holder = saved_holder; ··· 262 323 int type = vtype(val); 263 324 yyjson_mut_val *result = NULL; 264 325 265 - if (is_special_object(val)) { 266 - ant_value_t toJSON = js_get(js, val, "toJSON"); 267 - if (is_err(toJSON)) { 268 - json_capture_error(ctx, toJSON); 269 - return NULL; 270 - } 271 - 272 - if (vtype(toJSON) == T_FUNC) { 273 - ant_value_t r = sv_vm_call(js->vm, js, toJSON, js_mkundef(), &val, 1, NULL, false); 274 - if (vtype(r) == T_ERR) { json_capture_error(ctx, r); return NULL; } 275 - return ant_value_to_yyjson_impl(js, doc, r, ctx, in_array); 276 - }} 277 - 278 326 switch (type) { 279 327 case T_NULL: return yyjson_mut_null(doc); 280 328 case T_BOOL: return yyjson_mut_bool(doc, val == js_true); 281 329 282 330 case T_UNDEF: return in_array ? yyjson_mut_null(doc) : YYJSON_SKIP_VALUE; 283 331 case T_FUNC: return in_array ? yyjson_mut_null(doc) : YYJSON_SKIP_VALUE; 332 + case T_SYMBOL: return in_array ? yyjson_mut_null(doc) : YYJSON_SKIP_VALUE; 284 333 285 334 case T_NUM: { 286 335 double num = js_getnum(val); ··· 317 366 ant_t *js, yyjson_mut_doc *doc, const char *key, 318 367 ant_value_t val, json_cycle_ctx *ctx, int in_array 319 368 ) { 320 - if (vtype(ctx->replacer_func) != T_FUNC) 321 - return ant_value_to_yyjson_impl(js, doc, val, ctx, in_array); 322 - 323 - ant_value_t key_str = js_mkstr(js, key, strlen(key)); 324 - ant_value_t call_args[2] = { key_str, val }; 325 - ant_value_t transformed = sv_vm_call(js->vm, js, ctx->replacer_func, js_mkundef(), call_args, 2, NULL, false); 326 - 327 - if (vtype(transformed) == T_ERR) { 328 - json_capture_error(ctx, transformed); 329 - return NULL; 330 - } 331 - 332 - return ant_value_to_yyjson_impl(js, doc, transformed, ctx, in_array); 369 + val = json_apply_tojson(js, key, val, ctx); 370 + if (json_has_abort(ctx)) return NULL; 371 + 372 + val = json_apply_replacer(js, key, val, ctx); 373 + if (json_has_abort(ctx)) return NULL; 374 + 375 + return ant_value_to_yyjson_impl(js, doc, val, ctx, in_array); 333 376 } 334 377 335 378 static yyjson_mut_val *ant_value_to_yyjson(ant_t *js, yyjson_mut_doc *doc, ant_value_t val, json_cycle_ctx *ctx) { ··· 338 381 339 382 static ant_value_t apply_reviver_call(ant_t *js, ant_value_t holder, const char *key, ant_value_t reviver) { 340 383 ant_value_t key_str = js_mkstr(js, key, strlen(key)); 341 - ant_value_t call_args[2] = { key_str, js_get(js, holder, key) }; 342 - return sv_vm_call(js->vm, js, reviver, holder, call_args, 2, NULL, false); 384 + ant_value_t current_value = js_get(js, holder, key); 385 + ant_value_t call_args[2] = { key_str, current_value }; 386 + 387 + ant_value_t result = sv_vm_call( 388 + js->vm, js, reviver, holder, 389 + call_args, 2, NULL, false 390 + ); 391 + 392 + return result; 343 393 } 344 394 345 395 static void apply_reviver_to_array(ant_t *js, ant_value_t value, ant_value_t reviver) { 346 396 ant_offset_t length = js_arr_len(js, value); 347 397 348 398 for (ant_offset_t i = 0; i < length; i++) { 349 - char idxstr[32]; 350 - size_t idx_len = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i); 351 - ant_value_t new_elem = apply_reviver(js, value, idxstr, reviver); 352 - if (vtype(new_elem) == T_UNDEF) js_delete_prop(js, value, idxstr, idx_len); 353 - else js_set(js, value, idxstr, new_elem); 354 - } 399 + char idxstr[32]; 400 + size_t idx_len = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i); 401 + ant_value_t new_elem = apply_reviver(js, value, idxstr, reviver); 402 + if (vtype(new_elem) == T_UNDEF) js_delete_prop(js, value, idxstr, idx_len); 403 + else { 404 + ant_value_t key_val = js_mkstr(js, idxstr, idx_len); 405 + js_setprop(js, value, key_val, new_elem); 406 + }} 355 407 } 356 408 357 409 static void apply_reviver_to_object(ant_t *js, ant_value_t value, ant_value_t reviver) { ··· 382 434 ant_value_t js_json_parse(ant_t *js, ant_value_t *args, int nargs) { 383 435 if (nargs < 1) return js_mkerr(js, "JSON.parse() requires at least 1 argument"); 384 436 if (vtype(args[0]) != T_STR) return js_mkerr(js, "JSON.parse() argument must be a string"); 437 + bool saved_gc_disabled = gc_disabled; 385 438 386 439 size_t len; 387 440 char *json_str = js_getstr(js, args[0], &len); 388 441 442 + gc_disabled = true; 389 443 yyjson_doc *doc = yyjson_read(json_str, len, 0); 390 - if (!doc) return js_mkerr_typed(js, JS_ERR_SYNTAX, "JSON.parse: unexpected character"); 444 + 445 + if (!doc) { 446 + gc_disabled = saved_gc_disabled; 447 + return js_mkerr_typed(js, JS_ERR_SYNTAX, "JSON.parse: unexpected character"); 448 + } 391 449 392 450 ant_value_t result = yyjson_to_jsval(js, yyjson_doc_get_root(doc)); 393 451 yyjson_doc_free(doc); 394 452 395 - if (nargs >= 2 && vtype(args[1]) == T_FUNC) { 453 + if (nargs >= 2 && is_callable(args[1])) { 396 454 ant_value_t reviver = args[1]; 397 455 ant_value_t root = js_mkobj(js); 398 456 js_set(js, root, "", result); 399 457 result = apply_reviver(js, root, "", reviver); 400 458 } 401 459 460 + gc_disabled = saved_gc_disabled; 402 461 return result; 403 462 } 404 463 ··· 424 483 ant_value_t js_json_stringify(ant_t *js, ant_value_t *args, int nargs) { 425 484 ant_value_t result; 426 485 yyjson_mut_doc *doc = NULL; 486 + bool saved_gc_disabled = gc_disabled; 427 487 428 488 json_cycle_ctx ctx = { 429 489 .js = js, ··· 435 495 436 496 char *json_str = NULL; 437 497 size_t len; 498 + ant_value_t root_holder = js_mkundef(); 438 499 439 500 if (nargs < 1) return js_mkerr(js, "JSON.stringify() requires at least 1 argument"); 440 501 int top_type = vtype(args[0]); 441 - if (top_type == T_UNDEF || top_type == T_FUNC || top_type == T_SYMBOL) return js_mkundef(); 442 502 443 503 if (nargs < 2 && top_type == T_STR) { 444 504 size_t byte_len = 0; ··· 453 513 454 514 return result; 455 515 } 516 + 517 + gc_disabled = true; 456 518 457 519 if (nargs >= 2) { 458 520 ant_value_t replacer = args[1]; 459 - if (vtype(replacer) == T_FUNC) ctx.replacer_func = replacer; 521 + if (is_callable(replacer)) ctx.replacer_func = replacer; 460 522 461 523 else if (is_special_object(replacer)) { 462 524 ant_value_t len_val = js_get(js, replacer, "length"); ··· 468 530 469 531 doc = yyjson_mut_doc_new(NULL); 470 532 if (!doc) return js_mkerr(js, "JSON.stringify() failed: out of memory"); 533 + 534 + root_holder = json_create_root_holder(js, args[0]); 535 + if (is_err(root_holder)) { 536 + result = root_holder; 537 + goto cleanup; 538 + } 539 + ctx.holder = root_holder; 471 540 472 541 yyjson_mut_val *root = ant_value_to_yyjson(js, doc, args[0], &ctx); 473 542 ··· 498 567 result = js_mkstr(js, json_str, len); 499 568 500 569 cleanup: 570 + gc_disabled = saved_gc_disabled; 501 571 free(json_str); 502 572 free(ctx.stack); 503 573 yyjson_mut_doc_free(doc);
+56 -29
src/modules/net.c
··· 52 52 net_socket_t *sockets; 53 53 struct net_server_s *next_active; 54 54 char *host; 55 + char *path; 55 56 int port; 56 57 int backlog; 57 58 int max_connections; ··· 66 67 67 68 struct net_listen_args_s { 68 69 const char *host; 70 + const char *path; 69 71 int port; 70 72 int backlog; 71 73 ant_value_t callback; ··· 251 253 252 254 if (vtype(args[0]) == T_OBJ) { 253 255 ant_value_t value = js_get(js, args[0], "path"); 254 - if (vtype(value) != T_UNDEF) { 255 - out->error = net_not_implemented(js, "IPC server listen"); 256 - return false; 256 + if (vtype(value) == T_STR) { 257 + out->path = js_getstr(js, value, NULL); 257 258 } 258 259 259 260 value = js_get(js, args[0], "port"); ··· 263 264 size_t len = 0; 264 265 out->host = js_getstr(js, value, &len); 265 266 } 267 + 266 268 value = js_get(js, args[0], "backlog"); 267 269 if (vtype(value) == T_NUM) out->backlog = (int)js_getnum(value); 268 270 if (nargs > 1 && is_callable(args[1])) out->callback = args[1]; 271 + 269 272 return true; 270 273 } 271 274 272 275 if (vtype(args[0]) == T_STR) { 273 - out->error = net_not_implemented(js, "IPC server listen"); 274 - return false; 276 + out->path = js_getstr(js, args[0], NULL); 277 + if (nargs > 1 && vtype(args[1]) == T_NUM) out->backlog = (int)js_getnum(args[1]); 278 + if (nargs > 2 && is_callable(args[2])) out->callback = args[2]; 279 + else if (nargs > 1 && is_callable(args[1])) out->callback = args[1]; 280 + return true; 275 281 } 276 282 277 283 if (is_callable(args[0])) { ··· 760 766 return server->obj; 761 767 } 762 768 769 + static ant_value_t net_server_bind_listener( 770 + ant_t *js, 771 + net_server_t *server, 772 + const net_listen_args_t *parsed, 773 + const ant_listener_callbacks_t *callbacks 774 + ) { 775 + uv_loop_t *loop = uv_default_loop(); 776 + int rc = 0; 777 + 778 + if (parsed->path) { 779 + server->path = strdup(parsed->path); 780 + if (!server->path) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); 781 + 782 + rc = ant_listener_listen_pipe( 783 + &server->listener, loop, server->path, 784 + server->backlog, 0, callbacks, server 785 + ); 786 + } else { 787 + const char *host = parsed->host; 788 + net_server_parse_host(host, &host); 789 + 790 + free(server->host); 791 + server->host = strdup(host ? host : "0.0.0.0"); 792 + if (!server->host) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); 793 + 794 + rc = ant_listener_listen_tcp( 795 + &server->listener, loop, server->host, parsed->port, 796 + server->backlog, 0, callbacks, server 797 + ); 798 + } 799 + 800 + if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); 801 + return js_mkundef(); 802 + } 803 + 763 804 static ant_value_t js_net_server_listen(ant_t *js, ant_value_t *args, int nargs) { 764 805 net_server_t *server = net_require_server(js, js_getthis(js)); 765 806 ant_listener_callbacks_t callbacks = {0}; 766 807 net_listen_args_t parsed; 767 - const char *host = NULL; 768 - int rc = 0; 769 808 770 809 if (!server) return js->thrown_value; 771 810 if (server->listening) return js_mkerr_typed(js, JS_ERR_TYPE, "Server is already listening"); 772 811 if (!net_parse_listen_args(js, args, nargs, &parsed)) return parsed.error; 773 812 774 - host = parsed.host; 775 - net_server_parse_host(host, &host); 776 - free(server->host); 777 - server->host = strdup(host ? host : "0.0.0.0"); 813 + free(server->path); 814 + server->path = NULL; 778 815 server->port = parsed.port; 779 816 server->backlog = parsed.backlog > 0 ? parsed.backlog : server->backlog; 780 817 ··· 786 823 callbacks.on_conn_close = net_server_on_conn_close; 787 824 callbacks.on_listener_close = net_server_on_listener_close; 788 825 789 - rc = ant_listener_listen_tcp( 790 - &server->listener, 791 - uv_default_loop(), 792 - server->host, 793 - parsed.port, 794 - server->backlog, 795 - 0, 796 - &callbacks, 797 - server 798 - ); 799 - if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); 826 + ant_value_t bind_result = net_server_bind_listener(js, server, &parsed, &callbacks); 827 + if (is_err(bind_result)) return bind_result; 800 828 801 829 server->port = ant_listener_port(&server->listener); 802 830 server->listening = true; ··· 813 841 net_server_t *server = net_require_server(js, js_getthis(js)); 814 842 815 843 if (!server) return js->thrown_value; 816 - 817 844 if (nargs > 0 && is_callable(args[0])) net_add_listener(js, server->obj, "close", args[0], true); 818 845 819 846 if (!server->listening && !server->closing) { ··· 833 860 static ant_value_t js_net_server_address(ant_t *js, ant_value_t *args, int nargs) { 834 861 net_server_t *server = net_require_server(js, js_getthis(js)); 835 862 ant_value_t out = js_mknull(); 863 + 836 864 if (!server) return js->thrown_value; 837 865 if (!server || !server->listening) return out; 866 + if (server->path) return js_mkstr(js, server->path, strlen(server->path)); 838 867 839 868 out = js_mkobj(js); 840 869 js_set(js, out, "port", js_mknum(server->port)); ··· 856 885 static ant_value_t js_net_server_ref(ant_t *js, ant_value_t *args, int nargs) { 857 886 net_server_t *server = net_require_server(js, js_getthis(js)); 858 887 if (!server) return js->thrown_value; 859 - if (server && !uv_is_closing((uv_handle_t *)&server->listener.handle)) 860 - uv_ref((uv_handle_t *)&server->listener.handle); 888 + if (server) ant_listener_ref(&server->listener); 861 889 return js_getthis(js); 862 890 } 863 891 864 892 static ant_value_t js_net_server_unref(ant_t *js, ant_value_t *args, int nargs) { 865 893 net_server_t *server = net_require_server(js, js_getthis(js)); 866 894 if (!server) return js->thrown_value; 867 - if (server && !uv_is_closing((uv_handle_t *)&server->listener.handle)) 868 - uv_unref((uv_handle_t *)&server->listener.handle); 895 + if (server) ant_listener_unref(&server->listener); 869 896 return js_getthis(js); 870 897 } 871 898 ··· 948 975 ant_value_t lib = js_mkobj(js); 949 976 950 977 net_init_constructors(js); 951 - 952 978 js_set(js, lib, "Server", g_net_server_ctor); 953 979 js_set(js, lib, "Socket", g_net_socket_ctor); 954 980 js_set(js, lib, "createServer", js_mkfun(js_net_createServer)); ··· 963 989 js_set(js, lib, "setDefaultAutoSelectFamilyAttemptTimeout", js_mkfun(js_net_setDefaultAutoSelectFamilyAttemptTimeout)); 964 990 js_set(js, lib, "default", lib); 965 991 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "net", 3)); 992 + 966 993 return lib; 967 994 } 968 995 ··· 977 1004 978 1005 for (server = g_active_servers; server; server = server->next_active) 979 1006 mark(js, server->obj); 980 - 1007 + 981 1008 for (socket = g_active_sockets; socket; socket = socket->next_active) { 982 1009 mark(js, socket->obj); 983 1010 if (vtype(socket->encoding) != T_UNDEF) mark(js, socket->encoding);
+523 -145
src/modules/path.c
··· 1 1 #include <compat.h> // IWYU pragma: keep 2 2 3 + #include <ctype.h> 3 4 #include <stdio.h> 4 5 #include <stdlib.h> 5 6 #include <string.h> ··· 23 24 #define PATH_DELIMITER ':' 24 25 #endif 25 26 26 - static char* normalize_separators(const char *path, size_t len) { 27 + typedef enum { 28 + PATH_STYLE_POSIX = 1, 29 + PATH_STYLE_WIN32 = 2, 30 + } path_style_t; 31 + 32 + static path_style_t path_host_style(void) { 33 + #ifdef _WIN32 34 + return PATH_STYLE_WIN32; 35 + #else 36 + return PATH_STYLE_POSIX; 37 + #endif 38 + } 39 + 40 + static path_style_t path_current_style(ant_t *js) { 41 + ant_value_t data = js_get_slot(js->current_func, SLOT_DATA); 42 + if (vtype(data) == T_NUM) { 43 + int style = (int)js_getnum(data); 44 + if (style == PATH_STYLE_WIN32) return PATH_STYLE_WIN32; 45 + if (style == PATH_STYLE_POSIX) return PATH_STYLE_POSIX; 46 + } 47 + return path_host_style(); 48 + } 49 + 50 + static bool path_style_is_windows(path_style_t style) { 51 + return style == PATH_STYLE_WIN32; 52 + } 53 + 54 + static bool path_is_sep(path_style_t style, char ch) { 55 + if (style == PATH_STYLE_WIN32) return ch == '\\' || ch == '/'; 56 + return ch == '/'; 57 + } 58 + 59 + static char path_sep_char(path_style_t style) { 60 + return style == PATH_STYLE_WIN32 ? '\\' : '/'; 61 + } 62 + 63 + static const char *path_sep_str(path_style_t style) { 64 + return style == PATH_STYLE_WIN32 ? "\\" : "/"; 65 + } 66 + 67 + static const char *path_delimiter_str(path_style_t style) { 68 + return style == PATH_STYLE_WIN32 ? ";" : ":"; 69 + } 70 + 71 + static bool path_has_drive_letter(const char *path, size_t len) { 72 + return len >= 2 && isalpha((unsigned char)path[0]) && path[1] == ':'; 73 + } 74 + 75 + static bool path_is_absolute_style(path_style_t style, const char *path, size_t len) { 76 + if (!path || len == 0) return false; 77 + if (style == PATH_STYLE_WIN32) { 78 + if (path_is_sep(style, path[0])) return true; 79 + return len >= 3 && path_has_drive_letter(path, len) && path_is_sep(style, path[2]); 80 + } 81 + return path[0] == '/'; 82 + } 83 + 84 + static char *path_normalize_separators(path_style_t style, const char *path, size_t len) { 27 85 char *result = malloc(len + 1); 28 86 if (!result) return NULL; 29 - 87 + 30 88 for (size_t i = 0; i < len; i++) { 31 - if (path[i] == '\\' || path[i] == '/') { 32 - result[i] = PATH_SEP; 33 - } else { 34 - result[i] = path[i]; 35 - } 89 + if (style == PATH_STYLE_WIN32 && (path[i] == '\\' || path[i] == '/')) 90 + result[i] = '\\'; 91 + else result[i] = path[i]; 36 92 } 93 + 37 94 result[len] = '\0'; 38 95 return result; 39 96 } 40 97 98 + static size_t path_root_length(path_style_t style, const char *path, size_t len) { 99 + if (!path || len == 0) return 0; 100 + if (style != PATH_STYLE_WIN32) return path[0] == '/' ? 1 : 0; 101 + 102 + if (len >= 2 && path_is_sep(style, path[0]) && path_is_sep(style, path[1])) { 103 + size_t i = 2; 104 + int parts = 0; 105 + 106 + while (i < len) { 107 + while (i < len && path_is_sep(style, path[i])) i++; 108 + 109 + size_t start = i; 110 + while (i < len && !path_is_sep(style, path[i])) i++; 111 + 112 + if (i == start) continue; 113 + parts++; 114 + 115 + if (parts == 2) { 116 + while (i < len && path_is_sep(style, path[i])) i++; 117 + return i; 118 + }} 119 + 120 + return 2; 121 + } 122 + 123 + if (path_has_drive_letter(path, len)) 124 + return (len >= 3 && path_is_sep(style, path[2])) ? 3 : 2; 125 + 126 + return path_is_sep(style, path[0]) ? 1 : 0; 127 + } 128 + 129 + static bool path_is_drive_relative(path_style_t style, const char *path, size_t len) { 130 + return style == PATH_STYLE_WIN32 && path_has_drive_letter(path, len) && !(len >= 3 && path_is_sep(style, path[2])); 131 + } 132 + 133 + typedef struct { 134 + char *from_norm; 135 + char *to_norm; 136 + char **from_segs; 137 + char **to_segs; 138 + size_t *from_lens; 139 + size_t *to_lens; 140 + int from_count; 141 + int to_count; 142 + size_t from_root_len; 143 + size_t to_root_len; 144 + int common; 145 + char *result; 146 + size_t result_cap; 147 + size_t pos; 148 + } path_relative_ctx_t; 149 + 150 + static size_t path_trimmed_end(path_style_t style, const char *path, size_t len) { 151 + size_t root_len = path_root_length(style, path, len); 152 + 153 + while (len > root_len && path_is_sep(style, path[len - 1])) len--; 154 + return len; 155 + } 156 + 157 + static size_t path_basename_start(path_style_t style, const char *path, size_t len) { 158 + size_t root_len = path_root_length(style, path, len); 159 + size_t end = path_trimmed_end(style, path, len); 160 + size_t start = end; 161 + 162 + while (start > root_len && !path_is_sep(style, path[start - 1])) start--; 163 + return start; 164 + } 165 + 166 + static ant_value_t path_make_string(ant_t *js, const char *src, size_t len) { 167 + return js_mkstr(js, src, len); 168 + } 169 + 41 170 static ant_value_t builtin_path_basename(ant_t *js, ant_value_t *args, int nargs) { 171 + path_style_t style = path_current_style(js); 42 172 if (nargs < 1) return js_mkerr(js, "basename() requires a path argument"); 43 173 if (vtype(args[0]) != T_STR) return js_mkerr(js, "basename() path must be a string"); 44 174 45 175 size_t path_len; 46 176 char *path = js_getstr(js, args[0], &path_len); 47 177 if (!path || path_len == 0) return js_mkstr(js, "", 0); 48 - 49 - char *path_copy = strndup(path, path_len); 50 - if (!path_copy) return js_mkerr(js, "Out of memory"); 51 - char *base = basename(path_copy); 178 + 179 + size_t end = path_trimmed_end(style, path, path_len); 180 + size_t start = path_basename_start(style, path, path_len); 181 + const char *base = path + start; 182 + size_t base_len = end > start ? end - start : 0; 52 183 53 184 if (nargs >= 2 && vtype(args[1]) == T_STR) { 54 185 size_t ext_len; 55 186 char *ext = js_getstr(js, args[1], &ext_len); 56 187 57 - if (ext && ext_len > 0) { 58 - size_t base_len = strlen(base); 59 - if (base_len >= ext_len && strcmp(base + base_len - ext_len, ext) == 0) base[base_len - ext_len] = '\0'; 188 + if (ext && ext_len > 0 && base_len >= ext_len) { 189 + if (memcmp(base + base_len - ext_len, ext, ext_len) == 0) base_len -= ext_len; 60 190 } 61 191 } 62 192 63 - ant_value_t result = js_mkstr(js, base, strlen(base)); 64 - free(path_copy); 65 - return result; 193 + return path_make_string(js, base, base_len); 66 194 } 67 195 68 196 // path.dirname(path) 69 197 static ant_value_t builtin_path_dirname(ant_t *js, ant_value_t *args, int nargs) { 198 + path_style_t style = path_current_style(js); 199 + 70 200 if (nargs < 1) return js_mkerr(js, "dirname() requires a path argument"); 71 201 if (vtype(args[0]) != T_STR) return js_mkerr(js, "dirname() path must be a string"); 72 202 73 203 size_t path_len; 74 204 char *path = js_getstr(js, args[0], &path_len); 205 + 75 206 if (!path || path_len == 0) return js_mkstr(js, ".", 1); 207 + char *normalized = path_normalize_separators(style, path, path_len); 76 208 77 - char *path_copy = strndup(path, path_len); 78 - if (!path_copy) return js_mkerr(js, "Out of memory"); 209 + size_t root_len = 0; 210 + size_t end = 0; 211 + size_t cut = 0; 79 212 80 - char *dir = dirname(path_copy); 81 - ant_value_t result = js_mkstr(js, dir, strlen(dir)); 82 - free(path_copy); 213 + if (!normalized) return js_mkerr(js, "Out of memory"); 214 + root_len = path_root_length(style, normalized, path_len); 215 + end = path_trimmed_end(style, normalized, path_len); 216 + cut = end; 217 + 218 + while (cut > root_len && !path_is_sep(style, normalized[cut - 1])) cut--; 219 + while (cut > root_len && path_is_sep(style, normalized[cut - 1])) cut--; 220 + 221 + if (cut == 0) { 222 + free(normalized); 223 + return js_mkstr(js, ".", 1); 224 + } 225 + 226 + if (cut < root_len) cut = root_len; 227 + 228 + ant_value_t result = path_make_string(js, normalized, cut); 229 + free(normalized); 83 230 return result; 84 231 } 85 232 86 233 // path.extname(path) 87 234 static ant_value_t builtin_path_extname(ant_t *js, ant_value_t *args, int nargs) { 235 + path_style_t style = path_current_style(js); 236 + 88 237 if (nargs < 1) return js_mkerr(js, "extname() requires a path argument"); 89 238 if (vtype(args[0]) != T_STR) return js_mkerr(js, "extname() path must be a string"); 90 239 91 240 size_t path_len; 92 241 char *path = js_getstr(js, args[0], &path_len); 93 242 if (!path || path_len == 0) return js_mkstr(js, "", 0); 94 - 95 - char *path_copy = strndup(path, path_len); 96 - if (!path_copy) return js_mkerr(js, "Out of memory"); 97 - 98 - char *base = basename(path_copy); 99 - char *last_dot = strrchr(base, '.'); 100 - 101 - if (last_dot && last_dot != base) { 102 - ant_value_t result = js_mkstr(js, last_dot, strlen(last_dot)); 103 - free(path_copy); 104 - return result; 243 + 244 + size_t start = path_basename_start(style, path, path_len); 245 + size_t end = path_trimmed_end(style, path, path_len); 246 + const char *base = path + start; 247 + size_t base_len = end > start ? end - start : 0; 248 + 249 + for (size_t i = base_len; i > 0; i--) { 250 + if (base[i - 1] != '.') continue; 251 + if (i - 1 == 0) break; 252 + return path_make_string(js, base + i - 1, base_len - (i - 1)); 105 253 } 106 - 107 - free(path_copy); 254 + 108 255 return js_mkstr(js, "", 0); 109 256 } 110 257 111 - static int is_dotdot(char *seg, size_t len) { 258 + static int is_dotdot(const char *seg, size_t len) { 112 259 return len == 2 && seg[0] == '.' && seg[1] == '.'; 113 260 } 114 261 115 - static char* normalize_path_full(const char *path, size_t path_len) { 262 + static char* normalize_path_full(path_style_t style, const char *path, size_t path_len) { 116 263 char *normalized = NULL; 117 264 char **segments = NULL; 118 265 size_t *seg_lens = NULL; 266 + 119 267 char *result = NULL; 268 + char sep = path_sep_char(style); 269 + 270 + size_t root_len = 0; 271 + bool drive_relative = false; 120 272 121 273 if (path_len == 0) return strdup("."); 122 274 123 - normalized = normalize_separators(path, path_len); 275 + normalized = path_normalize_separators(style, path, path_len); 124 276 if (!normalized) goto fail; 125 277 126 278 segments = malloc(path_len * sizeof(char*)); 127 279 seg_lens = malloc(path_len * sizeof(size_t)); 128 280 if (!segments || !seg_lens) goto fail; 129 281 130 - int is_absolute = (normalized[0] == PATH_SEP); 282 + root_len = path_root_length(style, normalized, path_len); 283 + drive_relative = path_is_drive_relative(style, normalized, path_len); 131 284 int seg_count = 0; 132 285 133 - char *start = is_absolute ? normalized + 1 : normalized; 286 + char *start = normalized + root_len; 134 287 char *seg_start = start; 135 288 136 289 for (char *p = start; ; p++) { 137 - if (*p != PATH_SEP && *p != '\0') continue; 290 + if (!path_is_sep(style, *p) && *p != '\0') continue; 138 291 139 292 size_t len = p - seg_start; 140 293 if (len == 0 || (len == 1 && seg_start[0] == '.')) goto next; 141 294 142 295 if (is_dotdot(seg_start, len)) { 143 - if (seg_count > 0 && !is_dotdot(segments[seg_count-1], seg_lens[seg_count-1])) 144 - seg_count--; 145 - else if (!is_absolute) 146 - goto add_segment; 296 + if (seg_count > 0 && !is_dotdot(segments[seg_count - 1], seg_lens[seg_count - 1])) seg_count--; 297 + else if (root_len == 0 || drive_relative) goto add_segment; 147 298 goto next; 148 299 } 149 300 150 - add_segment: 301 + add_segment: 151 302 segments[seg_count] = seg_start; 152 303 seg_lens[seg_count] = len; 153 304 seg_count++; 154 305 155 - next: 306 + next: 156 307 if (*p == '\0') break; 157 308 seg_start = p + 1; 158 309 } 159 310 160 - size_t result_len = is_absolute ? 1 : 0; 311 + size_t result_len = root_len; 161 312 for (int i = 0; i < seg_count; i++) { 162 313 result_len += seg_lens[i]; 163 314 if (i < seg_count - 1) result_len++; ··· 168 319 if (!result) goto fail; 169 320 170 321 size_t pos = 0; 171 - if (is_absolute) result[pos++] = PATH_SEP; 322 + if (root_len > 0) { 323 + memcpy(result + pos, normalized, root_len); 324 + pos += root_len; 325 + } 172 326 173 327 for (int i = 0; i < seg_count; i++) { 328 + if (pos > 0 && result[pos - 1] != sep && !(drive_relative && pos == root_len)) 329 + result[pos++] = sep; 174 330 memcpy(result + pos, segments[i], seg_lens[i]); 175 331 pos += seg_lens[i]; 176 - if (i < seg_count - 1) result[pos++] = PATH_SEP; 177 332 } 178 333 179 334 if (pos == 0) result[pos++] = '.'; ··· 193 348 194 349 // path.normalize(path) 195 350 static ant_value_t builtin_path_normalize(ant_t *js, ant_value_t *args, int nargs) { 351 + path_style_t style = path_current_style(js); 196 352 if (nargs < 1) return js_mkerr(js, "normalize() requires a path argument"); 197 353 if (vtype(args[0]) != T_STR) return js_mkerr(js, "normalize() path must be a string"); 198 354 ··· 200 356 char *path = js_getstr(js, args[0], &path_len); 201 357 if (!path || path_len == 0) return js_mkstr(js, ".", 1); 202 358 203 - char *result = normalize_path_full(path, path_len); 359 + char *result = normalize_path_full(style, path, path_len); 204 360 if (!result) return js_mkerr(js, "Out of memory"); 205 361 206 362 ant_value_t ret = js_mkstr(js, result, strlen(result)); ··· 210 366 211 367 // path.join(...paths) 212 368 static ant_value_t builtin_path_join(ant_t *js, ant_value_t *args, int nargs) { 369 + path_style_t style = path_current_style(js); 370 + char sep = path_sep_char(style); 213 371 if (nargs < 1) return js_mkstr(js, ".", 1); 214 372 215 373 size_t total_len = 0; ··· 225 383 int valid_segments = 0; 226 384 227 385 for (int i = 0; i < nargs; i++) { 228 - if (vtype(args[i]) == T_STR) { 229 - segments[valid_segments] = js_getstr(js, args[i], &lengths[valid_segments]); 230 - if (segments[valid_segments] && lengths[valid_segments] > 0) { 231 - total_len += lengths[valid_segments] + 1; // +1 for separator 232 - valid_segments++; 233 - } 234 - } 235 - } 386 + if (vtype(args[i]) == T_STR) { 387 + segments[valid_segments] = js_getstr(js, args[i], &lengths[valid_segments]); 388 + if (segments[valid_segments] && lengths[valid_segments] > 0) { 389 + total_len += lengths[valid_segments] + 1; // +1 for separator 390 + valid_segments++; 391 + }}} 236 392 237 393 if (valid_segments == 0) { 238 394 free(segments); ··· 249 405 250 406 size_t pos = 0; 251 407 for (int i = 0; i < valid_segments; i++) { 252 - if (i > 0 && pos > 0 && result[pos - 1] != PATH_SEP) { 253 - result[pos++] = PATH_SEP; 408 + if (i > 0 && pos > 0 && result[pos - 1] != sep) { 409 + result[pos++] = sep; 254 410 } 255 411 256 412 size_t start = 0; 257 - if (i > 0 && segments[i][0] == PATH_SEP) start = 1; 413 + if (i > 0 && path_is_sep(style, segments[i][0])) start = 1; 258 414 259 415 size_t seg_len = lengths[i]; 260 - if (seg_len > start && segments[i][seg_len - 1] == PATH_SEP) seg_len--; 416 + while (seg_len > start && path_is_sep(style, segments[i][seg_len - 1])) seg_len--; 261 417 262 418 if (seg_len > start) { 263 419 memcpy(result + pos, segments[i] + start, seg_len - start); ··· 267 423 268 424 result[pos] = '\0'; 269 425 270 - char *normalized = normalize_path_full(result, pos); 426 + char *normalized = normalize_path_full(style, result, pos); 271 427 free(result); 272 428 free(segments); 273 429 free(lengths); ··· 281 437 282 438 // path.resolve(...paths) 283 439 static ant_value_t builtin_path_resolve(ant_t *js, ant_value_t *args, int nargs) { 440 + path_style_t style = path_current_style(js); 441 + 284 442 char cwd[PATH_MAX]; 285 - if (getcwd(cwd, sizeof(cwd)) == NULL) { 443 + char *cwd_norm = NULL; 444 + char *joined = NULL; 445 + char *normalized = NULL; 446 + char drive_prefix[3] = {0}; 447 + 448 + bool saw_absolute = false; 449 + char sep = path_sep_char(style); 450 + 451 + if (getcwd(cwd, sizeof(cwd)) == NULL) 286 452 return js_mkerr(js, "Failed to get current working directory"); 287 - } 288 - 289 - char *result = strdup(cwd); 290 - if (!result) return js_mkerr(js, "Out of memory"); 291 - 292 - for (int i = 0; i < nargs; i++) { 453 + 454 + cwd_norm = path_normalize_separators(style, cwd, strlen(cwd)); 455 + if (!cwd_norm) return js_mkerr(js, "Out of memory"); 456 + 457 + for (int i = nargs - 1; i >= 0; i--) { 458 + char *next = NULL; 459 + size_t seg_len = 0; 460 + size_t joined_len = 0; 461 + size_t next_len = 0; 462 + char *segment = NULL; 463 + size_t prefix_len = 0; 464 + 293 465 if (vtype(args[i]) != T_STR) continue; 294 - 295 - size_t seg_len; 296 - char *segment = js_getstr(js, args[i], &seg_len); 466 + 467 + segment = js_getstr(js, args[i], &seg_len); 297 468 if (!segment || seg_len == 0) continue; 298 - 299 - if (segment[0] == PATH_SEP) { 300 - free(result); 301 - result = strndup(segment, seg_len); 302 - if (!result) return js_mkerr(js, "Out of memory"); 469 + 470 + if (style == PATH_STYLE_WIN32 && path_is_drive_relative(style, segment, seg_len)) { 471 + if (drive_prefix[0] == '\0') { 472 + drive_prefix[0] = segment[0]; 473 + drive_prefix[1] = ':'; 474 + drive_prefix[2] = '\0'; 475 + } 476 + prefix_len = 2; 477 + } 478 + 479 + if (!joined) { 480 + joined = strndup(segment + prefix_len, seg_len - prefix_len); 481 + if (!joined) { 482 + free(cwd_norm); 483 + return js_mkerr(js, "Out of memory"); 484 + } 303 485 } else { 304 - size_t result_len = strlen(result); 305 - size_t new_len = result_len + seg_len + 2; 306 - char *new_result = malloc(new_len); 307 - if (!new_result) { 308 - free(result); 486 + joined_len = strlen(joined); 487 + next_len = (seg_len - prefix_len) + 1 + joined_len + 1; 488 + next = malloc(next_len); 489 + if (!next) { 490 + free(cwd_norm); 491 + free(joined); 309 492 return js_mkerr(js, "Out of memory"); 310 493 } 311 - 312 - snprintf(new_result, new_len, "%s%c%.*s", result, PATH_SEP, (int)seg_len, segment); 313 - free(result); 314 - result = new_result; 494 + 495 + snprintf(next, next_len, "%.*s%c%s", (int)(seg_len - prefix_len), segment + prefix_len, sep, joined); 496 + free(joined); 497 + joined = next; 498 + } 499 + 500 + if (path_is_absolute_style(style, segment, seg_len)) { 501 + saw_absolute = true; 502 + break; 315 503 } 316 504 } 317 - 318 - ant_value_t ret = js_mkstr(js, result, strlen(result)); 319 - free(result); 505 + 506 + if (!joined) { 507 + if (style == PATH_STYLE_WIN32 && drive_prefix[0] != '\0') { 508 + size_t cwd_len = strlen(cwd_norm); 509 + joined = malloc(2 + cwd_len + 1); 510 + if (!joined) { 511 + free(cwd_norm); 512 + return js_mkerr(js, "Out of memory"); 513 + } 514 + snprintf(joined, 2 + cwd_len + 1, "%s%s", drive_prefix, cwd_norm); 515 + } else { 516 + joined = strdup(cwd_norm); 517 + if (!joined) { 518 + free(cwd_norm); 519 + return js_mkerr(js, "Out of memory"); 520 + } 521 + } 522 + } else if (!saw_absolute) { 523 + size_t cwd_len = strlen(cwd_norm); 524 + size_t joined_len = strlen(joined); 525 + size_t prefix_len = (style == PATH_STYLE_WIN32 && drive_prefix[0] != '\0') ? 2 : 0; 526 + char *next = malloc(prefix_len + cwd_len + 1 + joined_len + 1); 527 + if (!next) { 528 + free(cwd_norm); 529 + free(joined); 530 + return js_mkerr(js, "Out of memory"); 531 + } 532 + 533 + if (prefix_len > 0) 534 + snprintf(next, prefix_len + cwd_len + 1 + joined_len + 1, "%s%s%c%s", drive_prefix, cwd_norm, sep, joined); 535 + else snprintf(next, prefix_len + cwd_len + 1 + joined_len + 1, "%s%c%s", cwd_norm, sep, joined); 536 + free(joined); 537 + joined = next; 538 + } 539 + 540 + normalized = normalize_path_full(style, joined, strlen(joined)); 541 + free(cwd_norm); 542 + free(joined); 543 + if (!normalized) return js_mkerr(js, "Out of memory"); 544 + 545 + ant_value_t ret = js_mkstr(js, normalized, strlen(normalized)); 546 + free(normalized); 320 547 return ret; 321 548 } 322 549 323 550 // path.relative(from, to) 324 551 static ant_value_t builtin_path_relative(ant_t *js, ant_value_t *args, int nargs) { 552 + path_style_t style = path_current_style(js); 553 + path_relative_ctx_t rel = {0}; 554 + 555 + char sep = path_sep_char(style); 325 556 if (nargs < 2) return js_mkerr(js, "relative() requires from and to arguments"); 326 557 if (vtype(args[0]) != T_STR) return js_mkerr(js, "relative() from must be a string"); 327 558 if (vtype(args[1]) != T_STR) return js_mkerr(js, "relative() to must be a string"); ··· 332 563 333 564 if (!from || !to) return js_mkerr(js, "Failed to get arguments"); 334 565 if (from_len == to_len && strncmp(from, to, from_len) == 0) return js_mkstr(js, "", 0); 335 - 336 - return js_mkstr(js, to, to_len); 566 + 567 + rel.from_norm = normalize_path_full(style, from, from_len); 568 + rel.to_norm = normalize_path_full(style, to, to_len); 569 + if (!rel.from_norm || !rel.to_norm) goto relative_fail; 570 + 571 + rel.from_root_len = path_root_length(style, rel.from_norm, strlen(rel.from_norm)); 572 + rel.to_root_len = path_root_length(style, rel.to_norm, strlen(rel.to_norm)); 573 + 574 + if (style == PATH_STYLE_WIN32) { 575 + if (rel.from_root_len != rel.to_root_len || strncasecmp(rel.from_norm, rel.to_norm, rel.from_root_len) != 0) { 576 + ant_value_t out = js_mkstr(js, rel.to_norm, strlen(rel.to_norm)); 577 + free(rel.from_norm); 578 + free(rel.to_norm); 579 + return out; 580 + } 581 + } else if (rel.from_root_len != rel.to_root_len || strncmp(rel.from_norm, rel.to_norm, rel.from_root_len) != 0) { 582 + ant_value_t out = js_mkstr(js, rel.to_norm, strlen(rel.to_norm)); 583 + free(rel.from_norm); 584 + free(rel.to_norm); 585 + return out; 586 + } 587 + 588 + rel.from_segs = malloc(strlen(rel.from_norm) * sizeof(char *)); 589 + rel.to_segs = malloc(strlen(rel.to_norm) * sizeof(char *)); 590 + rel.from_lens = malloc(strlen(rel.from_norm) * sizeof(size_t)); 591 + rel.to_lens = malloc(strlen(rel.to_norm) * sizeof(size_t)); 592 + if (!rel.from_segs || !rel.to_segs || !rel.from_lens || !rel.to_lens) goto relative_fail; 593 + 594 + for (size_t i = rel.from_root_len, start = rel.from_root_len;; i++) { 595 + if (rel.from_norm[i] != '\0' && !path_is_sep(style, rel.from_norm[i])) continue; 596 + if (i > start) { 597 + rel.from_segs[rel.from_count] = rel.from_norm + start; 598 + rel.from_lens[rel.from_count++] = i - start; 599 + } 600 + if (rel.from_norm[i] == '\0') break; 601 + start = i + 1; 602 + } 603 + 604 + for (size_t i = rel.to_root_len, start = rel.to_root_len;; i++) { 605 + if (rel.to_norm[i] != '\0' && !path_is_sep(style, rel.to_norm[i])) continue; 606 + if (i > start) { 607 + rel.to_segs[rel.to_count] = rel.to_norm + start; 608 + rel.to_lens[rel.to_count++] = i - start; 609 + } 610 + if (rel.to_norm[i] == '\0') break; 611 + start = i + 1; 612 + } 613 + 614 + while (rel.common < rel.from_count && rel.common < rel.to_count) { 615 + bool equal = false; 616 + if (rel.from_lens[rel.common] == rel.to_lens[rel.common]) { 617 + equal = style == PATH_STYLE_WIN32 618 + ? strncasecmp(rel.from_segs[rel.common], rel.to_segs[rel.common], rel.from_lens[rel.common]) == 0 619 + : strncmp(rel.from_segs[rel.common], rel.to_segs[rel.common], rel.from_lens[rel.common]) == 0; 620 + } 621 + if (!equal) break; 622 + rel.common++; 623 + } 624 + 625 + rel.result_cap = strlen(rel.to_norm) + (size_t)(rel.from_count - rel.common) * 3 + 2; 626 + rel.result = malloc(rel.result_cap); 627 + if (!rel.result) goto relative_fail; 628 + 629 + for (int i = rel.common; i < rel.from_count; i++) { 630 + if (rel.pos > 0) rel.result[rel.pos++] = sep; 631 + rel.result[rel.pos++] = '.'; 632 + rel.result[rel.pos++] = '.'; 633 + } 634 + 635 + for (int i = rel.common; i < rel.to_count; i++) { 636 + if (rel.pos > 0) rel.result[rel.pos++] = sep; 637 + memcpy(rel.result + rel.pos, rel.to_segs[i], rel.to_lens[i]); 638 + rel.pos += rel.to_lens[i]; 639 + } 640 + 641 + if (rel.pos == 0) rel.result[rel.pos++] = '.'; 642 + rel.result[rel.pos] = '\0'; 643 + 644 + ant_value_t out = js_mkstr(js, rel.result, rel.pos); 645 + free(rel.result); 646 + free(rel.from_norm); 647 + free(rel.to_norm); 648 + free(rel.from_segs); 649 + free(rel.to_segs); 650 + free(rel.from_lens); 651 + free(rel.to_lens); 652 + return out; 653 + 654 + relative_fail: 655 + free(rel.result); 656 + free(rel.from_norm); 657 + free(rel.to_norm); 658 + free(rel.from_segs); 659 + free(rel.to_segs); 660 + free(rel.from_lens); 661 + free(rel.to_lens); 662 + return js_mkerr(js, "Out of memory"); 337 663 } 338 664 339 665 // path.isAbsolute(path) 340 666 static ant_value_t builtin_path_isAbsolute(ant_t *js, ant_value_t *args, int nargs) { 667 + path_style_t style = path_current_style(js); 341 668 if (nargs < 1) return js_mkerr(js, "isAbsolute() requires a path argument"); 342 669 if (vtype(args[0]) != T_STR) return js_mkerr(js, "isAbsolute() path must be a string"); 343 670 344 671 size_t path_len; 345 672 char *path = js_getstr(js, args[0], &path_len); 346 673 if (!path || path_len == 0) return js_false; 347 - 348 - #ifdef _WIN32 349 - if (path_len >= 2 && path[1] == ':') return js_true; 350 - if (path_len >= 2 && path[0] == '\\' && path[1] == '\\') return js_true; 351 - return js_false; 352 - #else 353 - return js_bool(path[0] == '/'); 354 - #endif 674 + 675 + return js_bool(path_is_absolute_style(style, path, path_len)); 355 676 } 356 677 357 678 // path.parse(path) 358 679 static ant_value_t builtin_path_parse(ant_t *js, ant_value_t *args, int nargs) { 680 + path_style_t style = path_current_style(js); 359 681 if (nargs < 1) return js_mkerr(js, "parse() requires a path argument"); 360 682 if (vtype(args[0]) != T_STR) return js_mkerr(js, "parse() path must be a string"); 361 683 ··· 364 686 if (!path) return js_mkerr(js, "Failed to get path string"); 365 687 366 688 ant_value_t result = js_mkobj(js); 367 - 368 - if (path_len > 0 && path[0] == PATH_SEP) { 369 - js_set(js, result, "root", js_mkstr(js, PATH_SEP_STR, 1)); 370 - } else { 371 - js_set(js, result, "root", js_mkstr(js, "", 0)); 689 + char *normalized = path_normalize_separators(style, path, path_len); 690 + size_t root_len = 0; 691 + size_t start = 0; 692 + size_t end = 0; 693 + const char *base = NULL; 694 + size_t base_len = 0; 695 + if (!normalized) return js_mkerr(js, "Out of memory"); 696 + 697 + root_len = path_root_length(style, normalized, path_len); 698 + js_set(js, result, "root", path_make_string(js, normalized, root_len)); 699 + 700 + end = path_trimmed_end(style, normalized, path_len); 701 + start = path_basename_start(style, normalized, path_len); 702 + base = normalized + start; 703 + base_len = end > start ? end - start : 0; 704 + 705 + if (start == 0) js_set(js, result, "dir", js_mkstr(js, ".", 1)); 706 + else { 707 + size_t dir_len = start; 708 + while (dir_len > root_len && path_is_sep(style, normalized[dir_len - 1])) dir_len--; 709 + js_set(js, result, "dir", path_make_string(js, normalized, dir_len)); 372 710 } 373 - 374 - char *path_copy = strndup(path, path_len); 375 - if (!path_copy) return js_mkerr(js, "Out of memory"); 376 - char *dir = dirname(path_copy); 377 - js_set(js, result, "dir", js_mkstr(js, dir, strlen(dir))); 378 - free(path_copy); 379 - 380 - path_copy = strndup(path, path_len); 381 - if (!path_copy) return js_mkerr(js, "Out of memory"); 382 - char *base = basename(path_copy); 383 - js_set(js, result, "base", js_mkstr(js, base, strlen(base))); 384 - 385 - char *last_dot = strrchr(base, '.'); 386 - if (last_dot && last_dot != base && *(last_dot + 1) != '\0') { 387 - js_set(js, result, "ext", js_mkstr(js, last_dot, strlen(last_dot))); 388 - size_t name_len = last_dot - base; 389 - js_set(js, result, "name", js_mkstr(js, base, name_len)); 390 - } else { 391 - js_set(js, result, "ext", js_mkstr(js, "", 0)); 392 - js_set(js, result, "name", js_mkstr(js, base, strlen(base))); 711 + 712 + js_set(js, result, "base", path_make_string(js, base, base_len)); 713 + 714 + for (size_t i = base_len; i > 0; i--) { 715 + if (base[i - 1] != '.') continue; 716 + if (i - 1 == 0) break; 717 + js_set(js, result, "ext", path_make_string(js, base + i - 1, base_len - (i - 1))); 718 + js_set(js, result, "name", path_make_string(js, base, i - 1)); 719 + free(normalized); 720 + return result; 393 721 } 394 - 395 - free(path_copy); 722 + 723 + js_set(js, result, "ext", js_mkstr(js, "", 0)); 724 + js_set(js, result, "name", path_make_string(js, base, base_len)); 725 + free(normalized); 396 726 return result; 397 727 } 398 728 399 729 // path.format(pathObject) 400 730 static ant_value_t builtin_path_format(ant_t *js, ant_value_t *args, int nargs) { 731 + path_style_t style = path_current_style(js); 732 + 733 + char sep = path_sep_char(style); 401 734 if (nargs < 1) return js_mkerr(js, "format() requires a path object argument"); 402 735 if (!is_special_object(args[0])) return js_mkerr(js, "format() argument must be an object"); 403 736 ··· 418 751 if (str && len > 0 && pos + len < PATH_MAX) { 419 752 memcpy(result + pos, str, len); 420 753 pos += len; 421 - if (result[pos - 1] != PATH_SEP && pos < PATH_MAX - 1) { 422 - result[pos++] = PATH_SEP; 754 + if (result[pos - 1] != sep && pos < PATH_MAX - 1) { 755 + result[pos++] = sep; 423 756 } 424 757 } 425 758 } else if (vtype(root_val) == T_STR) { ··· 460 793 return js_mkstr(js, result, pos); 461 794 } 462 795 796 + typedef struct { 797 + ant_value_t basename; 798 + ant_value_t dirname; 799 + ant_value_t extname; 800 + ant_value_t join; 801 + ant_value_t normalize; 802 + ant_value_t resolve; 803 + ant_value_t relative; 804 + ant_value_t isAbsolute; 805 + ant_value_t parse; 806 + ant_value_t format; 807 + } path_api_t; 808 + 809 + static path_api_t path_build_api_for_style(ant_t *js, path_style_t style) { 810 + ant_value_t style_value = js_mknum((double)style); 811 + return (path_api_t){ 812 + .basename = js_heavy_mkfun(js, builtin_path_basename, style_value), 813 + .dirname = js_heavy_mkfun(js, builtin_path_dirname, style_value), 814 + .extname = js_heavy_mkfun(js, builtin_path_extname, style_value), 815 + .join = js_heavy_mkfun(js, builtin_path_join, style_value), 816 + .normalize = js_heavy_mkfun(js, builtin_path_normalize, style_value), 817 + .resolve = js_heavy_mkfun(js, builtin_path_resolve, style_value), 818 + .relative = js_heavy_mkfun(js, builtin_path_relative, style_value), 819 + .isAbsolute = js_heavy_mkfun(js, builtin_path_isAbsolute, style_value), 820 + .parse = js_heavy_mkfun(js, builtin_path_parse, style_value), 821 + .format = js_heavy_mkfun(js, builtin_path_format, style_value) 822 + };} 823 + 824 + static void path_apply_api(ant_t *js, ant_value_t target, const path_api_t *api) { 825 + js_set(js, target, "basename", api->basename); 826 + js_set(js, target, "dirname", api->dirname); 827 + js_set(js, target, "extname", api->extname); 828 + js_set(js, target, "join", api->join); 829 + js_set(js, target, "normalize", api->normalize); 830 + js_set(js, target, "resolve", api->resolve); 831 + js_set(js, target, "relative", api->relative); 832 + js_set(js, target, "isAbsolute", api->isAbsolute); 833 + js_set(js, target, "parse", api->parse); 834 + js_set(js, target, "format", api->format); 835 + } 836 + 837 + static ant_value_t path_make_variant(ant_t *js, const path_api_t *api, path_style_t style) { 838 + ant_value_t variant = js_mkobj(js); 839 + path_apply_api(js, variant, api); 840 + 841 + js_set(js, variant, "sep", js_mkstr(js, path_sep_str(style), 1)); 842 + js_set(js, variant, "delimiter", js_mkstr(js, path_delimiter_str(style), 1)); 843 + 844 + return variant; 845 + } 846 + 463 847 ant_value_t path_library(ant_t *js) { 464 - ant_value_t lib = js_mkobj(js); 848 + path_api_t api = path_build_api_for_style(js, path_host_style()); 849 + path_api_t posix_api = path_build_api_for_style(js, PATH_STYLE_POSIX); 850 + path_api_t win32_api = path_build_api_for_style(js, PATH_STYLE_WIN32); 465 851 466 - js_set(js, lib, "basename", js_mkfun(builtin_path_basename)); 467 - js_set(js, lib, "dirname", js_mkfun(builtin_path_dirname)); 468 - js_set(js, lib, "extname", js_mkfun(builtin_path_extname)); 469 - js_set(js, lib, "join", js_mkfun(builtin_path_join)); 470 - js_set(js, lib, "normalize", js_mkfun(builtin_path_normalize)); 471 - js_set(js, lib, "resolve", js_mkfun(builtin_path_resolve)); 472 - js_set(js, lib, "relative", js_mkfun(builtin_path_relative)); 473 - js_set(js, lib, "isAbsolute", js_mkfun(builtin_path_isAbsolute)); 474 - js_set(js, lib, "parse", js_mkfun(builtin_path_parse)); 475 - js_set(js, lib, "format", js_mkfun(builtin_path_format)); 476 - 477 - js_set(js, lib, "sep", js_mkstr(js, PATH_SEP_STR, 1)); 478 - char delimiter_str[2] = {PATH_DELIMITER, '\0'}; 479 - js_set(js, lib, "delimiter", js_mkstr(js, delimiter_str, 1)); 852 + ant_value_t lib = path_make_variant(js, &api, path_host_style()); 853 + ant_value_t posix = path_make_variant(js, &posix_api, PATH_STYLE_POSIX); 854 + ant_value_t win32 = path_make_variant(js, &win32_api, PATH_STYLE_WIN32); 855 + 856 + js_set(js, lib, "posix", posix); 857 + js_set(js, lib, "win32", win32); 480 858 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "path", 4)); 481 859 482 860 return lib;
+19 -22
src/modules/request.c
··· 46 46 47 47 if (vtype(signal) != T_UNDEF) return signal; 48 48 signal = abort_signal_create_dependent(js, js_mkundef()); 49 + 49 50 if (is_err(signal)) return signal; 51 + ant_value_t abort_reason = js_get_slot(obj, SLOT_REQUEST_ABORT_REASON); 52 + 53 + if (vtype(abort_reason) != T_UNDEF) signal_do_abort(js, signal, abort_reason); 50 54 js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, signal); 55 + 51 56 return signal; 52 57 } 53 58 ··· 67 72 free(d); 68 73 } 69 74 70 - static request_data_t *data_new(void) { 71 - request_data_t *d = calloc(1, sizeof(request_data_t)); 72 - if (!d) return NULL; 73 - d->method = strdup("GET"); 74 - d->referrer = strdup("client"); 75 - d->referrer_policy = strdup(""); 76 - d->mode = strdup("cors"); 77 - d->credentials = strdup("same-origin"); 78 - d->cache = strdup("default"); 79 - d->redirect = strdup("follow"); 80 - d->integrity = strdup(""); 81 - if (!d->method || !d->referrer || !d->referrer_policy || 82 - !d->mode || !d->credentials || !d->cache || !d->redirect || !d->integrity) { 83 - data_free(d); 84 - return NULL; 85 - } 86 - return d; 87 - } 88 - 89 - static request_data_t *data_new_server(const char *method) { 75 + static request_data_t *data_new_with(const char *method, const char *mode) { 90 76 request_data_t *d = calloc(1, sizeof(request_data_t)); 91 77 if (!d) return NULL; 92 78 93 79 d->method = strdup(method ? method : "GET"); 94 80 d->referrer = strdup("client"); 95 81 d->referrer_policy = strdup(""); 96 - d->mode = strdup("same-origin"); 82 + d->mode = strdup(mode); 97 83 d->credentials = strdup("same-origin"); 98 84 d->cache = strdup("default"); 99 85 d->redirect = strdup("follow"); ··· 112 98 return d; 113 99 } 114 100 101 + static request_data_t *data_new(void) { return data_new_with("GET", "cors"); } 102 + static request_data_t *data_new_server(const char *method) { return data_new_with(method, "same-origin"); } 103 + 115 104 static ant_value_t request_create_object(ant_t *js, request_data_t *req, ant_value_t headers_obj, bool create_signal) { 116 105 ant_value_t obj = js_mkobj(js); 117 106 ant_value_t hdrs = is_object_type(headers_obj) ··· 130 119 131 120 headers_apply_guard(hdrs); 132 121 js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, hdrs); 122 + js_set_slot(obj, SLOT_REQUEST_ABORT_REASON, js_mkundef()); 123 + 133 124 js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, create_signal 134 125 ? abort_signal_create_dependent(js, js_mkundef()) 135 126 : js_mkundef() ··· 891 882 js_set_slot(obj, SLOT_DATA, ANT_PTR(nd)); 892 883 893 884 js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, new_headers); 885 + js_set_slot(obj, SLOT_REQUEST_ABORT_REASON, js_mkundef()); 894 886 js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, new_signal); 895 887 896 888 ant_value_t src_stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM); ··· 928 920 *out_src = NULL; 929 921 *out_input_signal = js_mkundef(); 930 922 931 - if (vtype(input) == T_OBJ) src = get_data(input); 923 + if ( 924 + vtype(input) == T_OBJ && 925 + js_check_brand(input, BRAND_REQUEST) 926 + ) src = get_data(input); 932 927 933 928 if (!src) { 934 929 size_t ulen = 0; ··· 1244 1239 1245 1240 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST)); 1246 1241 js_set_slot(obj, SLOT_DATA, ANT_PTR(req)); 1242 + js_set_slot(obj, SLOT_REQUEST_ABORT_REASON, js_mkundef()); 1247 1243 1248 1244 signal = abort_signal_create_dependent(js, input_signal); 1249 1245 if (is_err(signal)) { data_free(req); return signal; } ··· 1321 1317 js_set_proto_init(obj, g_request_proto); 1322 1318 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST)); 1323 1319 js_set_slot(obj, SLOT_DATA, ANT_PTR(req)); 1320 + js_set_slot(obj, SLOT_REQUEST_ABORT_REASON, js_mkundef()); 1324 1321 1325 1322 signal = abort_signal_create_dependent(js, input_signal); 1326 1323 if (is_err(signal)) { data_free(req); return signal; }
+91 -8
src/modules/server.c
··· 21 21 #include "http/http1_writer.h" 22 22 23 23 #include "modules/assert.h" 24 + #include "modules/abort.h" 24 25 #include "modules/buffer.h" 25 26 #include "modules/headers.h" 26 27 #include "modules/request.h" 27 28 #include "modules/response.h" 28 29 #include "modules/server.h" 30 + #include "modules/timer.h" 31 + #include "modules/domexception.h" 29 32 30 33 typedef struct server_runtime_s server_runtime_t; 31 34 typedef struct server_request_s server_request_t; ··· 98 101 server_request_t *requests; 99 102 100 103 char *hostname; 104 + char *unix_path; 105 + 101 106 uint64_t request_timeout_ms; 102 107 uint64_t idle_timeout_ms; 103 108 ··· 285 290 else result = sv_vm_call(js->vm, js, server->fetch_fn, server->export_obj, args, 2, NULL, false); 286 291 js->this_val = saved_this; 287 292 return result; 293 + } 294 + 295 + static ant_value_t server_abort_signal_task(ant_t *js, ant_value_t *args, int nargs) { 296 + ant_value_t payload = js_get_slot(js->current_func, SLOT_DATA); 297 + ant_value_t signal = 0; ant_value_t reason = 0; 298 + if (vtype(payload) != T_OBJ) return js_mkundef(); 299 + 300 + signal = js_get(js, payload, "signal"); 301 + reason = js_get(js, payload, "reason"); 302 + 303 + if (abort_signal_is_signal(signal) && !abort_signal_is_aborted(signal)) 304 + signal_do_abort(js, signal, reason); 305 + 306 + return js_mkundef(); 307 + } 308 + 309 + static void server_queue_abort_signal(ant_t *js, ant_value_t signal, ant_value_t reason) { 310 + ant_value_t callback = 0; 311 + ant_value_t payload = 0; 312 + 313 + if (!abort_signal_is_signal(signal) || abort_signal_is_aborted(signal)) return; 314 + payload = js_mkobj(js); 315 + if (is_err(payload)) return; 316 + 317 + js_set(js, payload, "signal", signal); 318 + js_set(js, payload, "reason", reason); 319 + callback = js_heavy_mkfun(js, server_abort_signal_task, payload); 320 + 321 + queue_microtask(js, callback); 322 + } 323 + 324 + static void server_abort_request(server_request_t *req, const char *message) { 325 + ant_t *js = NULL; 326 + 327 + ant_value_t signal = 0; ant_value_t reason = 0; ant_value_t abort_reason = 0; 328 + if (!req || !req->server || !is_object_type(req->request_obj)) return; 329 + 330 + js = req->server->js; 331 + abort_reason = js_get_slot(req->request_obj, SLOT_REQUEST_ABORT_REASON); 332 + if (vtype(abort_reason) != T_UNDEF) return; 333 + 334 + reason = make_dom_exception(js, 335 + message ? message : "The request was aborted", "AbortError" 336 + ); 337 + 338 + js_set_slot_wb(js, req->request_obj, SLOT_REQUEST_ABORT_REASON, reason); 339 + signal = js_get_slot(req->request_obj, SLOT_REQUEST_SIGNAL); 340 + 341 + if (!abort_signal_is_signal(signal) || abort_signal_is_aborted(signal)) return; 342 + server_queue_abort_signal(js, signal, reason); 288 343 } 289 344 290 345 static bool server_response_chunk(server_request_t *req, ant_value_t value, const uint8_t **out, size_t *len) { ··· 853 908 if (!uv_is_closing((uv_handle_t *)&cs->drain_timer)) 854 909 uv_close((uv_handle_t *)&cs->drain_timer, server_on_drain_timer_close); 855 910 if (cs->active_req) { 911 + server_abort_request(cs->active_req, "Client disconnected"); 856 912 cs->active_req->conn = NULL; 857 913 cs->active_req = NULL; 858 914 server_request_release(&cs->request); ··· 976 1032 .export_obj = default_export, 977 1033 .fetch_fn = js_get(js, default_export, "fetch"), 978 1034 .hostname = strdup("0.0.0.0"), 1035 + .unix_path = NULL, 979 1036 .port = 3000, 980 1037 .request_timeout_ms = 5000, 981 1038 .idle_timeout_ms = 5000, ··· 989 1046 990 1047 unix_v = js_get(js, default_export, "unix"); 991 1048 tls_v = js_get(js, default_export, "tls"); 1049 + 992 1050 if (vtype(unix_v) != T_UNDEF && vtype(unix_v) != T_NULL) { 993 - free(server->hostname); 994 - free(server); 995 - return js_mkerr_typed(js, JS_ERR_TYPE, "unix sockets are not implemented yet"); 1051 + if (vtype(unix_v) != T_STR) { 1052 + free(server->hostname); 1053 + free(server); 1054 + return js_mkerr_typed(js, JS_ERR_TYPE, "server unix must be a string"); 1055 + } 1056 + 1057 + server->unix_path = strdup(js_getstr(js, unix_v, NULL)); 1058 + if (!server->unix_path) { 1059 + free(server->hostname); 1060 + free(server); 1061 + return js_mkerr(js, "out of memory"); 1062 + } 996 1063 } 997 1064 998 1065 if (vtype(tls_v) != T_UNDEF && vtype(tls_v) != T_NULL) { 1066 + free(server->unix_path); 999 1067 free(server->hostname); 1000 1068 free(server); 1001 1069 return js_mkerr_typed(js, JS_ERR_TYPE, "tls server config is not implemented yet"); ··· 1007 1075 1008 1076 if (vtype(port_v) != T_UNDEF && vtype(port_v) != T_NULL) { 1009 1077 if (vtype(port_v) != T_NUM) { 1078 + free(server->unix_path); 1010 1079 free(server->hostname); 1011 1080 free(server); 1012 1081 return js_mkerr_typed(js, JS_ERR_TYPE, "server port must be a number"); 1013 1082 } 1014 1083 server->port = (int)js_getnum(port_v); 1015 1084 if (server->port < 0 || server->port > 65535) { 1085 + free(server->unix_path); 1016 1086 free(server->hostname); 1017 1087 free(server); 1018 1088 return js_mkerr_typed(js, JS_ERR_RANGE, "server port must be between 0 and 65535"); ··· 1022 1092 if (vtype(hostname_v) != T_UNDEF && vtype(hostname_v) != T_NULL) { 1023 1093 char *next_hostname = NULL; 1024 1094 if (vtype(hostname_v) != T_STR) { 1095 + free(server->unix_path); 1025 1096 free(server->hostname); 1026 1097 free(server); 1027 1098 return js_mkerr_typed(js, JS_ERR_TYPE, "server hostname must be a string"); 1028 1099 } 1029 1100 next_hostname = strdup(js_getstr(js, hostname_v, NULL)); 1030 1101 if (!next_hostname) { 1102 + free(server->unix_path); 1031 1103 free(server->hostname); 1032 1104 free(server); 1033 1105 return js_mkerr(js, "out of memory"); ··· 1039 1111 if (vtype(timeout_v) != T_UNDEF && vtype(timeout_v) != T_NULL) { 1040 1112 double timeout = 0; 1041 1113 if (vtype(timeout_v) != T_NUM) { 1114 + free(server->unix_path); 1042 1115 free(server->hostname); 1043 1116 free(server); 1044 1117 return js_mkerr_typed(js, JS_ERR_TYPE, "server idleTimeout must be a number"); 1045 1118 } 1046 1119 timeout = js_getnum(timeout_v); 1047 1120 if (timeout < 0) { 1121 + free(server->unix_path); 1048 1122 free(server->hostname); 1049 1123 free(server); 1050 1124 return js_mkerr_typed(js, JS_ERR_RANGE, "server idleTimeout must be >= 0"); ··· 1064 1138 callbacks.on_conn_close = server_on_conn_close; 1065 1139 callbacks.on_listener_close = server_on_listener_close; 1066 1140 1067 - rc = ant_listener_listen_tcp( 1068 - &server->listener, server->loop, 1069 - server->hostname, server->port, 1070 - 128, server->idle_timeout_ms, &callbacks, server 1071 - ); 1141 + if (server->unix_path) { 1142 + rc = ant_listener_listen_pipe( 1143 + &server->listener, server->loop, 1144 + server->unix_path, 1145 + 128, server->idle_timeout_ms, &callbacks, server 1146 + ); 1147 + } else { 1148 + rc = ant_listener_listen_tcp( 1149 + &server->listener, server->loop, 1150 + server->hostname, server->port, 1151 + 128, server->idle_timeout_ms, &callbacks, server 1152 + ); 1153 + } 1072 1154 1073 1155 if (rc != 0) { 1156 + free(server->unix_path); 1074 1157 free(server->hostname); 1075 1158 free(server); 1076 1159 return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc));
+14 -4
src/modules/structured-clone.c
··· 208 208 209 209 map_entry_t *ne = ant_calloc(sizeof(map_entry_t)); 210 210 if (!ne) return js_mkerr(js, "out of memory"); 211 - ne->key = strdup(e->key); 211 + 212 + ne->key = (unsigned char *)strdup((const char *)e->key); 212 213 ne->key_val = e->key_val; 213 214 ne->value = vc; 214 - HASH_ADD_STR(*new_head, key, ne); 215 + 216 + HASH_ADD_KEYPTR( 217 + hh, *new_head, ne->key, 218 + (unsigned)strlen((const char *)ne->key), ne 219 + ); 215 220 }} 216 221 217 222 return clone; ··· 239 244 240 245 set_entry_t *ne = ant_calloc(sizeof(set_entry_t)); 241 246 if (!ne) return js_mkerr(js, "out of memory"); 242 - ne->key = strdup(e->key); 247 + 248 + ne->key = (unsigned char *)strdup((const char *)e->key); 243 249 ne->value = vc; 244 - HASH_ADD_STR(*new_head, key, ne); 250 + 251 + HASH_ADD_KEYPTR( 252 + hh, *new_head, ne->key, 253 + (unsigned)strlen((const char *)ne->key), ne 254 + ); 245 255 }} 246 256 247 257 return clone;
+206
src/modules/timer.c
··· 12 12 #include "silver/engine.h" 13 13 #include "gc/roots.h" 14 14 #include "gc/modules.h" 15 + #include "modules/abort.h" 15 16 #include "modules/timer.h" 16 17 #include "modules/symbol.h" 17 18 ··· 285 286 return js_mkundef(); 286 287 } 287 288 289 + static ant_value_t timers_promises_get_state(ant_t *js) { 290 + return js_get_slot(js->current_func, SLOT_DATA); 291 + } 292 + 293 + static ant_value_t timers_promises_abort_reason(ant_t *js, ant_value_t signal) { 294 + ant_value_t reason = abort_signal_get_reason(signal); 295 + if (vtype(reason) != T_UNDEF && vtype(reason) != T_NULL) return reason; 296 + return js_mkerr_typed(js, JS_ERR_TYPE, "The operation was aborted"); 297 + } 298 + 299 + static void timers_promises_remove_abort_listener(ant_t *js, ant_value_t state) { 300 + ant_value_t signal = 0; 301 + ant_value_t listener = 0; 302 + 303 + if (!is_object_type(state)) return; 304 + 305 + signal = js_get(js, state, "signal"); 306 + listener = js_get(js, state, "abortListener"); 307 + 308 + if (abort_signal_is_signal(signal) && is_callable(listener)) 309 + abort_signal_remove_listener(js, signal, listener); 310 + 311 + js_set(js, state, "abortListener", js_mkundef()); 312 + } 313 + 314 + static void timers_promises_settle(ant_t *js, ant_value_t state, bool reject, ant_value_t value) { 315 + ant_value_t settled = 0; 316 + ant_value_t promise = 0; 317 + 318 + if (!is_object_type(state)) return; 319 + 320 + settled = js_get(js, state, "settled"); 321 + if (js_truthy(js, settled)) return; 322 + 323 + js_set(js, state, "settled", js_true); 324 + timers_promises_remove_abort_listener(js, state); 325 + js_set(js, state, "handle", js_mkundef()); 326 + 327 + promise = js_get(js, state, "promise"); 328 + if (reject) js_reject_promise(js, promise, value); 329 + else js_resolve_promise(js, promise, value); 330 + } 331 + 332 + static ant_value_t timers_promises_resolve(ant_t *js, ant_value_t *args, int nargs) { 333 + ant_value_t state = timers_promises_get_state(js); 334 + ant_value_t value = js_mkundef(); 335 + if (!is_object_type(state)) return js_mkundef(); 336 + value = js_get(js, state, "value"); 337 + timers_promises_settle(js, state, false, value); 338 + return js_mkundef(); 339 + } 340 + 341 + static ant_value_t timers_promises_on_abort(ant_t *js, ant_value_t *args, int nargs) { 342 + ant_value_t state = timers_promises_get_state(js); 343 + ant_value_t signal = 0; 344 + ant_value_t handle = 0; 345 + ant_value_t is_immediate = 0; 346 + ant_value_t reason = js_mkundef(); 347 + ant_value_t clear_args[1]; 348 + 349 + if (!is_object_type(state)) return js_mkundef(); 350 + 351 + signal = js_get(js, state, "signal"); 352 + handle = js_get(js, state, "handle"); 353 + is_immediate = js_get(js, state, "isImmediate"); 354 + 355 + if (vtype(handle) != T_UNDEF && vtype(handle) != T_NULL) { 356 + clear_args[0] = handle; 357 + if (js_truthy(js, is_immediate)) js_clear_immediate(js, clear_args, 1); 358 + else js_clear_timeout(js, clear_args, 1); 359 + } 360 + 361 + if (abort_signal_is_signal(signal)) reason = timers_promises_abort_reason(js, signal); 362 + else reason = js_mkerr_typed(js, JS_ERR_TYPE, "The operation was aborted"); 363 + 364 + timers_promises_settle(js, state, true, reason); 365 + return js_mkundef(); 366 + } 367 + 368 + static bool timers_promises_parse_options( 369 + ant_t *js, 370 + ant_value_t value, 371 + ant_value_t *signal_out, 372 + ant_value_t *error_out 373 + ) { 374 + ant_value_t signal = js_mkundef(); 375 + 376 + if (signal_out) *signal_out = js_mkundef(); 377 + if (error_out) *error_out = js_mkundef(); 378 + 379 + if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true; 380 + if (vtype(value) != T_OBJ) { 381 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Timer options must be an object"); 382 + return false; 383 + } 384 + 385 + signal = js_get(js, value, "signal"); 386 + if (vtype(signal) != T_UNDEF && vtype(signal) != T_NULL && !abort_signal_is_signal(signal)) { 387 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "options.signal must be an AbortSignal"); 388 + return false; 389 + } 390 + 391 + if (signal_out) *signal_out = signal; 392 + return true; 393 + } 394 + 395 + static ant_value_t timers_promises_schedule( 396 + ant_t *js, 397 + double delay_ms, 398 + ant_value_t value, 399 + ant_value_t signal, 400 + bool is_immediate 401 + ) { 402 + ant_value_t promise = js_mkpromise(js); 403 + ant_value_t state = js_mkobj(js); 404 + ant_value_t callback = 0; 405 + ant_value_t handle = 0; 406 + ant_value_t args[2]; 407 + 408 + if (abort_signal_is_signal(signal) && abort_signal_is_aborted(signal)) { 409 + js_reject_promise(js, promise, timers_promises_abort_reason(js, signal)); 410 + return promise; 411 + } 412 + 413 + js_set(js, state, "promise", promise); 414 + js_set(js, state, "value", value); 415 + js_set(js, state, "signal", signal); 416 + js_set(js, state, "abortListener", js_mkundef()); 417 + js_set(js, state, "handle", js_mkundef()); 418 + js_set(js, state, "settled", js_false); 419 + js_set(js, state, "isImmediate", js_bool(is_immediate)); 420 + 421 + callback = js_heavy_mkfun(js, timers_promises_resolve, state); 422 + if (is_immediate) handle = js_set_immediate(js, &callback, 1); 423 + else { 424 + args[0] = callback; 425 + args[1] = js_mknum(delay_ms); 426 + handle = js_set_timeout(js, args, 2); 427 + } 428 + 429 + if (is_err(handle)) { 430 + js_reject_promise(js, promise, handle); 431 + return promise; 432 + } 433 + 434 + js_set(js, state, "handle", handle); 435 + 436 + if (abort_signal_is_signal(signal)) { 437 + ant_value_t listener = js_heavy_mkfun(js, timers_promises_on_abort, state); 438 + js_set(js, state, "abortListener", listener); 439 + abort_signal_add_listener(js, signal, listener); 440 + } 441 + 442 + return promise; 443 + } 444 + 445 + static ant_value_t js_timers_promises_setTimeout(ant_t *js, ant_value_t *args, int nargs) { 446 + double delay_ms = nargs > 0 ? js_getnum(args[0]) : 0; 447 + ant_value_t value = nargs > 1 ? args[1] : js_mkundef(); 448 + ant_value_t options = nargs > 2 ? args[2] : js_mkundef(); 449 + ant_value_t signal = js_mkundef(); 450 + ant_value_t error = js_mkundef(); 451 + 452 + if (!timers_promises_parse_options(js, options, &signal, &error)) return error; 453 + return timers_promises_schedule(js, delay_ms, value, signal, false); 454 + } 455 + 456 + static ant_value_t js_timers_promises_setImmediate(ant_t *js, ant_value_t *args, int nargs) { 457 + ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); 458 + ant_value_t options = nargs > 1 ? args[1] : js_mkundef(); 459 + ant_value_t signal = js_mkundef(); 460 + ant_value_t error = js_mkundef(); 461 + 462 + if (!timers_promises_parse_options(js, options, &signal, &error)) return error; 463 + return timers_promises_schedule(js, 0, value, signal, true); 464 + } 465 + 466 + static ant_value_t js_timers_promises_setInterval(ant_t *js, ant_value_t *args, int nargs) { 467 + return js_mkerr_typed(js, JS_ERR_TYPE, "node:timers/promises setInterval() is not implemented yet"); 468 + } 469 + 470 + static ant_value_t js_timers_promises_scheduler_wait(ant_t *js, ant_value_t *args, int nargs) { 471 + return js_timers_promises_setTimeout(js, args, nargs); 472 + } 473 + 474 + static ant_value_t js_timers_promises_scheduler_yield(ant_t *js, ant_value_t *args, int nargs) { 475 + return js_timers_promises_setImmediate(js, args, nargs); 476 + } 477 + 288 478 void queue_microtask(ant_t *js, ant_value_t callback) { 289 479 microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t)); 290 480 if (entry == NULL) return; ··· 437 627 438 628 int has_pending_microtasks(void) { 439 629 return timer_state.microtasks != NULL ? 1 : 0; 630 + } 631 + 632 + // TODO: mostly stubbed 633 + ant_value_t timers_promises_library(ant_t *js) { 634 + ant_value_t lib = js_mkobj(js); 635 + ant_value_t scheduler = js_mkobj(js); 636 + 637 + js_set(js, lib, "scheduler", scheduler); 638 + js_set(js, lib, "setTimeout", js_mkfun(js_timers_promises_setTimeout)); 639 + js_set(js, lib, "setImmediate", js_mkfun(js_timers_promises_setImmediate)); 640 + js_set(js, lib, "setInterval", js_mkfun(js_timers_promises_setInterval)); 641 + js_set(js, scheduler, "wait", js_mkfun(js_timers_promises_scheduler_wait)); 642 + js_set(js, scheduler, "yield", js_mkfun(js_timers_promises_scheduler_yield)); 643 + js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "timers/promises", 16)); 644 + 645 + return lib; 440 646 } 441 647 442 648 void init_timer_module() {
+272
src/modules/tls.c
··· 1 + #include <compat.h> // IWYU pragma: keep 2 + 3 + #include <stdbool.h> 4 + #include <stdint.h> 5 + #include <stdlib.h> 6 + #include <string.h> 7 + 8 + #include <tlsuv/tlsuv.h> 9 + #include <tlsuv/tls_engine.h> 10 + 11 + #include "ant.h" 12 + #include "ptr.h" 13 + #include "errors.h" 14 + 15 + #include "modules/tls.h" 16 + #include "modules/buffer.h" 17 + 18 + typedef struct ant_tls_context_wrap_s { 19 + ant_value_t obj; 20 + tls_context *ctx; 21 + tlsuv_private_key_t key; 22 + tlsuv_certificate_t cert; 23 + bool closed; 24 + } ant_tls_context_wrap_t; 25 + 26 + enum { TLS_CONTEXT_NATIVE_TAG = 0x544c5343u }; 27 + static ant_value_t g_tls_context_proto = 0; 28 + 29 + static void tls_context_free(ant_tls_context_wrap_t *wrap) { 30 + if (!wrap || wrap->closed) return; 31 + wrap->closed = true; 32 + 33 + if (wrap->cert && wrap->cert->free) wrap->cert->free(wrap->cert); 34 + if (wrap->key && wrap->key->free) wrap->key->free(wrap->key); 35 + if (wrap->ctx && wrap->ctx->free_ctx) wrap->ctx->free_ctx(wrap->ctx); 36 + 37 + wrap->cert = NULL; 38 + wrap->key = NULL; 39 + wrap->ctx = NULL; 40 + } 41 + 42 + static ant_tls_context_wrap_t *tls_context_data(ant_value_t value) { 43 + if (!js_check_native_tag(value, TLS_CONTEXT_NATIVE_TAG)) return NULL; 44 + return (ant_tls_context_wrap_t *)js_get_native_ptr(value); 45 + } 46 + 47 + static bool tls_value_bytes( 48 + ant_t *js, 49 + ant_value_t value, 50 + const char **bytes_out, 51 + size_t *len_out, 52 + ant_value_t *error_out 53 + ) { 54 + ant_value_t str_value = 0; 55 + const uint8_t *buffer_bytes = NULL; 56 + 57 + if (error_out) *error_out = js_mkundef(); 58 + if (bytes_out) *bytes_out = NULL; 59 + if (len_out) *len_out = 0; 60 + 61 + if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true; 62 + if (buffer_source_get_bytes(js, value, &buffer_bytes, len_out)) { 63 + if (bytes_out) *bytes_out = (const char *)buffer_bytes; 64 + return true; 65 + } 66 + 67 + str_value = js_tostring_val(js, value); 68 + if (is_err(str_value)) { 69 + if (error_out) *error_out = str_value; 70 + return false; 71 + } 72 + 73 + if (!bytes_out || !len_out) return true; 74 + *bytes_out = js_getstr(js, str_value, len_out); 75 + 76 + if (!*bytes_out) { 77 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS input"); 78 + return false; 79 + } 80 + 81 + return true; 82 + } 83 + 84 + static ant_value_t tls_make_error(ant_t *js, tls_context *ctx, long code, const char *fallback) { 85 + const char *message = fallback; 86 + if (ctx && ctx->strerror) { 87 + const char *detail = ctx->strerror(code); 88 + if (detail && *detail) message = detail; 89 + } 90 + return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "TLS error"); 91 + } 92 + 93 + static ant_value_t js_tls_context_close(ant_t *js, ant_value_t *args, int nargs) { 94 + ant_tls_context_wrap_t *wrap = tls_context_data(js_getthis(js)); 95 + if (!wrap) return js_getthis(js); 96 + 97 + tls_context_free(wrap); 98 + js_set_native_ptr(wrap->obj, NULL); 99 + js_set_native_tag(wrap->obj, 0); 100 + return js_getthis(js); 101 + } 102 + 103 + static void tls_init_context_proto(ant_t *js) { 104 + if (g_tls_context_proto) return; 105 + 106 + g_tls_context_proto = js_mkobj(js); 107 + js_set(js, g_tls_context_proto, "close", js_mkfun(js_tls_context_close)); 108 + } 109 + 110 + static ant_value_t js_tls_create_context(ant_t *js, ant_value_t *args, int nargs) { 111 + ant_value_t options = nargs > 0 ? args[0] : js_mkundef(); 112 + ant_value_t obj = 0; 113 + ant_value_t error = js_mkundef(); 114 + 115 + ant_tls_context_wrap_t *wrap = NULL; 116 + ant_value_t ca_v = js_mkundef(); 117 + ant_value_t key_v = js_mkundef(); 118 + ant_value_t cert_v = js_mkundef(); 119 + ant_value_t partial_v = js_mkundef(); 120 + 121 + const char *ca = NULL; 122 + const char *key_data = NULL; 123 + const char *cert_data = NULL; 124 + 125 + size_t ca_len = 0; 126 + size_t key_len = 0; 127 + size_t cert_len = 0; 128 + int rc = 0; 129 + 130 + if (vtype(options) != T_UNDEF && vtype(options) != T_NULL && vtype(options) != T_OBJ) 131 + return js_mkerr_typed(js, JS_ERR_TYPE, "TLS context options must be an object"); 132 + 133 + tls_init_context_proto(js); 134 + 135 + if (vtype(options) == T_OBJ) { 136 + ca_v = js_get(js, options, "ca"); 137 + key_v = js_get(js, options, "key"); 138 + cert_v = js_get(js, options, "cert"); 139 + partial_v = js_get(js, options, "allowPartialChain"); 140 + } 141 + 142 + if (!tls_value_bytes(js, ca_v, &ca, &ca_len, &error)) return error; 143 + if (!tls_value_bytes(js, key_v, &key_data, &key_len, &error)) return error; 144 + if (!tls_value_bytes(js, cert_v, &cert_data, &cert_len, &error)) return error; 145 + 146 + wrap = calloc(1, sizeof(*wrap)); 147 + if (!wrap) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); 148 + 149 + wrap->ctx = default_tls_context(ca, ca_len); 150 + if (!wrap->ctx) { 151 + free(wrap); 152 + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to create TLS context"); 153 + } 154 + 155 + if (wrap->ctx->allow_partial_chain && js_truthy(js, partial_v)) { 156 + rc = wrap->ctx->allow_partial_chain(wrap->ctx, 1); 157 + if (rc != 0) { 158 + ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to enable partial chain verification"); 159 + tls_context_free(wrap); 160 + free(wrap); 161 + return err; 162 + }} 163 + 164 + if (key_data) { 165 + if (!wrap->ctx->load_key) { 166 + tls_context_free(wrap); 167 + free(wrap); 168 + return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading private keys"); 169 + } 170 + 171 + rc = wrap->ctx->load_key(&wrap->key, key_data, key_len); 172 + if (rc != 0) { 173 + ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to load TLS private key"); 174 + tls_context_free(wrap); 175 + free(wrap); 176 + return err; 177 + } 178 + 179 + if (cert_data) { 180 + if (!wrap->ctx->load_cert) { 181 + tls_context_free(wrap); 182 + free(wrap); 183 + return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading certificates"); 184 + } 185 + 186 + rc = wrap->ctx->load_cert(&wrap->cert, cert_data, cert_len); 187 + if (rc != 0) { 188 + ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to load TLS certificate"); 189 + tls_context_free(wrap); 190 + free(wrap); 191 + return err; 192 + }} 193 + 194 + if (!wrap->ctx->set_own_cert) { 195 + tls_context_free(wrap); 196 + free(wrap); 197 + return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support own certificate configuration"); 198 + } 199 + 200 + rc = wrap->ctx->set_own_cert(wrap->ctx, wrap->key, wrap->cert); 201 + if (rc != 0) { 202 + ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to configure TLS certificate"); 203 + tls_context_free(wrap); 204 + free(wrap); 205 + return err; 206 + } 207 + } else if (cert_data) { 208 + tls_context_free(wrap); 209 + free(wrap); 210 + return js_mkerr_typed(js, JS_ERR_TYPE, "TLS certificate requires a private key"); 211 + } 212 + 213 + obj = js_mkobj(js); 214 + js_set_proto_init(obj, g_tls_context_proto); 215 + wrap->obj = obj; 216 + 217 + js_set_native_ptr(obj, wrap); 218 + js_set_native_tag(obj, TLS_CONTEXT_NATIVE_TAG); 219 + 220 + return obj; 221 + } 222 + 223 + static ant_value_t js_tls_is_context(ant_t *js, ant_value_t *args, int nargs) { 224 + ant_tls_context_wrap_t *wrap = nargs > 0 ? tls_context_data(args[0]) : NULL; 225 + return js_bool(wrap && !wrap->closed && wrap->ctx); 226 + } 227 + 228 + static ant_value_t js_tls_set_config_path(ant_t *js, ant_value_t *args, int nargs) { 229 + ant_value_t str_value = js_mkundef(); 230 + const char *path = NULL; 231 + int rc = 0; 232 + 233 + if (nargs < 1 || vtype(args[0]) == T_UNDEF || vtype(args[0]) == T_NULL) { 234 + rc = tlsuv_set_config_path(NULL); 235 + if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); 236 + return js_mkundef(); 237 + } 238 + 239 + str_value = js_tostring_val(js, args[0]); 240 + if (is_err(str_value)) return str_value; 241 + 242 + path = js_getstr(js, str_value, NULL); 243 + if (!path) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS config path"); 244 + 245 + rc = tlsuv_set_config_path(path); 246 + if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); 247 + 248 + return js_mkundef(); 249 + } 250 + 251 + ant_value_t internal_tls_library(ant_t *js) { 252 + ant_value_t lib = js_mkobj(js); 253 + ant_value_t version = js_mkstr(js, "unknown", 7); 254 + 255 + tls_context *ctx = default_tls_context(NULL, 0); 256 + const char *version_str = NULL; 257 + 258 + if (ctx && ctx->version) { 259 + version_str = ctx->version(); 260 + if (version_str) version = js_mkstr(js, version_str, strlen(version_str)); 261 + } 262 + 263 + if (ctx && ctx->free_ctx) ctx->free_ctx(ctx); 264 + tls_init_context_proto(js); 265 + 266 + js_set(js, lib, "version", version); 267 + js_set(js, lib, "createContext", js_mkfun(js_tls_create_context)); 268 + js_set(js, lib, "isContext", js_mkfun(js_tls_is_context)); 269 + js_set(js, lib, "setConfigPath", js_mkfun(js_tls_set_config_path)); 270 + 271 + return lib; 272 + }
+4 -4
src/modules/uri.c
··· 74 74 } 75 75 76 76 // encodeURIComponent() 77 - static ant_value_t js_encodeURIComponent(ant_t *js, ant_value_t *args, int nargs) { 77 + ant_value_t js_encodeURIComponent(ant_t *js, ant_value_t *args, int nargs) { 78 78 ant_value_t result; 79 79 char *out = NULL; 80 80 ··· 127 127 } 128 128 129 129 // encodeURI() 130 - static ant_value_t js_encodeURI(ant_t *js, ant_value_t *args, int nargs) { 130 + ant_value_t js_encodeURI(ant_t *js, ant_value_t *args, int nargs) { 131 131 ant_value_t result; 132 132 char *out = NULL; 133 133 ··· 180 180 } 181 181 182 182 // decodeURIComponent() 183 - static ant_value_t js_decodeURIComponent(ant_t *js, ant_value_t *args, int nargs) { 183 + ant_value_t js_decodeURIComponent(ant_t *js, ant_value_t *args, int nargs) { 184 184 ant_value_t result; 185 185 char *out = NULL; 186 186 ··· 229 229 } 230 230 231 231 // decodeURI() 232 - static ant_value_t js_decodeURI(ant_t *js, ant_value_t *args, int nargs) { 232 + ant_value_t js_decodeURI(ant_t *js, ant_value_t *args, int nargs) { 233 233 ant_value_t result; 234 234 char *out = NULL; 235 235
+71 -29
src/net/connection.c
··· 70 70 } 71 71 72 72 static bool ant_conn_store_peer_addr(ant_conn_t *conn) { 73 + if (!conn || conn->kind != ANT_CONN_KIND_TCP) return false; 73 74 return ant_conn_store_addr( 74 - &conn->handle, 75 + &conn->handle.tcp, 75 76 (int (*)(const uv_tcp_t *, struct sockaddr *, int *))uv_tcp_getpeername, 76 77 conn->remote_addr, 77 78 sizeof(conn->remote_addr), ··· 83 84 } 84 85 85 86 static bool ant_conn_store_local_addr(ant_conn_t *conn) { 87 + if (!conn || conn->kind != ANT_CONN_KIND_TCP) return false; 86 88 return ant_conn_store_addr( 87 - &conn->handle, 89 + &conn->handle.tcp, 88 90 (int (*)(const uv_tcp_t *, struct sockaddr *, int *))uv_tcp_getsockname, 89 91 conn->local_addr, 90 92 sizeof(conn->local_addr), ··· 116 118 117 119 static void ant_conn_alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { 118 120 ant_conn_t *conn = (ant_conn_t *)handle->data; 119 - size_t need = 0; 120 121 size_t next_cap = 0; 121 122 char *next = NULL; 122 123 ··· 126 127 return; 127 128 } 128 129 129 - need = conn->buffer_len + suggested_size; 130 - if (need > conn->buffer_cap) { 130 + if (conn->buffer_len + suggested_size > conn->buffer_cap - conn->buffer_offset) { 131 + if (conn->buffer_offset > 0 && conn->buffer_len > 0) memmove(conn->buffer, conn->buffer + conn->buffer_offset, conn->buffer_len); 132 + else if (conn->buffer_offset > 0) conn->buffer_len = 0; 133 + conn->buffer_offset = 0; 134 + 135 + if (conn->buffer_len + suggested_size > conn->buffer_cap) { 131 136 next_cap = conn->buffer_cap ? conn->buffer_cap * 2 : ANT_CONN_READ_BUFFER_SIZE; 132 - while (next_cap < need) next_cap *= 2; 137 + while (next_cap < conn->buffer_len + suggested_size) next_cap *= 2; 133 138 next = realloc(conn->buffer, next_cap); 134 139 if (!next) { 135 140 buf->base = NULL; ··· 138 143 } 139 144 conn->buffer = next; 140 145 conn->buffer_cap = next_cap; 141 - } 146 + }} 142 147 143 - buf->base = conn->buffer + conn->buffer_len; 144 - buf->len = conn->buffer_cap - conn->buffer_len; 148 + buf->base = conn->buffer + conn->buffer_offset + conn->buffer_len; 149 + buf->len = conn->buffer_cap - conn->buffer_offset - conn->buffer_len; 145 150 } 146 151 147 152 static void ant_conn_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { ··· 152 157 if (nread < 0) { 153 158 if (nread == UV_EOF) { 154 159 conn->read_eof = true; 155 - uv_read_stop((uv_stream_t *)&conn->handle); 160 + uv_read_stop(ant_conn_stream(conn)); 156 161 if (listener->callbacks.on_end) 157 162 listener->callbacks.on_end(conn, listener->user_data); 158 163 return; ··· 215 220 conn = calloc(1, sizeof(*conn)); 216 221 if (!conn) return NULL; 217 222 223 + conn->kind = ANT_CONN_KIND_TCP; 218 224 conn->listener = listener; 219 225 conn->timeout_ms = timeout_ms; 220 226 conn->buffer_cap = ANT_CONN_READ_BUFFER_SIZE; 221 227 conn->buffer = malloc(conn->buffer_cap); 228 + 222 229 if (!conn->buffer) { 223 230 free(conn); 224 231 return NULL; 225 232 } 226 233 227 - uv_tcp_init(listener->loop, &conn->handle); 234 + uv_tcp_init(listener->loop, &conn->handle.tcp); 228 235 uv_timer_init(listener->loop, &conn->timer); 229 - conn->handle.data = conn; 236 + conn->handle.tcp.data = conn; 230 237 conn->timer.data = conn; 238 + 239 + return conn; 240 + } 241 + 242 + ant_conn_t *ant_conn_create_pipe(ant_listener_t *listener, uint64_t timeout_ms) { 243 + ant_conn_t *conn = NULL; 244 + 245 + if (!listener || !listener->loop) return NULL; 246 + 247 + conn = calloc(1, sizeof(*conn)); 248 + if (!conn) return NULL; 249 + 250 + conn->kind = ANT_CONN_KIND_PIPE; 251 + conn->listener = listener; 252 + conn->timeout_ms = timeout_ms; 253 + conn->buffer_cap = ANT_CONN_READ_BUFFER_SIZE; 254 + conn->buffer = malloc(conn->buffer_cap); 255 + 256 + if (!conn->buffer) { 257 + free(conn); 258 + return NULL; 259 + } 260 + 261 + uv_pipe_init(listener->loop, &conn->handle.pipe, 0); 262 + uv_timer_init(listener->loop, &conn->timer); 263 + conn->handle.pipe.data = conn; 264 + conn->timer.data = conn; 265 + 231 266 return conn; 232 267 } 233 268 234 269 int ant_conn_accept(ant_conn_t *conn, uv_stream_t *server_stream) { 235 270 if (!conn || !server_stream) return UV_EINVAL; 236 271 237 - if (uv_accept(server_stream, (uv_stream_t *)&conn->handle) != 0) 272 + if (uv_accept(server_stream, ant_conn_stream(conn)) != 0) 238 273 return UV_ECONNABORTED; 239 274 240 - ant_conn_store_peer_addr(conn); 241 - ant_conn_store_local_addr(conn); 275 + if (conn->kind == ANT_CONN_KIND_TCP) { 276 + ant_conn_store_peer_addr(conn); 277 + ant_conn_store_local_addr(conn); 278 + } 279 + 242 280 conn->next = conn->listener->connections; 243 281 conn->listener->connections = conn; 282 + 244 283 return 0; 245 284 } 246 285 ··· 248 287 if (!conn || conn->closing) return; 249 288 conn->read_paused = false; 250 289 ant_conn_restart_timer(conn); 251 - uv_read_start((uv_stream_t *)&conn->handle, ant_conn_alloc_cb, ant_conn_read_cb); 290 + uv_read_start(ant_conn_stream(conn), ant_conn_alloc_cb, ant_conn_read_cb); 252 291 } 253 292 254 293 void ant_conn_set_user_data(ant_conn_t *conn, void *user_data) { ··· 264 303 } 265 304 266 305 const char *ant_conn_buffer(const ant_conn_t *conn) { 267 - return conn ? conn->buffer : NULL; 306 + return conn ? conn->buffer + conn->buffer_offset : NULL; 268 307 } 269 308 270 309 size_t ant_conn_buffer_len(const ant_conn_t *conn) { ··· 320 359 void ant_conn_pause_read(ant_conn_t *conn) { 321 360 if (!conn || conn->closing) return; 322 361 conn->read_paused = true; 323 - uv_read_stop((uv_stream_t *)&conn->handle); 362 + uv_read_stop(ant_conn_stream(conn)); 324 363 } 325 364 326 365 void ant_conn_resume_read(ant_conn_t *conn) { 327 366 if (!conn || conn->closing || conn->read_eof || !conn->read_paused) return; 328 367 conn->read_paused = false; 329 368 ant_conn_restart_timer(conn); 330 - uv_read_start((uv_stream_t *)&conn->handle, ant_conn_alloc_cb, ant_conn_read_cb); 369 + uv_read_start(ant_conn_stream(conn), ant_conn_alloc_cb, ant_conn_read_cb); 331 370 } 332 371 333 372 void ant_conn_shutdown(ant_conn_t *conn) { ··· 342 381 return; 343 382 } 344 383 345 - rc = uv_shutdown(&req->req, (uv_stream_t *)&conn->handle, ant_conn_shutdown_cb); 384 + rc = uv_shutdown(&req->req, ant_conn_stream(conn), ant_conn_shutdown_cb); 346 385 if (rc != 0) { 347 386 free(req); 348 387 ant_conn_close(conn); ··· 351 390 352 391 void ant_conn_ref(ant_conn_t *conn) { 353 392 if (!conn) return; 354 - if (!uv_is_closing((uv_handle_t *)&conn->handle)) uv_ref((uv_handle_t *)&conn->handle); 393 + if (!uv_is_closing((uv_handle_t *)ant_conn_stream(conn))) uv_ref((uv_handle_t *)ant_conn_stream(conn)); 355 394 if (!uv_is_closing((uv_handle_t *)&conn->timer)) uv_ref((uv_handle_t *)&conn->timer); 356 395 } 357 396 358 397 void ant_conn_unref(ant_conn_t *conn) { 359 398 if (!conn) return; 360 - if (!uv_is_closing((uv_handle_t *)&conn->handle)) uv_unref((uv_handle_t *)&conn->handle); 399 + if (!uv_is_closing((uv_handle_t *)ant_conn_stream(conn))) uv_unref((uv_handle_t *)ant_conn_stream(conn)); 361 400 if (!uv_is_closing((uv_handle_t *)&conn->timer)) uv_unref((uv_handle_t *)&conn->timer); 362 401 } 363 402 364 403 int ant_conn_set_no_delay(ant_conn_t *conn, bool enable) { 365 404 if (!conn || conn->closing) return UV_EINVAL; 366 - return uv_tcp_nodelay(&conn->handle, enable ? 1 : 0); 405 + if (conn->kind != ANT_CONN_KIND_TCP) return UV_ENOTSUP; 406 + return uv_tcp_nodelay(&conn->handle.tcp, enable ? 1 : 0); 367 407 } 368 408 369 409 int ant_conn_set_keep_alive(ant_conn_t *conn, bool enable, unsigned int delay_secs) { 370 410 if (!conn || conn->closing) return UV_EINVAL; 371 - return uv_tcp_keepalive(&conn->handle, enable ? 1 : 0, delay_secs); 411 + if (conn->kind != ANT_CONN_KIND_TCP) return UV_ENOTSUP; 412 + return uv_tcp_keepalive(&conn->handle.tcp, enable ? 1 : 0, delay_secs); 372 413 } 373 414 374 415 void ant_conn_consume(ant_conn_t *conn, size_t len) { 375 416 if (!conn || conn->buffer_len == 0 || len == 0) return; 376 417 if (len >= conn->buffer_len) { 418 + conn->buffer_offset = 0; 377 419 conn->buffer_len = 0; 378 420 return; 379 421 } 380 422 381 - memmove(conn->buffer, conn->buffer + len, conn->buffer_len - len); 423 + conn->buffer_offset += len; 382 424 conn->buffer_len -= len; 383 425 } 384 426 ··· 393 435 conn->close_handles++; 394 436 } 395 437 396 - if (!uv_is_closing((uv_handle_t *)&conn->handle)) { 397 - uv_close((uv_handle_t *)&conn->handle, ant_conn_close_cb); 438 + if (!uv_is_closing((uv_handle_t *)ant_conn_stream(conn))) { 439 + uv_close((uv_handle_t *)ant_conn_stream(conn), ant_conn_close_cb); 398 440 conn->close_handles++; 399 441 } 400 442 401 - if (conn->close_handles == 0) ant_conn_close_cb((uv_handle_t *)&conn->handle); 443 + if (conn->close_handles == 0) ant_conn_close_cb((uv_handle_t *)ant_conn_stream(conn)); 402 444 } 403 445 404 446 int ant_conn_write(ant_conn_t *conn, char *data, size_t len, ant_conn_write_cb cb, void *user_data) { ··· 422 464 wr->user_data = user_data; 423 465 424 466 ant_conn_restart_timer(conn); 425 - rc = uv_write(&wr->req, (uv_stream_t *)&conn->handle, &wr->buf, 1, ant_conn_write_cb_impl); 467 + rc = uv_write(&wr->req, ant_conn_stream(conn), &wr->buf, 1, ant_conn_write_cb_impl); 426 468 if (rc != 0) { 427 469 free(wr->buf.base); 428 470 free(wr);
+82 -8
src/net/listener.c
··· 3 3 #ifndef _WIN32 4 4 #include <arpa/inet.h> 5 5 #include <netinet/in.h> 6 + #include <unistd.h> 6 7 #endif 7 8 #include <stdlib.h> 8 9 #include <string.h> ··· 13 14 static void ant_listener_close_cb(uv_handle_t *handle) { 14 15 ant_listener_t *listener = (ant_listener_t *)handle->data; 15 16 if (!listener) return; 17 + 18 + #ifndef _WIN32 19 + if (listener->kind == ANT_LISTENER_KIND_PIPE && listener->path && listener->path[0] != '\0') 20 + unlink(listener->path); 21 + #endif 22 + 16 23 listener->closed = true; 17 24 if (listener->callbacks.on_listener_close) 18 25 listener->callbacks.on_listener_close(listener, listener->user_data); 26 + 27 + free(listener->path); 28 + listener->path = NULL; 19 29 } 20 30 21 31 static void ant_listener_accept_cb(uv_stream_t *server_stream, int status) { ··· 24 34 25 35 if (status < 0 || !listener || listener->closing) return; 26 36 27 - conn = ant_conn_create_tcp(listener, listener->idle_timeout_ms); 37 + conn = listener->kind == ANT_LISTENER_KIND_PIPE 38 + ? ant_conn_create_pipe(listener, listener->idle_timeout_ms) 39 + : ant_conn_create_tcp(listener, listener->idle_timeout_ms); 28 40 if (!conn) return; 29 41 30 42 if (ant_conn_accept(conn, server_stream) != 0) { ··· 60 72 listener->idle_timeout_ms = idle_timeout_ms; 61 73 listener->port = port; 62 74 listener->backlog = backlog > 0 ? backlog : 128; 75 + listener->kind = ANT_LISTENER_KIND_TCP; 63 76 if (callbacks) listener->callbacks = *callbacks; 64 77 65 - uv_tcp_init(loop, &listener->handle); 66 - listener->handle.data = listener; 78 + uv_tcp_init(loop, &listener->handle.tcp); 79 + listener->handle.tcp.data = listener; 67 80 68 81 rc = uv_ip4_addr(hostname, port, &addr); 69 82 if (rc != 0) return rc; 70 83 71 - rc = uv_tcp_bind(&listener->handle, (const struct sockaddr *)&addr, 0); 84 + rc = uv_tcp_bind(&listener->handle.tcp, (const struct sockaddr *)&addr, 0); 72 85 if (rc != 0) return rc; 73 86 74 - rc = uv_listen((uv_stream_t *)&listener->handle, listener->backlog, ant_listener_accept_cb); 87 + rc = uv_listen((uv_stream_t *)&listener->handle.tcp, listener->backlog, ant_listener_accept_cb); 75 88 if (rc != 0) return rc; 76 89 77 90 listener->started = true; 78 - if (port == 0 && uv_tcp_getsockname(&listener->handle, (struct sockaddr *)&addr, &addr_len) == 0) 91 + if (port == 0 && uv_tcp_getsockname(&listener->handle.tcp, (struct sockaddr *)&addr, &addr_len) == 0) 79 92 listener->port = ntohs(addr.sin_port); 80 93 81 94 return 0; 82 95 } 83 96 97 + int ant_listener_listen_pipe( 98 + ant_listener_t *listener, 99 + uv_loop_t *loop, 100 + const char *path, 101 + int backlog, 102 + uint64_t idle_timeout_ms, 103 + const ant_listener_callbacks_t *callbacks, 104 + void *user_data 105 + ) { 106 + int rc = 0; 107 + 108 + if (!listener || !loop || !path || !*path) return UV_EINVAL; 109 + memset(listener, 0, sizeof(*listener)); 110 + 111 + listener->loop = loop; 112 + listener->user_data = user_data; 113 + listener->idle_timeout_ms = idle_timeout_ms; 114 + listener->backlog = backlog > 0 ? backlog : 128; 115 + listener->kind = ANT_LISTENER_KIND_PIPE; 116 + listener->path = strdup(path); 117 + if (callbacks) listener->callbacks = *callbacks; 118 + 119 + if (!listener->path) return UV_ENOMEM; 120 + 121 + uv_pipe_init(loop, &listener->handle.pipe, 0); 122 + listener->handle.pipe.data = listener; 123 + 124 + rc = uv_pipe_bind(&listener->handle.pipe, path); 125 + if (rc != 0) { 126 + free(listener->path); 127 + listener->path = NULL; 128 + return rc; 129 + } 130 + 131 + rc = uv_listen((uv_stream_t *)&listener->handle.pipe, listener->backlog, ant_listener_accept_cb); 132 + if (rc != 0) { 133 + free(listener->path); 134 + listener->path = NULL; 135 + return rc; 136 + } 137 + 138 + listener->started = true; 139 + return 0; 140 + } 141 + 84 142 void ant_listener_stop(ant_listener_t *listener, bool force) { 85 143 ant_conn_t *conn = NULL; 86 144 ant_conn_t *next = NULL; ··· 88 146 if (!listener) return; 89 147 listener->closing = true; 90 148 91 - if (listener->started && !uv_is_closing((uv_handle_t *)&listener->handle)) 92 - uv_close((uv_handle_t *)&listener->handle, ant_listener_close_cb); 149 + if (listener->started && !uv_is_closing((uv_handle_t *)ant_listener_stream(listener))) 150 + uv_close((uv_handle_t *)ant_listener_stream(listener), ant_listener_close_cb); 93 151 else if (!listener->started) listener->closed = true; 94 152 95 153 if (force) { ··· 114 172 void *ant_listener_get_user_data(const ant_listener_t *listener) { 115 173 return listener ? listener->user_data : NULL; 116 174 } 175 + 176 + void ant_listener_ref(ant_listener_t *listener) { 177 + if (!listener) return; 178 + if (!listener->started) return; 179 + if (!uv_is_closing((uv_handle_t *)ant_listener_stream(listener))) uv_ref((uv_handle_t *)ant_listener_stream(listener)); 180 + } 181 + 182 + void ant_listener_unref(ant_listener_t *listener) { 183 + if (!listener) return; 184 + if (!listener->started) return; 185 + if (!uv_is_closing((uv_handle_t *)ant_listener_stream(listener))) uv_unref((uv_handle_t *)ant_listener_stream(listener)); 186 + } 187 + 188 + const char *ant_listener_path(const ant_listener_t *listener) { 189 + return listener ? listener->path : NULL; 190 + }
+7 -1
src/silver/compiler.c
··· 51 51 52 52 typedef struct sv_compiler { 53 53 ant_t *js; 54 + const char *filename; 54 55 const char *source; 55 56 ant_offset_t source_len; 56 57 ··· 3906 3907 c->computed_key_locals = computed_key_locals; 3907 3908 sv_compiler_t comp = {0}; 3908 3909 comp.js = c->js; 3910 + comp.filename = c->filename; 3909 3911 comp.source = c->source; 3910 3912 comp.source_len = c->source_len; 3911 3913 comp.enclosing = c; ··· 4056 4058 ) { 4057 4059 sv_compiler_t comp = {0}; 4058 4060 comp.js = enclosing->js; 4061 + comp.filename = enclosing->filename; 4059 4062 comp.source = enclosing->source; 4060 4063 comp.source_len = enclosing->source_len; 4061 4064 comp.enclosing = enclosing; ··· 4441 4444 func->is_generator = !!(node->flags & FN_GENERATOR); 4442 4445 func->is_method = !!(node->flags & FN_METHOD); 4443 4446 func->is_tla = comp.is_tla; 4444 - func->filename = enclosing->js->filename; 4447 + func->filename = enclosing->filename ? enclosing->filename : enclosing->js->filename; 4445 4448 func->source_line = (int)node->line; 4446 4449 if (node->str && node->len > 0) { 4447 4450 char *name = code_arena_bump(node->len + 1); ··· 4696 4699 4697 4700 sv_compiler_t root = {0}; 4698 4701 root.js = js; 4702 + root.filename = js->filename; 4699 4703 root.source = pin_source_text(source, source_len); 4700 4704 root.source_len = source_len; 4701 4705 root.mode = mode; ··· 4736 4740 4737 4741 sv_compiler_t root = {0}; 4738 4742 root.js = js; 4743 + root.filename = js->filename; 4739 4744 root.source = pin_source_text(wrapped, (ant_offset_t)wrapped_len); 4740 4745 root.source_len = (ant_offset_t)wrapped_len; 4741 4746 root.mode = SV_COMPILE_SCRIPT; ··· 4803 4808 4804 4809 sv_compiler_t root = {0}; 4805 4810 root.js = js; 4811 + root.filename = js->filename; 4806 4812 root.source = pin_source_text(body, (ant_offset_t)body_len); 4807 4813 root.source_len = (ant_offset_t)body_len; 4808 4814 root.mode = SV_COMPILE_SCRIPT;
-1
src/silver/engine.c
··· 243 243 244 244 ant_value_t result = sv_execute_frame(vm, func, this_val, super_val, args, argc); 245 245 if (out_this) *out_this = vm->frames[vm->fp].this; 246 - 247 246 if (!vm->suspended) vm->fp = saved_fp; 248 247 249 248 return result;
+77 -13
src/streams/readable.c
··· 16 16 ant_value_t g_rs_proto; 17 17 ant_value_t g_reader_proto; 18 18 ant_value_t g_controller_proto; 19 + ant_value_t g_rs_async_iter_proto; 19 20 20 21 bool rs_is_stream(ant_value_t obj) { 21 22 return js_check_brand(obj, BRAND_READABLE_STREAM) ··· 604 605 return rs_default_reader_read(js, js->this_val); 605 606 } 606 607 607 - static ant_value_t js_rs_reader_release_lock(ant_t *js, ant_value_t *args, int nargs) { 608 - ant_value_t stream_obj = rs_reader_stream(js->this_val); 608 + static ant_value_t rs_release_reader_lock(ant_t *js, ant_value_t reader_obj) { 609 + ant_value_t stream_obj = rs_reader_stream(reader_obj); 609 610 if (!rs_is_stream(stream_obj)) return js_mkundef(); 610 611 rs_stream_t *stream = rs_get_stream(stream_obj); 611 612 612 - if (rs_reader_has_reqs(js, js->this_val)) { 613 + if (rs_reader_has_reqs(js, reader_obj)) { 613 614 ant_value_t release_err = js_make_error_silent(js, JS_ERR_TYPE, "Reader was released"); 614 - rs_default_reader_error_read_requests(js, js->this_val, release_err); 615 + rs_default_reader_error_read_requests(js, reader_obj, release_err); 615 616 } 616 617 617 618 ant_value_t new_closed = js_mkpromise(js); 618 619 ant_value_t release_err = js_make_error_silent(js, JS_ERR_TYPE, "Reader was released"); 619 620 620 621 if (stream->state == RS_STATE_READABLE) { 621 - ant_value_t old_closed = rs_reader_closed(js->this_val); 622 + ant_value_t old_closed = rs_reader_closed(reader_obj); 622 623 js_reject_promise(js, old_closed, release_err); 623 624 promise_mark_handled(old_closed); 624 625 } 625 626 626 627 js_reject_promise(js, new_closed, release_err); 627 628 promise_mark_handled(new_closed); 628 - js_set_slot(js->this_val, SLOT_RS_CLOSED, new_closed); 629 + js_set_slot(reader_obj, SLOT_RS_CLOSED, new_closed); 629 630 630 631 js_set_slot(stream_obj, SLOT_CTOR, js_mkundef()); 631 - js_set_slot(js->this_val, SLOT_ENTRIES, js_mkundef()); 632 + js_set_slot(reader_obj, SLOT_ENTRIES, js_mkundef()); 633 + 632 634 return js_mkundef(); 635 + } 636 + 637 + static ant_value_t js_rs_reader_release_lock(ant_t *js, ant_value_t *args, int nargs) { 638 + return rs_release_reader_lock(js, js->this_val); 633 639 } 634 640 635 641 static ant_value_t js_rs_reader_cancel(ant_t *js, ant_value_t *args, int nargs) { ··· 725 731 return reader; 726 732 } 727 733 734 + static ant_value_t js_rs_async_iter_next(ant_t *js, ant_value_t *args, int nargs) { 735 + ant_value_t reader = js_get_slot(js->this_val, SLOT_DATA); 736 + if (!rs_is_reader(reader)) return js_mkerr_typed( 737 + js, JS_ERR_TYPE, "ReadableStream async iterator has no reader" 738 + ); 739 + return rs_default_reader_read(js, reader); 740 + } 741 + 742 + static ant_value_t js_rs_async_iter_return(ant_t *js, ant_value_t *args, int nargs) { 743 + ant_value_t reader = js_get_slot(js->this_val, SLOT_DATA); 744 + 745 + if (!rs_is_reader(reader)) { 746 + ant_value_t p = js_mkpromise(js); 747 + js_resolve_promise(js, p, js_iter_result(js, false, js_mkundef())); 748 + return p; 749 + } 750 + 751 + ant_value_t stream_obj = rs_reader_stream(reader); 752 + ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef(); 753 + 754 + readable_stream_cancel(js, stream_obj, reason); 755 + rs_release_reader_lock(js, reader); 756 + 757 + ant_value_t p = js_mkpromise(js); 758 + js_resolve_promise(js, p, js_iter_result(js, false, js_mkundef())); 759 + 760 + return p; 761 + } 762 + 728 763 static ant_value_t js_rs_values(ant_t *js, ant_value_t *args, int nargs) { 729 - return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStream async iteration is not yet implemented"); 764 + ant_value_t reader = js_rs_get_reader(js, NULL, 0); 765 + if (is_err(reader)) return reader; 766 + 767 + ant_value_t iterator = js_mkobj(js); 768 + js_set_proto_init(iterator, g_rs_async_iter_proto); 769 + js_set_slot(iterator, SLOT_DATA, reader); 770 + 771 + return iterator; 730 772 } 731 773 732 774 static ant_value_t rs_start_resolve_handler(ant_t *js, ant_value_t *args, int nargs) { 733 775 ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA); 734 776 rs_controller_t *ctrl = rs_get_controller(ctrl_obj); 777 + 735 778 if (!ctrl) return js_mkundef(); 736 779 ctrl->started = true; 737 780 ctrl->pulling = false; 738 781 ctrl->pull_again = false; 739 782 rs_default_controller_call_pull_if_needed(js, ctrl_obj); 783 + 740 784 return js_mkundef(); 741 785 } 742 786 ··· 744 788 ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA); 745 789 rs_controller_t *ctrl = rs_get_controller(ctrl_obj); 746 790 if (!ctrl) return js_mkundef(); 791 + 747 792 ant_value_t stream_obj = rs_ctrl_stream(ctrl_obj); 748 793 rs_stream_t *stream = rs_get_stream(stream_obj); 749 - if (stream && stream->state == RS_STATE_READABLE) 750 - readable_stream_error(js, stream_obj, nargs > 0 ? args[0] : js_mkundef()); 794 + 795 + if (stream && stream->state == RS_STATE_READABLE) readable_stream_error( 796 + js, stream_obj, 797 + nargs > 0 ? args[0] : js_mkundef() 798 + ); 799 + 751 800 return js_mkundef(); 752 801 } 753 802 ··· 780 829 return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStream constructor requires 'new'"); 781 830 782 831 ant_value_t underlying_source = js_mkundef(); 832 + bool is_bytes_source = false; 783 833 if (nargs > 0 && !is_undefined(args[0])) { 784 834 if (is_null(args[0])) 785 835 return js_mkerr_typed(js, JS_ERR_TYPE, "The underlying source cannot be null"); ··· 796 846 } 797 847 size_t tlen; 798 848 const char *tstr = js_getstr(js, coerced, &tlen); 799 - if (tstr && tlen == 5 && memcmp(tstr, "bytes", 5) == 0) 800 - return js_mkerr_typed(js, JS_ERR_RANGE, "ReadableStream byte sources are not yet implemented"); 801 - return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid type is specified"); 849 + if (tstr && tlen == 5 && memcmp(tstr, "bytes", 5) == 0) { 850 + // TODO: accept byte sources, but currently back them with the default 851 + // controller path until BYOB readers/controllers are implemented 852 + is_bytes_source = true; 853 + } else return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid type is specified"); 802 854 }} 803 855 804 856 ant_value_t strategy = js_mkundef(); ··· 837 889 js_set_slot(obj, SLOT_DATA, ANT_PTR(st)); 838 890 js_set_finalizer(obj, rs_stream_finalize); 839 891 892 + if (is_bytes_source) 893 + js_set(js, obj, "supportsBYOB", js_false); 894 + 840 895 ant_value_t pull_fn = js_mkundef(); 841 896 ant_value_t cancel_fn = js_mkundef(); 842 897 ant_value_t start_fn = js_mkundef(); ··· 925 980 mark(js, g_rs_proto); 926 981 mark(js, g_reader_proto); 927 982 mark(js, g_controller_proto); 983 + mark(js, g_rs_async_iter_proto); 928 984 } 929 985 930 986 void init_readable_stream_module(void) { ··· 959 1015 js_set(js, g, "ReadableStreamDefaultReader", reader_ctor); 960 1016 js_set_descriptor(js, g, "ReadableStreamDefaultReader", 27, JS_DESC_W | JS_DESC_C); 961 1017 1018 + g_rs_async_iter_proto = js_mkobj(js); 1019 + js_set(js, g_rs_async_iter_proto, "next", js_mkfun(js_rs_async_iter_next)); 1020 + js_set_descriptor(js, g_rs_async_iter_proto, "next", 4, JS_DESC_W | JS_DESC_C); 1021 + js_set(js, g_rs_async_iter_proto, "return", js_mkfun(js_rs_async_iter_return)); 1022 + js_set_descriptor(js, g_rs_async_iter_proto, "return", 6, JS_DESC_W | JS_DESC_C); 1023 + js_set_sym(js, g_rs_async_iter_proto, get_asyncIterator_sym(), js_mkfun(sym_this_cb)); 1024 + 962 1025 g_rs_proto = js_mkobj(js); 963 1026 js_set_getter_desc(js, g_rs_proto, "locked", 6, js_mkfun(js_rs_get_locked), JS_DESC_C); 964 1027 js_set(js, g_rs_proto, "cancel", js_mkfun(js_rs_cancel)); ··· 969 1032 970 1033 js_set(js, g_rs_proto, "values", js_mkfun(js_rs_values)); 971 1034 js_set_descriptor(js, g_rs_proto, "values", 6, JS_DESC_W | JS_DESC_C); 1035 + js_set_sym(js, g_rs_proto, get_asyncIterator_sym(), js_get(js, g_rs_proto, "values")); 972 1036 js_set_sym(js, g_rs_proto, get_toStringTag_sym(), js_mkstr(js, "ReadableStream", 14)); 973 1037 974 1038 ant_value_t rs_ctor = js_make_ctor(js, js_rs_ctor, g_rs_proto, "ReadableStream", 14);
+22 -7
src/tools/gen_builtin_bundle.js
··· 21 21 throw new Error(`Unsupported builtin module path: ${relativePath}`); 22 22 } 23 23 24 + function toAliasSpecifiers(specifier) { 25 + if (specifier.startsWith('node:')) return [specifier.slice('node:'.length)]; 26 + return []; 27 + } 28 + 24 29 function toFormat(filePath) { 25 30 if (/\.(cts|cjs)$/u.test(filePath)) return 'MODULE_EVAL_FORMAT_CJS'; 26 31 if (/\.(mts|mjs)$/u.test(filePath)) return 'MODULE_EVAL_FORMAT_ESM'; ··· 81 86 lines.push(''); 82 87 }); 83 88 84 - lines.push('static const ant_builtin_bundle_entry_t ant_builtin_bundle_data[] = {'); 89 + lines.push('static const ant_builtin_bundle_module_t ant_builtin_bundle_modules[] = {'); 85 90 bundles.forEach((bundle, index) => { 86 - lines.push( 87 - ` { ${cString(bundle.specifier)}, ${cString(bundle.specifier)}, ant_builtin_bundle_${index}, sizeof(ant_builtin_bundle_${index}), ${bundle.format} },` 88 - ); 91 + lines.push(` { ant_builtin_bundle_${index}, sizeof(ant_builtin_bundle_${index}), ${bundle.format} },`); 89 92 }); 90 93 lines.push('};'); 91 94 lines.push(''); 92 - lines.push('static const size_t ant_builtin_bundle_data_count ='); 93 - lines.push(' sizeof(ant_builtin_bundle_data) / sizeof(ant_builtin_bundle_data[0]);'); 95 + lines.push('static const ant_builtin_bundle_alias_t ant_builtin_bundle_aliases[] = {'); 96 + bundles.forEach((bundle, index) => { 97 + for (const specifier of bundle.specifiers) { 98 + lines.push(` { ${cString(specifier)}, ${specifier.length}, ${cString(bundle.specifier)}, ${index} },`); 99 + } 100 + }); 101 + lines.push('};'); 102 + lines.push(''); 103 + lines.push('static const size_t ant_builtin_bundle_module_count ='); 104 + lines.push(' sizeof(ant_builtin_bundle_modules) / sizeof(ant_builtin_bundle_modules[0]);'); 105 + lines.push(''); 106 + lines.push('static const size_t ant_builtin_bundle_alias_count ='); 107 + lines.push(' sizeof(ant_builtin_bundle_aliases) / sizeof(ant_builtin_bundle_aliases[0]);'); 94 108 lines.push(''); 95 109 lines.push('#endif'); 96 110 ··· 111 125 const specifier = toSpecifier(builtinsRoot, entryPath); 112 126 const format = toFormat(entryPath); 113 127 const bytes = await bundleBuiltin(entryPath, format); 114 - bundles.push({ entryPath, specifier, format, bytes }); 128 + const specifiers = [specifier, ...toAliasSpecifiers(specifier)]; 129 + bundles.push({ entryPath, specifier, specifiers, format, bytes }); 115 130 } 116 131 117 132 const header = generateHeader(builtinsRoot, bundles);
+6
src/types/modules/fs.d.ts
··· 59 59 function accessSync(path: string, mode?: number): void; 60 60 function readdir(path: string): Promise<string[]>; 61 61 function readdirSync(path: string): string[]; 62 + function realpath(path: string): Promise<string>; 63 + function realpathSync(path: string): string; 64 + namespace realpathSync { 65 + function native(path: string): string; 66 + } 62 67 } 63 68 64 69 declare module 'ant:fs' { ··· 110 115 function exists(path: string): Promise<boolean>; 111 116 function access(path: string, mode?: number): Promise<void>; 112 117 function readdir(path: string): Promise<string[]>; 118 + function realpath(path: string): Promise<string>; 113 119 } 114 120 115 121 declare module 'ant:fs/promises' {
+18 -20
src/types/modules/path.d.ts
··· 7 7 name: string; 8 8 } 9 9 10 - const sep: string; 11 - const delimiter: string; 12 - 13 - function basename(path: string, ext?: string): string; 14 - function dirname(path: string): string; 15 - function extname(path: string): string; 16 - function join(...paths: string[]): string; 17 - function normalize(path: string): string; 18 - function resolve(...paths: string[]): string; 19 - function relative(from: string, to: string): string; 20 - function isAbsolute(path: string): boolean; 21 - function parse(path: string): ParsedPath; 22 - function format(pathObject: Partial<ParsedPath>): string; 23 - } 24 - 25 - declare module 'ant:path' { 26 - export * from 'path'; 27 - } 10 + interface PathModule { 11 + sep: string; 12 + delimiter: string; 13 + basename(path: string, ext?: string): string; 14 + dirname(path: string): string; 15 + extname(path: string): string; 16 + join(...paths: string[]): string; 17 + normalize(path: string): string; 18 + resolve(...paths: string[]): string; 19 + relative(from: string, to: string): string; 20 + isAbsolute(path: string): boolean; 21 + parse(path: string): ParsedPath; 22 + format(pathObject: Partial<ParsedPath>): string; 23 + posix: PathModule; 24 + win32: PathModule; 25 + } 28 26 29 - declare module 'node:path' { 30 - export * from 'path'; 27 + const path: PathModule; 28 + export = path; 31 29 }
+35
tests/await_reactpromise_shape.mjs
··· 1 + function ReactPromiseShape(value) { 2 + this.value = value; 3 + } 4 + 5 + ReactPromiseShape.prototype = Object.create(Promise.prototype); 6 + ReactPromiseShape.prototype.constructor = ReactPromiseShape; 7 + let lastThis = null; 8 + ReactPromiseShape.prototype.then = function(resolve, _reject) { 9 + lastThis = this; 10 + resolve(this.value); 11 + }; 12 + 13 + async function main() { 14 + const first = new ReactPromiseShape([1]); 15 + console.log(`first.then.type:${typeof first.then}`); 16 + console.log(`first.hasOwn.then:${Object.prototype.hasOwnProperty.call(first, 'then')}`); 17 + console.log(`proto.hasOwn.then:${Object.prototype.hasOwnProperty.call(ReactPromiseShape.prototype, 'then')}`); 18 + console.log(`first.then.eq.proto:${first.then === ReactPromiseShape.prototype.then}`); 19 + 20 + const awaited = await first; 21 + console.log(`await.protoThen.isUndefined:${awaited === undefined}`); 22 + console.log(`await.protoThen.isArray:${Array.isArray(awaited)}`); 23 + console.log(`await.protoThen.0:${String(awaited?.[0])}`); 24 + console.log(`await.protoThen.thisEqInstance:${lastThis === first}`); 25 + 26 + const second = new ReactPromiseShape([2]); 27 + lastThis = null; 28 + const resolved = await Promise.resolve(second); 29 + console.log(`promiseResolve.protoThen.isUndefined:${resolved === undefined}`); 30 + console.log(`promiseResolve.protoThen.isArray:${Array.isArray(resolved)}`); 31 + console.log(`promiseResolve.protoThen.0:${String(resolved?.[0])}`); 32 + console.log(`promiseResolve.protoThen.thisEqInstance:${lastThis === second}`); 33 + } 34 + 35 + await main();
+11
tests/await_thenable.mjs
··· 1 + async function main() { 2 + const value = await { 3 + then(resolve) { 4 + resolve(123); 5 + } 6 + }; 7 + 8 + console.log(`await.thenable:${value}`); 9 + } 10 + 11 + await main();
+22
tests/http_incomingmessage_async_iter.mjs
··· 1 + import { createServer } from 'node:http'; 2 + 3 + const server = createServer(async (req, res) => { 4 + const chunks = []; 5 + 6 + for await (const chunk of req) chunks.push(chunk); 7 + 8 + res.writeHead(200, { 'content-type': 'text/plain' }); 9 + res.end(Buffer.concat(chunks).toString('utf8')); 10 + }); 11 + 12 + server.listen(0, async () => { 13 + const address = server.address(); 14 + const response = await fetch(`http://127.0.0.1:${address.port}/`, { 15 + method: 'POST', 16 + body: 'hello-body', 17 + }); 18 + 19 + console.log(`status:${response.status}`); 20 + console.log(`text:${await response.text()}`); 21 + server.close(); 22 + });
+22
tests/json_parse_flight_reviver.cjs
··· 1 + function flightReviver(key, value) { 2 + if (typeof value === 'string' && value[0] === '$' && value.length > 1) { 3 + return { 4 + ref: value, 5 + holderIsArray: Array.isArray(this), 6 + holderKeys: Object.keys(this).join(',') 7 + }; 8 + } 9 + 10 + return value; 11 + } 12 + 13 + const topLevelArray = JSON.parse('["$5","$8"]', flightReviver); 14 + console.log(`top0:${topLevelArray[0].ref}:${topLevelArray[0].holderIsArray}:${topLevelArray[0].holderKeys}`); 15 + console.log(`top1:${topLevelArray[1].ref}:${topLevelArray[1].holderIsArray}:${topLevelArray[1].holderKeys}`); 16 + 17 + const nested = JSON.parse('{"root":["$5","$8"]}', flightReviver); 18 + console.log(`nested0:${nested.root[0].ref}:${nested.root[0].holderIsArray}:${nested.root[0].holderKeys}`); 19 + console.log(`nested1:${nested.root[1].ref}:${nested.root[1].holderIsArray}:${nested.root[1].holderKeys}`); 20 + 21 + const escaped = JSON.parse('{"literal":"$$escaped"}', flightReviver); 22 + console.log(`escaped:${escaped.literal.ref || escaped.literal}`);
+19
tests/json_stringify_nested_function_replacer.cjs
··· 1 + const obj = { 2 + action() { 3 + return 1; 4 + }, 5 + child: { 6 + inner() { 7 + return 2; 8 + }, 9 + }, 10 + missing: undefined, 11 + }; 12 + 13 + const out = JSON.stringify(obj, (_key, value) => { 14 + if (typeof value === 'function') return `[fn:${value.name || 'anonymous'}]`; 15 + if (value === undefined) return '[undef]'; 16 + return value; 17 + }); 18 + 19 + console.log(out);
+16
tests/json_stringify_root_replacer.js
··· 1 + function show(label, value) { 2 + const out = JSON.stringify(value, function (key, current) { 3 + if (key === '') { 4 + if (typeof current === 'function') return { root: 'function' }; 5 + if (current === undefined) return { root: 'undefined' }; 6 + if (typeof current === 'symbol') return { root: 'symbol' }; 7 + } 8 + return current; 9 + }); 10 + 11 + console.log(`${label}:${out}`); 12 + } 13 + 14 + show('fn', function demo() {}); 15 + show('undef', undefined); 16 + show('sym', Symbol.for('demo'));
+19
tests/promise_expando.cjs
··· 1 + const p = Promise.resolve(123); 2 + 3 + console.log(`t0.status:${p.status}`); 4 + p.status = 'pending'; 5 + console.log(`t1.status:${p.status}`); 6 + p.value = 123; 7 + console.log(`t2.value:${p.value}`); 8 + 9 + p.then((value) => { 10 + p.status = 'fulfilled'; 11 + p.value = value; 12 + console.log(`then.status:${p.status}`); 13 + console.log(`then.value:${p.value}`); 14 + }); 15 + 16 + Promise.resolve().then(() => { 17 + console.log(`micro.status:${p.status}`); 18 + console.log(`micro.value:${p.value}`); 19 + });
+28
tests/proxy_has_own.cjs
··· 1 + const target = {}; 2 + function makeClientCounter() { 3 + return function ClientCounter() {}; 4 + } 5 + 6 + const proxy = new Proxy(target, { 7 + get(_target, key) { 8 + if (key === 'ClientCounter') return makeClientCounter(); 9 + return undefined; 10 + }, 11 + getOwnPropertyDescriptor(_target, key) { 12 + if (key === 'ClientCounter') { 13 + return { 14 + value: makeClientCounter(), 15 + writable: false, 16 + configurable: false, 17 + enumerable: false, 18 + }; 19 + } 20 + return undefined; 21 + }, 22 + }); 23 + 24 + console.log(`get:${typeof proxy.ClientCounter}`); 25 + console.log(`hasOwn:${Object.prototype.hasOwnProperty.call(proxy, 'ClientCounter')}`); 26 + console.log(`objHasOwn:${Object.hasOwn(proxy, 'ClientCounter')}`); 27 + const desc = Object.getOwnPropertyDescriptor(proxy, 'ClientCounter'); 28 + console.log(`desc.value:${typeof desc?.value}`);
+96
tests/react_like_async_iterable_buffer.js
··· 1 + class ReactPromise { 2 + constructor(status, value, reason) { 3 + this.status = status; 4 + this.value = value; 5 + this.reason = reason; 6 + } 7 + } 8 + 9 + function createPendingChunk(response) { 10 + return new ReactPromise("pending", null, null); 11 + } 12 + 13 + function createResolvedIteratorResultChunk(response, value, done) { 14 + return new ReactPromise( 15 + "resolved_model", 16 + (done ? "{\"done\":true,\"value\":" : "{\"done\":false,\"value\":") + value + "}", 17 + response 18 + ); 19 + } 20 + 21 + function resolveModelChunk(response, chunk, value) { 22 + if ("pending" !== chunk.status) { 23 + return chunk.reason.enqueueModel(value); 24 + } 25 + 26 + chunk.status = "resolved_model"; 27 + chunk.value = value; 28 + chunk.reason = response; 29 + } 30 + 31 + function resolveIteratorResultChunk(response, chunk, value, done) { 32 + resolveModelChunk( 33 + response, 34 + chunk, 35 + (done ? "{\"done\":true,\"value\":" : "{\"done\":false,\"value\":") + value + "}" 36 + ); 37 + } 38 + 39 + function startAsyncIterable(response) { 40 + const buffer = []; 41 + let closed = false; 42 + let nextWriteIndex = 0; 43 + 44 + const iterable = {}; 45 + iterable[Symbol.asyncIterator] = function () { 46 + let nextReadIndex = 0; 47 + return { 48 + next(arg) { 49 + if (arg !== undefined) throw new Error("bad arg"); 50 + if (nextReadIndex === buffer.length) { 51 + if (closed) { 52 + return new ReactPromise("fulfilled", { done: true, value: undefined }, null); 53 + } 54 + buffer[nextReadIndex] = createPendingChunk(response); 55 + } 56 + return buffer[nextReadIndex++]; 57 + } 58 + }; 59 + }; 60 + 61 + return { 62 + buffer, 63 + iterator: iterable[Symbol.asyncIterator](), 64 + enqueueModel(value) { 65 + if (nextWriteIndex === buffer.length) { 66 + buffer[nextWriteIndex] = createResolvedIteratorResultChunk(response, value, false); 67 + } else { 68 + resolveIteratorResultChunk(response, buffer[nextWriteIndex], value, false); 69 + } 70 + nextWriteIndex++; 71 + } 72 + }; 73 + } 74 + 75 + const response = { 76 + seen: [], 77 + enqueueModel(value) { 78 + this.seen.push(value); 79 + } 80 + }; 81 + 82 + const state = startAsyncIterable(response); 83 + 84 + console.log(`len0:${state.buffer.length}`); 85 + const first = state.iterator.next(); 86 + console.log(`len1:${state.buffer.length}`); 87 + console.log(`first.status:${first.status}`); 88 + 89 + state.enqueueModel("1"); 90 + console.log(`len2:${state.buffer.length}`); 91 + console.log(`slot0.status:${state.buffer[0].status}`); 92 + console.log(`slot0.reasonType:${typeof state.buffer[0].reason}`); 93 + 94 + state.enqueueModel("2"); 95 + console.log(`len3:${state.buffer.length}`); 96 + console.log(`seen:${response.seen.join(",")}`);
+132
tests/react_like_async_iterable_controller.js
··· 1 + class ReactPromise { 2 + constructor(status, value, reason) { 3 + this.status = status; 4 + this.value = value; 5 + this.reason = reason; 6 + } 7 + } 8 + 9 + function createPendingChunk() { 10 + return new ReactPromise("pending", null, null); 11 + } 12 + 13 + function createResolvedIteratorResultChunk(response, value, done) { 14 + return new ReactPromise( 15 + "resolved_model", 16 + (done ? "{\"done\":true,\"value\":" : "{\"done\":false,\"value\":") + value + "}", 17 + response 18 + ); 19 + } 20 + 21 + function resolveModelChunk(response, chunk, value) { 22 + if ("pending" !== chunk.status) { 23 + chunk.reason.enqueueModel(value); 24 + return; 25 + } 26 + 27 + chunk.status = "resolved_model"; 28 + chunk.value = value; 29 + chunk.reason = response; 30 + } 31 + 32 + function resolveIteratorResultChunk(response, chunk, value, done) { 33 + resolveModelChunk( 34 + response, 35 + chunk, 36 + (done ? "{\"done\":true,\"value\":" : "{\"done\":false,\"value\":") + value + "}" 37 + ); 38 + } 39 + 40 + function startAsyncIterable(response, id, iterator) { 41 + const buffer = []; 42 + let closed = false; 43 + let nextWriteIndex = 0; 44 + const iterable = {}; 45 + 46 + iterable[Symbol.asyncIterator] = function () { 47 + let nextReadIndex = 0; 48 + return { 49 + next(arg) { 50 + if (arg !== undefined) throw new Error("bad arg"); 51 + if (nextReadIndex === buffer.length) { 52 + if (closed) { 53 + return new ReactPromise("fulfilled", { done: true, value: undefined }, null); 54 + } 55 + buffer[nextReadIndex] = createPendingChunk(response); 56 + } 57 + return buffer[nextReadIndex++]; 58 + } 59 + }; 60 + }; 61 + 62 + const controller = { 63 + enqueueValue(value) { 64 + if (nextWriteIndex === buffer.length) { 65 + buffer[nextWriteIndex] = new ReactPromise("fulfilled", { done: false, value }, null); 66 + } else { 67 + const chunk = buffer[nextWriteIndex]; 68 + chunk.status = "fulfilled"; 69 + chunk.value = { done: false, value }; 70 + chunk.reason = null; 71 + } 72 + nextWriteIndex++; 73 + }, 74 + enqueueModel(value) { 75 + if (nextWriteIndex === buffer.length) { 76 + buffer[nextWriteIndex] = createResolvedIteratorResultChunk(response, value, false); 77 + } else { 78 + resolveIteratorResultChunk(response, buffer[nextWriteIndex], value, false); 79 + } 80 + nextWriteIndex++; 81 + }, 82 + close(value) { 83 + if (!closed) { 84 + closed = true; 85 + if (nextWriteIndex === buffer.length) { 86 + buffer[nextWriteIndex] = createResolvedIteratorResultChunk(response, value, true); 87 + } else { 88 + resolveIteratorResultChunk(response, buffer[nextWriteIndex], value, true); 89 + } 90 + nextWriteIndex++; 91 + } 92 + }, 93 + error(error) { 94 + throw error; 95 + } 96 + }; 97 + 98 + response._chunks.set(id, new ReactPromise("fulfilled", iterator ? iterable[Symbol.asyncIterator]() : iterable, controller)); 99 + return { buffer, controller }; 100 + } 101 + 102 + function log(label, fn) { 103 + try { 104 + const out = fn(); 105 + console.log(`${label}:${out}`); 106 + } catch (error) { 107 + console.log(`${label}:ERR:${error && error.message ? error.message : String(error)}`); 108 + } 109 + } 110 + 111 + const response = { _chunks: new Map() }; 112 + const id = 1; 113 + 114 + startAsyncIterable(response, id, true); 115 + const chunk = response._chunks.get(id); 116 + 117 + log("reasonType", () => typeof chunk.reason); 118 + log("enqueueModelType", () => typeof chunk.reason.enqueueModel); 119 + 120 + resolveModelChunk(response, chunk, "\"1\""); 121 + console.log(`bufferLength:${chunk.reason ? "controller" : "missing"}`); 122 + 123 + const response2 = { _chunks: new Map() }; 124 + const id2 = 2; 125 + response2._chunks.set(id2, new ReactPromise("resolved_model", "\"boot\"", response2)); 126 + const badChunk = response2._chunks.get(id2); 127 + log("badReasonType", () => typeof badChunk.reason); 128 + log("badEnqueueModelType", () => typeof badChunk.reason.enqueueModel); 129 + log("badResolve", () => { 130 + resolveModelChunk(response2, badChunk, "\"next\""); 131 + return "ok"; 132 + });
+16
tests/react_like_bigint_parse.js
··· 1 + const samples = [ 2 + "$n0", 3 + "$n1", 4 + "$n123", 5 + "$n-42", 6 + "$n0x10", 7 + ]; 8 + 9 + for (const value of samples) { 10 + try { 11 + const parsed = BigInt(value.slice(2)); 12 + console.log(`${value} -> ${parsed.toString()}`); 13 + } catch (error) { 14 + console.log(`${value} -> error:${error && error.message ? error.message : String(error)}`); 15 + } 16 + }
+181
tests/react_like_fetch_row_parser.cjs
··· 1 + const http = require('node:http'); 2 + 3 + class ReactPromise { 4 + constructor(status, value, reason) { 5 + this.status = status; 6 + this.value = value; 7 + this.reason = reason; 8 + } 9 + } 10 + 11 + function createPendingChunk() { 12 + return new ReactPromise('pending', null, null); 13 + } 14 + 15 + function createResolvedModelChunk(response, value) { 16 + return new ReactPromise('resolved_model', value, response); 17 + } 18 + 19 + function resolveModelChunk(response, chunk, value) { 20 + if ('pending' !== chunk.status) { 21 + chunk.reason.enqueueModel(value); 22 + return; 23 + } 24 + chunk.status = 'resolved_model'; 25 + chunk.value = value; 26 + chunk.reason = response; 27 + } 28 + 29 + function startAsyncIterable(response, id) { 30 + const seen = []; 31 + const controller = { 32 + enqueueModel(value) { 33 + seen.push(value); 34 + }, 35 + close(value) { 36 + seen.push(`close:${value}`); 37 + }, 38 + error(error) { 39 + seen.push(`error:${error && error.message ? error.message : String(error)}`); 40 + } 41 + }; 42 + response.controllers.set(id, { seen, controller }); 43 + response._chunks.set(id, new ReactPromise('fulfilled', {}, controller)); 44 + } 45 + 46 + function processFullBinaryRow(response, id, tag, buffer, chunk) { 47 + let row = ''; 48 + const decoder = new TextDecoder(); 49 + for (let i = 0; i < buffer.length; i++) row += decoder.decode(buffer[i], { stream: true }); 50 + row += decoder.decode(chunk); 51 + 52 + switch (tag) { 53 + case 120: 54 + startAsyncIterable(response, id); 55 + break; 56 + case 67: { 57 + const resolved = response._chunks.get(id); 58 + if (resolved && resolved.status === 'fulfilled') resolved.reason.close(row === '' ? '"$undefined"' : row); 59 + break; 60 + } 61 + default: { 62 + const existing = response._chunks.get(id); 63 + if (existing) { 64 + resolveModelChunk(response, existing, row); 65 + } else { 66 + response._chunks.set(id, createResolvedModelChunk(response, row)); 67 + } 68 + break; 69 + } 70 + } 71 + } 72 + 73 + async function parseResponseBody(body) { 74 + const reader = body.getReader(); 75 + const response = { 76 + _chunks: new Map(), 77 + controllers: new Map() 78 + }; 79 + 80 + let rowState = 0; 81 + let rowID = 0; 82 + let rowTag = 0; 83 + let rowLength = 0; 84 + const buffer = []; 85 + 86 + while (true) { 87 + const { value, done } = await reader.read(); 88 + if (done) break; 89 + 90 + let i = 0; 91 + while (i < value.length) { 92 + let lastIdx = -1; 93 + switch (rowState) { 94 + case 0: { 95 + const ch = value[i++]; 96 + if (ch === 58) rowState = 1; 97 + else rowID = (rowID << 4) | (ch > 96 ? ch - 87 : ch - 48); 98 + continue; 99 + } 100 + case 1: { 101 + rowState = value[i]; 102 + if ( 103 + rowState === 84 || rowState === 65 || rowState === 79 || rowState === 111 || 104 + rowState === 85 || rowState === 83 || rowState === 115 || rowState === 76 || 105 + rowState === 108 || rowState === 71 || rowState === 103 || rowState === 77 || 106 + rowState === 109 || rowState === 86 107 + ) { 108 + rowTag = rowState; 109 + rowState = 2; 110 + i++; 111 + } else if ( 112 + (rowState > 64 && rowState < 91) || rowState === 35 || rowState === 114 || rowState === 120 113 + ) { 114 + rowTag = rowState; 115 + rowState = 3; 116 + i++; 117 + } else { 118 + rowTag = 0; 119 + rowState = 3; 120 + } 121 + continue; 122 + } 123 + case 2: { 124 + const ch = value[i++]; 125 + if (ch === 44) rowState = 4; 126 + else rowLength = (rowLength << 4) | (ch > 96 ? ch - 87 : ch - 48); 127 + continue; 128 + } 129 + case 3: 130 + lastIdx = value.indexOf(10, i); 131 + break; 132 + case 4: 133 + lastIdx = i + rowLength; 134 + if (lastIdx > value.length) lastIdx = -1; 135 + break; 136 + } 137 + 138 + const offset = value.byteOffset + i; 139 + if (lastIdx > -1) { 140 + const rowChunk = new Uint8Array(value.buffer, offset, lastIdx - i); 141 + processFullBinaryRow(response, rowID, rowTag, buffer, rowChunk); 142 + i = lastIdx; 143 + if (rowState === 3) i++; 144 + rowLength = 0; 145 + rowID = 0; 146 + rowTag = 0; 147 + rowState = 0; 148 + buffer.length = 0; 149 + } else { 150 + const partial = new Uint8Array(value.buffer, offset, value.byteLength - i); 151 + buffer.push(partial); 152 + rowLength -= partial.byteLength; 153 + break; 154 + } 155 + } 156 + } 157 + 158 + return response; 159 + } 160 + 161 + const server = http.createServer((_req, res) => { 162 + res.writeHead(200, { 'content-type': 'application/octet-stream' }); 163 + res.write('1:x\n1:"a'); 164 + res.write('"\n1:"b"\n'); 165 + res.end(); 166 + }); 167 + 168 + server.listen(0, async () => { 169 + const { port } = server.address(); 170 + try { 171 + const res = await fetch(`http://127.0.0.1:${port}/`); 172 + const parsed = await parseResponseBody(res.body); 173 + const state = parsed.controllers.get(1); 174 + console.log(`controllerType:${typeof (state && state.controller)}`); 175 + console.log(`seen:${state ? state.seen.join(',') : 'missing'}`); 176 + } catch (error) { 177 + console.log(`ERR:${error && error.stack ? error.stack : String(error)}`); 178 + } finally { 179 + server.close(); 180 + } 181 + });
+193
tests/react_like_http_mixed_row_parser.cjs
··· 1 + const http = require('node:http'); 2 + 3 + class ReactPromise { 4 + constructor(status, value, reason) { 5 + this.status = status; 6 + this.value = value; 7 + this.reason = reason; 8 + } 9 + } 10 + 11 + function createPendingChunk() { 12 + return new ReactPromise('pending', null, null); 13 + } 14 + 15 + function createResolvedModelChunk(response, value) { 16 + return new ReactPromise('resolved_model', value, response); 17 + } 18 + 19 + function resolveModelChunk(response, chunk, value) { 20 + if ('pending' !== chunk.status) { 21 + chunk.reason.enqueueModel(value); 22 + return; 23 + } 24 + 25 + chunk.status = 'resolved_model'; 26 + chunk.value = value; 27 + chunk.reason = response; 28 + } 29 + 30 + function startAsyncIterable(response, id) { 31 + const seen = []; 32 + const controller = { 33 + enqueueModel(value) { 34 + seen.push(value); 35 + }, 36 + close(value) { 37 + seen.push(`close:${value}`); 38 + }, 39 + error(error) { 40 + seen.push(`error:${error && error.message ? error.message : String(error)}`); 41 + } 42 + }; 43 + 44 + response.controllers.set(id, { seen, controller }); 45 + response._chunks.set(id, new ReactPromise('fulfilled', {}, controller)); 46 + } 47 + 48 + function processFullBinaryRow(response, id, tag, buffer, chunk) { 49 + let row = ''; 50 + const decoder = new TextDecoder(); 51 + 52 + for (let i = 0; i < buffer.length; i++) row += decoder.decode(buffer[i], { stream: true }); 53 + row += decoder.decode(chunk); 54 + 55 + switch (tag) { 56 + case 84: { 57 + const existing = response._chunks.get(id); 58 + if (existing && existing.status !== 'pending') existing.reason.enqueueValue(row); 59 + else response._chunks.set(id, new ReactPromise('fulfilled', row, null)); 60 + return; 61 + } 62 + case 120: 63 + startAsyncIterable(response, id); 64 + return; 65 + case 67: { 66 + const existing = response._chunks.get(id); 67 + if (existing && existing.status === 'fulfilled') existing.reason.close(row === '' ? '"$undefined"' : row); 68 + return; 69 + } 70 + default: { 71 + const existing = response._chunks.get(id); 72 + if (existing) resolveModelChunk(response, existing, row); 73 + else response._chunks.set(id, createResolvedModelChunk(response, row)); 74 + return; 75 + } 76 + } 77 + } 78 + 79 + async function parseResponseBody(body) { 80 + const reader = body.getReader(); 81 + const response = { 82 + _chunks: new Map(), 83 + controllers: new Map() 84 + }; 85 + 86 + let rowState = 0; 87 + let rowID = 0; 88 + let rowTag = 0; 89 + let rowLength = 0; 90 + const buffer = []; 91 + 92 + while (true) { 93 + const { value, done } = await reader.read(); 94 + if (done) break; 95 + 96 + let i = 0; 97 + while (i < value.length) { 98 + let lastIdx = -1; 99 + switch (rowState) { 100 + case 0: { 101 + const ch = value[i++]; 102 + if (ch === 58) rowState = 1; 103 + else rowID = (rowID << 4) | (ch > 96 ? ch - 87 : ch - 48); 104 + continue; 105 + } 106 + case 1: { 107 + rowState = value[i]; 108 + if ( 109 + rowState === 84 || rowState === 65 || rowState === 79 || rowState === 111 || 110 + rowState === 85 || rowState === 83 || rowState === 115 || rowState === 76 || 111 + rowState === 108 || rowState === 71 || rowState === 103 || rowState === 77 || 112 + rowState === 109 || rowState === 86 113 + ) { 114 + rowTag = rowState; 115 + rowState = 2; 116 + i++; 117 + } else if ( 118 + (rowState > 64 && rowState < 91) || rowState === 35 || rowState === 114 || rowState === 120 119 + ) { 120 + rowTag = rowState; 121 + rowState = 3; 122 + i++; 123 + } else { 124 + rowTag = 0; 125 + rowState = 3; 126 + } 127 + continue; 128 + } 129 + case 2: { 130 + const ch = value[i++]; 131 + if (ch === 44) rowState = 4; 132 + else rowLength = (rowLength << 4) | (ch > 96 ? ch - 87 : ch - 48); 133 + continue; 134 + } 135 + case 3: 136 + lastIdx = value.indexOf(10, i); 137 + break; 138 + case 4: 139 + lastIdx = i + rowLength; 140 + if (lastIdx > value.length) lastIdx = -1; 141 + break; 142 + } 143 + 144 + const offset = value.byteOffset + i; 145 + if (lastIdx > -1) { 146 + const rowChunk = new Uint8Array(value.buffer, offset, lastIdx - i); 147 + processFullBinaryRow(response, rowID, rowTag, buffer, rowChunk); 148 + i = lastIdx; 149 + if (rowState === 3) i++; 150 + rowLength = 0; 151 + rowID = 0; 152 + rowTag = 0; 153 + rowState = 0; 154 + buffer.length = 0; 155 + } else { 156 + const partial = new Uint8Array(value.buffer, offset, value.byteLength - i); 157 + buffer.push(partial); 158 + rowLength -= partial.byteLength; 159 + break; 160 + } 161 + } 162 + } 163 + 164 + return response; 165 + } 166 + 167 + const server = http.createServer((_req, res) => { 168 + res.writeHead(200, { 'content-type': 'application/octet-stream' }); 169 + res.write('2:T5,he'); 170 + res.write('llo1:x\n1:"a'); 171 + res.write('"\n1:"b"\n'); 172 + res.end(); 173 + }); 174 + 175 + server.listen(0, async () => { 176 + const { port } = server.address(); 177 + 178 + try { 179 + const res = await fetch(`http://127.0.0.1:${port}/`); 180 + const parsed = await parseResponseBody(res.body); 181 + const prelude = parsed._chunks.get(2); 182 + const iterable = parsed.controllers.get(1); 183 + 184 + console.log(`preludeStatus:${prelude ? prelude.status : 'missing'}`); 185 + console.log(`preludeValue:${prelude ? prelude.value : 'missing'}`); 186 + console.log(`controllerType:${typeof (iterable && iterable.controller)}`); 187 + console.log(`seen:${iterable ? iterable.seen.join(',') : 'missing'}`); 188 + } catch (error) { 189 + console.log(`ERR:${error && error.stack ? error.stack : String(error)}`); 190 + } finally { 191 + server.close(); 192 + } 193 + });
+39
tests/react_like_stream_controller.js
··· 1 + class ReactPromise { 2 + constructor(status, value, reason) { 3 + this.status = status; 4 + this.value = value; 5 + this.reason = reason; 6 + } 7 + } 8 + 9 + function resolveModelChunk(chunk, value) { 10 + if ("pending" !== chunk.status) { 11 + chunk.reason.enqueueModel(value); 12 + return; 13 + } 14 + 15 + throw new Error("unexpected pending chunk"); 16 + } 17 + 18 + const seen = []; 19 + 20 + function makeController() { 21 + return { 22 + enqueueValue(value) { 23 + seen.push(`value:${value}`); 24 + }, 25 + enqueueModel(value) { 26 + seen.push(`model:${value}`); 27 + }, 28 + close() { 29 + seen.push("close"); 30 + }, 31 + error(error) { 32 + seen.push(`error:${error && error.message ? error.message : String(error)}`); 33 + } 34 + }; 35 + } 36 + 37 + const chunk = new ReactPromise("fulfilled", null, makeController()); 38 + resolveModelChunk(chunk, "ok"); 39 + console.log(seen.join(","));
+135
tests/react_server_action_encode_trace.mjs
··· 1 + const knownServerReferences = new WeakMap(); 2 + const boundCache = new WeakMap(); 3 + const FunctionBind = Function.prototype.bind; 4 + const ArraySlice = Array.prototype.slice; 5 + 6 + function encodeFormData(reference) { 7 + let resolve; 8 + let reject; 9 + const thenable = new Promise((res, rej) => { 10 + resolve = res; 11 + reject = rej; 12 + }); 13 + 14 + Promise.resolve(reference.bound).then((bound) => { 15 + const data = new FormData(); 16 + data.append('id', reference.id); 17 + data.append('boundCount', String(bound.length)); 18 + thenable.status = 'fulfilled'; 19 + thenable.value = data; 20 + resolve(data); 21 + }, (error) => { 22 + thenable.status = 'rejected'; 23 + thenable.reason = error; 24 + reject(error); 25 + }); 26 + 27 + return thenable; 28 + } 29 + 30 + function defaultEncodeFormAction(identifierPrefix) { 31 + let referenceClosure = knownServerReferences.get(this); 32 + let data = null; 33 + 34 + if (referenceClosure.bound !== null) { 35 + data = boundCache.get(referenceClosure); 36 + if (!data) { 37 + data = encodeFormData({ 38 + id: referenceClosure.id, 39 + bound: referenceClosure.bound, 40 + }); 41 + boundCache.set(referenceClosure, data); 42 + } 43 + 44 + if (data.status === 'rejected') throw data.reason; 45 + if (data.status !== 'fulfilled') throw data; 46 + 47 + referenceClosure = data.value; 48 + const prefixedData = new FormData(); 49 + referenceClosure.forEach((value, key) => { 50 + prefixedData.append(`$ACTION_${identifierPrefix}:${key}`, value); 51 + }); 52 + data = prefixedData; 53 + referenceClosure = `$ACTION_REF_${identifierPrefix}`; 54 + } else { 55 + referenceClosure = `$ACTION_ID_${referenceClosure.id}`; 56 + } 57 + 58 + return { 59 + name: referenceClosure, 60 + method: 'POST', 61 + encType: 'multipart/form-data', 62 + data, 63 + }; 64 + } 65 + 66 + function bind() { 67 + const referenceClosure = knownServerReferences.get(this); 68 + if (!referenceClosure) return FunctionBind.apply(this, arguments); 69 + 70 + const newFn = referenceClosure.originalBind.apply(this, arguments); 71 + const args = ArraySlice.call(arguments, 1); 72 + const boundPromise = referenceClosure.bound !== null 73 + ? Promise.resolve(referenceClosure.bound).then((boundArgs) => boundArgs.concat(args)) 74 + : Promise.resolve(args); 75 + 76 + knownServerReferences.set(newFn, { 77 + id: referenceClosure.id, 78 + originalBind: newFn.bind, 79 + bound: boundPromise, 80 + }); 81 + 82 + Object.defineProperties(newFn, { 83 + $$FORM_ACTION: { value: this.$$FORM_ACTION }, 84 + bind: { value: bind }, 85 + }); 86 + 87 + return newFn; 88 + } 89 + 90 + function registerBoundServerReference(reference, id, bound) { 91 + knownServerReferences.set(reference, { 92 + id, 93 + originalBind: reference.bind, 94 + bound, 95 + }); 96 + 97 + Object.defineProperties(reference, { 98 + $$FORM_ACTION: { value: defaultEncodeFormAction }, 99 + bind: { value: bind }, 100 + }); 101 + } 102 + 103 + async function action(x) { 104 + return x; 105 + } 106 + 107 + registerBoundServerReference(action, 'demo', null); 108 + 109 + const bound = action.bind(null, 1); 110 + console.log(`bound.formAction.type:${typeof bound.$$FORM_ACTION}`); 111 + 112 + try { 113 + const info = bound.$$FORM_ACTION('demo'); 114 + console.log(`first.name:${info.name}`); 115 + } catch (error) { 116 + console.log(`first.throw.type:${typeof error}`); 117 + console.log(`first.throw.then:${typeof error?.then}`); 118 + console.log(`first.throw.status:${error?.status ?? 'missing'}`); 119 + } 120 + 121 + await Promise.resolve(); 122 + 123 + try { 124 + const info = bound.$$FORM_ACTION('demo'); 125 + console.log(`second.name:${info.name}`); 126 + console.log(`second.method:${info.method}`); 127 + console.log(`second.encType:${info.encType}`); 128 + const pairs = []; 129 + info.data?.forEach((value, key) => { 130 + pairs.push(`${key}=${value}`); 131 + }); 132 + console.log(`second.data:${pairs.join(',')}`); 133 + } catch (error) { 134 + console.log(`second.throw:${error?.message ?? String(error)}`); 135 + }
+67
tests/server_reference_bind_trace.mjs
··· 1 + const knownServerReferences = new WeakMap(); 2 + const FunctionBind = Function.prototype.bind; 3 + const ArraySlice = Array.prototype.slice; 4 + 5 + function defaultEncodeFormAction() { 6 + return { name: '$ACTION_ID_demo', method: 'POST' }; 7 + } 8 + 9 + function registerBoundServerReference(reference, id) { 10 + if (knownServerReferences.has(reference)) return; 11 + 12 + knownServerReferences.set(reference, { 13 + id, 14 + originalBind: reference.bind, 15 + bound: null, 16 + }); 17 + 18 + Object.defineProperties(reference, { 19 + $$FORM_ACTION: { 20 + value: defaultEncodeFormAction, 21 + }, 22 + bind: { 23 + value: bind, 24 + }, 25 + }); 26 + } 27 + 28 + function bind() { 29 + const referenceClosure = knownServerReferences.get(this); 30 + if (!referenceClosure) return FunctionBind.apply(this, arguments); 31 + 32 + const newFn = referenceClosure.originalBind.apply(this, arguments); 33 + const args = ArraySlice.call(arguments, 1); 34 + 35 + knownServerReferences.set(newFn, { 36 + id: referenceClosure.id, 37 + originalBind: newFn.bind, 38 + bound: Promise.resolve(args), 39 + }); 40 + 41 + Object.defineProperties(newFn, { 42 + $$FORM_ACTION: { 43 + value: this.$$FORM_ACTION, 44 + }, 45 + bind: { 46 + value: bind, 47 + }, 48 + }); 49 + 50 + return newFn; 51 + } 52 + 53 + async function action(x) { 54 + return x; 55 + } 56 + 57 + registerBoundServerReference(action, 'demo'); 58 + 59 + console.log(`action.bind.eq.custom:${action.bind === bind}`); 60 + console.log(`action.formAction.type:${typeof action.$$FORM_ACTION}`); 61 + 62 + const bound = action.bind(null, 1); 63 + 64 + console.log(`bound.type:${typeof bound}`); 65 + console.log(`bound.bind.eq.custom:${bound.bind === bind}`); 66 + console.log(`bound.formAction.type:${typeof bound.$$FORM_ACTION}`); 67 + console.log(`bound.formAction.name:${bound.$$FORM_ACTION ? bound.$$FORM_ACTION().name : 'missing'}`);
+18
tests/test_map.js
··· 39 39 console.log('Get null key:', map.get(null)); // Should be 'null key' 40 40 console.log('Final map size:', map.size); // Should be 3 41 41 42 + const obj1 = {}; 43 + const obj2 = {}; 44 + map.set(obj1, 'obj1'); 45 + map.set(obj2, 'obj2'); 46 + console.log('Object key 1:', map.get(obj1)); 47 + console.log('Object key 2:', map.get(obj2)); 48 + console.log('Distinct object keys:', map.get(obj1) !== map.get(obj2)); 49 + 50 + map.set('1', 'string one'); 51 + map.set(1, 'number one'); 52 + console.log('String/number split:', map.get('1'), map.get(1)); 53 + 54 + map.set(NaN, 'nan key'); 55 + console.log('NaN key:', map.get(NaN)); 56 + 57 + map.set(-0, 'minus zero'); 58 + console.log('Zero key normalized:', map.get(0), map.get(-0)); 59 + 42 60 console.log('Map tests completed!');