Fix possible double free during context destruction

This commit is contained in:
2025-09-09 00:48:50 -07:00
parent 52008a0110
commit 9282e0bbc6
2 changed files with 36 additions and 18 deletions

View File

@ -95,23 +95,32 @@ refcount_make_context(size_t entry_offset,
} }
/** /**
* Cleanup a #RefcountContext and free any associated resources. This also * Callback that deinits a static, but sets its static_entry field to NULL
* causes all remaining references, both static and non-static, to be dropped * first. Used in #refcount_context_destroy.
* and the context's destroy callback to be called on each object. */
static void deinit_static_for_context_destroy(void *obj, void *ctx_raw) {
RefcountContext *ctx = ctx_raw;
ENTRY->impl.static_entry = NULL;
refcount_context_deinit_static(ctx, obj);
}
/**
* Cleanup a #RefcountContext and free any associated resources. This first
* frees all static objects, then runs the garbage collector.
* @param ctx The #RefcountContext * @param ctx The #RefcountContext
*/ */
void refcount_context_destroy(RefcountContext *ctx) { void refcount_context_destroy(RefcountContext *ctx) {
refcount_list_free_with_data_full( refcount_list_free_with_data_full(ctx->static_objects,
ctx->static_objects, refcount_context_deinit_static_as_callback, ctx, deinit_static_for_context_destroy, ctx,
&ctx->alloc); &ctx->alloc);
refcount_context_garbage_collect(ctx);
#ifdef REFCOUNT_HAS_THREADS #ifdef REFCOUNT_HAS_THREADS
mtx_destroy(&ctx->so_mtx); mtx_destroy(&ctx->so_mtx);
mtx_destroy(&ctx->gr_mtx); mtx_destroy(&ctx->gr_mtx);
#endif #endif
refcount_context_garbage_collect(ctx);
refcount_free(&ctx->alloc, ctx); refcount_free(&ctx->alloc, ctx);
} }
@ -325,8 +334,11 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
ht_free(ENTRY->destructors); ht_free(ENTRY->destructors);
unref_weakref(ctx, ENTRY->weak_ref); unref_weakref(ctx, ENTRY->weak_ref);
unlock_entry_mtx(ENTRY); unlock_entry_mtx(ENTRY);
ctx->static_objects = refcount_list_remove_full( // this is set to null if we are destroying the context
ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc); if (ENTRY->impl.static_entry) {
ctx->static_objects = refcount_list_remove_full(
ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc);
}
refcount_list_free_with_data_full( refcount_list_free_with_data_full(
held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc); held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc);
success = true; success = true;

View File

@ -55,12 +55,16 @@ int main(int argc, const char **argv) {
ctx_and_flag.ctx = c; ctx_and_flag.ctx = c;
A static_a = { #define STATIC_A(id) \
.num = 0, A static_a_##id = { \
.str = counting_strdup("static"), .num = __LINE__, \
.next = make_a(c, 0, "in static"), .str = counting_strdup("static " #id), \
}; .next = make_a(c, 0, "in static " #id), \
refcount_context_init_static(c, &static_a); }; \
refcount_context_init_static(c, &static_a_##id)
STATIC_A(1);
STATIC_A(2);
STATIC_A(3);
A *a = make_a(c, 10, "Hello world\n"); A *a = make_a(c, 10, "Hello world\n");
assert(!refcount_context_is_static(c, a)); assert(!refcount_context_is_static(c, a));
@ -187,10 +191,12 @@ int main(int argc, const char **argv) {
assert(refcount_context_num_refs(c, a) == 1); assert(refcount_context_num_refs(c, a) == 1);
assert(!refcount_context_unref(c, a)); assert(!refcount_context_unref(c, a));
refcount_context_deinit_static(c, &static_a); refcount_context_deinit_static(c, &static_a_1);
counting_free(static_a.str); counting_free(static_a_1.str);
refcount_context_destroy(c); refcount_context_destroy(c);
counting_free(static_a_2.str);
counting_free(static_a_3.str);
check_allocator_status(); check_allocator_status();
return 0; return 0;