From d49fa05c9ce166ad53ce9ebb73e0a5be2bb63a70 Mon Sep 17 00:00:00 2001
From: "Robert A. Burks" <rburksdev@gmail.com>
Date: Thu, 7 Mar 2024 02:07:29 -0500
Subject: [PATCH 03/31] Fix multiple watcher notification in set_internal
 (bug#00001)

* src/data.c (set_internal):
Variable watcher will now only execute one time when setting inside a
let binding of a forwarded symbol. (bug#00001)
---
 src/data.c | 82 ++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 58 insertions(+), 24 deletions(-)

diff --git a/src/data.c b/src/data.c
index 48da5cee429..e50adb94619 100644
--- a/src/data.c
+++ b/src/data.c
@@ -1634,40 +1634,49 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where,
 
   CHECK_SYMBOL (symbol);
   struct Lisp_Symbol *sym = XSYMBOL (symbol);
-  switch (sym->u.s.trapped_write)
+  /* Update for aliasing and sanitize the input to ensure only a bare
+     symbol is stored.  It may be accessed with positions disabled.
+     (e.g. A blv cons cell may be accessed directly.)  */
+ start:
+  XSETSYMBOL (symbol, sym);
+
+  if (sym->u.s.trapped_write == SYMBOL_NOWRITE)
     {
-    case SYMBOL_NOWRITE:
       if (NILP (Fkeywordp (symbol))
           || !EQ (newval, Fsymbol_value (symbol)))
         xsignal1 (Qsetting_constant, symbol);
       else
         /* Allow setting keywords to their own value.  */
-        return;
-
-    case SYMBOL_TRAPPED_WRITE:
-      /* Setting due to thread-switching doesn't count.  */
-      if (bindflag != SET_INTERNAL_THREAD_SWITCH)
-        notify_variable_watchers (symbol, voide? Qnil : newval,
-                                  (bindflag == SET_INTERNAL_BIND? Qlet :
-                                   bindflag == SET_INTERNAL_UNBIND? Qunlet :
-                                   voide? Qmakunbound : Qset),
-                                  where);
-      break;
-
-    case SYMBOL_UNTRAPPED_WRITE:
-      break;
-
-    default: emacs_abort ();
+	return;
     }
 
- start:
   switch (sym->u.s.redirect)
     {
     case SYMBOL_VARALIAS: sym = SYMBOL_ALIAS (sym); goto start;
-    case SYMBOL_PLAINVAL: SET_SYMBOL_VAL (sym , newval); return;
+    case SYMBOL_PLAINVAL:
+      {
+	/* If watched notify. Plain values are handled by 'unrewind'.
+	   We will not reach here during thread switch.  */
+	if (sym->u.s.trapped_write == SYMBOL_TRAPPED_WRITE)
+	  notify_variable_watchers (symbol, voide? Qnil : newval,
+				    (bindflag == SET_INTERNAL_BIND? Qlet :
+				     bindflag == SET_INTERNAL_UNBIND? Qunlet :
+				     voide? Qmakunbound : Qset), Qnil);
+	SET_SYMBOL_VAL (sym, newval);
+	return;
+      }
     case SYMBOL_LOCALIZED:
       {
 	struct Lisp_Buffer_Local_Value *blv = SYMBOL_BLV (sym);
+
+	/* If watched notify unless thread switching.  */
+	if (sym->u.s.trapped_write == SYMBOL_TRAPPED_WRITE
+	    && bindflag != SET_INTERNAL_THREAD_SWITCH)
+	  notify_variable_watchers (symbol, voide? Qnil : newval,
+				    (bindflag == SET_INTERNAL_BIND? Qlet :
+				     bindflag == SET_INTERNAL_UNBIND? Qunlet :
+				     voide? Qmakunbound : Qset), where);
+
 	if (NILP (where))
 	  XSETBUFFER (where, current_buffer);
 
@@ -1686,8 +1695,6 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where,
 	    if (blv->fwd.fwdptr)
 	      set_blv_value (blv, do_symval_forwarding (blv->fwd));
 
-	    /* Find the new binding.  */
-	    XSETSYMBOL (symbol, sym); /* May have changed via aliasing.  */
 	    Lisp_Object tem1
 	      = assq_no_quit (symbol,
 			      BVAR (XBUFFER (where), local_var_alist));
@@ -1746,6 +1753,14 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where,
       }
     case SYMBOL_FORWARDED:
       {
+	/* If let binding shadows buffer binding set_default_internal
+	   will be called, let it handle notification.
+	   Otherwise, make sure notification occurs prior to setting
+	   per buffer indicator. (Could these be combined? Yes. Would
+	   it confuse the next guy? Yes.)  */
+	bool notify = true;
+	int  per_buffer_after = 0;
+
 	struct buffer *buf
 	  = BUFFERP (where) ? XBUFFER (where) : current_buffer;
 	lispfwd innercontents = SYMBOL_FWD (sym);
@@ -1757,12 +1772,31 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where,
 	        && !PER_BUFFER_VALUE_P (buf, idx))
 	      {
 		if (let_shadows_buffer_binding_p (sym))
-		  set_default_internal (symbol, newval, bindflag);
+		  {
+		    notify = false;
+		    set_default_internal (symbol, newval, bindflag);
+		  }
 		else
-		  SET_PER_BUFFER_VALUE_P (buf, idx, 1);
+		  per_buffer_after = idx;
 	      }
 	  }
 
+	/* If watched notify unless thread switching.  Do not notify
+	   if set_default_internal was called, that path will.  */
+	if (sym->u.s.trapped_write == SYMBOL_TRAPPED_WRITE
+	    && notify
+	    && bindflag != SET_INTERNAL_THREAD_SWITCH)
+	  {
+	    notify_variable_watchers (symbol, voide? Qnil : newval,
+				      (bindflag == SET_INTERNAL_BIND? Qlet :
+				       bindflag == SET_INTERNAL_UNBIND? Qunlet :
+				       voide? Qmakunbound : Qset), where);
+	  }
+
+	/* Set per buffer indicator after notification.  */
+	if (per_buffer_after > 0)
+	   SET_PER_BUFFER_VALUE_P (buf, per_buffer_after, 1);
+
 	if (voide)
 	  { /* If storing void (making the symbol void), forward only through
 	       buffer-local indicator, not through Lisp_Objfwd, etc.  */
-- 
2.34.1

