1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-19 16:58:48 +02:00

Improve top-level fatal exception handling in PHP 7+

Summary:
Depends on D20137. Ref T13250. Ref T12101. In versions of PHP beyond 7, various engine errors are gradually changing from internal fatals or internal errors to `Throwables`, a superclass of `Exception`.

This is generally a good change, but code written against PHP 5.x before `Throwable` was introduced may not catch these errors, even when the code is intended to be a top-level exception handler.

(The double-catch pattern here and elsewhere is because `Throwable` does not exist in older PHP, so `catch (Throwable $ex)` catches nothing. The `Exception $ex` clause catches everything in old PHP, the `Throwable $ex` clause catches everything in newer PHP.)

Generalize some `Exception` into `Throwable`.

Test Plan:
  - Added a bogus function call to the rendering stack.
  - Before change: got a blank page.
  - After change: nice exception page.

{F6205012}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13250, T12101

Differential Revision: https://secure.phabricator.com/D20138
This commit is contained in:
epriestley 2019-02-11 09:58:45 -08:00
parent e03079aaaa
commit b9a1260ef5
3 changed files with 58 additions and 10 deletions

View file

@ -286,6 +286,7 @@ final class AphrontApplicationConfiguration
$original_exception = $ex; $original_exception = $ex;
} }
$response_exception = null;
try { try {
if ($original_exception) { if ($original_exception) {
$response = $this->handleThrowable($original_exception); $response = $this->handleThrowable($original_exception);
@ -296,7 +297,13 @@ final class AphrontApplicationConfiguration
$response->setRequest($request); $response->setRequest($request);
self::writeResponse($sink, $response); self::writeResponse($sink, $response);
} catch (Exception $response_exception) { } catch (Exception $ex) {
$response_exception = $ex;
} catch (Throwable $ex) {
$response_exception = $ex;
}
if ($response_exception) {
// If we encountered an exception while building a normal response, then // If we encountered an exception while building a normal response, then
// encountered another exception while building a response for the first // encountered another exception while building a response for the first
// exception, just throw the original exception. It is more likely to be // exception, just throw the original exception. It is more likely to be

View file

@ -315,7 +315,7 @@ final class PhabricatorStartup {
* *
* @param string Brief description of the exception context, like * @param string Brief description of the exception context, like
* `"Rendering Exception"`. * `"Rendering Exception"`.
* @param Exception The exception itself. * @param Throwable The exception itself.
* @param bool True if it's okay to show the exception's stack trace * @param bool True if it's okay to show the exception's stack trace
* to the user. The trace will always be logged. * to the user. The trace will always be logged.
* @return exit This method **does not return**. * @return exit This method **does not return**.
@ -324,7 +324,7 @@ final class PhabricatorStartup {
*/ */
public static function didEncounterFatalException( public static function didEncounterFatalException(
$note, $note,
Exception $ex, $ex,
$show_trace) { $show_trace) {
$message = '['.$note.'/'.get_class($ex).'] '.$ex->getMessage(); $message = '['.$note.'/'.get_class($ex).'] '.$ex->getMessage();

View file

@ -2,6 +2,7 @@
phabricator_startup(); phabricator_startup();
$fatal_exception = null;
try { try {
PhabricatorStartup::beginStartupPhase('libraries'); PhabricatorStartup::beginStartupPhase('libraries');
PhabricatorStartup::loadCoreLibraries(); PhabricatorStartup::loadCoreLibraries();
@ -12,25 +13,65 @@ try {
PhabricatorStartup::beginStartupPhase('sink'); PhabricatorStartup::beginStartupPhase('sink');
$sink = new AphrontPHPHTTPSink(); $sink = new AphrontPHPHTTPSink();
// PHP introduced a "Throwable" interface in PHP 7 and began making more
// runtime errors throw as "Throwable" errors. This is generally good, but
// makes top-level exception handling that is compatible with both PHP 5
// and PHP 7 a bit tricky.
// In PHP 5, "Throwable" does not exist, so "catch (Throwable $ex)" catches
// nothing.
// In PHP 7, various runtime conditions raise an Error which is a Throwable
// but NOT an Exception, so "catch (Exception $ex)" will not catch them.
// To cover both cases, we "catch (Exception $ex)" to catch everything in
// PHP 5, and most things in PHP 7. Then, we "catch (Throwable $ex)" to catch
// everything else in PHP 7. For the most part, we only need to do this at
// the top level.
$main_exception = null;
try { try {
PhabricatorStartup::beginStartupPhase('run'); PhabricatorStartup::beginStartupPhase('run');
AphrontApplicationConfiguration::runHTTPRequest($sink); AphrontApplicationConfiguration::runHTTPRequest($sink);
} catch (Exception $ex) { } catch (Exception $ex) {
$main_exception = $ex;
} catch (Throwable $ex) {
$main_exception = $ex;
}
if ($main_exception) {
$response_exception = null;
try { try {
$response = new AphrontUnhandledExceptionResponse(); $response = new AphrontUnhandledExceptionResponse();
$response->setException($ex); $response->setException($main_exception);
PhabricatorStartup::endOutputCapture(); PhabricatorStartup::endOutputCapture();
$sink->writeResponse($response); $sink->writeResponse($response);
} catch (Exception $response_exception) { } catch (Exception $ex) {
// If we hit a rendering exception, ignore it and throw the original $response_exception = $ex;
// exception. It is generally more interesting and more likely to be } catch (Throwable $ex) {
// the root cause. $response_exception = $ex;
throw $ex; }
// If we hit a rendering exception, ignore it and throw the original
// exception. It is generally more interesting and more likely to be
// the root cause.
if ($response_exception) {
throw $main_exception;
} }
} }
} catch (Exception $ex) { } catch (Exception $ex) {
PhabricatorStartup::didEncounterFatalException('Core Exception', $ex, false); $fatal_exception = $ex;
} catch (Throwable $ex) {
$fatal_exception = $ex;
}
if ($fatal_exception) {
PhabricatorStartup::didEncounterFatalException(
'Core Exception',
$fatal_exception,
false);
} }
function phabricator_startup() { function phabricator_startup() {