]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - expr.c
Bash-4.4 patch 19
[thirdparty/bash.git] / expr.c
diff --git a/expr.c b/expr.c
index c25160051ec6d1782e9dd1a7145530afee981ef6..172964a24a48eb321d1a325559bc7eaf28121fff 100644 (file)
--- a/expr.c
+++ b/expr.c
@@ -1,6 +1,6 @@
 /* expr.c -- arithmetic expression evaluation. */
 
-/* Copyright (C) 1990-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1990-2015 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -63,7 +63,7 @@
  Implementation is a recursive-descent parser.
 
  Chet Ramey
- chet@ins.CWRU.Edu
+ chet@po.cwru.edu
 */
 
 #include "config.h"
 #include "bashintl.h"
 
 #include "shell.h"
+#include "typemax.h"           /* INTMAX_MAX, INTMAX_MIN */
 
 /* Because of the $((...)) construct, expressions may include newlines.
    Here is a macro which accepts newlines, tabs and spaces as whitespace. */
 #define cr_whitespace(c) (whitespace(c) || ((c) == '\n'))
 
-/* Size be which the expression stack grows when neccessary. */
+/* Size be which the expression stack grows when necessary. */
 #define EXPR_STACK_GROW_SIZE 10
 
 /* Maximum amount of recursion allowed.  This prevents a non-integer
    highest precedence. */
 #define EXP_HIGHEST    expcomma
 
+#ifndef MAX_INT_LEN
+#  define MAX_INT_LEN 32
+#endif
+
+struct lvalue
+{
+  char *tokstr;                /* possibly-rewritten lvalue if not NULL */
+  intmax_t tokval;     /* expression evaluated value */
+  SHELL_VAR *tokvar;   /* variable described by array or var reference */
+  intmax_t ind;                /* array index if not -1 */
+};
+
+/* A structure defining a single expression context. */
+typedef struct {
+  int curtok, lasttok;
+  char *expression, *tp, *lasttp;
+  intmax_t tokval;
+  char *tokstr;
+  int noeval;
+  struct lvalue lval;
+} EXPR_CONTEXT;
+
 static char    *expression;    /* The current expression */
 static char    *tp;            /* token lexical position */
 static char    *lasttp;        /* pointer to last token position */
@@ -148,10 +171,17 @@ static intmax_t   tokval;         /* current token value */
 static int     noeval;         /* set to 1 if no assignment to be done */
 static procenv_t evalbuf;
 
+static struct lvalue curlval = {0, 0, 0, -1};
+static struct lvalue lastlval = {0, 0, 0, -1};
+
 static int     _is_arithop __P((int));
 static void    readtok __P((void));    /* lexical analyzer */
 
-static intmax_t        expr_streval __P((char *, int));
+static void    init_lvalue __P((struct lvalue *));
+static struct lvalue *alloc_lvalue __P((void));
+static void    free_lvalue __P((struct lvalue *));
+
+static intmax_t        expr_streval __P((char *, int, struct lvalue *));
 static intmax_t        strlong __P((char *));
 static void    evalerror __P((const char *));
 
@@ -159,6 +189,9 @@ static void pushexp __P((void));
 static void    popexp __P((void));
 static void    expr_unwind __P((void));
 static void    expr_bind_variable __P((char *, char *));
+#if defined (ARRAY_VARS)
+static void    expr_bind_array_element __P((char *, arrayind_t, char *));
+#endif
 
 static intmax_t subexpr __P((char *));
 
@@ -179,30 +212,13 @@ static intmax_t   exppower __P((void));
 static intmax_t exp1 __P((void));
 static intmax_t exp0 __P((void));
 
-/* A structure defining a single expression context. */
-typedef struct {
-  int curtok, lasttok;
-  char *expression, *tp, *lasttp;
-  intmax_t tokval;
-  char *tokstr;
-  int noeval;
-} EXPR_CONTEXT;
-
-#ifdef INCLUDE_UNUSED
-/* Not used yet. */
-typedef struct {
-  char *tokstr;
-  intmax_t tokval;
-} LVALUE;
-#endif
-
 /* Global var which contains the stack of expression contexts. */
 static EXPR_CONTEXT **expr_stack;
 static int expr_depth;            /* Location in the stack. */
 static int expr_stack_size;       /* Number of slots already allocated. */
 
 extern char *this_command_name;
-extern int unbound_vars_is_error;
+extern int unbound_vars_is_error, last_command_exit_value;
 
 #if defined (ARRAY_VARS)
 extern const char * const bash_badsub_errmsg;
