Add tree walking debug function
This commit is contained in:
@ -457,6 +457,34 @@ static inline bool refcount_remove_destructor(void *obj, void *key) {
|
||||
}
|
||||
|
||||
// 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,
|
||||
void *obj, void *target);
|
||||
|
@ -846,6 +846,70 @@ bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj,
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -859,20 +923,13 @@ bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj,
|
||||
*/
|
||||
uint64_t refcount_debug_context_count_object(const RefcountContext *ctx,
|
||||
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) {
|
||||
return 0;
|
||||
}
|
||||
RefcountList *queue = NULL;
|
||||
obj_held_refs(ctx, obj, &queue);
|
||||
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) {
|
||||
void *cur = queue->data;
|
||||
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
|
||||
|
@ -46,6 +46,16 @@ void reref_destructor(void *a, void *ctx_raw) {
|
||||
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) {
|
||||
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_unref(c, a_with_destructor));
|
||||
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;
|
||||
assert(refcount_context_garbage_collect(c) == 26);
|
||||
ctx_and_flag.should_be_doing_gc = false;
|
||||
|
Reference in New Issue
Block a user