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
|
// 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);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user