diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2023-06-25 17:42:05 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2023-06-25 17:42:05 +0200 |
commit | c1c267fd36b0fcac8c8871232eecc1e360173990 (patch) | |
tree | 83a490c0098926b5e5ef66f474c2b8a6dcfd6620 /shell/math.c | |
parent | 019dd31150526b7dd9dd0addfc38f08bcf7ec551 (diff) | |
download | busybox-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>
Diffstat (limited to 'shell/math.c')
-rw-r--r-- | shell/math.c | 84 |
1 files changed, 48 insertions, 36 deletions
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 |
534 | static arith_t strto_arith_t(const char *nptr, char **endptr) | 534 | static 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 | |||
577 | static 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 | ||