]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - arrayfunc.c
Bash-5.2 patch 26: fix typo when specifying readline's custom color prefix
[thirdparty/bash.git] / arrayfunc.c
index 658b5aa51d5ee08db50e918d55c21edfb022176c..412562276df315057549bcf89ef48c409c44bea0 100644 (file)
@@ -1,6 +1,6 @@
 /* arrayfunc.c -- High-level array functions used by other parts of the shell. */
 
-/* Copyright (C) 2001-2011 Free Software Foundation, Inc.
+/* Copyright (C) 2001-2021 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
 #include "bashintl.h"
 
 #include "shell.h"
+#include "execute_cmd.h"
 #include "pathexp.h"
 
 #include "shmbutil.h"
+#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR)
+#  include <mbstr.h>           /* mbschr */
+#endif
 
 #include "builtins/common.h"
 
-extern char *this_command_name;
-extern int last_command_exit_value;
-extern int array_needs_making;
+#ifndef LBRACK
+#  define LBRACK '['
+#  define RBRACK ']'
+#endif
+
+/* This variable means to not expand associative array subscripts more than
+   once, when performing variable expansion. */
+int assoc_expand_once = 0;
+
+/* Ditto for indexed array subscripts -- currently unused */
+int array_expand_once = 0;
 
-static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, char *, int));
-static SHELL_VAR *assign_array_element_internal __P((SHELL_VAR *, char *, char *, char *, int, char *, int));
+static SHELL_VAR *bind_array_var_internal PARAMS((SHELL_VAR *, arrayind_t, char *, char *, int));
+static SHELL_VAR *assign_array_element_internal PARAMS((SHELL_VAR *, char *, char *, char *, int, char *, int, array_eltstate_t *));
 
-static char *quote_assign __P((const char *));
-static void quote_array_assignment_chars __P((WORD_LIST *));
-static char *array_value_internal __P((char *, int, int, int *, arrayind_t *));
+static void assign_assoc_from_kvlist PARAMS((SHELL_VAR *, WORD_LIST *, HASH_TABLE *, int));
+
+static char *quote_assign PARAMS((const char *));
+static void quote_array_assignment_chars PARAMS((WORD_LIST *));
+static char *quote_compound_array_word PARAMS((char *, int));
+static char *array_value_internal PARAMS((const char *, int, int, array_eltstate_t *));
 
 /* Standard error message to use when encountering an invalid array subscript */
 const char * const bash_badsub_errmsg = N_("bad array subscript");
@@ -82,7 +97,14 @@ convert_var_to_array (var)
     array_needs_making++;
 
   VSETATTR (var, att_array);
-  VUNSETATTR (var, att_invisible);
+  if (oldval)
+    VUNSETATTR (var, att_invisible);
+
+  /* Make sure it's not marked as an associative array any more */
+  VUNSETATTR (var, att_assoc);
+
+  /* Since namerefs can't be array variables, turn off nameref attribute */
+  VUNSETATTR (var, att_nameref);
 
   return var;
 }
@@ -113,7 +135,14 @@ convert_var_to_assoc (var)
     array_needs_making++;
 
   VSETATTR (var, att_assoc);
-  VUNSETATTR (var, att_invisible);
+  if (oldval)
+    VUNSETATTR (var, att_invisible);
+
+  /* Make sure it's not marked as an indexed array any more */
+  VUNSETATTR (var, att_array);
+
+  /* Since namerefs can't be array variables, turn off nameref attribute */
+  VUNSETATTR (var, att_nameref);
 
   return var;
 }
@@ -158,7 +187,41 @@ make_array_variable_value (entry, ind, key, value, flags)
 
   return newval;
 }
-  
+
+/* Assign HASH[KEY]=VALUE according to FLAGS. ENTRY is an associative array
+   variable; HASH is the hash table to assign into. HASH may or may not be
+   the hash table associated with ENTRY; if it's not, the caller takes care
+   of it.
+   XXX - make sure that any dynamic associative array variables recreate the
+   hash table on each assignment. BASH_CMDS and BASH_ALIASES already do this */
+static SHELL_VAR *
+bind_assoc_var_internal (entry, hash, key, value, flags)
+     SHELL_VAR *entry;
+     HASH_TABLE *hash;
+     char *key;
+     char *value;
+     int flags;
+{
+  char *newval;
+
+  /* Use the existing array contents to expand the value */
+  newval = make_array_variable_value (entry, 0, key, value, flags);
+
+  if (entry->assign_func)
+    (*entry->assign_func) (entry, newval, 0, key);
+  else
+    assoc_insert (hash, key, newval);
+
+  FREE (newval);
+
+  VUNSETATTR (entry, att_invisible);   /* no longer invisible */
+
+  /* check mark_modified_variables if we ever want to export array vars */
+  return (entry);
+}
+
+/* Perform ENTRY[IND]=VALUE or ENTRY[KEY]=VALUE. This is not called for every
+   assignment to an associative array; see assign_compound_array_list below. */
 static SHELL_VAR *
 bind_array_var_internal (entry, ind, key, value, flags)
      SHELL_VAR *entry;
@@ -180,6 +243,8 @@ bind_array_var_internal (entry, ind, key, value, flags)
   FREE (newval);
 
   VUNSETATTR (entry, att_invisible);   /* no longer invisible */
+
+  /* check mark_modified_variables if we ever want to export array vars */
   return (entry);
 }
 
@@ -201,9 +266,18 @@ bind_array_variable (name, ind, value, flags)
 
   entry = find_shell_variable (name);
 
+  if (entry == (SHELL_VAR *) 0)
+    {
+      /* Is NAME a nameref variable that points to an unset variable? */
+      entry = find_variable_nameref_for_create (name, 0);
+      if (entry == INVALID_NAMEREF_VALUE)
+       return ((SHELL_VAR *)0);
+      if (entry && nameref_p (entry))
+       entry = make_new_array_variable (nameref_cell (entry));
+    }
   if (entry == (SHELL_VAR *) 0)
     entry = make_new_array_variable (name);
