zoukankan      html  css  js  c++  java
  • base_convert()函数探秘及小bug记录

    php base_convert函数原型:

     string base_convert ( string $number , int $frombase , int $tobase )

    base_convert — 在任意进制之间转换数字
    返回一字符串,包含 numbertobase 进制的表示。number 本身的进制由 frombase 指定。frombasetobase 都只能在 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 }
    View Code

    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 }
    View Code

    由上面的代码可以看到,\_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 = '';
    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 }
    View Code

    这部分代码的主体部分是用来处理用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 = '';
    21 
    22     do {
    23         *--ptr = digits[value % base];
    24         value /= base;
    25     } while (ptr > buf && value);
    26 
    27     return estrndup(ptr, end - ptr);
    28 }
    View Code

    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 >
  • 相关阅读:
    数据库常见操作三
    数据库常见操作二
    readelf源码学习
    c++ 常用排序
    分析笔记-反编译失败的锁机apk简单分析
    低自尊者
    Microstation软件操作学习2
    Bentley MicroStation版本号
    Microstation软件操作学习1
    MSCEC#创建工程
  • 原文地址:https://www.cnblogs.com/jade640/p/7581350.html
Copyright © 2011-2022 走看看