diff --git a/Zend/tests/add_002.phpt b/Zend/tests/add_002.phpt index de0a3e8a6317a..726d35a66dcb3 100644 --- a/Zend/tests/add_002.phpt +++ b/Zend/tests/add_002.phpt @@ -20,10 +20,14 @@ var_dump($c); echo "Done\n"; ?> --EXPECTF-- +Notice: You have to implement the __add function in class stdClass to use this operator with an object in %sadd_002.php on line %d + Notice: Object of class stdClass could not be converted to number in %sadd_002.php on line %d Exception: Unsupported operand types +Notice: You have to implement the __add function in class stdClass to use this operator with an object in %sadd_002.php on line %d + Notice: Object of class stdClass could not be converted to number in %s on line %d Fatal error: Uncaught Error: Unsupported operand types in %s:%d diff --git a/Zend/tests/add_003.phpt b/Zend/tests/add_003.phpt index f0066ee3cd1e5..0067ac9d6ffa5 100644 --- a/Zend/tests/add_003.phpt +++ b/Zend/tests/add_003.phpt @@ -20,10 +20,14 @@ var_dump($c); echo "Done\n"; ?> --EXPECTF-- +Notice: You have to implement the __add function in class stdClass to use this operator with an object in %sadd_003.php on line %d + Notice: Object of class stdClass could not be converted to number in %sadd_003.php on line %d Exception: Unsupported operand types +Notice: You have to implement the __add function in class stdClass to use this operator with an object in %sadd_003.php on line %d + Notice: Object of class stdClass could not be converted to number in %s on line %d Fatal error: Uncaught Error: Unsupported operand types in %s:%d diff --git a/Zend/zend.h b/Zend/zend.h index b965a765e35f9..df3e8dca96c78 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -140,6 +140,22 @@ struct _zend_class_entry { zend_function *__callstatic; zend_function *__tostring; zend_function *__debugInfo; + + /* magic functions for operator overloading */ + zend_function *__add; + zend_function *__sub; + zend_function *__mul; + zend_function *__pow; + zend_function *__div; + zend_function *__concat; + zend_function *__mod; + zend_function *__shiftLeft; + zend_function *__shiftRight; + zend_function *__bitwiseOr; + zend_function *__bitwiseAnd; + zend_function *__bitwiseXor; + zend_function *__bitwiseNot; + zend_function *serialize_func; zend_function *unserialize_func; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 8ca81fc7d02be..5a4d7e6dc2f33 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1967,6 +1967,110 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce, } else if (name_len == sizeof(ZEND_DEBUGINFO_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_DEBUGINFO_FUNC_NAME, sizeof(ZEND_DEBUGINFO_FUNC_NAME)-1) && fptr->common.num_args != 0) { zend_error(error_type, "Method %s::%s() cannot take arguments", ZSTR_VAL(ce->name), ZEND_DEBUGINFO_FUNC_NAME); + } else if (name_len == sizeof(ZEND_ADD_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_ADD_FUNC_NAME, sizeof(ZEND_ADD_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), ZEND_ADD_FUNC_NAME); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), ZEND_ADD_FUNC_NAME); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), ZEND_ADD_FUNC_NAME); + } + } else if (name_len == sizeof(ZEND_SUB_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_SUB_FUNC_NAME, sizeof(ZEND_SUB_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), ZEND_SUB_FUNC_NAME); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), ZEND_SUB_FUNC_NAME); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), ZEND_SUB_FUNC_NAME); + } + } else if (name_len == sizeof(ZEND_MUL_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_MUL_FUNC_NAME, sizeof(ZEND_MUL_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), ZEND_MUL_FUNC_NAME); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), ZEND_MUL_FUNC_NAME); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), ZEND_MUL_FUNC_NAME); + } + } else if (name_len == sizeof(ZEND_DIV_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_DIV_FUNC_NAME, sizeof(ZEND_DIV_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), ZEND_DIV_FUNC_NAME); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), ZEND_DIV_FUNC_NAME); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), ZEND_DIV_FUNC_NAME); + } + } else if (name_len == sizeof(ZEND_POW_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_POW_FUNC_NAME, sizeof(ZEND_POW_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), ZEND_POW_FUNC_NAME); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), ZEND_POW_FUNC_NAME); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), ZEND_POW_FUNC_NAME); + } + } else if (name_len == sizeof(ZEND_MOD_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_MOD_FUNC_NAME, sizeof(ZEND_MOD_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), ZEND_MOD_FUNC_NAME); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), ZEND_MOD_FUNC_NAME); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), ZEND_MOD_FUNC_NAME); + } + } else if (name_len == sizeof(ZEND_CONCAT_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_CONCAT_FUNC_NAME, sizeof(ZEND_CONCAT_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), ZEND_CONCAT_FUNC_NAME); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), ZEND_CONCAT_FUNC_NAME); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), ZEND_CONCAT_FUNC_NAME); + } + } else if (name_len == sizeof(ZEND_SHIFT_LEFT_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_SHIFT_LEFT_FUNC_NAME, sizeof(ZEND_SHIFT_LEFT_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), "__shiftLeft"); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), "__shiftLeft"); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), "__shiftLeft"); + } + } else if (name_len == sizeof(ZEND_SHIFT_RIGHT_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_SHIFT_RIGHT_FUNC_NAME, sizeof(ZEND_SHIFT_RIGHT_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), "__shiftRight"); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), "__shiftRight"); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), "__shiftRight"); + } + } else if (name_len == sizeof(ZEND_OR_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_OR_FUNC_NAME, sizeof(ZEND_OR_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), "__bitwiseOr"); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), "__bitwiseOr"); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), "__bitwiseOr"); + } + } else if (name_len == sizeof(ZEND_AND_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_AND_FUNC_NAME, sizeof(ZEND_AND_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), "__bitwiseAnd"); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints", ZSTR_VAL(ce->name), ZEND_ADD_FUNC_NAME); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), "__bitwisAnd"); + } + } else if (name_len == sizeof(ZEND_XOR_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_XOR_FUNC_NAME, sizeof(ZEND_XOR_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 2) { + zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ZSTR_VAL(ce->name), "__bitwiseXor"); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints.", ZSTR_VAL(ce->name), "__bitwiseXor"); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), "__bitwiseXor"); + } + } else if (name_len == sizeof(ZEND_NOT_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_NOT_FUNC_NAME, sizeof(ZEND_NOT_FUNC_NAME) - 1)) { + if (fptr->common.num_args != 1) { + zend_error(error_type, "Method %s::%s() must take exactly 1 arguments", ZSTR_VAL(ce->name), "__bitwiseNot"); + } else if (fptr->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_error(error_type, "Method %s::%s() can not declare argument typehints.", ZSTR_VAL(ce->name), "__bitwiseNot"); + } else if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 1) || QUICK_ARG_SHOULD_BE_SENT_BY_REF(fptr, 2)) { + zend_error(error_type, "Method %s::%s() cannot take arguments by reference", ZSTR_VAL(ce->name), "__bitwiseNot"); + } } } /* }}} */ @@ -1981,6 +2085,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio HashTable *target_function_table = function_table; int error_type; zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL, *__debugInfo = NULL, *serialize_func = NULL, *unserialize_func = NULL; + zend_function *__add = NULL, *__sub = NULL, *__mul = NULL, *__div = NULL, *__pow = NULL, *__concat = NULL, *__mod = NULL, *__bitwiseAnd = NULL, *__bitwiseOr = NULL, *__bitwiseXor = NULL, *__bitwiseNot = NULL, *__shiftLeft = NULL, *__shiftRight = NULL; zend_string *lowercase_name; size_t fname_len; const char *lc_class_name = NULL; @@ -2176,6 +2281,32 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio scope->ce_flags |= ZEND_ACC_USE_GUARDS; } else if (zend_string_equals_literal(lowercase_name, ZEND_DEBUGINFO_FUNC_NAME)) { __debugInfo = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_ADD_FUNC_NAME)) { + __add = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_SUB_FUNC_NAME)) { + __sub = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_MUL_FUNC_NAME)) { + __mul = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_DIV_FUNC_NAME)) { + __div = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_POW_FUNC_NAME)) { + __pow = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_MOD_FUNC_NAME)) { + __mod = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_CONCAT_FUNC_NAME)) { + __concat = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_AND_FUNC_NAME)) { + __bitwiseAnd = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_OR_FUNC_NAME)) { + __bitwiseOr = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_XOR_FUNC_NAME)) { + __bitwiseXor = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_NOT_FUNC_NAME)) { + __bitwiseNot = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_SHIFT_LEFT_FUNC_NAME)) { + __shiftLeft = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_SHIFT_RIGHT_FUNC_NAME)) { + __shiftRight = reg_function; } else { reg_function = NULL; } @@ -2218,6 +2349,19 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio scope->__debugInfo = __debugInfo; scope->serialize_func = serialize_func; scope->unserialize_func = unserialize_func; + scope->__add = __add; + scope->__sub = __sub; + scope->__mul = __mul; + scope->__div = __div; + scope->__pow = __pow; + scope->__mod = __mod; + scope->__bitwiseAnd = __bitwiseAnd; + scope->__bitwiseOr = __bitwiseOr; + scope->__bitwiseXor = __bitwiseXor; + scope->__bitwiseNot = __bitwiseNot; + scope->__concat = __concat; + scope->__shiftLeft = __shiftLeft; + scope->__shiftRight = __shiftRight; if (ctor) { ctor->common.fn_flags |= ZEND_ACC_CTOR; if (ctor->common.fn_flags & ZEND_ACC_STATIC) { @@ -2288,6 +2432,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio if (clone && (clone->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { zend_error_noreturn(E_CORE_ERROR, "%s::%s() cannot declare a return type", ZSTR_VAL(scope->name), ZSTR_VAL(clone->common.function_name)); } + efree((char*)lc_class_name); } return SUCCESS; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2295b55e39a0c..11151b553098a 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1832,6 +1832,19 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify ce->serialize_func = NULL; ce->unserialize_func = NULL; ce->__debugInfo = NULL; + ce->__add = NULL; + ce->__sub = NULL; + ce->__mul = NULL; + ce->__div = NULL; + ce->__pow = NULL; + ce->__mod = NULL; + ce->__concat = NULL; + ce->__shiftLeft = NULL; + ce->__shiftRight = NULL; + ce->__bitwiseAnd = NULL; + ce->__bitwiseOr = NULL; + ce->__bitwiseXor = NULL; + ce->__bitwiseNot = NULL; if (ce->type == ZEND_INTERNAL_CLASS) { ce->info.internal.module = NULL; ce->info.internal.builtin_functions = NULL; @@ -6092,6 +6105,45 @@ void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_boo } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) { zend_check_magic_method_attr(fn_flags, "__debugInfo", 0); ce->__debugInfo = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_ADD_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__add", 1); + ce->__add = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_SUB_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__sub", 1); + ce->__sub = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_MUL_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__mul", 1); + ce->__mul = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_POW_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__pow", 1); + ce->__pow = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_DIV_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__div", 1); + ce->__div = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_CONCAT_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__concat", 1); + ce->__concat = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_MOD_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__mod", 1); + ce->__mod = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_SHIFT_LEFT_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__shiftLeft", 1); + ce->__shiftLeft = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_SHIFT_RIGHT_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__shiftRight", 1); + ce->__shiftRight = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_OR_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__bitwiseOr", 1); + ce->__bitwiseOr = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_AND_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__bitwiseAnd", 1); + ce->__bitwiseAnd = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_XOR_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__bitwiseXor", 1); + ce->__bitwiseXor = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_NOT_FUNC_NAME)) { + zend_check_magic_method_attr(fn_flags, "__bitwiseNot", 1); + ce->__bitwiseNot = (zend_function *) op_array; } zend_string_release_ex(lcname, 0); @@ -7395,23 +7447,26 @@ void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */ } } } else if (opcode == ZEND_CONCAT) { - /* convert constant operands to strings at compile-time */ - if (left_node.op_type == IS_CONST) { - if (Z_TYPE(left_node.u.constant) == IS_ARRAY) { - zend_emit_op_tmp(&left_node, ZEND_CAST, &left_node, NULL)->extended_value = IS_STRING; - } else { - convert_to_string(&left_node.u.constant); + /* Only do optimization if none of the operands are objects (they can have overloaded concat operator) */ + if(!(left_node.op_type == IS_OBJECT || right_node.op_type == IS_OBJECT)) { + /* convert constant operands to strings at compile-time */ + if (left_node.op_type == IS_CONST) { + if (Z_TYPE(left_node.u.constant) == IS_ARRAY) { + zend_emit_op_tmp(&left_node, ZEND_CAST, &left_node, NULL)->extended_value = IS_STRING; + } else { + convert_to_string(&left_node.u.constant); + } } - } - if (right_node.op_type == IS_CONST) { - if (Z_TYPE(right_node.u.constant) == IS_ARRAY) { - zend_emit_op_tmp(&right_node, ZEND_CAST, &right_node, NULL)->extended_value = IS_STRING; - } else { - convert_to_string(&right_node.u.constant); + if (right_node.op_type == IS_CONST) { + if (Z_TYPE(right_node.u.constant) == IS_ARRAY) { + zend_emit_op_tmp(&right_node, ZEND_CAST, &right_node, NULL)->extended_value = IS_STRING; + } else { + convert_to_string(&right_node.u.constant); + } + } + if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) { + opcode = ZEND_FAST_CONCAT; } - } - if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) { - opcode = ZEND_FAST_CONCAT; } } zend_emit_op_tmp(result, opcode, &left_node, &right_node); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index a87204d26753b..bed83176ea66c 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1041,6 +1041,22 @@ END_EXTERN_C() #define ZEND_INVOKE_FUNC_NAME "__invoke" #define ZEND_DEBUGINFO_FUNC_NAME "__debuginfo" + +#define ZEND_ADD_FUNC_NAME "__add" +#define ZEND_SUB_FUNC_NAME "__sub" +#define ZEND_MUL_FUNC_NAME "__mul" +#define ZEND_DIV_FUNC_NAME "__div" +#define ZEND_POW_FUNC_NAME "__pow" +#define ZEND_CONCAT_FUNC_NAME "__concat" +#define ZEND_MOD_FUNC_NAME "__mod" +#define ZEND_SHIFT_LEFT_FUNC_NAME "__shiftleft" +#define ZEND_SHIFT_RIGHT_FUNC_NAME "__shiftright" +#define ZEND_OR_FUNC_NAME "__bitwiseor" +#define ZEND_AND_FUNC_NAME "__bitwiseand" +#define ZEND_XOR_FUNC_NAME "__bitwisexor" +#define ZEND_NOT_FUNC_NAME "__bitwisenot" + + /* The following constants may be combined in CG(compiler_options) * to change the default compiler behavior */ diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index 0885525676bff..f54400ac72f5d 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -138,6 +138,9 @@ void zend_register_standard_constants(void) REGISTER_MAIN_BOOL_CONSTANT("FALSE", 0, CONST_PERSISTENT); REGISTER_MAIN_NULL_CONSTANT("NULL", CONST_PERSISTENT); + /** Operand Type not supported constant */ + REGISTER_MAIN_NULL_CONSTANT("PHP_OPERAND_TYPES_NOT_SUPPORTED", CONST_PERSISTENT); + true_const = zend_hash_str_find_ptr(EG(zend_constants), "TRUE", sizeof("TRUE")-1); false_const = zend_hash_str_find_ptr(EG(zend_constants), "FALSE", sizeof("FALSE")-1); null_const = zend_hash_str_find_ptr(EG(zend_constants), "NULL", sizeof("NULL")-1); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index deb89bc7e3d97..f8146b576cc00 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1597,7 +1597,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, string_len = Z_STRLEN_P(value); c = (zend_uchar)Z_STRVAL_P(value)[0]; } - + if (string_len != 1) { if (string_len == 0) { /* Error on empty input string */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 3a9deabb06da3..3d109be02137d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -185,6 +185,47 @@ static void do_inherit_parent_constructor(zend_class_entry *ce) /* {{{ */ ce->__debugInfo = parent->__debugInfo; } + if (EXPECTED(!ce->__add)) { + ce->__add = parent->__add; + } + if (EXPECTED(!ce->__sub)) { + ce->__sub = parent->__sub; + } + if (EXPECTED(!ce->__mul)) { + ce->__mul = parent->__mul; + } + if (EXPECTED(!ce->__div)) { + ce->__div = parent->__div; + } + if (EXPECTED(!ce->__pow)) { + ce->__pow = parent->__pow; + } + if (EXPECTED(!ce->__mod)) { + ce->__mod = parent->__mod; + } + if (EXPECTED(!ce->__concat)) { + ce->__concat = parent->__concat; + } + if (EXPECTED(!ce->__shiftLeft)) { + ce->__shiftLeft = parent->__shiftLeft; + } + if (EXPECTED(!ce->__shiftRight)) { + ce->__shiftRight = parent->__shiftRight; + } + if (EXPECTED(!ce->__bitwiseAnd)) { + ce->__bitwiseAnd = parent->__bitwiseAnd; + } + if (EXPECTED(!ce->__bitwiseOr)) { + ce->__bitwiseOr = parent->__bitwiseOr; + } + if (EXPECTED(!ce->__bitwiseXor)) { + ce->__bitwiseXor = parent->__bitwiseXor; + } + if (EXPECTED(!ce->__bitwiseNot)) { + ce->__bitwiseNot = parent->__bitwiseNot; + } + + if (ce->constructor) { if (parent->constructor && UNEXPECTED(parent->constructor->common.fn_flags & ZEND_ACC_FINAL)) { zend_error_noreturn(E_ERROR, "Cannot override final %s::%s() with %s::%s()", diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 57529afa3fc28..51607d7bc12a9 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -30,6 +30,7 @@ #include "zend_closures.h" #include "zend_compile.h" #include "zend_hash.h" +#include "zend_vm_opcodes.h" #define DEBUG_OBJECT_HANDLERS 0 @@ -175,6 +176,163 @@ ZEND_API HashTable *zend_std_get_debug_info(zend_object *object, int *is_temp) / } /* }}} */ + +static int zend_std_call_op_handler(zend_uchar opcode, zval *result, zval *op1, zval *op2) /* {{{ */ +{ + zend_bool is_retry = 0; + + zend_class_entry *ce; + zend_object *zobj; + if(Z_TYPE_P(op1) == IS_OBJECT) { + zobj = Z_OBJ_P(op1); + ce = Z_OBJCE_P(op1); + } else if(Z_TYPE_P(op2) == IS_OBJECT) { + zobj = Z_OBJ_P(op2); + ce = Z_OBJCE_P(op2); + } else { + return FAILURE; + } + + zend_class_entry *orig_fake_scope = EG(fake_scope); + zend_fcall_info fci; + zend_fcall_info_cache fcic; + + EG(fake_scope) = NULL; + + zval params[2]; + /* Unary operator handlers have only one argument */ + if (op2 == NULL) { + fci.param_count = 1; + params[0] = *op1; + fci.params = params; + } else { /* binary op handlers like __add are called with two operands op1, op2 */ + fci.param_count = 2; + params[0] = *op1; + params[1] = *op2; + fci.params = params; + } + + fci.size = sizeof(fci); + fci.retval = result; + fci.no_separation = 1; + //ZVAL_UNDEF(&fci.function_name); /* Unused */ + + do + { + fci.object = zobj; + + /* Determine what handler should be used, based on opcode */ + switch (opcode) + { + case ZEND_ADD: + fcic.function_handler = ce->__add; + ZVAL_STRING(&fci.function_name, ZEND_ADD_FUNC_NAME); + break; + case ZEND_SUB: + fcic.function_handler = ce->__sub; + ZVAL_STRING(&fci.function_name, ZEND_SUB_FUNC_NAME); + break; + case ZEND_MUL: + fcic.function_handler = ce->__mul; + ZVAL_STRING(&fci.function_name, ZEND_MUL_FUNC_NAME); + break; + case ZEND_DIV: + fcic.function_handler = ce->__div; + ZVAL_STRING(&fci.function_name, ZEND_DIV_FUNC_NAME); + break; + case ZEND_POW: + fcic.function_handler = ce->__pow; + ZVAL_STRING(&fci.function_name, ZEND_POW_FUNC_NAME); + break; + case ZEND_MOD: + fcic.function_handler = ce->__mod; + ZVAL_STRING(&fci.function_name, ZEND_MOD_FUNC_NAME); + break; + case ZEND_CONCAT: + fcic.function_handler = ce->__concat; + ZVAL_STRING(&fci.function_name, ZEND_CONCAT_FUNC_NAME); + break; + case ZEND_SL: + fcic.function_handler = ce->__shiftLeft; + ZVAL_STRING(&fci.function_name, "__shiftLeft"); + break; + case ZEND_SR: + fcic.function_handler = ce->__shiftRight; + ZVAL_STRING(&fci.function_name, "__shiftRight"); + break; + case ZEND_BW_OR: + fcic.function_handler = ce->__bitwiseOr; + ZVAL_STRING(&fci.function_name, "__bitwiseOr"); + break; + case ZEND_BW_AND: + fcic.function_handler = ce->__bitwiseAnd; + ZVAL_STRING(&fci.function_name, "__bitwiseAnd"); + break; + case ZEND_BW_XOR: + fcic.function_handler = ce->__bitwiseXor; + ZVAL_STRING(&fci.function_name, "__bitwiseXor"); + break; + case ZEND_BW_NOT: + fcic.function_handler = ce->__bitwiseNot; + ZVAL_STRING(&fci.function_name, "__bitwiseNot"); + break; + default: + return FAILURE; + break; + } + + /* Check if function exists, check on other operand if possible */ + if (fcic.function_handler == NULL) + { + if(zobj == Z_OBJ_P(op1) && op2 != NULL && Z_TYPE_P(op2) == IS_OBJECT && !is_retry) { +retry: + zobj = Z_OBJ_P(op2); + ce = Z_OBJCE_P(op2); + zval_ptr_dtor(&fci.function_name); + /* Retry checking if other operand has method */ + is_retry = 1; + continue; + } + + zend_error(E_NOTICE, "You have to implement the %s function in class %s to use this operator with an object", + Z_STRVAL(fci.function_name), ZSTR_VAL(ce->name)); + zval_ptr_dtor(&fci.function_name); + return FAILURE; + } + + fcic.called_scope = ce; + fcic.object = zobj; + fcic.function_handler->type = ZEND_USER_FUNCTION; + + int tmp = zend_call_function(&fci, &fcic); + + /** The operand handler can either return PHP_OPERAND_NOT_SUPPORTED */ + if (Z_TYPE_P(result) == IS_NULL) { + if(zobj == Z_OBJ_P(op1) && op2 != NULL && Z_TYPE_P(op2) == IS_OBJECT && !is_retry) { + goto retry; + } else { + if(op2 != NULL) { /* binary operators */ + zend_type_error("The operand handlers %s do not support the given operand types combination (%s and %s given)", + Z_STRVAL(fci.function_name), + Z_TYPE_P(op1) == IS_OBJECT ? ZSTR_VAL(Z_OBJCE_P(op1)->name) : zend_zval_type_name(op1), + Z_TYPE_P(op2) == IS_OBJECT ? ZSTR_VAL(Z_OBJCE_P(op2)->name) : zend_zval_type_name(op2)); + } else { /* unary operators */ + zend_type_error("The operand handler %s do not support the given operand type (%s given)", + Z_STRVAL(fci.function_name), + Z_TYPE_P(op1) == IS_OBJECT ? ZSTR_VAL(Z_OBJCE_P(op1)->name) : zend_zval_type_name(op1)); + } + } + } + + EG(fake_scope) = orig_fake_scope; + + zval_ptr_dtor(&fci.function_name); + + return tmp; + } while (1); +} +/* }}} */ + static void zend_std_call_getter(zend_object *zobj, zend_string *prop_name, zval *retval) /* {{{ */ { zend_class_entry *ce = zobj->ce; @@ -1904,7 +2062,7 @@ ZEND_API const zend_object_handlers std_object_handlers = { zend_std_get_debug_info, /* get_debug_info */ zend_std_get_closure, /* get_closure */ zend_std_get_gc, /* get_gc */ - NULL, /* do_operation */ + zend_std_call_op_handler, /* do_operation */ zend_std_compare_objects, /* compare */ NULL, /* get_properties_for */ }; diff --git a/tests/classes/operator_overloading_001.phpt b/tests/classes/operator_overloading_001.phpt new file mode 100644 index 0000000000000..59d11542440fe --- /dev/null +++ b/tests/classes/operator_overloading_001.phpt @@ -0,0 +1,130 @@ +--TEST-- +Userspace operator overloading - Handlers +--FILE-- +> 1; +$b = $test & 1; +$b = $test | 1; +$b = $test ^ 1; +$b = ~$test; + +//Test indirect overloadable operators +$b = + $test; +$b = - $test; + +$b = $test++; + +//Increment overwrites $test, so recreate it +$test = new Test(); +$b = $test--; + +--EXPECT-- +__add called +__sub called +__mul called +__div called +__pow called +__mod called +__concat called +__shiftLeft called +__shiftRight called +__bitwiseAnd called +__bitwiseOr called +__bitwiseXor called +__bitwiseNot called +__mul called +__mul called +__add called +__sub called diff --git a/tests/classes/operator_overloading_002.phpt b/tests/classes/operator_overloading_002.phpt new file mode 100644 index 0000000000000..3db65a83bd26c --- /dev/null +++ b/tests/classes/operator_overloading_002.phpt @@ -0,0 +1,63 @@ +--TEST-- +Userspace operator overloading - Evaluation order +--FILE-- + call ClassA +$a / $b; +$b / $a; + +//Use PHP_OPERAND_TYPES_NOT_SUPPORTED to skip handler +$b * $a; +$a * $b; + +--EXPECT-- +__add on Class A called +__add on Class B called +__div on Class A called +__div on Class A called +__mul on Class B called +__mul on Class A called and skiped +__mul on Class B called diff --git a/tests/classes/operator_overloading_003.phpt b/tests/classes/operator_overloading_003.phpt new file mode 100644 index 0000000000000..a85cba6daaa49 --- /dev/null +++ b/tests/classes/operator_overloading_003.phpt @@ -0,0 +1,15 @@ +--TEST-- +Userspace operator overloading - Disallow typehint in arguments +--FILE-- +getMessage() . "\n"; +} + +try { + ~$a; +} catch(Error $exception) { + echo $exception->getMessage() . "\n"; +} + +--EXPECTF-- +Notice: You have to implement the __div function in class A to use this operator with an object in %s on line %d + +Notice: Object of class A could not be converted to number in %s on line %d +The operand handlers __add do not support the given operand types combination (A and int given) +The operand handler __bitwiseNot do not support the given operand type (A given) diff --git a/tests/classes/operator_overloading_006.phpt b/tests/classes/operator_overloading_006.phpt new file mode 100644 index 0000000000000..414090044618c --- /dev/null +++ b/tests/classes/operator_overloading_006.phpt @@ -0,0 +1,57 @@ +--TEST-- +Userspace operator overloading - Type test +--FILE-- +