Lv_obj_is_valid() doesn't work 100% of the time

I’ve made a GUI consisting of several different screens. These screens have shared objects as well as some objects that stay the same across all screens. I’ve made a light and dark mode for all screens and the approach I took used lv_obj_is_valid(). I’ve also made a “stress test” (based off of the lv_demo_music autoplay feature) for my GUI that will interact with each screen and toggle the light/dark mode multiple times before switching to the next screen and looping. What happens is that after a while (sometimes over an hour) lv_obj_is_valid() will return true to an object that does not exist on a screen and cause a crash. I’m unsure if this is known or not, as lv_obj_is_valid() works 99% of the time, has anyone else run into something similar?

Below I’ll outline my approach to the GUI with multiple screens and how the light/dark mode toggle would execute.

The parent of all objects in the GUI is called main_screen, main_screen has children that are the objects that are persistent on all screens and then an invisible container that is the holder for children of a particular screen.

Say main_screen has children main_obj1, main_obj2, screen_cont1, and screen_cont1 has the children obj1, obj2, obj3. And say we want to switch to a different screen. I would call lv_obj_del(screen_cont1), and then setup (create) screen_cont2. screen_cont2 has the children obj2, obj3, obj4. Because screen_cont1 and screen_cont2 share many objects I didn’t want to make a toggle light/dark mode function for every screen using only the objects that exist on that screen. Instead I made a single function to toggle all the objects and just check if they are valid or not, like so:

void adjust_styles(void)
{
    // don't check if these objects are valid or not because they are persistent across all screens...
    lv_obj_set_style_bg_color(main_obj1, color[screen_mode], LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_invalidate(main_obj1);

    lv_obj_set_style_bg_color(main_obj2, color[screen_mode], LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_invalidate(main_obj2);

    // check if these objects are valid
    if (lv_obj_is_valid(obj1))
    {
        lv_obj_set_style_bg_color(obj1, color[screen_mode], LV_PART_MAIN | LV_STATE_DEFAULT);
        lv_obj_invalidate(obj1);
    }

    if (lv_obj_is_valid(obj2))
    {
        lv_obj_set_style_bg_color(obj2, color[screen_mode], LV_PART_MAIN | LV_STATE_DEFAULT);
        lv_obj_invalidate(obj2);
    }

    if (lv_obj_is_valid(obj3))
    {
        lv_obj_set_style_bg_color(obj3, color[screen_mode], LV_PART_MAIN | LV_STATE_DEFAULT);
        lv_obj_invalidate(obj3);
    }

    if (lv_obj_is_valid(obj4))
    {
        lv_obj_set_style_bg_color(obj4, color[screen_mode], LV_PART_MAIN | LV_STATE_DEFAULT);
        lv_obj_invalidate(obj4);
    }

}

Now what would happen is that this would work for a while, but eventually if adjust_styles() was called while screen_cont2 was the active screen container, somehow lv_obj_is_valid(obj1) would return true even though it doesn’t exist on that screen.

My current workaround is after I delete a screen I then set the object pointers to NULL and check if an object points to NULL or not inside adjust_styles(). It ran for over 16 hours before I was satisfied and closed the debug session. What I’m getting at is I think something may be wrong with lv_obj_is_valid()?

lv_obj_is_valid can fail in very rare cases. Let’s say lv_obj_t * scr1_obj1 points to a valid object. You delete it and start to create new widgets. It can happen that a new widget is allocated to the very same address which is pointed by scr1_obj1. So scr1_obj1 by accident will point to a valid widget.

I think we can not do anything to prevent it and detect these cases. Setting scr1_obj1 = NULL when it’s deleted is definitely the recommended practice.

1 Like

Okay, that makes sense to why it only happens when running a stress test for a long period of time (lots of deleting and creating of objects). Would it not be possible to set an object (and its children) to point to NULL inside of lv_obj_del?

If you don’t delete them it will cause memory leak. So they should be both deleted and set to NULL.

I suppose I was slightly unclear. I whipped up a quick demonstration in the LVGL simulator for Visual Studio.

It consists of two objects in which we create both and delete one. The code exists as this:

typedef struct {
    lv_obj_t* main_screen;
    lv_obj_t* obj1;
}lvgl_obj_struct_t;

void test_func(lvgl_obj_struct_t* ui)
{
    ui->main_screen = lv_obj_create(NULL);
    lv_obj_set_style_bg_color(ui->main_screen, lv_color_hex(0xffffff), MAIN_DEFAULT);

    ui->obj1 = lv_btn_create(ui->main_screen);

    lv_obj_del(ui->obj1);

    return;
}

I took some images of the what the objects are pointing to.

Before anything is created:


Both objects point to NULL.

After “main_screen” is created:


“main_screen” now points to 0x00007ff6e325be58

After “obj1” is created:


“obj1” now points to 0x00007ff6e325bf10

After “obj1” is deleted:


“obj1” still points to 0x00007ff6e325bf10, but it’s memory has been freed by lv_obj_del

The solution would obviously be to set the object to point to NULL after deleting it like below.

void test_func(lvgl_obj_struct_t* ui)
{
    ui->main_screen = lv_obj_create(NULL);
    lv_obj_set_style_bg_color(ui->main_screen, lv_color_hex(0xffffff), MAIN_DEFAULT);

    ui->obj1 = lv_btn_create(ui->main_screen);

    lv_obj_del(ui->obj1);
    ui->obj1 = NULL;

    return;
}

So that we get this after deleting “obj1”:


“obj1” now points to NULL.

What I was trying to say is can the ui->obj1 = NULL portion of this function be inside of the lv_obj_del? This way that the user does not have to manually point deleted objects to NULL and instead it is just done automatically when calling lv_obj_del

Let’s say we have this code:

lv_obj_t * obj1 = lv_obj_create(scr);
...
lv_obj_del(obj1);

In lv_obj_del(lv_obj_t * obj) setting obj = NULL won’t help as it sets only the parameter and not obj1. To achieve what you said we needed:

lv_obj_del(&obj1);

void lv_obj_del(lv_obj_t ** obj)
{
 *obj = NULL;
}

It might work, but if you saved a children of obj1 as child1 this function won’t be able to find and NULL child1.

Anyway, if it fits to your implementation you can easily implement this in your code:

lv_obj_del_null(lv_obj_t ** obj)
{
  lv_obj_del(obj);
  (*obj) = NULL;
}

An other option could be to have an event handler which is trigger on LV_EVENT_DELETE like this:

lv_obj_add_event_cb(obj1, lv_obj_del_null_event_cb, LV_EVENT_DELETED, &obj1);


/*Will be called when the obj is deleted*/
void lv_obj_del_null_event_cb(lv_event_t * e)
{
   lv_obj_t ** var = lv_event_get_user_data(e);
   (*var) = NULL;
}

For the sake of simplicity we can have a wrapper like:

void lv_obj_del_auto_null(lv_obj_t ** obj) 
{
  lv_obj_add_event_cb(*obj, lv_obj_del_null_event_cb, LV_EVENT_DELETED, obj);
}

What do you think?

1 Like

Yes, that appears to do exactly what I want it to! I was playing around with lv_obj_del(lv_obj_t* obj) yesterday, and like you said, I was able to get it to set the parent object to NULL if I changed it to lv_obj_del(lv_obj_t** obj) but couldn’t figure out a way to get the children to point to NULL as well. For some reason it never occurred to me to use the LV_EVENT_DELETE event. I’ve added your implementation to my code and I’m testing to see if I run into any problems. Thank you!

1 Like