Lagrange [work/v1.7]

Avoid accessing recently deleted widgets

=> f6aea1fd30c97e9e799ae324ae7a32a66d6f6572

diff --git a/src/app.c b/src/app.c
index 7dae4b2f..590913cf 100644
--- a/src/app.c
+++ b/src/app.c
@@ -2283,6 +2283,7 @@ void refresh_App(void) {
     init_PtrArray(&windows);
     listWindows_App_(d, &windows);
     /* Destroy pending widgets. */ {
+        clearRecentlyDeleted_Widget();
         iConstForEach(PtrArray, j, &windows) {
             iWindow *win = j.ptr;
             setCurrent_Window(win);
diff --git a/src/ui/command.c b/src/ui/command.c
index 307fd44c..49a4ad60 100644
--- a/src/ui/command.c
+++ b/src/ui/command.c
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 
 #include "command.h"
+#include "widget.h"
 #include "app.h"
 
 #include 
@@ -122,7 +123,12 @@ void *pointerLabel_Command(const char *cmd, const char *label) {
 }
 
 void *pointer_Command(const char *cmd) {
-    return pointerLabel_Command(cmd, "ptr");
+    void *ptr = pointerLabel_Command(cmd, "ptr");
+    if (isRecentlyDeleted_Widget(ptr)) {
+        /* This widget has been marked as deleted, so we cannot reference it any more. */
+        return NULL;
+    }
+    return ptr;
 }
 
 const char *suffixPtr_Command(const char *cmd, const char *label) {
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 2ba742be..014cad64 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -52,6 +52,32 @@ struct Impl_WidgetDrawBuffer {
     iInt2        oldOrigin;
 };
 
+iDeclareType(RecentlyDeleted)
+
+/* Keep track of widgets that were recently deleted, so events related to them can be ignored. */
+struct Impl_RecentlyDeleted {
+    iMutex    mtx; /* async callbacks must not post events related to deleted widgets */
+    iPtrSet * objs;
+};
+static iRecentlyDeleted recentlyDeleted_;
+
+static void maybeInit_RecentlyDeleted_(iRecentlyDeleted *d) {
+    if (!d->objs) {
+        init_Mutex(&d->mtx);
+        d->objs = new_PtrSet();
+    }
+}
+
+static iBool contains_RecentlyDeleted_(iRecentlyDeleted *d, const iAnyObject *obj) {
+    if (d->objs && obj) {
+        lock_Mutex(&d->mtx);
+        const iBool wasDel = contains_PtrSet(d->objs, obj);
+        unlock_Mutex(&d->mtx);
+        return wasDel;
+    }
+    return iFalse;
+}
+
 static void init_WidgetDrawBuffer(iWidgetDrawBuffer *d) {
     d->texture   = NULL;
     d->size      = zero_I2();
@@ -175,6 +201,7 @@ static int treeSize_Widget_(const iWidget *d, int n) {
 }
 
 void deinit_Widget(iWidget *d) {
+    addRecentlyDeleted_Widget(d);
     if (d->flags2 & usedAsPeriodicContext_WidgetFlag2) {
         remove_Periodic(periodic_App(), d); /* periodic context being deleted */
     }
@@ -2401,6 +2428,9 @@ iWidget *mouseGrab_Widget(void) {
 }
 
 void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
+    if (isRecentlyDeleted_Widget(d)) {
+        return; /* invalid context */
+    }
     iString str;
     init_String(&str); {
         va_list args;
@@ -2547,3 +2577,21 @@ void identify_Widget(const iWidget *d) {
     printf("Root %d: %p\n", 1 + (d->root == get_Window()->roots[1]), d->root);
     fflush(stdout);
 }
+
+void addRecentlyDeleted_Widget(iAnyObject *obj) {
+    /* We sometimes include pointers to widgets in command events. Before an event is processed,
+       it is possible that the referened widget has been destroyed. Keeping track of recently
+       deleted widgets allows ignoring these events. */
+    maybeInit_RecentlyDeleted_(&recentlyDeleted_);
+    iGuardMutex(&recentlyDeleted_.mtx, insert_PtrSet(recentlyDeleted_.objs, obj));
+}
+
+void clearRecentlyDeleted_Widget(void) {
+    if (recentlyDeleted_.objs) {
+        iGuardMutex(&recentlyDeleted_.mtx, clear_PtrSet(recentlyDeleted_.objs));
+    }
+}
+
+iBool isRecentlyDeleted_Widget(const iAnyObject *obj) {
+    return contains_RecentlyDeleted_(&recentlyDeleted_, obj);
+}
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 940604df..61fafe35 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -357,3 +357,8 @@ iBool       hasVisibleChildOnTop_Widget
                                     (const iWidget *parent);
 void        printTree_Widget        (const iWidget *);
 void        identify_Widget         (const iWidget *); /* prints to stdout */
+
+void        addRecentlyDeleted_Widget   (iAnyObject *obj);
+iBool       isRecentlyDeleted_Widget    (const iAnyObject *obj);
+void        clearRecentlyDeleted_Widget (void);
+    
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.7/cdiff/f6aea1fd30c97e9e799ae324ae7a32a66d6f6572
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
76.289567 milliseconds
Gemini-to-HTML Time
0.229383 milliseconds

This content has been proxied by September (3851b).