/* 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.
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 */
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 *));
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 *));
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;
(X)->tokval = tokval; \
(X)->tokstr = tokstr; \
(X)->noeval = noeval; \
+ (X)->lval = curlval; \
} while (0)
#define RESTORETOK(X) \
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
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
FASTCOPY (evalbuf, oevalbuf, sizeof (evalbuf));
- c = setjmp (evalbuf);
+ c = setjmp_nosigs (evalbuf);
if (c)
{
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 ();
{
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)
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;
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);
}
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)
{
noeval++;
}
+ readtok ();
+ if (curtok == 0)
+ evalerror (_("expression expected"));
val2 = expcond ();
+
if (set_noeval)
noeval--;
rval = cval ? val1 : val2;
readtok ();
val2 = expbxor ();
val1 = val1 | val2;
+ lasttok = NUM;
}
return (val1);
readtok ();
val2 = expband ();
val1 = val1 ^ val2;
+ lasttok = NUM;
}
return (val1);
readtok ();
val2 = exp5 ();
val1 = val1 & val2;
+ lasttok = NUM;
}
return (val1);
val1 = (val1 == val2);
else if (op == NEQ)
val1 = (val1 != val2);
+ lasttok = NUM;
}
return (val1);
}
val1 = val1 < val2;
else /* (op == GT) */
val1 = val1 > val2;
+ lasttok = NUM;
}
return (val1);
}
val1 = val1 << val2;
else
val1 = val1 >> val2;
+ lasttok = NUM;
}
return (val1);
val1 += val2;
else if (op == MINUS)
val1 -= val2;
+ lasttok = NUM;
}
return (val1);
}
exp2 ()
{
register intmax_t val1, val2;
+#if defined (HAVE_IMAXDIV)
+ imaxdiv_t idiv;
+#endif
val1 = exppower ();
(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 ()
{
{
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);
}
{
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 ();
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 ();
/* 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 ();
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)
value = tok;
#endif
+ last_command_exit_value = EXECUTION_FAILURE;
err_unboundvar (value);
#if defined (ARRAY_VARS)
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 ();
}
#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);
}
register char *cp, *xp;
register unsigned char c, c1;
register int e;
+ struct lvalue lval;
/* Skip leading whitespace. */
cp = tp;
#if defined (ARRAY_VARS)
if (c == '[')
{
- e = skipsubscript (cp, 0);
+ e = skipsubscript (cp, 0, 0);
if (cp[e] == ']')
{
cp += e + 1;
#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;
/* 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;
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.