summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bin/expr/expr.c99
-rw-r--r--regress/bin/Makefile4
-rw-r--r--regress/bin/expr/Makefile8
-rwxr-xr-xregress/bin/expr/expr.sh109
4 files changed, 171 insertions, 49 deletions
diff --git a/bin/expr/expr.c b/bin/expr/expr.c
index 99ae1ead4e4..d37349081c9 100644
--- a/bin/expr/expr.c
+++ b/bin/expr/expr.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: expr.c,v 1.26 2016/10/19 18:20:25 schwarze Exp $ */
+/* $OpenBSD: expr.c,v 1.27 2018/03/31 14:50:56 tobias Exp $ */
/* $NetBSD: expr.c,v 1.3.6.1 1996/06/04 20:41:47 cgd Exp $ */
/*
@@ -19,8 +19,8 @@
struct val *make_int(int64_t);
struct val *make_str(char *);
void free_value(struct val *);
-int is_integer(struct val *, int64_t *);
-int to_integer(struct val *);
+int is_integer(struct val *, int64_t *, const char **);
+int to_integer(struct val *, const char **);
void to_string(struct val *);
int is_zero_or_null(struct val *);
void nexttoken(int);
@@ -94,11 +94,13 @@ free_value(struct val *vp)
/* determine if vp is an integer; if so, return it's value in *r */
int
-is_integer(struct val *vp, int64_t *r)
+is_integer(struct val *vp, int64_t *r, const char **errstr)
{
- char *s;
- int neg;
- int64_t i;
+ const char *errstrp;
+
+ if (errstr == NULL)
+ errstr = &errstrp;
+ *errstr = NULL;
if (vp->type == integer) {
*r = vp->u.i;
@@ -107,43 +109,27 @@ is_integer(struct val *vp, int64_t *r)
/*
* POSIX.2 defines an "integer" as an optional unary minus
- * followed by digits.
+ * followed by digits. Other representations are unspecified,
+ * which means that strtonum(3) is a viable option here.
*/
- s = vp->u.s;
- i = 0;
-
- neg = (*s == '-');
- if (neg)
- s++;
-
- while (*s) {
- if (!isdigit((unsigned char)*s))
- return 0;
-
- i *= 10;
- i += *s - '0';
-
- s++;
- }
-
- if (neg)
- i *= -1;
-
- *r = i;
- return 1;
+ *r = strtonum(vp->u.s, INT64_MIN, INT64_MAX, errstr);
+ return *errstr == NULL;
}
/* coerce to vp to an integer */
int
-to_integer(struct val *vp)
+to_integer(struct val *vp, const char **errstr)
{
int64_t r;
+ if (errstr != NULL)
+ *errstr = NULL;
+
if (vp->type == integer)
return 1;
- if (is_integer(vp, &r)) {
+ if (is_integer(vp, &r, errstr)) {
free(vp->u.s);
vp->u.i = r;
vp->type = integer;
@@ -176,7 +162,7 @@ is_zero_or_null(struct val *vp)
if (vp->type == integer)
return vp->u.i == 0;
else
- return *vp->u.s == 0 || (to_integer(vp) && vp->u.i == 0);
+ return *vp->u.s == 0 || (to_integer(vp, NULL) && vp->u.i == 0);
}
void
@@ -303,20 +289,26 @@ eval5(void)
struct val *
eval4(void)
{
- struct val *l, *r;
- enum token op;
+ const char *errstr;
+ struct val *l, *r;
+ enum token op;
+ volatile int64_t res;
l = eval5();
while ((op = token) == MUL || op == DIV || op == MOD) {
nexttoken(0);
r = eval5();
- if (!to_integer(l) || !to_integer(r)) {
- errx(2, "non-numeric argument");
- }
+ if (!to_integer(l, &errstr))
+ errx(2, "number \"%s\" is %s", l->u.s, errstr);
+ if (!to_integer(r, &errstr))
+ errx(2, "number \"%s\" is %s", r->u.s, errstr);
if (op == MUL) {
- l->u.i *= r->u.i;
+ res = l->u.i * r->u.i;
+ if (r->u.i != 0 && l->u.i != res / r->u.i)
+ errx(3, "overflow");
+ l->u.i = res;
} else {
if (r->u.i == 0) {
errx(2, "division by zero");
@@ -324,6 +316,8 @@ eval4(void)
if (op == DIV) {
if (l->u.i != INT64_MIN || r->u.i != -1)
l->u.i /= r->u.i;
+ else
+ errx(3, "overflow");
} else {
if (l->u.i != INT64_MIN || r->u.i != -1)
l->u.i %= r->u.i;
@@ -342,22 +336,33 @@ eval4(void)
struct val *
eval3(void)
{
- struct val *l, *r;
- enum token op;
+ const char *errstr;
+ struct val *l, *r;
+ enum token op;
+ volatile int64_t res;
l = eval4();
while ((op = token) == ADD || op == SUB) {
nexttoken(0);
r = eval4();
- if (!to_integer(l) || !to_integer(r)) {
- errx(2, "non-numeric argument");
- }
+ if (!to_integer(l, &errstr))
+ errx(2, "number \"%s\" is %s", l->u.s, errstr);
+ if (!to_integer(r, &errstr))
+ errx(2, "number \"%s\" is %s", r->u.s, errstr);
if (op == ADD) {
- l->u.i += r->u.i;
+ res = l->u.i + r->u.i;
+ if ((l->u.i > 0 && r->u.i > 0 && res <= 0) ||
+ (l->u.i < 0 && r->u.i < 0 && res >= 0))
+ errx(3, "overflow");
+ l->u.i = res;
} else {
- l->u.i -= r->u.i;
+ res = l->u.i - r->u.i;
+ if ((l->u.i >= 0 && r->u.i < 0 && res <= 0) ||
+ (l->u.i < 0 && r->u.i > 0 && res >= 0))
+ errx(3, "overflow");
+ l->u.i = res;
}
free_value(r);
@@ -380,7 +385,7 @@ eval2(void)
nexttoken(0);
r = eval3();
- if (is_integer(l, &li) && is_integer(r, &ri)) {
+ if (is_integer(l, &li, NULL) && is_integer(r, &ri, NULL)) {
switch (op) {
case GT:
v = (li > ri);
diff --git a/regress/bin/Makefile b/regress/bin/Makefile
index 2c8805707d9..12be52fbfde 100644
--- a/regress/bin/Makefile
+++ b/regress/bin/Makefile
@@ -1,6 +1,6 @@
-# $OpenBSD: Makefile,v 1.13 2018/01/14 22:04:47 bluhm Exp $
+# $OpenBSD: Makefile,v 1.14 2018/03/31 14:50:56 tobias Exp $
-SUBDIR+= cat chmod csh ed ksh ln md5 pax ps test
+SUBDIR+= cat chmod csh ed expr ksh ln md5 pax ps test
install:
diff --git a/regress/bin/expr/Makefile b/regress/bin/expr/Makefile
new file mode 100644
index 00000000000..2d77c8fa0f5
--- /dev/null
+++ b/regress/bin/expr/Makefile
@@ -0,0 +1,8 @@
+# $OpenBSD: Makefile,v 1.1 2018/03/31 14:50:56 tobias Exp $
+
+REGRESS_TARGETS = expr
+
+expr:
+ sh ${.CURDIR}/expr.sh
+
+.include <bsd.regress.mk>
diff --git a/regress/bin/expr/expr.sh b/regress/bin/expr/expr.sh
new file mode 100755
index 00000000000..da8bf0f1367
--- /dev/null
+++ b/regress/bin/expr/expr.sh
@@ -0,0 +1,109 @@
+#!/bin/ksh
+# $OpenBSD: expr.sh,v 1.1 2018/03/31 14:50:56 tobias Exp $
+
+: ${EXPR=expr}
+
+function test_expr {
+ #echo "Testing: `eval echo $1`"
+ res=`eval $EXPR $1 2>&1`
+ if [ "$res" != "$2" ]; then
+ echo "Expected $2, got $res from expression: `eval echo $1`"
+ exit 1
+ fi
+}
+
+# The first arg will get eval'd so escape any meta characters
+# The 2nd arg is an expected string/response from expr for that op.
+
+# Test overflow cases
+test_expr '4611686018427387904 + 4611686018427387903' '9223372036854775807'
+test_expr '4611686018427387904 + 4611686018427387904' "expr: overflow"
+test_expr '4611686018427387904 - -4611686018427387904' "expr: overflow"
+test_expr '-4611686018427387904 - 4611686018427387903' '-9223372036854775807'
+test_expr '-4611686018427387904 - 4611686018427387905' "expr: overflow"
+test_expr '-4611686018427387904 \* 1' '-4611686018427387904'
+test_expr '-4611686018427387904 \* -1' '4611686018427387904'
+test_expr '-4611686018427387904 \* 2' '-9223372036854775808'
+test_expr '-4611686018427387904 \* 3' "expr: overflow"
+test_expr '-4611686018427387904 \* -2' "expr: overflow"
+test_expr '4611686018427387904 \* 1' '4611686018427387904'
+test_expr '4611686018427387904 \* 2' "expr: overflow"
+test_expr '4611686018427387904 \* 3' "expr: overflow"
+
+# Test from gtk-- configure that cause problems on old expr
+test_expr '3 \> 3 \| 3 = 3 \& 4 \> 4 \| 3 = 3 \& 4 = 4 \& 5 \>= 5' '1'
+test_expr '3 \> 3 \| 3 = 3 \& 4 \> 4 \| 3 = 3 \& 4 = 4 \& 5 \>= 6' '0'
+test_expr '3 \> 3 \| 3 = 3 \& 4 \> 4 \| 3 = 3 \& 4 = 3 \& 5 \>= 5' '0'
+test_expr '3 \> 3 \| 3 = 3 \& 4 \> 4 \| 3 = 2 \& 4 = 4 \& 5 \>= 5' '0'
+test_expr '3 \> 2 \| 3 = 3 \& 4 \> 4 \| 3 = 3 \& 4 = 4 \& 5 \>= 6' '1'
+test_expr '3 \> 3 \| 3 = 3 \& 4 \> 3 \| 3 = 3 \& 4 = 4 \& 5 \>= 5' '1'
+
+# Basic precendence test with the : operator vs. math
+test_expr '2 : 4 / 2' '0'
+test_expr '4 : 4 % 3' '1'
+
+# Dangling arithemtic operator
+test_expr '.java_wrapper : /' '0'
+test_expr '4 : \*' '0'
+test_expr '4 : +' '0'
+test_expr '4 : -' '0'
+test_expr '4 : /' '0'
+test_expr '4 : %' '0'
+
+# Basic math test
+test_expr '2 + 4 \* 5' '22'
+
+# Basic functional tests
+test_expr '2' '2'
+test_expr '-4' '-4'
+test_expr 'hello' 'hello'
+
+# Compare operator precendence test
+test_expr '2 \> 1 \* 17' '0'
+
+# Compare operator tests
+test_expr '2 \!= 5' '1'
+test_expr '2 \!= 2' '0'
+test_expr '2 \<= 3' '1'
+test_expr '2 \<= 2' '1'
+test_expr '2 \<= 1' '0'
+test_expr '2 \< 3' '1'
+test_expr '2 \< 2' '0'
+test_expr '2 = 2' '1'
+test_expr '2 = 4' '0'
+test_expr '2 \>= 1' '1'
+test_expr '2 \>= 2' '1'
+test_expr '2 \>= 3' '0'
+test_expr '2 \> 1' '1'
+test_expr '2 \> 2' '0'
+
+# Known failure once
+test_expr '1 \* -1' '-1'
+
+# Test a known case that should fail
+test_expr '- 1 + 5' 'expr: syntax error'
+
+# Double check negative numbers
+test_expr '1 - -5' '6'
+
+# More complex math test for precedence
+test_expr '-3 + -1 \* 4 + 3 / -6' '-7'
+
+# The next two are messy but the shell escapes cause that.
+# Test precendence
+test_expr 'X1/2/3 : X\\\(.\*[^/]\\\)//\*[^/][^/]\*/\*$ \| . : \\\(.\\\)' '1/2'
+
+# Test proper () returning \1 from a regex
+test_expr '1/2 : .\*/\\\(.\*\\\)' '2'
+
+# Test integer edge cases
+test_expr '-9223372036854775807 + 0' '-9223372036854775807'
+test_expr '-9223372036854775807 - 1' '-9223372036854775808'
+test_expr '-9223372036854775808 + 0' '-9223372036854775808'
+test_expr '-9223372036854775807 - 2' 'expr: overflow'
+test_expr '-9223372036854775808 - 1' 'expr: overflow'
+test_expr '-9223372036854775809 + 0' \
+ 'expr: number "-9223372036854775809" is too small'
+test_expr '-36854775808 - \( -9223372036854775807 - 1 \)' '9223372000000000000'
+
+exit 0