aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias EngdegÄrd2023-02-20 15:23:12 +0100
committerMattias EngdegÄrd2023-02-21 10:42:00 +0100
commitdb3fea2e5ce46229ae40aa9ca6a89964261a7a5a (patch)
tree43264b77f2bc165a310090c40065ee6dfc750169
parentc4c4af022d6091537fb0d748342eba5e7d3ddd23 (diff)
downloademacs-db3fea2e5ce46229ae40aa9ca6a89964261a7a5a.tar.gz
emacs-db3fea2e5ce46229ae40aa9ca6a89964261a7a5a.zip
Detect and prevent function alias loops in `fset` and `defalias`
Make `fset` and `defalias` signal an error on attempts to create circular alias chains. This is more effective, efficient and convenient than permitting alias loops to be created and trying to detect them at run time each time a function is called, which is what we have been doing until now, badly. * lisp/help-fns.el (help-fns--analyze-function): Don't pass obsolete argument. * lisp/subr.el (function-alias-p): * src/data.c (indirect_function, Findirect_function): Simplify. Now error-free, second argument obsolete. (Ffset): Detect loops. * test/lisp/help-fns-tests.el (help-fns--analyze-function-recursive): * test/lisp/subr-tests.el (test-alias-p): Adapt tests. * test/src/data-tests.el (data-tests-fset, data-tests-defalias): New. * doc/lispref/eval.texi (Function Indirection): * doc/lispref/functions.texi (Defining Functions, Function Cells): Update manual. * etc/NEWS: Announce.
-rw-r--r--doc/lispref/eval.texi17
-rw-r--r--doc/lispref/functions.texi13
-rw-r--r--etc/NEWS15
-rw-r--r--lisp/help-fns.el2
-rw-r--r--lisp/subr.el30
-rw-r--r--src/data.c63
-rw-r--r--src/eval.c2
-rw-r--r--test/lisp/help-fns-tests.el4
-rw-r--r--test/lisp/subr-tests.el10
-rw-r--r--test/src/data-tests.el36
10 files changed, 103 insertions, 89 deletions
diff --git a/doc/lispref/eval.texi b/doc/lispref/eval.texi
index 88e899de1e8..a45517287b7 100644
--- a/doc/lispref/eval.texi
+++ b/doc/lispref/eval.texi
@@ -252,11 +252,8 @@ the original symbol. If the contents are another symbol, this
252process, called @dfn{symbol function indirection}, is repeated until 252process, called @dfn{symbol function indirection}, is repeated until
253it obtains a non-symbol. @xref{Function Names}, for more information 253it obtains a non-symbol. @xref{Function Names}, for more information
254about symbol function indirection. 254about symbol function indirection.
255 255We eventually obtain a non-symbol, which ought to be a function or
256 One possible consequence of this process is an infinite loop, in the 256other suitable object.
257event that a symbol's function cell refers to the same symbol.
258Otherwise, we eventually obtain a non-symbol, which ought to be a
259function or other suitable object.
260 257
261@kindex invalid-function 258@kindex invalid-function
262 More precisely, we should now have a Lisp function (a lambda 259 More precisely, we should now have a Lisp function (a lambda
@@ -332,19 +329,17 @@ or just
332 The built-in function @code{indirect-function} provides an easy way to 329 The built-in function @code{indirect-function} provides an easy way to
333perform symbol function indirection explicitly. 330perform symbol function indirection explicitly.
334 331
335@defun indirect-function function &optional noerror 332@defun indirect-function function
336@anchor{Definition of indirect-function} 333@anchor{Definition of indirect-function}
337This function returns the meaning of @var{function} as a function. If 334This function returns the meaning of @var{function} as a function. If
338@var{function} is a symbol, then it finds @var{function}'s function 335@var{function} is a symbol, then it finds @var{function}'s function
339definition and starts over with that value. If @var{function} is not a 336definition and starts over with that value. If @var{function} is not a
340symbol, then it returns @var{function} itself. 337symbol, then it returns @var{function} itself.
341 338
342This function returns @code{nil} if the final symbol is unbound. It 339This function returns @code{nil} if the final symbol is unbound.
343signals a @code{cyclic-function-indirection} error if there is a loop
344in the chain of symbols.
345 340
346The optional argument @var{noerror} is obsolete, kept for backward 341There is also a second, optional argument that is obsolete and has no
347compatibility, and has no effect. 342effect.
348 343
349Here is how you could define @code{indirect-function} in Lisp: 344Here is how you could define @code{indirect-function} in Lisp:
350 345
diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi
index f5572e447d3..b6a4ee13308 100644
--- a/doc/lispref/functions.texi
+++ b/doc/lispref/functions.texi
@@ -737,9 +737,12 @@ explicitly in the source file being loaded. This is because
737By contrast, in programs that manipulate function definitions for other 737By contrast, in programs that manipulate function definitions for other
738purposes, it is better to use @code{fset}, which does not keep such 738purposes, it is better to use @code{fset}, which does not keep such
739records. @xref{Function Cells}. 739records. @xref{Function Cells}.
740
741If the resulting function definition chain would be circular, then
742Emacs will signal a @code{cyclic-function-indirection} error.
740@end defun 743@end defun
741 744
742@defun function-alias-p object &optional noerror 745@defun function-alias-p object
743Checks whether @var{object} is a function alias. If it is, it returns 746Checks whether @var{object} is a function alias. If it is, it returns
744a list of symbols representing the function alias chain, else 747a list of symbols representing the function alias chain, else
745@code{nil}. For instance, if @code{a} is an alias for @code{b}, and 748@code{nil}. For instance, if @code{a} is an alias for @code{b}, and
@@ -750,9 +753,8 @@ a list of symbols representing the function alias chain, else
750 @result{} (b c) 753 @result{} (b c)
751@end example 754@end example
752 755
753If there's a loop in the definitions, an error will be signaled. If 756There is also a second, optional argument that is obsolete and has no
754@var{noerror} is non-@code{nil}, the non-looping parts of the chain is 757effect.
755returned instead.
756@end defun 758@end defun
757 759
758 You cannot create a new primitive function with @code{defun} or 760 You cannot create a new primitive function with @code{defun} or
@@ -1539,6 +1541,9 @@ is not a function, e.g., a keyboard macro (@pxref{Keyboard Macros}):
1539If you wish to use @code{fset} to make an alternate name for a 1541If you wish to use @code{fset} to make an alternate name for a
1540function, consider using @code{defalias} instead. @xref{Definition of 1542function, consider using @code{defalias} instead. @xref{Definition of
1541defalias}. 1543defalias}.
1544
1545If the resulting function definition chain would be circular, then
1546Emacs will signal a @code{cyclic-function-indirection} error.
1542@end defun 1547@end defun
1543 1548
1544@node Closures 1549@node Closures
diff --git a/etc/NEWS b/etc/NEWS
index bcce416ebc1..4b0e4e6bd46 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -327,6 +327,21 @@ This function is like 'user-uid', but is aware of file name handlers,
327so it will return the remote UID for remote files (or -1 if the 327so it will return the remote UID for remote files (or -1 if the
328connection has no associated user). 328connection has no associated user).
329 329
330+++
331** 'fset' and 'defalias' now signal an error for circular alias chains.
332Previously, 'fset' and 'defalias' could be made to build circular
333function indirection chains as in
334
335 (defalias 'able 'baker)
336 (defalias 'baker 'able)
337
338but trying to call them would often make Emacs hang. Now, an attempt
339to create such a loop results in an error.
340
341Since circular alias chains now cannot occur, 'function-alias-p' and
342'indirect-function' will never signal an error. Their second
343'noerror' arguments have no effect and are therefore obsolete.
344
330 345
331* Changes in Emacs 30.1 on Non-Free Operating Systems 346* Changes in Emacs 30.1 on Non-Free Operating Systems
332 347
diff --git a/lisp/help-fns.el b/lisp/help-fns.el
index 8bf8af73d30..1172f068934 100644
--- a/lisp/help-fns.el
+++ b/lisp/help-fns.el
@@ -996,7 +996,7 @@ Returns a list of the form (REAL-FUNCTION DEF ALIASED REAL-DEF)."
996 (symbol-name function))))))) 996 (symbol-name function)))))))
997 (real-def (cond 997 (real-def (cond
998 ((and aliased (not (subrp def))) 998 ((and aliased (not (subrp def)))
999 (car (function-alias-p real-function t))) 999 (car (function-alias-p real-function)))
1000 ((subrp def) (intern (subr-name def))) 1000 ((subrp def) (intern (subr-name def)))
1001 (t def)))) 1001 (t def))))
1002 1002
diff --git a/lisp/subr.el b/lisp/subr.el
index 1a4ecc08931..916b6de494b 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -7002,27 +7002,17 @@ string will be displayed only if BODY takes longer than TIMEOUT seconds.
7002 (lambda () 7002 (lambda ()
7003 ,@body))) 7003 ,@body)))
7004 7004
7005(defun function-alias-p (func &optional noerror) 7005(defun function-alias-p (func &optional _noerror)
7006 "Return nil if FUNC is not a function alias. 7006 "Return nil if FUNC is not a function alias.
7007If FUNC is a function alias, return the function alias chain. 7007If FUNC is a function alias, return the function alias chain."
7008 7008 (declare (advertised-calling-convention (func) "30.1")
7009If the function alias chain contains loops, an error will be 7009 (side-effect-free error-free))
7010signaled. If NOERROR, the non-loop parts of the chain is returned." 7010 (let ((chain nil))
7011 (declare (side-effect-free t)) 7011 (while (and (symbolp func)
7012 (let ((chain nil) 7012 (setq func (symbol-function func))
7013 (orig-func func)) 7013 (symbolp func))
7014 (nreverse 7014 (push func chain))
7015 (catch 'loop 7015 (nreverse chain)))
7016 (while (and (symbolp func)
7017 (setq func (symbol-function func))
7018 (symbolp func))
7019 (when (or (memq func chain)
7020 (eq func orig-func))
7021 (if noerror
7022 (throw 'loop chain)
7023 (signal 'cyclic-function-indirection (list orig-func))))
7024 (push func chain))
7025 chain))))
7026 7016
7027(defun readablep (object) 7017(defun readablep (object)
7028 "Say whether OBJECT has a readable syntax. 7018 "Say whether OBJECT has a readable syntax.
diff --git a/src/data.c b/src/data.c
index 1fa8b0358b5..d2f4d40d7bc 100644
--- a/src/data.c
+++ b/src/data.c
@@ -840,7 +840,9 @@ the position will be taken. */)
840} 840}
841 841
842DEFUN ("fset", Ffset, Sfset, 2, 2, 0, 842DEFUN ("fset", Ffset, Sfset, 2, 2, 0,
843 doc: /* Set SYMBOL's function definition to DEFINITION, and return DEFINITION. */) 843 doc: /* Set SYMBOL's function definition to DEFINITION, and return DEFINITION.
844If the resulting chain of function definitions would contain a loop,
845signal a `cyclic-function-indirection' error. */)
844 (register Lisp_Object symbol, Lisp_Object definition) 846 (register Lisp_Object symbol, Lisp_Object definition)
845{ 847{
846 CHECK_SYMBOL (symbol); 848 CHECK_SYMBOL (symbol);
@@ -852,6 +854,12 @@ DEFUN ("fset", Ffset, Sfset, 2, 2, 0,
852 854
853 eassert (valid_lisp_object_p (definition)); 855 eassert (valid_lisp_object_p (definition));
854 856
857 /* Ensure non-circularity. */
858 for (Lisp_Object s = definition; SYMBOLP (s) && !NILP (s);
859 s = XSYMBOL (s)->u.s.function)
860 if (EQ (s, symbol))
861 xsignal1 (Qcyclic_function_indirection, symbol);
862
855#ifdef HAVE_NATIVE_COMP 863#ifdef HAVE_NATIVE_COMP
856 register Lisp_Object function = XSYMBOL (symbol)->u.s.function; 864 register Lisp_Object function = XSYMBOL (symbol)->u.s.function;
857 865
@@ -1078,7 +1086,7 @@ If CMD is not a command, the return value is nil.
1078Value, if non-nil, is a list (interactive SPEC). */) 1086Value, if non-nil, is a list (interactive SPEC). */)
1079 (Lisp_Object cmd) 1087 (Lisp_Object cmd)
1080{ 1088{
1081 Lisp_Object fun = indirect_function (cmd); /* Check cycles. */ 1089 Lisp_Object fun = indirect_function (cmd);
1082 bool genfun = false; 1090 bool genfun = false;
1083 1091
1084 if (NILP (fun)) 1092 if (NILP (fun))
@@ -1168,7 +1176,7 @@ If COMMAND is not a command, the return value is nil.
1168The value, if non-nil, is a list of mode name symbols. */) 1176The value, if non-nil, is a list of mode name symbols. */)
1169 (Lisp_Object command) 1177 (Lisp_Object command)
1170{ 1178{
1171 Lisp_Object fun = indirect_function (command); /* Check cycles. */ 1179 Lisp_Object fun = indirect_function (command);
1172 1180
1173 if (NILP (fun)) 1181 if (NILP (fun))
1174 return Qnil; 1182 return Qnil;
@@ -2482,55 +2490,22 @@ If the current binding is global (the default), the value is nil. */)
2482 2490
2483/* If OBJECT is a symbol, find the end of its function chain and 2491/* If OBJECT is a symbol, find the end of its function chain and
2484 return the value found there. If OBJECT is not a symbol, just 2492 return the value found there. If OBJECT is not a symbol, just
2485 return it. If there is a cycle in the function chain, signal a 2493 return it. */
2486 cyclic-function-indirection error.
2487
2488 This is like Findirect_function, except that it doesn't signal an
2489 error if the chain ends up unbound. */
2490Lisp_Object 2494Lisp_Object
2491indirect_function (register Lisp_Object object) 2495indirect_function (Lisp_Object object)
2492{ 2496{
2493 Lisp_Object tortoise, hare; 2497 while (SYMBOLP (object) && !NILP (object))
2494 2498 object = XSYMBOL (object)->u.s.function;
2495 hare = tortoise = object; 2499 return object;
2496
2497 for (;;)
2498 {
2499 if (!SYMBOLP (hare) || NILP (hare))
2500 break;
2501 hare = XSYMBOL (hare)->u.s.function;
2502 if (!SYMBOLP (hare) || NILP (hare))
2503 break;
2504 hare = XSYMBOL (hare)->u.s.function;
2505
2506 tortoise = XSYMBOL (tortoise)->u.s.function;
2507
2508 if (EQ (hare, tortoise))
2509 xsignal1 (Qcyclic_function_indirection, object);
2510 }
2511
2512 return hare;
2513} 2500}
2514 2501
2515DEFUN ("indirect-function", Findirect_function, Sindirect_function, 1, 2, 0, 2502DEFUN ("indirect-function", Findirect_function, Sindirect_function, 1, 2, 0,
2516 doc: /* Return the function at the end of OBJECT's function chain. 2503 doc: /* Return the function at the end of OBJECT's function chain.
2517If OBJECT is not a symbol, just return it. Otherwise, follow all 2504If OBJECT is not a symbol, just return it. Otherwise, follow all
2518function indirections to find the final function binding and return it. 2505function indirections to find the final function binding and return it. */)
2519Signal a cyclic-function-indirection error if there is a loop in the 2506 (Lisp_Object object, Lisp_Object noerror)
2520function chain of symbols. */)
2521 (register Lisp_Object object, Lisp_Object noerror)
2522{ 2507{
2523 Lisp_Object result; 2508 return indirect_function (object);
2524
2525 /* Optimize for no indirection. */
2526 result = object;
2527 if (SYMBOLP (result) && !NILP (result)
2528 && (result = XSYMBOL (result)->u.s.function, SYMBOLP (result)))
2529 result = indirect_function (result);
2530 if (!NILP (result))
2531 return result;
2532
2533 return Qnil;
2534} 2509}
2535 2510
2536/* Extract and set vector and string elements. */ 2511/* Extract and set vector and string elements. */
diff --git a/src/eval.c b/src/eval.c
index e377e30c6fb..eb40c953f96 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -2116,7 +2116,7 @@ then strings and vectors are not accepted. */)
2116 2116
2117 fun = function; 2117 fun = function;
2118 2118
2119 fun = indirect_function (fun); /* Check cycles. */ 2119 fun = indirect_function (fun);
2120 if (NILP (fun)) 2120 if (NILP (fun))
2121 return Qnil; 2121 return Qnil;
2122 2122
diff --git a/test/lisp/help-fns-tests.el b/test/lisp/help-fns-tests.el
index 4d715cde1d5..243a45ae6d2 100644
--- a/test/lisp/help-fns-tests.el
+++ b/test/lisp/help-fns-tests.el
@@ -181,10 +181,6 @@ Return first line of the output of (describe-function-1 FUNC)."
181(ert-deftest help-fns--analyze-function-recursive () 181(ert-deftest help-fns--analyze-function-recursive ()
182 (defalias 'help-fns--a 'help-fns--b) 182 (defalias 'help-fns--a 'help-fns--b)
183 (should (equal (help-fns--analyze-function 'help-fns--a) 183 (should (equal (help-fns--analyze-function 'help-fns--a)
184 '(help-fns--a help-fns--b t help-fns--b)))
185 ;; Make a loop and see that it doesn't infloop.
186 (defalias 'help-fns--b 'help-fns--a)
187 (should (equal (help-fns--analyze-function 'help-fns--a)
188 '(help-fns--a help-fns--b t help-fns--b)))) 184 '(help-fns--a help-fns--b t help-fns--b))))
189 185
190;;; help-fns-tests.el ends here 186;;; help-fns-tests.el ends here
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el
index 1abd3be4ea1..d5efabc1370 100644
--- a/test/lisp/subr-tests.el
+++ b/test/lisp/subr-tests.el
@@ -1058,10 +1058,12 @@ final or penultimate step during initialization."))
1058 '(subr-tests--b subr-tests--c))) 1058 '(subr-tests--b subr-tests--c)))
1059 1059
1060 (defalias 'subr-tests--d 'subr-tests--e) 1060 (defalias 'subr-tests--d 'subr-tests--e)
1061 (defalias 'subr-tests--e 'subr-tests--d) 1061 (should (equal (function-alias-p 'subr-tests--d)
1062 (should-error (function-alias-p 'subr-tests--d)) 1062 '(subr-tests--e)))
1063 (should (equal (function-alias-p 'subr-tests--d t) 1063
1064 '(subr-tests--e)))) 1064 (fset 'subr-tests--f 'subr-tests--a)
1065 (should (equal (function-alias-p 'subr-tests--f)
1066 '(subr-tests--a subr-tests--b subr-tests--c))))
1065 1067
1066(ert-deftest test-readablep () 1068(ert-deftest test-readablep ()
1067 (should (readablep "foo")) 1069 (should (readablep "foo"))
diff --git a/test/src/data-tests.el b/test/src/data-tests.el
index 28cee9d2c5b..680fdd57d71 100644
--- a/test/src/data-tests.el
+++ b/test/src/data-tests.el
@@ -772,4 +772,40 @@ comparing the subr with a much slower Lisp implementation."
772 "Can't set variable marked with 'make_symbol_constant'." 772 "Can't set variable marked with 'make_symbol_constant'."
773 (should-error (setq most-positive-fixnum 1) :type 'setting-constant)) 773 (should-error (setq most-positive-fixnum 1) :type 'setting-constant))
774 774
775(ert-deftest data-tests-fset ()
776 (fset 'data-tests--fs-fun (lambda () 'moo))
777 (declare-function data-tests--fs-fun nil)
778 (should (equal (data-tests--fs-fun) 'moo))
779
780 (fset 'data-tests--fs-fun1 'data-tests--fs-fun)
781 (declare-function data-tests--fs-fun1 nil)
782 (should (equal (data-tests--fs-fun1) 'moo))
783
784 (fset 'data-tests--fs-a 'data-tests--fs-b)
785 (fset 'data-tests--fs-b 'data-tests--fs-c)
786
787 (should-error (fset 'data-tests--fs-c 'data-tests--fs-c)
788 :type 'cyclic-function-indirection)
789 (fset 'data-tests--fs-d 'data-tests--fs-a)
790 (should-error (fset 'data-tests--fs-c 'data-tests--fs-d)
791 :type 'cyclic-function-indirection))
792
793(ert-deftest data-tests-defalias ()
794 (defalias 'data-tests--da-fun (lambda () 'baa))
795 (declare-function data-tests--da-fun nil)
796 (should (equal (data-tests--da-fun) 'baa))
797
798 (defalias 'data-tests--da-fun1 'data-tests--da-fun)
799 (declare-function data-tests--da-fun1 nil)
800 (should (equal (data-tests--da-fun1) 'baa))
801
802 (defalias 'data-tests--da-a 'data-tests--da-b)
803 (defalias 'data-tests--da-b 'data-tests--da-c)
804
805 (should-error (defalias 'data-tests--da-c 'data-tests--da-c)
806 :type 'cyclic-function-indirection)
807 (defalias 'data-tests--da-d 'data-tests--da-a)
808 (should-error (defalias 'data-tests--da-c 'data-tests--da-d)
809 :type 'cyclic-function-indirection))
810
775;;; data-tests.el ends here 811;;; data-tests.el ends here