@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+58 -10
+8 -1
src/aphront/configuration/AphrontApplicationConfiguration.php
··· 286 286 $original_exception = $ex; 287 287 } 288 288 289 + $response_exception = null; 289 290 try { 290 291 if ($original_exception) { 291 292 $response = $this->handleThrowable($original_exception); ··· 296 297 $response->setRequest($request); 297 298 298 299 self::writeResponse($sink, $response); 299 - } catch (Exception $response_exception) { 300 + } catch (Exception $ex) { 301 + $response_exception = $ex; 302 + } catch (Throwable $ex) { 303 + $response_exception = $ex; 304 + } 305 + 306 + if ($response_exception) { 300 307 // If we encountered an exception while building a normal response, then 301 308 // encountered another exception while building a response for the first 302 309 // exception, just throw the original exception. It is more likely to be
+2 -2
support/startup/PhabricatorStartup.php
··· 315 315 * 316 316 * @param string Brief description of the exception context, like 317 317 * `"Rendering Exception"`. 318 - * @param Exception The exception itself. 318 + * @param Throwable The exception itself. 319 319 * @param bool True if it's okay to show the exception's stack trace 320 320 * to the user. The trace will always be logged. 321 321 * @return exit This method **does not return**. ··· 324 324 */ 325 325 public static function didEncounterFatalException( 326 326 $note, 327 - Exception $ex, 327 + $ex, 328 328 $show_trace) { 329 329 330 330 $message = '['.$note.'/'.get_class($ex).'] '.$ex->getMessage();
+48 -7
webroot/index.php
··· 2 2 3 3 phabricator_startup(); 4 4 5 + $fatal_exception = null; 5 6 try { 6 7 PhabricatorStartup::beginStartupPhase('libraries'); 7 8 PhabricatorStartup::loadCoreLibraries(); ··· 12 13 PhabricatorStartup::beginStartupPhase('sink'); 13 14 $sink = new AphrontPHPHTTPSink(); 14 15 16 + // PHP introduced a "Throwable" interface in PHP 7 and began making more 17 + // runtime errors throw as "Throwable" errors. This is generally good, but 18 + // makes top-level exception handling that is compatible with both PHP 5 19 + // and PHP 7 a bit tricky. 20 + 21 + // In PHP 5, "Throwable" does not exist, so "catch (Throwable $ex)" catches 22 + // nothing. 23 + 24 + // In PHP 7, various runtime conditions raise an Error which is a Throwable 25 + // but NOT an Exception, so "catch (Exception $ex)" will not catch them. 26 + 27 + // To cover both cases, we "catch (Exception $ex)" to catch everything in 28 + // PHP 5, and most things in PHP 7. Then, we "catch (Throwable $ex)" to catch 29 + // everything else in PHP 7. For the most part, we only need to do this at 30 + // the top level. 31 + 32 + $main_exception = null; 15 33 try { 16 34 PhabricatorStartup::beginStartupPhase('run'); 17 35 AphrontApplicationConfiguration::runHTTPRequest($sink); 18 36 } catch (Exception $ex) { 37 + $main_exception = $ex; 38 + } catch (Throwable $ex) { 39 + $main_exception = $ex; 40 + } 41 + 42 + if ($main_exception) { 43 + $response_exception = null; 19 44 try { 20 45 $response = new AphrontUnhandledExceptionResponse(); 21 - $response->setException($ex); 46 + $response->setException($main_exception); 22 47 23 48 PhabricatorStartup::endOutputCapture(); 24 49 $sink->writeResponse($response); 25 - } catch (Exception $response_exception) { 26 - // If we hit a rendering exception, ignore it and throw the original 27 - // exception. It is generally more interesting and more likely to be 28 - // the root cause. 29 - throw $ex; 50 + } catch (Exception $ex) { 51 + $response_exception = $ex; 52 + } catch (Throwable $ex) { 53 + $response_exception = $ex; 54 + } 55 + 56 + // If we hit a rendering exception, ignore it and throw the original 57 + // exception. It is generally more interesting and more likely to be 58 + // the root cause. 59 + 60 + if ($response_exception) { 61 + throw $main_exception; 30 62 } 31 63 } 32 64 } catch (Exception $ex) { 33 - PhabricatorStartup::didEncounterFatalException('Core Exception', $ex, false); 65 + $fatal_exception = $ex; 66 + } catch (Throwable $ex) { 67 + $fatal_exception = $ex; 68 + } 69 + 70 + if ($fatal_exception) { 71 + PhabricatorStartup::didEncounterFatalException( 72 + 'Core Exception', 73 + $fatal_exception, 74 + false); 34 75 } 35 76 36 77 function phabricator_startup() {