8000 bug #25408 [Debug] Fix catching fatal errors in case of nested error … · symfony/symfony@891b321 · GitHub
[go: up one dir, main page]

Skip to content

Commit 891b321

Browse files
committed
bug #25408 [Debug] Fix catching fatal errors in case of nested error handlers (nicolas-grekas)
This PR was merged into the 2.7 branch. Discussion ---------- [Debug] Fix catching fatal errors in case of nested error handlers | Q | A | ------------- | --- | Branch? | 2.7 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #8703, #16980 | License | MIT | Doc PR | - Fixing a bug from 2013 :) Commits ------- 27dc9a6 [Debug] Fix catching fatal errors in case of nested error handlers
2 parents 0a7c659 + 27dc9a6 commit 891b321

File tree

3 files changed

+106
-12
lines changed

3 files changed

+106
-12
lines changed

src/Symfony/Component/Debug/ErrorHandler.php

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ public function handleException($exception, array $error = null)
485485
$exception = new FatalThrowableError($exception);
486486
}
487487
$type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
488+
$handlerException = null;
488489

489490
if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
490491
$e = array(
@@ -529,18 +530,20 @@ public function handleException($exception, array $error = null)
529530
}
530531
}
531532
}
532-
if (empty($this->exceptionHandler)) {
533-
throw $exception; // Give back $exception to the native handler
534-
}
535533
try {
536-
call_user_func($this->exceptionHandler, $exception);
534+
if (null !== $this->exceptionHandler) {
535+
return \call_user_func($this->exceptionHandler, $exception);
536+
}
537+
$handlerException = $handlerException ?: $exception;
537538
} catch (\Exception $handlerException) {
538539
} catch (\Throwable $handlerException) {
539540
}
540-
if (isset($handlerException)) {
541-
$this->exceptionHandler = null;
542-
$this->handleException($handlerException);
541+
$this->exceptionHandler = null;
542+
if ($exception === $handlerException) {
543+
self::$reservedMemory = null; // Disable the fatal error handler
544+
throw $exception; // Give back $exception to the native handler
543545
}
546+
$this->handleException($handlerException);
544547
}
545548

546549
/**
@@ -556,15 +559,30 @@ public static function handleFatalError(array $error = null)
556559
return;
557560
}
558561

559-
self::$reservedMemory = null;
562+
$handler = self::$reservedMemory = null;
563+
$handlers = array();
560564

561-
$handler = set_error_handler('var_dump');
562-
$handler = is_array($handler) ? $handler[0] : null;
563-
restore_error_handler();
565+
while (!is_array($handler) || !$handler[0] instanceof self) {
566+
$handler = set_exception_handler('var_dump');
567+
restore_exception_handler();
564568

565-
if (!$handler instanceof self) {
569+
if (!$handler) {
570+
break;
571+
}
572+
restore_exception_handler();
573+
array_unshift($handlers, $handler);
574+
}
575+
foreach ($handlers as $h) {
576+
set_exception_handler($h);
577+
}
578+
if (!$handler) {
566579
return;
567580
}
581+
if ($handler !== $h) {
582+
$handler[0]->setExceptionHandler($h);
583+
}
584+
$handler = $handler[0];
585+
$handlers = array();
568586

569587
if ($exit = null === $error) {
570588
$error = error_get_last();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
Test rethrowing in custom exception handler
3+
--FILE--
4+
<?php
5+
6+
namespace Symfony\Component\Debug;
7+
8+
$vendor = __DIR__;
9+
while (!file_exists($vendor.'/vendor')) {
10+
$vendor = dirname($vendor);
11+
}
12+
require $vendor.'/vendor/autoload.php';
13+
14+
if (true) {
15+
class TestLogger extends \Psr\Log\AbstractLogger
16+
{
17+
public function log($level, $message, array $context = array())
18+
{
19+
echo $message, "\n";
20+
}
21+
}
22+
}
23+
24+
set_exception_handler(function ($e) { echo 123; throw $e; });
25+
ErrorHandler::register()->setDefaultLogger(new TestLogger());
26+
ini_set('display_errors', 1);
27+
28+
throw new \Exception('foo');
29+
30+
?>
31+
--EXPECTF--
32+
Uncaught Exception: foo
33+
123
34+
Fatal error: Uncaught %s:25
35+
Stack trace:
36+
%a
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
Test catching fatal errors when handlers are nested
3+
--FILE--
4+
<?php
5+
6+
namespace Symfony\Component\Debug;
7+
8+
$vendor = __DIR__;
9+
while (!file_exists($vendor.'/vendor')) {
10+
$vendor = dirname($vendor);
11+
}
12+
require $vendor.'/vendor/autoload.php';
13+
14+
Debug::enable();
15+
ini_set('display_errors', 0);
16+
17+
$eHandler = set_error_handler('var_dump');
18+
$xHandler = set_exception_handler('var_dump');
19+
20+
var_dump(array(
21+
$eHandler[0] === $xHandler[0] ? 'Error and exception handlers do match' : 'Error and exception handlers are different',
22+
));
23+
24+
$eHandler[0]->setExceptionHandler('print_r');
25+
26+
if (true) {
27+
class Broken implements \Serializable {};
28+
}
29+
30+
?>
31+
--EXPECTF--
32+
array(1) {
33+
[0]=>
34+
string(37) "Error and exception handlers do match"
35+
}
36+
object(Symfony\Component\Debug\Exception\FatalErrorException)#4 (8) {
37+
["message":protected]=>
38+
string(199) "Error: Class Symfony\Component\Debug\Broken contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize)"
39+
%a
40+
}

0 commit comments

Comments
 (0)
0