8000 [Debug] error stacking + fatal screaming + case testing by nicolas-grekas · Pull Request #10201 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Debug] error stacking + fatal screaming + case testing #10201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 19, 2014
Merged
Show file tree
Hide file tree
Changes from all 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
[Debug] error stacking+fatal screaming+case testing
  • Loading branch information
nicolas-grekas committed Feb 16, 2014
commit 6de362b9fb3a7b3eb7a1d87eb50cc9b010162fff
102 changes: 86 additions & 16 deletions src/Symfony/Component/Debug/DebugClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,67 @@
/**
* Autoloader checking if the class is really defined in the file found.
*
* The ClassLoader will wrap all registered autoloaders providing a
* findFile method and will throw an exception if a file is found but does
* The ClassLoader will wrap all registered autoloaders
* and will throw an exception if a file is found but does
* not declare the class.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Nicolas Grekas <p@tchwork.com>
*
* @api
*/
class DebugClassLoader
{
private $classFinder;
private $classLoader;
private $isFinder;
private $wasFinder;

/**
* Constructor.
*
* @param object $classFinder
* @param callable|object $classLoader
*
* @api
* @deprecated since 2.5, passing an object is deprecated and support for it will be removed in 3.0
*/
public function __construct($classFinder)
public function __construct($classLoader)
{
$this->classFinder = $classFinder;
$this->wasFinder = is_object($classLoader) && method_exists($classLoader, 'findFile');

if ($this->wasFinder) {
$this->classLoader = array($classLoader, 'loadClass');
$this->isFinder = true;
} else {
$this->classLoader = $classLoader;
$this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
}
}

/**
* Gets the wrapped class loader.
*
* @return object a class loader instance
* @return callable|object a class loader
*
* @deprecated since 2.5, returning an object is deprecated and support for it will be removed in 3.0
*/
public function getClassLoader()
{
return $this->classFinder;
if ($this->wasFinder) {
return $this->classLoader[0];
} else {
return $this->classLoader;
}
}

/**
* Replaces all autoloaders implementing a findFile method by a DebugClassLoader wrapper.
* Wraps all autoloaders
*/
public static function enable()
{
// Ensures we don't hit https://bugs.php.net/42098
class_exists(__NAMESPACE__.'\ErrorHandler', true);

if (!is_array($functions = spl_autoload_functions())) {
return;
}
Expand All @@ -63,8 +84,8 @@ public static function enable()
}

foreach ($functions as $function) {
if (is_array($function) && !$function[0] instanceof self && method_exists($function[0], 'findFile')) {
$function = array(new static($function[0]), 'loadClass');
if (!is_array($function) || !$function[0] instanceof self) {
$function = array(new static($function), 'loadClass');
}

spl_autoload_register($function);
Expand All @@ -86,7 +107,7 @@ public static function disable()

foreach ($functions as $function) {
if (is_array($function) && $function[0] instanceof self) {
$function[0] = $function[0]->getClassLoader();
$function = $function[0]->getClassLoader();
}

spl_autoload_register($function);
Expand All @@ -99,10 +120,14 @@ public static function disable()
* @param string $class A class name to resolve to file
*
* @return string|null
*
* @deprecated Deprecated since 2.5, to be removed in 3.0.
*/
public function findFile($class)
{
return $this->classFinder->findFile($class);
if ($this->wasFinder) {
return $this->classLoader[0]->findFile($class);
}
}

/**
Expand All @@ -116,10 +141,55 @@ public function findFile($class)
*/
public function loadClass($class)
{
if ($file = $this->classFinder->findFile($class)) {
require $file;
ErrorHandler::stackErrors();

try {
if ($this->isFinder) {
if ($file = $this->classLoader[0]->findFile($class)) {
require $file;
}
} else {
call_user_func($this->classLoader, $class);
$file = false;
}
} catch (\Exception $e) {
ErrorHandler::unstackErrors();

throw $e;
}

ErrorHandler::unstackErrors();

$exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));

if ($exists) {
$name = new \ReflectionClass($class);
$name = $name->getName();

if ($name !== $class) {
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
}
}

if ($file) {
if ('\\' == $class[0]) {
$class = substr($class, 1);
}

$i = -1;
$tail = str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
$len = strlen($tail);

do {
$tail = substr($tail, $i+1);
$len -= $i+1;

if (! substr_compare($file, $tail, -$len, $len, true) && substr_compare($file, $tail, -$len, $len, false)) {
throw new \RuntimeException(sprintf('Case mismatch between class and source file names: %s vs %s', $class, $file));
}
} while (false !== $i = strpos($tail, '\\'));

if (!class_exists($class, false) && !interface_exists($class, false) && (!function_exists('trait_exists') || !trait_exists($class, false))) {
if (! $exists) {
if (false !== strpos($class, '/')) {
throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
}
Expand Down
106 changes: 80 additions & 26 deletions src/Symfony/Component/Debug/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Konstantin Myakshin <koc-dp@yandex.ru>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ErrorHandler
{
Expand Down Expand Up @@ -57,6 +58,10 @@ class ErrorHandler
*/
private static $loggers = array();

private static $stackedErrors = array();

private static $stackedErrorLevels = array();

/**
* Registers the error handler.
*
Expand Down Expand Up @@ -121,45 +126,46 @@ public function handle($level, $message, $file = 'unknown', $line = 0, $context

if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
if (isset(self::$loggers['deprecation'])) {
if (version_compare(PHP_VERSION, '5.4', '<')) {
$stack = array_map(
function ($row) {
unset($row['args']);

return $row;
},
array_slice(debug_backtrace(false), 0, 10)
);
if (self::$stackedErrorLevels) {
self::$stackedErrors[] = func_get_args();
} else {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
}
if (version_compare(PHP_VERSION, '5.4', '<')) {
$stack = array_map(
function ($row) {
unset($row['args']);

return $row;
},
array_slice(debug_backtrace(false), 0, 10)
);
} else {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
}

self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
}
}

return true;
}

if ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
// make sure the ContextErrorException class is loaded (https://bugs.php.net/bug.php?id=65322)
if (!class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
require __DIR__.'/Exception/ContextErrorException.php';
}

$exception = new ContextErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line, $context);

// Exceptions thrown from error handlers are sometimes not caught by the exception
// handler, so we invoke it directly (https://bugs.php.net/bug.php?id=54275)
$exceptionHandler = set_exception_handler(function () {});
$exceptionHandler = set_exception_handler('var_dump');
restore_exception_handler();

if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
$exceptionHandler[0]->handle($exception);
if (self::$stackedErrorLevels) {
self::$stackedErrors[] = func_get_args();

if (!class_exists('Symfony\Component\Debug\Exception\DummyException')) {
require __DIR__.'/Exception/DummyException.php';
return true;
}

$exception = sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line);
$exception = new ContextErrorException($exception, 0, $level, $file, $line, $context);
$exceptionHandler[0]->handle($exception);

// we must stop the PHP script execution, as the exception has
// already been dealt with, so, let's throw an exception that
// will be caught by a dummy exception handler
Expand All @@ -179,13 +185,61 @@ function ($row) {
return false;
}

/**
* Configure the error handler for delayed handling.
* Ensures also that non-catchable fatal errors are never silenced.
*
* As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
* PHP has a compile stage where it behaves unusually. To workaround it,
* we plug an error handler that only stacks errors for later.
*
* The most important feature of this is to prevent
* autoloading until unstackErrors() is called.
*/
public static funct 1E0A ion stackErrors()
{
self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
}

/**
* Unstacks stacked errors and forwards to the regular handler
*/
public static function unstackErrors()
{
$level = array_pop(self::$stackedErrorLevels);

if (null !== $level) {
error_reporting($level);
}

if (empty(self::$stackedErrorLevels)) {
$errors = self::$stackedErrors;
self::$stackedErrors = array();

$errorHandler = set_error_handler('var_dump');
restore_error_handler();

if ($errorHandler) {
foreach ($errors as $e) {
call_user_func_array($errorHandler, $e);
}
}
}
}

public function handleFatal()
{
if (null === $error = error_get_last()) {
$this->reservedMemory = '';
$error = error_get_last();

while (self::$stackedErrorLevels) {
static::unstackErrors();
}

if (null === $error) {
return;
}

$this->reservedMemory = '';
$type = $error['type'];
if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
return;
Expand All @@ -206,7 +260,7 @@ public function handleFatal()
}

// get current exception handler
$exceptionHandler = set_exception_handler(function () {});
$exceptionHandler = set_exception_handler('var_dump');
restore_exception_handler();

if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
Expand Down
Loading
0