Subject: bug#36190: 27.0.50; `put-text-property' etc. with buffer argument calls current buffer's `after-change-functions'

From: Pip Cet <[email-masked]>
Date: Sat, 15 Jun 2019 19:27:30 +0000

On Sat, Jun 15, 2019 at 3:23 PM Eli Zaretskii <[email-masked]> wrote:
> > From: Pip Cet <[email-masked]>
> > Date: Sat, 15 Jun 2019 15:14:43 +0000
> > Cc: [email-masked]
> >
> > How about simply emulating (with-current-buffer buffer
> > (put-text-property ...)), as the attached patch does? That should fix
> > the immediate problem.
>
> Looks better to me, but I'd suggest a comment explaining why we do
> such an unusual thing.

Sounds good to me. The attached patch contains the comments and a hint
in the documentation. (Maybe the comments should be a FIXME, because
there's really no need to switch buffers at all when there are no
applicable hooks?)

Obviously, feel free to reword as you consider appropriate.
From 01784e6b16c40aef362f39519a4e8b29982c8fcd Mon Sep 17 00:00:00 2001
From: Pip Cet <[email-masked]>
Date: Sat, 15 Jun 2019 15:12:14 +0000
Subject: [PATCH] Update current buffer when changing text properties.

* src/textprop.c (add_text_properties_1, set_text_properties)
(set_text_properties_1, Fremove_text_properties):
---
 doc/lispref/text.texi |  2 +-
 src/textprop.c        | 58 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index 2e7c497f57..035fea6a6b 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -2800,7 +2800,7 @@ Examining Properties
 
   These functions handle both strings and buffers.  Keep in mind that
 positions in a string start from 0, whereas positions in a buffer start
-from 1.
+from 1.  Passing a buffer other than the current buffer may be slow.
 
 @defun get-text-property pos prop &optional object
 This function returns the value of the @var{prop} property of the
diff --git a/src/textprop.c b/src/textprop.c
index ae42c44185..56f276b9ad 100644
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -1141,6 +1141,17 @@ DEFUN ("previous-single-property-change", Fprevious_single_property_change,
 add_text_properties_1 (Lisp_Object start, Lisp_Object end,
 		       Lisp_Object properties, Lisp_Object object,
 		       enum property_set_type set_type) {
+  /* Ensure we run the modification hooks for the right buffer,
+     without switching buffers twice (bug 36190). */
+  if (BUFFERP (object) && XBUFFER (object) != current_buffer)
+    {
+      ptrdiff_t count = SPECPDL_INDEX ();
+      record_unwind_current_buffer ();
+      set_buffer_internal (XBUFFER (object));
+      return unbind_to (count, add_text_properties_1 (start, end, properties,
+						      object, set_type));
+    }
+
   INTERVAL i, unchanged;
   ptrdiff_t s, len;
   bool modified = false;
@@ -1342,6 +1353,17 @@ face(s) are retained.  This is done by setting the `face' property to
 set_text_properties (Lisp_Object start, Lisp_Object end, Lisp_Object properties,
 		     Lisp_Object object, Lisp_Object coherent_change_p)
 {
+  /* Ensure we run the modification hooks for the right buffer,
+     without switching buffers twice (bug 36190). */
+  if (BUFFERP (object) && XBUFFER (object) != current_buffer)
+    {
+      ptrdiff_t count = SPECPDL_INDEX ();
+      record_unwind_current_buffer ();
+      set_buffer_internal (XBUFFER (object));
+      return unbind_to (count, set_text_properties (start, end, properties,
+						    object, coherent_change_p));
+    }
+
   INTERVAL i;
   bool first_time = true;
 
@@ -1412,6 +1434,19 @@ set_text_properties (Lisp_Object start, Lisp_Object end, Lisp_Object properties,
 set_text_properties_1 (Lisp_Object start, Lisp_Object end,
 		       Lisp_Object properties, Lisp_Object object, INTERVAL i)
 {
+  /* Ensure we run the modification hooks for the right buffer,
+     without switching buffers twice (bug 36190). */
+  if (BUFFERP (object) && XBUFFER (object) != current_buffer)
+    {
+      ptrdiff_t count = SPECPDL_INDEX ();
+      record_unwind_current_buffer ();
+      set_buffer_internal (XBUFFER (object));
+
+      set_text_properties_1 (start, end, properties, object, i);
+      unbind_to (count, Qnil);
+      return;
+    }
+
   INTERVAL prev_changed = NULL;
   ptrdiff_t s = XFIXNUM (start);
   ptrdiff_t len = XFIXNUM (end) - s;
@@ -1494,6 +1529,17 @@ DEFUN ("remove-text-properties", Fremove_text_properties,
 Use `set-text-properties' if you want to remove all text properties.  */)
   (Lisp_Object start, Lisp_Object end, Lisp_Object properties, Lisp_Object object)
 {
+  /* Ensure we run the modification hooks for the right buffer,
+     without switching buffers twice (bug 36190). */
+  if (BUFFERP (object) && XBUFFER (object) != current_buffer)
+    {
+      ptrdiff_t count = SPECPDL_INDEX ();
+      record_unwind_current_buffer ();
+      set_buffer_internal (XBUFFER (object));
+      return unbind_to (count, Fremove_text_properties (start, end, properties,
+							object));
+    }
+
   INTERVAL i, unchanged;
   ptrdiff_t s, len;
   bool modified = false;
@@ -1606,6 +1652,18 @@ DEFUN ("remove-list-of-text-properties", Fremove_list_of_text_properties,
 Return t if any property was actually removed, nil otherwise.  */)
   (Lisp_Object start, Lisp_Object end, Lisp_Object list_of_properties, Lisp_Object object)
 {
+  /* Ensure we run the modification hooks for the right buffer,
+     without switching buffers twice (bug 36190). */
+  if (BUFFERP (object) && XBUFFER (object) != current_buffer)
+    {
+      ptrdiff_t count = SPECPDL_INDEX ();
+      record_unwind_current_buffer ();
+      set_buffer_internal (XBUFFER (object));
+      return unbind_to (count, Fremove_list_of_text_properties (start, end,
+								list_of_properties,
+								object));
+    }
+
   INTERVAL i, unchanged;
   ptrdiff_t s, len;
   bool modified = false;
-- 
2.20.1