@@ -217,6 +233,7 @@ extern const char * const bash_badsub_errmsg;
     (X)->tokval = tokval; \
     (X)->tokstr = tokstr; \
     (X)->noeval = noeval; \
+    (X)->lval = curlval; \
   } while (0)
 
 #define RESTORETOK(X) \
@@ -228,6 +245,7 @@ extern const char * const bash_badsub_errmsg;
     tokval = (X)->tokval; \
     tokstr = (X)->tokstr; \
     noeval = (X)->noeval; \
+    curlval = (X)->lval; \
   } while (0)
 
 /* Push and save away the contents of the globals describing the
@@ -294,10 +312,42 @@ static void
 expr_bind_variable (lhs, rhs)
      char *lhs, *rhs;
 {
-  (void)bind_int_variable (lhs, rhs);
+  SHELL_VAR *v;
+
+  v = bind_int_variable (lhs, rhs);
+  if (v && (readonly_p (v) || noassign_p (v)))
+    sh_longjmp (evalbuf, 1);   /* variable assignment error */
   stupidly_hack_special_variables (lhs);
 }
 
+#if defined (ARRAY_VARS)
+/* Rewrite tok, which is of the form vname[expression], to vname[ind], where
+   IND is the already-calculated value of expression. */
+static void
+expr_bind_array_element (tok, ind, rhs)
+     char *tok;
+     arrayind_t ind;
+     char *rhs;
+{
+  char *lhs, *vname;
+  size_t llen;
+  char ibuf[INT_STRLEN_BOUND (arrayind_t) + 1], *istr;
+
+  istr = fmtumax (ind, 10, ibuf, sizeof (ibuf), 0);
+  vname = array_variable_name (tok, (char **)NULL, (int *)NULL);
+
+  llen = strlen (vname) + sizeof (ibuf) + 3;
+  lhs = xmalloc (llen);
+
+  sprintf (lhs, "%s[%s]", vname, istr);                /* XXX */
+  
+/*itrace("expr_bind_array_element: %s=%s", lhs, rhs);*/
+  expr_bind_variable (lhs, rhs);
+  free (vname);
+  free (lhs);
+}
+#endif /* ARRAY_VARS */
+
 /* Evaluate EXPR, and return the arithmetic result.  If VALIDP is
    non-null, a zero is stored into the location to which it points
    if the expression is invalid, non-zero otherwise.  If a non-zero
@@ -325,7 +375,7 @@ evalexp (expr, validp)
 
   FASTCOPY (evalbuf, oevalbuf, sizeof (evalbuf));
 
-  c = setjmp (evalbuf);
+  c = setjmp_nosigs (evalbuf);
 
   if (c)
     {
@@ -364,12 +414,14 @@ subexpr (expr)
     return (0);
 
   pushexp ();
-  curtok = lasttok = 0;
   expression = savestring (expr);
   tp = expression;
 
+  curtok = lasttok = 0;
   tokstr = (char *)NULL;
   tokval = 0;
+  init_lvalue (&curlval);
+  lastlval = curlval;
 
   readtok ();
 
@@ -406,6 +458,10 @@ expassign ()
 {
   register intmax_t value;
   char *lhs, *rhs;
+  arrayind_t lind;
+#if defined (HAVE_IMAXDIV)
+  imaxdiv_t idiv;
+#endif
 
   value = expcond ();
   if (curtok == EQ || curtok == OP_ASSIGN)
@@ -424,26 +480,41 @@ expassign ()
          lvalue = value;
        }
 
+      /* XXX - watch out for pointer aliasing issues here */
       lhs = savestring (tokstr);
