If a funsub declares a local OPTIND, the calling context's getopts state
gets messed up.

For example, the following loop will not terminate:

    OPTIND=1; while getopts ab opt -ab; do
        echo $opt ${ local OPTIND; }
    done
From f7f16f70cf0ef623541ff3b6a36cff10b4933914 Mon Sep 17 00:00:00 2001
From: Grisha Levit <grishale...@gmail.com>
Date: Tue, 28 Nov 2023 01:13:12 -0500
Subject: [PATCH] restore getopt state after funsub

---
 execute_cmd.c | 2 +-
 execute_cmd.h | 1 +
 subst.c       | 8 ++++++++
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/execute_cmd.c b/execute_cmd.c
index 56707b98..ae20a4cb 100644
--- a/execute_cmd.c
+++ b/execute_cmd.c
@@ -5035,7 +5035,7 @@ execute_builtin (sh_builtin_func_t *builtin, WORD_LIST *words, int flags, int su
   return (result);
 }
 
-static void
+void
 uw_maybe_restore_getopt_state (void *arg)
 {
   sh_getopt_state_t *gs;
diff --git a/execute_cmd.h b/execute_cmd.h
index f8004342..97a70509 100644
--- a/execute_cmd.h
+++ b/execute_cmd.h
@@ -121,6 +121,7 @@ extern void restore_funcarray_state (struct func_array_state *);
 extern void uw_restore_funcarray_state (void *);
 #endif
 
+extern void uw_maybe_restore_getopt_state (void *);
 extern void uw_lastpipe_cleanup (void *);
 
 extern void bind_lastarg (char *);
diff --git a/subst.c b/subst.c
index aa7f2f56..019e6aa9 100644
--- a/subst.c
+++ b/subst.c
@@ -6867,6 +6867,8 @@ function_substitute (char *string, int quoted, int flags)
   int afd;
   char *afn;
   sigset_t set, oset;
+  sh_getopt_state_t *gs;
+  SHELL_VAR *gv;
 #if defined (ARRAY_VARS)
   ARRAY *ps;
 #endif
@@ -6895,8 +6897,10 @@ function_substitute (char *string, int quoted, int flags)
 	  exp_jump_to_top_level (DISCARD);		/* XXX */
 	}
     }
+  gs = sh_getopt_save_istate ();
 
   begin_unwind_frame ("nofork comsub");
+  add_unwind_protect (uw_maybe_restore_getopt_state, gs);
 
   /* Save command and expansion state we need. */
   if (valsub == 0)
@@ -7024,6 +7028,10 @@ function_substitute (char *string, int quoted, int flags)
       istring = s ? comsub_quote_string (s, quoted, flags) : savestring ("");
     }
 
+  gv = find_variable ("OPTIND");
+  if (gv && gv->context == variable_context)
+    gs->gs_flags |= 1;
+
   run_unwind_frame ("nofork comsub");	/* restores stdout, job control stuff */
 
   last_command_subst_status = result;
-- 
2.43.0

Reply via email to