diff --git a/src/Symfony/Component/Debug/Resources/ext/README.md b/src/Symfony/Component/Debug/Resources/ext/README.md new file mode 100644 index 000000000000..0672ef8f4ace --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/README.md @@ -0,0 +1,133 @@ +Symfony Debug Extension +======================= + +This extension publishes several functions to help building powerful debugging tools. + +symfony_zval_info() +------------------- + +- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP, +- does work with references, preventing memory copying. + +Its behavior is about the same as: + +```php + gettype($array[$key]), + 'zval_hash' => /* hashed memory address of $array[$key] */, + 'zval_refcount' => /* internal zval refcount of $array[$key] */, + 'zval_isref' => /* is_ref status of $array[$key] */, + ); + + switch ($info['type']) { + case 'object': + $info += array( + 'object_class' => get_class($array[$key]), + 'object_refcount' => /* internal object refcount of $array[$key] */, + 'object_hash' => spl_object_hash($array[$key]), + 'object_handle' => /* internal object handle $array[$key] */, + ); + break; + + case 'resource': + $info += array( + 'resource_handle' => (int) $array[$key], + 'resource_type' => get_resource_type($array[$key]), + 'resource_refcount' => /* internal resource refcount of $array[$key] */, + ); + break; + + case 'array': + $info += array( + 'array_count' => count($array[$key]), + ); + break; + + case 'string': + $info += array( + 'strlen' => strlen($array[$key]), + ); + break; + } + + return $info; +} +``` + +symfony_debug_backtrace() +------------------------- + +This function works like debug_backtrace(), except that it can fetch the full backtrace in case of fatal errors: + +```php +function foo() { fatal(); } +function bar() { foo(); } + +function sd() { var_dump(symfony_debug_backtrace()); } + +register_shutdown_function('sd'); + +bar(); + +/* Will output +Fatal error: Call to undefined function fatal() in foo.php on line 42 +array(3) { + [0]=> + array(2) { + ["function"]=> + string(2) "sd" + ["args"]=> + array(0) { + } + } + [1]=> + array(4) { + ["file"]=> + string(7) "foo.php" + ["line"]=> + int(1) + ["function"]=> + string(3) "foo" + ["args"]=> + array(0) { + } + } + [2]=> + array(4) { + ["file"]=> + string(102) "foo.php" + ["line"]=> + int(2) + ["function"]=> + string(3) "bar" + ["args"]=> + array(0) { + } + } +} +*/ +``` + +Usage +----- + +The extension is compatible with ZTS mode, and should be supported by PHP5.3, 5.4, 5.5 and 5.6. +To enable the extension from source, run: + +``` + phpize + ./configure + make + sudo make install +``` diff --git a/src/Symfony/Component/Debug/Resources/ext/README.rst b/src/Symfony/Component/Debug/Resources/ext/README.rst deleted file mode 100644 index b0d1c58959fc..000000000000 --- a/src/Symfony/Component/Debug/Resources/ext/README.rst +++ /dev/null @@ -1,72 +0,0 @@ -Symfony Debug Extension -======================= - -This extension adds a ``symfony_zval_info($key, $array, $options = 0)`` function that: - -- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP, -- does work with references, preventing memory copying. - -Its behavior is about the same as: - -.. code-block:: php - - gettype($array[$key]), - 'zval_hash' => /* hashed memory address of $array[$key] */, - 'zval_refcount' => /* internal zval refcount of $array[$key] */, - 'zval_isref' => /* is_ref status of $array[$key] */, - ); - - switch ($info['type']) { - case 'object': - $info += array( - 'object_class' => get_class($array[$key]), - 'object_refcount' => /* internal object refcount of $array[$key] */, - 'object_hash' => spl_object_hash($array[$key]), - 'object_handle' => /* internal object handle $array[$key] */, - ); - break; - - case 'resource': - $info += array( - 'resource_handle' => (int) $array[$key], - 'resource_type' => get_resource_type($array[$key]), - 'resource_refcount' => /* internal resource refcount of $array[$key] */, - ); - break; - - case 'array': - $info += array( - 'array_count' => count($array[$key]), - ); - break; - - case 'string': - $info += array( - 'strlen' => strlen($array[$key]), - ); - break; - } - - return $info; - } - -To enable the extension from source, run: - -.. code-block:: sh - - phpize - ./configure - make - sudo make install - diff --git a/src/Symfony/Component/Debug/Resources/ext/php_symfony_debug.h b/src/Symfony/Component/Debug/Resources/ext/php_symfony_debug.h index c935f6701929..26d0e8c01203 100644 --- a/src/Symfony/Component/Debug/Resources/ext/php_symfony_debug.h +++ b/src/Symfony/Component/Debug/Resources/ext/php_symfony_debug.h @@ -13,7 +13,7 @@ extern zend_module_entry symfony_debug_module_entry; #define phpext_symfony_debug_ptr &symfony_debug_module_entry -#define PHP_SYMFONY_DEBUG_VERSION "1.0" +#define PHP_SYMFONY_DEBUG_VERSION "2.7" #ifdef PHP_WIN32 # define PHP_SYMFONY_DEBUG_API __declspec(dllexport) @@ -29,6 +29,8 @@ extern zend_module_entry symfony_debug_module_entry; ZEND_BEGIN_MODULE_GLOBALS(symfony_debug) intptr_t req_rand_init; + void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + zval *debug_bt; ZEND_END_MODULE_GLOBALS(symfony_debug) PHP_MINIT_FUNCTION(symfony_debug); @@ -40,11 +42,14 @@ PHP_GINIT_FUNCTION(symfony_debug); PHP_GSHUTDOWN_FUNCTION(symfony_debug); PHP_FUNCTION(symfony_zval_info); +PHP_FUNCTION(symfony_debug_backtrace); -static char *_symfony_debug_memory_address_hash(void *); +static char *_symfony_debug_memory_address_hash(void * TSRMLS_DC); static const char *_symfony_debug_zval_type(zval *); -static const char* _symfony_debug_get_resource_type(long); -static int _symfony_debug_get_resource_refcount(long); +static const char* _symfony_debug_get_resource_type(long TSRMLS_DC); +static int _symfony_debug_get_resource_refcount(long TSRMLS_DC); + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); #ifdef ZTS #define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v) diff --git a/src/Symfony/Component/Debug/Resources/ext/symfony_debug.c b/src/Symfony/Component/Debug/Resources/ext/symfony_debug.c index 8dc5d4356a81..0d7cb602320f 100644 --- a/src/Symfony/Component/Debug/Resources/ext/symfony_debug.c +++ b/src/Symfony/Component/Debug/Resources/ext/symfony_debug.c @@ -12,6 +12,9 @@ #endif #include "php.h" +#ifdef ZTS +#include "TSRM.h" +#endif #include "php_ini.h" #include "ext/standard/info.h" #include "php_symfony_debug.h" @@ -19,6 +22,13 @@ #include "ext/standard/php_lcg.h" #include "ext/spl/php_spl.h" #include "Zend/zend_gc.h" +#include "Zend/zend_builtin_functions.h" +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ +#include "ext/standard/php_array.h" +#include "Zend/zend_interfaces.h" +#include "SAPI.h" + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == 220090626 ZEND_DECLARE_MODULE_GLOBALS(symfony_debug) @@ -30,9 +40,28 @@ ZEND_END_ARG_INFO() const zend_function_entry symfony_debug_functions[] = { PHP_FE(symfony_zval_info, symfony_zval_arginfo) + PHP_FE(symfony_debug_backtrace, NULL) PHP_FE_END }; +PHP_FUNCTION(symfony_debug_backtrace) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } +#if IS_PHP_53 + zend_fetch_debug_backtrace(return_value, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(return_value, 1, 0, 0 TSRMLS_CC); +#endif + + if (!SYMFONY_DEBUG_G(debug_bt)) { + return; + } + + php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(SYMFONY_DEBUG_G(debug_bt)), 0 TSRMLS_CC); +} + PHP_FUNCTION(symfony_zval_info) { zval *key = NULL, *arg = NULL; @@ -40,7 +69,7 @@ PHP_FUNCTION(symfony_zval_info) HashTable *array = NULL; long options = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "zh|l", &key, &array, &options) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zh|l", &key, &array, &options) == FAILURE) { return; } @@ -62,13 +91,14 @@ PHP_FUNCTION(symfony_zval_info) array_init(return_value); add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1); - add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg), 16, 1); + add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg TSRMLS_CC), 16, 0); add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg)); add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg)); if (Z_TYPE_P(arg) == IS_OBJECT) { - static char hash[33] = {0}; - php_spl_object_hash(arg, (char *)hash); + char hash[33] = {0}; + + php_spl_object_hash(arg, (char *)hash TSRMLS_CC); add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1); add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount); add_assoc_string(return_value, "object_hash", hash, 1); @@ -77,17 +107,41 @@ PHP_FUNCTION(symfony_zval_info) add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg))); } else if(Z_TYPE_P(arg) == IS_RESOURCE) { add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg)); - add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg)), 1); - add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg))); + add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg) TSRMLS_CC), 1); + add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg) TSRMLS_CC)); } else if (Z_TYPE_P(arg) == IS_STRING) { add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg)); } } -static const char* _symfony_debug_get_resource_type(long rsid) +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) +{ + TSRMLS_FETCH(); + zval *retval; + + switch (type) { + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + ALLOC_INIT_ZVAL(retval); +#if IS_PHP_53 + zend_fetch_debug_backtrace(retval, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(retval, 1, 0, 0 TSRMLS_CC); +#endif + SYMFONY_DEBUG_G(debug_bt) = retval; + } + + SYMFONY_DEBUG_G(old_error_cb)(type, error_filename, error_lineno, format, args); +} + +static const char* _symfony_debug_get_resource_type(long rsid TSRMLS_DC) { const char *res_type; - res_type = zend_rsrc_list_get_rsrc_type(rsid); + res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC); if (!res_type) { return "Unknown"; @@ -96,7 +150,7 @@ static const char* _symfony_debug_get_resource_type(long rsid) return res_type; } -static int _symfony_debug_get_resource_refcount(long rsid) +static int _symfony_debug_get_resource_refcount(long rsid TSRMLS_DC) { zend_rsrc_list_entry *le; @@ -107,21 +161,21 @@ static int _symfony_debug_get_resource_refcount(long rsid) return 0; } -static char *_symfony_debug_memory_address_hash(void *address) +static char *_symfony_debug_memory_address_hash(void *address TSRMLS_DC) { - static char result[17] = {0}; + char *result = NULL; intptr_t address_rand; if (!SYMFONY_DEBUG_G(req_rand_init)) { if (!BG(mt_rand_is_seeded)) { php_mt_srand(GENERATE_SEED() TSRMLS_CC); } - SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(); + SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(TSRMLS_C); } address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init); - snprintf(result, 17, "%016zx", address_rand); + spprintf(&result, 17, "%016zx", address_rand); return result; } @@ -187,7 +241,7 @@ ZEND_GET_MODULE(symfony_debug) PHP_GINIT_FUNCTION(symfony_debug) { - symfony_debug_globals->req_rand_init = 0; + memset(symfony_debug_globals, 0 , sizeof(*symfony_debug_globals)); } PHP_GSHUTDOWN_FUNCTION(symfony_debug) @@ -197,11 +251,16 @@ PHP_GSHUTDOWN_FUNCTION(symfony_debug) PHP_MINIT_FUNCTION(symfony_debug) { + SYMFONY_DEBUG_G(old_error_cb) = zend_error_cb; + zend_error_cb = symfony_debug_error_cb; + return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(symfony_debug) { + zend_error_cb = SYMFONY_DEBUG_G(old_error_cb); + return SUCCESS; } diff --git a/src/Symfony/Component/Debug/Resources/ext/tests/001.phpt b/src/Symfony/Component/Debug/Resources/ext/tests/001.phpt index 30b25a25e223..4d41417b436e 100644 --- a/src/Symfony/Component/Debug/Resources/ext/tests/001.phpt +++ b/src/Symfony/Component/Debug/Resources/ext/tests/001.phpt @@ -3,7 +3,7 @@ Test symfony_zval_info API --SKIPIF-- --FILE-- - string(32) "%s" ["object_handle"]=> - int(1) + int(%d) } array(5) { ["type"]=> @@ -112,7 +112,7 @@ array(7) { ["zval_isref"]=> bool(false) ["resource_handle"]=> - int(4) + int(%d) ["resource_type"]=> string(6) "stream" ["resource_refcount"]=> diff --git a/src/Symfony/Component/Debug/Resources/ext/tests/002.phpt b/src/Symfony/Component/Debug/Resources/ext/tests/002.phpt new file mode 100644 index 000000000000..ebe2f32d8f30 --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/tests/002.phpt @@ -0,0 +1,64 @@ +--TEST-- +Test symfony_debug_backtrace in case of fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Call to undefined function notexist() in %s on line %d +Array +( + [0] => Array + ( + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => foo + [args] => Array + ( + ) + + ) + + [2] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/src/Symfony/Component/Debug/Resources/ext/tests/002_1.phpt b/src/Symfony/Component/Debug/Resources/ext/tests/002_1.phpt new file mode 100644 index 000000000000..4d52dbf45702 --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/tests/002_1.phpt @@ -0,0 +1,47 @@ +--TEST-- +Test symfony_debug_backtrace in case of non fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Array +( + [0] => Array + ( + [file] => %s + [line] => %d + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +)