Subject: bug#75520: Circular code or data can hang Emacs unquittably

From: Eli Zaretskii <[email-masked]>
Date: Sun, 12 Jan 2025 20:43:25 +0200

> Date: Sun, 12 Jan 2025 17:08:24 +0000
> From:  Pip Cet via "Bug reports for GNU Emacs,
>  the Swiss army knife of text editors" <[email-masked]>
> 
> When evaluated in -Q, this code:
> 
> (funcall '(lambda () . #1=(t . #1#)))
> 
> causes an unquittable infloop.
> 
> IMHO, circular code isn't something we should support in any meaningful
> way: it's perfectly okay for it to have unexpected results, and throw
> any error message we can think of, or none, until I hit quit.  However,
> I do think the loops should be quittable.
> 
> The easiest way to ensure this is to move the maybe_quit call in
> eval_sub to the top of the function.  I mentioned this problem to Paul
> Eggert and he provided the patch below, which also fixes two other cases
> I'd overlooked.
> 
> There are similar issues with circular data: if Lisp code turns
> process-environment into a circular structure, for example, call_process
> will hang Emacs.  If this should be fixed, please let me know so I can
> create a separate bug report and look for similar issues.

Thanks, let's hear what others (CC'ed) think about this.

> >From a2015ba2f81c67b1a77054f0715ba3d643504a98 Mon Sep 17 00:00:00 2001
> From: Paul Eggert <[email-masked]>
> Date: Sun, 5 Jan 2025 17:30:18 -0800
> Subject: [PATCH] Some maybe_quit fixes in eval.c
> 
> * src/eval.c (skip_debugger):
> Use FOR_EACH_TAIL to avoid unquittable infloops.
> (eval_sub): Call maybe_quit earlier.
> (funcall_lambda): Use FOR_EACH_TAIL rather than calling
> maybe_quit each time through the loop.
> ---
>  src/eval.c | 30 ++++++++++++++++--------------
>  1 file changed, 16 insertions(+), 14 deletions(-)
> 
> diff --git a/src/eval.c b/src/eval.c
> index 3e899db4436..2fa60af90af 100644
> --- a/src/eval.c
> +++ b/src/eval.c
> @@ -2044,35 +2044,36 @@ wants_debugger (Lisp_Object list, Lisp_Object conditions)
>  static bool
>  skip_debugger (Lisp_Object conditions, Lisp_Object data)
>  {
> -  Lisp_Object tail;
> -  bool first_string = 1;
> +  Lisp_Object tail = Vdebug_ignored_errors;
> +  bool first_string = true;
>    Lisp_Object error_message;
>  
>    error_message = Qnil;
> -  for (tail = Vdebug_ignored_errors; CONSP (tail); tail = XCDR (tail))
> +
> +  FOR_EACH_TAIL (tail)
>      {
>        if (STRINGP (XCAR (tail)))
>  	{
>  	  if (first_string)
>  	    {
>  	      error_message = Ferror_message_string (data);
> -	      first_string = 0;
> +	      first_string = false;
>  	    }
>  
>  	  if (fast_string_match (XCAR (tail), error_message) >= 0)
> -	    return 1;
> +	    return true;
>  	}
>        else
>  	{
> -	  Lisp_Object contail;
> +	  Lisp_Object contail = conditions;
>  
> -	  for (contail = conditions; CONSP (contail); contail = XCDR (contail))
> +	  FOR_EACH_TAIL (contail)
>  	    if (EQ (XCAR (tail), XCAR (contail)))
> -	      return 1;
> +	      return true;
>  	}
>      }
>  
> -  return 0;
> +  return false;
>  }
>  
>  /* Say whether SIGNAL is a `quit' error (or inherits from it).  */
> @@ -2470,6 +2471,11 @@ grow_specpdl_allocation (void)
>  Lisp_Object
>  eval_sub (Lisp_Object form)
>  {
> +  /* Call maybe_quit now, rather than just before calling maybe_gc, so
> +     that callers like Fand, Fprogn and Fsetq need not call maybe_quit
> +     each time through their loops.  */
> +  maybe_quit ();
> +
>    if (SYMBOLP (form))
>      {
>        /* Look up its binding in the lexical environment.
> @@ -2483,8 +2489,6 @@ eval_sub (Lisp_Object form)
>    if (!CONSP (form))
>      return form;
>  
> -  maybe_quit ();
> -
>    maybe_gc ();
>  
>    if (++lisp_eval_depth > max_lisp_eval_depth)
> @@ -3262,10 +3266,8 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, Lisp_Object *arg_vector)
>    bool optional = false;
>    bool rest = false;
>    bool previous_rest = false;
> -  for (; CONSP (syms_left); syms_left = XCDR (syms_left))
> +  FOR_EACH_TAIL (syms_left)
>      {
> -      maybe_quit ();
> -
>        Lisp_Object next = maybe_remove_pos_from_symbol (XCAR (syms_left));
>        if (!BARE_SYMBOL_P (next))
>  	xsignal1 (Qinvalid_function, fun);
> -- 
> 2.45.2
> 
> 
> 
> 
> 
>