Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Debug] Allow throwing from __toString() with return trigger_error($e, E_USER_ERROR); #15076

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
Jun 25, 2015
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
47 changes: 46 additions & 1 deletion src/Symfony/Component/Debug/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class ErrorHandler
private static $reservedMemory;
private static $stackedErrors = array();
private static $stackedErrorLevels = array();
private static $toStringException = null;

/**
* Same init value as thrownErrors.
Expand Down Expand Up @@ -377,7 +378,10 @@ public function handleError($type, $message, $file, $line, array $context, array
}

if ($throw) {
if (($this->scopedErrors & $type) && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
if (null !== self::$toStringException) {
$throw = self::$toStringException;
self::$toStringException = null;
} elseif (($this->scopedErrors & $type) && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
// Checking for class existence is a work around for https://bugs.php.net/42098
$throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
} else {
Expand All @@ -392,6 +396,47 @@ public function handleError($type, $message, $file, $line, array $context, array
$throw->errorHandlerCanary = new ErrorHandlerCanary();
}

if (E_USER_ERROR & $type) {
$backtrace = $backtrace ?: $throw->getTrace();

for ($i = 1; isset($backtrace[$i]); ++$i) {
if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
&& '__toString' === $backtrace[$i]['function']
&& '->' === $backtrace[$i]['type']
&& !isset($backtrace[$i - 1]['class'])
&& ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
) {
// Here, we know trigger_error() has been called from __toString().
// HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
// A small convention allows working around the limitation:
// given a caught $e exception in __toString(), quitting the method with
// `return trigger_error($e, E_USER_ERROR);` allows this error handler
// to make $e get through the __toString() barrier.

foreach ($context as $e) {
if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
if (1 === $i) {
// On HHVM
$throw = $e;
break;
}
self::$toStringException = $e;

return true;
}
}

if (1 < $i) {
// On PHP (not on HHVM), display the original error message instead of the default one.
$this->handleException($throw);

// Stop the process by giving back the error to the native handler.
return false;
}
}
}
}

throw $throw;
}

Expand Down
27 changes: 27 additions & 0 deletions src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,33 @@ public function testHandleError()
}
}

public function testHandleUserError()
{
try {
$handler = ErrorHandler::register();
$handler->throwAt(0, true);

$e = null;
$x = new \Exception('Foo');

try {
$f = new Fixtures\ToStringThrower($x);
$f .= ''; // Trigger $f->__toString()
} catch (\Exception $e) {
}

$this->assertSame($x, $e);

restore_error_handler();
restore_exception_handler();
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();

throw $e;
}
}

public function testHandleException()
{
try {
Expand Down
24 changes: 24 additions & 0 deletions src/Symfony/Component/Debug/Tests/Fixtures/ToStringThrower.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class ToStringThrower
{
private $exception;

public function __construct(\Exception $e)
{
$this->exception = $e;
}

public function __toString()
{
try {
throw $this->exception;
} catch (\Exception $e) {
// Using user_error() here is on purpose so we do not forget
// that this alias also should work alongside with trigger_error().
return user_error($e, E_USER_ERROR);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use trigger_error everywhere else

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why I use user_error here: to make sure we do not forget about this alias.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment added about that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then there is no test for trigger_error is there?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true...

}
}
}