From 8279ec5e9e7a61766729a75407eda925db67c6c6 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Thu, 22 Sep 2022 00:38:26 +0000 Subject: [PATCH] Debug features for leaked promises. And then chased down some subsequent use after free issues. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3985 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- Makefile | 4 ++- core/core.js | 4 +++ src/socket.js.c | 40 +++++++++++++-------- src/task.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 4de4ebbd..d1077796 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,8 @@ CFLAGS += \ -fdata-sections LDFLAGS += -Wl,-gc-sections -debug windebug: CFLAGS += -Og -g +debug windebug: CFLAGS += -Og -g -fno-omit-frame-pointer +debug release: LDFLAGS += -rdynamic release winrelease: CFLAGS += -DNDEBUG -O3 -g windebug winrelease: CC = i686-w64-mingw32-gcc-win32 windebug winrelease: AS = $(CC) @@ -68,6 +69,7 @@ UV_SOURCES := \ deps/libuv/src/inet.c \ deps/libuv/src/random.c \ deps/libuv/src/strscpy.c \ + deps/libuv/src/strtok.c \ deps/libuv/src/threadpool.c \ deps/libuv/src/timer.c \ deps/libuv/src/uv-common.c \ diff --git a/core/core.js b/core/core.js index ba5f5648..3a4d8a29 100644 --- a/core/core.js +++ b/core/core.js @@ -793,6 +793,10 @@ loadSettings().then(function() { var data = trace(); response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()}); return response.end(data); + } else if (match = /^\/debug$/.exec(request.uri)) { + var data = JSON.stringify(getDebug(), null, 2); + response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()}); + return response.end(data); } else if (request.uri == "/robots.txt") { return blobHandler(request, response, null, request.uri); } else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) { diff --git a/src/socket.js.c b/src/socket.js.c index b3f5b89e..f37eb348 100644 --- a/src/socket.js.c +++ b/src/socket.js.c @@ -95,22 +95,19 @@ static void _socket_reportError(socket_t* socket, const char* error); static void _socket_set_handler(socket_t* socket, JSValue* handler, JSValue new_value) { JSContext* context = tf_task_get_context(socket->_task); - bool had_handler = !JS_IsUndefined(*handler); - if (!had_handler && !JS_IsUndefined(new_value)) + + JSValue old_handler = *handler; + + if (JS_IsUndefined(old_handler) && !JS_IsUndefined(new_value)) { JS_DupValue(context, socket->_object); } - if (had_handler) - { - JSValue value = *handler; - *handler = JS_UNDEFINED; - JS_FreeValue(context, value); - } - if (!JS_IsUndefined(new_value)) - { - *handler = JS_DupValue(context, new_value); - } - if (had_handler && JS_IsUndefined(new_value)) + + *handler = JS_DupValue(context, new_value); + + JS_FreeValue(context, old_handler); + + if (!JS_IsUndefined(old_handler) && JS_IsUndefined(new_value)) { JS_FreeValue(context, socket->_object); } @@ -280,7 +277,9 @@ void _socket_reportError(socket_t* socket, const char* error) if (JS_IsFunction(context, socket-> _onError)) { JSValue exception = JS_ThrowInternalError(context, "%s", error); + JSValue cb_ref = JS_DupValue(context, socket->_onError); JSValue result = JS_Call(context, socket->_onError, socket->_object, 1, &exception); + JS_FreeValue(context, cb_ref); tf_util_report_error(context, result); JS_FreeValue(context, exception); JS_FreeValue(context, result); @@ -547,7 +546,7 @@ JSValue _socket_listen(JSContext* context, JSValueConst this_val, int argc, JSVa { socket_t* socket = JS_GetOpaque(this_val, _classId); socket->_listening = true; - int backlog = 1; + int backlog = 16; JS_ToInt32(context, &backlog, argv[0]); if (JS_IsUndefined(socket->_onConnect)) { @@ -572,7 +571,9 @@ void _socket_onNewConnection(uv_stream_t* server, int status) JSValue ref = JS_DupValue(context, socket->_object); if (!JS_IsUndefined(socket->_onConnect)) { + JSValue cb_ref = JS_DupValue(context, socket->_onConnect); JSValue result = JS_Call(context, socket->_onConnect, socket->_object, 0, NULL); + JS_FreeValue(context, cb_ref); tf_util_report_error(context, result); JS_FreeValue(context, result); } @@ -586,6 +587,7 @@ JSValue _socket_accept(JSContext* context, JSValueConst this_val, int argc, JSVa socket_t* client = _socket_create_internal(context); client->_direction = kAccept; promiseid_t promise; + JSValue ref = JS_DupValue(context, client->_object); JSValue result = tf_task_allocate_promise(socket->_task, &promise); int status = uv_accept((uv_stream_t*)&socket->_socket, (uv_stream_t*)&client->_socket); if (status == 0) @@ -603,7 +605,7 @@ JSValue _socket_accept(JSContext* context, JSValueConst this_val, int argc, JSVa { tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(context, "uv_accept: %s", uv_strerror(status))); } - JS_FreeValue(context, client->_object); + JS_FreeValue(context, ref); return result; } @@ -728,7 +730,9 @@ void _socket_onRead(uv_stream_t* stream, ssize_t readSize, const uv_buf_t* buffe if (!JS_IsUndefined(socket->_onRead)) { JSValue args[] = { JS_UNDEFINED }; + JSValue cb_ref = JS_DupValue(context, socket->_onRead); JSValue result = JS_Call(context, socket->_onRead, socket->_object, 1, args); + JS_FreeValue(context, cb_ref); tf_util_report_error(context, result); JS_FreeValue(context, result); } @@ -784,7 +788,9 @@ void _socket_onRead(uv_stream_t* stream, ssize_t readSize, const uv_buf_t* buffe if (!JS_IsUndefined(socket->_onRead)) { JSValue args[] = { JS_UNDEFINED }; + JSValue cb_ref = JS_DupValue(context, socket->_onRead); JSValue result = JS_Call(context, socket->_onRead, socket->_object, 1, args); + JS_FreeValue(context, cb_ref); tf_util_report_error(context, result); JS_FreeValue(context, result); } @@ -833,7 +839,9 @@ void _socket_notifyDataRead(socket_t* socket, const char* data, size_t length) JSValue args[] = { typedArray }; if (!JS_IsUndefined(socket->_onRead)) { + JSValue cb_ref = JS_DupValue(context, socket->_onRead); JSValue result = JS_Call(context, socket->_onRead, socket->_object, 1, args); + JS_FreeValue(context, cb_ref); tf_util_report_error(context, result); JS_FreeValue(context, result); } @@ -913,6 +921,7 @@ JSValue _socket_write(JSContext* context, JSValueConst this_val, int argc, JSVal socket_t* socket = JS_GetOpaque(this_val, _classId); promiseid_t promise = -1; JSValue write_result = tf_task_allocate_promise(socket->_task, &promise); + JSValue ref = JS_DupValue(context, socket->_object); if (!JS_IsUndefined(argv[0])) { if (socket->_tls) @@ -950,6 +959,7 @@ JSValue _socket_write(JSContext* context, JSValueConst this_val, int argc, JSVal { tf_task_reject_promise(socket->_task, promise, JS_NewInt32(context, -2)); } + JS_FreeValue(context, ref); return write_result; } diff --git a/src/task.c b/src/task.c index 5952784e..fe667f23 100644 --- a/src/task.c +++ b/src/task.c @@ -52,8 +52,16 @@ typedef struct _promise_t { promiseid_t id; JSValue values[2]; + uint32_t stack_hash; } promise_t; +typedef struct _promise_stack_t +{ + uint32_t hash; + const char* stack; + int count; +} promise_stack_t; + typedef struct _tf_task_t { taskid_t _nextTask; @@ -107,6 +115,9 @@ typedef struct _tf_task_t char _db_path[256]; char _secrets_path[256]; const char* _args; + + promise_stack_t* _promise_stacks; + int _promise_stack_count; } tf_task_t; typedef struct _export_record_t @@ -720,6 +731,24 @@ static JSValue _tf_task_getStats(JSContext* context, JSValueConst this_val, int return result; } +static JSValue _tf_task_getDebug(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + tf_task_t* task = JS_GetContextOpaque(context); + JSValue result = JS_NewObject(context); + + JSValue promises = JS_NewObject(context); + JS_SetPropertyStr(context, result, "promises", promises); + for (int i = 0; i < task->_promise_stack_count; i++) + { + if (task->_promise_stacks[i].count) + { + JS_SetPropertyStr(context, promises, task->_promise_stacks[i].stack, JS_NewInt32(context, task->_promise_stacks[i].count)); + } + } + + return result; +} + static JSValue _tf_task_getFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); @@ -982,19 +1011,76 @@ static promise_t* _tf_task_find_promise(tf_task_t* task, promiseid_t id) return it ? it : NULL; } +static int _promise_stack_compare(const void* a, const void* b) +{ + const uint32_t* pa = a; + const promise_stack_t* pb = b; + return *pa < pb->hash ? -1 : *pa > pb->hash ? 1 : 0; +} + +static void _add_promise_stack(tf_task_t* task, uint32_t hash, const char* stack) +{ + int index = tf_util_insert_index(&hash, task->_promise_stacks, task->_promise_stack_count, sizeof(promise_stack_t), _promise_stack_compare); + if (index < task->_promise_stack_count && task->_promise_stacks[index].hash == hash) + { + task->_promise_stacks[index].count++; + } + else + { + task->_promise_stacks = tf_resize_vec(task->_promise_stacks, sizeof(promise_stack_t) * (task->_promise_stack_count + 1)); + if (task->_promise_stack_count - index) + { + memmove(task->_promise_stacks + index + 1, task->_promise_stacks + index, sizeof(promise_stack_t) * (task->_promise_stack_count - index)); + } + task->_promise_stacks[index] = (promise_stack_t) { .hash = hash, .stack = tf_strdup(stack), .count = 1 }; + task->_promise_stack_count++; + } +} + +static void _remove_promise_stack(tf_task_t* task, uint32_t hash) +{ + promise_stack_t* found = bsearch(&hash, task->_promise_stacks, task->_promise_stack_count, sizeof(promise_stack_t), _promise_stack_compare); + if (found) + { + found->count--; + } +} + static void _tf_task_free_promise(tf_task_t* task, promiseid_t id) { promise_t* it = bsearch((void*)(intptr_t)id, task->_promises, task->_promise_count, sizeof(promise_t), _promise_compare); if (it) { + _remove_promise_stack(task, it->stack_hash); int index = it - task->_promises; memmove(it, it + 1, sizeof(promise_t) * (task->_promise_count - index - 1)); task->_promise_count--; } } +uint32_t fnv32a(const char* buffer, uint32_t start) +{ + uint32_t result = 0x811c9dc5; + for (const char* p = buffer; *p; p++) { + result ^= *p; + result += (result << 1) + (result << 4) + (result << 7) + (result << 8) + (result << 24); + } + return result; +} + JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise) { + JSValue error = JS_ThrowInternalError(task->_context, "promise callstack"); + JSValue exception = JS_GetException(task->_context); + JSValue stack_value = JS_GetPropertyStr(task->_context, exception, "stack"); + const char* stack = JS_ToCString(task->_context, stack_value); + uint32_t stack_hash = fnv32a(stack, 0); + _add_promise_stack(task, stack_hash, stack); + JS_FreeCString(task->_context, stack); + JS_FreeValue(task->_context, stack_value); + JS_FreeValue(task->_context, exception); + JS_FreeValue(task->_context, error); + promiseid_t promiseId; do { promiseId = task->_nextPromise++; @@ -1004,6 +1090,7 @@ JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise) { .id = promiseId, .values = { JS_NULL, JS_NULL }, + .stack_hash = stack_hash, }; JSValue result = JS_NewPromiseCapability(task->_context, promise.values); int index = tf_util_insert_index((void*)(intptr_t)promiseId, task->_promises, task->_promise_count, sizeof(promise_t), _promise_compare); @@ -1384,6 +1471,7 @@ void tf_task_activate(tf_task_t* task) JS_SetPropertyStr(context, global, "trace", JS_NewCFunction(context, _tf_task_trace, "trace", 1)); JS_SetPropertyStr(context, global, "getStats", JS_NewCFunction(context, _tf_task_getStats, "getStats", 0)); + JS_SetPropertyStr(context, global, "getDebug", JS_NewCFunction(context, _tf_task_getDebug, "getDebug", 0)); } else { @@ -1506,6 +1594,11 @@ void tf_task_destroy(tf_task_t* task) tf_trace_destroy(task->_trace); } --_count; + for (int i = 0; i < task->_promise_stack_count; i++) + { + tf_free((void*)task->_promise_stacks[i].stack); + } + tf_free(task->_promise_stacks); tf_free((void*)task->_path); tf_free(task); }