diff options
author | Sean Whitton <spwhitton@spwhitton.name> | 2020-07-25 20:10:53 -0700 |
---|---|---|
committer | Sean Whitton <spwhitton@spwhitton.name> | 2020-07-25 20:10:53 -0700 |
commit | c2c422ba2c1851d86a99038fde2e93805e217394 (patch) | |
tree | f76c7cc101d8dc6b3f1533b5f9c0b4762e5e2f29 | |
parent | f3de5bd709b1909a9dd5956bc38d3ca4ba9295a4 (diff) | |
parent | 9f01ce6327af886f26399924a9aadf16cdd4fd9f (diff) | |
download | emacs-c2c422ba2c1851d86a99038fde2e93805e217394.tar.gz |
Merge remote-tracking branch 'origin/master' into athena/unstable
-rw-r--r-- | doc/emacs/custom.texi | 4 | ||||
-rw-r--r-- | doc/emacs/emacs.texi | 6 | ||||
-rw-r--r-- | doc/emacs/maintaining.texi | 161 | ||||
-rw-r--r-- | etc/NEWS | 20 | ||||
-rw-r--r-- | lisp/emacs-lisp/byte-opt.el | 10 | ||||
-rw-r--r-- | lisp/emacs-lisp/eldoc.el | 61 | ||||
-rw-r--r-- | lisp/gnus/mm-decode.el | 10 | ||||
-rw-r--r-- | lisp/ielm.el | 4 | ||||
-rw-r--r-- | lisp/man.el | 4 | ||||
-rw-r--r-- | lisp/progmodes/bug-reference.el | 66 | ||||
-rw-r--r-- | lisp/progmodes/project.el | 5 | ||||
-rw-r--r-- | lisp/vc/vc-git.el | 2 | ||||
-rw-r--r-- | src/emacs-module.c | 108 | ||||
-rw-r--r-- | test/data/emacs-module/mod-test.c | 32 | ||||
-rw-r--r-- | test/src/emacs-module-tests.el | 29 |
15 files changed, 396 insertions, 126 deletions
diff --git a/doc/emacs/custom.texi b/doc/emacs/custom.texi index 00c8ee4f98b..acd7fb13ae1 100644 --- a/doc/emacs/custom.texi +++ b/doc/emacs/custom.texi @@ -1630,6 +1630,10 @@ characters are actually defined by this map. @item @vindex mode-specific-map @code{mode-specific-map} is for characters that follow @kbd{C-c}. +@item +@vindex project-prefix-map +@code{project-prefix-map} is for characters that follow @kbd{C-x p}, +used for project-related commands (@pxref{Projects}). @end itemize @node Local Keymaps diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index 6b82aeb8234..5b6b7b7e93e 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -857,6 +857,12 @@ Customizing VC * CVS Options:: Options for CVS. @end ifnottex +Projects + +* Project File Commands:: Commands for handling project files. +* Project Buffer Commands:: Commands for handling project buffers. +* Switching Projects:: Switching between projects. + Change Logs * Change Log Commands:: Commands for editing change log files. diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index cc7415e7ad5..43ec2d4e9f2 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -1657,12 +1657,43 @@ the project back-end. For example, the VC back-end doesn't consider ``ignored'' files (@pxref{VC Ignore}) to be part of the project. @menu -* Project File Commands:: Commands for handling project files. -* Switching Projects:: Switching between projects. +* Project File Commands:: Commands for handling project files. +* Project Buffer Commands:: Commands for handling project buffers. +* Switching Projects:: Switching between projects. @end menu @node Project File Commands -@subsection Project File Commands +@subsection Project Commands That Operate on Files + +@table @kbd +@item C-x p f +Visit a file that belongs to the current project +(@code{project-find-file}). +@item C-x p g +Find matches for a regexp in all files that belong to the current +project (@code{project-find-regexp}). +@item M-x project-search +Interactively search for regexp matches in all files that belong to +the current project. +@item C-x p r +Perform query-replace for a regexp in all files that belong to the +current project (@code{project-query-replace-regexp}). +@item C-x p d +Run Dired in the current project's root directory +(@code{project-dired}). +@item C-x p v +Run @code{vc-dir} in the current project's root directory +(@code{project-vc-dir}). +@item C-x p s +Start an inferior shell in the current project's root directory +(@code{project-shell}). +@item C-x p e +Start Eshell in the current project's root directory +(@code{project-eshell}). +@item C-x p c +Run compilation in the current project's root directory +(@code{project-compile}). +@end table Emacs provides commands for handling project files conveniently. This subsection describes these commands. @@ -1676,25 +1707,26 @@ doesn't seem to belong to a recognizable project, these commands prompt you for the project directory. @findex project-find-file - The command @code{project-find-file} is a convenient way of visiting -files (@pxref{Visiting}) that belong to the current project. Unlike -@kbd{C-x C-f}, this command doesn't require to type the full file name -of the file to visit, you can type only the file's base name (i.e., -omit the leading directories). In addition, the completion candidates -considered by the command include only the files belonging to the -current project, and nothing else. If there's a file name at point, -this command offers that file as the default to visit. + The command @kbd{C-x p f} (@code{project-find-file}) is a convenient +way of visiting files (@pxref{Visiting}) that belong to the current +project. Unlike @kbd{C-x C-f}, this command doesn't require to type +the full file name of the file to visit, you can type only the file's +base name (i.e., omit the leading directories). In addition, the +completion candidates considered by the command include only the files +belonging to the current project, and nothing else. If there's a file +name at point, this command offers that file as the default to visit. @findex project-find-regexp - The command @code{project-find-regexp} is similar to @code{rgrep} -(@pxref{Grep Searching}), but it searches only the files that belong -to the current project. The command prompts for the regular -expression to search, and pops up an Xref mode buffer with the search -results, where you can select a match using the Xref mode commands -(@pxref{Xref Commands}). When invoked with a prefix argument, this -command additionally prompts for the base directory from which to -start the search; this allows, for example, to limit the search only -to project files under a certain subdirectory of the project root. + The command @kbd{C-x p g} (@code{project-find-regexp}) is similar to +@code{rgrep} (@pxref{Grep Searching}), but it searches only the files +that belong to the current project. The command prompts for the +regular expression to search, and pops up an Xref mode buffer with the +search results, where you can select a match using the Xref mode +commands (@pxref{Xref Commands}). When invoked with a prefix +argument, this command additionally prompts for the base directory +from which to start the search; this allows, for example, to limit the +search only to project files under a certain subdirectory of the +project root. @findex project-search @kbd{M-x project-search} is an interactive variant of @@ -1706,7 +1738,7 @@ matched file. To find the rest of the matches, type @w{@kbd{M-x fileloop-continue @key{RET}}}. @findex project-query-replace-regexp - @kbd{M-x project-query-replace-regexp} is similar to + @kbd{C-x p r} (@code{project-query-replace-regexp}) is similar to @code{project-search}, but it prompts you for whether to replace each match it finds, like @code{query-replace} does (@pxref{Query Replace}), and continues to the next match after you respond. If your @@ -1714,40 +1746,85 @@ response causes Emacs to exit the query-replace loop, you can later continue with @w{@kbd{M-x fileloop-continue @key{RET}}}. @findex project-dired - The command @code{project-dired} opens a Dired buffer -(@pxref{Dired}) listing the files in the current project's root + The command @kbd{C-x p d} (@code{project-dired}) opens a Dired +buffer (@pxref{Dired}) listing the files in the current project's root directory. @findex project-vc-dir - The command @code{project-vc-dir} opens a VC Directory buffer -(@pxref{VC Directory Mode}) listing the version control statuses of -the files in a directory tree under the current project's -root directory. + The command @kbd{C-x p v} (@code{project-vc-dir}) opens a VC +Directory buffer (@pxref{VC Directory Mode}) listing the version +control statuses of the files in a directory tree under the current +project's root directory. @findex project-shell - The command @code{project-shell} starts a shell session -(@pxref{Shell}) in a new buffer with the current project's root as the -working directory. + The command @kbd{C-x p s} (@code{project-shell}) starts a shell +session (@pxref{Shell}) in a new buffer with the current project's +root as the working directory. @findex project-eshell - The command @code{project-eshell} starts an Eshell session in a new -buffer with the current project's root as the working directory. -@xref{Top,Eshell,Eshell, eshell, Eshell: The Emacs Shell}. + The command @kbd{C-x p e} (@code{project-eshell}) starts an Eshell +session in a new buffer with the current project's root as the working +directory. @xref{Top,Eshell,Eshell, eshell, Eshell: The Emacs Shell}. + +@findex project-compile + The command @kbd{C-x p c} (@code{project-compile}) runs compilation +(@pxref{Compilation}) in the current project's root directory. + +@node Project Buffer Commands +@subsection Project Commands That Operate on Buffers + +@table @kbd +@item C-x p b +Switch to another buffer belonging to the current project +(@code{project-switch-to-buffer}). +@item C-x p k +Kill all live buffers that belong to the current project +(@code{project-kill-buffers}). +@end table + +@findex project-switch-to-buffer + Working on a project could potentially involve having many buffers +visiting files that belong to the project, and also buffers that +belong to the project, but don't visit any files (like the +@file{*compilation*} buffer created by @code{project-compile}). The +command @kbd{C-x p b} (@code{project-switch-to-buffer}) helps you +switch between buffers that belong to the current project by prompting +for a buffer to switch and considering only the current project's +buffers as candidates for completion. + +@findex project-kill-buffers +@vindex project-kill-buffers-ignores + When you finish working on the project, you may wish to kill all the +buffers that belong to the project, to keep your Emacs session +smaller. The command @kbd{C-x p k} (@code{project-kill-buffers}) +accomplishes that: it kills all the buffers that belong to the current +project, except if @code{project-kill-buffers-ignores} tells +otherwise. @node Switching Projects @subsection Switching Projects +@table @kbd +@item C-x p p +Run an Emacs command for another project (@code{project-switch-project}). +@end table + +@findex project-switch-project +@vindex project-switch-commands Commands that operate on project files (@pxref{Project File Commands}) will conveniently prompt you for a project directory when -no project is current. When you are inside a project but you want to -operate on a different project, the command -@code{project-switch-project} can be used. - - This command prompts you to choose a directory among known project -roots, and then displays the menu of available commands to operate on -the chosen project. The variable @code{project-switch-commands} -controls which commands are available in the menu, and by which keys -they are invoked. +no project is current. When you are inside some project, but you want +to operate on a different project, use the @kbd{C-x p p} command +(@code{project-switch-project}). This command prompts you to choose a +directory among known project roots, and then displays the menu of +available commands to operate on the project you choose. The variable +@code{project-switch-commands} controls which commands are available +in the menu, and which key invokes each command. + +@vindex project-list-file + The variable @code{project-list-file} names the file in which Emacs +records the list of known projects. It defaults to the file +@file{projects} in @code{user-emacs-directory} (@pxref{Find Init}). @node Change Log @section Change Logs @@ -502,21 +502,31 @@ information, see the related entry about 'shr-browse-url' above. *** New user option 'project-vc-merge-submodules'. -*** Previously used project directories are now suggested by -all commands that prompt for a project directory. +*** Project commands now have their own history. +Previously used project directories are now suggested by all commands +that prompt for a project directory. + ++++ +*** New prefix keymap 'project-prefix-map'. +Key sequences that invoke project-related commands start with the +prefix 'C-x p'. Type "C-x p C-h" to show the full list. +++ *** New commands 'project-dired', 'project-vc-dir', 'project-shell', 'project-eshell'. These commands run Dired/VC-Dir and Shell/Eshell in a project's root directory, respectively. -*** New command 'project-compile', which runs compilation. ++++ +*** New command 'project-compile'. +This command runs compilation in the current project's root +directory. +++ *** New command 'project-switch-project'. This command lets you "switch" to another project and run a project command chosen from a dispatch menu. ++++ *** New user option 'project-list-file'. ** json.el @@ -566,8 +576,8 @@ appropriate values for those two variables. There are three guessing mechanisms so far: based on version control information of the current buffer's file, based on newsgroup/mail-folder name and several news and mail message headers in Gnus buffers, and based on IRC channel and -server in rcirc buffers. All mechanisms are extensible with custom -rules, see the variables 'bug-reference-setup-from-vc-alist', +network in rcirc and ERC buffers. All mechanisms are extensible with +custom rules, see the variables 'bug-reference-setup-from-vc-alist', 'bug-reference-setup-from-mail-alist', and 'bug-reference-setup-from-irc-alist'. diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 194ceee176f..6f801be5457 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -709,6 +709,9 @@ (integer (if integer-is-first arg1 arg2)) (other (if integer-is-first arg2 arg1))) (list (if (eq integer 1) '1+ '1-) other))) + ;; (+ x y z) -> (+ (+ x y) z) + ((= (length args) 3) + `(+ ,(byte-optimize-plus `(+ ,(car args) ,(cadr args))) ,@(cddr args))) ;; not further optimized ((equal args (cdr form)) form) (t (cons '+ args))))) @@ -737,6 +740,9 @@ ((and (null (cdr args)) (numberp (car args))) (- (car args))) + ;; (- x y z) -> (- (- x y) z) + ((= (length args) 3) + `(- ,(byte-optimize-minus `(- ,(car args) ,(cadr args))) ,@(cddr args))) ;; not further optimized ((equal args (cdr form)) form) (t (cons '- args)))))) @@ -764,6 +770,10 @@ ((null args) 1) ;; (* n) -> n, where n is a number ((and (null (cdr args)) (numberp (car args))) (car args)) + ;; (* x y z) -> (* (* x y) z) + ((= (length args) 3) + `(* ,(byte-optimize-multiply `(* ,(car args) ,(cadr args))) + ,@(cddr args))) ;; not further optimized ((equal args (cdr form)) form) (t (cons '* args))))) diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 6ed5bff9f44..fcb104e5477 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -5,7 +5,7 @@ ;; Author: Noah Friedman <friedman@splode.com> ;; Keywords: extensions ;; Created: 1995-10-06 -;; Version: 1.5.0 +;; Version: 1.6.0 ;; Package-Requires: ((emacs "26.3")) ;; This is a GNU ELPA :core package. Avoid functionality that is not @@ -340,16 +340,32 @@ Also store it in `eldoc-last-message' and return that value." ;; for us, but do note that the last-message will be gone. (setq eldoc-last-message nil)))) -;; Decide whether now is a good time to display a message. -(defun eldoc-display-message-p () - "Return non-nil when it is appropriate to display an ElDoc message." - (and (eldoc-display-message-no-interference-p) - ;; If this-command is non-nil while running via an idle - ;; timer, we're still in the middle of executing a command, - ;; e.g. a query-replace where it would be annoying to - ;; overwrite the echo area. - (not this-command) - (eldoc--message-command-p last-command))) +(defvar-local eldoc--last-request-state nil + "Tuple containing information about last ElDoc request.") +(defun eldoc--request-state () + "Compute information to store in `eldoc--last-request-state'." + (list (current-buffer) (buffer-modified-tick) (point))) + +(defun eldoc--request-docs-p (request-state) + "Return non-nil when it is appropriate to request docs. +REQUEST-STATE is a candidate for `eldoc--last-request-state'" + (and + ;; FIXME: The original idea behind this function is to protect the + ;; Echo area from ElDoc interference, but since that is only one of + ;; the possible outlets of ElDoc, this must soon be reworked. + (eldoc-display-message-no-interference-p) + (not (and eldoc--doc-buffer + (get-buffer-window eldoc--doc-buffer) + (equal request-state + (with-current-buffer + eldoc--doc-buffer + eldoc--last-request-state)))) + ;; If this-command is non-nil while running via an idle + ;; timer, we're still in the middle of executing a command, + ;; e.g. a query-replace where it would be annoying to + ;; overwrite the echo area. + (not this-command) + (eldoc--message-command-p last-command))) ;; Check various conditions about the current environment that might make @@ -400,7 +416,8 @@ so that the global value (i.e. the default value of the hook) is taken into account if the major mode specific function does not return any documentation.") -(defvar eldoc--doc-buffer nil "Buffer holding latest eldoc-produced docs.") +(defvar eldoc--doc-buffer nil "Buffer displaying latest ElDoc-produced docs.") + (defun eldoc-doc-buffer (&optional interactive) "Get latest *eldoc* help buffer. Interactively, display it." (interactive (list t)) @@ -410,6 +427,7 @@ return any documentation.") (setq eldoc--doc-buffer (get-buffer-create "*eldoc*"))) (when interactive (display-buffer eldoc--doc-buffer)))) + (defun eldoc--handle-docs (docs) "Display multiple DOCS in echo area. DOCS is a list of (STRING PLIST...). It is already sorted. @@ -429,9 +447,12 @@ Honor most of `eldoc-echo-area-use-multiline-p'." (integer val) (t 1))) (things-reported-on) + (request eldoc--last-request-state) single-doc single-doc-sym) ;; Then, compose the contents of the `*eldoc*' buffer. (with-current-buffer (eldoc-doc-buffer) + ;; Set doc-buffer's `eldoc--last-request-state', too + (setq eldoc--last-request-state request) (let ((inhibit-read-only t)) (erase-buffer) (setq buffer-read-only t) (local-set-key "q" 'quit-window) @@ -741,14 +762,16 @@ should endeavour to display the docstrings eventually produced." (defun eldoc-print-current-symbol-info (&optional interactive) "Document thing at point." (interactive '(t)) - (cond (interactive - (eldoc--invoke-strategy)) - (t - (if (not (eldoc-display-message-p)) - ;; Erase the last message if we won't display a new one. - (when eldoc-last-message - (eldoc--message nil)) + (let ((token (eldoc--request-state))) + (cond (interactive + (eldoc--invoke-strategy)) + ((not (eldoc--request-docs-p token)) + ;; Erase the last message if we won't display a new one. + (when eldoc-last-message + (eldoc--message nil))) + (t (let ((non-essential t)) + (setq eldoc--last-request-state token) ;; Only keep looking for the info as long as the user hasn't ;; requested our attention. This also locally disables ;; inhibit-quit. diff --git a/lisp/gnus/mm-decode.el b/lisp/gnus/mm-decode.el index 96695aabfde..587c4e01b92 100644 --- a/lisp/gnus/mm-decode.el +++ b/lisp/gnus/mm-decode.el @@ -1680,8 +1680,14 @@ If RECURSIVE, search recursively." (t (y-or-n-p (format "Decrypt (S/MIME) part? ")))) (mm-view-pkcs7 parts from)) - (goto-char (point-min)) - (insert "Content-type: text/plain\n\n") + ;; Normally there will be a Content-type header here, but + ;; some mailers don't add that to the encrypted part, which + ;; makes the subsequent re-dissection fail here. + (save-restriction + (mail-narrow-to-head) + (unless (mail-fetch-field "content-type") + (goto-char (point-max)) + (insert "Content-type: text/plain\n\n"))) (setq parts (mm-dissect-buffer t))))) ((equal subtype "signed") (unless (and (setq protocol diff --git a/lisp/ielm.el b/lisp/ielm.el index 47c5158ce41..b3654b91d37 100644 --- a/lisp/ielm.el +++ b/lisp/ielm.el @@ -537,7 +537,9 @@ Customized bindings may be defined in `ielm-map', which currently contains: '(comint-replace-by-expanded-history ielm-complete-filename elisp-completion-at-point)) (add-hook 'eldoc-documentation-functions - #'elisp-eldoc-documentation-function nil t) + #'elisp-eldoc-var-docstring nil t) + (add-hook 'eldoc-documentation-functions + #'elisp-eldoc-funcall nil t) (set (make-local-variable 'ielm-prompt-internal) ielm-prompt) (set (make-local-variable 'comint-prompt-read-only) ielm-prompt-read-only) (setq comint-get-old-input 'ielm-get-old-input) diff --git a/lisp/man.el b/lisp/man.el index 8a36f3ac25d..3121334c6f9 100644 --- a/lisp/man.el +++ b/lisp/man.el @@ -1396,7 +1396,7 @@ synchronously, PROCESS is the name of the buffer where the manpage command is run. Second argument STRING is the entire string of output." (save-excursion (let ((Man-buffer (process-buffer process))) - (if (null (buffer-name Man-buffer)) ;; deleted buffer + (if (not (buffer-live-p Man-buffer)) ;; deleted buffer (set-process-buffer process nil) (with-current-buffer Man-buffer @@ -1430,7 +1430,7 @@ manpage command." (delete-buff nil) message) - (if (null (buffer-name Man-buffer)) ;; deleted buffer + (if (not (buffer-live-p Man-buffer)) ;; deleted buffer (or (stringp process) (set-process-buffer process nil)) diff --git a/lisp/progmodes/bug-reference.el b/lisp/progmodes/bug-reference.el index b88ea0af82c..c52331f84fa 100644 --- a/lisp/progmodes/bug-reference.el +++ b/lisp/progmodes/bug-reference.el @@ -353,38 +353,45 @@ and set it if applicable." This takes action if `bug-reference-mode' is enabled in IRC channels using one of Emacs' IRC clients (rcirc and ERC). -Currently, only rcirc is supported. +Currently, rcirc and ERC are supported. Each element has the form - (CHANNEL-REGEXP SERVER-REGEXP BUG-REGEXP URL-FORMAT) + (CHANNEL-REGEXP NETWORK-REGEXP BUG-REGEXP URL-FORMAT) -CHANNEL-REGEXP is a regexp matched against the current mail IRC -channel name. SERVER-REGEXP is matched against the IRC server -name. If any of those matches, BUG-REGEXP is set as +CHANNEL-REGEXP is a regexp matched against the current IRC +channel name (e.g. #emacs). NETWORK-REGEXP is matched against +the IRC network name (e.g. freenode). Both entries are optional. +If all given entries match, BUG-REGEXP is set as `bug-reference-bug-regexp' and URL-FORMAT is set as `bug-reference-url-format'.") -(defun bug-reference--maybe-setup-from-irc (channel server) - "Set up according to IRC CHANNEL or SERVER. -CHANNEL is an IRC channel name and SERVER is that channel's -server name. +(defun bug-reference--maybe-setup-from-irc (channel network) + "Set up according to IRC CHANNEL or NETWORK. +CHANNEL is an IRC channel name (or generally a target, i.e., it +could also be a user name) and NETWORK is that channel's network +name. -If any CHANNEL-REGEXP or SERVER-REGEXP of -`bug-reference-setup-from-irc-alist' matches CHANNEL or SERVER, -the corresponding BUG-REGEXP and URL-FORMAT are set." +If any `bug-reference-setup-from-irc-alist' entry's +CHANNEL-REGEXP and NETWORK-REGEXP match CHANNEL and NETWORK, the +corresponding BUG-REGEXP and URL-FORMAT are set." (catch 'setup-done (dolist (config bug-reference-setup-from-irc-alist) - (when (or - (and channel - (car config) - (string-match-p (car config) channel)) - (and server - (nth 1 config) - (string-match-p (car config) server))) - (setq-local bug-reference-bug-regexp (nth 2 config)) - (setq-local bug-reference-url-format (nth 3 config)) - (throw 'setup-done t))))) + (let ((channel-rx (car config)) + (network-rx (nth 1 config))) + (when (and + ;; One of both has to be given. + (or channel-rx network-rx) + ;; The args have to be set. + channel network) + (when (and + (or (null channel-rx) + (string-match-p channel-rx channel)) + (or (null network-rx) + (string-match-p network-rx network))) + (setq-local bug-reference-bug-regexp (nth 2 config)) + (setq-local bug-reference-url-format (nth 3 config)) + (throw 'setup-done t))))))) (defvar rcirc-target) (defvar rcirc-server-buffer) @@ -402,6 +409,18 @@ and set it if applicable." (with-current-buffer rcirc-server-buffer rcirc-server))))) +(declare-function erc-format-target "erc") +(declare-function erc-network-name "erc-networks") + +(defun bug-reference-try-setup-from-erc () + "Try setting up `bug-reference-mode' based on ERC channel and server. +Test each configuration in `bug-reference-setup-from-irc-alist' +and set it if applicable." + (when (derived-mode-p 'erc-mode) + (bug-reference--maybe-setup-from-irc + (erc-format-target) + (erc-network-name)))) + (defun bug-reference--run-auto-setup () (when (or bug-reference-mode bug-reference-prog-mode) @@ -414,7 +433,8 @@ and set it if applicable." (catch 'setup (dolist (f (list #'bug-reference-try-setup-from-vc #'bug-reference-try-setup-from-gnus - #'bug-reference-try-setup-from-rcirc)) + #'bug-reference-try-setup-from-rcirc + #'bug-reference-try-setup-from-erc)) (when (funcall f) (throw 'setup t)))))))) diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index a0930553bd7..5cfc6a20986 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -197,7 +197,7 @@ of the project instance object." pr (project--find-in-directory directory)))) (when maybe-prompt (if pr - (project--add-to-project-list-front pr) + (project-remember-project pr) (project--remove-from-project-list directory) (setq pr (cons 'transient directory)))) pr)) @@ -987,7 +987,8 @@ With some possible metadata (to be decided).") (pp project--list (current-buffer)) (write-region nil nil filename nil 'silent)))) -(defun project--add-to-project-list-front (pr) +;;;###autoload +(defun project-remember-project (pr) "Add project PR to the front of the project list. Save the result in `project-list-file' if the list of projects has changed." (project--ensure-read-project-list) diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index b5cb842aeee..7f6e1db1ed7 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -1237,7 +1237,7 @@ log entries." (set (make-local-variable 'log-view-message-re) (if (not (memq vc-log-view-type '(long log-search with-diff))) (cadr vc-git-root-log-format) - "^commit *\\([0-9a-z]+\\)")) + "^commit +\\([0-9a-z]+\\)")) ;; Allow expanding short log entries. (when (memq vc-log-view-type '(short log-outgoing log-incoming mergebase)) (setq truncate-lines t) diff --git a/src/emacs-module.c b/src/emacs-module.c index 3d1827c7dad..e4e7da088d7 100644 --- a/src/emacs-module.c +++ b/src/emacs-module.c @@ -78,6 +78,7 @@ To add a new module function, proceed as follows: #include "emacs-module.h" #include <stdarg.h> +#include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <stdlib.h> @@ -154,11 +155,11 @@ struct emacs_value_frame /* A structure that holds an initial frame (so that the first local values require no dynamic allocation) and keeps track of the current frame. */ -static struct emacs_value_storage +struct emacs_value_storage { struct emacs_value_frame initial; struct emacs_value_frame *current; -} global_storage; +}; /* Private runtime and environment members. */ @@ -371,10 +372,57 @@ module_get_environment (struct emacs_runtime *runtime) } /* To make global refs (GC-protected global values) keep a hash that - maps global Lisp objects to reference counts. */ + maps global Lisp objects to 'struct module_global_reference' + objects. We store the 'emacs_value' in the hash table so that it + is automatically garbage-collected (Bug#42482). */ static Lisp_Object Vmodule_refs_hash; +/* Pseudovector type for global references. The pseudovector tag is + PVEC_OTHER since these values are never printed and don't need to + be special-cased for garbage collection. */ + +struct module_global_reference { + /* Pseudovector header, must come first. */ + union vectorlike_header header; + + /* Holds the emacs_value for the object. The Lisp_Object stored + therein must be the same as the hash key. */ + struct emacs_value_tag value; + + /* Reference count, always positive. */ + ptrdiff_t refcount; +}; + +static struct module_global_reference * +XMODULE_GLOBAL_REFERENCE (Lisp_Object o) +{ + eassert (PSEUDOVECTORP (o, PVEC_OTHER)); + return XUNTAG (o, Lisp_Vectorlike, struct module_global_reference); +} + +/* Returns whether V is a global reference. Only used to check module + assertions. If V is not a global reference, increment *N by the + number of global references (for debugging output). */ + +static bool +module_global_reference_p (emacs_value v, ptrdiff_t *n) +{ + struct Lisp_Hash_Table *h = XHASH_TABLE (Vmodule_refs_hash); + /* Note that we can't use `hash_lookup' because V might be a local + reference that's identical to some global reference. */ + for (ptrdiff_t i = 0; i < HASH_TABLE_SIZE (h); ++i) + { + if (!EQ (HASH_KEY (h, i), Qunbound) + && &XMODULE_GLOBAL_REFERENCE (HASH_VALUE (h, i))->value == v) + return true; + } + /* Only used for debugging, so we don't care about overflow, just + make sure the operation is defined. */ + INT_ADD_WRAPV (*n, h->count, n); + return false; +} + static emacs_value module_make_global_ref (emacs_env *env, emacs_value value) { @@ -383,21 +431,30 @@ module_make_global_ref (emacs_env *env, emacs_value value) Lisp_Object new_obj = value_to_lisp (value), hashcode; ptrdiff_t i = hash_lookup (h, new_obj, &hashcode); + /* Note: This approach requires the garbage collector to never move + objects. */ + if (i >= 0) { Lisp_Object value = HASH_VALUE (h, i); - EMACS_INT refcount = XFIXNAT (value) + 1; - if (MOST_POSITIVE_FIXNUM < refcount) + struct module_global_reference *ref = XMODULE_GLOBAL_REFERENCE (value); + bool overflow = INT_ADD_WRAPV (ref->refcount, 1, &ref->refcount); + if (overflow) overflow_error (); - value = make_fixed_natnum (refcount); - set_hash_value_slot (h, i, value); + return &ref->value; } else { - hash_put (h, new_obj, make_fixed_natnum (1), hashcode); + struct module_global_reference *ref + = ALLOCATE_PLAIN_PSEUDOVECTOR (struct module_global_reference, + PVEC_OTHER); + ref->value.v = new_obj; + ref->refcount = 1; + Lisp_Object value; + XSETPSEUDOVECTOR (value, ref, PVEC_OTHER); + hash_put (h, new_obj, value, hashcode); + return &ref->value; } - - return allocate_emacs_value (env, &global_storage, new_obj); } static void @@ -411,25 +468,21 @@ module_free_global_ref (emacs_env *env, emacs_value global_value) Lisp_Object obj = value_to_lisp (global_value); ptrdiff_t i = hash_lookup (h, obj, NULL); - if (i >= 0) + if (module_assertions) { - EMACS_INT refcount = XFIXNAT (HASH_VALUE (h, i)) - 1; - if (refcount > 0) - set_hash_value_slot (h, i, make_fixed_natnum (refcount)); - else - { - eassert (refcount == 0); - hash_remove_from_table (h, obj); - } + ptrdiff_t n = 0; + if (! module_global_reference_p (global_value, &n)) + module_abort ("Global value was not found in list of %"pD"d globals", + n); } - if (module_assertions) + if (i >= 0) { - ptrdiff_t count = 0; - if (value_storage_contains_p (&global_storage, global_value, &count)) - return; - module_abort ("Global value was not found in list of %"pD"d globals", - count); + Lisp_Object value = HASH_VALUE (h, i); + struct module_global_reference *ref = XMODULE_GLOBAL_REFERENCE (value); + eassert (0 < ref->refcount); + if (--ref->refcount == 0) + hash_remove_from_table (h, obj); } } @@ -1250,7 +1303,7 @@ value_to_lisp (emacs_value v) ++num_environments; } /* Also check global values. */ - if (value_storage_contains_p (&global_storage, v, &num_values)) + if (module_global_reference_p (v, &num_values)) goto ok; module_abort (("Emacs value not found in %"pD"d values " "of %"pD"d environments"), @@ -1467,10 +1520,7 @@ module_handle_nonlocal_exit (emacs_env *env, enum nonlocal_exit type, void init_module_assertions (bool enable) { - /* If enabling module assertions, use a hidden environment for - storing the globals. This environment is never freed. */ module_assertions = enable; - initialize_storage (&global_storage); } /* Return whether STORAGE contains VALUE. Used to check module diff --git a/test/data/emacs-module/mod-test.c b/test/data/emacs-module/mod-test.c index 1e64bcd65f1..ed289d7a863 100644 --- a/test/data/emacs-module/mod-test.c +++ b/test/data/emacs-module/mod-test.c @@ -201,7 +201,19 @@ Fmod_test_globref_free (emacs_env *env, ptrdiff_t nargs, emacs_value args[], return env->intern (env, "ok"); } +/* Treat a local reference as global and free it. Module assertions + should detect this case even if a global reference representing the + same object also exists. */ +static emacs_value +Fmod_test_globref_invalid_free (emacs_env *env, ptrdiff_t nargs, + emacs_value *args, void *data) +{ + emacs_value local = env->make_integer (env, 9876); + env->make_global_ref (env, local); + env->free_global_ref (env, local); /* Not allowed. */ + return env->intern (env, "nil"); +} /* Return a copy of the argument string where every 'a' is replaced with 'b'. */ @@ -306,6 +318,22 @@ Fmod_test_invalid_load (emacs_env *env, ptrdiff_t nargs, emacs_value *args, return invalid_stored_value; } +/* The next function works in conjunction with the two previous ones. + It stows away a copy of the object created by + `Fmod_test_invalid_store' in a global reference. Module assertions + should still detect the invalid load of the local reference. */ + +static emacs_value global_copy_of_invalid_stored_value; + +static emacs_value +Fmod_test_invalid_store_copy (emacs_env *env, ptrdiff_t nargs, + emacs_value *args, void *data) +{ + emacs_value local = Fmod_test_invalid_store (env, 0, NULL, NULL); + return global_copy_of_invalid_stored_value + = env->make_global_ref (env, local); +} + /* An invalid finalizer: Finalizers are run during garbage collection, where Lisp code can't be executed. -module-assertions tests for this case. */ @@ -678,12 +706,16 @@ emacs_module_init (struct emacs_runtime *ert) 1, 1, NULL, NULL); DEFUN ("mod-test-globref-make", Fmod_test_globref_make, 0, 0, NULL, NULL); DEFUN ("mod-test-globref-free", Fmod_test_globref_free, 4, 4, NULL, NULL); + DEFUN ("mod-test-globref-invalid-free", Fmod_test_globref_invalid_free, 0, 0, + NULL, NULL); DEFUN ("mod-test-string-a-to-b", Fmod_test_string_a_to_b, 1, 1, NULL, NULL); DEFUN ("mod-test-userptr-make", Fmod_test_userptr_make, 1, 1, NULL, NULL); DEFUN ("mod-test-userptr-get", Fmod_test_userptr_get, 1, 1, NULL, NULL); DEFUN ("mod-test-vector-fill", Fmod_test_vector_fill, 2, 2, NULL, NULL); DEFUN ("mod-test-vector-eq", Fmod_test_vector_eq, 2, 2, NULL, NULL); DEFUN ("mod-test-invalid-store", Fmod_test_invalid_store, 0, 0, NULL, NULL); + DEFUN ("mod-test-invalid-store-copy", Fmod_test_invalid_store_copy, 0, 0, + NULL, NULL); DEFUN ("mod-test-invalid-load", Fmod_test_invalid_load, 0, 0, NULL, NULL); DEFUN ("mod-test-invalid-finalizer", Fmod_test_invalid_finalizer, 0, 0, NULL, NULL); diff --git a/test/src/emacs-module-tests.el b/test/src/emacs-module-tests.el index 411b4505da0..8465fd02e1e 100644 --- a/test/src/emacs-module-tests.el +++ b/test/src/emacs-module-tests.el @@ -272,6 +272,24 @@ must evaluate to a regular expression string." (mod-test-invalid-store) (mod-test-invalid-load))) +(ert-deftest module--test-assertions--load-non-live-object-with-global-copy () + "Check that -module-assertions verify that non-live objects aren't accessed. +This differs from `module--test-assertions-load-non-live-object' +in that it stows away a global reference. The module assertions +should nevertheless detect the invalid load." + (skip-unless (or (file-executable-p mod-test-emacs) + (and (eq system-type 'windows-nt) + (file-executable-p (concat mod-test-emacs ".exe"))))) + ;; This doesn't yet cause undefined behavior. + (should (eq (mod-test-invalid-store-copy) 123)) + (module--test-assertion (rx "Emacs value not found in " + (+ digit) " values of " + (+ digit) " environments\n") + ;; Storing and reloading a local value causes undefined behavior, + ;; which should be detected by the module assertions. + (mod-test-invalid-store-copy) + (mod-test-invalid-load))) + (ert-deftest module--test-assertions--call-emacs-from-gc () "Check that -module-assertions prevents calling Emacs functions during garbage collection." @@ -283,6 +301,17 @@ during garbage collection." (mod-test-invalid-finalizer) (garbage-collect))) +(ert-deftest module--test-assertions--globref-invalid-free () + "Check that -module-assertions detects invalid freeing of a +local reference." + (skip-unless (or (file-executable-p mod-test-emacs) + (and (eq system-type 'windows-nt) + (file-executable-p (concat mod-test-emacs ".exe"))))) + (module--test-assertion + (rx "Global value was not found in list of " (+ digit) " globals") + (mod-test-globref-invalid-free) + (garbage-collect))) + (ert-deftest module/describe-function-1 () "Check that Bug#30163 is fixed." (with-temp-buffer |