Add debug functions
This commit is contained in:
		| @ -454,6 +454,24 @@ static inline bool refcount_remove_destructor(void *obj, void *key) { | |||||||
|                                               key); |                                               key); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Debug Functions | ||||||
|  |  | ||||||
|  | uint64_t refcount_debug_context_count_object(const RefcountContext *ctx, | ||||||
|  |                                              void *obj, void *target); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Same as #refcount_debug_context_count_object, but only operates on the global | ||||||
|  |  * context. | ||||||
|  |  * @param obj The root object | ||||||
|  |  * @param target The object to look for | ||||||
|  |  * @return The number of times the target appeared in the reference tree of the | ||||||
|  |  * root | ||||||
|  |  */ | ||||||
|  | static inline uint64_t refcount_debug_count_object(void *obj, void *target) { | ||||||
|  |     return refcount_debug_context_count_object(refcount_default_context, obj, | ||||||
|  |                                                target); | ||||||
|  | } | ||||||
|  |  | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -250,7 +250,7 @@ end: | |||||||
|  * @param refs Where to store the refs |  * @param refs Where to store the refs | ||||||
|  * @return True on success |  * @return True on success | ||||||
|  */ |  */ | ||||||
| static inline bool obj_held_refs(RefcountContext *ctx, void *obj, | static inline bool obj_held_refs(const RefcountContext *ctx, void *obj, | ||||||
|                                  RefcountList **refs) { |                                  RefcountList **refs) { | ||||||
|     if (ctx->held_refs_callback) { |     if (ctx->held_refs_callback) { | ||||||
|         return ctx->held_refs_callback(obj, refs, ctx->user_data); |         return ctx->held_refs_callback(obj, refs, ctx->user_data); | ||||||
| @ -843,3 +843,48 @@ bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj, | |||||||
|     unlock_entry_mtx(ENTRY); |     unlock_entry_mtx(ENTRY); | ||||||
|     return success; |     return success; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Debug Functions | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Count all instances of a target object by walking the references of some root | ||||||
|  |  * object. This is for debug purposes only. The root is not included in the | ||||||
|  |  * count (as in, if `obj == target`, it will not be counted). | ||||||
|  |  * @param ctx The #RefcountContext | ||||||
|  |  * @param obj The root object | ||||||
|  |  * @param target The object to look for | ||||||
|  |  * @return The number of times the target appeared in the reference tree of the | ||||||
|  |  * root | ||||||
|  |  */ | ||||||
|  | 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); | ||||||
|  |     while (queue) { | ||||||
|  |         void *cur = queue->data; | ||||||
|  |         queue = refcount_list_pop_full(queue, NULL, &ctx->alloc); | ||||||
|  |         // count NULL | ||||||
|  |         if (cur == target) { | ||||||
|  |             ++total_count; | ||||||
|  |         } | ||||||
|  |         // but don't try to descend into it | ||||||
|  |         if (cur && !ht_has(seen, cur)) { | ||||||
|  |             ht_insert(seen, cur, NULL); | ||||||
|  |             obj_held_refs(ctx, cur, &queue); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     ht_free(seen); | ||||||
|  |     return total_count; | ||||||
|  | } | ||||||
|  | |||||||
| @ -65,6 +65,10 @@ int main(int argc, const char **argv) { | |||||||
|     STATIC_A(1); |     STATIC_A(1); | ||||||
|     STATIC_A(2); |     STATIC_A(2); | ||||||
|     STATIC_A(3); |     STATIC_A(3); | ||||||
|  |     assert(refcount_debug_context_count_object(c, &static_a_1, static_a_1.next) | ||||||
|  |            == 1); | ||||||
|  |     assert(refcount_debug_context_count_object(c, &static_a_1, static_a_2.next) | ||||||
|  |            == 0); | ||||||
|  |  | ||||||
|     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)); | ||||||
| @ -124,6 +128,7 @@ int main(int argc, const char **argv) { | |||||||
|     first->next = a; |     first->next = a; | ||||||
|     a = first; |     a = first; | ||||||
|     assert(refcount_context_num_refs(c, a) == 1); |     assert(refcount_context_num_refs(c, a) == 1); | ||||||
|  |     assert(refcount_debug_context_count_object(c, a, a) == 1); | ||||||
|  |  | ||||||
|     refcount_context_ref(c, a); |     refcount_context_ref(c, a); | ||||||
|     refcount_context_unref(c, a); |     refcount_context_unref(c, a); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user