-  else if (readonly_p (entry) || noassign_p (entry))
+  else if ((readonly_p (entry) && (flags&ASS_FORCE) == 0) || noassign_p (entry))
     {
       if (readonly_p (entry))
        err_readonly (name);
@@ -234,51 +308,91 @@ bind_assoc_variable (entry, name, key, value, flags)
      char *value;
      int flags;
 {
-  SHELL_VAR *dentry;
-  char *newval;
-
-  if (readonly_p (entry) || noassign_p (entry))
+  if ((readonly_p (entry) && (flags&ASS_FORCE) == 0) || noassign_p (entry))
     {
       if (readonly_p (entry))
        err_readonly (name);
       return (entry);
     }
 
-  return (bind_array_var_internal (entry, 0, key, value, flags));
+  return (bind_assoc_var_internal (entry, assoc_cell (entry), key, value, flags));
+}
+
+inline void
+init_eltstate (array_eltstate_t *estatep)
+{
+  if (estatep)
+    {
+      estatep->type = ARRAY_INVALID;
+      estatep->subtype = 0;
+      estatep->key = estatep->value = 0;
+      estatep->ind = INTMAX_MIN;
+    }
+}
+
+inline void
+flush_eltstate (array_eltstate_t *estatep)
+{
+  if (estatep)
+    FREE (estatep->key);
 }
 
 /* Parse NAME, a lhs of an assignment statement of the form v[s], and
-   assign VALUE to that array element by calling bind_array_variable(). */
+   assign VALUE to that array element by calling bind_array_variable().
+   Flags are ASS_ assignment flags */
 SHELL_VAR *
-assign_array_element (name, value, flags)
+assign_array_element (name, value, flags, estatep)
      char *name, *value;
      int flags;
+     array_eltstate_t *estatep;
 {
   char *sub, *vname;
-  int sublen;
+  int sublen, isassoc, avflags;
   SHELL_VAR *entry;
 
-  vname = array_variable_name (name, &sub, &sublen);
+  avflags = 0;
+  if (flags & ASS_NOEXPAND)
+    avflags |= AV_NOEXPAND;
+  if (flags & ASS_ONEWORD)
+    avflags |= AV_ONEWORD;
+  vname = array_variable_name (name, avflags, &sub, &sublen);
 
   if (vname == 0)
     return ((SHELL_VAR *)NULL);
 
-  if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1))
+  entry = find_variable (vname);
+  isassoc = entry && assoc_p (entry);
+
+  /* We don't allow assignment to `*' or `@' associative array keys if the
+     caller hasn't told us the subscript has already been expanded
+     (ASS_NOEXPAND). If the caller has explicitly told us it's ok
+     (ASS_ALLOWALLSUB) we allow it. */
+  if (((isassoc == 0 || (flags & (ASS_NOEXPAND|ASS_ALLOWALLSUB)) == 0) &&
+       (ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']')) ||
+      (sublen <= 1) ||
+      (sub[sublen] != '\0'))           /* sanity check */
     {
       free (vname);
       err_badarraysub (name);
       return ((SHELL_VAR *)NULL);
     }
 
-  entry = find_variable (vname);
-  entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags);
+  entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags, estatep);
+
+#if ARRAY_EXPORT
+  if (entry && exported_p (entry))
+    {
+      INVALIDATE_EXPORTSTR (entry);
+      array_needs_making = 1;
+    }
+#endif
 
   free (vname);
   return entry;
 }
 
 static SHELL_VAR *
-assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
+assign_array_element_internal (entry, name, vname, sub, sublen, value, flags, estatep)
      SHELL_VAR *entry;
      char *name;               /* only used for error messages */
      char *vname;
@@ -286,14 +400,21 @@ assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
      int sublen;
      char *value;
      int flags;
+     array_eltstate_t *estatep;
 {
-  char *akey;
+  char *akey, *nkey;
   arrayind_t ind;
+  char *newval;
+
+  /* rely on the caller to initialize estatep */
 
   if (entry && assoc_p (entry))
     {
       sub[sublen-1] = '\0';
-      akey = expand_assignment_string_to_string (sub, 0);      /* [ */
+      if ((flags & ASS_NOEXPAND) == 0)
+       akey = expand_subscript_string (sub, 0);        /* [ */
+      else
+       akey = savestring (sub);
       sub[sublen-1] = ']';
       if (akey == 0 || *akey == 0)
        {
@@ -301,11 +422,19 @@ assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
          FREE (akey);
          return ((SHELL_VAR *)NULL);
        }
+      if (estatep)
+       nkey = savestring (akey);       /* assoc_insert/assoc_replace frees akey */
       entry = bind_assoc_variable (entry, vname, akey, value, flags);
+      if (estatep)
+       {
+         estatep->type = ARRAY_ASSOC;
+         estatep->key = nkey;
+         estatep->value = entry ? assoc_reference (assoc_cell (entry), nkey) : 0;
+       }
     }
   else
     {
-      ind = array_expand_index (entry, sub, sublen);
+      ind = array_expand_index (entry, sub, sublen, 0);
       /* negative subscripts to indexed arrays count back from end */
       if (entry && ind < 0)
        ind = (array_p (entry) ? array_max_index (array_cell (entry)) : 0) + 1 + ind;
@@ -315,6 +444,12 @@ assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
          return ((SHELL_VAR *)NULL);
        }
       entry = bind_array_variable (vname, ind, value, flags);
+      if (estatep)
+       {
+         estatep->type = ARRAY_INDEXED;
+         estatep->ind = ind;
+         estatep->value = entry ? array_reference (array_cell (entry), ind) : 0;
+       }
     }
 
   return (entry);
@@ -338,9 +473,21 @@ find_or_make_array_variable (name, flags)
     {
       /* See if we have a nameref pointing to a variable that hasn't been
         created yet. */
-      var = find_variable_last_nameref (name);
+      var = find_variable_last_nameref (name, 1);
+      if (var && nameref_p (var) && invisible_p (var))
+       {
+         internal_warning (_("%s: removing nameref attribute"), name);
+         VUNSETATTR (var, att_nameref);
+       }
       if (var && nameref_p (var))
-       var = (flags & 2) ? make_new_assoc_variable (nameref_cell (var)) : make_new_array_variable (nameref_cell (var));
+       {
+         if (valid_nameref_value (nameref_cell (var), 2) == 0)
+           {
+             sh_invalidid (nameref_cell (var));
+             return ((SHELL_VAR *)NULL);
+           }
+         var = (flags & 2) ? make_new_assoc_variable (nameref_cell (var)) : make_new_array_variable (nameref_cell (var));
+       }
     }
 
   if (var == 0)
@@ -353,10 +500,12 @@ find_or_make_array_variable (name, flags)
     }
   else if ((flags & 2) && array_p (var))
     {
-      last_command_exit_value = 1;
+      set_exit_status (EXECUTION_FAILURE);
       report_error (_("%s: cannot convert indexed to associative array"), name);
       return ((SHELL_VAR *)NULL);
     }
+  else if (flags & 2)
+    var = assoc_p (var) ? var : convert_var_to_assoc (var);
   else if (array_p (var) == 0 && assoc_p (var) == 0)
     var = convert_var_to_array (var);
 
@@ -400,10 +549,10 @@ assign_array_var_from_word_list (var, list, flags)
   i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
 
   for (l = list; l; l = l->next, i++)
-    if (var->assign_func)
-      (*var->assign_func) (var, l->word->word, i, 0);
-    else
-      array_insert (a, i, l->word->word);
+    bind_array_var_internal (var, i, 0, l->word->word, flags & ~ASS_APPEND);
+
+  VUNSETATTR (var, att_invisible);     /* no longer invisible */
+
   return var;
 }
 
@@ -414,7 +563,6 @@ expand_compound_array_assignment (var, value, flags)
      int flags;
 {
   WORD_LIST *list, *nlist;
-  WORD_LIST *hd, *tl, *t, *n;
   char *val;
   int ni;
 
@@ -435,8 +583,20 @@ expand_compound_array_assignment (var, value, flags)
      shell expansions including pathname generation and word splitting. */
   /* First we split the string on whitespace, using the shell parser
      (ksh93 seems to do this). */
+  /* XXX - this needs a rethink, maybe use split_at_delims */
   list = parse_string_to_word_list (val, 1, "array assign");
 
+  /* If the parser has quoted CTLESC and CTNLNUL with CTLESC in unquoted
+     words, we need to remove those here because the code below assumes
+     they are there because they exist in the original word. */
+  /* XXX - if we rethink parse_string_to_word_list above, change this. */
+  for (nlist = list; nlist; nlist = nlist->next)
+    if ((nlist->word->flags & W_QUOTED) == 0)
+      remove_quoted_escapes (nlist->word->word);
+
+  /* Note that we defer expansion of the assignment statements for associative
+     arrays here, so we don't have to scan the subscript and find the ending
+     bracket twice. See the caller below. */
   if (var && assoc_p (var))
     {
       if (val != value)
@@ -463,7 +623,78 @@ expand_compound_array_assignment (var, value, flags)
   return nlist;
 }
 
-/* Callers ensure that VAR is not NULL */
+#if ASSOC_KVPAIR_ASSIGNMENT
+static void
+assign_assoc_from_kvlist (var, nlist, h, flags)
+     SHELL_VAR *var;
+     WORD_LIST *nlist;
+     HASH_TABLE *h;
+     int flags;
+{
+  WORD_LIST *list;
+  char *akey, *aval, *k, *v;
+
+  for (list = nlist; list; list = list->next)
+    {
+      k = list->word->word;
+      v = list->next ? list->next->word->word : 0;
+
+      if (list->next)
+        list = list->next;
+
+      akey = expand_subscript_string (k, 0);
+      if (akey == 0 || *akey == 0)
+       {
+         err_badarraysub (k);
+         FREE (akey);
+         continue;
+       }             
+
+      aval = expand_assignment_string_to_string (v, 0);
+      if (aval == 0)
+       {
+         aval = (char *)xmalloc (1);
+         aval[0] = '\0';       /* like do_assignment_internal */
+       }
+
+      bind_assoc_var_internal (var, h, akey, aval, flags);
+      free (aval);
+    }
+}
+
+/* Return non-zero if L appears to be a key-value pair associative array
+   compound assignment. */ 
+int
+kvpair_assignment_p (l)
+     WORD_LIST *l;
+{
+  return (l && (l->word->flags & W_ASSIGNMENT) == 0 && l->word->word[0] != '[');       /*]*/
+}
+
+char *
+expand_and_quote_kvpair_word (w)
+     char *w;
+{
+  char *r, *s, *t;
+
+  t = w ? expand_subscript_string (w, 0) : 0;
+  s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t;
+  r = sh_single_quote (s ? s : "");
+  if (s != t)
+    free (s);
+  free (t);
+  return r;
+}
+#endif
+     
+/* Callers ensure that VAR is not NULL. Associative array assignments have not
+   been expanded when this is called, or have been expanded once and single-
+   quoted, so we don't have to scan through an unquoted expanded subscript to
+   find the ending bracket; indexed array assignments have been expanded and
+   possibly single-quoted to prevent further expansion.
+
+   If this is an associative array, we perform the assignments into NHASH and
+   set NHASH to be the value of VAR after processing the assignments in NLIST */
 void
 assign_compound_array_list (var, nlist, flags)
      SHELL_VAR *var;
@@ -471,15 +702,15 @@ assign_compound_array_list (var, nlist, flags)
      int flags;
 {
   ARRAY *a;
-  HASH_TABLE *h;
+  HASH_TABLE *h, *nhash;
   WORD_LIST *list;
-  char *w, *val, *nval;
+  char *w, *val, *nval, *savecmd;
   int len, iflags, free_val;
   arrayind_t ind, last_ind;
   char *akey;
 
   a = (var && array_p (var)) ? array_cell (var) : (ARRAY *)0;
-  h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0;
+  nhash = h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0;
 
   akey = (char *)0;
   ind = 0;
@@ -491,14 +722,31 @@ assign_compound_array_list (var, nlist, flags)
       if (a && array_p (var))
        array_flush (a);
       else if (h && assoc_p (var))
-       assoc_flush (h);
+       nhash = assoc_create (h->nbuckets);
     }
 
   last_ind = (a && (flags & ASS_APPEND)) ? array_max_index (a) + 1 : 0;
 
+#if ASSOC_KVPAIR_ASSIGNMENT
+  if (assoc_p (var) && kvpair_assignment_p (nlist))
+    {
+      iflags = flags & ~ASS_APPEND;
+      assign_assoc_from_kvlist (var, nlist, nhash, iflags);
+      if (nhash && nhash != h)
+       {
+         h = assoc_cell (var);
+         var_setassoc (var, nhash);
+         assoc_dispose (h);
+       }
+      return;
+    }
+#endif
+
   for (list = nlist; list; list = list->next)
     {
-      iflags = flags;
+      /* Don't allow var+=(values) to make assignments in VALUES append to
+        existing values by default. */
+      iflags = flags & ~ASS_APPEND;
       w = list->word->word;
 
       /* We have a word of the form [ind]=value */
@@ -533,19 +781,16 @@ assign_compound_array_list (var, nlist, flags)
              continue;
            }
 
-         if (ALL_ELEMENT_SUB (w[1]) && len == 2)
+         if (ALL_ELEMENT_SUB (w[1]) && len == 2 && array_p (var))
            {
-             last_command_exit_value = 1;
-             if (assoc_p (var))
-               report_error (_("%s: invalid associative array key"), w);
-             else
-               report_error (_("%s: cannot assign to non-numeric index"), w);
+             set_exit_status (EXECUTION_FAILURE);
+             report_error (_("%s: cannot assign to non-numeric index"), w);
              continue;
            }
 
          if (array_p (var))
            {
-             ind = array_expand_index (var, w + 1, len);
+             ind = array_expand_index (var, w + 1, len, 0);
              /* negative subscripts to indexed arrays count back from end */
              if (ind < 0)
                ind = array_max_index (array_cell (var)) + 1 + ind;
@@ -561,7 +806,7 @@ assign_compound_array_list (var, nlist, flags)
            {
              /* This is not performed above, see expand_compound_array_assignment */
              w[len] = '\0';    /*[*/
-             akey = expand_assignment_string_to_string (w+1, 0);
+             akey = expand_subscript_string (w+1, 0);
              w[len] = ']';
              /* And we need to expand the value also, see below */
              if (akey == 0 || *akey == 0)
@@ -583,7 +828,7 @@ assign_compound_array_list (var, nlist, flags)
        }
       else if (assoc_p (var))
        {
-         last_command_exit_value = 1;
+         set_exit_status (EXECUTION_FAILURE);
          report_error (_("%s: %s: must use subscript when assigning associative array"), var->name, w);
          continue;
        }
@@ -606,14 +851,26 @@ assign_compound_array_list (var, nlist, flags)
          free_val = 1;
        }
 
+      savecmd = this_command_name;
       if (integer_p (var))
        this_command_name = (char *)NULL;       /* no command name for errors */
-      bind_array_var_internal (var, ind, akey, val, iflags);
+      if (assoc_p (var))
+       bind_assoc_var_internal (var, nhash, akey, val, iflags);
+      else
+       bind_array_var_internal (var, ind, akey, val, iflags);
       last_ind++;
+      this_command_name = savecmd;
 
       if (free_val)
        free (val);
     }
+
+  if (assoc_p (var) && nhash && nhash != h)
+    {
+      h = assoc_cell (var);
+      var_setassoc (var, nhash);
+      assoc_dispose (h);
+    }
 }
 
 /* Perform a compound array assignment:  VAR->name=( VALUE ).  The
@@ -634,6 +891,10 @@ assign_array_var_from_string (var, value, flags)
 
   if (nlist)
     dispose_words (nlist);
+
+  if (var)
+    VUNSETATTR (var, att_invisible);   /* no longer invisible */
+
   return (var);
 }
 
@@ -683,6 +944,137 @@ quote_assign (string)
   return temp;
 }
 
