diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index 7eae0729d85..8bb251dfb7d 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -132,9 +132,9 @@ PHP_MINFO_FUNCTION(bcmath) /* {{{ php_str2num Convert to bc_num detecting scale */ -static zend_result php_str2num(bc_num *num, char *str) +static zend_result php_str2num(bc_num *num, const zend_string *str) { - if (!bc_str2num(num, str, 0, true)) { + if (!bc_str2num(num, ZSTR_VAL(str), ZSTR_VAL(str) + ZSTR_LEN(str), 0, true)) { return FAILURE; } @@ -167,12 +167,12 @@ PHP_FUNCTION(bcadd) scale = (int) scale_param; } - if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) { + if (php_str2num(&first, left) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } - if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) { + if (php_str2num(&second, right) == FAILURE) { zend_argument_value_error(2, "is not well-formed"); goto cleanup; } @@ -214,12 +214,12 @@ PHP_FUNCTION(bcsub) scale = (int) scale_param; } - if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) { + if (php_str2num(&first, left) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } - if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) { + if (php_str2num(&second, right) == FAILURE) { zend_argument_value_error(2, "is not well-formed"); goto cleanup; } @@ -261,12 +261,12 @@ PHP_FUNCTION(bcmul) scale = (int) scale_param; } - if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) { + if (php_str2num(&first, left) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } - if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) { + if (php_str2num(&second, right) == FAILURE) { zend_argument_value_error(2, "is not well-formed"); goto cleanup; } @@ -310,12 +310,12 @@ PHP_FUNCTION(bcdiv) bc_init_num(&result); - if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) { + if (php_str2num(&first, left) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } - if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) { + if (php_str2num(&second, right) == FAILURE) { zend_argument_value_error(2, "is not well-formed"); goto cleanup; } @@ -362,12 +362,12 @@ PHP_FUNCTION(bcmod) bc_init_num(&result); - if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) { + if (php_str2num(&first, left) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } - if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) { + if (php_str2num(&second, right) == FAILURE) { zend_argument_value_error(2, "is not well-formed"); goto cleanup; } @@ -415,17 +415,17 @@ PHP_FUNCTION(bcpowmod) bc_init_num(&result); - if (php_str2num(&bc_base, ZSTR_VAL(base_str)) == FAILURE) { + if (php_str2num(&bc_base, base_str) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } - if (php_str2num(&bc_expo, ZSTR_VAL(exponent_str)) == FAILURE) { + if (php_str2num(&bc_expo, exponent_str) == FAILURE) { zend_argument_value_error(2, "is not well-formed"); goto cleanup; } - if (php_str2num(&bc_modulus, ZSTR_VAL(modulus_str)) == FAILURE) { + if (php_str2num(&bc_modulus, modulus_str) == FAILURE) { zend_argument_value_error(3, "is not well-formed"); goto cleanup; } @@ -489,12 +489,12 @@ PHP_FUNCTION(bcpow) bc_init_num(&result); - if (php_str2num(&first, ZSTR_VAL(base_str)) == FAILURE) { + if (php_str2num(&first, base_str) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } - if (php_str2num(&bc_exponent, ZSTR_VAL(exponent_str)) == FAILURE) { + if (php_str2num(&bc_exponent, exponent_str) == FAILURE) { zend_argument_value_error(2, "is not well-formed"); goto cleanup; } @@ -546,7 +546,7 @@ PHP_FUNCTION(bcsqrt) scale = (int) scale_param; } - if (php_str2num(&result, ZSTR_VAL(left)) == FAILURE) { + if (php_str2num(&result, left) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } @@ -588,12 +588,12 @@ PHP_FUNCTION(bccomp) scale = (int) scale_param; } - if (!bc_str2num(&first, ZSTR_VAL(left), scale, false)) { + if (!bc_str2num(&first, ZSTR_VAL(left), ZSTR_VAL(left) + ZSTR_LEN(left), scale, false)) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } - if (!bc_str2num(&second, ZSTR_VAL(right), scale, false)) { + if (!bc_str2num(&second, ZSTR_VAL(right), ZSTR_VAL(right) + ZSTR_LEN(right), scale, false)) { zend_argument_value_error(2, "is not well-formed"); goto cleanup; } @@ -617,7 +617,7 @@ static void bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAMETERS, bool is_floor) Z_PARAM_STR(numstr) ZEND_PARSE_PARAMETERS_END(); - if (php_str2num(&num, ZSTR_VAL(numstr)) == FAILURE) { + if (php_str2num(&num, numstr) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } @@ -678,7 +678,7 @@ PHP_FUNCTION(bcround) bc_init_num(&result); - if (php_str2num(&num, ZSTR_VAL(numstr)) == FAILURE) { + if (php_str2num(&num, numstr) == FAILURE) { zend_argument_value_error(1, "is not well-formed"); goto cleanup; } diff --git a/ext/bcmath/libbcmath/src/bcmath.h b/ext/bcmath/libbcmath/src/bcmath.h index 6b3b14bd159..23c8e8a749e 100644 --- a/ext/bcmath/libbcmath/src/bcmath.h +++ b/ext/bcmath/libbcmath/src/bcmath.h @@ -97,7 +97,7 @@ static inline bc_num bc_copy_num(bc_num num) void bc_init_num(bc_num *num); -bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale); +bool bc_str2num(bc_num *num, const char *str, const char *end, size_t scale, bool auto_scale); zend_string *bc_num2str_ex(bc_num num, size_t scale); diff --git a/ext/bcmath/libbcmath/src/str2num.c b/ext/bcmath/libbcmath/src/str2num.c index 79649a45781..8a94567a6f4 100644 --- a/ext/bcmath/libbcmath/src/str2num.c +++ b/ext/bcmath/libbcmath/src/str2num.c @@ -33,17 +33,55 @@ #include "convert.h" #include #include +#ifdef __SSE2__ +# include +#endif /* Convert strings to bc numbers. Base 10 only.*/ +static const char *bc_count_digits(const char *str, const char *end) +{ + /* Process in bulk */ +#ifdef __SSE2__ + const __m128i offset = _mm_set1_epi8((signed char) (SCHAR_MIN - '0')); + /* we use the less than comparator, so add 1 */ + const __m128i threshold = _mm_set1_epi8(SCHAR_MIN + ('9' + 1 - '0')); + + while (str + sizeof(__m128i) <= end) { + __m128i bytes = _mm_loadu_si128((const __m128i *) str); + /* Wrapping-add the offset to the bytes, such that all bytes below '0' are positive and others are negative. + * More specifically, '0' will be -128 and '9' will be -119. */ + bytes = _mm_add_epi8(bytes, offset); + /* Now mark all bytes that are <= '9', i.e. <= -119, i.e. < -118, i.e. the threshold. */ + bytes = _mm_cmplt_epi8(bytes, threshold); + + int mask = _mm_movemask_epi8(bytes); + if (mask != 0xffff) { + /* At least one of the bytes is not within range. Move to the first offending byte. */ +#ifdef PHP_HAVE_BUILTIN_CTZL + return str + __builtin_ctz(~mask); +#else + break; +#endif + } + + str += sizeof(__m128i); + } +#endif + + while (*str >= '0' && *str <= '9') { + str++; + } + + return str; +} /* Assumes `num` points to NULL, i.e. does yet not hold a number. */ -bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale) +bool bc_str2num(bc_num *num, const char *str, const char *end, size_t scale, bool auto_scale) { - size_t digits = 0; size_t str_scale = 0; - char *ptr = str; - char *fractional_ptr = NULL; - char *fractional_end = NULL; + const char *ptr = str; + const char *fractional_ptr = NULL; + const char *fractional_end = NULL; bool zero_int = false; ZEND_ASSERT(*num == NULL); @@ -59,12 +97,10 @@ bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale) } const char *integer_ptr = ptr; /* digits before the decimal point */ - while (*ptr >= '0' && *ptr <= '9') { - ptr++; - digits++; - } + ptr = bc_count_digits(ptr, end); + size_t digits = ptr - integer_ptr; /* decimal point */ - char *decimal_point = (*ptr == '.') ? ptr : NULL; + const char *decimal_point = (*ptr == '.') ? ptr : NULL; /* If a non-digit and non-decimal-point indicator is in the string, i.e. an invalid character */ if (!decimal_point && *ptr != '\0') { @@ -80,14 +116,17 @@ bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale) } /* validate */ - while (*fractional_ptr >= '0' && *fractional_ptr <= '9') { - fractional_end = *fractional_ptr != '0' ? fractional_ptr + 1 : fractional_end; - fractional_ptr++; - } - if (*fractional_ptr != '\0') { + fractional_end = bc_count_digits(fractional_ptr, end); + if (*fractional_end != '\0') { /* invalid num */ goto fail; } + + /* Exclude trailing zeros. */ + while (fractional_end - 1 > decimal_point && fractional_end[-1] == '0') { + fractional_end--; + } + /* Move the pointer to the beginning of the fraction. */ fractional_ptr = decimal_point + 1;