PHP源码分析之等于操作符(==)

thomas0yang 2019-06-25

PHP 中不同类型的变量进行弱类型比较时,有其语言本身比较特殊的规则。本文就起比较规则,就其源码角度进行解释。

PHP-version:5.3

官方规则

参考: PHP.net

PHP源码分析之等于操作符(==)

规则解释

通过词法分析/语法分析/利用vld查看opcode,我发现 PHP 源码中,比较操作符实现的核心方法是 compare_function,位于 Zend/zend_operators.c +1376

确定核心方法源码位置

词法分析

PHP源码分析之等于操作符(==)
如上图示,通过 Zend/zend_language_scanner.l +1201 词法分析规则,得到 == 对应的 Token 为:T_IS_EQUAL

语法分析

PHP源码分析之等于操作符(==)
Zend/zend_language_parser.y 搜索上一步得到的 Token 值,得到语法分析中调用生成 opcode 的方法为:zend_do_binary_op,同时看到 == 所对应的 opcodeZEND_IS_EQUAL

确定 zend 执行时的函数

opcode 对应的调用函数实现于 Zend/zend_vm_execute.h 文件中,在此文件中搜索 ZEND_IS_EQUAL 得到如下的函数列表:
PHP源码分析之等于操作符(==)

方法均以 ZEND_IS_EQUAL_SPEC 开头,名字后缀受 == 两个操作数的 zval 类型影响,具体的类型可以通过 vld 查看,如:
PHP源码分析之等于操作符(==)

猜测是左操作数的类型 + 右操作数的类型。
以上图为例,左操作数类型为 IS_CONST, 右操作数类型为 IS_CV,则对应的处理方法应该是:ZEND_IS_EQUAL_SPEC_CONST_CV_HANDLER
(注:这种确认 handler 的方法只是经验规则,还有待源码验证)

以上的推测正确与否,不影响我们接下来的判断,因为我们发现这些方法都会调用同一个核心方法 compare_function,比较的规则就在这个方法中。通过看此方法源码,比较规则一目了然。
PHP源码分析之等于操作符(==)

compare_function 方法源码如下:

