diff --git a/Zend/tests/debug_info.phpt b/Zend/tests/debug_info.phpt new file mode 100644 index 0000000000000..c7c9f23be58c5 --- /dev/null +++ b/Zend/tests/debug_info.phpt @@ -0,0 +1,26 @@ +--TEST-- +Testing __debugInfo() magic method +--FILE-- +1, "\0*\0b"=>2, "\0Foo\0c"=>3]; + } +} + +$f = new Foo; +var_dump($f); +--EXPECT-- +object(Foo)#1 (3) { + ["a"]=> + int(1) + ["b":protected]=> + int(2) + ["c":"Foo":private]=> + int(3) +} diff --git a/Zend/zend.h b/Zend/zend.h index c8b57cd043ff7..b6e5ef0e4af1c 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -502,6 +502,7 @@ struct _zend_class_entry { union _zend_function *__call; union _zend_function *__callstatic; union _zend_function *__tostring; + union _zend_function *__debugInfo; union _zend_function *serialize_func; union _zend_function *unserialize_func; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 553060e0a07ea..5b9279124a14b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2018,6 +2018,9 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce, !memcmp(lcname, ZEND_TOSTRING_FUNC_NAME, sizeof(ZEND_TOSTRING_FUNC_NAME)-1) && fptr->common.num_args != 0 ) { zend_error(error_type, "Method %s::%s() cannot take arguments", ce->name, ZEND_TOSTRING_FUNC_NAME); + } 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", ce->name, ZEND_DEBUGINFO_FUNC_NAME); } } /* }}} */ @@ -2031,7 +2034,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio int count=0, unload=0; 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; + zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL, *__debugInfo = NULL; const char *lowercase_name; int fname_len; const char *lc_class_name = NULL; @@ -2180,6 +2183,8 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio __unset = reg_function; } else if ((fname_len == sizeof(ZEND_ISSET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_ISSET_FUNC_NAME, sizeof(ZEND_ISSET_FUNC_NAME) - 1)) { __isset = reg_function; + } else if ((fname_len == sizeof(ZEND_DEBUGINFO_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_DEBUGINFO_FUNC_NAME, sizeof(ZEND_DEBUGINFO_FUNC_NAME) - 1)) { + __debugInfo = reg_function; } else { reg_function = NULL; } @@ -2218,6 +2223,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio scope->__set = __set; scope->__unset = __unset; scope->__isset = __isset; + scope->__debugInfo = __debugInfo; if (ctor) { ctor->common.fn_flags |= ZEND_ACC_CTOR; if (ctor->common.fn_flags & ZEND_ACC_STATIC) { @@ -2281,6 +2287,11 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio } __isset->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC; } + if (__debugInfo) { + if (__debugInfo->common.fn_flags & ZEND_ACC_STATIC) { + zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __debugInfo->common.function_name); + } + } efree((char*)lc_class_name); } return SUCCESS; diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 569407b19a7ef..62a4a8307c5b9 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -197,6 +197,7 @@ typedef struct _zend_fcall_info_cache { class_container.__set = handle_propset; \ class_container.__unset = handle_propunset; \ class_container.__isset = handle_propisset; \ + class_container.__debugInfo = NULL; \ class_container.serialize_func = NULL; \ class_container.unserialize_func = NULL; \ class_container.serialize = NULL; \ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f789e3397f25c..34cc6cd5803ed 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1622,6 +1622,11 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) { zend_error(E_WARNING, "The magic method __invoke() must have public visibility and cannot be static"); } + + } else if ((name_len == sizeof(ZEND_DEBUGINFO_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_DEBUGINFO_FUNC_NAME, sizeof(ZEND_DEBUGINFO_FUNC_NAME)-1))) { + if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) { + zend_error(E_WARNING, "The magic method __debugInfo() must have public visibility and cannot be static"); + } } } else { char *class_lcname; @@ -1682,6 +1687,11 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) { zend_error(E_WARNING, "The magic method __invoke() must have public visibility and cannot be static"); } + } else if ((name_len == sizeof(ZEND_DEBUGINFO_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_DEBUGINFO_FUNC_NAME, sizeof(ZEND_DEBUGINFO_FUNC_NAME)-1))) { + if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) { + zend_error(E_WARNING, "The magic method __debugInfo() must have public visibility and cannot be static"); + } + CG(active_class_entry)->__debugInfo = (zend_function *) CG(active_op_array); } else if (!(fn_flags & ZEND_ACC_STATIC)) { CG(active_op_array)->fn_flags |= ZEND_ACC_ALLOW_STATIC; } @@ -3954,6 +3964,8 @@ static void zend_add_magic_methods(zend_class_entry* ce, const char* mname, uint ce->__callstatic = fe; } else if (!strncmp(mname, ZEND_TOSTRING_FUNC_NAME, mname_len)) { ce->__tostring = fe; + } else if (!strncmp(mname, ZEND_DEBUGINFO_FUNC_NAME, mname_len)) { + ce->__debugInfo = fe; } else if (ce->name_length + 1 == mname_len) { char *lowercase_name = emalloc(ce->name_length + 1); zend_str_tolower_copy(lowercase_name, ce->name, ce->name_length); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index f884df19466a5..9974c18431ab7 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -873,6 +873,7 @@ END_EXTERN_C() #define ZEND_TOSTRING_FUNC_NAME "__tostring" #define ZEND_AUTOLOAD_FUNC_NAME "__autoload" #define ZEND_INVOKE_FUNC_NAME "__invoke" +#define ZEND_DEBUGINFO_FUNC_NAME "__debuginfo" /* The following constants may be combined in CG(compiler_options) * to change the default compiler behavior */ diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 0af8e278b2d3d..ed5256ee57472 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -138,8 +138,37 @@ ZEND_API HashTable *zend_std_get_gc(zval *object, zval ***table, int *n TSRMLS_D ZEND_API HashTable *zend_std_get_debug_info(zval *object, int *is_temp TSRMLS_DC) /* {{{ */ { - *is_temp = 0; - return zend_std_get_properties(object TSRMLS_CC); + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval = NULL; + + if (!ce->__debugInfo) { + *is_temp = 0; + return Z_OBJ_HANDLER_P(object, get_properties) + ? Z_OBJ_HANDLER_P(object, get_properties)(object TSRMLS_CC) + : NULL; + } + + zend_call_method_with_0_params(&object, ce, &ce->__debugInfo, ZEND_DEBUGINFO_FUNC_NAME, &retval); + if (retval && Z_TYPE_P(retval) == IS_ARRAY) { + HashTable *ht = Z_ARRVAL_P(retval); + if (Z_REFCOUNT_P(retval) <= 1) { + *is_temp = 1; + efree(retval); + return ht; + } else { + *is_temp = 0; + zval_ptr_dtor(&retval); + } + return ht; + } + if (retval && Z_TYPE_P(retval) == IS_NULL) { + zval ret; + array_init(&ret); + *is_temp = 1; + return Z_ARRVAL(ret); + } + + zend_error_noreturn(E_ERROR, ZEND_DEBUGINFO_FUNC_NAME "() must return an array"); } /* }}} */ @@ -1639,7 +1668,7 @@ ZEND_API zend_object_handlers std_object_handlers = { zend_std_compare_objects, /* compare_objects */ zend_std_cast_object_tostring, /* cast_object */ NULL, /* count_elements */ - NULL, /* get_debug_info */ + zend_std_get_debug_info, /* get_debug_info */ zend_std_get_closure, /* get_closure */ zend_std_get_gc, /* get_gc */ NULL, /* do_operation */