diff --git a/src/modules/timer.c b/src/modules/timer.c index a2e21a8..ae27b6d 100644 --- a/src/modules/timer.c +++ b/src/modules/timer.c @@ -19,6 +19,7 @@ typedef struct timer_entry { uv_timer_t handle; + ant_value_t obj; ant_value_t callback; ant_value_t *args; int nargs; @@ -92,6 +93,8 @@ static void remove_timer_entry(timer_entry_t *entry) { if (entry->prev) entry->prev->next = entry->next; else timer_state.timers = entry->next; if (entry->next) entry->next->prev = entry->prev; + entry->next = NULL; + entry->prev = NULL; } static int timer_entry_is_registered(timer_entry_t *entry) { @@ -206,49 +209,63 @@ static ant_value_t js_timer_has_ref(ant_t *js, ant_value_t *args, int nargs) { return js_bool(uv_has_ref((const uv_handle_t *)&entry->handle) != 0); } +static int timer_id_from_arg(ant_t *js, ant_value_t arg) { + if (vtype(arg) == T_NUM) return (int)js_getnum(arg); + return (int)js_getnum(js_get_slot(arg, SLOT_DATA)); +} + +static void timer_close_cb(uv_handle_t *h) { + timer_entry_t *entry = (timer_entry_t *)h->data; + + if (!entry) return; + if (timer_entry_is_registered(entry)) remove_timer_entry(entry); + + entry->closed = 1; + entry->active = 0; + entry->obj = js_mkundef(); + timer_release_callback_args(entry); + free(entry); +} + +static void timer_close_entry(timer_entry_t *entry) { + if (!entry || entry->closed) return; + if (entry->active) { + entry->active = 0; + timer_state.active_timer_count--; + } + if (!uv_is_closing((uv_handle_t *)&entry->handle)) + uv_close((uv_handle_t *)&entry->handle, timer_close_cb); +} + +static void timer_object_finalize(ant_t *js, ant_object_t *obj) { + ant_value_t timer_obj = js_obj_from_ptr(obj); + int timer_id = (int)js_getnum(js_get_slot(timer_obj, SLOT_DATA)); + timer_entry_t *entry = find_timer_entry_by_id(timer_id); + if (entry) timer_close_entry(entry); +} + static ant_value_t timer_make_object( - ant_t *js, - int id, - double delay_ms, - int is_interval, - ant_value_t callback, - ant_value_t timer_args + ant_t *js, timer_entry_t *entry, + double delay_ms, int is_interval, ant_value_t timer_args ) { ant_value_t obj = js_mkobj(js); ant_value_t proto = is_interval ? g_interval_proto : g_timeout_proto; - if (is_object_type(proto)) js_set_proto_init(obj, proto); js_set(js, obj, "delay", js_mknum(delay_ms)); js_set(js, obj, "repeat", is_interval ? js_mknum(delay_ms) : js_mknull()); - - js_set(js, obj, "callback", callback); + js_set(js, obj, "callback", entry->callback); js_set_descriptor(js, obj, "callback", 8, JS_DESC_W | JS_DESC_C); - js_set_slot(obj, SLOT_DATA, js_mknum((double)id)); + js_set_slot(obj, SLOT_DATA, js_mknum((double)entry->timer_id)); js_set_slot_wb(js, obj, SLOT_AUX, timer_args); js_set_sym(js, obj, get_toPrimitive_sym(), js_mkfun(timer_to_primitive)); + js_set_finalizer(obj, timer_object_finalize); + entry->obj = obj; return obj; } -static int timer_id_from_arg(ant_t *js, ant_value_t arg) { - if (vtype(arg) == T_NUM) return (int)js_getnum(arg); - return (int)js_getnum(js_get_slot(arg, SLOT_DATA)); -} - -static void timer_close_cb(uv_handle_t *h) { - timer_entry_t *entry = (timer_entry_t *)h->data; - if (!entry) return; - if (entry->closed) return; - if (timer_entry_is_registered(entry)) remove_timer_entry(entry); - entry->closed = 1; - entry->active = 0; - timer_release_callback_args(entry); - entry->next = NULL; - entry->prev = NULL; -} - static void timer_callback(uv_timer_t *handle) { timer_entry_t *entry = (timer_entry_t *)handle->data; if (!entry || entry->closed || !timer_entry_is_registered(entry) || !entry->active) return; @@ -328,7 +345,7 @@ static ant_value_t js_set_timeout(ant_t *js, ant_value_t *args, int nargs) { timer_state.active_timer_count++; uv_timer_start(&entry->handle, timer_callback, ms, 0); - return timer_make_object(js, entry->timer_id, delay_ms, 0, callback, timer_args); + return timer_make_object(js, entry, delay_ms, 0, timer_args); } // setInterval(callback, delay, ...args) @@ -363,7 +380,7 @@ static ant_value_t js_set_interval(ant_t *js, ant_value_t *args, int nargs) { timer_state.active_timer_count++; uv_timer_start(&entry->handle, timer_callback, ms, ms); - return timer_make_object(js, entry->timer_id, delay_ms, 1, callback, timer_args); + return timer_make_object(js, entry, delay_ms, 1, timer_args); } // clearTimeout(timerId | timerObject) @@ -373,11 +390,7 @@ static ant_value_t js_clear_timeout(ant_t *js, ant_value_t *args, int nargs) { for (timer_entry_t *entry = timer_state.timers; entry != NULL; entry = entry->next) { if (entry->timer_id == timer_id && !entry->closed) { - if (entry->active) { - entry->active = 0; - timer_state.active_timer_count--; - } - if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close((uv_handle_t *)&entry->handle, timer_close_cb); + timer_close_entry(entry); break; }} @@ -924,6 +937,7 @@ void gc_mark_timers(ant_t *js, gc_mark_fn mark) { if (is_object_type(g_interval_proto)) mark(js, g_interval_proto); for (timer_entry_t *t = timer_state.timers; t; t = t->next) { if (!t->active) continue; + if (is_object_type(t->obj)) mark(js, t->obj); mark(js, t->callback); for (int i = 0; i < t->nargs; i++) mark(js, t->args[i]); } diff --git a/src/modules/tls.c b/src/modules/tls.c index cf8e0f6..244b1fe 100644 --- a/src/modules/tls.c +++ b/src/modules/tls.c @@ -272,6 +272,72 @@ static void tls_socket_free_read_queue(ant_tls_socket_t *socket) { } } +static ant_value_t tls_make_error(ant_t *js, tls_context *ctx, long code, const char *fallback) { + const char *message = fallback; + if (ctx && ctx->strerror) { + const char *detail = ctx->strerror(code); + if (detail && *detail) message = detail; + } + return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "TLS error"); +} + +static void tls_socket_free_alpn(ant_tls_socket_t *socket) { + if (!socket || !socket->alpn_protocols) return; + for (int i = 0; i < socket->alpn_count; i++) free(socket->alpn_protocols[i]); + free(socket->alpn_protocols); + socket->alpn_protocols = NULL; + socket->alpn_count = 0; +} + +static void tls_socket_free(ant_tls_socket_t *socket) { + if (!socket) return; + tls_remove_active_socket(socket); + + if (is_object_type(socket->obj)) + js_clear_native(socket->obj, TLS_SOCKET_NATIVE_TAG); + tls_socket_free_read_queue(socket); + tls_socket_free_alpn(socket); + + if (socket->ctx_wrap) tls_context_release(socket->ctx_wrap); + if (socket->owns_ctx && socket->ctx && socket->ctx->free_ctx) + socket->ctx->free_ctx(socket->ctx); + + free(socket->host); + free(socket->servername); + free(socket); +} + +static void tls_socket_close_cb(uv_handle_t *handle) { + tlsuv_stream_t *tls_stream = (tlsuv_stream_t *)handle; + ant_tls_socket_t *socket = tls_stream ? (ant_tls_socket_t *)tls_stream->data : NULL; + + ant_t *js = socket ? socket->js : NULL; + ant_value_t had_error = 0; + if (!socket || !js) return; + + socket->destroyed = true; + socket->connecting = false; + socket->closing = false; + + tls_socket_sync_state(socket); + had_error = js_bool(socket->had_error); + tls_emit(js, socket->obj, "close", &had_error, 1); + tls_socket_free(socket); +} + +static void tls_socket_close(ant_tls_socket_t *socket) { + if (!socket || socket->closing || socket->destroyed) return; + socket->closing = true; + tlsuv_stream_close(&socket->stream, tls_socket_close_cb); +} + +static void tls_socket_maybe_emit_end(ant_tls_socket_t *socket) { + ant_t *js = socket ? socket->js : NULL; + if (!socket || !js || !socket->ended || socket->read_len != 0 || socket->read_head) return; + tls_emit(js, socket->obj, "end", NULL, 0); + tls_socket_close(socket); +} + static ant_value_t tls_socket_drain_read_queue(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); ant_tls_socket_t *socket = tls_socket_data(obj); @@ -290,21 +356,22 @@ static ant_value_t tls_socket_drain_read_queue(ant_t *js, ant_value_t *args, int ? js_mkstr(js, chunk->data + chunk->off, len) : tls_make_buffer_chunk(js, chunk->data + chunk->off, len); - socket->read_head = chunk->next; - if (socket->read_tail == chunk) socket->read_tail = NULL; - socket->read_len -= len; - free(chunk->data); - free(chunk); - if (is_err(data)) { socket->had_error = true; tls_emit(js, obj, "error", &data, 1); return data; } + socket->read_head = chunk->next; + if (socket->read_tail == chunk) socket->read_tail = NULL; + socket->read_len -= len; + free(chunk->data); + free(chunk); + tls_emit(js, obj, "data", &data, 1); } + tls_socket_maybe_emit_end(socket); return js_mkundef(); } @@ -351,36 +418,6 @@ static bool tls_value_bytes( return true; } -static ant_value_t tls_make_error(ant_t *js, tls_context *ctx, long code, const char *fallback) { - const char *message = fallback; - if (ctx && ctx->strerror) { - const char *detail = ctx->strerror(code); - if (detail && *detail) message = detail; - } - return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "TLS error"); -} - -static void tls_socket_free_alpn(ant_tls_socket_t *socket) { - if (!socket || !socket->alpn_protocols) return; - for (int i = 0; i < socket->alpn_count; i++) free(socket->alpn_protocols[i]); - free(socket->alpn_protocols); - socket->alpn_protocols = NULL; - socket->alpn_count = 0; -} - -static void tls_socket_free(ant_tls_socket_t *socket) { - if (!socket) return; - tls_remove_active_socket(socket); - if (is_object_type(socket->obj)) js_clear_native(socket->obj, TLS_SOCKET_NATIVE_TAG); - tls_socket_free_read_queue(socket); - tls_socket_free_alpn(socket); - if (socket->ctx_wrap) tls_context_release(socket->ctx_wrap); - if (socket->owns_ctx && socket->ctx && socket->ctx->free_ctx) socket->ctx->free_ctx(socket->ctx); - free(socket->host); - free(socket->servername); - free(socket); -} - static ant_value_t tls_stream_error(ant_tls_socket_t *socket, int status, const char *fallback) { const char *message = fallback; if (socket) { @@ -391,29 +428,6 @@ static ant_value_t tls_stream_error(ant_tls_socket_t *socket, int status, const return js_mkerr_typed(socket->js, JS_ERR_TYPE, "%s", message ? message : "TLS error"); } -static void tls_socket_close_cb(uv_handle_t *handle) { - tlsuv_stream_t *tls_stream = (tlsuv_stream_t *)handle; - ant_tls_socket_t *socket = tls_stream ? (ant_tls_socket_t *)tls_stream->data : NULL; - ant_t *js = socket ? socket->js : NULL; - ant_value_t had_error = 0; - - if (!socket || !js) return; - - socket->destroyed = true; - socket->connecting = false; - socket->closing = false; - tls_socket_sync_state(socket); - had_error = js_bool(socket->had_error); - tls_emit(js, socket->obj, "close", &had_error, 1); - tls_socket_free(socket); -} - -static void tls_socket_close(ant_tls_socket_t *socket) { - if (!socket || socket->closing || socket->destroyed) return; - socket->closing = true; - tlsuv_stream_close(&socket->stream, tls_socket_close_cb); -} - static bool tls_parse_write_args( ant_t *js, ant_value_t *args, @@ -558,8 +572,7 @@ static void tls_socket_on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_ } } else if (nread == UV_EOF) { socket->ended = true; - tls_emit(js, socket->obj, "end", NULL, 0); - tls_socket_close(socket); + tls_socket_maybe_emit_end(socket); } else if (nread < 0) { ant_value_t err = tls_stream_error(socket, (int)nread, "TLS read failed"); socket->had_error = true; @@ -639,6 +652,7 @@ static ant_value_t js_tls_socket_read(ant_t *js, ant_value_t *args, int nargs) { out = js_mkstr(js, data, copied); else out = tls_make_buffer_chunk(js, data, copied); free(data); + tls_socket_maybe_emit_end(socket); return out; } @@ -1236,7 +1250,7 @@ static ant_value_t js_tls_connect(ant_t *js, ant_value_t *args, int nargs) { } static ant_value_t js_tls_check_server_identity(ant_t *js, ant_value_t *args, int nargs) { - return js_mkundef(); + return js_mkerr_typed(js, JS_ERR_TYPE, "tls.checkServerIdentity is not implemented"); } static ant_value_t js_tls_get_ciphers(ant_t *js, ant_value_t *args, int nargs) { diff --git a/tests/test_tls_secure_connect_event.cjs b/tests/test_tls_secure_connect_event.cjs index db4bef6..cab4ac3 100644 --- a/tests/test_tls_secure_connect_event.cjs +++ b/tests/test_tls_secure_connect_event.cjs @@ -18,7 +18,10 @@ assert.strictEqual(tls.isSecureContext(context), true); assert.strictEqual(tls.isSecureContext(constructedContext), true); assert.strictEqual(tls.createContext, undefined); assert.strictEqual(tls.isContext, undefined); -assert.strictEqual(tls.checkServerIdentity('localhost', {}), undefined); +assert.throws( + () => tls.checkServerIdentity('localhost', {}), + /tls\.checkServerIdentity is not implemented/ +); assert.deepStrictEqual(tls.getCiphers(), []); assert.deepStrictEqual(tls.rootCertificates, []); assert.strictEqual(Object.isFrozen(tls.rootCertificates), true);