Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@ 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 *__sl;
zend_function *__sr;
zend_function *__or;
zend_function *__and;
zend_function *__xor;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My two bits for better developer experience:

I suggest to use full names of methods, this is just not readable. __add, __subtract, __multiply etc. is much clearer.

Also, on* method naming convention came to my mind (__onAddition, __onMultiplication etc.) You can think about the operator as an event emitter ($a + $b) and the magic method as a handler. Not sure if it is a good idea, but I guess it is more explicit about when the method is run actually.


zend_function *serialize_func;
zend_function *unserialize_func;

Expand Down
48 changes: 48 additions & 0 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -1967,6 +1967,54 @@ 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 (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 (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 (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 (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 (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 (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 (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), ZEND_SHIFT_LEFT_FUNC_NAME);
}
} 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), ZEND_SHIFT_RIGHT_FUNC_NAME);
}
} 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), ZEND_OR_FUNC_NAME);
}
} 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), ZEND_AND_FUNC_NAME);
}
} 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), ZEND_XOR_FUNC_NAME);
}
}
}
/* }}} */
Expand Down
36 changes: 36 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6092,6 +6092,42 @@ 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, "__sl", 1);
ce->__sl = (zend_function *) op_array;
} else if (zend_string_equals_literal(lcname, ZEND_SHIFT_RIGHT_FUNC_NAME)) {
zend_check_magic_method_attr(fn_flags, "__sr", 1);
ce->__sr = (zend_function *) op_array;
} else if (zend_string_equals_literal(lcname, ZEND_OR_FUNC_NAME)) {
zend_check_magic_method_attr(fn_flags, "__or", 1);
ce->__or = (zend_function *) op_array;
} else if (zend_string_equals_literal(lcname, ZEND_AND_FUNC_NAME)) {
zend_check_magic_method_attr(fn_flags, "__and", 1);
ce->__and = (zend_function *) op_array;
} else if (zend_string_equals_literal(lcname, ZEND_XOR_FUNC_NAME)) {
zend_check_magic_method_attr(fn_flags, "__xor", 1);
ce->__xor = (zend_function *) op_array;
}

zend_string_release_ex(lcname, 0);
Expand Down
15 changes: 15 additions & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,21 @@ 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 "__sl"
#define ZEND_SHIFT_RIGHT_FUNC_NAME "__sr"
#define ZEND_OR_FUNC_NAME "__or"
#define ZEND_AND_FUNC_NAME "__and"
#define ZEND_XOR_FUNC_NAME "__xor"


/* The following constants may be combined in CG(compiler_options)
* to change the default compiler behavior */

Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_constants.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
25 changes: 24 additions & 1 deletion Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,29 @@ static zend_always_inline zend_bool zend_check_type(
return zend_check_type_slow(type, arg, ref, cache_slot, scope, is_return_type, is_internal);
}

ZEND_API int zend_check_arg_types(zend_function *zf, uint32_t arg_count, zval **arg)
{
zend_arg_info *cur_arg_info;

/* We need cache for class_entry pointers */
void **cache_slot = (void**) ecalloc(arg_count, sizeof(void*));

ZEND_ASSERT(arg_count <= zf->common.num_args);

for(uint32_t i=0; i<arg_count; i++) {
cur_arg_info = &zf->common.arg_info[i];
if (ZEND_TYPE_IS_SET(cur_arg_info->type)
&& UNEXPECTED(!zend_check_type(cur_arg_info->type, arg[i], cache_slot, zf->common.scope, 0, 0))) {
efree(cache_slot);
return 0;
}
}

efree(cache_slot);

return 1;
}

static zend_always_inline int zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot)
{
zend_arg_info *cur_arg_info;
Expand Down Expand Up @@ -1597,7 +1620,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 */
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ ZEND_API int zend_eval_stringl(const char *str, size_t str_len, zval *retval_ptr
ZEND_API int zend_eval_string_ex(const char *str, zval *retval_ptr, const char *string_name, int handle_exceptions);
ZEND_API int zend_eval_stringl_ex(const char *str, size_t str_len, zval *retval_ptr, const char *string_name, int handle_exceptions);

ZEND_API int zend_check_arg_types(zend_function *zf, uint32_t arg_num, zval **arg);

/* export zend_pass_function to allow comparisons against it */
extern ZEND_API const zend_internal_function zend_pass_function;

Expand Down
148 changes: 147 additions & 1 deletion Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -175,6 +176,151 @@ 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_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;
zval params[2] = {*op1, *op2};

EG(fake_scope) = NULL;

/* op handlers like __add are called with two operands op1, op2 */
fci.size = sizeof(fci);
fci.retval = result;
fci.param_count = 2;
fci.params = params;
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually needed? If the ce->__add etc are properly pre-initialized, then it should not be necessary to set the function name here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind this, was to save the name of magic function called, so it can be for the usage notice below.
Maybe I should move this to the zend_error(E_NOTICE, ..) line, so the var is only created if really needed.

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->__sl;
ZVAL_STRING(&fci.function_name, ZEND_SHIFT_LEFT_FUNC_NAME);
break;
case ZEND_SR:
fcic.function_handler = ce->__sr;
ZVAL_STRING(&fci.function_name, ZEND_SHIFT_RIGHT_FUNC_NAME);
break;
case ZEND_BW_OR:
fcic.function_handler = ce->__or;
ZVAL_STRING(&fci.function_name, ZEND_OR_FUNC_NAME);
break;
case ZEND_BW_AND:
fcic.function_handler = ce->__and;
ZVAL_STRING(&fci.function_name, ZEND_AND_FUNC_NAME);
break;
case ZEND_BW_XOR:
fcic.function_handler = ce->__xor;
ZVAL_STRING(&fci.function_name, ZEND_XOR_FUNC_NAME);
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) && Z_TYPE_P(op2) == IS_OBJECT) {
retry:
zobj = Z_OBJ_P(op2);
ce = Z_OBJCE_P(op2);
zval_ptr_dtor(&fci.function_name);
/* Retry checking if other operand has method */
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;
}


/* Check if the given types are supported by the given handler signature */

zval* arr[2] = {op1, op2};
if (!zend_check_arg_types(fcic.function_handler, 2, arr))
{
if (zobj == Z_OBJ_P(op1) && Z_TYPE_P(op2) == IS_OBJECT) {
goto retry;
} else {
zend_type_error("The operand handlers do not support the given operand types!");
}
}

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) && Z_TYPE_P(op2) == IS_OBJECT) {
goto retry;
} else {
zend_type_error("The operand handlers do not support the given operand types!");
}
}

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;
Expand Down Expand Up @@ -1904,7 +2050,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 */
};