summaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorDenys Vlasenko2023-06-25 17:42:05 +0200
committerDenys Vlasenko2023-06-25 17:42:05 +0200
commitc1c267fd36b0fcac8c8871232eecc1e360173990 (patch)
tree83a490c0098926b5e5ef66f474c2b8a6dcfd6620 /shell
parent019dd31150526b7dd9dd0addfc38f08bcf7ec551 (diff)
downloadbusybox-c1c267fd36b0fcac8c8871232eecc1e360173990.zip
busybox-c1c267fd36b0fcac8c8871232eecc1e360173990.tar.gz
shell/math: bash-compatible handling of too large numbers
function old new delta parse_with_base - 170 +170 evaluate_string 1477 1309 -168 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/1 up/down: 170/-168) Total: 2 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
-rw-r--r--shell/ash_test/ash-arith/arith-bignum1.right13
-rwxr-xr-xshell/ash_test/ash-arith/arith-bignum1.tests17
-rw-r--r--shell/ash_test/ash-arith/arith.right3
-rwxr-xr-xshell/ash_test/ash-arith/arith.tests6
-rw-r--r--shell/hush_test/hush-arith/arith-bignum1.right13
-rwxr-xr-xshell/hush_test/hush-arith/arith-bignum1.tests17
-rw-r--r--shell/hush_test/hush-arith/arith.right3
-rwxr-xr-xshell/hush_test/hush-arith/arith.tests6
-rw-r--r--shell/math.c84
9 files changed, 118 insertions, 44 deletions
diff --git a/shell/ash_test/ash-arith/arith-bignum1.right b/shell/ash_test/ash-arith/arith-bignum1.right
new file mode 100644
index 0000000..42a8016
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-bignum1.right
@@ -0,0 +1,13 @@
+18 digits: 999999999999999999
+19 digits: -8446744073709551617
+20 digits: 7766279631452241919
+18 digits- -999999999999999999
+19 digits- 8446744073709551617
+20 digits- -7766279631452241919
+Hex base#:
+16 digits: 9876543210abcedf
+17 digits: 876543210abcedfc
+18 digits: 76543210abcedfcc
+16 digits: 6789abcdef543121
+17 digits: 789abcdef5431204
+18 digits: 89abcdef54312034
diff --git a/shell/ash_test/ash-arith/arith-bignum1.tests b/shell/ash_test/ash-arith/arith-bignum1.tests
new file mode 100755
index 0000000..ef8f928
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-bignum1.tests
@@ -0,0 +1,17 @@
+exec 2>&1
+# If the number does not fit in 64 bits, bash uses truncated 64-bit value
+# (essentially, it does not check for overflow in "n = n * base + digit"
+# calculation).
+echo 18 digits: $((999999999999999999))
+echo 19 digits: $((9999999999999999999))
+echo 20 digits: $((99999999999999999999))
+echo 18 digits- $((-999999999999999999))
+echo 19 digits- $((-9999999999999999999))
+echo 20 digits- $((-99999999999999999999))
+echo "Hex base#:"
+printf '16 digits: %016x\n' $((16#9876543210abcedf))
+printf '17 digits: %016x\n' $((16#9876543210abcedfc))
+printf '18 digits: %016x\n' $((16#9876543210abcedfcc))
+printf '16 digits: %016x\n' $((-16#9876543210abcedf))
+printf '17 digits: %016x\n' $((-16#9876543210abcedfc))
+printf '18 digits: %016x\n' $((-16#9876543210abcedfcc))
diff --git a/shell/ash_test/ash-arith/arith.right b/shell/ash_test/ash-arith/arith.right
index 8bc78b8..b2c3f56 100644
--- a/shell/ash_test/ash-arith/arith.right
+++ b/shell/ash_test/ash-arith/arith.right
@@ -80,8 +80,9 @@ other bases
62 62
63 63
missing number after base
-0 0
+./arith.tests: line 161: arithmetic syntax error
./arith.tests: line 162: arithmetic syntax error
+./arith.tests: line 163: arithmetic syntax error
./arith.tests: line 164: divide by zero
./arith.tests: let: line 165: arithmetic syntax error
./arith.tests: line 166: arithmetic syntax error
diff --git a/shell/ash_test/ash-arith/arith.tests b/shell/ash_test/ash-arith/arith.tests
index b9cb8ba..42cd7fd 100755
--- a/shell/ash_test/ash-arith/arith.tests
+++ b/shell/ash_test/ash-arith/arith.tests
@@ -155,12 +155,12 @@ echo 63 $(( 64#_ ))
#ash# # weird bases (error)
#ash# echo $(( 3425#56 ))
-echo missing number after base
-echo 0 $(( 2# ))
# these should generate errors
+echo missing number after base
+( echo $(( 2# )) )
( echo $(( 7 = 43 )) )
-#ash# echo $(( 2#44 ))
+( echo $(( 2#44 )) )
( echo $(( 44 / 0 )) )
( let 'jv += $iv' )
( echo $(( jv += \$iv )) )
diff --git a/shell/hush_test/hush-arith/arith-bignum1.right b/shell/hush_test/hush-arith/arith-bignum1.right
new file mode 100644
index 0000000..42a8016
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-bignum1.right
@@ -0,0 +1,13 @@
+18 digits: 999999999999999999
+19 digits: -8446744073709551617
+20 digits: 7766279631452241919
+18 digits- -999999999999999999
+19 digits- 8446744073709551617
+20 digits- -7766279631452241919
+Hex base#:
+16 digits: 9876543210abcedf
+17 digits: 876543210abcedfc
+18 digits: 76543210abcedfcc
+16 digits: 6789abcdef543121
+17 digits: 789abcdef5431204
+18 digits: 89abcdef54312034
diff --git a/shell/hush_test/hush-arith/arith-bignum1.tests b/shell/hush_test/hush-arith/arith-bignum1.tests
new file mode 100755
index 0000000..ef8f928
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-bignum1.tests
@@ -0,0 +1,17 @@
+exec 2>&1
+# If the number does not fit in 64 bits, bash uses truncated 64-bit value
+# (essentially, it does not check for overflow in "n = n * base + digit"
+# calculation).
+echo 18 digits: $((999999999999999999))
+echo 19 digits: $((9999999999999999999))
+echo 20 digits: $((99999999999999999999))
+echo 18 digits- $((-999999999999999999))
+echo 19 digits- $((-9999999999999999999))
+echo 20 digits- $((-99999999999999999999))
+echo "Hex base#:"
+printf '16 digits: %016x\n' $((16#9876543210abcedf))
+printf '17 digits: %016x\n' $((16#9876543210abcedfc))
+printf '18 digits: %016x\n' $((16#9876543210abcedfcc))
+printf '16 digits: %016x\n' $((-16#9876543210abcedf))
+printf '17 digits: %016x\n' $((-16#9876543210abcedfc))
+printf '18 digits: %016x\n' $((-16#9876543210abcedfcc))
diff --git a/shell/hush_test/hush-arith/arith.right b/shell/hush_test/hush-arith/arith.right
index df8154f..d3a9786 100644
--- a/shell/hush_test/hush-arith/arith.right
+++ b/shell/hush_test/hush-arith/arith.right
@@ -82,7 +82,8 @@ other bases
62 62
63 63
missing number after base
-0 0
+hush: arithmetic syntax error
+hush: arithmetic syntax error
hush: arithmetic syntax error
hush: divide by zero
hush: can't execute 'let': No such file or directory
diff --git a/shell/hush_test/hush-arith/arith.tests b/shell/hush_test/hush-arith/arith.tests
index 6b70748..9f03998 100755
--- a/shell/hush_test/hush-arith/arith.tests
+++ b/shell/hush_test/hush-arith/arith.tests
@@ -159,12 +159,12 @@ echo 63 $(( 64#_ ))
#ash# # weird bases (error)
#ash# echo $(( 3425#56 ))
-echo missing number after base
-echo 0 $(( 2# ))
# these should generate errors
+echo missing number after base
+( echo $(( 2# )) )
( echo $(( 7 = 43 )) )
-#ash# echo $(( 2#44 ))
+( echo $(( 2#44 )) )
( echo $(( 44 / 0 )) )
( let 'jv += $iv' )
( echo $(( jv += \$iv )) )
diff --git a/shell/math.c b/shell/math.c
index ac75863..fbf5c58 100644
--- a/shell/math.c
+++ b/shell/math.c
@@ -531,29 +531,11 @@ static const char op_tokens[] ALIGN1 = {
#define END_POINTER (&op_tokens[sizeof(op_tokens)-1])
#if ENABLE_FEATURE_SH_MATH_BASE
-static arith_t strto_arith_t(const char *nptr, char **endptr)
+static arith_t parse_with_base(const char *nptr, char **endptr, unsigned base)
{
- unsigned base;
- arith_t n;
-
-# if ENABLE_FEATURE_SH_MATH_64
- n = strtoull(nptr, endptr, 0);
-# else
- n = strtoul(nptr, endptr, 0);
-# endif
- if (**endptr != '#'
- || (*nptr < '1' || *nptr > '9')
- || (n < 2 || n > 64)
- ) {
- return n;
- }
+ arith_t n = 0;
+ const char *start = nptr;
- /* It's "N#nnnn" or "NN#nnnn" syntax, NN can't start with 0,
- * NN is in 2..64 range.
- */
- base = (unsigned)n;
- n = 0;
- nptr = *endptr + 1;
for (;;) {
unsigned digit = (unsigned)*nptr - '0';
if (digit >= 10 /* not 0..9 */
@@ -582,15 +564,52 @@ static arith_t strto_arith_t(const char *nptr, char **endptr)
n = n * base + digit;
nptr++;
}
- /* Note: we do not set errno on bad chars, we just set a pointer
- * to the first invalid char. For example, this allows
- * "N#" (empty "nnnn" part): 64#+1 is a valid expression,
- * it means 64# + 1, whereas 64#~... is not, since ~ is not a valid
- * operator.
- */
*endptr = (char*)nptr;
+ /* "64#" and "64#+1" used to be valid expressions, but bash 5.2.15
+ * no longer allow such, detect this:
+ */
+// NB: bash allows $((0x)), this is probably a bug...
+ if (nptr == start)
+ *endptr = NULL; /* there weren't any digits, bad */
return n;
}
+
+static arith_t strto_arith_t(const char *nptr, char **endptr)
+{
+/* NB: we do not use strtoull here to be bash-compatible:
+ * $((99999999999999999999)) is 7766279631452241919
+ * (the 64-bit truncated value).
+ */
+ unsigned base;
+
+ /* nptr[0] is '0'..'9' here */
+
+ base = nptr[0] - '0';
+ if (base == 0) { /* nptr[0] is '0' */
+ base = 8;
+ if ((nptr[1] | 0x20) == 'x') {
+ base = 16;
+ nptr += 2;
+ }
+// NB: bash allows $((0x)), this is probably a bug...
+ return parse_with_base(nptr, endptr, base);
+ }
+
+ if (nptr[1] == '#') {
+ if (base > 1)
+ return parse_with_base(nptr + 2, endptr, base);
+ /* else: bash says "invalid arithmetic base" */
+ }
+
+ if (isdigit(nptr[1]) && nptr[2] == '#') {
+ base = 10 * base + (nptr[1] - '0');
+ if (base >= 2 && base <= 64)
+ return parse_with_base(nptr + 3, endptr, base);
+ /* else: bash says "invalid arithmetic base" */
+ }
+
+ return parse_with_base(nptr, endptr, 10);
+}
#else /* !ENABLE_FEATURE_SH_MATH_BASE */
# if ENABLE_FEATURE_SH_MATH_64
# define strto_arith_t(nptr, endptr) strtoull(nptr, endptr, 0)
@@ -702,7 +721,6 @@ evaluate_string(arith_state_t *math_state, const char *expr)
dbg("[%d] var:IGNORED", (int)(numstackptr - numstack));
expr = p;
numstackptr->var_name = NULL;
- push_num0:
numstackptr->val = 0;
}
push_num:
@@ -715,11 +733,12 @@ evaluate_string(arith_state_t *math_state, const char *expr)
/* Number */
char *end;
numstackptr->var_name = NULL;
- errno = 0;
/* code is smaller compared to using &expr here: */
numstackptr->val = strto_arith_t(expr, &end);
expr = end;
dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val);
+ if (!expr) /* example: $((10#)) */
+ goto syntax_err;
/* A number can't be followed by another number, or a variable name.
* We'd catch this later anyway, but this would require numstack[]
* to be ~twice as deep to handle strings where _every_ char is
@@ -728,13 +747,6 @@ evaluate_string(arith_state_t *math_state, const char *expr)
*/
if (isalnum(*expr) || *expr == '_')
goto syntax_err;
- if (errno) {
-// TODO: bash 5.2.15 does not catch ERANGE (some older version did?).
-// $((99999999999999999999)) is 7766279631452241919 (the 64-bit truncated value).
-// Our BASE# code does this as well: try $((10#99999999999999999999)),
-// but the "ordinary" code path with strtoull() does not do this.
- goto push_num0; /* bash compat */
- }
goto push_num;
}