diff --git a/src/lib/eo/efl_object.eo b/src/lib/eo/efl_object.eo index 7de713e5be..f41b6ee3eb 100644 --- a/src/lib/eo/efl_object.eo +++ b/src/lib/eo/efl_object.eo @@ -288,6 +288,9 @@ abstract Efl.Object () callback,add @hot; [[A callback was added.]] callback,del @hot; [[A callback was deleted.]] del @hot; [[Object is being deleted.]] + destruct @hot; [[Object has been fully destroyed. It can not be used + beyond this point. This event should only serve to clean up any + dangling pointer.]] } } diff --git a/src/lib/eo/eo_base_class.c b/src/lib/eo/eo_base_class.c index e5d932addc..a5295fa373 100644 --- a/src/lib/eo/eo_base_class.c +++ b/src/lib/eo/eo_base_class.c @@ -59,6 +59,7 @@ typedef struct Eina_Bool need_cleaning : 1; Eina_Bool parent_sunk : 1; // If parent ref has already been settled (parent has been set, or we are in add_ref mode Eina_Bool allow_parent_unref : 1; // Allows unref to zero even with a parent + Eina_Bool has_destroyed_event_cb : 1; // No proper count: minor optimization triggered at destruction only } Efl_Object_Data; typedef enum @@ -146,10 +147,12 @@ _eo_generic_data_node_free(Eo_Generic_Data_Node *node) case DATA_PTR: break; case DATA_OBJ: + // FIXME: should this use "destruct" event instead? efl_event_callback_del(node->d.obj, EFL_EVENT_DEL, _key_generic_cb_del, node); efl_unref(node->d.obj); break; case DATA_OBJ_WEAK: + // FIXME: should this use "destruct" event instead? efl_event_callback_del(node->d.obj, EFL_EVENT_DEL, _key_generic_cb_del, node); break; case DATA_VAL: @@ -1068,6 +1071,8 @@ _special_event_count_inc(Efl_Object_Data *pd, const Efl_Callback_Array_Item *it) CB_COUNT_INC(pd->event_cb_efl_event_callback_del_count); else if (it->desc == EFL_EVENT_DEL) CB_COUNT_INC(pd->event_cb_efl_event_del_count); + else if (it->desc == EFL_EVENT_DESTRUCT) + pd->has_destroyed_event_cb = EINA_TRUE; } static inline void @@ -1126,6 +1131,7 @@ _eo_callback_remove_all(Efl_Object_Data *pd) eina_freeq_ptr_main_add(pd->callbacks, free, 0); pd->callbacks = NULL; pd->callbacks_count = 0; + pd->has_destroyed_event_cb = EINA_FALSE; #ifdef EFL_EVENT_SPECIAL_SKIP pd->event_cb_efl_event_callback_add_count = 0; pd->event_cb_efl_event_callback_del_count = 0; @@ -1284,6 +1290,8 @@ _efl_object_event_callback_priority_add(Eo *obj, Efl_Object_Data *pd, #ifdef EFL_EVENT_SPECIAL_SKIP _special_event_count_inc(pd, &(cb->items.item)); #endif + if (EINA_UNLIKELY(desc == EFL_EVENT_DESTRUCT)) + pd->has_destroyed_event_cb = EINA_TRUE; efl_event_callback_call(obj, EFL_EVENT_CALLBACK_ADD, (void *)arr); @@ -1389,6 +1397,16 @@ _efl_object_event_callback_array_priority_add(Eo *obj, Efl_Object_Data *pd, #ifdef EFL_EVENT_SPECIAL_SKIP for (it = cb->items.item_array; it->func; it++) _special_event_count_inc(pd, it); +#else + if (!pd->has_destroyed_event_cb) + { + for (it = cb->items.item_array; it->func; it++) + if (it->desc == EFL_EVENT_DESTRUCT) + { + pd->has_destroyed_event_cb = EINA_TRUE; + break; + } + } #endif efl_event_callback_call(obj, EFL_EVENT_CALLBACK_ADD, (void *)array); @@ -2044,8 +2062,16 @@ composite_obj_back: err_parent_back: _efl_pending_futures_clear(pd); - _eo_generic_data_del_all(obj, pd); _wref_destruct(pd); + + // this isn't 100% correct, as the object is still "slightly" alive at this + // point (so efl_destructed_is() returns false), but triggering the + // "destruct" event here is the simplest, safest solution. + if (EINA_UNLIKELY(pd->has_destroyed_event_cb)) + _event_callback_call(obj, pd, EFL_EVENT_DESTRUCT, NULL, EINA_FALSE); + + // remove generic data after this final event, in case they are used in a cb + _eo_generic_data_del_all(obj, pd); _eo_callback_remove_all(pd); ext = pd->ext; diff --git a/src/tests/eo/suite/eo_test_general.c b/src/tests/eo/suite/eo_test_general.c index 46c351fdd9..e475889e95 100644 --- a/src/tests/eo/suite/eo_test_general.c +++ b/src/tests/eo/suite/eo_test_general.c @@ -1719,6 +1719,44 @@ START_TEST(efl_cast_test) } END_TEST +static void _destruct_test_del_cb(void *data, const Efl_Event *ev EINA_UNUSED) +{ + int *var = data; + *var = 1; +} + +static void _destruct_test_destruct_cb(void *data, const Efl_Event *ev) +{ + int *var = data; + *var *= 2; + + ck_assert_int_eq(efl_ref_count(ev->object), 0); + + // test disabled: object isn't yet marked as destructed (we're inside the + // base class destructor here). + //ck_assert_int_ne(efl_destructed_is(ev->object), 0); +} + +START_TEST(efl_object_destruct_test) +{ + int var = 0; + Eo *obj; + + efl_object_init(); + + obj = efl_add(SIMPLE_CLASS, NULL); + fail_if(efl_ref_count(obj) != 1); + efl_event_callback_add(obj, EFL_EVENT_DEL, _destruct_test_del_cb, &var); + efl_event_callback_add(obj, EFL_EVENT_DESTRUCT, _destruct_test_destruct_cb, &var); + efl_del(obj); + + // var should be 2 if del then destruct, 0 otherwise + ck_assert_int_eq(var, 2); + + efl_object_shutdown(); +} +END_TEST + static void _auto_unref_del_cb(void *data, const Efl_Event *ev EINA_UNUSED) { @@ -1797,5 +1835,6 @@ void eo_test_general(TCase *tc) tcase_add_test(tc, eo_rec_interface); tcase_add_test(tc, eo_domain); tcase_add_test(tc, efl_cast_test); + tcase_add_test(tc, efl_object_destruct_test); tcase_add_test(tc, efl_object_auto_unref_test); }