Subject: Re: bug#73041: 30.0.90; track-changes-mode logs warnings (#70541 regression? not actually fixed?)

From: Stefan Monnier <[email-masked]>
Date: Thu, 03 Oct 2024 15:21:24 -0400

Hi Eric,

> Duh, sorry.  Try this patch instead.

I pushed to Emacs `master` a patch to `track-changes.el` which doesn't
fix any bug but allows it to record more information to help find the
root cause of errors.

If you're able to try Emacs `master` (which would be helpful), then the
patch I sent doesn't apply to it (any more), so please use the patch
below instead and add `(setq track-changes-record-errors 'trace)` to your
init file so as to activate the additional recording.

        Stefan
diff --git a/lisp/emacs-lisp/track-changes.el b/lisp/emacs-lisp/track-changes.el
index 1b0f64f544d..245b7b89ae5 100644
--- a/lisp/emacs-lisp/track-changes.el
+++ b/lisp/emacs-lisp/track-changes.el
@@ -219,6 +219,7 @@ track-changes-register
 When IMMEDIATE is non-nil, the SIGNAL should probably not always call
 `track-changes-fetch', since that would defeat the purpose of this library."
   (track-changes--trace)
+  (cl-assert (track-changes--sane-state-p))
   (when (and nobefore disjoint)
     ;; FIXME: Without `before-change-functions', we can discover
     ;; a disjoint change only after the fact, which is not good enough.
@@ -235,6 +236,7 @@ track-changes-register
     (push tracker track-changes--clean-trackers)
     (when disjoint
       (push tracker track-changes--disjoint-trackers))
+    (cl-assert (track-changes--sane-state-p))
     tracker))
 
 (defun track-changes-unregister (id)
@@ -243,6 +245,7 @@ track-changes-unregister
 not called), so it is good practice to unregister them when you don't
 need them any more."
   (track-changes--trace)
+  (cl-assert (track-changes--sane-state-p))
   (unless (memq id track-changes--trackers)
     (error "Unregistering a non-registered tracker: %S" id))
   (setq track-changes--trackers (delq id track-changes--trackers))
@@ -260,7 +263,8 @@ track-changes-unregister
             track-changes--buffer-size
             track-changes--before-clean
             track-changes--state))
-    (remove-hook 'after-change-functions  #'track-changes--after  t)))
+    (remove-hook 'after-change-functions  #'track-changes--after  t))
+  (cl-assert (track-changes--sane-state-p)))
 
 (defun track-changes-fetch (id func)
   "Fetch the pending changes for tracker ID pass them to FUNC.
@@ -380,7 +384,8 @@ track-changes-fetch
           (funcall func beg end (or before lenbefore)))
       ;; Re-enable the tracker's signal only after running `func', so
       ;; as to avoid nested invocations.
-      (cl-pushnew id track-changes--clean-trackers))))
+      (cl-pushnew id track-changes--clean-trackers)
+      (cl-assert (track-changes--sane-state-p)))))
 
 (defun track-changes-inconsistent-state-p ()
   "Return whether the current buffer is in an inconsistent state.
@@ -418,6 +423,26 @@ track-changes--trace
                        (track-change--backtrace
                         10 #'track-changes--trace)))))
 
+(defun track-changes--sane-state-p ()
+  (if (null track-changes--trackers)
+      (and (zerop track-changes--before-beg)
+           (zerop track-changes--before-end)
+           (equal track-changes--before-string "")
+           (null track-changes--buffer-size)
+           (eq track-changes--before-clean 'unset)
+           (null track-changes--state))
+    (and (equal track-changes--buffer-size (buffer-size))
+         (or track-changes--before-no
+             (pcase track-changes--before-clean
+               ('unset t)
+               ('set (<= track-changes--before-beg track-changes--before-end))
+               ('nil
+                (<= track-changes--before-beg
+                    (track-changes--state-beg track-changes--state)
+                    (track-changes--state-end track-changes--state)
+                    track-changes--before-end))
+               (_ nil))))))
+
 (defun track-changes--clean-state ()
   (cond
    ((null track-changes--state)
@@ -586,7 +611,8 @@ track-changes--before
                                      (length track-changes--before-string))))))
               (setf track-changes--before-end new-bend)
               (cl-callf concat track-changes--before-string
-                (buffer-substring-no-properties old-bend new-bend)))))))))
+                (buffer-substring-no-properties old-bend new-bend))))))))
+  (cl-assert (track-changes--sane-state-p)))
 
 (defun track-changes--after (beg end len)
   (track-changes--trace)
@@ -612,10 +638,26 @@ track-changes--after
         ;; BEG..END is not covered by previous `before-change-functions'!!
         (track-changes--recover-from-error `(unexpected-after ,beg ,end ,len))
       ;; Note the new changes.
+      (let ((orig-beg (track-changes--state-beg track-changes--state))
+            (orig-end (track-changes--state-end track-changes--state)))
       (when (< beg (track-changes--state-beg track-changes--state))
         (setf (track-changes--state-beg track-changes--state) beg))
       (cl-callf (lambda (old-end) (max end (+ old-end offset)))
           (track-changes--state-end track-changes--state))
+      (cl-assert (or track-changes--before-no
+                     (<= track-changes--before-beg
+                         (track-changes--state-beg track-changes--state)))
+                 nil "<=? %S %S (was %S)"
+                 track-changes--before-beg
+                 (track-changes--state-beg track-changes--state)
+                 orig-beg)
+      (cl-assert (or track-changes--before-no
+                     (<= (track-changes--state-end track-changes--state)
+                         track-changes--before-end))
+                 nil "<=? %S %S (was %S)"
+                 (track-changes--state-end track-changes--state)
+                 track-changes--before-end
+                 orig-end))
       (cl-assert (or track-changes--before-no
                      (<= track-changes--before-beg
                          (track-changes--state-beg track-changes--state)
@@ -627,7 +669,8 @@ track-changes--after
       (if (track-changes--tracker-immediate tracker)
           (funcall (track-changes--tracker-signal tracker) tracker)
         (run-with-timer 0 nil #'track-changes--call-signal
-                        (current-buffer) tracker)))))
+         (current-buffer) tracker))))
+  (cl-assert (track-changes--sane-state-p)))
 
 (defun track-changes--call-signal (buf tracker)
   (when (buffer-live-p buf)