aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNoam Postavsky2017-06-29 19:42:32 -0400
committerNoam Postavsky2017-06-29 19:42:32 -0400
commit169532b0ebc3acb0b1c943d0b3d8b569cd57ca4b (patch)
tree60d7ae515b92944d1e90772df3ae5dac05b5c715
parent138447c3abd749d1c27d99d7089b1b0903352ade (diff)
parentc87c87fcc361494815bbd1d92f450b0b80a3ecbb (diff)
downloademacs-169532b0ebc3acb0b1c943d0b3d8b569cd57ca4b.tar.gz
emacs-169532b0ebc3acb0b1c943d0b3d8b569cd57ca4b.zip
; Merge: Backtrace printing improvements (Bug#6991)
-rw-r--r--doc/emacs/killing.texi4
-rw-r--r--etc/NEWS9
-rw-r--r--lisp/emacs-lisp/cl-print.el9
-rw-r--r--lisp/emacs-lisp/debug.el181
-rw-r--r--lisp/emacs-lisp/ert.el85
-rw-r--r--lisp/select.el3
-rw-r--r--lisp/subr.el3
-rw-r--r--lisp/term/w32-win.el2
-rw-r--r--src/print.c45
-rw-r--r--test/lisp/emacs-lisp/cl-print-tests.el2
-rw-r--r--test/lisp/emacs-lisp/ert-tests.el8
11 files changed, 162 insertions, 189 deletions
diff --git a/doc/emacs/killing.texi b/doc/emacs/killing.texi
index 47de0531292..0b5efd04a18 100644
--- a/doc/emacs/killing.texi
+++ b/doc/emacs/killing.texi
@@ -519,6 +519,10 @@ when exiting Emacs; if you wish to prevent Emacs from transferring
519data to the clipboard manager, change the variable 519data to the clipboard manager, change the variable
520@code{x-select-enable-clipboard-manager} to @code{nil}. 520@code{x-select-enable-clipboard-manager} to @code{nil}.
521 521
522 Since strings containing NUL bytes are usually truncated when passed
523through the clipboard, Emacs replaces such characters with ``\0''
524before transfering them to the system's clipboard.
525
522@vindex select-enable-primary 526@vindex select-enable-primary
523@findex clipboard-kill-region 527@findex clipboard-kill-region
524@findex clipboard-kill-ring-save 528@findex clipboard-kill-ring-save
diff --git a/etc/NEWS b/etc/NEWS
index 5e10ca9cb62..dc9393c87d4 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -320,6 +320,15 @@ questions, with a handy way to display help texts.
320all call stack frames in a Lisp backtrace buffer as lists. Both 320all call stack frames in a Lisp backtrace buffer as lists. Both
321debug.el and edebug.el have been updated to heed to this variable. 321debug.el and edebug.el have been updated to heed to this variable.
322 322
323---
324** Values in call stack frames are now displayed using 'cl-prin1'.
325The old behaviour of using 'prin1' can be restored by customizing the
326new option 'debugger-print-function'.
327
328+++
329** NUL bytes in strings copied to the system clipboard are now
330replaced with "\0".
331
323+++ 332+++
324** The new variable 'x-ctrl-keysym' has been added to the existing 333** The new variable 'x-ctrl-keysym' has been added to the existing
325roster of X keysyms. It can be used in combination with another 334roster of X keysyms. It can be used in combination with another
diff --git a/lisp/emacs-lisp/cl-print.el b/lisp/emacs-lisp/cl-print.el
index 89a71d1b6c5..824d0b7b4f5 100644
--- a/lisp/emacs-lisp/cl-print.el
+++ b/lisp/emacs-lisp/cl-print.el
@@ -105,10 +105,11 @@ into a button whose action shows the function's disassembly.")
105 (if args 105 (if args
106 (prin1 args stream) 106 (prin1 args stream)
107 (princ "()" stream))) 107 (princ "()" stream)))
108 (let ((doc (documentation object 'raw))) 108 (pcase (help-split-fundoc (documentation object 'raw) object)
109 (when doc 109 ;; Drop args which `help-function-arglist' already printed.
110 (princ " " stream) 110 (`(,_usage . ,(and doc (guard (stringp doc))))
111 (prin1 doc stream))) 111 (princ " " stream)
112 (prin1 doc stream)))
112 (let ((inter (interactive-form object))) 113 (let ((inter (interactive-form object)))
113 (when inter 114 (when inter
114 (princ " " stream) 115 (princ " " stream)
diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el
index 83456fc31a2..726005af9b1 100644
--- a/lisp/emacs-lisp/debug.el
+++ b/lisp/emacs-lisp/debug.el
@@ -49,6 +49,12 @@ the middle is discarded, and just the beginning and end are displayed."
49 :group 'debugger 49 :group 'debugger
50 :version "21.1") 50 :version "21.1")
51 51
52(defcustom debugger-print-function #'cl-prin1
53 "Function used to print values in the debugger backtraces."
54 :type 'function
55 :options '(cl-prin1 prin1)
56 :version "26.1")
57
52(defcustom debugger-bury-or-kill 'bury 58(defcustom debugger-bury-or-kill 'bury
53 "What to do with the debugger buffer when exiting `debug'. 59 "What to do with the debugger buffer when exiting `debug'.
54The value affects the behavior of operations on any window 60The value affects the behavior of operations on any window
@@ -264,6 +270,43 @@ first will be printed into the backtrace buffer."
264 (setq debug-on-next-call debugger-step-after-exit) 270 (setq debug-on-next-call debugger-step-after-exit)
265 debugger-value))) 271 debugger-value)))
266 272
273
274(defvar cl-print-compiled-button)
275
276(defun debugger-insert-backtrace (frames do-xrefs)
277 "Format and insert the backtrace FRAMES at point.
278Make functions into cross-reference buttons if DO-XREFS is non-nil."
279 (let ((standard-output (current-buffer))
280 (cl-print-compiled-button t)
281 (eval-buffers eval-buffer-list))
282 (require 'help-mode) ; Define `help-function-def' button type.
283 (pcase-dolist (`(,evald ,fun ,args ,flags) frames)
284 (insert (if (plist-get flags :debug-on-exit)
285 "* " " "))
286 (let ((fun-file (and do-xrefs (symbol-file fun 'defun)))
287 (fun-pt (point)))
288 (cond
289 ((and evald (not debugger-stack-frame-as-list))
290 (funcall debugger-print-function fun)
291 (if args (funcall debugger-print-function args) (princ "()")))
292 (t
293 (funcall debugger-print-function (cons fun args))
294 (cl-incf fun-pt)))
295 (when fun-file
296 (make-text-button fun-pt (+ fun-pt (length (symbol-name fun)))
297 :type 'help-function-def
298 'help-args (list fun fun-file))))
299 ;; After any frame that uses eval-buffer, insert a line that
300 ;; states the buffer position it's reading at.
301 (when (and eval-buffers (memq fun '(eval-buffer eval-region)))
302 (insert (format " ; Reading at buffer position %d"
303 ;; This will get the wrong result if there are
304 ;; two nested eval-region calls for the same
305 ;; buffer. That's not a very useful case.
306 (with-current-buffer (pop eval-buffers)
307 (point)))))
308 (insert "\n"))))
309
267(defun debugger-setup-buffer (args) 310(defun debugger-setup-buffer (args)
268 "Initialize the `*Backtrace*' buffer for entry to the debugger. 311 "Initialize the `*Backtrace*' buffer for entry to the debugger.
269That buffer should be current already." 312That buffer should be current already."
@@ -271,27 +314,20 @@ That buffer should be current already."
271 (erase-buffer) 314 (erase-buffer)
272 (set-buffer-multibyte t) ;Why was it nil ? -stef 315 (set-buffer-multibyte t) ;Why was it nil ? -stef
273 (setq buffer-undo-list t) 316 (setq buffer-undo-list t)
274 (let ((standard-output (current-buffer))
275 (print-escape-newlines t)
276 (print-level 8)
277 (print-length 50))
278 ;; FIXME the debugger could pass a custom callback to mapbacktrace
279 ;; instead of manipulating printed results.
280 (mapbacktrace #'backtrace--print-frame 'debug))
281 (goto-char (point-min))
282 (delete-region (point)
283 (progn
284 (forward-line (if (eq (car args) 'debug)
285 ;; Remove debug--implement-debug-on-entry
286 ;; and the advice's `apply' frame.
287 3
288 1))
289 (point)))
290 (insert "Debugger entered") 317 (insert "Debugger entered")
291 ;; lambda is for debug-on-call when a function call is next. 318 (let ((frames (nthcdr
292 ;; debug is for debug-on-entry function called. 319 ;; Remove debug--implement-debug-on-entry and the
293 (let ((pos (point))) 320 ;; advice's `apply' frame.
321 (if (eq (car args) 'debug) 3 1)
322 (backtrace-frames 'debug)))
323 (print-escape-newlines t)
324 (print-escape-control-characters t)
325 (print-level 8)
326 (print-length 50)
327 (pos (point)))
294 (pcase (car args) 328 (pcase (car args)
329 ;; lambda is for debug-on-call when a function call is next.
330 ;; debug is for debug-on-entry function called.
295 ((or `lambda `debug) 331 ((or `lambda `debug)
296 (insert "--entering a function:\n") 332 (insert "--entering a function:\n")
297 (setq pos (1- (point)))) 333 (setq pos (1- (point))))
@@ -300,11 +336,9 @@ That buffer should be current already."
300 (insert "--returning value: ") 336 (insert "--returning value: ")
301 (setq pos (point)) 337 (setq pos (point))
302 (setq debugger-value (nth 1 args)) 338 (setq debugger-value (nth 1 args))
303 (prin1 debugger-value (current-buffer)) 339 (funcall debugger-print-function debugger-value (current-buffer))
304 (insert ?\n) 340 (setf (cl-getf (nth 3 (car frames)) :debug-on-exit) nil)
305 (delete-char 1) 341 (insert ?\n))
306 (insert ? )
307 (beginning-of-line))
308 ;; Watchpoint triggered. 342 ;; Watchpoint triggered.
309 ((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args))) 343 ((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args)))
310 (insert 344 (insert
@@ -327,7 +361,7 @@ That buffer should be current already."
327 (`error 361 (`error
328 (insert "--Lisp error: ") 362 (insert "--Lisp error: ")
329 (setq pos (point)) 363 (setq pos (point))
330 (prin1 (nth 1 args) (current-buffer)) 364 (funcall debugger-print-function (nth 1 args) (current-buffer))
331 (insert ?\n)) 365 (insert ?\n))
332 ;; debug-on-call, when the next thing is an eval. 366 ;; debug-on-call, when the next thing is an eval.
333 (`t 367 (`t
@@ -337,98 +371,15 @@ That buffer should be current already."
337 (_ 371 (_
338 (insert ": ") 372 (insert ": ")
339 (setq pos (point)) 373 (setq pos (point))
340 (prin1 (if (eq (car args) 'nil) 374 (funcall debugger-print-function
341 (cdr args) args) 375 (if (eq (car args) 'nil)
342 (current-buffer)) 376 (cdr args) args)
377 (current-buffer))
343 (insert ?\n))) 378 (insert ?\n)))
379 (debugger-insert-backtrace frames t)
344 ;; Place point on "stack frame 0" (bug#15101). 380 ;; Place point on "stack frame 0" (bug#15101).
345 (goto-char pos)) 381 (goto-char pos)))
346 ;; After any frame that uses eval-buffer, 382
347 ;; insert a line that states the buffer position it's reading at.
348 (save-excursion
349 (let ((tem eval-buffer-list))
350 (while (and tem
351 (re-search-forward "^ eval-\\(buffer\\|region\\)(" nil t))
352 (end-of-line)
353 (insert (format " ; Reading at buffer position %d"
354 ;; This will get the wrong result
355 ;; if there are two nested eval-region calls
356 ;; for the same buffer. That's not a very useful case.
357 (with-current-buffer (car tem)
358 (point))))
359 (pop tem))))
360 (debugger-make-xrefs))
361
362(defun debugger-make-xrefs (&optional buffer)
363 "Attach cross-references to function names in the `*Backtrace*' buffer."
364 (interactive "b")
365 (with-current-buffer (or buffer (current-buffer))
366 (save-excursion
367 (setq buffer (current-buffer))
368 (let ((inhibit-read-only t)
369 (old-end (point-min)) (new-end (point-min)))
370 ;; If we saved an old backtrace, find the common part
371 ;; between the new and the old.
372 ;; Compare line by line, starting from the end,
373 ;; because that's the part that is likely to be unchanged.
374 (if debugger-previous-backtrace
375 (let (old-start new-start (all-match t))
376 (goto-char (point-max))
377 (with-temp-buffer
378 (insert debugger-previous-backtrace)
379 (while (and all-match (not (bobp)))
380 (setq old-end (point))
381 (forward-line -1)
382 (setq old-start (point))
383 (with-current-buffer buffer
384 (setq new-end (point))
385 (forward-line -1)
386 (setq new-start (point)))
387 (if (not (zerop
388 (let ((case-fold-search nil))
389 (compare-buffer-substrings
390 (current-buffer) old-start old-end
391 buffer new-start new-end))))
392 (setq all-match nil))))
393 ;; Now new-end is the position of the start of the
394 ;; unchanged part in the current buffer, and old-end is
395 ;; the position of that same text in the saved old
396 ;; backtrace. But we must subtract (point-min) since strings are
397 ;; indexed in origin 0.
398
399 ;; Replace the unchanged part of the backtrace
400 ;; with the text from debugger-previous-backtrace,
401 ;; since that already has the proper xrefs.
402 ;; With this optimization, we only need to scan
403 ;; the changed part of the backtrace.
404 (delete-region new-end (point-max))
405 (goto-char (point-max))
406 (insert (substring debugger-previous-backtrace
407 (- old-end (point-min))))
408 ;; Make the unchanged part of the backtrace inaccessible
409 ;; so it won't be scanned.
410 (narrow-to-region (point-min) new-end)))
411
412 ;; Scan the new part of the backtrace, inserting xrefs.
413 (goto-char (point-min))
414 (while (progn
415 (goto-char (+ (point) 2))
416 (skip-syntax-forward "^w_")
417 (not (eobp)))
418 (let* ((beg (point))
419 (end (progn (skip-syntax-forward "w_") (point)))
420 (sym (intern-soft (buffer-substring-no-properties
421 beg end)))
422 (file (and sym (symbol-file sym 'defun))))
423 (when file
424 (goto-char beg)
425 ;; help-xref-button needs to operate on something matched
426 ;; by a regexp, so set that up for it.
427 (re-search-forward "\\(\\sw\\|\\s_\\)+")
428 (help-xref-button 0 'help-function-def sym file)))
429 (forward-line 1))
430 (widen))
431 (setq debugger-previous-backtrace (buffer-string)))))
432 383
433(defun debugger-step-through () 384(defun debugger-step-through ()
434 "Proceed, stepping through subexpressions of this expression. 385 "Proceed, stepping through subexpressions of this expression.
diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el
index 2c49a634e35..7edc40188e1 100644
--- a/lisp/emacs-lisp/ert.el
+++ b/lisp/emacs-lisp/ert.el
@@ -670,48 +670,12 @@ and is displayed in front of the value of MESSAGE-FORM."
670(cl-defstruct (ert-test-aborted-with-non-local-exit 670(cl-defstruct (ert-test-aborted-with-non-local-exit
671 (:include ert-test-result))) 671 (:include ert-test-result)))
672 672
673 673(defun ert--print-backtrace (backtrace do-xrefs)
674(defun ert--record-backtrace ()
675 "Record the current backtrace (as a list) and return it."
676 ;; Since the backtrace is stored in the result object, result
677 ;; objects must only be printed with appropriate limits
678 ;; (`print-level' and `print-length') in place. For interactive
679 ;; use, the cost of ensuring this possibly outweighs the advantage
680 ;; of storing the backtrace for
681 ;; `ert-results-pop-to-backtrace-for-test-at-point' given that we
682 ;; already have `ert-results-rerun-test-debugging-errors-at-point'.
683 ;; For batch use, however, printing the backtrace may be useful.
684 (cl-loop
685 ;; 6 is the number of frames our own debugger adds (when
686 ;; compiled; more when interpreted). FIXME: Need to describe a
687 ;; procedure for determining this constant.
688 for i from 6
689 for frame = (backtrace-frame i)
690 while frame
691 collect frame))
692
693(defun ert--print-backtrace (backtrace)
694 "Format the backtrace BACKTRACE to the current buffer." 674 "Format the backtrace BACKTRACE to the current buffer."
695 ;; This is essentially a reimplementation of Fbacktrace
696 ;; (src/eval.c), but for a saved backtrace, not the current one.
697 (let ((print-escape-newlines t) 675 (let ((print-escape-newlines t)
698 (print-level 8) 676 (print-level 8)
699 (print-length 50)) 677 (print-length 50))
700 (dolist (frame backtrace) 678 (debugger-insert-backtrace backtrace do-xrefs)))
701 (pcase-exhaustive frame
702 (`(nil ,special-operator . ,arg-forms)
703 ;; Special operator.
704 (insert
705 (format " %S\n" (cons special-operator arg-forms))))
706 (`(t ,fn . ,args)
707 ;; Function call.
708 (insert (format " %S(" fn))
709 (cl-loop for firstp = t then nil
710 for arg in args do
711 (unless firstp
712 (insert " "))
713 (insert (format "%S" arg)))
714 (insert ")\n"))))))
715 679
716;; A container for the state of the execution of a single test and 680;; A container for the state of the execution of a single test and
717;; environment data needed during its execution. 681;; environment data needed during its execution.
@@ -750,7 +714,19 @@ run. ARGS are the arguments to `debugger'."
750 ((quit) 'quit) 714 ((quit) 'quit)
751 ((ert-test-skipped) 'skipped) 715 ((ert-test-skipped) 'skipped)
752 (otherwise 'failed))) 716 (otherwise 'failed)))
753 (backtrace (ert--record-backtrace)) 717 ;; We store the backtrace in the result object for
718 ;; `ert-results-pop-to-backtrace-for-test-at-point'.
719 ;; This means we have to limit `print-level' and
720 ;; `print-length' when printing result objects. That
721 ;; might not be worth while when we can also use
722 ;; `ert-results-rerun-test-debugging-errors-at-point',
723 ;; (i.e., when running interactively) but having the
724 ;; backtrace ready for printing is important for batch
725 ;; use.
726 ;;
727 ;; Grab the frames starting from `signal', frames below
728 ;; that are all from the debugger.
729 (backtrace (backtrace-frames 'signal))
754 (infos (reverse ert--infos))) 730 (infos (reverse ert--infos)))
755 (setf (ert--test-execution-info-result info) 731 (setf (ert--test-execution-info-result info)
756 (cl-ecase type 732 (cl-ecase type
@@ -1409,8 +1385,9 @@ Returns the stats object."
1409 (ert-test-result-with-condition 1385 (ert-test-result-with-condition
1410 (message "Test %S backtrace:" (ert-test-name test)) 1386 (message "Test %S backtrace:" (ert-test-name test))
1411 (with-temp-buffer 1387 (with-temp-buffer
1412 (ert--print-backtrace (ert-test-result-with-condition-backtrace 1388 (ert--print-backtrace
1413 result)) 1389 (ert-test-result-with-condition-backtrace result)
1390 nil)
1414 (goto-char (point-min)) 1391 (goto-char (point-min))
1415 (while (not (eobp)) 1392 (while (not (eobp))
1416 (let ((start (point)) 1393 (let ((start (point))
@@ -1828,12 +1805,23 @@ EWOC and STATS are arguments for `ert--results-update-stats-display'."
1828 1805
1829BEGIN and END specify a region in the current buffer." 1806BEGIN and END specify a region in the current buffer."
1830 (save-excursion 1807 (save-excursion
1831 (save-restriction 1808 (goto-char begin)
1832 (narrow-to-region begin end) 1809 (while (progn
1833 ;; Inhibit optimization in `debugger-make-xrefs' that would 1810 (goto-char (+ (point) 2))
1834 ;; sometimes insert unrelated backtrace info into our buffer. 1811 (skip-syntax-forward "^w_")
1835 (let ((debugger-previous-backtrace nil)) 1812 (< (point) end))
1836 (debugger-make-xrefs))))) 1813 (let* ((beg (point))
1814 (end (progn (skip-syntax-forward "w_") (point)))
1815 (sym (intern-soft (buffer-substring-no-properties
1816 beg end)))
1817 (file (and sym (symbol-file sym 'defun))))
1818 (when file
1819 (goto-char beg)
1820 ;; help-xref-button needs to operate on something matched
1821 ;; by a regexp, so set that up for it.
1822 (re-search-forward "\\(\\sw\\|\\s_\\)+")
1823 (help-xref-button 0 'help-function-def sym file)))
1824 (forward-line 1))))
1837 1825
1838(defun ert--string-first-line (s) 1826(defun ert--string-first-line (s)
1839 "Return the first line of S, or S if it contains no newlines. 1827 "Return the first line of S, or S if it contains no newlines.
@@ -2420,8 +2408,7 @@ To be used in the ERT results buffer."
2420 ;; Use unibyte because `debugger-setup-buffer' also does so. 2408 ;; Use unibyte because `debugger-setup-buffer' also does so.
2421 (set-buffer-multibyte nil) 2409 (set-buffer-multibyte nil)
2422 (setq truncate-lines t) 2410 (setq truncate-lines t)
2423 (ert--print-backtrace backtrace) 2411 (ert--print-backtrace backtrace t)
2424 (debugger-make-xrefs)
2425 (goto-char (point-min)) 2412 (goto-char (point-min))
2426 (insert (substitute-command-keys "Backtrace for test `")) 2413 (insert (substitute-command-keys "Backtrace for test `"))
2427 (ert-insert-test-name-button (ert-test-name test)) 2414 (ert-insert-test-name-button (ert-test-name test))
diff --git a/lisp/select.el b/lisp/select.el
index 4849d7d515e..579c5c7e2ee 100644
--- a/lisp/select.el
+++ b/lisp/select.el
@@ -475,6 +475,9 @@ two markers or an overlay. Otherwise, it is nil."
475 (t 475 (t
476 (error "Unknown selection type: %S" type))))) 476 (error "Unknown selection type: %S" type)))))
477 477
478 ;; Most programs are unable to handle NUL bytes in strings.
479 (setq str (replace-regexp-in-string "\0" "\\0" str t t))
480
478 (setq next-selection-coding-system nil) 481 (setq next-selection-coding-system nil)
479 (cons type str)))) 482 (cons type str))))
480 483
diff --git a/lisp/subr.el b/lisp/subr.el
index d0c8517c543..a9edff6166f 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -4514,7 +4514,8 @@ EVALD, FUNC, ARGS, FLAGS are as in `mapbacktrace'."
4514(defun backtrace () 4514(defun backtrace ()
4515 "Print a trace of Lisp function calls currently active. 4515 "Print a trace of Lisp function calls currently active.
4516Output stream used is value of `standard-output'." 4516Output stream used is value of `standard-output'."
4517 (let ((print-level (or print-level 8))) 4517 (let ((print-level (or print-level 8))
4518 (print-escape-control-characters t))
4518 (mapbacktrace #'backtrace--print-frame 'backtrace))) 4519 (mapbacktrace #'backtrace--print-frame 'backtrace)))
4519 4520
4520(defun backtrace-frames (&optional base) 4521(defun backtrace-frames (&optional base)
diff --git a/lisp/term/w32-win.el b/lisp/term/w32-win.el
index fda93884c40..be895a040da 100644
--- a/lisp/term/w32-win.el
+++ b/lisp/term/w32-win.el
@@ -396,7 +396,7 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.")
396;;; Fix interface to (X-specific) mouse.el 396;;; Fix interface to (X-specific) mouse.el
397(defun w32--set-selection (type value) 397(defun w32--set-selection (type value)
398 (if (eq type 'CLIPBOARD) 398 (if (eq type 'CLIPBOARD)
399 (w32-set-clipboard-data value) 399 (w32-set-clipboard-data (replace-regexp-in-string "\0" "\\0" value t t))
400 (put 'x-selections (or type 'PRIMARY) value))) 400 (put 'x-selections (or type 'PRIMARY) value)))
401 401
402(defun w32--get-selection (&optional type data-type) 402(defun w32--get-selection (&optional type data-type)
diff --git a/src/print.c b/src/print.c
index 6bf8af9ef93..50c75d7712c 100644
--- a/src/print.c
+++ b/src/print.c
@@ -1870,21 +1870,36 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
1870 } 1870 }
1871 else 1871 else
1872 { 1872 {
1873 bool still_need_nonhex = false;
1873 /* If we just had a hex escape, and this character 1874 /* If we just had a hex escape, and this character
1874 could be taken as part of it, 1875 could be taken as part of it,
1875 output `\ ' to prevent that. */ 1876 output `\ ' to prevent that. */
1876 if (need_nonhex && c_isxdigit (c)) 1877 if (c_isxdigit (c))
1877 print_c_string ("\\ ", printcharfun); 1878 {
1878 1879 if (need_nonhex)
1879 if (c == '\n' && print_escape_newlines 1880 print_c_string ("\\ ", printcharfun);
1880 ? (c = 'n', true) 1881 printchar (c, printcharfun);
1881 : c == '\f' && print_escape_newlines 1882 }
1882 ? (c = 'f', true) 1883 else if (c == '\n' && print_escape_newlines
1883 : c == '\"' || c == '\\') 1884 ? (c = 'n', true)
1884 printchar ('\\', printcharfun); 1885 : c == '\f' && print_escape_newlines
1885 1886 ? (c = 'f', true)
1886 printchar (c, printcharfun); 1887 : c == '\0' && print_escape_control_characters
1887 need_nonhex = false; 1888 ? (c = '0', still_need_nonhex = true)
1889 : c == '\"' || c == '\\')
1890 {
1891 printchar ('\\', printcharfun);
1892 printchar (c, printcharfun);
1893 }
1894 else if (print_escape_control_characters && c_iscntrl (c))
1895 {
1896 char outbuf[1 + 3 + 1];
1897 int len = sprintf (outbuf, "\\%03o", c + 0u);
1898 strout (outbuf, len, len, printcharfun);
1899 }
1900 else
1901 printchar (c, printcharfun);
1902 need_nonhex = still_need_nonhex;
1888 } 1903 }
1889 } 1904 }
1890 printchar ('\"', printcharfun); 1905 printchar ('\"', printcharfun);
@@ -2329,6 +2344,11 @@ A value of nil means no limit. See also `eval-expression-print-level'. */);
2329Also print formfeeds as `\\f'. */); 2344Also print formfeeds as `\\f'. */);
2330 print_escape_newlines = 0; 2345 print_escape_newlines = 0;
2331 2346
2347 DEFVAR_BOOL ("print-escape-control-characters", print_escape_control_characters,
2348 doc: /* Non-nil means print control characters in strings as `\\OOO'.
2349\(OOO is the octal representation of the character code.)*/);
2350 print_escape_control_characters = 0;
2351
2332 DEFVAR_BOOL ("print-escape-nonascii", print_escape_nonascii, 2352 DEFVAR_BOOL ("print-escape-nonascii", print_escape_nonascii,
2333 doc: /* Non-nil means print unibyte non-ASCII chars in strings as \\OOO. 2353 doc: /* Non-nil means print unibyte non-ASCII chars in strings as \\OOO.
2334\(OOO is the octal representation of the character code.) 2354\(OOO is the octal representation of the character code.)
@@ -2418,6 +2438,7 @@ priorities. */);
2418 DEFSYM (Qprint_escape_newlines, "print-escape-newlines"); 2438 DEFSYM (Qprint_escape_newlines, "print-escape-newlines");
2419 DEFSYM (Qprint_escape_multibyte, "print-escape-multibyte"); 2439 DEFSYM (Qprint_escape_multibyte, "print-escape-multibyte");
2420 DEFSYM (Qprint_escape_nonascii, "print-escape-nonascii"); 2440 DEFSYM (Qprint_escape_nonascii, "print-escape-nonascii");
2441 DEFSYM (Qprint_escape_control_characters, "print-escape-control-characters");
2421 2442
2422 print_prune_charset_plist = Qnil; 2443 print_prune_charset_plist = Qnil;
2423 staticpro (&print_prune_charset_plist); 2444 staticpro (&print_prune_charset_plist);
diff --git a/test/lisp/emacs-lisp/cl-print-tests.el b/test/lisp/emacs-lisp/cl-print-tests.el
index dfbe18d7844..6448a1b37f7 100644
--- a/test/lisp/emacs-lisp/cl-print-tests.el
+++ b/test/lisp/emacs-lisp/cl-print-tests.el
@@ -34,7 +34,7 @@
34 (let ((print-circle t)) 34 (let ((print-circle t))
35 (should (equal (cl-prin1-to-string `((x . ,x) (y . ,x))) 35 (should (equal (cl-prin1-to-string `((x . ,x) (y . ,x)))
36 "((x . #1=#s(cl-print--test :a 1 :b 2)) (y . #1#))"))) 36 "((x . #1=#s(cl-print--test :a 1 :b 2)) (y . #1#))")))
37 (should (string-match "\\`#f(compiled-function (x) .*\n\n.*)\\'" 37 (should (string-match "\\`#f(compiled-function (x) \"[^\"]+\" [^\)]*)\\'"
38 (cl-prin1-to-string (symbol-function #'caar)))))) 38 (cl-prin1-to-string (symbol-function #'caar))))))
39 39
40(ert-deftest cl-print-tests-2 () 40(ert-deftest cl-print-tests-2 ()
diff --git a/test/lisp/emacs-lisp/ert-tests.el b/test/lisp/emacs-lisp/ert-tests.el
index fc5790c3659..317838b250f 100644
--- a/test/lisp/emacs-lisp/ert-tests.el
+++ b/test/lisp/emacs-lisp/ert-tests.el
@@ -367,12 +367,8 @@ This macro is used to test if macroexpansion in `should' works."
367 (test (make-ert-test :body test-body)) 367 (test (make-ert-test :body test-body))
368 (result (ert-run-test test))) 368 (result (ert-run-test test)))
369 (should (ert-test-failed-p result)) 369 (should (ert-test-failed-p result))
370 (with-temp-buffer 370 (should (eq (nth 1 (car (ert-test-failed-backtrace result)))
371 (ert--print-backtrace (ert-test-failed-backtrace result)) 371 'signal))))
372 (goto-char (point-min))
373 (end-of-line)
374 (let ((first-line (buffer-substring-no-properties (point-min) (point))))
375 (should (equal first-line (format " %S()" test-body)))))))
376 372
377(ert-deftest ert-test-messages () 373(ert-deftest ert-test-messages ()
378 :tags '(:causes-redisplay) 374 :tags '(:causes-redisplay)