ZEND_API int compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
{
    int ret;
    int converted = 0;
    zval op1_copy, op2_copy;
    zval *op_free;

    while (1) {
        switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {
            case TYPE_PAIR(IS_LONG, IS_LONG):
                ZVAL_LONG(result, Z_LVAL_P(op1)>Z_LVAL_P(op2)?1:(Z_LVAL_P(op1)<Z_LVAL_P(op2)?-1:0));
                return SUCCESS;

            case TYPE_PAIR(IS_DOUBLE, IS_LONG):
                Z_DVAL_P(result) = Z_DVAL_P(op1) - (double)Z_LVAL_P(op2);
                ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
                return SUCCESS;

            case TYPE_PAIR(IS_LONG, IS_DOUBLE):
                Z_DVAL_P(result) = (double)Z_LVAL_P(op1) - Z_DVAL_P(op2);
                ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
                return SUCCESS;

            case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE):
                Z_DVAL_P(result) = Z_DVAL_P(op1) - Z_DVAL_P(op2);
                ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
                return SUCCESS;

            case TYPE_PAIR(IS_ARRAY, IS_ARRAY):
                zend_compare_arrays(result, op1, op2 TSRMLS_CC);
                return SUCCESS;

            case TYPE_PAIR(IS_NULL, IS_NULL):
                ZVAL_LONG(result, 0);
                return SUCCESS;

            case TYPE_PAIR(IS_NULL, IS_BOOL):
                ZVAL_LONG(result, Z_LVAL_P(op2) ? -1 : 0);
                return SUCCESS;

            case TYPE_PAIR(IS_BOOL, IS_NULL):
                ZVAL_LONG(result, Z_LVAL_P(op1) ? 1 : 0);
                return SUCCESS;

            case TYPE_PAIR(IS_BOOL, IS_BOOL):
                ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(op1) - Z_LVAL_P(op2)));
                return SUCCESS;

            case TYPE_PAIR(IS_STRING, IS_STRING):
                zendi_smart_strcmp(result, op1, op2);
                return SUCCESS;

            case TYPE_PAIR(IS_NULL, IS_STRING):
                ZVAL_LONG(result, zend_binary_strcmp("", 0, Z_STRVAL_P(op2), Z_STRLEN_P(op2)));
                return SUCCESS;

            case TYPE_PAIR(IS_STRING, IS_NULL):
                ZVAL_LONG(result, zend_binary_strcmp(Z_STRVAL_P(op1), Z_STRLEN_P(op1), "", 0));
                return SUCCESS;

            case TYPE_PAIR(IS_OBJECT, IS_NULL):
                ZVAL_LONG(result, 1);
                return SUCCESS;

            case TYPE_PAIR(IS_NULL, IS_OBJECT):
                ZVAL_LONG(result, -1);
                return SUCCESS;

            case TYPE_PAIR(IS_OBJECT, IS_OBJECT):
                /* If both are objects sharing the same comparision handler then use is */
                if (Z_OBJ_HANDLER_P(op1,compare_objects) == Z_OBJ_HANDLER_P(op2,compare_objects)) {
                    if (Z_OBJ_HANDLE_P(op1) == Z_OBJ_HANDLE_P(op2)) {
                        /* object handles are identical, apprently this is the same object */
                        ZVAL_LONG(result, 0);
                        return SUCCESS;
                    }
                    ZVAL_LONG(result, Z_OBJ_HT_P(op1)->compare_objects(op1, op2 TSRMLS_CC));
                    return SUCCESS;
                }
                /* break missing intentionally */

            default:
                if (Z_TYPE_P(op1) == IS_OBJECT) {
                    if (Z_OBJ_HT_P(op1)->get) {
                        op_free = Z_OBJ_HT_P(op1)->get(op1 TSRMLS_CC);
                        ret = compare_function(result, op_free, op2 TSRMLS_CC);
                        zend_free_obj_get_result(op_free TSRMLS_CC);
                        return ret;
                    } else if (Z_TYPE_P(op2) != IS_OBJECT && Z_OBJ_HT_P(op1)->cast_object) {
                        ALLOC_INIT_ZVAL(op_free);
                        if (Z_OBJ_HT_P(op1)->cast_object(op1, op_free, Z_TYPE_P(op2) TSRMLS_CC) == FAILURE) {
                            ZVAL_LONG(result, 1);
                            zend_free_obj_get_result(op_free TSRMLS_CC);
                            return SUCCESS;
                        }
                        ret = compare_function(result, op_free, op2 TSRMLS_CC);
                        zend_free_obj_get_result(op_free TSRMLS_CC);
                        return ret;
                    }
                }
                if (Z_TYPE_P(op2) == IS_OBJECT) {
                    if (Z_OBJ_HT_P(op2)->get) {
                        op_free = Z_OBJ_HT_P(op2)->get(op2 TSRMLS_CC);
                        ret = compare_function(result, op1, op_free TSRMLS_CC);
                        zend_free_obj_get_result(op_free TSRMLS_CC);
                        return ret;
                    } else if (Z_TYPE_P(op1) != IS_OBJECT && Z_OBJ_HT_P(op2)->cast_object) {
                        ALLOC_INIT_ZVAL(op_free);
                        if (Z_OBJ_HT_P(op2)->cast_object(op2, op_free, Z_TYPE_P(op1) TSRMLS_CC) == FAILURE) {
                            ZVAL_LONG(result, -1);
                            zend_free_obj_get_result(op_free TSRMLS_CC);
                            return SUCCESS;
                        }
                        ret = compare_function(result, op1, op_free TSRMLS_CC);
                        zend_free_obj_get_result(op_free TSRMLS_CC);
                        return ret;
                    } else if (Z_TYPE_P(op1) == IS_OBJECT) {
                        ZVAL_LONG(result, 1);
                        return SUCCESS;
                    }
                }
                if (!converted) {
                    if (Z_TYPE_P(op1) == IS_NULL) {
                        zendi_convert_to_boolean(op2, op2_copy, result);
                        ZVAL_LONG(result, Z_LVAL_P(op2) ? -1 : 0);
                        return SUCCESS;
                    } else if (Z_TYPE_P(op2) == IS_NULL) {
                        zendi_convert_to_boolean(op1, op1_copy, result);
                        ZVAL_LONG(result, Z_LVAL_P(op1) ? 1 : 0);
                        return SUCCESS;
                    } else if (Z_TYPE_P(op1) == IS_BOOL) {
                        zendi_convert_to_boolean(op2, op2_copy, result);
                        ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(op1) - Z_LVAL_P(op2)));
                        return SUCCESS;
                    } else if (Z_TYPE_P(op2) == IS_BOOL) {
                        zendi_convert_to_boolean(op1, op1_copy, result);
                        ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(op1) - Z_LVAL_P(op2)));
                        return SUCCESS;
                    } else {
                        zendi_convert_scalar_to_number(op1, op1_copy, result);
                        zendi_convert_scalar_to_number(op2, op2_copy, result);
                        converted = 1;
                    }
                } else if (Z_TYPE_P(op1)==IS_ARRAY) {
                    ZVAL_LONG(result, 1);
                    return SUCCESS;
                } else if (Z_TYPE_P(op2)==IS_ARRAY) {
                    ZVAL_LONG(result, -1);
                    return SUCCESS;
                } else if (Z_TYPE_P(op1)==IS_OBJECT) {
                    ZVAL_LONG(result, 1);
                    return SUCCESS;
                } else if (Z_TYPE_P(op2)==IS_OBJECT) {
                    ZVAL_LONG(result, -1);
                    return SUCCESS;
                } else {
                    ZVAL_LONG(result, 0);
                    return FAILURE;
                }
        }
    }
}

操作数类型举例

null

PHP源码分析之等于操作符(==)

  • 结果:将 NULL 转换为 "",进行数字或词汇比较
  • 源码说明:
    PHP源码分析之等于操作符(==)

array

PHP源码分析之等于操作符(==)

  • 结果:将字符串和资源转换成数字,按普通数学比较
  • 源码说明:
    PHP源码分析之等于操作符(==)

其他各类型比较规则见于 compare_function 源码

相关推荐

TiDBPingCAP / 0评论 2020-07-29