There is a similar question there: Running Elisp code when a variable is changed that is closed as a duplicate. The feature is needed there for emacs 24.5.
You get the backtrace
at a setq
of a critical symbol with the following code.
Execute the code below and (debug-setq 'desktop-dirname)
before you load the critical section of your lisp code.
Please note the remark of npostavs stating that no part of the critical section of your lisp code should be byte-compiled. Be warned that there may be auto-load
s and require
s in that section.
(defvar setq-critical-symbol)
(setq setq-critical-symbol nil)
(declare-function #'old-setq "subr" t t)
(unless (fboundp #'old-setq)
(fset #'old-setq (symbol-function #'setq)))
(defun reset-setq ()
"Mainly as a savior in the case of emergency."
(interactive)
(assert (fboundp #'old-setq)) ; should not happen with the form before the definition of `reset-setq'
(old-setq setq-debug nil)
(fset #'setq (symbol-function #'old-setq)))
(defun debug-setq (&optional critical-symbol)
"Instruments `setq' such that `backtrace' is called if the `symbol-value' of a critical symbol is set via `setq'.
The critical symbol is specified by the variable `setq-critical-symbol'.
If CRITICAL-SYMBOL is non-nil `setq-critical-symbol' is set to that value (a symbol)."
(interactive "SCritical symbol:")
(setq setq-critical-symbol critical-symbol)
(defmacro setq (&rest args)
(let ((myargs (make-symbol "*myargs*")))
`(let ((,myargs '(,@args))
(ret (old-setq ,@args))
debug-on-entry)
(when setq-critical-symbol
(while ,myargs
(when (eq (car ,myargs) setq-critical-symbol)
(fset #'setq (symbol-function #'old-setq)) ;; maybe that is over-cautious
(old-setq setq-critical-symbol nil)
(backtrace))
(old-setq ,myargs (cddr ,myargs))))
ret))))
A test demonstrating the function (can be run line-wise in the scratch-buffer):
(debug-setq 'test-arg)
(defun fun-with-critical-setq ()
(setq test-arg 0))
(fun-with-critical-setq)
(setq setq-critical-symbol nil)
(fun-with-critical-setq)
The first call to fun-with-critical-setq
spits the following backtrace out:
backtrace()
(progn (fset (function setq) (symbol-function (function old-setq))) (old-setq setq-critical-symbol nil) (backtrace))
(if (eq (car *myargs*) setq-critical-symbol) (progn (fset (function setq) (symbol-function (function old-setq))) (old-setq setq-critical-symbol nil) (backtrace)))
(while *myargs* (if (eq (car *myargs*) setq-critical-symbol) (progn (fset (function setq) (symbol-function (function old-setq))) (old-setq setq-critical-symbol nil) (backtrace))) (old-setq *myargs* (cdr (cdr *myargs*))))
(progn (while *myargs* (if (eq (car *myargs*) setq-critical-symbol) (progn (fset (function setq) (symbol-function (function old-setq))) (old-setq setq-critical-symbol nil) (backtrace))) (old-setq *myargs* (cdr (cdr *myargs*)))))
(if setq-critical-symbol (progn (while *myargs* (if (eq (car *myargs*) setq-critical-symbol) (progn (fset (function setq) (symbol-function (function old-setq))) (old-setq setq-critical-symbol nil) (backtrace))) (old-setq *myargs* (cdr (cdr *myargs*))))))
(let ((*myargs* (quote (test-arg 0))) (ret (old-setq test-arg 0)) debug-on-entry) (if setq-critical-symbol (progn (while *myargs* (if (eq (car *myargs*) setq-critical-symbol) (progn (fset (function setq) (symbol-function ...)) (old-setq setq-critical-symbol nil) (backtrace))) (old-setq *myargs* (cdr (cdr *myargs*)))))) ret)
fun-with-critical-setq()
eval((fun-with-critical-setq) nil)
elisp--eval-last-sexp(nil)
eval-last-sexp(nil)
funcall-interactively(eval-last-sexp nil)
call-interactively(eval-last-sexp nil nil)
command-execute(eval-last-sexp)
0 (#o0, #x0, ?\C-@)
You must ignore the overhead caused by the customized setq
at the beginning of the backtrace.
The second call to fun-with-critical-setq
works like the original setq
for two reasons:
- The customized
setq
resets its own symbol to the original version.
- The customized
setq
works like the original version if setq-critical-symbol
is set to nil
.
The second reason means that if you just call (debug-setq)
instead of (debug-setq 'test-arg)
the customized setq
works like the original variant until you set setq-critical-symbol
.