aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2023-06-25 17:42:05 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2023-06-25 17:42:05 +0200
commitc1c267fd36b0fcac8c8871232eecc1e360173990 (patch)
tree83a490c0098926b5e5ef66f474c2b8a6dcfd6620
parent019dd31150526b7dd9dd0addfc38f08bcf7ec551 (diff)
downloadbusybox-w32-c1c267fd36b0fcac8c8871232eecc1e360173990.tar.gz
busybox-w32-c1c267fd36b0fcac8c8871232eecc1e360173990.tar.bz2
busybox-w32-c1c267fd36b0fcac8c8871232eecc1e360173990.zip
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>
-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 000000000..42a8016ec
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-bignum1.right
@@ -0,0 +1,13 @@
118 digits: 999999999999999999
219 digits: -8446744073709551617
320 digits: 7766279631452241919
418 digits- -999999999999999999
519 digits- 8446744073709551617
620 digits- -7766279631452241919
7Hex base#:
816 digits: 9876543210abcedf
917 digits: 876543210abcedfc
1018 digits: 76543210abcedfcc
1116 digits: 6789abcdef543121
1217 digits: 789abcdef5431204
1318 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 000000000..ef8f928bc
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-bignum1.tests
@@ -0,0 +1,17 @@
1exec 2>&1
2# If the number does not fit in 64 bits, bash uses truncated 64-bit value
3# (essentially, it does not check for overflow in "n = n * base + digit"
4# calculation).
5echo 18 digits: $((999999999999999999))
6echo 19 digits: $((9999999999999999999))
7echo 20 digits: $((99999999999999999999))
8echo 18 digits- $((-999999999999999999))
9echo 19 digits- $((-9999999999999999999))
10echo 20 digits- $((-99999999999999999999))
11echo "Hex base#:"
12printf '16 digits: %016x\n' $((16#9876543210abcedf))
13printf '17 digits: %016x\n' $((16#9876543210abcedfc))
14printf '18 digits: %016x\n' $((16#9876543210abcedfcc))
15printf '16 digits: %016x\n' $((-16#9876543210abcedf))
16printf '17 digits: %016x\n' $((-16#9876543210abcedfc))
17printf '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 8bc78b8d1..b2c3f56d8 100644
--- a/shell/ash_test/ash-arith/arith.right
+++ b/shell/ash_test/ash-arith/arith.right
@@ -80,8 +80,9 @@ other bases
8062 62 8062 62
8163 63 8163 63
82missing number after base 82missing number after base
830 0 83./arith.tests: line 161: arithmetic syntax error
84./arith.tests: line 162: arithmetic syntax error 84./arith.tests: line 162: arithmetic syntax error
85./arith.tests: line 163: arithmetic syntax error
85./arith.tests: line 164: divide by zero 86./arith.tests: line 164: divide by zero
86./arith.tests: let: line 165: arithmetic syntax error 87./arith.tests: let: line 165: arithmetic syntax error
87./arith.tests: line 166: arithmetic syntax error 88./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 b9cb8ba4c..42cd7fdbf 100755
--- a/shell/ash_test/ash-arith/arith.tests
+++ b/shell/ash_test/ash-arith/arith.tests
@@ -155,12 +155,12 @@ echo 63 $(( 64#_ ))
155#ash# # weird bases (error) 155#ash# # weird bases (error)
156#ash# echo $(( 3425#56 )) 156#ash# echo $(( 3425#56 ))
157 157
158echo missing number after base
159echo 0 $(( 2# ))
160 158
161# these should generate errors 159# these should generate errors
160echo missing number after base
161( echo $(( 2# )) )
162( echo $(( 7 = 43 )) ) 162( echo $(( 7 = 43 )) )
163#ash# echo $(( 2#44 )) 163( echo $(( 2#44 )) )
164( echo $(( 44 / 0 )) ) 164( echo $(( 44 / 0 )) )
165( let 'jv += $iv' ) 165( let 'jv += $iv' )
166( echo $(( jv += \$iv )) ) 166( 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 000000000..42a8016ec
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-bignum1.right
@@ -0,0 +1,13 @@
118 digits: 999999999999999999
219 digits: -8446744073709551617
320 digits: 7766279631452241919
418 digits- -999999999999999999
519 digits- 8446744073709551617
620 digits- -7766279631452241919
7Hex base#:
816 digits: 9876543210abcedf
917 digits: 876543210abcedfc
1018 digits: 76543210abcedfcc
1116 digits: 6789abcdef543121
1217 digits: 789abcdef5431204
1318 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 000000000..ef8f928bc
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-bignum1.tests
@@ -0,0 +1,17 @@
1exec 2>&1
2# If the number does not fit in 64 bits, bash uses truncated 64-bit value
3# (essentially, it does not check for overflow in "n = n * base + digit"
4# calculation).
5echo 18 digits: $((999999999999999999))
6echo 19 digits: $((9999999999999999999))
7echo 20 digits: $((99999999999999999999))
8echo 18 digits- $((-999999999999999999))
9echo 19 digits- $((-9999999999999999999))
10echo 20 digits- $((-99999999999999999999))
11echo "Hex base#:"
12printf '16 digits: %016x\n' $((16#9876543210abcedf))
13printf '17 digits: %016x\n' $((16#9876543210abcedfc))
14printf '18 digits: %016x\n' $((16#9876543210abcedfcc))
15printf '16 digits: %016x\n' $((-16#9876543210abcedf))
16printf '17 digits: %016x\n' $((-16#9876543210abcedfc))
17printf '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 df8154f97..d3a978611 100644
--- a/shell/hush_test/hush-arith/arith.right
+++ b/shell/hush_test/hush-arith/arith.right
@@ -82,7 +82,8 @@ other bases
8262 62 8262 62
8363 63 8363 63
84missing number after base 84missing number after base
850 0 85hush: arithmetic syntax error
86hush: arithmetic syntax error
86hush: arithmetic syntax error 87hush: arithmetic syntax error
87hush: divide by zero 88hush: divide by zero
88hush: can't execute 'let': No such file or directory 89hush: 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 6b707486c..9f0399816 100755
--- a/shell/hush_test/hush-arith/arith.tests
+++ b/shell/hush_test/hush-arith/arith.tests
@@ -159,12 +159,12 @@ echo 63 $(( 64#_ ))
159#ash# # weird bases (error) 159#ash# # weird bases (error)
160#ash# echo $(( 3425#56 )) 160#ash# echo $(( 3425#56 ))
161 161
162echo missing number after base
163echo 0 $(( 2# ))
164 162
165# these should generate errors 163# these should generate errors
164echo missing number after base
165( echo $(( 2# )) )
166( echo $(( 7 = 43 )) ) 166( echo $(( 7 = 43 )) )
167#ash# echo $(( 2#44 )) 167( echo $(( 2#44 )) )
168( echo $(( 44 / 0 )) ) 168( echo $(( 44 / 0 )) )
169( let 'jv += $iv' ) 169( let 'jv += $iv' )
170( echo $(( jv += \$iv )) ) 170( echo $(( jv += \$iv )) )
diff --git a/shell/math.c b/shell/math.c
index ac758639f..fbf5c587e 100644
--- a/shell/math.c
+++ b/shell/math.c
@@ -531,29 +531,11 @@ static const char op_tokens[] ALIGN1 = {
531#define END_POINTER (&op_tokens[sizeof(op_tokens)-1]) 531#define END_POINTER (&op_tokens[sizeof(op_tokens)-1])
532 532
533#if ENABLE_FEATURE_SH_MATH_BASE 533#if ENABLE_FEATURE_SH_MATH_BASE
534static arith_t strto_arith_t(const char *nptr, char **endptr) 534static arith_t parse_with_base(const char *nptr, char **endptr, unsigned base)
535{ 535{
536 unsigned base; 536 arith_t n = 0;
537 arith_t n; 537 const char *start = nptr;
538
539# if ENABLE_FEATURE_SH_MATH_64
540 n = strtoull(nptr, endptr, 0);
541# else
542 n = strtoul(nptr, endptr, 0);
543# endif
544 if (**endptr != '#'
545 || (*nptr < '1' || *nptr > '9')
546 || (n < 2 || n > 64)
547 ) {
548 return n;
549 }
550 538
551 /* It's "N#nnnn" or "NN#nnnn" syntax, NN can't start with 0,
552 * NN is in 2..64 range.
553 */
554 base = (unsigned)n;
555 n = 0;
556 nptr = *endptr + 1;
557 for (;;) { 539 for (;;) {
558 unsigned digit = (unsigned)*nptr - '0'; 540 unsigned digit = (unsigned)*nptr - '0';
559 if (digit >= 10 /* not 0..9 */ 541 if (digit >= 10 /* not 0..9 */
@@ -582,15 +564,52 @@ static arith_t strto_arith_t(const char *nptr, char **endptr)
582 n = n * base + digit; 564 n = n * base + digit;
583 nptr++; 565 nptr++;
584 } 566 }
585 /* Note: we do not set errno on bad chars, we just set a pointer
586 * to the first invalid char. For example, this allows
587 * "N#" (empty "nnnn" part): 64#+1 is a valid expression,
588 * it means 64# + 1, whereas 64#~... is not, since ~ is not a valid
589 * operator.
590 */
591 *endptr = (char*)nptr; 567 *endptr = (char*)nptr;
568 /* "64#" and "64#+1" used to be valid expressions, but bash 5.2.15
569 * no longer allow such, detect this:
570 */
571// NB: bash allows $((0x)), this is probably a bug...
572 if (nptr == start)
573 *endptr = NULL; /* there weren't any digits, bad */
592 return n; 574 return n;
593} 575}
576
577static arith_t strto_arith_t(const char *nptr, char **endptr)
578{
579/* NB: we do not use strtoull here to be bash-compatible:
580 * $((99999999999999999999)) is 7766279631452241919
581 * (the 64-bit truncated value).
582 */
583 unsigned base;
584
585 /* nptr[0] is '0'..'9' here */
586
587 base = nptr[0] - '0';
588 if (base == 0) { /* nptr[0] is '0' */
589 base = 8;
590 if ((nptr[1] | 0x20) == 'x') {
591 base = 16;
592 nptr += 2;
593 }
594// NB: bash allows $((0x)), this is probably a bug...
595 return parse_with_base(nptr, endptr, base);
596 }
597
598 if (nptr[1] == '#') {
599 if (base > 1)
600 return parse_with_base(nptr + 2, endptr, base);
601 /* else: bash says "invalid arithmetic base" */
602 }
603
604 if (isdigit(nptr[1]) && nptr[2] == '#') {
605 base = 10 * base + (nptr[1] - '0');
606 if (base >= 2 && base <= 64)
607 return parse_with_base(nptr + 3, endptr, base);
608 /* else: bash says "invalid arithmetic base" */
609 }
610
611 return parse_with_base(nptr, endptr, 10);
612}
594#else /* !ENABLE_FEATURE_SH_MATH_BASE */ 613#else /* !ENABLE_FEATURE_SH_MATH_BASE */
595# if ENABLE_FEATURE_SH_MATH_64 614# if ENABLE_FEATURE_SH_MATH_64
596# define strto_arith_t(nptr, endptr) strtoull(nptr, endptr, 0) 615# define strto_arith_t(nptr, endptr) strtoull(nptr, endptr, 0)
@@ -702,7 +721,6 @@ evaluate_string(arith_state_t *math_state, const char *expr)
702 dbg("[%d] var:IGNORED", (int)(numstackptr - numstack)); 721 dbg("[%d] var:IGNORED", (int)(numstackptr - numstack));
703 expr = p; 722 expr = p;
704 numstackptr->var_name = NULL; 723 numstackptr->var_name = NULL;
705 push_num0:
706 numstackptr->val = 0; 724 numstackptr->val = 0;
707 } 725 }
708 push_num: 726 push_num:
@@ -715,11 +733,12 @@ evaluate_string(arith_state_t *math_state, const char *expr)
715 /* Number */ 733 /* Number */
716 char *end; 734 char *end;
717 numstackptr->var_name = NULL; 735 numstackptr->var_name = NULL;
718 errno = 0;
719 /* code is smaller compared to using &expr here: */ 736 /* code is smaller compared to using &expr here: */
720 numstackptr->val = strto_arith_t(expr, &end); 737 numstackptr->val = strto_arith_t(expr, &end);
721 expr = end; 738 expr = end;
722 dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val); 739 dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val);
740 if (!expr) /* example: $((10#)) */
741 goto syntax_err;
723 /* A number can't be followed by another number, or a variable name. 742 /* A number can't be followed by another number, or a variable name.
724 * We'd catch this later anyway, but this would require numstack[] 743 * We'd catch this later anyway, but this would require numstack[]
725 * to be ~twice as deep to handle strings where _every_ char is 744 * 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)
728 */ 747 */
729 if (isalnum(*expr) || *expr == '_') 748 if (isalnum(*expr) || *expr == '_')
730 goto syntax_err; 749 goto syntax_err;
731 if (errno) {
732// TODO: bash 5.2.15 does not catch ERANGE (some older version did?).
733// $((99999999999999999999)) is 7766279631452241919 (the 64-bit truncated value).
734// Our BASE# code does this as well: try $((10#99999999999999999999)),
735// but the "ordinary" code path with strtoull() does not do this.
736 goto push_num0; /* bash compat */
737 }
738 goto push_num; 750 goto push_num;
739 } 751 }
740 752