quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit 764e417aeeeaa1b2f214d6d870147f7f5316d6fe
parent 8329d100a28c53f7a1ba083df7892e80447167b5
Author: Florian Dold <dold@taler.net>
Date:   Sun,  9 Aug 2026 21:15:12 +0200

http: make request ownership race-safe

Diffstat:
Mquickjs/quickjs-http.c | 177++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mquickjs/quickjs-http.h | 16+++++-----------
Mquickjs/quickjs-libc.c | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
3 files changed, 263 insertions(+), 63 deletions(-)

diff --git a/quickjs/quickjs-http.c b/quickjs/quickjs-http.c @@ -31,6 +31,7 @@ struct CurlClientState { pthread_t thread; pthread_mutex_t mutex; + pthread_cond_t callback_done; BOOL started; BOOL stopped; CURLSH *curlsh; @@ -48,6 +49,9 @@ struct CurlRequestState { struct list_head link_cancel; /* for cancel_queue */ DynBuf response_data; BOOL cancelled; + BOOL cancel_queued; + BOOL added; + BOOL callback_active; CURL *curl; int request_id; enum JSHttpRedirectFlag redirect; @@ -57,8 +61,20 @@ struct CurlRequestState { struct curl_slist *req_headers; struct curl_slist *resp_headers; char *errbuf; + size_t response_header_bytes; }; +#define MAX_HTTP_RESPONSE_SIZE (64U * 1024U * 1024U) +#define MAX_HTTP_HEADER_SIZE (1024U * 1024U) + +static pthread_once_t curl_global_once = PTHREAD_ONCE_INIT; +static CURLcode curl_global_status = CURLE_FAILED_INIT; + +static void init_curl_global_state(void) +{ + curl_global_status = curl_global_init(CURL_GLOBAL_DEFAULT); +} + // Must only be called with locked client mutex static void destroy_curl_request_state(struct CurlRequestState *crs) { @@ -66,7 +82,15 @@ static void destroy_curl_request_state(struct CurlRequestState *crs) return; } - list_del(&crs->link_req); + if (crs->link_add.prev) { + list_del(&crs->link_add); + } + if (crs->link_cancel.prev) { + list_del(&crs->link_cancel); + } + if (crs->link_req.prev) { + list_del(&crs->link_req); + } curl_slist_free_all(crs->req_headers); curl_slist_free_all(crs->resp_headers); dbuf_free(&crs->response_data); @@ -145,6 +169,9 @@ done: pthread_mutex_lock(&ccs->mutex); cancelled = crs->cancelled; + if (!cancelled) { + crs->callback_active = TRUE; + } pthread_mutex_unlock(&ccs->mutex); if (cancelled == FALSE) { @@ -152,10 +179,12 @@ done: crs->response_cb(crs->response_cb_cls, &hri); } - free(headers); pthread_mutex_lock(&ccs->mutex); + crs->callback_active = FALSE; + pthread_cond_broadcast(&ccs->callback_done); destroy_curl_request_state(crs); pthread_mutex_unlock(&ccs->mutex); + free(headers); return NULL; } @@ -165,13 +194,26 @@ static size_t curl_header_callback(char *buffer, size_t size, struct CurlRequestState *crs = userdata; size_t sz = size * nitems; char *hval; + struct curl_slist *new_headers; + + if (size != 0 && sz / size != nitems) { + return 0; + } + if (sz > MAX_HTTP_HEADER_SIZE - crs->response_header_bytes) { + return 0; + } hval = strndup(buffer, sz); if (!hval) { return 0; } - crs->resp_headers = curl_slist_append(crs->resp_headers, hval); + new_headers = curl_slist_append(crs->resp_headers, hval); free(hval); + if (!new_headers) { + return 0; + } + crs->resp_headers = new_headers; + crs->response_header_bytes += sz; return sz; } @@ -181,6 +223,12 @@ static size_t curl_write_cb(void *data, size_t size, size_t nmemb, void *userp) size_t realsize = size * nmemb; struct CurlRequestState *rctx = userp; + if (size != 0 && realsize / size != nmemb) { + return 0; + } + if (realsize > MAX_HTTP_RESPONSE_SIZE - rctx->response_data.size) { + return 0; + } if (0 != dbuf_put(&rctx->response_data, data, realsize)) { return 0; } @@ -197,13 +245,20 @@ create_impl(void *cls, struct JSHttpRequestInfo *req_info) CURL *curl; BOOL debug = req_info->debug > 0; const char *method = req_info->method; + struct curl_slist *new_headers; + + pthread_mutex_lock(&ccs->mutex); + if (ccs->stopped) { + pthread_mutex_unlock(&ccs->mutex); + return -1; + } + pthread_mutex_unlock(&ccs->mutex); crs = malloc(sizeof *crs); if (!crs) { return -1; } memset(crs, 0, sizeof *crs); - crs->request_id = ++ccs->last_request_id; crs->ccs = ccs; crs->response_cb = req_info->response_cb; crs->response_cb_cls = req_info->response_cb_cls; @@ -215,13 +270,17 @@ create_impl(void *cls, struct JSHttpRequestInfo *req_info) dbuf_init(&crs->response_data); curl = curl_easy_init(); + if (!curl) { + goto error; + } crs->curl = curl; curl_easy_setopt(curl, CURLOPT_PRIVATE, crs); curl_easy_setopt(curl, CURLOPT_SHARE, ccs->curlsh); curl_easy_setopt(curl, CURLOPT_URL, req_info->url); - curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, "9.9.9.9"); curl_easy_setopt(curl, CURLOPT_USERAGENT, "qtart"); - curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt"); + curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "http,https"); + curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, crs); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb); @@ -293,18 +352,32 @@ create_impl(void *cls, struct JSHttpRequestInfo *req_info) if (req_info->request_headers != NULL) { char **h = req_info->request_headers; while (*h) { - crs->req_headers = curl_slist_append(crs->req_headers, *h); + new_headers = curl_slist_append(crs->req_headers, *h); + if (!new_headers) { + goto error; + } + crs->req_headers = new_headers; h++; } } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, crs->req_headers); pthread_mutex_lock(&ccs->mutex); + if (ccs->stopped) { + pthread_mutex_unlock(&ccs->mutex); + goto error; + } + if (ccs->last_request_id == INT_MAX) { + ccs->last_request_id = 1; + } else { + ccs->last_request_id++; + } + crs->request_id = ccs->last_request_id; list_add_tail(&crs->link_add, &ccs->add_queue); list_add_tail(&crs->link_req, &ccs->request_list); pthread_mutex_unlock(&ccs->mutex); - curl_multi_wakeup(ccs->curlm); + (void) curl_multi_wakeup(ccs->curlm); return crs->request_id; error: @@ -316,6 +389,8 @@ error: if (crs->curl) { curl_easy_cleanup(crs->curl); } + curl_slist_free_all(crs->req_headers); + curl_slist_free_all(crs->resp_headers); free(crs); } return -1; @@ -331,14 +406,27 @@ destroy_impl(void *cls, int request_id) list_for_each(el, &ccs->request_list) { struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_req); - if (crs->request_id == request_id && !crs->cancelled) { - list_add_tail(&crs->link_cancel, &ccs->cancel_queue); + if (crs->request_id == request_id) { + crs->cancelled = TRUE; + while (crs->callback_active && + !pthread_equal(pthread_self(), ccs->thread)) { + pthread_cond_wait(&ccs->callback_done, &ccs->mutex); + pthread_mutex_unlock(&ccs->mutex); + return destroy_impl(cls, request_id); + } + if (!crs->callback_active && !crs->cancel_queued) { + crs->cancel_queued = TRUE; + list_add_tail(&crs->link_cancel, &ccs->cancel_queue); + } + break; } } pthread_mutex_unlock(&ccs->mutex); - curl_multi_wakeup(ccs->curlm); + if (ccs->curlm) { + (void) curl_multi_wakeup(ccs->curlm); + } return 0; } @@ -385,31 +473,45 @@ curl_multi_thread_run(void *cls) pthread_mutex_lock(&ccs->mutex); list_for_each_safe(el, el1, &ccs->add_queue) { struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_add); - curl_multi_add_handle(ccs->curlm, crs->curl); + if (CURLM_OK == curl_multi_add_handle(ccs->curlm, crs->curl)) { + crs->added = TRUE; + } else { + crs->cancelled = TRUE; + if (!crs->cancel_queued) { + crs->cancel_queued = TRUE; + list_add_tail(&crs->link_cancel, &ccs->cancel_queue); + } + } list_del(el); } pthread_mutex_unlock(&ccs->mutex); + // Process finished requests before cancellation so CURLMSG_DONE + // never references an easy handle that cancellation already freed. + int msgq = 0; + while ((m = curl_multi_info_read(ccs->curlm, &msgq)) != NULL) { + if (m->msg == CURLMSG_DONE) { + CURL *e = m->easy_handle; + curl_multi_remove_handle(ccs->curlm, e); + handle_done(e, m->data.result); + } + } + // Cancel requests in queue pthread_mutex_lock(&ccs->mutex); list_for_each_safe(el, el1, &ccs->cancel_queue) { struct CurlRequestState *crs = list_entry(el, struct CurlRequestState, link_cancel); - curl_multi_remove_handle(ccs->curlm, crs->curl); - crs->cancelled = TRUE; - list_del(el); + if (crs->added) { + curl_multi_remove_handle(ccs->curlm, crs->curl); + } + destroy_curl_request_state(crs); } pthread_mutex_unlock(&ccs->mutex); - - // Process finished request - int msgq = 0; - m = curl_multi_info_read(ccs->curlm, &msgq); - if (m && (m->msg == CURLMSG_DONE)) { - CURL *e = m->easy_handle; - curl_multi_remove_handle(ccs->curlm, e); - handle_done(e, m->data.result); - } - } while(m); + } while (m); } + pthread_mutex_lock(&ccs->mutex); + ccs->stopped = TRUE; + pthread_mutex_unlock(&ccs->mutex); if (CURLM_OK != curl_multi_cleanup(ccs->curlm)) { fprintf(stderr, "warning: curl_multi_cleanup failed\n"); } @@ -426,15 +528,18 @@ js_curl_http_client_create() struct CurlClientState *ccs = NULL; int res; - ccs = malloc(sizeof *ccs); + if (0 != pthread_once(&curl_global_once, init_curl_global_state) || + CURLE_OK != curl_global_status) { + return NULL; + } + + ccs = calloc(1, sizeof *ccs); if (!ccs) { goto error; } pthread_mutex_init(&ccs->mutex, NULL); - ccs->started = FALSE; - ccs->stopped = FALSE; - ccs->last_request_id = 0; + pthread_cond_init(&ccs->callback_done, NULL); ccs->curlsh = curl_share_init(); if (!ccs->curlsh) { goto error; @@ -460,17 +565,22 @@ js_curl_http_client_create() impl->cls = ccs; res = pthread_create(&ccs->thread, NULL, &curl_multi_thread_run, ccs); - ccs->started = TRUE; - if (0 != res) { goto error; } + ccs->started = TRUE; return impl; error: if (ccs) { - curl_share_cleanup(ccs->curlsh); - curl_multi_cleanup(ccs->curlm); + if (ccs->curlsh) { + curl_share_cleanup(ccs->curlsh); + } + if (ccs->curlm) { + curl_multi_cleanup(ccs->curlm); + } + pthread_cond_destroy(&ccs->callback_done); + pthread_mutex_destroy(&ccs->mutex); free(ccs); } if (impl) { @@ -506,6 +616,7 @@ destroy_client_state(struct CurlClientState *ccs) destroy_curl_request_state(crs); } pthread_mutex_unlock(&ccs->mutex); + pthread_cond_destroy(&ccs->callback_done); pthread_mutex_destroy(&ccs->mutex); free(ccs); } diff --git a/quickjs/quickjs-http.h b/quickjs/quickjs-http.h @@ -124,7 +124,7 @@ struct JSHttpRequestInfo { /** * Length or request body or 0. */ - uint32_t req_body_len; + size_t req_body_len; }; /** @@ -167,17 +167,10 @@ struct JSHttpResponseInfo { /** * Length of the response body or 0. */ - uint32_t body_len; + size_t body_len; }; /** - * Callback called when an HTTP response has arrived. - * - * IMPORTANT: May be called from an arbitrary thread. - */ -typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp); - -/** * Function to create a new HTTP fetch request. * The request can still be configured until it is started. * An identifier for the request will be written to @a handle. @@ -187,8 +180,9 @@ typedef void (*JSHttpResponseCb)(void *cls, struct JSHttpResponseInfo *resp); typedef int (*JSHttpReqCreateFn)(void *cls, struct JSHttpRequestInfo *req_info); /** - * Cancel a request. The request_id will become invalid - * and the callback won't be called with request_id. + * Cancel a request. The request_id will become invalid. When this function + * returns, the response callback is no longer running and will not be called + * in the future for this request. */ typedef int (*JSHttpReqCancelFn)(void *cls, int request_id); diff --git a/quickjs/quickjs-libc.c b/quickjs/quickjs-libc.c @@ -2319,7 +2319,8 @@ int expect_property_str_bool(JSContext *ctx, JSValueConst this_val, const char * return bool_val; } -static void free_http_request_context(HttpRequestContext *req_context) +static void free_http_request_context(HttpRequestContext *req_context, + BOOL cancel_native) { JSContext *ctx; JSThreadState *ts; @@ -2329,8 +2330,10 @@ static void free_http_request_context(HttpRequestContext *req_context) } ctx = req_context->ctx; ts = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); - ts->http_client_impl->req_cancel(ts->http_client_impl->cls, req_context->request_id); - req_context->ctx = NULL; + if (cancel_native && ts->http_client_impl && req_context->request_id > 0) { + ts->http_client_impl->req_cancel(ts->http_client_impl->cls, + req_context->request_id); + } JS_FreeValue(ctx, req_context->resolve_func); JS_FreeValue(ctx, req_context->reject_func); if (NULL != req_context->link.prev) { @@ -2386,6 +2389,9 @@ static void handle_http_resp(void *cls, struct JSHttpResponseInfo *resp_info) int num_headers; num_headers = resp_info->num_response_headers; + if (num_headers < 0 || num_headers > 10000) { + goto fail; + } msg->response_headers = malloc((num_headers + 1) * sizeof (char *)); if (!msg->response_headers) { @@ -2411,6 +2417,9 @@ static void handle_http_resp(void *cls, struct JSHttpResponseInfo *resp_info) } if (resp_info->body_len > 0) { + if (!resp_info->body) { + goto fail; + } msg->body = malloc(resp_info->body_len); if (!msg->body) { goto fail; @@ -2451,11 +2460,39 @@ static JSValue cancel_http_req(JSContext *ctx, JSValueConst this_val, JSThreadState *ts = JS_GetRuntimeOpaque(rt); int req_id; int ret; + struct list_head *el; + HttpRequestContext *req_context = NULL; + + if (0 != JS_ToInt32(ctx, &req_id, func_data[0])) { + return JS_EXCEPTION; + } - JS_ToInt32(ctx, &req_id, func_data[0]); + list_for_each(el, &ts->http_requests) { + HttpRequestContext *candidate = + list_entry(el, HttpRequestContext, link); + if (candidate->request_id == req_id) { + req_context = candidate; + break; + } + } + if (!req_context) { + return JS_NewInt32(ctx, 0); + } // cancel HTTP request ret = ts->http_client_impl->req_cancel(ts->http_client_impl->cls, req_id); + if (ret == 0) { + JSValue error = JS_NewError(ctx); + JSValue result; + + JS_DefinePropertyValueStr(ctx, error, "message", + JS_NewString(ctx, "HTTP request cancelled"), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + result = JS_Call(ctx, req_context->reject_func, JS_UNDEFINED, 1, &error); + JS_FreeValue(ctx, result); + JS_FreeValue(ctx, error); + free_http_request_context(req_context, FALSE); + } return JS_NewInt32(ctx, ret); } @@ -2483,11 +2520,18 @@ static char **gather_http_headers(JSContext *ctx, JSValueConst js_headers) return NULL; } if (0 != JS_ToUint32(ctx, &length, length_prop)) { + JS_FreeValue(ctx, length_prop); return NULL; } JS_FreeValue(ctx, length_prop); - headers = js_mallocz(ctx, (length + 1) * sizeof (char *)); +#if SIZE_MAX <= UINT32_MAX + if (length > SIZE_MAX / sizeof(char *) - 1) { + JS_ThrowRangeError(ctx, "too many HTTP headers"); + goto exception; + } +#endif + headers = js_mallocz(ctx, ((size_t) length + 1) * sizeof (char *)); if (!headers) { goto exception; } @@ -2505,6 +2549,8 @@ static char **gather_http_headers(JSContext *ctx, JSValueConst js_headers) } hval = js_strdup(ctx, cstr); if (!hval) { + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, item); goto exception; } JS_FreeCString(ctx, cstr); @@ -2531,6 +2577,10 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val, JSRuntime *rt = JS_GetRuntime(ctx); JSThreadState *ts = JS_GetRuntimeOpaque(rt); JSValue resolving_funs[2]; + JSValue promise = JS_UNDEFINED; + JSValue requestId = JS_UNDEFINED; + JSValue cancelCls = JS_UNDEFINED; + JSValue cancelFn = JS_UNDEFINED; JSValue options = JS_UNINITIALIZED; JSValue method = JS_UNINITIALIZED; const char *method_str = NULL; @@ -2540,6 +2590,7 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val, BOOL debug = FALSE; int redirect = 0; int ret; + BOOL request_created = FALSE; if (NULL == ts->http_client_impl) { JS_ThrowInternalError(ctx, "no HTTP client implementation available"); @@ -2547,7 +2598,12 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val, } req_context = js_mallocz(ctx, sizeof *req_context); + if (!req_context) { + goto exception; + } req_context->ctx = ctx; + req_context->resolve_func = JS_UNDEFINED; + req_context->reject_func = JS_UNDEFINED; req_url = JS_ToCString(ctx, argv[0]); if (!req_url) { @@ -2561,7 +2617,17 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val, int has_prop_redirect; method = JS_GetPropertyStr(ctx, options, "method"); + if (JS_IsException(method)) { + goto exception; + } + if (JS_IsUndefined(method)) { + JS_FreeValue(ctx, method); + method = JS_NewString(ctx, "get"); + } debug = expect_property_str_bool(ctx, options, "debug"); + if (debug < 0) { + goto exception; + } has_prop_redirect = JS_HasPropertyStr(ctx, options, "redirect"); if (has_prop_redirect < 0) { @@ -2574,13 +2640,16 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val, goto exception; } if (JS_ToInt32(ctx, &redir_num, redir_val)) { + JS_FreeValue(ctx, redir_val); goto exception; } if (redir_num < 0 || redir_num > JS_HTTP_REDIRECT_ERROR) { + JS_FreeValue(ctx, redir_val); JS_ThrowTypeError(ctx, "redirect option out of range"); goto exception; } redirect = redir_num; + JS_FreeValue(ctx, redir_val); } } else { JS_ThrowTypeError(ctx, "invalid options"); @@ -2622,11 +2691,13 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val, if (!(JS_IsNull(data) || JS_IsUndefined(data))) { data_ptr = JS_GetArrayBuffer(ctx, &data_len, data); if (!data_ptr) { + JS_FreeValue(ctx, data); goto exception; } } req.req_body = data_ptr; req.req_body_len = data_len; + JS_FreeValue(ctx, data); } } @@ -2638,43 +2709,64 @@ static JSValue js_os_fetchHttp(JSContext *ctx, JSValueConst this_val, req.redirect = redirect; req.response_cb = &handle_http_resp; req.response_cb_cls = req_context; + + promise = JS_NewPromiseCapability(ctx, resolving_funs); + if (JS_IsException(promise)) { + goto exception; + } + req_context->resolve_func = resolving_funs[0]; + req_context->reject_func = resolving_funs[1]; + ret = ts->http_client_impl->req_create(ts->http_client_impl->cls, &req); if (ret < 0) { JS_ThrowInternalError(ctx, "failed to create request"); goto exception; } + request_created = TRUE; + req_context->request_id = ret; list_add_tail(&req_context->link, &ts->http_requests); // requestId: number - JSValue requestId = JS_NewInt32(ctx, ret); + requestId = JS_NewInt32(ctx, ret); - // promise: Promise<Response> - JSValue promise = JS_NewPromiseCapability(ctx, resolving_funs); - if (JS_IsException(promise)) { + // cancelFn: () => void + cancelCls = JS_NewInt32(ctx, ret); + cancelFn = JS_NewCFunctionData(ctx, &cancel_http_req, 0, 0, 1, &cancelCls); + JS_FreeValue(ctx, cancelCls); + cancelCls = JS_UNDEFINED; + if (JS_IsException(cancelFn)) { goto exception; } - req_context->request_id = ret; - req_context->resolve_func = resolving_funs[0]; - req_context->reject_func = resolving_funs[1]; - - // cancelFn: () => void - JSValue cancelCls = JS_NewInt32(ctx, ret); - JSValue cancelFn = JS_NewCFunctionData(ctx, &cancel_http_req, 0, 0, 1, &cancelCls); ret_val = JS_NewObject(ctx); + if (JS_IsException(ret_val)) { + goto exception; + } JS_SetPropertyStr(ctx, ret_val, "requestId", requestId); + requestId = JS_UNDEFINED; JS_SetPropertyStr(ctx, ret_val, "promise", promise); + promise = JS_UNDEFINED; JS_SetPropertyStr(ctx, ret_val, "cancelFn", cancelFn); + cancelFn = JS_UNDEFINED; done: free_http_headers(ctx, req.request_headers); JS_FreeValue(ctx, method); JS_FreeCString(ctx, req_url); JS_FreeCString(ctx, method_str); + JS_FreeValue(ctx, promise); + JS_FreeValue(ctx, requestId); + JS_FreeValue(ctx, cancelCls); + JS_FreeValue(ctx, cancelFn); return ret_val; exception: + if (req_context) { + free_http_request_context(req_context, request_created); + req_context = NULL; + } + JS_FreeValue(ctx, ret_val); ret_val = JS_EXCEPTION; goto done; @@ -3123,7 +3215,7 @@ static int handle_http_message(JSRuntime *rt, JSContext *ctx) struct list_head *req_el; JSHttpMessage *msg; JSValue obj, func, retval; - HttpRequestContext *request_ctx; + HttpRequestContext *request_ctx = NULL; pthread_mutex_lock(&hp->mutex); if (!list_empty(&hp->msg_queue)) { @@ -3183,12 +3275,15 @@ static int handle_http_message(JSRuntime *rt, JSContext *ctx) atom_message = JS_NewAtom(ctx, "message"); obj = JS_NewError(ctx); JS_DefinePropertyValue(ctx, obj, atom_message, - JS_NewString(ctx, msg->errmsg), + JS_NewString(ctx, msg->errmsg ? msg->errmsg : + "HTTP request failed"), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); retval = JS_Call(ctx, request_ctx->reject_func, JS_UNDEFINED, 1, &obj); JS_FreeAtom(ctx, atom_message); JS_FreeValue(ctx, retval); + JS_FreeValue(ctx, obj); } + free_http_request_context(request_ctx, FALSE); break; } } @@ -5110,7 +5205,7 @@ void js_std_free_handlers(JSRuntime *rt) #ifndef NO_HTTP list_for_each_safe(el, el1, &ts->http_requests) { HttpRequestContext *request_ctx = list_entry(el, HttpRequestContext, link); - free_http_request_context(request_ctx); + free_http_request_context(request_ctx, TRUE); } #endif