base_convert()函数探秘及小bug记录
Posted jade640
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了base_convert()函数探秘及小bug记录相关的知识,希望对你有一定的参考价值。
php base_convert函数原型:
string base_convert ( string $number , int $frombase , int $tobase )
base_convert — 在任意进制之间转换数字
返回一字符串,包含number
以tobase
进制的表示。number
本身的进制由frombase
指定。frombase
和tobase
都只能在 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
内核源码如下:
1 /* {{{ proto string base_convert(string number, int frombase, int tobase) 2 Converts a number in a string from any base <= 36 to any base <= 36 */ 3 PHP_FUNCTION(base_convert) 4 { 5 zval **number, temp; 6 long frombase, tobase; 7 char *result; 8 9 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Zll", &number, &frombase, &tobase) == FAILURE) { 10 return; 11 } 12 convert_to_string_ex(number); 13 14 if (frombase < 2 || frombase > 36) { 15 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid `from base‘ (%ld)", frombase); 16 RETURN_FALSE; 17 } 18 if (tobase < 2 || tobase > 36) { 19 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid `to base‘ (%ld)", tobase); 20 RETURN_FALSE; 21 } 22 23 if(_php_math_basetozval(*number, frombase, &temp) == FAILURE) { 24 RETURN_FALSE; 25 } 26 result = _php_math_zvaltobase(&temp, tobase TSRMLS_CC); 27 RETVAL_STRING(result, 0); 28 }
PHP_FUNCTION(base_convert)首先对输入的参数进行了存储及校验,将\$number以string的形式存储于zval中,并校验\$frombase和\$tobase是否在2和36之间,不在则报错返回。之后调用\_php\_math_basetozval()函数根据frombase将存储于zval中的string转换成对应的数值,代码如下:
1 /* }}} */ 2 3 /* {{{ _php_math_basetozval */ 4 /* 5 * Convert a string representation of a base(2-36) number to a zval. 6 */ 7 PHPAPI int _php_math_basetozval(zval *arg, int base, zval *ret) 8 { 9 long num = 0; 10 double fnum = 0; 11 int i; 12 int mode = 0; 13 char c, *s; 14 long cutoff; 15 int cutlim; 16 17 if (Z_TYPE_P(arg) != IS_STRING || base < 2 || base > 36) { 18 return FAILURE; 19 } 20 21 s = Z_STRVAL_P(arg); 22 23 cutoff = LONG_MAX / base; 24 cutlim = LONG_MAX % base; 25 26 for (i = Z_STRLEN_P(arg); i > 0; i--) { 27 c = *s++; 28 29 /* might not work for EBCDIC */ 30 if (c >= ‘0‘ && c <= ‘9‘) 31 c -= ‘0‘; 32 else if (c >= ‘A‘ && c <= ‘Z‘) 33 c -= ‘A‘ - 10; 34 else if (c >= ‘a‘ && c <= ‘z‘) 35 c -= ‘a‘ - 10; 36 else 37 continue; 38 39 if (c >= base) 40 continue; 41 42 if (c >= base) 43 continue; 44 45 switch (mode) { 46 case 0: /* Integer */ 47 if (num < cutoff || (num == cutoff && c <= cutlim)) { 48 num = num * base + c; 49 break; 50 } else { 51 fnum = num; 52 mode = 1; 53 } 54 /* fall-through */ 55 case 1: /* Float */ 56 fnum = fnum * base + c; 57 } 58 } 59 60 if (mode == 1) { 61 ZVAL_DOUBLE(ret, fnum); 62 } else { 63 ZVAL_LONG(ret, num); 64 } 65 return SUCCESS; 66 }
由上面的代码可以看到,\_php_math_basetozval()根据输入字符串的字面值大小,计算出对应数值并保存在num或fnum中(当数值能被long存储时存在num中,大于long的最大值时,则存储于fnum中,fnum是double类型),然后保存在zval中返回。这里就又一个小bug,该函数在基于from_base将字符串转换为数值时,如果遇到不是0-9或者a-z或者A-Z的字符时,是跳过该字符,并继续处理下一个字符(36行-37行代码),这个地方就隐含着bug了。即base_convert(‘122348738947.653‘,10,8)和echo base_convert(‘122348738947653‘,10,8)的输出相同。另外,如果$number中的字符超过了from_base时,也是跳过该字符不做处理(39行-40行代码)。
通过_php_math_basetozval()我们已经确定了$number所代表的数值,接着调用\_php\_math\_zvaltobase()将数值转换为以$to_base为基数的数值字符串,
1 /* {{{ _php_math_zvaltobase */ 2 /* 3 * Convert a zval to a string containing a base(2-36) representation of 4 * the number. 5 */ 6 PHPAPI char * _php_math_zvaltobase(zval *arg, int base TSRMLS_DC) 7 { 8 static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 9 10 if ((Z_TYPE_P(arg) != IS_LONG && Z_TYPE_P(arg) != IS_DOUBLE) || base < 2 || base > 36) { 11 return STR_EMPTY_ALLOC(); 12 } 13 14 if (Z_TYPE_P(arg) == IS_DOUBLE) { 15 double fvalue = floor(Z_DVAL_P(arg)); /* floor it just in case */ 16 char *ptr, *end; 17 char buf[(sizeof(double) << 3) + 1]; 18 19 /* Don‘t try to convert +/- infinity */ 20 if (fvalue == HUGE_VAL || fvalue == -HUGE_VAL) { 21 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Number too large"); 22 return STR_EMPTY_ALLOC(); 23 } 24 25 end = ptr = buf + sizeof(buf) - 1; 26 *ptr = ‘\0‘; 27 28 do { 29 *--ptr = digits[(int) fmod(fvalue, base)]; 30 fvalue /= base; 31 } while (ptr > buf && fabs(fvalue) >= 1); 32 33 return estrndup(ptr, end - ptr); 34 } 35 36 return _php_math_longtobase(arg, base); 37 }
这部分代码的主体部分是用来处理用double的数值,下面的函数_php_math_longtobase()则是用来处理long存储的数字。
1 /* {{{ _php_math_longtobase */ 2 /* 3 * Convert a long to a string containing a base(2-36) representation of 4 * the number. 5 */ 6 PHPAPI char * _php_math_longtobase(zval *arg, int base) 7 { 8 static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 9 char buf[(sizeof(unsigned long) << 3) + 1]; 10 char *ptr, *end; 11 unsigned long value; 12 13 if (Z_TYPE_P(arg) != IS_LONG || base < 2 || base > 36) { 14 return STR_EMPTY_ALLOC(); 15 } 16 17 value = Z_LVAL_P(arg); 18 19 end = ptr = buf + sizeof(buf) - 1; 20 *ptr = ‘\0‘; 21 22 do { 23 *--ptr = digits[value % base]; 24 value /= base; 25 } while (ptr > buf && value); 26 27 return estrndup(ptr, end - ptr); 28 }
bug演示:
1 php > echo base_convert(‘122348738947.653‘,10,8); 2 3364321107725105 3 php > echo base_convert(‘122348738947‘,10,8); 4 1617443314603 5 php > echo base_convert(‘122348738947653‘,10,8); 6 3364321107725105 7 php > echo base_convert(‘13526~~009‘,10,10); 8 13526009 9 php > echo base_convert(‘13526bb009‘,10,10); 10 13526009 11 php >
以上是关于base_convert()函数探秘及小bug记录的主要内容,如果未能解决你的问题,请参考以下文章