diff --git a/.travis.yml b/.travis.yml
index 746b228fe0..6c03aaf46a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,6 +31,7 @@ before_install:
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then (pecl install -f memcached-2.1.0 && echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) || echo "Let's continue without memcache extension"; fi;
+ - if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo "extension = $(pwd)/modules/symfony_debug.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini); fi;
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then php -i; fi;
- sudo locale-gen fr_FR.UTF-8 && sudo update-locale
# Set the COMPOSER_ROOT_VERSION to the right version according to the branch being built
diff --git a/src/Symfony/Component/Debug/CHANGELOG.md b/src/Symfony/Component/Debug/CHANGELOG.md
index ff7c6af2b7..31f0de9c23 100644
--- a/src/Symfony/Component/Debug/CHANGELOG.md
+++ b/src/Symfony/Component/Debug/CHANGELOG.md
@@ -1,6 +1,14 @@
CHANGELOG
=========
+2.7.0
+-----
+
+* added deprecations checking for parent interfaces/classes to DebugClassLoader
+* added ZTS support to symfony_debug extension
+* added symfony_debug_backtrace() to symfony_debug extension
+ to track the backtrace of fatal errors
+
2.6.0
-----
diff --git a/src/Symfony/Component/Debug/Exception/FatalErrorException.php b/src/Symfony/Component/Debug/Exception/FatalErrorException.php
index d142051ba9..41bb97c6a9 100644
--- a/src/Symfony/Component/Debug/Exception/FatalErrorException.php
+++ b/src/Symfony/Component/Debug/Exception/FatalErrorException.php
@@ -69,6 +69,11 @@ class FatalErrorException extends LegacyFatalErrorException
unset($frame);
$trace = array_reverse($trace);
+ } elseif (function_exists('symfony_debug_backtrace')) {
+ $trace = symfony_debug_backtrace();
+ if (0 < $traceOffset) {
+ array_splice($trace, 0, $traceOffset);
+ }
} else {
$trace = array();
}
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 0000000000..0672ef8f4a
--- /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 b0d1c58959..0000000000
--- 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 c935f67019..26d0e8c012 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 8dc5d4356a..0d7cb60232 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 30b25a25e2..4d41417b43 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 0000000000..ebe2f32d8f
--- /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 0000000000..4d52dbf457
--- /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
+ (
+ )
+
+ )
+
+)
diff --git a/src/Symfony/Component/Debug/Resources/ext/tests/003.phpt b/src/Symfony/Component/Debug/Resources/ext/tests/003.phpt
new file mode 100644
index 0000000000..1d464720b7
--- /dev/null
+++ b/src/Symfony/Component/Debug/Resources/ext/tests/003.phpt
@@ -0,0 +1,85 @@
+--TEST--
+Test ErrorHandler in case of fatal error
+--SKIPIF--
+
+--FILE--
+setExceptionHandler('print_r');
+
+if (function_exists('xdebug_disable')) {
+ xdebug_disable();
+}
+
+bar();
+?>
+--EXPECTF--
+Fatal error: Call to undefined function Symfony\Component\Debug\notexist() in %s on line %d
+Symfony\Component\Debug\Exception\UndefinedFunctionException Object
+(
+ [message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug".
+ [string:Exception:private] =>
+ [code:protected] => 0
+ [file:protected] => -
+ [line:protected] => %d
+ [trace:Exception:private] => Array
+ (
+ [0] => Array
+ (
+%A [function] => Symfony\Component\Debug\foo
+%A [args] => Array
+ (
+ )
+
+ )
+
+ [1] => Array
+ (
+%A [function] => Symfony\Component\Debug\bar
+%A [args] => Array
+ (
+ )
+
+ )
+%A
+ )
+
+ [previous:Exception:private] =>
+ [severity:protected] => 1
+)
diff --git a/src/Symfony/Component/Debug/phpunit.xml.dist b/src/Symfony/Component/Debug/phpunit.xml.dist
index 20b0313f0c..e99c4ddf44 100644
--- a/src/Symfony/Component/Debug/phpunit.xml.dist
+++ b/src/Symfony/Component/Debug/phpunit.xml.dist
@@ -14,6 +14,9 @@
./Tests/
+
+ ./Resources/ext/tests/
+