diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -286,6 +286,7 @@ $original_exception = $ex; } + $response_exception = null; try { if ($original_exception) { $response = $this->handleThrowable($original_exception); @@ -296,7 +297,13 @@ $response->setRequest($request); 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 // encountered another exception while building a response for the first // exception, just throw the original exception. It is more likely to be diff --git a/support/startup/PhabricatorStartup.php b/support/startup/PhabricatorStartup.php --- a/support/startup/PhabricatorStartup.php +++ b/support/startup/PhabricatorStartup.php @@ -315,7 +315,7 @@ * * @param string Brief description of the exception context, like * `"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 * to the user. The trace will always be logged. * @return exit This method **does not return**. @@ -324,7 +324,7 @@ */ public static function didEncounterFatalException( $note, - Exception $ex, + $ex, $show_trace) { $message = '['.$note.'/'.get_class($ex).'] '.$ex->getMessage(); diff --git a/webroot/index.php b/webroot/index.php --- a/webroot/index.php +++ b/webroot/index.php @@ -2,6 +2,7 @@ phabricator_startup(); +$fatal_exception = null; try { PhabricatorStartup::beginStartupPhase('libraries'); PhabricatorStartup::loadCoreLibraries(); @@ -12,25 +13,65 @@ PhabricatorStartup::beginStartupPhase('sink'); $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 { PhabricatorStartup::beginStartupPhase('run'); AphrontApplicationConfiguration::runHTTPRequest($sink); } catch (Exception $ex) { + $main_exception = $ex; + } catch (Throwable $ex) { + $main_exception = $ex; + } + + if ($main_exception) { + $response_exception = null; try { $response = new AphrontUnhandledExceptionResponse(); - $response->setException($ex); + $response->setException($main_exception); PhabricatorStartup::endOutputCapture(); $sink->writeResponse($response); - } catch (Exception $response_exception) { - // 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. - throw $ex; + } catch (Exception $ex) { + $response_exception = $ex; + } catch (Throwable $ex) { + $response_exception = $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) { - 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() {