/* expr.c -- arithmetic expression evaluation. */
-/* Copyright (C) 1990-2010 Free Software Foundation, Inc.
+/* Copyright (C) 1990-2015 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
order of decreasing precedence.
"id++", "id--" [post-increment and post-decrement]
- "++id", "--id" [pre-increment and pre-decrement]
"-", "+" [(unary operators)]
+ "++id", "--id" [pre-increment and pre-decrement]
"!", "~"
"**" [(exponentiation)]
"*", "/", "%"
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 "arrayfunc.h"
+#include "execute_cmd.h"
+#include "flags.h"
+#include "subst.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
static int noeval; /* set to 1 if no assignment to be done */
static procenv_t evalbuf;
+/* set to 1 if the expression has already been run through word expansion */
+static int already_expanded;
+
static struct lvalue curlval = {0, 0, 0, -1};
static struct lvalue lastlval = {0, 0, 0, -1};
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 exp4 __P((void));
static intmax_t expshift __P((void));
static intmax_t exp3 __P((void));
-static intmax_t exp2 __P((void));
+static intmax_t expmuldiv __P((void));
static intmax_t exppower __P((void));
static intmax_t exp1 __P((void));
static intmax_t exp0 __P((void));
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, last_command_exit_value;
-
#if defined (ARRAY_VARS)
extern const char * const bash_badsub_errmsg;
#endif
{
EXPR_CONTEXT *context;
- if (expr_depth == 0)
- evalerror (_("recursion stack underflow"));
+ if (expr_depth <= 0)
+ {
+ /* See the comment at the top of evalexp() for an explanation of why
+ this is done. */
+ expression = lasttp = 0;
+ evalerror (_("recursion stack underflow"));
+ }
context = expr_stack[--expr_depth];
free (expr_stack[expr_depth]);
}
- free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */
+ if (expr_depth == 0)
+ free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */
noeval = 0; /* XXX */
}
expr_bind_variable (lhs, rhs)
char *lhs, *rhs;
{
- (void)bind_int_variable (lhs, rhs);
+ SHELL_VAR *v;
+ int aflags;
+
+ if (lhs == 0 || *lhs == 0)
+ return; /* XXX */
+
+#if defined (ARRAY_VARS)
+ aflags = (assoc_expand_once && already_expanded) ? ASS_NOEXPAND : 0;
+#else
+ aflags = 0;
+#endif
+ v = bind_int_variable (lhs, rhs, aflags);
+ if (v && (readonly_p (v) || noassign_p (v)))
+ sh_longjmp (evalbuf, 1); /* variable assignment error */
stupidly_hack_special_variables (lhs);
}
+#if defined (ARRAY_VARS)
+/* This is similar to the logic in arrayfunc.c:valid_array_subscript when
+ you pass VA_NOEXPAND. */
+static int
+expr_skipsubscript (vp, cp)
+ char *vp, *cp;
+{
+ int flags, isassoc;
+ SHELL_VAR *entry;
+
+ isassoc = 0;
+ entry = 0;
+ if (assoc_expand_once & already_expanded)
+ {
+ *cp = '\0';
+ isassoc = legal_identifier (vp) && (entry = find_variable (vp)) && assoc_p (entry);
+ *cp = '['; /* ] */
+ }
+ flags = (isassoc && assoc_expand_once && already_expanded) ? VA_NOEXPAND : 0;
+ return (skipsubscript (cp, 0, flags));
+}
+
/* Rewrite tok, which is of the form vname[expression], to vname[ind], where
IND is the already-calculated value of expression. */
static void
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);
+ vname = array_variable_name (tok, 0, (char **)NULL, (int *)NULL);
llen = strlen (vname) + sizeof (ibuf) + 3;
lhs = xmalloc (llen);
sprintf (lhs, "%s[%s]", vname, istr); /* XXX */
- expr_bind_variable (lhs, rhs);
/*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
safe to let the loop terminate when expr_depth == 0, without freeing up
any of the expr_depth[0] stuff. */
intmax_t
-evalexp (expr, validp)
+evalexp (expr, flags, validp)
char *expr;
+ int flags;
int *validp;
{
intmax_t val;
val = 0;
noeval = 0;
+ already_expanded = (flags&EXP_EXPANDED);
FASTCOPY (evalbuf, oevalbuf, sizeof (evalbuf));
- c = setjmp (evalbuf);
+ c = setjmp_nosigs (evalbuf);
if (c)
{
tokstr = expression = (char *)NULL;
expr_unwind ();
+ expr_depth = 0; /* XXX - make sure */
+
+ /* We copy in case we've called evalexp recursively */
+ FASTCOPY (oevalbuf, evalbuf, sizeof (evalbuf));
if (validp)
*validp = 0;
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;
}
+ if (tokstr == 0)
+ evalerror (_("syntax error in variable assignment"));
+
+ /* 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;
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)
{
+#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);
{
register intmax_t val1, val2;
- val1 = exp2 ();
+ val1 = expmuldiv ();
while ((curtok == PLUS) || (curtok == MINUS))
{
int op = curtok;
readtok ();
- val2 = exp2 ();
+ val2 = expmuldiv ();
if (op == PLUS)
val1 += val2;
else if (op == MINUS)
val1 -= val2;
+ lasttok = NUM;
}
return (val1);
}
static intmax_t
-exp2 ()
+expmuldiv ()
{
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 ();
vincdec = itos (v2);
if (noeval == 0)
{
+#if defined (ARRAY_VARS)
if (curlval.ind != -1)
expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
else
- expr_bind_variable (tokstr, vincdec);
+#endif
+ if (tokstr)
+ expr_bind_variable (tokstr, vincdec);
}
free (vincdec);
val = v2;
}
else if (curtok == LPAR)
{
+ /* XXX - save curlval here? Or entire expression context? */
readtok ();
val = EXP_HIGHEST ();
vincdec = itos (v2);
if (noeval == 0)
{
+#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);
}
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 ();
SHELL_VAR *v;
char *value;
intmax_t tval;
+ int initial_depth;
#if defined (ARRAY_VARS)
arrayind_t ind;
+ int tflag, aflag;
+#endif
+
+/*itrace("expr_streval: %s: noeval = %d expanded=%d", tok, noeval, already_expanded);*/
+ /* If we are suppressing evaluation, just short-circuit here instead of
+ going through the rest of the evaluator. */
+ if (noeval)
+ return (0);
+
+ initial_depth = expr_depth;
+
+#if defined (ARRAY_VARS)
+ tflag = assoc_expand_once && already_expanded; /* for a start */
#endif
/* [[[[[ */
#if defined (ARRAY_VARS)
- v = (e == ']') ? array_variable_part (tok, (char **)0, (int *)0) : find_variable (tok);
+ aflag = (tflag) ? AV_NOEXPAND : 0;
+ v = (e == ']') ? array_variable_part (tok, tflag, (char **)0, (int *)0) : find_variable (tok);
#else
v = find_variable (tok);
#endif
if ((v == 0 || invisible_p (v)) && unbound_vars_is_error)
{
#if defined (ARRAY_VARS)
- value = (e == ']') ? array_variable_name (tok, (char **)0, (int *)0) : tok;
+ value = (e == ']') ? array_variable_name (tok, tflag, (char **)0, (int *)0) : tok;
#else
value = tok;
#endif
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 ();
jump_to_top_level (FORCE_EOF);
}
- ind = -1;
#if defined (ARRAY_VARS)
- /* 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, &ind) : get_variable_value (v);
+ ind = -1;
+ /* If the second argument to get_array_value doesn't include AV_ALLOWALL,
+ 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. AFLAG is set above and is either AV_NOEXPAND
+ or 0. */
+ value = (e == ']') ? get_array_value (tok, aflag, (int *)NULL, &ind) : get_variable_value (v);
#else
value = get_variable_value (v);
#endif
+ if (expr_depth < initial_depth)
+ {
+ if (no_longjmp_on_fatal_error && interactive_shell)
+ sh_longjmp (evalbuf, 1);
+ return (0);
+ }
+
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);
#if defined (ARRAY_VARS)
if (c == '[')
{
- e = skipsubscript (cp, 0, 0);
+ e = expr_skipsubscript (tp, cp); /* XXX - was skipsubscript */
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;
c = POWER;
else if ((c == '-' || c == '+') && c1 == c && curtok == STR)
c = (c == '-') ? POSTDEC : POSTINC;
+ else if ((c == '-' || c == '+') && c1 == c && curtok == NUM && (lasttok == PREINC || lasttok == PREDEC))
+ {
+ /* This catches something like --FOO++ */
+ if (c == '-')
+ evalerror ("--: assignment requires lvalue");
+ else
+ evalerror ("++: assignment requires lvalue");
+ }
else if ((c == '-' || c == '+') && c1 == c)
{
/* Quickly scan forward to see if this is followed by optional
if (legal_variable_starter ((unsigned char)*xp))
c = (c == '-') ? PREDEC : PREINC;
else
+ /* Could force parsing as preinc or predec and throw an error */
+#if 0
+ {
+ /* Posix says unary plus and minus have higher priority than
+ preinc and predec. */
+ /* This catches something like --4++ */
+ if (c == '-')
+ evalerror ("--: assignment requires lvalue");
+ else
+ evalerror ("++: assignment requires lvalue");
+ }
+#else
cp--; /* not preinc or predec, so unget the character */
+#endif
}
else if (c1 == EQ && member (c, "*/%+-&^|"))
{
char *name, *t;
name = this_command_name;
- for (t = expression; whitespace (*t); t++)
+ for (t = expression; t && whitespace (*t); t++)
;
internal_error (_("%s%s%s: %s (error token is \"%s\")"),
- name ? name : "", name ? ": " : "", t,
- msg, (lasttp && *lasttp) ? lasttp : "");
- longjmp (evalbuf, 1);
+ name ? name : "", name ? ": " : "",
+ t ? t : "", msg, (lasttp && *lasttp) ? lasttp : "");
+ sh_longjmp (evalbuf, 1);
}
/* Convert a string to an intmax_t integer, with an arbitrary base.
for (i = 1; i < argc; i++)
{
- v = evalexp (argv[i], &expok);
+ v = evalexp (argv[i], 0, &expok);
if (expok == 0)
fprintf (stderr, _("%s: expression error\n"), argv[i]);
else