*/
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' )
+ );
}
/**
}
}
} 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" .
*
* try {
* ...
- * } catch ( MWException $e ) {
+ * } catch ( Exception $e ) {
* $e->report();
* } catch ( Exception $e ) {
* echo $e->__toString();
case E_USER_DEPRECATED:
$levelName = 'Deprecated';
break;
+ case /* HHVM's FATAL_ERROR */ 16777217:
+ $levelName = 'Fatal';
+ break;
default:
$levelName = 'Unknown error';
break;
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
*
'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
* 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' );
}
}
}