+/* Take a word W of the form [IND]=VALUE and transform it to ['IND']='VALUE'
+   to prevent further expansion. This is called for compound assignments to
+   indexed arrays. W has already undergone word expansions. If W has no [IND]=,
+   just single-quote and return it. */
+static char *
+quote_compound_array_word (w, type)
+     char *w;
+     int type;
+{
+  char *nword, *sub, *value, *t;
+  int ind, wlen, i;
+
+  if (w[0] != LBRACK)
+    return (sh_single_quote (w));      /* XXX - quote CTLESC */
+  ind = skipsubscript (w, 0, 0);
+  if (w[ind] != RBRACK)
+    return (sh_single_quote (w));      /* XXX - quote CTLESC */
+
+  wlen = strlen (w);
+  w[ind] = '\0';
+  t = (strchr (w+1, CTLESC)) ? quote_escapes (w+1) : w+1;
+  sub = sh_single_quote (t);
+  if (t != w+1)
+   free (t);
+  w[ind] = RBRACK;
+
+  nword = xmalloc (wlen * 4 + 5);      /* wlen*4 is max single quoted length */
+  nword[0] = LBRACK;
+  i = STRLEN (sub);
+  memcpy (nword+1, sub, i);
+  free (sub);
+  i++;                         /* accommodate the opening LBRACK */
+  nword[i++] = w[ind++];       /* RBRACK */
+  if (w[ind] == '+')
+    nword[i++] = w[ind++];
+  nword[i++] = w[ind++];
+  t = (strchr (w+ind, CTLESC)) ? quote_escapes (w+ind) : w+ind;
+  value = sh_single_quote (t);
+  if (t != w+ind)
+   free (t);
+  strcpy (nword + i, value);
+
+  return nword;
+}
+
+/* Expand the key and value in W, which is of the form [KEY]=VALUE, and
+   reconstruct W with the expanded and single-quoted version:
+   ['expanded-key']='expanded-value'. If there is no [KEY]=, single-quote the
+   word and return it. Very similar to previous function, but does not assume
+   W has already been expanded, and expands the KEY and VALUE separately.
+   Used for compound assignments to associative arrays that are arguments to
+   declaration builtins (declare -A a=( list )). */
+char *
+expand_and_quote_assoc_word (w, type)
+     char *w;
+     int type;
+{
+  char *nword, *key, *value, *s, *t;
+  int ind, wlen, i;
+
+  if (w[0] != LBRACK)
+    return (sh_single_quote (w));      /* XXX - quote_escapes */
+  ind = skipsubscript (w, 0, 0);
+  if (w[ind] != RBRACK)
+    return (sh_single_quote (w));      /* XXX - quote_escapes */
+
+  w[ind] = '\0';
+  t = expand_subscript_string (w+1, 0);
+  s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t;
+  key = sh_single_quote (s ? s : "");
+  if (s != t)
+    free (s);
+  w[ind] = RBRACK;
+  free (t);
+
+  wlen = STRLEN (key);
+  nword = xmalloc (wlen + 5);
+  nword[0] = LBRACK;
+  memcpy (nword+1, key, wlen);
+  i = wlen + 1;                        /* accommodate the opening LBRACK */
+
+  nword[i++] = w[ind++];       /* RBRACK */
+  if (w[ind] == '+')
+    nword[i++] = w[ind++];
+  nword[i++] = w[ind++];
+
+  t = expand_assignment_string_to_string (w+ind, 0);
+  s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t;
+  value = sh_single_quote (s ? s : "");
+  if (s != t)
+    free (s);
+  free (t);
+  nword = xrealloc (nword, wlen + 5 + STRLEN (value));
+  strcpy (nword + i, value);
+
+  free (key);
+  free (value);
+
+  return nword;
+}
+
+/* For each word in a compound array assignment, if the word looks like
+   [ind]=value, single-quote ind and value, but leave the brackets and
+   the = sign (and any `+') alone. If it's not an assignment, just single-
+   quote the word. This is used for indexed arrays. */
+void
+quote_compound_array_list (list, type)
+     WORD_LIST *list;
+     int type;
+{
+  char *s, *t;
+  WORD_LIST *l;
+
+  for (l = list; l; l = l->next)
+    {
+      if (l->word == 0 || l->word->word == 0)
+       continue;       /* should not happen, but just in case... */
+      if ((l->word->flags & W_ASSIGNMENT) == 0)
+       {
+         s = (strchr (l->word->word, CTLESC)) ? quote_escapes (l->word->word) : l->word->word;
+         t = sh_single_quote (s);
+         if (s != l->word->word)
+           free (s);
+       }
+      else 
+       t = quote_compound_array_word (l->word->word, type);
+      free (l->word->word);
+      l->word->word = t;
+    }
+}
+
 /* For each word in a compound array assignment, if the word looks like
    [ind]=value, quote globbing chars and characters in $IFS before the `='. */
 static void
@@ -715,33 +1107,42 @@ quote_array_assignment_chars (list)
 /* This function is called with SUB pointing to just after the beginning
    `[' of an array subscript and removes the array element to which SUB
    expands from array VAR.  A subscript of `*' or `@' unsets the array. */
+/* If FLAGS&1 (VA_NOEXPAND) we don't expand the subscript; we just use it
+   as-is. If FLAGS&VA_ONEWORD, we don't try to use skipsubscript to parse
+   the subscript, we just assume the subscript ends with a close bracket,
+   if one is present, and use what's inside the brackets. */
 int
-unbind_array_element (var, sub)
+unbind_array_element (var, sub, flags)
      SHELL_VAR *var;
      char *sub;
+     int flags;
 {
-  int len;
   arrayind_t ind;
   char *akey;
   ARRAY_ELEMENT *ae;
 
-  len = skipsubscript (sub, 0, 0);
-  if (sub[len] != ']' || len == 0)
-    {
-      builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg));
-      return -1;
-    }
-  sub[len] = '\0';
+  /* Assume that the caller (unset_builtin) passes us a null-terminated SUB,
+     so we don't have to use VA_ONEWORD or parse the subscript again with
+     skipsubscript(). */
 
   if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
     {
-      unbind_variable (var->name);
-      return (0);
+      if (array_p (var) || assoc_p (var))
+       {
+         if (flags & VA_ALLOWALL)
+           {
+             unbind_variable (var->name);      /* XXX -- {array,assoc}_flush ? */
+             return (0);
+           }
+         /* otherwise we fall through and try to unset element `@' or `*' */
+       }
+      else
+       return -2;      /* don't allow this to unset scalar variables */
     }
 
   if (assoc_p (var))
     {
-      akey = expand_assignment_string_to_string (sub, 0);     /* [ */
+      akey = (flags & VA_NOEXPAND) ? sub : expand_subscript_string (sub, 0);
       if (akey == 0 || *akey == 0)
        {
          builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
@@ -749,11 +1150,33 @@ unbind_array_element (var, sub)
          return -1;
        }
       assoc_remove (assoc_cell (var), akey);
-      free (akey);
+      if (akey != sub)
+       free (akey);
     }
