Mercurial > hg > xemacs-beta
diff lisp/prim/debug.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | b82b59fe008d |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/prim/debug.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,515 @@ +;;; debug.el --- debuggers and related commands for XEmacs + +;; Copyright (C) 1985, 1986, 1993, 1994 Free Software Foundation, Inc. + +;; Maintainer: FSF +;; Keyword: lisp, tools + +;; This file is part of XEmacs. + +;; XEmacs is free software; you can redistribute it and/or modify it +;; under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; XEmacs is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with XEmacs; see the file COPYING. If not, write to the Free +;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Synched up with: FSF 19.30. + +;;; Code: + +(defvar debug-function-list nil + "List of functions currently set for debug on entry.") + +(defvar debugger-step-after-exit nil + "Non-nil means \"single-step\" after the debugger exits.") + +(defvar debugger-value nil + "This is the value for the debugger to return, when it returns.") + +(defvar debugger-old-buffer nil + "This is the buffer that was current when the debugger was entered.") + +;;#### This is terminally random an nigh-unmaintainable. +;;#### (need progv in elisp...) +(defvar debugger-outer-match-data) +(defvar debugger-outer-load-read-function) +(defvar debugger-outer-overriding-local-map) +;; FSFmacs (defvar debugger-outer-track-mouse) +(defvar debugger-outer-last-command) +(defvar debugger-outer-this-command) +(defvar debugger-outer-unread-command-event) +(defvar debugger-outer-last-input-event) +(defvar debugger-outer-last-input-char) +(defvar debugger-outer-last-input-time) +(defvar debugger-outer-last-command-event) +(defvar debugger-outer-last-command-char) +(defvar debugger-outer-standard-input) +(defvar debugger-outer-standard-output) +(defvar debugger-outer-cursor-in-echo-area) + +;;;don't ###autoload, loadup.el does something smarter. +(setq debugger 'debug) + +(defvar debugger-step-after-exit) +(defvar debugger-old-buffer) +(defvar debugger-value) + +;;;###autoload +(defun debug (&rest debugger-args) + "Enter debugger. To return, type \\<debugger-mode-map>`\\[debugger-continue]'. +Arguments are mainly for use when this is called + from the internals of the evaluator. +You may call with no args, or you may + pass nil as the first arg and any other args you like. + In that case, the list of args after the first will + be printed into the backtrace buffer." + (interactive) + ;; XEmacs: it doesn't work to enter the debugger non-interactively + ;; so just print out a backtrace and exit. + (if (noninteractive) (apply 'early-error-handler debugger-args)) + (message "Entering debugger...") + (let (debugger-value + (debug-on-error nil) + (debug-on-quit nil) + (debug-on-signal nil) + (debugger-buffer (let ((default-major-mode 'fundamental-mode)) + (get-buffer-create "*Backtrace*"))) + ;; #### I18N3 set the debugger-buffer to output-translating + (debugger-old-buffer (current-buffer)) + (debugger-step-after-exit nil) + ;; Don't keep reading from an executing kbd macro! + (executing-macro nil) + ;; Save the outer values of these vars for the `e' command + ;; before we replace the values. + (debugger-outer-match-data (match-data)) + (debugger-outer-load-read-function load-read-function) + (debugger-outer-overriding-local-map overriding-local-map) + ;; FSFmacs (debugger-outer-track-mouse track-mouse) + (debugger-outer-last-command last-command) + (debugger-outer-this-command this-command) + (debugger-outer-unread-command-event unread-command-event) + (debugger-outer-last-input-event last-input-event) + (debugger-outer-last-input-char last-input-char) + (debugger-outer-last-input-time last-input-time) + (debugger-outer-last-command-event last-command-event) + (debugger-outer-last-command-char last-command-char) + (debugger-outer-standard-input standard-input) + (debugger-outer-standard-output standard-output) + (debugger-outer-cursor-in-echo-area cursor-in-echo-area) + ) + ;; Don't let these magic variables affect the debugger itself. + (unwind-protect ;XEmacs change + (let ((last-command nil) + (this-command nil) + (unread-command-event nil) + (last-input-event (allocate-event)) + (last-input-char -1) + (last-input-time nil) + (last-command-event (allocate-event)) + (last-command-char -1) + overriding-local-map + load-read-function + (standard-input t) + (standard-output t) + (cursor-in-echo-area nil)) + (save-excursion + (save-window-excursion + (pop-to-buffer debugger-buffer) + (erase-buffer) + (let ((standard-output (current-buffer)) + (print-escape-newlines t) + (print-length 50)) + (backtrace)) + (goto-char (point-min)) + (debugger-mode) + (delete-region (point) + (progn + (re-search-forward "\n[* ] debug(") + (forward-line 1) + (point))) + ;; lambda is for debug-on-call when a function call is next. + ;; debug is for debug-on-entry function called. + (debugger-reenable) + (cond ((memq (car debugger-args) '(lambda debug)) + (insert "Entering:\n") + (if (eq (car debugger-args) 'debug) + (progn + ;; Skip the frames for backtrace-debug, byte-code, + ;; and debug. + (backtrace-debug 3 t) + (delete-char 1) + (insert ?*) + (beginning-of-line)))) + ;; Exiting a function. + ((eq (car debugger-args) 'exit) + (insert "Return value: ") + (setq debugger-value (nth 1 debugger-args)) + (prin1 debugger-value (current-buffer)) + (insert ?\n) + (delete-char 1) + (insert ? ) + (beginning-of-line)) + ;; Debugger entered for an error. + ((eq (car debugger-args) 'error) + (insert "Signalling: ") + (prin1 (nth 1 debugger-args) (current-buffer)) + (insert ?\n)) + ;; debug-on-call, when the next thing is an eval. + ((eq (car debugger-args) t) + (insert + "Beginning evaluation of function call form:\n")) + ;; User calls debug directly. + (t + (prin1 (if (eq (car debugger-args) 'nil) + (cdr debugger-args) debugger-args) + (current-buffer)) + (insert ?\n))) + (message "") + (let ((inhibit-trace t) + (standard-output nil) + (buffer-read-only t)) + (message nil) + (recursive-edit))) + debugger-value)) + ;; Kill or at least neuter the backtrace buffer, so that users + ;; don't try to execute debugger commands in an invalid context. + (if (get-buffer-window debugger-buffer 'visible) + ;; Still visible despite the save-window-excursion? Maybe it + ;; it's in a pop-up frame. It would be annoying to delete and + ;; recreate it every time the debugger stops, so instead we'll + ;; erase it but leave it visible. + (save-excursion + (set-buffer debugger-buffer) + (erase-buffer) + (fundamental-mode)) + (kill-buffer debugger-buffer)) + (store-match-data debugger-outer-match-data) + ;; Put into effect the modified values of these variables + ;; in case the user set them with the `e' command. + (setq load-read-function debugger-outer-load-read-function) + (setq overriding-local-map debugger-outer-overriding-local-map) + ;; FSFmacs (setq track-mouse debugger-outer-track-mouse) + (setq last-command debugger-outer-last-command) + (setq this-command debugger-outer-this-command) + (setq unread-command-event debugger-outer-unread-command-event) + (setq last-input-event debugger-outer-last-input-event) + (setq last-input-char debugger-outer-last-input-char) + (setq last-input-time debugger-outer-last-input-time) + (setq last-command-event debugger-outer-last-command-event) + (setq last-command-char debugger-outer-last-command-char) + (setq standard-input debugger-outer-standard-input) + (setq standard-output debugger-outer-standard-output) + (setq cursor-in-echo-area debugger-outer-cursor-in-echo-area) + (setq debug-on-next-call debugger-step-after-exit) ;do this last! + ))) + + +(defun debugger-exit () + (condition-case nil + (let ((debug-on-error nil) + (debug-on-signal nil)) + ;; Tell signal to keep searching for handlers + (throw 'debugger t)) + ;; Called from an old version of Emacs, perhaps? + (no-catch (exit-recursive-edit)))) + + +(defun debugger-step-through () + "Proceed, stepping through subexpressions of this expression. +Enter another debugger on next entry to eval, apply or funcall." + (interactive) + (setq debugger-step-after-exit t) + (message "Proceeding, will debug on next eval or call.") + (debugger-exit)) + +(defun debugger-continue () + "Continue, evaluating this expression without stopping." + (interactive) + (message "Continuing.") + (debugger-exit)) + +(defun debugger-return-value (val) + "Continue, specifying value to return. +This is only useful when the value returned from the debugger +will be used, such as in a debug on exit from a frame." + (interactive "XReturn value (evaluated): ") + (setq debugger-value val) + (princ "Returning " t) + (prin1 debugger-value) + (exit-recursive-edit)) + +;; Chosen empirically to account for all the frames +;; that will exist when debugger-frame is called +;; within the first one that appears in the backtrace buffer. +;; Assumes debugger-frame is called from a key; +;; will be wrong if it is called with Meta-x. +(defconst debugger-frame-offset 8 "") + +(defun debugger-jump () + "Continue to exit from this frame, with all debug-on-entry suspended." + (interactive) + ;; Compensate for the two extra stack frames for debugger-jump. + (let ((debugger-frame-offset (+ debugger-frame-offset 2))) + (debugger-frame)) + ;; Turn off all debug-on-entry functions + ;; but leave them in the list. + (let ((list debug-function-list)) + (while list + (fset (car list) + (debug-on-entry-1 (car list) (symbol-function (car list)) nil)) + (setq list (cdr list)))) + (message "Continuing through this frame") + (debugger-exit)) + +(defun debugger-reenable () + "Turn all debug-on-entry functions back on." + (let ((list debug-function-list)) + (while list + (or (consp (symbol-function (car list))) + (debug-convert-byte-code (car list))) + (fset (car list) + (debug-on-entry-1 (car list) (symbol-function (car list)) t)) + (setq list (cdr list))))) + +(defun debugger-frame-number () + "Return number of frames in backtrace before the one point points at." + (save-excursion + (beginning-of-line) + (let ((opoint (point)) + (count 0)) + (goto-char (point-min)) + ;; #### I18N3 will not localize properly! + (if (or (equal (buffer-substring (point) (+ (point) 6)) + (gettext "Signal")) + (equal (buffer-substring (point) (+ (point) 6)) + (gettext "Return"))) + (progn + (search-forward ":") + (forward-sexp 1))) + (forward-line 1) + (while (progn + (forward-char 2) + (if (= (following-char) ?\() + (forward-sexp 1) + (forward-sexp 2)) + (forward-line 1) + (<= (point) opoint)) + (setq count (1+ count))) + count))) + +(defun debugger-frame () + "Request entry to debugger when this frame exits. +Applies to the frame whose line point is on in the backtrace." + (interactive) + (beginning-of-line) + (let ((level (debugger-frame-number))) + (backtrace-debug (+ level debugger-frame-offset) t)) + (if (= (following-char) ? ) + (let ((buffer-read-only nil)) + (delete-char 1) + (insert ?*))) + (beginning-of-line)) + +(defun debugger-frame-clear () + "Do not enter to debugger when this frame exits. +Applies to the frame whose line point is on in the backtrace." + (interactive) + (beginning-of-line) + (let ((level (debugger-frame-number))) + (backtrace-debug (+ level debugger-frame-offset) nil)) + (if (= (following-char) ?*) + (let ((buffer-read-only nil)) + (delete-char 1) + (insert ? ))) + (beginning-of-line)) + +(defun debugger-eval-expression (debugger-exp) + "Eval an expression, in an environment like that outside the debugger." + (interactive + (list (read-from-minibuffer "Eval: " + nil read-expression-map t + 'read-expression-history))) + (save-excursion + (if (null (buffer-name debugger-old-buffer)) + ;; old buffer deleted + (setq debugger-old-buffer (current-buffer))) + (set-buffer debugger-old-buffer) + (let ((last-command debugger-outer-last-command) + (this-command debugger-outer-this-command) + (unread-command-event debugger-outer-unread-command-event) + (last-input-event debugger-outer-last-input-event) + (last-input-char debugger-outer-last-input-char) + (last-input-time debugger-outer-last-input-time) + (last-command-event debugger-outer-last-command-event) + (last-command-char debugger-outer-last-command-char) + (standard-input debugger-outer-standard-input) + (standard-output debugger-outer-standard-output) + (cursor-in-echo-area debugger-outer-cursor-in-echo-area) + (overriding-local-map debugger-outer-overriding-local-map) + (load-read-function debugger-outer-load-read-function)) + (store-match-data debugger-outer-match-data) + (prog1 (eval-expression debugger-exp) + (setq debugger-outer-match-data (match-data) + debugger-outer-load-read-function load-read-function + debugger-outer-overriding-local-map overriding-local-map + debugger-outer-last-command last-command + debugger-outer-this-command this-command + debugger-outer-unread-command-event unread-command-event + debugger-outer-last-input-event last-input-event + debugger-outer-last-input-char last-input-char + debugger-outer-last-input-time last-input-time + debugger-outer-last-command-event last-command-event + debugger-outer-last-command-char last-command-char + debugger-outer-standard-input standard-input + debugger-outer-standard-output standard-output + debugger-outer-cursor-in-echo-area cursor-in-echo-area))))) + +(defvar debugger-mode-map + (let ((map (make-keymap))) + (set-keymap-name map 'debugger-mode-map) + (suppress-keymap map) + (define-key map "-" 'negative-argument) + (define-key map "b" 'debugger-frame) + (define-key map "c" 'debugger-continue) + (define-key map "j" 'debugger-jump) + (define-key map "r" 'debugger-return-value) + (define-key map "u" 'debugger-frame-clear) + (define-key map "d" 'debugger-step-through) + (define-key map "l" 'debugger-list-functions) + (define-key map "h" 'describe-mode) + (define-key map "q" 'top-level) + (define-key map "e" 'debugger-eval-expression) + (define-key map " " 'next-line) + map)) + +(put 'debugger-mode 'mode-class 'special) + +(defun debugger-mode () + "Mode for backtrace buffers, selected in debugger. +\\<debugger-mode-map> +A line starts with `*' if exiting that frame will call the debugger. +Type \\[debugger-frame] or \\[debugger-frame-clear] to set or remove the `*'. + +When in debugger due to frame being exited, +use the \\[debugger-return-value] command to override the value +being returned from that frame. + +Use \\[debug-on-entry] and \\[cancel-debug-on-entry] to control +which functions will enter the debugger when called. + +Complete list of commands: +\\{debugger-mode-map}" + (kill-all-local-variables) + (setq major-mode 'debugger-mode) + (setq mode-name (gettext "Debugger")) + (setq truncate-lines t) + (set-syntax-table emacs-lisp-mode-syntax-table) + (use-local-map debugger-mode-map)) + +;;;###autoload +(defun debug-on-entry (function) + "Request FUNCTION to invoke debugger each time it is called. +If you tell the debugger to continue, FUNCTION's execution proceeds. +This works by modifying the definition of FUNCTION, +which must be written in Lisp, not predefined. +Use \\[cancel-debug-on-entry] to cancel the effect of this command. +Redefining FUNCTION also cancels it." + (interactive "aDebug on entry (to function): ") + (debugger-reenable) + (if (subrp (symbol-function function)) + (error "Function %s is a primitive" function)) + (or (consp (symbol-function function)) + (debug-convert-byte-code function)) + (or (consp (symbol-function function)) + (error "Definition of %s is not a list" function)) + (fset function (debug-on-entry-1 function (symbol-function function) t)) + (or (memq function debug-function-list) + (setq debug-function-list (cons function debug-function-list))) + function) + +;;;###autoload +(defun cancel-debug-on-entry (&optional function) + "Undo effect of \\[debug-on-entry] on FUNCTION. +If argument is nil or an empty string, cancel for all functions." + (interactive + (list (let ((name + (completing-read "Cancel debug on entry (to function): " + ;; Make an "alist" of the functions + ;; that now have debug on entry. + (mapcar 'list (mapcar 'symbol-name + debug-function-list)) + nil t nil))) + (if name (intern name))))) + (debugger-reenable) + (if (and function (not (string= function ""))) + (progn + (fset function + (debug-on-entry-1 function (symbol-function function) nil)) + (setq debug-function-list (delq function debug-function-list)) + function) + (message "Cancelling debug-on-entry for all functions") + (mapcar 'cancel-debug-on-entry debug-function-list))) + +(defun debug-convert-byte-code (function) + (let ((defn (symbol-function function))) + (if (not (consp defn)) + ;; Assume a compiled code object. + (let* ((contents (append defn nil)) + (body + (list (list 'byte-code (nth 1 contents) + (nth 2 contents) (nth 3 contents))))) + (if (nthcdr 5 contents) + (setq body (cons (list 'interactive (nth 5 contents)) body))) + (if (nth 4 contents) + ;; Use `documentation' here, to get the actual string, + ;; in case the compiled function has a reference + ;; to the .elc file. + (setq body (cons (documentation function) body))) + (fset function (cons 'lambda (cons (car contents) body))))))) + +(defun debug-on-entry-1 (function defn flag) + (if (subrp defn) + (error "%s is a built-in function" function) + (if (eq (car defn) 'macro) + (debug-on-entry-1 function (cdr defn) flag) + (or (eq (car defn) 'lambda) + (error "%s not user-defined Lisp function" function)) + (let (tail prec) + (if (stringp (car (nthcdr 2 defn))) + (setq tail (nthcdr 3 defn) + prec (list (car defn) (car (cdr defn)) (car (cdr (cdr defn))))) + (setq tail (nthcdr 2 defn) + prec (list (car defn) (car (cdr defn))))) + (if (eq flag (equal (car tail) '(debug 'debug))) + defn + (if flag + (nconc prec (cons '(debug 'debug) tail)) + (nconc prec (cdr tail)))))))) + +(defun debugger-list-functions () + "Display a list of all the functions now set to debug on entry." + (interactive) + (with-output-to-temp-buffer "*Help*" + (if (null debug-function-list) + (princ "No debug-on-entry functions now\n") + (princ "Functions set to debug on entry:\n\n") + (let ((list debug-function-list)) + (while list + (prin1 (car list)) + (terpri) + (setq list (cdr list)))) + (princ "Note: if you have redefined a function, then it may no longer\n") + (princ "be set to debug on entry, even if it is in the list.")) + (save-excursion + (set-buffer standard-output) + (help-mode)))) + +;;; debug.el ends here