This is an automated email from the git hooks/post-receive script.

git pushed a commit to reference refs/pull/36/head
in repository terminology.

View the commit online.

commit 9f97638f5c63fb85f76c53bf51ffd67bfa09cacc
Author: [email protected] <[email protected]>
AuthorDate: Sat Mar 21 17:12:51 2026 -0600

    fix: defer tymux widget switch to avoid use-after-free
    
    _termio_tymux_switch was called from within the PTY's read handler
    chain (_handle_read → termpty_handle_buf → _handle_esc →
    _smart_pty_command). Calling termpty_free on the PTY mid-parse left
    dangling pointers up the call stack, causing a crash on the next
    write to the freed Termpty.
    
    Fix: store the session name in sd->tymux_pending and schedule the
    actual switch via ecore_job_add. The job runs on the next main loop
    iteration, after the PTY read handler has returned cleanly.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 src/bin/termio.c          | 37 ++++++++++++++++++++++++++++---------
 src/bin/termiointernals.h |  1 +
 2 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/src/bin/termio.c b/src/bin/termio.c
index 16568308..024f33b6 100644
--- a/src/bin/termio.c
+++ b/src/bin/termio.c
@@ -3537,6 +3537,7 @@ _smart_del(Evas_Object *obj)
    if (sd->pty) termpty_free(sd->pty);
 #ifdef HAVE_TYMUX
    eina_stringshare_del(sd->tymux_name);
+   eina_stringshare_del(sd->tymux_pending);
 #endif
    eina_stringshare_del(sd->link.string);
    if (sd->glayer) evas_object_del(sd->glayer);
@@ -3813,14 +3814,21 @@ _tymux_name_valid(const char *name)
    return EINA_TRUE;
 }
 
+/* Deferred tymux switch — runs on the next Ecore main loop iteration
+ * so we don't free the PTY while we're still inside its read handler. */
 static void
-_termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
+_termio_tymux_switch_job(void *data)
 {
+   Evas_Object *obj = data;
+   Termio *sd = evas_object_smart_data_get(obj);
    Termpty   *spty  = NULL;
    TermTymux *tymux = NULL;
+   const char *tymux_name;
 
-   EINA_SAFETY_ON_NULL_RETURN(tymux_name);
-   if (!_tymux_name_valid(tymux_name)) return;
+   if (!sd) return;
+   tymux_name = sd->tymux_pending;
+   if (!tymux_name) return;
+   sd->tymux_pending = NULL;
 
    /* Clean up any previous tymux or plain PTY state before attaching. */
    if (sd->tymux)
@@ -3835,8 +3843,6 @@ _termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
      }
    else if (sd->pty)
      {
-        /* Plain PTY — free it.  The shell and 'tymux attach' helper both
-         * receive SIGHUP when the PTY master is closed. */
         termpty_free(sd->pty);
         sd->pty = NULL;
      }
@@ -3848,16 +3854,16 @@ _termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
    if (!tymux)
      {
         ERR("tymux switch: could not attach to '%s'", tymux_name);
+        eina_stringshare_del(tymux_name);
         return;
      }
 
-   /* Install the shadow PTY that renders the tymux session. */
    sd->pty = spty;
    sd->pty->obj = obj;
 
    /* Replace tymux tracking fields. */
    eina_stringshare_del(sd->tymux_name);
-   sd->tymux_name = eina_stringshare_add(tymux_name);
+   sd->tymux_name = tymux_name; /* take ownership of the stringshare */
    sd->tymux = tymux;
 
    /* Wire callbacks — same pattern as the tymux branch in termio_add(). */
@@ -3870,13 +3876,26 @@ _termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
    sd->tymux->cb_exit.func   = _smart_pty_exited;
    sd->tymux->cb_exit.data   = ""
 
-   /* Light up the tymux indicator in the containing Term widget. */
    if (sd->term)
      term_tymux_indicator_set(sd->term, EINA_TRUE);
 
-   /* Force an immediate redraw so the session content appears. */
    _smart_apply(obj);
 }
+
+static void
+_termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
+{
+   EINA_SAFETY_ON_NULL_RETURN(tymux_name);
+   if (!_tymux_name_valid(tymux_name)) return;
+
+   /* Store the tymux name and schedule the actual switch for the next
+    * main loop iteration.  We cannot free the PTY here because we are
+    * called from within its read handler (_handle_read → termpty_handle_buf
+    * → _handle_esc → _smart_pty_command).  Freeing it now would leave
+    * dangling pointers up the call stack. */
+   eina_stringshare_replace(&sd->tymux_pending, tymux_name);
+   ecore_job_add(_termio_tymux_switch_job, obj);
+}
 #endif /* HAVE_TYMUX */
 
 static void
diff --git a/src/bin/termiointernals.h b/src/bin/termiointernals.h
index 61417573..1b766b49 100644
--- a/src/bin/termiointernals.h
+++ b/src/bin/termiointernals.h
@@ -81,6 +81,7 @@ struct tag_Termio
    /* tymux_name: tymux name (eina_stringshare).  Kept for display
     * purposes (e.g. window title) even after the tymux fd is open. */
    const char  *tymux_name;   /* eina_stringshare */
+   const char  *tymux_pending;  /* eina_stringshare; deferred switch target */
 #endif
    Ecore_Animator *anim;
    Ecore_Timer *delayed_size_timer;

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to