-  else
+  else if (array_p (var))
     {
-      ind = array_expand_index (var, sub, len+1);
+      if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
+       {
+         /* We can go several ways here:
+               1) remove the array (backwards compatible)
+               2) empty the array (new behavior)
+               3) do nothing; treat the `@' or `*' as an expression and throw
+                  an error
+         */
+         /* Behavior 1 */
+         if (shell_compatibility_level <= 51)
+           {
+             unbind_variable (name_cell (var));
+             return 0;
+           }
+         else /* Behavior 2 */
+           {
+             array_flush (array_cell (var));
+             return 0;
+           }
+         /* Fall through for behavior 3 */
+       }
+      ind = array_expand_index (var, sub, strlen (sub) + 1, 0);
       /* negative subscripts to indexed arrays count back from end */
       if (ind < 0)
        ind = array_max_index (array_cell (var)) + 1 + ind;
@@ -766,6 +1189,19 @@ unbind_array_element (var, sub)
       if (ae)
        array_dispose_element (ae);
     }
+  else /* array_p (var) == 0 && assoc_p (var) == 0 */
+    {
+      akey = this_command_name;
+      ind = array_expand_index (var, sub, strlen (sub) + 1, 0);
+      this_command_name = akey;
+      if (ind == 0)
+       {
+         unbind_variable (var->name);
+         return (0);
+       }
+      else
+       return -2;      /* any subscript other than 0 is invalid with scalar variables */
+    }
 
   return 0;
 }
@@ -817,56 +1253,124 @@ print_assoc_assignment (var, quoted)
 /***********************************************************************/
 
 /* Return 1 if NAME is a properly-formed array reference v[sub]. */
+
+/* Return 1 if NAME is a properly-formed array reference v[sub]. */
+
+/* When NAME is a properly-formed array reference and a non-null argument SUBP
+   is supplied, '[' and ']' that enclose the subscript are replaced by '\0',
+   and the pointer to the subscript in NAME is assigned to *SUBP, so that NAME
+   and SUBP can be later used as the array name and the subscript,
+   respectively.  When SUBP is the null pointer, the original string NAME will
+   not be modified. */
+/* We need to reserve 1 for FLAGS, which we pass to skipsubscript. */
 int
-valid_array_reference (name)
+tokenize_array_reference (name, flags, subp)
      char *name;
+     int flags;
+     char **subp;
 {
   char *t;
-  int r, len;
+  int r, len, isassoc, ssflags;
+  SHELL_VAR *entry;
 
   t = mbschr (name, '[');      /* ] */
+  isassoc = 0;
   if (t)
     {
       *t = '\0';
       r = legal_identifier (name);
+      if (flags & VA_NOEXPAND) /* Don't waste a lookup if we don't need one */
+       isassoc = (entry = find_variable (name)) && assoc_p (entry);      
       *t = '[';
       if (r == 0)
        return 0;
-      /* Check for a properly-terminated non-blank subscript. */
-      len = skipsubscript (t, 0, 0);
-      if (t[len] != ']' || len == 1)
+
+      ssflags = 0;
+      if (isassoc && ((flags & (VA_NOEXPAND|VA_ONEWORD)) == (VA_NOEXPAND|VA_ONEWORD)))
+       len = strlen (t) - 1;
+      else if (isassoc)
+       {
+         if (flags & VA_NOEXPAND)
+           ssflags |= 1;
+         len = skipsubscript (t, 0, ssflags);
+       }
+      else
+       /* Check for a properly-terminated non-null subscript. */
+       len = skipsubscript (t, 0, 0);          /* arithmetic expression */
+
+      if (t[len] != ']' || len == 1 || t[len+1] != '\0')
        return 0;
+
+#if 0
+      /* Could check and allow subscripts consisting only of whitespace for
+        existing associative arrays, using isassoc */
       for (r = 1; r < len; r++)
        if (whitespace (t[r]) == 0)
-         return 1;
-      return 0;
+         break;
+      if (r == len)
+       return 0; /* Fail if the subscript contains only whitespaces. */
+#endif
+
+      if (subp)
+       {
+         t[0] = t[len] = '\0';
+         *subp = t + 1;
+       }
+
+      /* This allows blank subscripts */
+      return 1;
     }
   return 0;
 }
 
+/* Return 1 if NAME is a properly-formed array reference v[sub]. */
+
+/* We need to reserve 1 for FLAGS, which we pass to skipsubscript. */
+int
+valid_array_reference (name, flags)
+     const char *name;
+     int flags;
+{
+  return tokenize_array_reference ((char *)name, flags, (char **)NULL);
+}
+
 /* Expand the array index beginning at S and extending LEN characters. */
 arrayind_t
-array_expand_index (var, s, len)
+array_expand_index (var, s, len, flags)
      SHELL_VAR *var;
      char *s;
      int len;
