Merge "Remove a hack, and a hack for the hack, for MediaWiki UI input fields"
[lhc/web/wiklou.git] / includes / exception / MWExceptionHandler.php
index 9db04cb..5644231 100644 (file)
  */
 class MWExceptionHandler {
 
+       protected static $reservedMemory;
+       protected static $fatalErrorTypes = array(
+               E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR,
+               /* HHVM's FATAL_ERROR level */ 16777217,
+       );
+
        /**
         * Install handlers with PHP.
         */
        public static function installHandler() {
                set_exception_handler( array( 'MWExceptionHandler', 'handleException' ) );
                set_error_handler( array( 'MWExceptionHandler', 'handleError' ) );
+
+               // Reserve 16k of memory so we can report OOM fatals
+               self::$reservedMemory = str_repeat( ' ', 16384 );
+               register_shutdown_function(
+                       array( 'MWExceptionHandler', 'handleFatalError' )
+               );
        }
 
        /**
@@ -71,8 +83,7 @@ class MWExceptionHandler {
                                }
                        }
                } else {
-                       $message = "Unexpected non-MediaWiki exception encountered, of type \"" .
-                               get_class( $e ) . "\"";
+                       $message = "Exception encountered, of type \"" . get_class( $e ) . "\"";
 
                        if ( $wgShowExceptionDetails ) {
                                $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
@@ -130,7 +141,7 @@ class MWExceptionHandler {
         *
         *   try {
         *       ...
-        *   } catch ( MWException $e ) {
+        *   } catch ( Exception $e ) {
         *       $e->report();
         *   } catch ( Exception $e ) {
         *       echo $e->__toString();
@@ -194,6 +205,9 @@ class MWExceptionHandler {
                        case E_USER_DEPRECATED:
                                $levelName = 'Deprecated';
                                break;
+                       case /* HHVM's FATAL_ERROR */ 16777217:
+                               $levelName = 'Fatal';
+                               break;
                        default:
                                $levelName = 'Unknown error';
                                break;
@@ -207,6 +221,46 @@ class MWExceptionHandler {
                return false;
        }
 
+
+       /**
+        * Look for a fatal error as the cause of the request termination and log
+        * as an exception.
+        *
+        * Special handling is included for missing class errors as they may
+        * indicate that the user needs to install 3rd-party libraries via
+        * Composer or other means.
+        *
+        * @since 1.25
+        */
+       public static function handleFatalError() {
+               self::$reservedMemory = null;
+               $lastError = error_get_last();
+
+               if ( $lastError &&
+                       isset( $lastError['type'] ) &&
+                       in_array( $lastError['type'], self::$fatalErrorTypes )
+               ) {
+                       $msg = "Fatal Error: {$lastError['message']}";
+                       // HHVM: Class undefined: foo
+                       // PHP5: Class 'foo' not found
+                       if ( preg_match( "/Class (undefined: \w+|'\w+' not found)/",
+                               $lastError['message']
+                       ) ) {
+                               // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
+                               $msg = <<<TXT
+{$msg}
+
+MediaWiki or an installed extension requires this class but it is not embedded directly in MediaWiki's git repository and must be installed separately by the end user.
+
+Please see <a href="https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries">mediawiki.org</a> for help on installing the required components.
+TXT;
+                               // @codingStandardsIgnoreEnd
+                       }
+                       $e = new ErrorException( $msg, 0, $lastError['type'] );
+                       self::logError( $e );
+               }
+       }
+
        /**
         * Generate a string representation of an exception's stack trace
         *
@@ -384,6 +438,11 @@ class MWExceptionHandler {
                        'message' => $e->getMessage(),
                );
 
+               if ( $e instanceof ErrorException && ( error_reporting() & $e->getSeverity() ) === 0 ) {
+                       // Flag surpressed errors
+                       $exceptionData['suppressed'] = true;
+               }
+
                // Because MediaWiki is first and foremost a web application, we set a
                // 'url' key unconditionally, but set it to null if the exception does
                // not occur in the context of a web request, as a way of making that
@@ -429,16 +488,26 @@ class MWExceptionHandler {
         * Log an exception that wasn't thrown but made to wrap an error.
         *
         * @since 1.25
-        * @param Exception $e
+        * @param ErrorException $e
        */
-       protected static function logError( Exception $e ) {
+       protected static function logError( ErrorException $e ) {
                global $wgLogExceptionBacktrace;
 
-               $log = self::getLogMessage( $e );
-               if ( $wgLogExceptionBacktrace ) {
-                       wfDebugLog( 'error', $log . "\n" . $e->getTraceAsString() );
-               } else {
-                       wfDebugLog( 'error', $log );
+               // The set_error_handler callback is independent from error_reporting.
+               // Filter out unwanted errors manually (e.g. when wfSuppressWarnings is active).
+               if ( ( error_reporting() & $e->getSeverity() ) !== 0 ) {
+                       $log = self::getLogMessage( $e );
+                       if ( $wgLogExceptionBacktrace ) {
+                               wfDebugLog( 'error', $log . "\n" . $e->getTraceAsString() );
+                       } else {
+                               wfDebugLog( 'error', $log );
+                       }
+               }
+
+               // Include all errors in the json log (surpressed errors will be flagged)
+               $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK );
+               if ( $json !== false ) {
+                       wfDebugLog( 'error-json', $json, 'private' );
                }
        }
 }