This fixes bug #61962.
* lisp/subr.el (safe-copy-tree): New function.
* lisp/emacs-lisp/bytecomp.el (byte-compile-initial-macro-environment): Amend
the entry for eval-and-compile to use safe-copy-tree and
byte-run-strip-symbol-positions for the eval part.
* doc/lispref/lists.texi (Building Lists): Document safe-copy-tree.
* etc/NEWS: Note the new function safe-copy-tree.
Normally, when @var{tree} is anything other than a cons cell,
@code{copy-tree} simply returns @var{tree}. However, if @var{vecp} is
non-@code{nil}, it copies vectors too (and operates recursively on
-their elements).
+their elements). This function cannot cope with circular lists.
@end defun
+@defun safe-copy-tree tree &optional vecp
+This function returns a copy of the tree @var{tree}. If @var{tree} is
+a cons cell, this make a new cons cell with the same @sc{car} and
+@sc{cdr}, then recursively copies the @sc{car} and @sc{cdr} in the
+same way.
+
+Normally, when @var{tree} is anything other than a cons cell,
+@code{copy-tree} simply returns @var{tree}. However, if @var{vecp} is
+non-@code{nil}, it copies vectors and records too (and operates
+recursively on their elements). This function handles circular lists
+and vectors, and is thus slower than @code{copy-tree} for typical cases.
+
@defun flatten-tree tree
This function returns a ``flattened'' copy of @var{tree}, that is,
a list containing all the non-@code{nil} terminal nodes, or leaves, of
This warning can be suppressed using 'with-suppressed-warnings' with
the warning name 'suspicious'.
++++
+** New function 'safe-copy-tree'
+This function is a version of copy-tree which handles circular lists
+and circular vectors/records.
+
+++
** New function 'file-user-uid'.
This function is like 'user-uid', but is aware of file name handlers,
(macroexpand--all-toplevel
form
macroexpand-all-environment)))
- (eval expanded lexical-binding)
+ (eval (byte-run-strip-symbol-positions
+ (safe-copy-tree expanded))
+ lexical-binding)
expanded)))))
(with-suppressed-warnings
. ,(lambda (warnings &rest body)
(symbols-with-pos-enabled t)
(value (eval
(displaying-byte-compile-warnings
+;;;; NEW STOUGH, 2023-03-05
+ (byte-run-strip-symbol-positions
+;;;; END OF NEW STOUGH
(byte-compile-sexp
(let ((form (read-positioning-symbols (current-buffer))))
(push form byte-compile-form-stack)
(eval-sexp-add-defvars
form
- start-read-position))))
+ start-read-position)))
+;;;; NEW STOUGH, 2023-03-05
+ )
+;;;; END OF NEW STOUGH
+ )
lexical-binding)))
(cond (arg
(message "Compiling from buffer... done.")
(aset tree i (copy-tree (aref tree i) vecp)))
tree)
tree)))
+
+(defvar safe-copy-tree--seen nil
+ "A hash table for conses/vectors/records already seen by safe-copy-tree-1.
+It's key is a cons or vector/record seen by the algorithm, and its value is
+the corresponding cons/vector/record in the copy.")
+
+(defun safe-copy-tree--1 (tree &optional vecp)
+ "Make a copy of TREE, taking circular structure into account.
+If TREE is a cons cell, this recursively copies both its car and its cdr.
+Contrast to `copy-sequence', which copies only along the cdrs. With second
+argument VECP, this copies vectors and records as well as conses."
+ (cond
+ ((gethash tree safe-copy-tree--seen))
+ ((consp tree)
+ (let* ((result (cons (car tree) (cdr tree)))
+ (newcons result)
+ hash)
+ (while (and (not hash) (consp tree))
+ (if (setq hash (gethash tree safe-copy-tree--seen))
+ (setq newcons hash)
+ (puthash tree newcons safe-copy-tree--seen))
+ (setq tree newcons)
+ (unless hash
+ (if (or (consp (car tree))
+ (and vecp (or (vectorp (car tree)) (recordp (car tree)))))
+ (let ((newcar (safe-copy-tree--1 (car tree) vecp)))
+ (setcar tree newcar)))
+ (setq newcons (if (consp (cdr tree))
+ (cons (cadr tree) (cddr tree))
+ (cdr tree)))
+ (setcdr tree newcons)
+ (setq tree (cdr tree))))
+ (nconc result
+ (if (and vecp (or (vectorp tree) (recordp tree)))
+ (safe-copy-tree--1 tree vecp) tree))))
+ ((and vecp (or (vectorp tree) (recordp tree)))
+ (let* ((newvec (copy-sequence tree))
+ (i (length newvec)))
+ (puthash tree newvec safe-copy-tree--seen)
+ (setq tree newvec)
+ (while (>= (setq i (1- i)) 0)
+ (aset tree i (safe-copy-tree--1 (aref tree i) vecp)))
+ tree))
+ (t tree)))
+
+(defun safe-copy-tree (tree &optional vecp)
+ "Make a copy of TREE, taking circular structure into account.
+If TREE is a cons cell, this recursively copies both its car and its cdr.
+Contrast to `copy-sequence', which copies only along the cdrs. With second
+argument VECP, this copies vectors and records as well as conses."
+ (setq safe-copy-tree--seen (make-hash-table :test #'eq))
+ (safe-copy-tree--1 tree vecp))
+
\f
;;;; Various list-search functions.