Add tree walking debug function

This commit is contained in:
2025-09-09 16:03:25 -07:00
parent 5064f304ca
commit 4fe4e293ad
3 changed files with 105 additions and 8 deletions

View File

@ -457,6 +457,34 @@ static inline bool refcount_remove_destructor(void *obj, void *key) {
} }
// Debug Functions // Debug Functions
/**
* Callback used from #refcount_debug_context_walk_tree.
* @param cur The current object in the walk
* @param trail The parent objects, up to the root object
* @return True if the walk should stop, false otherwise
*/
typedef bool (*refcount_debug_walk_callback_t)(void *cur,
const RefcountList *trail,
void *user_data);
bool refcount_debug_context_walk_tree(const RefcountContext *ctx, void *obj,
refcount_debug_walk_callback_t callback,
void *user_data);
/**
* Same as #refcount_debug_context_walk_tree, but only operates on the global
* context.
* @param obj The root object
* @param callback The callback
* @param user_data Extra data to pass to the callback
* @return True if the walk ended early because a callback returned true, false
*/
static inline bool
refcount_debug_walk_tree(void *obj, refcount_debug_walk_callback_t callback,
void *user_data) {
return refcount_debug_context_walk_tree(refcount_default_context, obj,
callback, user_data);
}
uint64_t refcount_debug_context_count_object(const RefcountContext *ctx, uint64_t refcount_debug_context_count_object(const RefcountContext *ctx,
void *obj, void *target); void *obj, void *target);

View File

@ -846,6 +846,70 @@ bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj,
} }
// Debug Functions // Debug Functions
static const HTTableFunctions DEBUG_SEEN_FNS = {
.destroy_key = NULL,
.destroy_value = NULL,
.equal = ht_intptr_equal_callback,
.hash = ht_intptr_hash_callback,
.user_data = NULL,
};
/**
* Walk the tree held references tree of a given object, calling a callback for
* each object found. The root object will also be called. This is for debug
* purposes only.
* @param ctx The #RefcountContext
* @param obj The root object
* @param callback The callback
* @param user_data Extra data to pass to the callback
* @return True if the walk ended early because a callback returned true, false
* otherwise
*/
bool refcount_debug_context_walk_tree(const RefcountContext *ctx, void *obj,
refcount_debug_walk_callback_t callback,
void *user_data) {
static int frame_start_mark, frame_end_mark;
if (!obj) {
return false;
}
RefcountList *queue = NULL;
queue = refcount_list_push_full(queue, obj, &ctx->alloc);
HTTable *seen = ht_new(&DEBUG_SEEN_FNS, &ctx->ht_alloc, NULL);
RefcountList *trail = NULL;
while (queue) {
void *cur = queue->data;
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
if (cur == &frame_start_mark) {
void *to_add = queue->data;
trail = refcount_list_push_full(trail, to_add, &ctx->alloc);
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
} else if (cur == &frame_end_mark) {
trail = refcount_list_pop_full(trail, NULL, &ctx->alloc);
} else {
if (callback) {
if (callback(cur, trail, user_data)) {
goto early_return;
}
}
if (cur && !ht_has(seen, cur)) {
ht_insert(seen, cur, NULL);
queue = refcount_list_push_full(queue, &frame_end_mark,
&ctx->alloc);
obj_held_refs(ctx, cur, &queue);
queue = refcount_list_push_full(queue, cur, &ctx->alloc);
queue = refcount_list_push_full(queue, &frame_start_mark,
&ctx->alloc);
}
}
}
ht_free(seen);
return false;
early_return:
ht_free(seen);
refcount_list_free_full(queue, NULL, &ctx->alloc);
refcount_list_free_full(trail, NULL, &ctx->alloc);
return true;
}
/** /**
* Count all instances of a target object by walking the references of some root * Count all instances of a target object by walking the references of some root
@ -859,20 +923,13 @@ bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj,
*/ */
uint64_t refcount_debug_context_count_object(const RefcountContext *ctx, uint64_t refcount_debug_context_count_object(const RefcountContext *ctx,
void *obj, void *target) { void *obj, void *target) {
static const HTTableFunctions SEEN_FNS = {
.destroy_key = NULL,
.destroy_value = NULL,
.equal = ht_intptr_equal_callback,
.hash = ht_intptr_hash_callback,
.user_data = NULL,
};
if (!obj) { if (!obj) {
return 0; return 0;
} }
RefcountList *queue = NULL; RefcountList *queue = NULL;
obj_held_refs(ctx, obj, &queue); obj_held_refs(ctx, obj, &queue);
uint64_t total_count = 0; uint64_t total_count = 0;
HTTable *seen = ht_new(&SEEN_FNS, &ctx->ht_alloc, NULL); HTTable *seen = ht_new(&DEBUG_SEEN_FNS, &ctx->ht_alloc, NULL);
while (queue) { while (queue) {
void *cur = queue->data; void *cur = queue->data;
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc); queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);

View File

@ -46,6 +46,16 @@ void reref_destructor(void *a, void *ctx_raw) {
refcount_context_ref(ctx, a); refcount_context_ref(ctx, a);
} }
UNUSED bool print_tree_walk_callback(void *obj, const RefcountList *trail,
void *ignored) {
size_t len = refcount_list_length(trail);
for (size_t i = 0; i < len; ++i) {
fprintf(stderr, " ");
}
fprintf(stderr, "- %p\n", obj);
return false;
}
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
struct ContextAndFlag ctx_and_flag = {.should_be_doing_gc = false}; struct ContextAndFlag ctx_and_flag = {.should_be_doing_gc = false};
@ -153,6 +163,8 @@ int main(int argc, const char **argv) {
assert(refcount_context_garbage_collect(c) == 0); assert(refcount_context_garbage_collect(c) == 0);
assert(refcount_context_unref(c, a_with_destructor)); assert(refcount_context_unref(c, a_with_destructor));
assert(!refcount_context_is_doing_gc(c)); assert(!refcount_context_is_doing_gc(c));
/* refcount_debug_context_walk_tree(c, a_with_destructor, */
/* print_tree_walk_callback, NULL); */
ctx_and_flag.should_be_doing_gc = true; ctx_and_flag.should_be_doing_gc = true;
assert(refcount_context_garbage_collect(c) == 26); assert(refcount_context_garbage_collect(c) == 26);
ctx_and_flag.should_be_doing_gc = false; ctx_and_flag.should_be_doing_gc = false;