+      /* save ind in case rhs is string var and evaluation overwrites it */
+      lind = curlval.ind;
       readtok ();
       value = expassign ();
 
       if (special)
        {
+         if ((op == DIV || op == MOD) && value == 0)
+           {
+             if (noeval == 0)
+               evalerror (_("division by 0"));
+             else
+               value = 1;
+           }
+
          switch (op)
            {
            case MUL:
              lvalue *= value;
              break;
            case DIV:
-             if (value == 0)
-               evalerror (_("division by 0"));
-             lvalue /= value;
-             break;
            case MOD:
-             if (value == 0)
-               evalerror (_("division by 0"));
-             lvalue %= value;
+             if (lvalue == INTMAX_MIN && value == -1)
+               lvalue = (op == DIV) ? INTMAX_MIN : 0;
+             else
+#if HAVE_IMAXDIV
+               {
+                 idiv = imaxdiv (lvalue, value);
+                 lvalue = (op == DIV) ? idiv.quot : idiv.rem;
+               }
+#else
+               lvalue = (op == DIV) ? lvalue / value : lvalue % value;
+#endif
              break;
            case PLUS:
              lvalue += value;
@@ -476,12 +547,23 @@ expassign ()
 
       rhs = itos (value);
       if (noeval == 0)
-       expr_bind_variable (lhs, rhs);
+       {
+#if defined (ARRAY_VARS)
+         if (lind != -1)
+           expr_bind_array_element (lhs, lind, rhs);
+         else
+#endif
+           expr_bind_variable (lhs, rhs);
+       }
+      if (curlval.tokstr && curlval.tokstr == tokstr)
+       init_lvalue (&curlval);
+
       free (rhs);
       free (lhs);
       FREE (tokstr);
       tokstr = (char *)NULL;           /* For freeing on errors. */
     }
+
   return (value);
 }
 
@@ -496,24 +578,23 @@ expcond ()
   rval = cval = explor ();
   if (curtok == QUES)          /* found conditional expr */
     {
-      readtok ();
-      if (curtok == 0 || curtok == COL)
-       evalerror (_("expression expected"));
       if (cval == 0)
        {
          set_noeval = 1;
          noeval++;
        }
 
+      readtok ();
+      if (curtok == 0 || curtok == COL)
+       evalerror (_("expression expected"));
+
       val1 = EXP_HIGHEST ();
 
       if (set_noeval)
        noeval--;
       if (curtok != COL)
        evalerror (_("`:' expected for conditional expression"));
-      readtok ();
-      if (curtok == 0)
-       evalerror (_("expression expected"));
+
       set_noeval = 0;
       if (cval)
        {
@@ -521,7 +602,11 @@ expcond ()
          noeval++;
        }
 
+      readtok ();
+      if (curtok == 0)
+       evalerror (_("expression expected"));
       val2 = expcond ();
+
       if (set_noeval)
        noeval--;
       rval = cval ? val1 : val2;
@@ -599,6 +684,7 @@ expbor ()
       readtok ();
       val2 = expbxor ();
       val1 = val1 | val2;
+      lasttok = NUM;
     }
 
   return (val1);
@@ -617,6 +703,7 @@ expbxor ()
       readtok ();
       val2 = expband ();
       val1 = val1 ^ val2;
+      lasttok = NUM;
     }
 
   return (val1);
@@ -635,6 +722,7 @@ expband ()
       readtok ();
       val2 = exp5 ();
       val1 = val1 & val2;
+      lasttok = NUM;
     }
 
   return (val1);
@@ -657,6 +745,7 @@ exp5 ()
        val1 = (val1 == val2);
       else if (op == NEQ)
        val1 = (val1 != val2);
+      lasttok = NUM;
     }
   return (val1);
 }
@@ -685,6 +774,7 @@ exp4 ()
        val1 = val1 < val2;
       else                     /* (op == GT) */
        val1 = val1 > val2;
+      lasttok = NUM;
     }
   return (val1);
 }
@@ -708,6 +798,7 @@ expshift ()
        val1 = val1 << val2;
       else
        val1 = val1 >> val2;
+      lasttok = NUM;
     }
 
   return (val1);
@@ -731,6 +822,7 @@ exp3 ()
        val1 += val2;
       else if (op == MINUS)
        val1 -= val2;
+      lasttok = NUM;
     }
   return (val1);
 }
@@ -739,6 +831,9 @@ static intmax_t
 exp2 ()
 {
   register intmax_t val1, val2;
+#if defined (HAVE_IMAXDIV)
+  imaxdiv_t idiv;
+#endif
 
   val1 = exppower ();
 
@@ -747,24 +842,69 @@ exp2 ()
         (curtok == MOD))
     {
       int op = curtok;
+      char *stp, *sltp;
 
+      stp = tp;
       readtok ();
 
       val2 = exppower ();
 
+      /* Handle division by 0 and twos-complement arithmetic overflow */
       if (((op == DIV) || (op == MOD)) && (val2 == 0))
-       evalerror (_("division by 0"));
+       {
+         if (noeval == 0)
+           {
+             sltp = lasttp;
+             lasttp = stp;
+             while (lasttp && *lasttp && whitespace (*lasttp))
+               lasttp++;
+             evalerror (_("division by 0"));
+             lasttp = sltp;
+           }
+         else
+           val2 = 1;
+       }
+      else if (op == MOD && val1 == INTMAX_MIN && val2 == -1)
+       {
+         val1 = 0;
+         continue;
+       }
+      else if (op == DIV && val1 == INTMAX_MIN && val2 == -1)
+       val2 = 1;
 
       if (op == MUL)
        val1 *= val2;
-      else if (op == DIV)
-       val1 /= val2;
-      else if (op == MOD)
-       val1 %= val2;
+      else if (op == DIV || op == MOD)
+#if defined (HAVE_IMAXDIV)
+       {
+         idiv = imaxdiv (val1, val2);
+         val1 = (op == DIV) ? idiv.quot : idiv.rem;
+       }
+#else
+       val1 = (op == DIV) ? val1 / val2 : val1 % val2;
+#endif
+      lasttok = NUM;
     }
   return (val1);
 }
 
