diff --git a/legacy/eina/configure.ac b/legacy/eina/configure.ac index 01bcca2c58..06f113660d 100644 --- a/legacy/eina/configure.ac +++ b/legacy/eina/configure.ac @@ -480,7 +480,9 @@ AC_SUBST([EINA_CONFIGURE_HAVE_DIRENT_H]) ### Checks for library functions AC_ISC_POSIX AC_FUNC_ALLOCA -AC_CHECK_FUNCS([strlcpy openat fstatat fpathconf execvp]) +AC_CHECK_FUNCS([strlcpy openat fstatat fpathconf execvp backtrace backtrace_symbols]) + +AC_CHECK_HEADERS([execinfo.h], [AC_DEFINE([HAVE_EXECINFO_H], [1], [Have execinfo.h])]) AC_MSG_CHECKING([for dirfd]) AC_LINK_IFELSE( diff --git a/legacy/eina/src/include/eina_model.h b/legacy/eina/src/include/eina_model.h index 996a135c2e..d91f477046 100644 --- a/legacy/eina/src/include/eina_model.h +++ b/legacy/eina/src/include/eina_model.h @@ -21,6 +21,7 @@ #include "eina_types.h" #include "eina_value.h" +#include "eina_inlist.h" #include /** @@ -262,29 +263,142 @@ EAPI Eina_Bool eina_model_interface_implemented(const Eina_Model *model, const E /** * @brief Increases the refcount of @a model. + * @param model The model to increase reference. + * @return The @a model with reference increased. * * @see eina_model_new() * @see eina_model_unref() * @since 1.2 */ EAPI Eina_Model *eina_model_ref(Eina_Model *model) EINA_ARG_NONNULL(1); + +/** + * @brief Increases the refcount of @a model, informs reference identifier. + * @param model The model to increase reference. + * @param id An identifier to mark this reference. + * @param label An optional label to help debug, may be @c NULL. + * @return The @a model with reference increased. + * + * This extended version of reference explicitly marks the origin of + * the reference and eina_model_xunref() should be used to check and + * remove it. + * + * Usually the @a id is another object, like a parent object, or some + * class/structure/file/function that is holding the reference for + * some reason. + * + * Its purpose is to help debuging if Eina was compiled with model + * usage debug enabled and environment variable @c EINA_MODEL_DEBUG=1 + * is set. + * + * It is recommended to use eina_model_xref() and eina_model_xunref() + * pair whenever you want to be sure you released your + * references. Both at your own type, or using applications. As an + * example #EINA_MODEL_INTERFACE_CHILDREN_INARRAY will use this to + * make sure it deleted every managed children. + * + * In order to debug leaks, consider using eina_model_xrefs_get() or + * eina_models_usage_dump() for a global picture. However, some + * references are not tracked, namely: + * + * @li eina_model_new() + * @li eina_model_child_get() + * @li eina_model_child_iterator_get() + * @li eina_model_child_reversed_iterator_get() + * @li eina_model_child_sorted_iterator_get() + * @li eina_model_child_filtered_iterator_get() + * @li eina_model_child_slice_iterator_get() + * @li eina_model_child_slice_reversed_iterator_get() + * @li eina_model_child_slice_sorted_iterator_get() + * @li eina_model_child_slice_filtered_iterator_get() + * + * @note this function is slower than eina_model_ref() if + * @c EINA_MODEL_DEBUG is set to "1" or "backtrace". Otherwise it + * should have the same performance cost. + * + * @see eina_model_ref() + * @see eina_model_xunref() + * @since 1.2 + */ +EAPI Eina_Model *eina_model_xref(Eina_Model *model, + const void *id, + const char *label) EINA_ARG_NONNULL(1, 2); + /** * @brief Decreases the refcount of @a model. + * @param model The model to decrease reference. + * + * After this function returns, consider @a model pointer invalid. * * @see eina_model_ref() * @see eina_model_del() * @since 1.2 */ EAPI void eina_model_unref(Eina_Model *model) EINA_ARG_NONNULL(1); + +/** + * @brief Decreases the refcount of @a model, informs reference identifier. + * @param model The model to decrease reference. + * @param id An identifier to mark this reference. + * + * This function will match eina_model_xref() and the @a id must match + * a previously call, otherwise it will produce an error if @c + * EINA_MODEL_DEBUG is set to "1" or "backtrace", and the reference is + * not decreased! + * + * After this function returns, consider @a model pointer invalid. + * + * @note this function is slower than eina_model_unref() if + * @c EINA_MODEL_DEBUG is set to "1" or "backtrace". Otherwise it + * should have the same performance cost. + * + * @see eina_model_xref() + * @since 1.2 + */ +EAPI void eina_model_xunref(Eina_Model *model, + const void *id) EINA_ARG_NONNULL(1, 2); + /** * @brief Returns the number of references to @a model. + * @param model The model to query number of references. + * @return number of references to model * * @see eina_model_ref() * @see eina_model_unref() + * @see eina_model_xref() + * @see eina_model_xunref() + * @see eina_model_xrefs_get() * @since 1.2 */ EAPI int eina_model_refcount(const Eina_Model *model) EINA_ARG_NONNULL(1); +typedef struct _Eina_Model_XRef Eina_Model_XRef; +struct _Eina_Model_XRef +{ + EINA_INLIST; + const void *id; /**< as given to eina_model_xref() */ + struct { + const void * const *symbols; /**< only if @c EINA_MODEL_DEBUG=backtrace is set, otherwise is @c NULL */ + unsigned int count; /**< only if @c EINA_MODEL_DEBUG=backtrace is set, otherwise is 0 */ + } backtrace; + char label[]; +}; + +/** + * @brief Returns the current references of this model. + * @param model The model to query references. + * @return List of reference holders as Eina_Model_XRef. This is the internal + * list for speed purposes, do not modify or free it in anyway! + * + * @note This list only exist if environment variable + * @c EINA_MODEL_DEBUG is set to "1" or "backtrace". + * + * @note The backtrace information is only available if environment + * variable @c EINA_MODEL_DEBUG=backtrace is set. + * @since 1.2 + */ +EAPI const Eina_Inlist *eina_model_xrefs_get(const Eina_Model *model) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT EINA_MALLOC; + /** * @brief Add a callback to be called when @a event_name is emited. * @@ -1418,6 +1532,33 @@ EAPI void eina_model_interface_children_sort(const Eina_Model_Interface *iface, */ EAPI extern const Eina_Model_Interface *EINA_MODEL_INTERFACE_CHILDREN_INARRAY; +/** + * @brief Dump usage of all existing modules. + * @since 1.2 + */ +EAPI void eina_models_usage_dump(void); + +/** + * @brief Return a list of all live models. + * @return a newly allocated list of Eina_Model. Free using + * eina_models_list_free() + * + * @note this is meant to debug purposes, do not modify the models in + * any way! + * + * @note due performance reasons, this is only @b enabled when + * @c EINA_MODEL_DEBUG is set to "1" or "backtrace". + * + * @since 1.2 + */ +EAPI Eina_List *eina_models_list_get(void); + +/** + * @brief Release list returned by eina_models_list_get() + * @param list the list to release. + */ +EAPI void eina_models_list_free(Eina_List *list); + /** * @} */ diff --git a/legacy/eina/src/lib/eina_model.c b/legacy/eina/src/lib/eina_model.c index 7b85eecc1b..2e2afd10eb 100644 --- a/legacy/eina/src/lib/eina_model.c +++ b/legacy/eina/src/lib/eina_model.c @@ -37,6 +37,10 @@ extern "C" void *alloca (size_t); #endif +#ifdef HAVE_EXECINFO_H +#include +#endif + #include "eina_config.h" #include "eina_private.h" #include "eina_error.h" @@ -66,6 +70,13 @@ static char *_eina_model_mp_choice = NULL; static Eina_Hash *_eina_model_descriptions = NULL; static Eina_Lock _eina_model_descriptions_lock; static int _eina_model_log_dom = -1; +static enum { + EINA_MODEL_DEBUG_NONE = 0, + EINA_MODEL_DEBUG_CHECK = 1, + EINA_MODEL_DEBUG_BACKTRACE = 2, +} _eina_model_debug = EINA_MODEL_DEBUG_NONE; +static Eina_Lock _eina_model_debug_list_lock; +static Eina_List *_eina_model_debug_list = NULL; static const char _eina_model_str_deleted[] = "deleted"; static const char _eina_model_str_freed[] = "freed"; @@ -1003,6 +1014,7 @@ struct _Eina_Model int walking; /**< increased while walking entries lists */ } listeners; void **privates; /**< private data per type and interface, each level gets its own stuff */ + Eina_Inlist *xrefs; /**< if EINA_MODEL_DEBUG and eina_model_xref() is used */ int refcount; /**< number of users of this model instance */ Eina_Bool deleted:1; /**< if deleted but still have references */ EINA_MAGIC @@ -1616,7 +1628,7 @@ _eina_model_type_base_child_iterator_free(Eina_Iterator *base) { Eina_Iterator_Model_Base *it; it = (Eina_Iterator_Model_Base *)base; - _eina_model_unref(it->model); + eina_model_xunref(it->model, it); free(it); } @@ -1632,7 +1644,7 @@ _eina_model_type_base_child_iterator_get(Eina_Model *model, unsigned int start, it->base.get_container = _eina_model_type_base_child_iterator_get_container; it->base.free = _eina_model_type_base_child_iterator_free; - it->model = eina_model_ref(model); + it->model = eina_model_xref(model, it, "eina_model_child_slice_iterator_get"); it->current = start; it->end = start + count; @@ -1678,7 +1690,7 @@ _eina_model_type_base_child_reversed_iterator_free(Eina_Iterator *base) { Eina_Iterator_Model_Base_Reversed *it; it = (Eina_Iterator_Model_Base_Reversed *)base; - _eina_model_unref(it->model); + eina_model_xunref(it->model, it); free(it); } @@ -1708,7 +1720,7 @@ _eina_model_type_base_child_reversed_iterator_get(Eina_Model *model, unsigned in it->base.get_container = _eina_model_type_base_child_reversed_iterator_get_container; it->base.free = _eina_model_type_base_child_reversed_iterator_free; - it->model = eina_model_ref(model); + it->model = eina_model_xref(model, it, "eina_model_child_slice_reversed_iterator_get"); it->current = start + count; it->end = start; @@ -1753,7 +1765,7 @@ _eina_model_type_base_child_sorted_iterator_free(Eina_Iterator *base) Eina_Iterator_Model_Base_Sorted *it; unsigned int i; it = (Eina_Iterator_Model_Base_Sorted *)base; - _eina_model_unref(it->model); + eina_model_xunref(it->model, it); for (i = 0; i < it->count; i++) _eina_model_unref(it->elements[i]); @@ -1788,7 +1800,7 @@ _eina_model_type_base_child_sorted_iterator_get(Eina_Model *model, unsigned int it->base.get_container = _eina_model_type_base_child_sorted_iterator_get_container; it->base.free = _eina_model_type_base_child_sorted_iterator_free; - it->model = eina_model_ref(model); + it->model = eina_model_xref(model, it, "eina_model_child_slice_sorted_iterator_get"); it->current = 0; it->count = count; @@ -1854,7 +1866,7 @@ _eina_model_type_base_child_filtered_iterator_free(Eina_Iterator *base) { Eina_Iterator_Model_Base_Filtered *it; it = (Eina_Iterator_Model_Base_Filtered *)base; - _eina_model_unref(it->model); + eina_model_xunref(it->model, it); free(it); } @@ -1870,7 +1882,7 @@ _eina_model_type_base_child_filtered_iterator_get(Eina_Model *model, unsigned in it->base.get_container = _eina_model_type_base_child_filtered_iterator_get_container; it->base.free = _eina_model_type_base_child_filtered_iterator_free; - it->model = eina_model_ref(model); + it->model = eina_model_xref(model, it, "eina_model_child_slice_filtered_iterator_get"); it->match = match; it->data = data; it->current = start; @@ -2750,7 +2762,7 @@ _eina_model_interface_children_inarray_destructor(Eina_Model *model) itr = priv->members; itr_end = itr + count; for (; itr < itr_end; itr++) - _eina_model_unref(*itr); + eina_model_xunref(*itr, EINA_MODEL_INTERFACE_CHILDREN_INARRAY); eina_inarray_flush(priv); return EINA_TRUE; @@ -2787,8 +2799,9 @@ _eina_model_interface_children_inarray_set(Eina_Model *model, unsigned int posit if (!eina_inarray_replace_at(priv, position, &child)) return EINA_FALSE; - eina_model_ref(child); - _eina_model_unref(old); + eina_model_xref(child, EINA_MODEL_INTERFACE_CHILDREN_INARRAY, + "eina_model_child_set"); + eina_model_xunref(old, EINA_MODEL_INTERFACE_CHILDREN_INARRAY); return EINA_TRUE; } @@ -2806,7 +2819,7 @@ _eina_model_interface_children_inarray_del(Eina_Model *model, unsigned int posit if (!eina_inarray_remove_at(priv, position)) return EINA_FALSE; - _eina_model_unref(old); + eina_model_xunref(old, EINA_MODEL_INTERFACE_CHILDREN_INARRAY); return EINA_TRUE; } @@ -2818,7 +2831,8 @@ _eina_model_interface_children_inarray_insert_at(Eina_Model *model, unsigned int if (!eina_inarray_insert_at(priv, position, &child)) return EINA_FALSE; - eina_model_ref(child); + eina_model_xref(child, EINA_MODEL_INTERFACE_CHILDREN_INARRAY, + "eina_model_child_insert_at"); return EINA_TRUE; } @@ -2922,6 +2936,15 @@ eina_model_init(void) return EINA_FALSE; } + choice = getenv("EINA_MODEL_DEBUG"); + if (choice) + { + if (strcmp(choice, "1") == 0) + _eina_model_debug = EINA_MODEL_DEBUG_CHECK; + else if (strcmp(choice, "backtrace") == 0) + _eina_model_debug = EINA_MODEL_DEBUG_BACKTRACE; + } + #ifdef EINA_DEFAULT_MEMPOOL choice = "pass_through"; #else @@ -2966,6 +2989,12 @@ eina_model_init(void) goto on_init_fail_hash_desc; } + if (!eina_lock_new(&_eina_model_debug_list_lock)) + { + ERR("Cannot create model debug list lock in model init."); + goto on_init_fail_lock_debug; + } + EINA_ERROR_MODEL_FAILED = eina_error_msg_static_register( EINA_ERROR_MODEL_FAILED_STR); EINA_ERROR_MODEL_METHOD_MISSING = eina_error_msg_static_register( @@ -2988,6 +3017,8 @@ eina_model_init(void) return EINA_TRUE; + on_init_fail_lock_debug: + eina_hash_free(_eina_model_descriptions); on_init_fail_hash_desc: eina_lock_free(&_eina_model_descriptions_lock); on_init_fail_lock_desc: @@ -3019,6 +3050,12 @@ eina_model_init(void) Eina_Bool eina_model_shutdown(void) { + eina_lock_take(&_eina_model_debug_list_lock); + if (eina_list_count(_eina_model_debug_list) > 0) + ERR("%d models are still alive!", eina_list_count(_eina_model_debug_list)); + eina_lock_release(&_eina_model_debug_list_lock); + eina_lock_free(&_eina_model_debug_list_lock); + eina_lock_take(&_eina_model_inner_mps_lock); if (eina_hash_population(_eina_model_inner_mps) != 0) ERR("Cannot free eina_model internal memory pools -- still in use!"); @@ -3125,6 +3162,7 @@ eina_model_new(const Eina_Model_Type *type) } model->refcount = 1; + model->xrefs = NULL; model->deleted = EINA_FALSE; EINA_MAGIC_SET(model, EINA_MAGIC_MODEL); @@ -3169,6 +3207,14 @@ eina_model_new(const Eina_Model_Type *type) goto failed_constructor; } + if (EINA_UNLIKELY(_eina_model_debug)) + { + eina_lock_take(&_eina_model_debug_list_lock); + _eina_model_debug_list = eina_list_append + (_eina_model_debug_list, model); + eina_lock_release(&_eina_model_debug_list_lock); + } + return model; failed_constructor: @@ -3206,6 +3252,28 @@ _eina_model_free(Eina_Model *model) model, model->desc->cache.types[0]->name, model->refcount, model->deleted); + if (EINA_UNLIKELY(_eina_model_debug)) + { + if (model->xrefs) + { + ERR("Model %p (%s) released with references pending:", + model, model->desc->cache.types[0]->name); + while (model->xrefs) + { + Eina_Model_XRef *ref = (Eina_Model_XRef *)model->xrefs; + model->xrefs = eina_inlist_remove(model->xrefs, model->xrefs); + + ERR("xref: %p '%s'", ref->id, ref->label); + free(ref); + } + } + + eina_lock_take(&_eina_model_debug_list_lock); + _eina_model_debug_list = eina_list_remove + (_eina_model_debug_list, model); + eina_lock_release(&_eina_model_debug_list_lock); + } + /* flush every interface, natural order */ for (i = 0; i < desc->total.ifaces; i++) if (desc->cache.ifaces[i]->flush) @@ -3477,6 +3545,56 @@ eina_model_ref(Eina_Model *model) return model; } +static Eina_Model * +_eina_model_xref_add(Eina_Model *model, const void *id, const char *label) +{ + Eina_Model_XRef *ref; + void *bt[256]; + int btlen, labellen; + + labellen = label ? strlen(label): 0; + btlen = 0; + +#ifdef HAVE_BACKTRACE + if (_eina_model_debug == EINA_MODEL_DEBUG_BACKTRACE) + btlen = backtrace(bt, EINA_C_ARRAY_LENGTH(bt)); +#endif + + ref = calloc(1, sizeof(*ref) + (btlen * sizeof(void *)) + (labellen + 1)); + EINA_SAFETY_ON_NULL_RETURN_VAL(ref, NULL); + + ref->id = id; + memcpy(ref->label, label, labellen); + ref->label[labellen] = '\0'; + ref->backtrace.count = btlen; + if (btlen == 0) ref->backtrace.symbols = NULL; + else + { + void *ptr = (unsigned char *)ref + sizeof(*ref) + (labellen + 1); + ref->backtrace.symbols = ptr; + memcpy(ptr, bt, btlen * sizeof(void *)); + } + + model->xrefs = eina_inlist_append(model->xrefs, EINA_INLIST_GET(ref)); + return model; +} + +EAPI Eina_Model * +eina_model_xref(Eina_Model *model, const void *id, const char *label) +{ + EINA_MODEL_INSTANCE_CHECK_VAL(model, NULL); + DBG("model %p (%s) refcount=%d deleted=" FMT_UCHAR" id=%p label=%s", + model, model->desc->cache.types[0]->name, + model->refcount, model->deleted, id, label ? label : ""); + + model->refcount++; + + if (EINA_LIKELY(!_eina_model_debug)) + return model; + + return _eina_model_xref_add(model, id, label); +} + EAPI void eina_model_unref(Eina_Model *model) { @@ -3484,6 +3602,31 @@ eina_model_unref(Eina_Model *model) _eina_model_unref(model); } +EAPI void +eina_model_xunref(Eina_Model *model, const void *id) +{ + Eina_Model_XRef *ref; + EINA_MODEL_INSTANCE_CHECK(model); + + if (EINA_LIKELY(!_eina_model_debug)) + { + _eina_model_unref(model); + return; + } + + EINA_INLIST_FOREACH(model->xrefs, ref) + { + if (ref->id != id) continue; + + model->xrefs = eina_inlist_remove(model->xrefs, EINA_INLIST_GET(ref)); + free(ref); + _eina_model_unref(model); + return; + } + + ERR("Could not find existing reference %p to model %p", id, model); +} + EAPI int eina_model_refcount(const Eina_Model *model) { @@ -3491,6 +3634,13 @@ eina_model_refcount(const Eina_Model *model) return model->refcount; } +EAPI const Eina_Inlist * +eina_model_xrefs_get(const Eina_Model *model) +{ + EINA_MODEL_INSTANCE_CHECK_VAL(model, NULL); + return model->xrefs; +} + EAPI Eina_Bool eina_model_event_callback_add(Eina_Model *model, const char *event_name, Eina_Model_Event_Cb cb, const void *data) { @@ -5187,3 +5337,82 @@ eina_model_struct_get(const Eina_Model *model, const Eina_Value_Struct_Desc **p_ if (p_memory) *p_memory = st.memory; return EINA_FALSE; } + +EAPI void +eina_models_usage_dump(void) +{ + const Eina_List *l; + const Eina_Model *m; + + eina_lock_take(&_eina_model_debug_list_lock); + + puts("DDD: model refs info (type, holders, backtrace)"); + puts("DDD: -------------- -------------- ---------------------------------"); + + EINA_LIST_FOREACH(_eina_model_debug_list, l, m) + { + Eina_Model_XRef *ref; + + printf("DDD: %14p %14d %s\n", + m, m->refcount, m->desc->cache.types[0]->name); + + EINA_INLIST_FOREACH(m->xrefs, ref) + { + printf("DDD: id: %p '%s'\n", + ref->id, ref->label); + if (ref->backtrace.count) + { + char **symbols; + unsigned int i; + +#ifdef HAVE_BACKTRACE_SYMBOLS + symbols = backtrace_symbols((void * const *)ref->backtrace.symbols, + ref->backtrace.count); +#else + symbols = NULL; +#endif + + printf("DDD: Backtrace: Address Symbol\n"); + for (i = 0; i < ref->backtrace.count; i++) + printf("DDD: %14p %s\n", + ref->backtrace.symbols[i], + symbols ? symbols[i] : "???"); + + free(symbols); + puts("DDD:"); + } + } + } + + eina_lock_release(&_eina_model_debug_list_lock); +} + +EAPI Eina_List * +eina_models_list_get(void) +{ + const Eina_List *l; + Eina_Model *m; + Eina_List *ret = NULL; + + eina_lock_take(&_eina_model_debug_list_lock); + + EINA_LIST_FOREACH(_eina_model_debug_list, l, m) + { + ret = eina_list_append + (ret, eina_model_xref + (m, eina_models_list_get, "eina_models_list_get")); + } + + eina_lock_release(&_eina_model_debug_list_lock); + + return ret; +} + +EAPI void +eina_models_list_free(Eina_List *list) +{ + Eina_Model *m; + + EINA_LIST_FREE(list, m) + eina_model_xunref(m, eina_models_list_get); +}