Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6de362b

Browse filesBrowse files
[Debug] error stacking+fatal screaming+case testing
1 parent f828aee commit 6de362b
Copy full SHA for 6de362b

File tree

Expand file treeCollapse file tree

5 files changed

+276
-108
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+276
-108
lines changed

‎src/Symfony/Component/Debug/DebugClassLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/DebugClassLoader.php
+86-16Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,46 +14,67 @@
1414
/**
1515
* Autoloader checking if the class is really defined in the file found.
1616
*
17-
* The ClassLoader will wrap all registered autoloaders providing a
18-
* findFile method and will throw an exception if a file is found but does
17+
* The ClassLoader will wrap all registered autoloaders
18+
* and will throw an exception if a file is found but does
1919
* not declare the class.
2020
*
2121
* @author Fabien Potencier <fabien@symfony.com>
2222
* @author Christophe Coevoet <stof@notk.org>
23+
* @author Nicolas Grekas <p@tchwork.com>
2324
*
2425
* @api
2526
*/
2627
class DebugClassLoader
2728
{
28-
private $classFinder;
29+
private $classLoader;
30+
private $isFinder;
31+
private $wasFinder;
2932

3033
/**
3134
* Constructor.
3235
*
33-
* @param object $classFinder
36+
* @param callable|object $classLoader
3437
*
3538
* @api
39+
* @deprecated since 2.5, passing an object is deprecated and support for it will be removed in 3.0
3640
*/
37-
public function __construct($classFinder)
41+
public function __construct($classLoader)
3842
{
39-
$this->classFinder = $classFinder;
43+
$this->wasFinder = is_object($classLoader) && method_exists($classLoader, 'findFile');
44+
45+
if ($this->wasFinder) {
46+
$this->classLoader = array($classLoader, 'loadClass');
47+
$this->isFinder = true;
48+
} else {
49+
$this->classLoader = $classLoader;
50+
$this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
51+
}
4052
}
4153

4254
/**
4355
* Gets the wrapped class loader.
4456
*
45-
* @return object a class loader instance
57+
* @return callable|object a class loader
58+
*
59+
* @deprecated since 2.5, returning an object is deprecated and support for it will be removed in 3.0
4660
*/
4761
public function getClassLoader()
4862
{
49-
return $this->classFinder;
63+
if ($this->wasFinder) {
64+
return $this->classLoader[0];
65+
} else {
66+
return $this->classLoader;
67+
}
5068
}
5169

5270
/**
53-
* Replaces all autoloaders implementing a findFile method by a DebugClassLoader wrapper.
71+
* Wraps all autoloaders
5472
*/
5573
public static function enable()
5674
{
75+
// Ensures we don't hit https://bugs.php.net/42098
76+
class_exists(__NAMESPACE__.'\ErrorHandler', true);
77+
5778
if (!is_array($functions = spl_autoload_functions())) {
5879
return;
5980
}
@@ -63,8 +84,8 @@ public static function enable()
6384
}
6485

6586
foreach ($functions as $function) {
66-
if (is_array($function) && !$function[0] instanceof self && method_exists($function[0], 'findFile')) {
67-
$function = array(new static($function[0]), 'loadClass');
87+
if (!is_array($function) || !$function[0] instanceof self) {
88+
$function = array(new static($function), 'loadClass');
6889
}
6990

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

87108
foreach ($functions as $function) {
88109
if (is_array($function) && $function[0] instanceof self) {
89-
$function[0] = $function[0]->getClassLoader();
110+
$function = $function[0]->getClassLoader();
90111
}
91112

92113
spl_autoload_register($function);
@@ -99,10 +120,14 @@ public static function disable()
99120
* @param string $class A class name to resolve to file
100121
*
101122
* @return string|null
123+
*
124+
* @deprecated Deprecated since 2.5, to be removed in 3.0.
102125
*/
103126
public function findFile($class)
104127
{
105-
return $this->classFinder->findFile($class);
128+
if ($this->wasFinder) {
129+
return $this->classLoader[0]->findFile($class);
130+
}
106131
}
107132

108133
/**
@@ -116,10 +141,55 @@ public function findFile($class)
116141
*/
117142
public function loadClass($class)
118143
{
119-
if ($file = $this->classFinder->findFile($class)) {
120-
require $file;
144+
ErrorHandler::stackErrors();
145+
146+
try {
147+
if ($this->isFinder) {
148+
if ($file = $this->classLoader[0]->findFile($class)) {
149+
require $file;
150+
}
151+
} else {
152+
call_user_func($this->classLoader, $class);
153+
$file = false;
154+
}
155+
} catch (\Exception $e) {
156+
ErrorHandler::unstackErrors();
157+
158+
throw $e;
159+
}
160+
161+
ErrorHandler::unstackErrors();
162+
163+
$exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
164+
165+
if ($exists) {
166+
$name = new \ReflectionClass($class);
167+
$name = $name->getName();
168+
169+
if ($name !== $class) {
170+
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
171+
}
172+
}
173+
174+
if ($file) {
175+
if ('\\' == $class[0]) {
176+
$class = substr($class, 1);
177+
}
178+
179+
$i = -1;
180+
$tail = str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
181+
$len = strlen($tail);
182+
183+
do {
184+
$tail = substr($tail, $i+1);
185+
$len -= $i+1;
186+
187+
if (! substr_compare($file, $tail, -$len, $len, true) && substr_compare($file, $tail, -$len, $len, false)) {
188+
throw new \RuntimeException(sprintf('Case mismatch between class and source file names: %s vs %s', $class, $file));
189+
}
190+
} while (false !== $i = strpos($tail, '\\'));
121191

122-
if (!class_exists($class, false) && !interface_exists($class, false) && (!function_exists('trait_exists') || !trait_exists($class, false))) {
192+
if (! $exists) {
123193
if (false !== strpos($class, '/')) {
124194
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));
125195
}

‎src/Symfony/Component/Debug/ErrorHandler.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/ErrorHandler.php
+80-26Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*
2626
* @author Fabien Potencier <fabien@symfony.com>
2727
* @author Konstantin Myakshin <koc-dp@yandex.ru>
28+
* @author Nicolas Grekas <p@tchwork.com>
2829
*/
2930
class ErrorHandler
3031
{
@@ -57,6 +58,10 @@ class ErrorHandler
5758
*/
5859
private static $loggers = array();
5960

61+
private static $stackedErrors = array();
62+
63+
private static $stackedErrorLevels = array();
64+
6065
/**
6166
* Registers the error handler.
6267
*
@@ -121,45 +126,46 @@ public function handle($level, $message, $file = 'unknown', $line = 0, $context
121126

122127
if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
123128
if (isset(self::$loggers['deprecation'])) {
124-
if (version_compare(PHP_VERSION, '5.4', '<')) {
125-
$stack = array_map(
126-
function ($row) {
127-
unset($row['args']);
128-
129-
return $row;
130-
},
131-
array_slice(debug_backtrace(false), 0, 10)
132-
);
129+
if (self::$stackedErrorLevels) {
130+
self::$stackedErrors[] = func_get_args();
133131
} else {
134-
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
135-
}
132+
if (version_compare(PHP_VERSION, '5.4', '<')) {
133+
$stack = array_map(
134+
function ($row) {
135+
unset($row['args']);
136+
137+
return $row;
138+
},
139+
array_slice(debug_backtrace(false), 0, 10)
140+
);
141+
} else {
142+
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
143+
}
136144

137-
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
145+
self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack));
146+
}
138147
}
139148

140149
return true;
141150
}
142151

143152
if ($this->displayErrors && error_reporting() & $level && $this->level & $level) {
144-
// make sure the ContextErrorException class is loaded (https://bugs.php.net/bug.php?id=65322)
145-
if (!class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
146-
require __DIR__.'/Exception/ContextErrorException.php';
147-
}
148-
149-
$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);
150-
151153
// Exceptions thrown from error handlers are sometimes not caught by the exception
152154
// handler, so we invoke it directly (https://bugs.php.net/bug.php?id=54275)
153-
$exceptionHandler = set_exception_handler(function () {});
155+
$exceptionHandler = set_exception_handler('var_dump');
154156
restore_exception_handler();
155157

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

159-
if (!class_exists('Symfony\Component\Debug\Exception\DummyException')) {
160-
require __DIR__.'/Exception/DummyException.php';
162+
return true;
161163
}
162164

165+
$exception = sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line);
166+
$exception = new ContextErrorException($exception, 0, $level, $file, $line, $context);
167+
$exceptionHandler[0]->handle($exception);
168+
163169
// we must stop the PHP script execution, as the exception has
164170
// already been dealt with, so, let's throw an exception that
165171
// will be caught by a dummy exception handler
@@ -179,13 +185,61 @@ function ($row) {
179185
return false;
180186
}
181187

188+
/**
189+
* Configure the error handler for delayed handling.
190+
* Ensures also that non-catchable fatal errors are never silenced.
191+
*
192+
* As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
193+
* PHP has a compile stage where it behaves unusually. To workaround it,
194+
* we plug an error handler that only stacks errors for later.
195+
*
196+
* The most important feature of this is to prevent
197+
* autoloading until unstackErrors() is called.
198+
*/
199+
public static function stackErrors()
200+
{
201+
self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
202+
}
203+
204+
/**
205+
* Unstacks stacked errors and forwards to the regular handler
206+
*/
207+
public static function unstackErrors()
208+
{
209+
$level = array_pop(self::$stackedErrorLevels);
210+
211+
if (null !== $level) {
212+
error_reporting($level);
213+
}
214+
215+
if (empty(self::$stackedErrorLevels)) {
216+
$errors = self::$stackedErrors;
217+
self::$stackedErrors = array();
218+
219+
$errorHandler = set_error_handler('var_dump');
220+
restore_error_handler();
221+
222+
if ($errorHandler) {
223+
foreach ($errors as $e) {
224+
call_user_func_array($errorHandler, $e);
225+
}
226+
}
227+
}
228+
}
229+
182230
public function handleFatal()
183231
{
184-
if (null === $error = error_get_last()) {
232+
$this->reservedMemory = '';
233+
$error = error_get_last();
234+
235+
while (self::$stackedErrorLevels) {
236+
static::unstackErrors();
237+
}
238+
239+
if (null === $error) {
185240
return;
186241
}
187242

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

208262
// get current exception handler
209-
$exceptionHandler = set_exception_handler(function () {});
263+
$exceptionHandler = set_exception_handler('var_dump');
210264
restore_exception_handler();
211265

212266
if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.