+static intmax_t
+ipow (base, exp)
+     intmax_t base, exp;
+{
+  intmax_t result;
+
+  result = 1;
+  while (exp)
+    {
+      if (exp & 1)
+       result *= base;
+      exp >>= 1;
+      base *= base;
+    }
+  return result;
+}
+
 static intmax_t
 exppower ()
 {
@@ -775,13 +915,12 @@ exppower ()
     {
       readtok ();
       val2 = exppower ();      /* exponentiation is right-associative */
+      lasttok = NUM;
       if (val2 == 0)
        return (1);
       if (val2 < 0)
        evalerror (_("exponent less than 0"));
-      for (c = 1; val2--; c *= val1)
-       ;
-      val1 = c;
+      val1 = ipow (val1, val2);
     }
   return (val1);
 }
@@ -795,11 +934,25 @@ exp1 ()
     {
       readtok ();
       val = !exp1 ();
+      lasttok = NUM;
     }
   else if (curtok == BNOT)
     {
       readtok ();
       val = ~exp1 ();
+      lasttok = NUM;
+    }
+  else if (curtok == MINUS)
+    {
+      readtok ();
+      val = - exp1 ();
+      lasttok = NUM;
+    }
+  else if (curtok == PLUS)
+    {
+      readtok ();
+      val = exp1 ();
+      lasttok = NUM;
     }
   else
     val = exp0 ();
@@ -828,25 +981,23 @@ exp0 ()
       v2 = tokval + ((stok == PREINC) ? 1 : -1);
       vincdec = itos (v2);
       if (noeval == 0)
-       expr_bind_variable (tokstr, vincdec);
+       {
+#if defined (ARRAY_VARS)
+         if (curlval.ind != -1)
+           expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
+         else
+#endif
+           expr_bind_variable (tokstr, vincdec);
+       }
       free (vincdec);
       val = v2;
 
       curtok = NUM;    /* make sure --x=7 is flagged as an error */
       readtok ();
     }
-  else if (curtok == MINUS)
-    {
-      readtok ();
-      val = - exp0 ();
-    }
-  else if (curtok == PLUS)
-    {
-      readtok ();
-      val = exp0 ();
-    }
   else if (curtok == LPAR)
     {
+      /* XXX - save curlval here?  Or entire expression context? */
       readtok ();
       val = EXP_HIGHEST ();
 
@@ -873,22 +1024,30 @@ exp0 ()
              /* restore certain portions of EC */
              tokstr = ec.tokstr;
              noeval = ec.noeval;
+             curlval = ec.lval;
              lasttok = STR;    /* ec.curtok */
 
              v2 = val + ((stok == POSTINC) ? 1 : -1);
              vincdec = itos (v2);
              if (noeval == 0)
-               expr_bind_variable (tokstr, vincdec);
+               {
+#if defined (ARRAY_VARS)
+                 if (curlval.ind != -1)
+                   expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
+                 else
+#endif
+                   expr_bind_variable (tokstr, vincdec);
+               }
              free (vincdec);
              curtok = NUM;     /* make sure x++=7 is flagged as an error */
            }
          else
            {
+             /* XXX - watch out for pointer aliasing issues here */
              if (stok == STR)  /* free new tokstr before old one is restored */
                FREE (tokstr);
              RESTORETOK (&ec);
            }
-
        }
          
       readtok ();
@@ -899,14 +1058,50 @@ exp0 ()
   return (val);
 }
 
+static void
+init_lvalue (lv)
+     struct lvalue *lv;
+{
+  lv->tokstr = 0;
+  lv->tokvar = 0;
+  lv->tokval = lv->ind = -1;
+}
+
+static struct lvalue *
+alloc_lvalue ()
+{
+  struct lvalue *lv;
+
+  lv = xmalloc (sizeof (struct lvalue));
+  init_lvalue (lv);
+  return (lv);
+}
+
+static void
+free_lvalue (lv)
+     struct lvalue *lv;
+{
+  free (lv);           /* should be inlined */
+}
+
 static intmax_t