+     int flags;
 {
-  char *exp, *t;
-  int expok;
+  char *exp, *t, *savecmd;
+  int expok, eflag;
   arrayind_t val;
 
   exp = (char *)xmalloc (len);
   strncpy (exp, s, len - 1);
   exp[len - 1] = '\0';
-  t = expand_arith_string (exp, 0);
+#if 0  /* TAG: maybe bash-5.2 */
+  if ((flags & AV_NOEXPAND) == 0)
+    t = expand_arith_string (exp, Q_DOUBLE_QUOTES|Q_ARITH|Q_ARRAYSUB); /* XXX - Q_ARRAYSUB for future use */
+  else
+    t = exp;
+#else
+  t = expand_arith_string (exp, Q_DOUBLE_QUOTES|Q_ARITH|Q_ARRAYSUB);   /* XXX - Q_ARRAYSUB for future use */
+#endif
+  savecmd = this_command_name;
   this_command_name = (char *)NULL;
-  val = evalexp (t, &expok);
-  free (t);
+  eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED;
+  val = evalexp (t, eflag, &expok);    /* XXX - was 0 but we expanded exp already */
+  this_command_name = savecmd;
+  if (t != exp)
+    free (t);
   free (exp);
   if (expok == 0)
     {
-      last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (EXECUTION_FAILURE);
 
+      if (no_longjmp_on_fatal_error)
+       return 0;
       top_level_cleanup ();      
       jump_to_top_level (DISCARD);
     }
@@ -878,12 +1382,14 @@ array_expand_index (var, s, len)
    in *SUBP. If LENP is non-null, the length of the subscript is returned
    in *LENP.  This returns newly-allocated memory. */
 char *
-array_variable_name (s, subp, lenp)
-     char *s, **subp;
+array_variable_name (s, flags, subp, lenp)
+     const char *s;
+     int flags;
+     char **subp;
      int *lenp;
 {
   char *t, *ret;
-  int ind, ni;
+  int ind, ni, ssflags;
 
   t = mbschr (s, '[');
   if (t == 0)
@@ -895,7 +1401,15 @@ array_variable_name (s, subp, lenp)
       return ((char *)NULL);
     }
   ind = t - s;
-  ni = skipsubscript (s, ind, 0);
+  if ((flags & (AV_NOEXPAND|AV_ONEWORD)) == (AV_NOEXPAND|AV_ONEWORD))
+    ni = strlen (s) - 1;
+  else
+    {
+      ssflags = 0;
+      if (flags & AV_NOEXPAND)
+       ssflags |= 1;
+      ni = skipsubscript (s, ind, ssflags);
+    }
   if (ni <= ind + 1 || s[ni] != ']')
     {
       err_badarraysub (s);
@@ -922,24 +1436,22 @@ array_variable_name (s, subp, lenp)
    non-null, return a pointer to the start of the subscript in *SUBP.
    If LENP is non-null, the length of the subscript is returned in *LENP. */
 SHELL_VAR *
-array_variable_part (s, subp, lenp)
-     char *s, **subp;
+array_variable_part (s, flags, subp, lenp)
+     const char *s;
+     int flags;
+     char **subp;
      int *lenp;
 {
   char *t;
   SHELL_VAR *var;
 
-  t = array_variable_name (s, subp, lenp);
+  t = array_variable_name (s, flags, subp, lenp);
   if (t == 0)
     return ((SHELL_VAR *)NULL);
-  var = find_variable (t);
+  var = find_variable (t);             /* XXX - handle namerefs here? */
 
   free (t);
-#if 0
-  return (var == 0 || invisible_p (var)) ? (SHELL_VAR *)0 : var;
-#else
   return var;  /* now return invisible variables; caller must handle */
-#endif
 }
 
 #define INDEX_ERROR() \
@@ -963,19 +1475,19 @@ array_variable_part (s, subp, lenp)
    is non-null it gets 1 if the array reference is name[*], 2 if the
    reference is name[@], and 0 otherwise. */
 static char *
-array_value_internal (s, quoted, flags, rtype, indp)
-     char *s;
-     int quoted, flags, *rtype;
-     arrayind_t *indp;
+array_value_internal (s, quoted, flags, estatep)
+     const char *s;
+     int quoted, flags;
+     array_eltstate_t *estatep;
 {
-  int len;
+  int len, isassoc, subtype;
   arrayind_t ind;
   char *akey;
   char *retval, *t, *temp;
   WORD_LIST *l;
   SHELL_VAR *var;
 
-  var = array_variable_part (s, &t, &len);
+  var = array_variable_part (s, flags, &t, &len);      /* XXX */
 
   /* Expand the index, even if the variable doesn't exist, in case side
      effects are needed, like ${w[i++]} where w is unset. */
@@ -987,54 +1499,73 @@ array_value_internal (s, quoted, flags, rtype, indp)
   if (len == 0)
     return ((char *)NULL);     /* error message already printed */
 
+  isassoc = var && assoc_p (var);
   /* [ */
   akey = 0;
-  if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+  subtype = 0;
+  if (estatep)
+    estatep->value = (char *)NULL;
+
+  /* Backwards compatibility: we only change the behavior of A[@] and A[*]
+     for associative arrays, and the caller has to request it. */
+  if ((isassoc == 0 || (flags & AV_ATSTARKEYS) == 0) && ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
     {
-      if (rtype)
-       *rtype = (t[0] == '*') ? 1 : 2;
+      if (estatep)
+       estatep->subtype = (t[0] == '*') ? 1 : 2;
       if ((flags & AV_ALLOWALL) == 0)
        {
          err_badarraysub (s);
          return ((char *)NULL);
        }
-      else if (var == 0 || value_cell (var) == 0)      /* XXX - check for invisible_p(var) ? */
+      else if (var == 0 || value_cell (var) == 0)
+       return ((char *)NULL);
+      else if (invisible_p (var))
        return ((char *)NULL);
       else if (array_p (var) == 0 && assoc_p (var) == 0)
-       l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
+        {
+          if (estatep)
+           estatep->type = ARRAY_SCALAR;
+         l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
+        }
       else if (assoc_p (var))
        {
+         if (estatep)
+           estatep->type = ARRAY_ASSOC;
          l = assoc_to_word_list (assoc_cell (var));
          if (l == (WORD_LIST *)NULL)
            return ((char *)NULL);
        }
       else
        {
+         if (estatep)
+           estatep->type = ARRAY_INDEXED;
          l = array_to_word_list (array_cell (var));
          if (l == (WORD_LIST *)NULL)
            return ((char *) NULL);
        }
 
+      /* Caller of array_value takes care of inspecting estatep->subtype and
+         duplicating retval if subtype == 0, so this is not a memory leak */
       if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        {
-         temp = string_list_dollar_star (l);
-         retval = quote_string (temp);         /* XXX - leak here */
+         temp = string_list_dollar_star (l, quoted, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0);
+         retval = quote_string (temp);
          free (temp);
        }
       else     /* ${name[@]} or unquoted ${name[*]} */
-       retval = string_list_dollar_at (l, quoted);     /* XXX - leak here */
+       retval = string_list_dollar_at (l, quoted, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0);
 
       dispose_words (l);
     }
   else
     {
-      if (rtype)
-       *rtype = 0;
+      if (estatep)
+       estatep->subtype = 0;
       if (var == 0 || array_p (var) || assoc_p (var) == 0)
        {
-         if ((flags & AV_USEIND) == 0 || indp == 0)
+         if ((flags & AV_USEIND) == 0 || estatep == 0)
            {
-             ind = array_expand_index (var, t, len);
+             ind = array_expand_index (var, t, len, flags);
              if (ind < 0)
                {
                  /* negative subscripts to indexed arrays count back from end */
@@ -1043,16 +1574,25 @@ array_value_internal (s, quoted, flags, rtype, indp)
                  if (ind < 0)
                    INDEX_ERROR();
                }
-             if (indp)
-               *indp = ind;
+             if (estatep)
+               estatep->ind = ind;
            }
-         else if (indp)
-           ind = *indp;
+         else if (estatep && (flags & AV_USEIND))
+           ind = estatep->ind;
+         if (estatep && var)
+           estatep->type = array_p (var) ? ARRAY_INDEXED : ARRAY_SCALAR;
        }
       else if (assoc_p (var))
        {
          t[len - 1] = '\0';
-         akey = expand_assignment_string_to_string (t, 0);     /* [ */
+         if (estatep)
+           estatep->type = ARRAY_ASSOC;
+         if ((flags & AV_USEIND) && estatep && estatep->key)
+           akey = savestring (estatep->key);
+         else if ((flags & AV_NOEXPAND) == 0)
+           akey = expand_subscript_string (t, 0);      /* [ */
+         else
+           akey = savestring (t);
          t[len - 1] = ']';
          if (akey == 0 || *akey == 0)
            {
@@ -1060,21 +1600,34 @@ array_value_internal (s, quoted, flags, rtype, indp)
              INDEX_ERROR();
            }
        }
-     
-      if (var == 0 || value_cell (var) == 0)   /* XXX - check invisible_p(var) ? */
+
+      if (var == 0 || value_cell (var) == 0)
+       {
+         FREE (akey);
+         return ((char *)NULL);
+       }
+      else if (invisible_p (var))
        {
-          FREE (akey);
+         FREE (akey);
          return ((char *)NULL);
        }
       if (array_p (var) == 0 && assoc_p (var) == 0)
-       return (ind == 0 ? value_cell (var) : (char *)NULL);
+       retval = (ind == 0) ? value_cell (var) : (char *)NULL;
       else if (assoc_p (var))
         {
          retval = assoc_reference (assoc_cell (var), akey);
-         free (akey);
+         if (estatep && estatep->key && (flags & AV_USEIND))
+           free (akey);                /* duplicated estatep->key */
+         else if (estatep)
+           estatep->key = akey;        /* XXX - caller must manage */
+         else                          /* not saving it anywhere */
+           free (akey);
         }
       else
        retval = array_reference (array_cell (var), ind);
+
+      if (estatep)
+       estatep->value = retval;
     }
 
   return retval;
@@ -1083,12 +1636,15 @@ array_value_internal (s, quoted, flags, rtype, indp)
 /* Return a string containing the elements described by the array and
    subscript contained in S, obeying quoting for subscripts * and @. */
 char *
-array_value (s, quoted, flags, rtype, indp)
-     char *s;
-     int quoted, flags, *rtype;
-     arrayind_t *indp;
+array_value (s, quoted, flags, estatep)
+     const char *s;
+     int quoted, flags;
+     array_eltstate_t *estatep;
 {
-  return (array_value_internal (s, quoted, flags|AV_ALLOWALL, rtype, indp));
+  char *retval;
+
+  retval = array_value_internal (s, quoted, flags|AV_ALLOWALL, estatep);
+  return retval;
 }
 
 /* Return the value of the array indexing expression S as a single string.
@@ -1096,25 +1652,28 @@ array_value (s, quoted, flags, rtype, indp)
    is used by other parts of the shell such as the arithmetic expression
    evaluator in expr.c. */
 char *
-get_array_value (s, flags, rtype, indp)
-     char *s;
-     int flags, *rtype;
-     arrayind_t *indp;
+get_array_value (s, flags, estatep)
+     const char *s;
+     int flags;
+     array_eltstate_t *estatep;
 {
-  return (array_value_internal (s, 0, flags, rtype, indp));
+  char *retval;
+
+  retval = array_value_internal (s, 0, flags, estatep);
+  return retval;
 }
 
 char *
-array_keys (s, quoted)
+array_keys (s, quoted, pflags)
      char *s;
-     int quoted;
+     int quoted, pflags;
 {
   int len;
   char *retval, *t, *temp;
   WORD_LIST *l;
   SHELL_VAR *var;
 
-  var = array_variable_part (s, &t, &len);
+  var = array_variable_part (s, 0, &t, &len);
 
   /* [ */
   if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']')
@@ -1132,14 +1691,7 @@ array_keys (s, quoted)
   if (l == (WORD_LIST *)NULL)
     return ((char *) NULL);
 
-  if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
-    {
-      temp = string_list_dollar_star (l);
-      retval = quote_string (temp);
-      free (temp);
-    }
-  else /* ${!name[@]} or unquoted ${!name[*]} */
-    retval = string_list_dollar_at (l, quoted);
+  retval = string_list_pos_params (t[0], l, quoted, pflags);
 
   dispose_words (l);
   return retval;