From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Lars Ingebrigtsen Newsgroups: gmane.emacs.bugs Subject: bug#1343: bug#27397: [PATCH] New commands for bulk tracing of elisp functions Date: Sun, 11 Sep 2022 13:49:51 +0200 Message-ID: <87leqqdtb4.fsf_-_@gnus.org> References: <1348823a-7623-8146-8cc0-8c0eff13e458@orcon.net.nz> <94d27dbb-46d3-1d6f-4849-251a0929d413@orcon.net.nz> <70000e90-328c-c084-3e3c-bbde96b16110@orcon.net.nz> <6b195c26-066c-870f-3432-0b24c6f619ec@orcon.net.nz> <83fa3d61-9173-c3f6-32b6-87c7c09502b7@orcon.net.nz> <15f4e949736351f2d98fdee0b57e4d24@webmail.orcon.net.nz> Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="37891"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux) Cc: 1343@debbugs.gnu.org, 27397@debbugs.gnu.org, Michael Albinus , Dmitry Gutov To: Phil Sainty Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Sun Sep 11 13:51:32 2022 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1oXLUq-0009eL-Ib for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 11 Sep 2022 13:51:32 +0200 Original-Received: from localhost ([::1]:56866 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1oXLUp-0004z3-DH for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 11 Sep 2022 07:51:31 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:58278) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oXLUN-0004ya-IP for bug-gnu-emacs@gnu.org; Sun, 11 Sep 2022 07:51:03 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]:52328) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1oXLUM-0006AR-5G for bug-gnu-emacs@gnu.org; Sun, 11 Sep 2022 07:51:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1oXLUM-0008WM-0z for bug-gnu-emacs@gnu.org; Sun, 11 Sep 2022 07:51:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Lars Ingebrigtsen Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sun, 11 Sep 2022 11:51:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 1343 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 1343-submit@debbugs.gnu.org id=B1343.166289700832639 (code B ref 1343); Sun, 11 Sep 2022 11:51:01 +0000 Original-Received: (at 1343) by debbugs.gnu.org; 11 Sep 2022 11:50:08 +0000 Original-Received: from localhost ([127.0.0.1]:41023 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oXLTS-0008UI-N8 for submit@debbugs.gnu.org; Sun, 11 Sep 2022 07:50:08 -0400 Original-Received: from quimby.gnus.org ([95.216.78.240]:39772) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oXLTO-0008TT-UX; Sun, 11 Sep 2022 07:50:05 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnus.org; s=20200322; h=Content-Type:MIME-Version:Message-ID:Date:References: In-Reply-To:Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=r5b5ztAs9odQ2P8PtuPpJ5o+nqEg77NPRtwk+UQfOWM=; b=Eou0Y+atzUVCFcUL41D/+tMkRD 9h6Ss23yYzhRHGKjt2kIAfxZhP2YQnPUhYbZlyOlAhMO38cZScYSfFXl6bYkPvT2i/qdDhm4pxirT Tu1tjVqqudYS5qOhSthzmztATltGsYYoSM0XbformcQcs71TFB4umezzQgL9qe00ii4k=; Original-Received: from [84.212.220.105] (helo=joga) by quimby.gnus.org with esmtpsa (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1oXLTD-00041Y-Kn; Sun, 11 Sep 2022 13:49:55 +0200 In-Reply-To: <15f4e949736351f2d98fdee0b57e4d24@webmail.orcon.net.nz> (Phil Sainty's message of "Fri, 15 Jul 2022 16:08:46 +1200") X-Now-Playing: Earwig's _Under My Skin I Am Laughing_: "Every Day Shines" X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:242160 Archived-At: Phil Sainty writes: > After a brief hiatus, I've resumed (maybe completed) my work on this. > > The branch scratch/bulk-tracing contains the updated code for Emacs 29 > (rebased over master). For reference, I've included the diff between master and the branch below. I have not tried the patch myself -- does anybody have any comments here? It seems like useful functionality to me. diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi index 058c931954..6bdcf33a95 100644 --- a/doc/lispref/debugging.texi +++ b/doc/lispref/debugging.texi @@ -20,14 +20,10 @@ Debugging You can use Edebug, a source-level debugger for Emacs Lisp. @item -@cindex tracing Lisp programs -You can trace the execution of functions involved in the problem using -the tracing facilities provided by the @file{trace.el} package. This -package provides the functions @code{trace-function-foreground} and -@code{trace-function-background} for tracing function calls, and -@code{trace-values} for adding values of select variables to the -trace. For the details, see the documentation of these facilities in -@file{trace.el}. +You can trace the execution of functions involved in the problem +(logging function calls, their arguments and return values, and other +context values) using the tracing facilities provided by the +@file{trace.el} package. @item If a syntactic problem is preventing Lisp from even reading the @@ -59,6 +55,7 @@ Debugging * Syntax Errors:: How to find syntax errors. * Test Coverage:: Ensuring you have tested all branches in your code. * Profiling:: Measuring the resources that your code uses. +* Tracing:: Log function calls, arguments, and return values. @end menu @node Debugger @@ -1072,3 +1069,327 @@ Profiling debugging Emacs. It actually stops the Lisp-level @kbd{M-x profiler-@dots{}} commands described above from working. @end ifnottex + + +@node Tracing +@section Tracing +@cindex tracing +@cindex trace +@cindex trace functions +@cindex tracing Lisp programs + +You can trace the execution of functions using the tracing facilities +provided by the @file{trace.el} library. Many functions can be traced +at the same time. The commands @code{trace-function-foreground} and +@code{trace-function-background} add a new trace to a single specified +function. The commands @code{trace-package}, @code{trace-regexp}, and +@code{trace-library} enable traces to be added to functions en masse. +Traces can also be added to autoloaded functions -- the associated +function will be traced if and when it is defined. + +@vindex trace-buffer +Calls to traced functions, including the values of their arguments and +their return values, are logged to the @file{*trace-output*} buffer +(or another buffer as specified -- either by the @code{trace-buffer} +user option, or as the @var{buffer} argument to a tracing command). + +@anchor{trace context} +@cindex @code{context} in trace functions +Optional @var{context} expressions are also evaluated, both when the +associated function is called and again when it returns, with the +value logged within square brackets alongside the call-time arguments +or return value respectively. This could be used to track the current +buffer or position of point, for instance. If @var{context} is a +function, it will be called (with no arguments) to obtain the value to +be inserted into the trace output buffer. + +Finally, you may add explicit calls to @code{trace-values} to your +code, to log arbitrary values to the trace buffer at any time. + +@anchor{background and foreground tracing} +@cindex foreground tracing +@cindex background tracing +When using ``foreground'' tracing, the output buffer will be displayed +whenever a traced function is called. When using ``background'' +tracing the output buffer is not forcibly displayed. Because +foreground tracing affects the window configuration, it should not be +used to trace functions that switch buffers, or have other +display-oriented behaviour. To avoid such problems, all bulk tracing +commands use background tracing -- @code{trace-function-foreground} is +the only command providing foreground tracing. + +@menu +* Commands for Tracing:: Commands and variables. +* Restrictions on Tracing:: Limitations on what can be traced. +* Examples of Tracing:: Usage examples. +@end menu + +@node Commands for Tracing +@subsection Commands and variables for tracing functions + +@defopt trace-buffer +This variable defines the buffer where trace output will be logged to +by default. Trace commands can be passed a @var{buffer} argument to +specify a non-default output buffer. +@end defopt + +@defvar inhibit-trace +If this variable is non-@code{nil}, all tracing is temporarily +inhibited (including any calls to @code{trace-values}). +@end defvar + +@deffn Command trace-function-background function &optional buffer context +This function adds a background trace (@pxref{background and +foreground tracing}) to @var{function}. When called interactively, it +prompts for @var{function} in the minibuffer. With a prefix argument, +it also prompts for the trace output @var{buffer} (defaulting to the +value of @code{trace-buffer}), and a Lisp expression @var{context} +(@pxref{trace context}). + +If @var{function} is an autoload, the associated function will be +traced if and when it is defined. + +Calling @code{trace-function-background} for an already-traced +@var{function} will update the optional argument behaviours to respect +the new values (and change to background tracing, if foreground +tracing was previously used). +@end deffn + +@deffn Command trace-function-foreground function &optional buffer context +This function adds a foreground trace (@pxref{background and +foreground tracing}) to @var{function}. When called interactively, it +prompts for @var{function} in the minibuffer. With a prefix argument, +it also prompts for the trace output @var{buffer} (defaulting to the +value of @code{trace-buffer}), and a Lisp expression @var{context} +(@pxref{trace context}). + +If @var{function} is an autoload, the associated function will be +traced if and when it is defined. + +Calling @code{trace-function-foreground} for an already-traced +@var{function} will update the optional argument behaviours to respect +the new values (and change to foreground tracing, if background +tracing was previously used). +@end deffn + +@deffn Command trace-package prefix &optional buffer context after-load +This function calls @code{trace-function-background} for all functions +with names starting with @var{prefix}. + +For any autoload declarations matching @var{prefix}, the associated +function will be traced if and when it is defined. + +With a prefix argument, also prompt for the trace output @var{buffer} +(defaulting to the value of @code{trace-buffer}); a Lisp expression +@var{context} (@pxref{trace context}); and boolean query +@var{after-load}. If @var{after-load} is non-@code{nil} then +re-process @var{prefix} after loading any file. + +Calling @code{trace-package} again for the same @var{prefix} will +update the optional argument behaviours to respect the new values. +@end deffn + +@deffn Command trace-regexp regexp &optional buffer context after-load +This function calls @code{trace-function-background} for all functions +matching in @var{regexp}. + +Background tracing is used. Switch to the trace output buffer to view +the results. For any autoload declarations matching @var{regexp}, the +associated function will be traced if and when it is defined. + +With a prefix argument, also prompt for the trace output @var{buffer} +(defaulting to the value of @code{trace-buffer}); a Lisp expression +@var{context} (@pxref{trace context}); and boolean query +@var{after-load}. If @var{after-load} is non-@code{nil} then +re-process @var{regexp} after loading any file. + +Calling @code{trace-regexp} again for the same @var{regexp} will +update the optional argument behaviours to respect the new values. + +@strong{Warning:} Do not attempt to trace all functions. Tracing too +many functions at one time will render Emacs unusable. +@end deffn + +@deffn Command trace-library library &optional buffer context after-load +This function calls @code{trace-function-background} for all functions +currently defined in @var{library} according to @var{load-history}. + +For any autoload declarations with a file name matching @var{library}, +the associated function will be traced if and when it is defined. +(Autoload file names will not match if @var{library} specifies a +longer, more specific path.) + +With a prefix argument, also prompt for the trace output @var{buffer} +(defaulting to the value of @code{trace-buffer}); a Lisp expression +@var{context} (@pxref{trace context}); and boolean query +@var{after-load}. If @var{after-load} is non-@code{nil} then +re-process @var{library} after loading it, (ensuring that all of its +functions will be traced). + +Calling @code{trace-library} again for the same @var{library} will +update the optional argument behaviours to respect the new values. +@end deffn + +@deffn Command trace-currently-traced &optional display-message +This function returns the list of currently traced function symbols. +When called interactively, or if @var{display-message} is +non-@code{nil}, it displays the list as a message. +@end deffn + +@deffn Command untrace-function function +This function removes the trace on @var{function}. This has no effect +if @var{function} was not being traced. When called interactively, it +prompts for @var{function} in the minibuffer. +@end deffn + +@deffn Command untrace-package prefix +This function calls @code{untrace-function} for all functions with +names starting with @var{prefix}. When called interactively, it +prompts for @var{prefix} in the minibuffer. +@end deffn + +@deffn Command untrace-regexp regexp +This function calls @code{untrace-function} for all functions matching +@var{regexp}. When called interactively, it prompts for @var{regexp} +in the minibuffer. +@end deffn + +@deffn Command untrace-library library +This function calls @code{untrace-function} for all functions defined +in @var{library}. When called interactively, it prompts for +@var{library} in the minibuffer. +@end deffn + +@deffn Command untrace-all +This function calls @code{untrace-function} for all functions. +@end deffn + +@deffn Function trace-values &rest values +This function inserts a message showing @var{values} into the trace +buffer. You can add explicit calls to @code{trace-values} into your +functions in order to provide additional tracing information. +@end deffn + + +@node Restrictions on Tracing +@subsection Limitations on what can be traced + +@itemize @bullet +@item +Only functions/macros/subrs that are called via their function cell +will generate trace output; hence, you won't get trace output for: + +@itemize @bullet +@item +Macros that were expanded during compilation. + +@item +Subrs called directly from other subrs/C-code. + +@item +Byte-compiled calls to subrs that have special byte-codes associated +with them: + +@example +(sort (cl-loop for sym being the symbols + if (and (subrp (symbol-function sym)) + (plist-get (symbol-plist sym) + 'byte-opcode)) + collect sym) + (lambda (s1 s2) + (string< (symbol-name s1) (symbol-name s2)))) +@end example +@end itemize + +@item +Tracing too many functions at one time will render Emacs unusable. Do +not attempt to trace all functions, and take care with the arguments +passed to the bulk tracing commands @code{trace-package} and +@code{trace-regexp}. + +@item +Foreground tracing should not be used to trace functions that switch +buffers, or have other display-oriented behaviour. + +@item +Each function can only be subject to a single trace. When a function +which is already being traced is targeted by any tracing command, the +new trace criteria (including optional argument values) will replace +the previous trace criteria for that function. + +Note that this also means there is no need to un-trace a function in +order to re-trace it with different arguments. + +@item +All the restrictions that apply to @file{nadvice.el} also apply to +tracing (as tracing is implemented using advice). @xref{Advising +Functions}. +@end itemize + +@node Examples of Tracing +@subsection Usage examples for function tracing + +The following is example trace output, including a context list +expression, for a function which also makes a call to +@code{trace-values}. The left hand column indicates the evaluation +depth of the function call. + +@example +@group +1 -> (funcname arg1 arg2) [(context1 context2)] +1 -> (trace-values value1 value2) +1 <- funcname: return [(context1 context2)] +@end group +@end example + +The trace output display of recursion/nesting levels can be +demonstrated by tracing a recursive function, such as a simplistic +factorial implementation: + +@example +@group +(defun fact (n) + "Calculate factorial of N." + (if (eql n 0) 1 + (* n (fact (1- n))))) + @result{} fact + +(trace-function 'fact) + @result{} fact + +Now, evaluating this... + +(fact 4) + @result{} 24 + +...will generate the following in *trace-buffer*: + +1 -> fact: n=4 +| 2 -> fact: n=3 +| | 3 -> fact: n=2 +| | | 4 -> fact: n=1 +| | | | 5 -> fact: n=0 +| | | | 5 <- fact: 1 +| | | 4 <- fact: 1 +| | 3 <- fact: 2 +| 2 <- fact: 6 +1 <- fact: 24 +@end group + +Try the following for some more interesting trace output: + +@group +(defun ack (x y z) + (if (= x 0) + (+ y z) + (if (and (<= x 2) (= z 0)) + (1- x) + (if (and (> x 2) (= z 0)) + y + (ack (1- x) y (ack x y (1- z))))))) + +(trace-function 'ack) + +(ack 3 3 1) +@end group +@end example diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index a3d1d80408..64f31cdf3d 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -669,6 +669,7 @@ Top * Syntax Errors:: How to find syntax errors. * Test Coverage:: Ensuring you have tested all branches in your code. * Profiling:: Measuring the resources that your code uses. +* Tracing:: Log function calls, arguments, and return values. The Lisp Debugger diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index e94093318f..ad2b175afb 100644 --- a/doc/lispref/modes.texi +++ b/doc/lispref/modes.texi @@ -2507,15 +2507,13 @@ %-Constructs @item %% The character @samp{%}---this is how to include a literal @samp{%} in a string in which @code{%}-constructs are allowed. -@end table - -The following @code{%}-construct is still supported, but it is -obsolete, since you can get the same result using the variable -@code{mode-name}. -@table @code @item %m -The value of @code{mode-name}. +Obsolete; use the @code{mode-name} variable instead. The @code{%m} +construct is still supported, but it is inadequate, as it produces an +empty string if the value of the @code{mode-name} variable is a +non-string mode-line construct (for example, in +@code{emacs-lisp-mode}). @end table @node Properties in Mode diff --git a/etc/NEWS b/etc/NEWS index 57845df979..9728edc303 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1918,6 +1918,16 @@ The newly created buffer will be displayed via 'display-buffer', which can be customized through the usual mechanism of 'display-buffer-alist' and friends. +** Trace + ++++ +*** New commands 'trace-package', 'trace-regexp', and 'trace-library' +(and their counterparts 'untrace-package', 'untrace-regexp', and +'untrace-library') allow for the bulk tracing of calls to functions +with names matching a specified prefix or regexp, or functions defined +by a specified file. New command 'trace-currently-traced' lists the +traced function symbols. + ** Tramp --- diff --git a/lisp/emacs-lisp/trace.el b/lisp/emacs-lisp/trace.el index c2f6c16226..a4fcfd50b0 100644 --- a/lisp/emacs-lisp/trace.el +++ b/lisp/emacs-lisp/trace.el @@ -40,8 +40,6 @@ ;; Restrictions: ;; ============= -;; - Traced subrs when called interactively will always show nil as the -;; value of their arguments. ;; - Only functions/macros/subrs that are called via their function cell will ;; generate trace output, hence, you won't get trace output for: ;; + Subrs called directly from other subrs/C-code @@ -52,14 +50,28 @@ ;; Usage: ;; ====== -;; - To trace a function say `M-x trace-function', which will ask you for the +;; - To trace a function use `M-x trace-function', which will ask you for the ;; name of the function/subr/macro to trace. ;; - If you want to trace a function that switches buffers or does other ;; display oriented stuff use `M-x trace-function-background', which will ;; generate the trace output silently in the background without popping ;; up windows and doing other irritating stuff. -;; - To untrace a function say `M-x untrace-function'. -;; - To untrace all currently traced functions say `M-x untrace-all'. +;; - `M-x trace-package' will ask you for a function name prefix, and trace +;; (in the background) all matching functions. +;; - `M-x trace-regexp' will ask you for a function name pattern (regexp), +;; and trace (in the background) all matching functions. +;; - `M-x trace-library' will ask you for a library name, and trace (in the +;; background) all functions defined by that file. +;; - Interactively in all cases, a prefix argument can be used to prompt +;; for the output buffer and context arguments and, for bulk tracing +;; commands, whether or not the traces should be automatically updated +;; after loading lisp files. +;; - To untrace a function use `M-x untrace-function'. +;; - To untrace multiple functions by prefix use `M-x untrace-package'. +;; - To untrace multiple functions by regexp use `M-x untrace-regexp'. +;; - To untrace multiple functions by file use `M-x untrace-library'. +;; - To untrace all currently traced functions use `M-x untrace-all'. +;; - To list all currently traced functions use `M-x trace-currently-traced'. ;; Examples: ;; ========= @@ -120,6 +132,23 @@ ;;; Change Log: +;; 2017-06-17 Phil Sainty +;; * New commands `trace-package', `untrace-package', `trace-regexp', +;; `untrace-regexp', `trace-library', `untrace-library'. +;; * Documentation added to the elisp reference manual. +;; +;; 2012-2014 Stefan Monnier, Glenn Morris +;; * Adapted for nadvice.el +;; * New `context' argument and display in trace buffer +;; * `trace-function' renamed to (and now an alias of) +;; `trace-function-foreground' +;; +;; 2005-02-27 Stefan Monnier +;; * New `inhibit-trace' variable +;; +;; 1998-04-05 Stephen Eglen +;; * New customize group `trace' +;; ;; Revision 2.0 1993/05/18 00:41:16 hans ;; * Adapted for advice.el 2.0; it now also works ;; for GNU Emacs-19 and Lemacs @@ -134,6 +163,8 @@ ;;; Code: +(eval-when-compile (require 'cl-macs)) + (defgroup trace nil "Tracing facility for Emacs Lisp functions." :prefix "trace-" @@ -181,7 +212,7 @@ trace-entry-message ;; FIXME: Make it so we can click the function name to jump to its ;; definition and/or untrace it. (cons function args) - context))) + (if context (format " [%s]" context) "")))) (defun trace-exit-message (function level value context) "Generate a string that describes that FUNCTION has exited. @@ -197,7 +228,7 @@ trace-exit-message function ;; Do this so we'll see strings: value - context))) + (if context (format " [%s]" context) "")))) (defvar trace--timer nil) @@ -218,8 +249,14 @@ trace-make-advice FUNCTION is the name of the traced function. BUFFER is the buffer where the trace should be printed. BACKGROUND if nil means to display BUFFER. -CONTEXT if non-nil should be a function that returns extra info that should -be printed along with the arguments in the trace." +CONTEXT, if non-nil, should be either a function or an expression +that returns extra info, which will be printed after the +arguments or return value in the trace." + (setq context (if context + (if (functionp context) + context + (trace-make-context context)) + (lambda () ""))) (lambda (body &rest args) (let ((trace-level (1+ trace-level)) (trace-buffer (get-buffer-create buffer)) @@ -227,6 +264,7 @@ trace-make-advice (ctx (funcall context))) (unless inhibit-trace (with-current-buffer trace-buffer + (setq-local page-delimiter (format "^%s" (regexp-quote trace-separator))) (setq-local window-point-insertion-type t) (unless background (trace--display-buffer trace-buffer)) (goto-char (point-max)) @@ -255,41 +293,70 @@ trace-function-internal "Add trace advice for FUNCTION." (advice-add function :around - (trace-make-advice function (or buffer trace-buffer) background - (or context (lambda () ""))) + (trace-make-advice function (or buffer trace-buffer) background context) `((name . ,trace-advice-name) (depth . -100)))) -(defun trace-is-traced (function) +(defun trace-is-traceable-p (sym) + "Whether the given symbol is a traceable function. +Autoloaded functions are traceable." + (or (functionp sym) (macrop sym))) + +(defun trace-is-traced-p (function) + "Whether FUNCTION is currently traced." (advice-member-p trace-advice-name function)) -(defun trace--read-args (prompt) - "Read a function name, prompting with string PROMPT. -If `current-prefix-arg' is non-nil, also read a buffer and a \"context\" -\(Lisp expression). Return (FUNCTION BUFFER FUNCTION-CONTEXT)." - (cons - (let ((default (function-called-at-point))) - (intern (completing-read (format-prompt prompt default) - obarray 'fboundp t nil nil - (if default (symbol-name default))))) - (when current-prefix-arg - (list - (read-buffer "Output to buffer" trace-buffer) - (let ((exp - (read-from-minibuffer "Context expression: " - nil read-expression-map t - 'read-expression-history))) - (lambda () - (let ((print-circle t) - (print-escape-newlines t)) - (concat " [" (prin1-to-string (eval exp t)) "]")))))))) +(define-obsolete-function-alias 'trace-is-traced 'trace-is-traced-p "29.1") + +(defun trace-currently-traced (&optional display-message) + "Return the list of currently traced function symbols. +Interactively, display the list as a message." + (interactive "p") + (let ((tracelist (cl-loop for sym being the symbols + if (trace-is-traced-p sym) + collect sym))) + (when display-message + (message "%S" tracelist)) + tracelist)) + +(defun trace--read-function (prompt) + "Read a function name, prompting with string PROMPT." + (let ((default (function-called-at-point))) + (intern (completing-read (format-prompt prompt default) + obarray 'trace-is-traceable-p t nil nil + (if default (symbol-name default)))))) + +(defun trace--read-library (&optional prompt) + "Read a library name, prompting with string PROMPT." + (completing-read + (or prompt "Library: ") + (apply-partially 'locate-file-completion-table + load-path (get-load-suffixes)))) + +(defun trace--read-extra-args () + "Read a buffer and a \"context\" (Lisp expression). +Return (BUFFER CONTEXT)." + (list + (read-buffer "Output to buffer" trace-buffer) + (when-let ((exp (read-from-minibuffer + "Context expression: " + nil read-expression-map t + 'read-expression-history "nil"))) + (trace-make-context exp)))) + +(defun trace-make-context (exp) + "Return a context function for expression EXP." + (lambda () + (let ((print-circle t) + (print-escape-newlines t)) + (prin1-to-string (eval exp t))))) ;;;###autoload (defun trace-function-foreground (function &optional buffer context) "Trace calls to function FUNCTION. -With a prefix argument, also prompt for the trace buffer (default -`trace-buffer'), and a Lisp expression CONTEXT. When called from -Lisp, CONTEXT should be a function of no arguments which returns -a value to insert into BUFFER during the trace. +With a prefix argument, also prompt for the trace output BUFFER +\(default `trace-buffer'), and a Lisp expression CONTEXT. +When called from Lisp, CONTEXT should be a function of no arguments +which returns a value to insert into BUFFER during the trace. Tracing a function causes every call to that function to insert into BUFFER Lisp-style trace messages that display the function's @@ -302,8 +369,14 @@ trace-function-foreground functions that switch buffers, or do any other display-oriented stuff - use `trace-function-background' instead. +Calling `trace-function-foreground' again for the same FUNCTION +will update the optional argument behaviours to respect the new +values. + To stop tracing a function, use `untrace-function' or `untrace-all'." - (interactive (trace--read-args "Trace function")) + (interactive + (cons (trace--read-function "Trace function") + (and current-prefix-arg (trace--read-extra-args)))) (trace-function-internal function buffer nil context)) ;;;###autoload @@ -311,26 +384,290 @@ trace-function-background "Trace calls to function FUNCTION, quietly. This is like `trace-function-foreground', but without popping up the output buffer or changing the window configuration." - (interactive (trace--read-args "Trace function in background")) + (interactive + (cons (trace--read-function "Trace function in background") + (and current-prefix-arg (trace--read-extra-args)))) (trace-function-internal function buffer t context)) ;;;###autoload (defalias 'trace-function 'trace-function-foreground) (defun untrace-function (function) - "Untraces FUNCTION and possibly activates all remaining advice. -Activation is performed with `ad-update', hence remaining advice will get -activated only if the advice of FUNCTION is currently active. If FUNCTION -was not traced this is a noop." + "Remove trace from FUNCTION. If FUNCTION was not traced this is a noop." (interactive (list (intern (completing-read "Untrace function: " - obarray #'trace-is-traced t)))) + obarray #'trace-is-traced-p t)))) (advice-remove function trace-advice-name)) +;;;###autoload +(defun trace-package (prefix &optional buffer context after-load) + "Trace all functions with names starting with PREFIX. +For example, to trace all diff functions, do the following: + +\\[trace-package] RET diff- RET + +Background tracing is used. Switch to the trace output buffer to +view the results. For any autoload declarations matching PREFIX, +the associated function will be traced if and when it is defined. + +With a prefix argument, also prompt for the optional arguments. +If AFTER-LOAD is non-nil then re-process PREFIX after loading any +file. See `trace-function-foreground' for details of BUFFER and +CONTEXT, and of foreground vs background tracing. + +Calling `trace-package' again for the same PREFIX will update the +optional argument behaviours to respect the new values. + +See also `untrace-package'." + ;; Derived in part from `elp-instrument-package'. + (interactive + (cons (completing-read "Prefix of package to trace: " + obarray #'trace-is-traceable-p) + (and current-prefix-arg + (nconc (trace--read-extra-args) + (list (y-or-n-p "Update traces after loading files?")))))) + (when (zerop (length prefix)) + (error "Tracing all Emacs functions would render Emacs unusable")) + (mapc (lambda (name) + (trace-function-background (intern name) buffer context)) + (all-completions prefix obarray #'trace-is-traceable-p)) + (message + "Tracing to %s. Use %s to untrace a package, or %s to remove all traces." + (or buffer trace-buffer) + (substitute-command-keys "\\[untrace-package]") + (substitute-command-keys "\\[untrace-all]")) + ;; Handle `after-load' argument. + (when after-load + (trace--after-load 'prefix prefix buffer context))) + +(defun untrace-package (prefix) + "Remove all traces from functions with names starting with PREFIX. + +See also `trace-package'." + (interactive + (list (completing-read "Prefix of package to untrace: " + obarray #'trace-is-traced-p))) + (if (and (zerop (length prefix)) + (y-or-n-p "Remove all function traces?")) + (untrace-all) + (mapc (lambda (name) + (untrace-function (intern name))) + (all-completions prefix obarray #'trace-is-traced-p))) + ;; Remove any `after-load' behaviour. + (trace--remove-after-load 'prefix prefix)) + +;;;###autoload +(defun trace-regexp (regexp &optional buffer context after-load) + "Trace all functions with names matching REGEXP. +For example, to trace indentation-related functions, you could try: + +\\[trace-regexp] RET indent\\|offset RET + +Warning: Do not attempt to trace all functions. Tracing too many +functions at one time will render Emacs unusable. + +Background tracing is used. Switch to the trace output buffer to +view the results. For any autoload declarations matching REGEXP, +the associated function will be traced if and when it is defined. + +With a prefix argument, also prompt for the optional arguments. +If AFTER-LOAD is non-nil then re-process REGEXP after loading any +file. See `trace-function-foreground' for details of BUFFER and +CONTEXT, and of foreground vs background tracing. + +Calling `trace-regexp' again for the same REGEXP will update the +optional argument behaviours to respect the new values. + +See also `untrace-regexp'." + (interactive + (cons (read-regexp "Regexp matching functions to trace: ") + (and current-prefix-arg + (nconc (trace--read-extra-args) + (list (y-or-n-p "Update traces after loading files?")))))) + (when (member regexp '("" "." ".+" ".*")) + ;; Not comprehensive, but it catches the most likely attempts. + (error "Tracing all Emacs functions would render Emacs unusable")) + (mapatoms + (lambda (sym) + (and (trace-is-traceable-p sym) + (string-match-p regexp (symbol-name sym)) + (trace-function-background sym buffer context)))) + (message + "Tracing to %s. Use %s to untrace by regexp, or %s to remove all traces." + (or buffer trace-buffer) + (substitute-command-keys "\\[untrace-regexp]") + (substitute-command-keys "\\[untrace-all]")) + ;; Handle `after-load' argument. + (when after-load + (trace--after-load 'regexp regexp buffer context))) + +(defun untrace-regexp (regexp) + "Remove all traces from functions with names matching REGEXP. + +See also `trace-regexp'." + (interactive + (list (read-regexp "Regexp matching functions to untrace: "))) + (if (and (zerop (length regexp)) + (y-or-n-p "Remove all function traces?")) + (untrace-all) + (mapatoms + (lambda (sym) + (and (trace-is-traced-p sym) + (string-match-p regexp (symbol-name sym)) + (untrace-function sym))))) + ;; Remove any `after-load' behaviour. + (trace--remove-after-load 'regexp regexp)) + +;;;###autoload +(defun trace-library (library &optional buffer context after-load) + "Trace functions defined by LIBRARY. +For example, to trace tramp.el functions, you could use: + +\\[trace-library] RET tramp RET + +Background tracing is used. Switch to the trace output buffer to +view the results. For any autoload declarations with a file name +matching LIBRARY, the associated function will be traced if and +when it is defined. (Autoload file names will not match if LIBRARY +specifies a longer, more specific path.) + +With a prefix argument, also prompt for the optional arguments. +If AFTER-LOAD is non-nil then re-process LIBRARY after loading it +\(ensuring that all of its functions will be traced). See +`trace-function-foreground' for details of BUFFER and CONTEXT, +and of foreground vs background tracing. + +Calling `trace-library' again for the same LIBRARY will update the +optional argument behaviours to respect the new values. + +See also `untrace-library'." + (interactive + (cons (trace--read-library) + (and current-prefix-arg + (nconc (trace--read-extra-args) + (list (y-or-n-p "Update traces after loading this library?")))))) + ;; Build list of library functions and autoloads. + (let ((defs (nconc (trace--library-defuns library) + (trace--library-autoloads library)))) + ;; Trace each of those definitions. + (mapc (lambda (func) + (trace-function-background func buffer context)) + defs)) + ;; Handle `after-load' argument. + (when after-load + (trace--after-load 'library library buffer context))) + +(defun trace--library-defuns (library) + "Returns a list of loaded function definitions associated with LIBRARY." + (delq nil (mapcar (lambda (x) + (and (consp x) + (eq (car x) 'defun) + (cdr x))) + (cdr (load-history-filename-element + (load-history-regexp library)))))) + +(defun trace--library-autoloads (library) + "Returns a list of all current autoloads associated with LIBRARY. + +Autoload file names will not match if LIBRARY specifies a longer, +more specific path than that of the autoload declaration itself." + (let* ((functions nil) + (filepattern (load-history-regexp library)) + (predicate (apply-partially 'trace--library-provides-autoload-p + filepattern))) + (mapatoms (lambda (sym) + (when (funcall predicate sym) + (push sym functions)))) + functions)) + +(defun trace--library-provides-autoload-p (filepattern sym) + "Whether symbol SYM is an autoload associated with FILEPATTERN. + +FILEPATTERN should be the result of calling `load-history-regexp'." + (when (fboundp sym) + (let ((f (symbol-function sym))) + (and (autoloadp f) + (string-match filepattern (cadr f)))))) + +(defun untrace-library (library) + "Remove all traces from functions defined by LIBRARY. + +See also `trace-library'." + (interactive (list (trace--read-library))) + ;; Remove traces from known LIBRARY defuns. + ;; (Also process autoloads, in case LIBRARY is unloaded.) + (let ((defs (nconc (trace--library-defuns library) + (trace--library-autoloads library)))) + (mapc (lambda (func) + (when (trace-is-traced-p func) + (untrace-function func))) + defs)) + ;; Remove any `after-load' behaviour. + (trace--remove-after-load 'library library)) + +(defvar trace--after-load-alist nil + "List of trace types to update after loading. + +Each list item has the form ((TYPE . VALUE) BUFFER CONTEXT), +where TYPE is one of the symbols `prefix', `regexp', or `library'; +and VALUE is the respective first argument to `trace-package', +`trace-regexp', or `trace-library'; with BUFFER and CONTEXT being +the values of those arguments as they were passed to the same +function.") + +(defun trace--after-load (type value &optional buffer context) + "Arrange to update traces after libraries are loaded. + +TYPE is one of the symbols `prefix', `regexp', or `library'; +VALUE is the respective first argument to `trace-package', +`trace-regexp', or `trace-library'; and BUFFER and CONTEXT are +the values of those arguments as they were passed to the same +function. + +Adds `trace--after-load-function' to `after-load-functions'." + ;; Remove any existing spec for this (TYPE VALUE) key. + (trace--remove-after-load type value) + ;; Add the new spec. + (push (list (cons type value) buffer context) + trace--after-load-alist) + ;; Arrange to call `trace--after-load-function'. + (add-hook 'after-load-functions #'trace--after-load-function)) + +(defun trace--after-load-function (file) + "React to FILE being loaded. Callback for `after-load-functions'. + +See also `trace--after-load'." + (dolist (spec trace--after-load-alist) + (cl-destructuring-bind ((type . value) buffer context) + spec + (cl-case type + (prefix (trace-package value nil buffer context)) + (regexp (trace-regexp value nil buffer context)) + (library (when (string-match (load-history-regexp value) file) + (trace-library value nil buffer context))))))) + +(defun trace--remove-after-load (type value) + "Remove any (TYPE . VALUE) entry from `trace--after-load-alist'. + +Remove `trace--after-load-function' from `after-load-functions' +if it is no longer needed." + (setq trace--after-load-alist + (cl-delete (cons type value) trace--after-load-alist + :key #'car :test #'equal)) + (unless trace--after-load-alist + (remove-hook 'after-load-functions #'trace--after-load-function))) + +(defun trace--remove-after-load-all () + "Reset `trace--after-load-alist'. +Remove `trace--after-load-function' from `after-load-functions'" + (setq trace--after-load-alist nil) + (remove-hook 'after-load-functions #'trace--after-load-function)) + (defun untrace-all () - "Untraces all currently traced functions." + "Remove traces from all currently traced functions." (interactive) - (mapatoms #'untrace-function)) + (mapatoms #'untrace-function) + (trace--remove-after-load-all)) (provide 'trace)