-expr_streval (tok, e)
+expr_streval (tok, e, lvalue)
      char *tok;
      int e;
+     struct lvalue *lvalue;
 {
   SHELL_VAR *v;
   char *value;
   intmax_t tval;
+#if defined (ARRAY_VARS)
+  arrayind_t ind;
+#endif
+
+/*itrace("expr_streval: %s: noeval = %d", tok, noeval);*/
+  /* If we are suppressing evaluation, just short-circuit here instead of
+     going through the rest of the evaluator. */
+  if (noeval)
+    return (0);
 
   /* [[[[[ */
 #if defined (ARRAY_VARS)
@@ -923,6 +1118,7 @@ expr_streval (tok, e)
       value = tok;
 #endif
 
+      last_command_exit_value = EXECUTION_FAILURE;
       err_unboundvar (value);
 
 #if defined (ARRAY_VARS)
@@ -930,6 +1126,9 @@ expr_streval (tok, e)
        FREE (value);   /* array_variable_name returns new memory */
 #endif
 
+      if (no_longjmp_on_fatal_error && interactive_shell)
+       sh_longjmp (evalbuf, 1);
+
       if (interactive_shell)
        {
          expr_unwind ();
@@ -941,17 +1140,30 @@ expr_streval (tok, e)
     }
 
 #if defined (ARRAY_VARS)
+  ind = -1;
   /* Second argument of 0 to get_array_value means that we don't allow
      references like array[@].  In this case, get_array_value is just
      like get_variable_value in that it does not return newly-allocated
      memory or quote the results. */
-  value = (e == ']') ? get_array_value (tok, 0, (int *)NULL) : get_variable_value (v);
+  value = (e == ']') ? get_array_value (tok, 0, (int *)NULL, &ind) : get_variable_value (v);
 #else
   value = get_variable_value (v);
 #endif
 
   tval = (value && *value) ? subexpr (value) : 0;
 
+  if (lvalue)
+    {
+      lvalue->tokstr = tok;    /* XXX */
+      lvalue->tokval = tval;
+      lvalue->tokvar = v;      /* XXX */
+#if defined (ARRAY_VARS)
+      lvalue->ind = ind;
+#else
+      lvalue->ind = -1;
+#endif
+    }
+         
   return (tval);
 }
 
@@ -1023,6 +1235,7 @@ readtok ()
   register char *cp, *xp;
   register unsigned char c, c1;
   register int e;
+  struct lvalue lval;
 
   /* Skip leading whitespace. */
   cp = tp;
@@ -1057,7 +1270,7 @@ readtok ()
 #if defined (ARRAY_VARS)
       if (c == '[')
        {
-         e = skipsubscript (cp, 0);
+         e = skipsubscript (cp, 0, 0);
          if (cp[e] == ']')
            {
              cp += e + 1;
@@ -1070,10 +1283,15 @@ readtok ()
 #endif /* ARRAY_VARS */
 
       *cp = '\0';
+      /* XXX - watch out for pointer aliasing issues here */
+      if (curlval.tokstr && curlval.tokstr == tokstr)
+       init_lvalue (&curlval);
+
       FREE (tokstr);
       tokstr = savestring (tp);
       *cp = c;
 
+      /* XXX - make peektok part of saved token state? */
       SAVETOK (&ec);
       tokstr = (char *)NULL;   /* keep it from being freed */
       tp = savecp = cp;
@@ -1089,7 +1307,10 @@ readtok ()
       /* The tests for PREINC and PREDEC aren't strictly correct, but they
         preserve old behavior if a construct like --x=9 is given. */
       if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ)
-       tokval = expr_streval (tokstr, e);
+        {
+          lastlval = curlval;
+         tokval = expr_streval (tokstr, e, &curlval);
+        }
       else
        tokval = 0;
 
@@ -1201,7 +1422,7 @@ evalerror (msg)
   internal_error (_("%s%s%s: %s (error token is \"%s\")"),
                   name ? name : "", name ? ": " : "", t,
                   msg, (lasttp && *lasttp) ? lasttp : "");
-  longjmp (evalbuf, 1);
+  sh_longjmp (evalbuf, 1);
 }
 
 /* Convert a string to an intmax_t integer, with an arbitrary base.