is_numeric函数的引起的一个BUG说起
生活随笔
收集整理的這篇文章主要介紹了
is_numeric函数的引起的一个BUG说起
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
A.1 問題描述
在日常的工作中,我們會(huì)偶爾用到 is_numberic來(lái)判斷一個(gè)字符串是否為數(shù)字類型的字符串,不過有時(shí)候會(huì)因?yàn)閷?duì)這個(gè)函數(shù)的了解不夠透徹而產(chǎn)生BUG。 我們這邊團(tuán)隊(duì)就遇到了這樣的案例。
具體的情形是這樣的:
對(duì)某個(gè)表單輸入的參數(shù)做is_numberic判斷
- 如果為數(shù)字類型,拼接SQL語(yǔ)法是: {$field} >= {$value}
- 如果為字符串,拼接SQL語(yǔ)法是: {$field} >= '{$value}'
當(dāng)表單輸入?yún)?shù)為'5e34'的時(shí)候觸發(fā)SQL語(yǔ)法錯(cuò)誤。
A.2 原因追溯
翻閱PHP5.6版本的源碼,找到is_numberic函數(shù)具體實(shí)現(xiàn),代碼如下
// ext\standard\type.c /* {{{ proto bool is_numeric(mixed value)Returns true if value is a number or a numeric string */ PHP_FUNCTION(is_numeric) {zval **arg;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &arg) == FAILURE) {return;}switch (Z_TYPE_PP(arg)) {case IS_LONG:case IS_DOUBLE:RETURN_TRUE;break;case IS_STRING:if (is_numeric_string(Z_STRVAL_PP(arg), Z_STRLEN_PP(arg), NULL, NULL, 0)) {RETURN_TRUE;} else {RETURN_FALSE;}break;default:RETURN_FALSE;break;} } /* }}} */從上述源碼我們確認(rèn)處理字符串類型的函數(shù)時(shí) is_numeric_string(Z_STRVAL_PP(arg), Z_STRLEN_PP(arg), NULL, NULL, 0); 定位到該函數(shù)查看。
// zend\Zend_operators.h /*** Checks whether the string "str" with length "length" is numeric. The value* of allow_errors determines whether it's required to be entirely numeric, or* just its prefix. Leading whitespace is allowed.** The function returns 0 if the string did not contain a valid number; IS_LONG* if it contained a number that fits within the range of a long; or IS_DOUBLE* if the number was out of long range or contained a decimal point/exponent.* The number's value is returned into the respective pointer, *lval or *dval,* if that pointer is not NULL.** This variant also gives information if a string that represents an integer* could not be represented as such due to overflow. It writes 1 to oflow_info* if the integer is larger than LONG_MAX and -1 if it's smaller than LONG_MIN.*/ static inline zend_uchar is_numeric_string_ex(const char *str, int length, long *lval, double *dval, int allow_errors, int *oflow_info) {const char *ptr;int base = 10, digits = 0, dp_or_e = 0;double local_dval = 0.0;zend_uchar type;if (!length) {return 0;}if (oflow_info != NULL) {*oflow_info = 0;}/* Skip any whitespace* This is much faster than the isspace() function */while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r' || *str == '\v' || *str == '\f') {str++;length--;}ptr = str;/* Skip [-|+] signed character */if (*ptr == '-' || *ptr == '+') {ptr++;}/* First char is digit */if (ZEND_IS_DIGIT(*ptr)) {/* Handle hex numbers* str is used instead of ptr to disallow signs and keep old behavior */if (length > 2 && *str == '0' && (str[1] == 'x' || str[1] == 'X')) {base = 16;ptr += 2;}/* Skip any leading 0s */while (*ptr == '0') {ptr++;}/* Count the number of digits. If a decimal point/exponent is found,* it's a double. Otherwise, if there's a dval or no need to check for* a full match, stop when there are too many digits for a long */for (type = IS_LONG; !(digits >= MAX_LENGTH_OF_LONG && (dval || allow_errors == 1)); digits++, ptr++) { check_digits:if (ZEND_IS_DIGIT(*ptr) || (base == 16 && ZEND_IS_XDIGIT(*ptr)){continue;} else if (base == 10) {if (*ptr == '.' && dp_or_e < 1) {goto process_double;} else if ((*ptr == 'e' || *ptr == 'E') && dp_or_e < 2){const char *e = ptr + 1;if (*e == '-' || *e == '+') {ptr = e++;}if (ZEND_IS_DIGIT(*e)) {goto process_double;}}}break;}if (base == 10) {if (digits >= MAX_LENGTH_OF_LONG) {if (oflow_info != NULL) {*oflow_info = *str == '-' ? -1 : 1;}dp_or_e = -1;goto process_double;}} else if (!(digits < SIZEOF_LONG * 2 || (digits == SIZEOF_LONG * 2 && ptr[-digits] <= '7'))) {if (dval) {local_dval = zend_hex_strtod(str, &ptr);}if (oflow_info != NULL) {*oflow_info = 1;}type = IS_DOUBLE;}} else if (*ptr == '.' && ZEND_IS_DIGIT(ptr[1])) { process_double:type = IS_DOUBLE;/* If there's a dval, do the conversion; else continue checking* the digits if we need to check for a full match */if (dval) {local_dval = zend_strtod(str, &ptr);} else if (allow_errors != 1 && dp_or_e != -1) {dp_or_e = (*ptr++ == '.') ? 1 : 2;goto check_digits;}} else {return 0;}if (ptr != str + length) {if (!allow_errors) {return 0;}if (allow_errors == -1) {zend_error(E_NOTICE, "A non well formed numeric value encountered");}}if (type == IS_LONG) {if (digits == MAX_LENGTH_OF_LONG - 1) {int cmp = strcmp(&ptr[-digits], long_min_digits);if (!(cmp < 0 || (cmp == 0 && *str == '-'))) {if (dval) {*dval = zend_strtod(str, NULL);}if (oflow_info != NULL) {*oflow_info = *str == '-' ? -1 : 1;}return IS_DOUBLE;}}if (lval) {*lval = strtol(str, NULL, base);}return IS_LONG;} else {if (dval) {*dval = local_dval;}return IS_DOUBLE;} }static inline zend_uchar is_numeric_string(const char *str, int length, long *lval, double *dval, int allow_errors) {return is_numeric_string_ex(str, length, lval, dval, allow_errors, NULL); }A.3 源碼剖析
- 在去掉前導(dǎo)空格, [-|+]符號(hào)標(biāo)識(shí)位后, 判斷剩下的字符串是否滿足
- 十六進(jìn)制
- 科學(xué)計(jì)數(shù)法
- 雙精度浮點(diǎn)小數(shù)
其中
- 十六進(jìn)制的正則: 0([x|X])([0-9|a-f|A-F]+)
- 科學(xué)計(jì)數(shù)法正則: \.?\d+([e|E][-|+]?)\d+$
- 雙精度浮點(diǎn)小數(shù): \d*\.\d+([e|E]?|[e|E][-|+]?)\d+$
測(cè)試代碼
<?php function dump($val) {var_dump(sprintf("%s => %d", $val, is_numeric($val))); }// true dump('5e34'); dump('6e-20'); dump('-1928'); dump('.1e1202'); dump('0x34af'); dump('000123'); dump(' 12345678');// false dump('12 345678'); dump('.e289'); dump('5e3d'); dump('e.1232'); dump('19-28');轉(zhuǎn)載于:https://my.oschina.net/stream/blog/825075
與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的is_numeric函数的引起的一个BUG说起的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从用户的视角看待网页设计(一)
- 下一篇: 菜鸟学习Javascript201701