Remove unneeded uses of else{} in api/
[lhc/web/wiklou.git] / includes / Exception.php
index aeafda8..008be15 100644 (file)
@@ -140,9 +140,8 @@ class MWException extends Exception {
                global $wgShowExceptionDetails;
 
                if ( $wgShowExceptionDetails ) {
-                       return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
-                               '</p><p>Backtrace:</p><p>' .
-                               nl2br( htmlspecialchars( MWExceptionHandler::formatRedactedTrace( $this ) ) ) .
+                       return '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) .
+                               '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
                                "</p>\n";
                } else {
                        return "<div class=\"errorbox\">" .
@@ -166,8 +165,8 @@ class MWException extends Exception {
                global $wgShowExceptionDetails;
 
                if ( $wgShowExceptionDetails ) {
-                       return $this->getMessage() .
-                               "\nBacktrace:\n" . MWExceptionHandler::formatRedactedTrace( $this ) . "\n";
+                       return MWExceptionHandler::getLogMessage( $this ) .
+                               "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n";
                } else {
                        return "Set \$wgShowExceptionDetails = true; " .
                                "in LocalSettings.php to show detailed debugging information.\n";
@@ -619,8 +618,10 @@ class MWExceptionHandler {
                                $message = "MediaWiki internal error.\n\n";
 
                                if ( $wgShowExceptionDetails ) {
-                                       $message .= 'Original exception: ' . self::formatRedactedTrace( $e ) . "\n\n" .
-                                               'Exception caught inside exception handler: ' . $e2->__toString();
+                                       $message .= 'Original exception: ' . self::getLogMessage( $e ) .
+                                                "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
+                                                "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
+                                                "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
                                } else {
                                        $message .= "Exception caught inside exception handler.\n\n" .
                                                "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
@@ -640,9 +641,8 @@ class MWExceptionHandler {
                                get_class( $e ) . "\"";
 
                        if ( $wgShowExceptionDetails ) {
-                               $message .= "\nexception '" . get_class( $e ) . "' in " .
-                                       $e->getFile() . ":" . $e->getLine() . "\nStack trace:\n" .
-                                       self::formatRedactedTrace( $e ) . "\n";
+                               $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
+                                       self::getRedactedTraceAsString( $e ) . "\n";
                        }
 
                        if ( $cmdLine ) {
@@ -700,68 +700,66 @@ class MWExceptionHandler {
        }
 
        /**
-        * Get the stack trace from the exception as a string, redacting certain
-        * function arguments in the process.
-        * @param Exception $e The exception
-        * @return string The stack trace as a string
+        * Generate a string representation of an exception's stack trace
+        *
+        * Like Exception::getTraceAsString, but replaces argument values with
+        * argument type or class name.
+        *
+        * @param Exception $e
+        * @return string
         */
-       public static function formatRedactedTrace( Exception $e ) {
-               global $wgRedactedFunctionArguments;
-               $finalExceptionText = '';
+       public static function getRedactedTraceAsString( Exception $e ) {
+               $text = '';
 
-               // Unique value to indicate redacted parameters
-               $redacted = new stdClass();
-
-               foreach ( $e->getTrace() as $i => $call ) {
-                       $checkFor = array();
-                       if ( isset( $call['class'] ) ) {
-                               $checkFor[] = $call['class'] . '::' . $call['function'];
-                               foreach ( class_parents( $call['class'] ) as $parent ) {
-                                       $checkFor[] = $parent . '::' . $call['function'];
-                               }
-                       } else {
-                               $checkFor[] = $call['function'];
-                       }
-
-                       foreach ( $checkFor as $check ) {
-                               if ( isset( $wgRedactedFunctionArguments[$check] ) ) {
-                                       foreach ( (array)$wgRedactedFunctionArguments[$check] as $argNo ) {
-                                               $call['args'][$argNo] = $redacted;
-                                       }
-                               }
-                       }
-
-                       if ( isset( $call['file'] ) && isset( $call['line'] ) ) {
-                               $finalExceptionText .= "#{$i} {$call['file']}({$call['line']}): ";
+               foreach ( self::getRedactedTrace( $e ) as $level => $frame ) {
+                       if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
+                               $text .= "#{$level} {$frame['file']}({$frame['line']}): ";
                        } else {
                                // 'file' and 'line' are unset for calls via call_user_func (bug 55634)
                                // This matches behaviour of Exception::getTraceAsString to instead
                                // display "[internal function]".
-                               $finalExceptionText .= "#{$i} [internal function]: ";
+                               $text .= "#{$level} [internal function]: ";
                        }
 
-                       if ( isset( $call['class'] ) ) {
-                               $finalExceptionText .= $call['class'] . $call['type'] . $call['function'];
+                       if ( isset( $frame['class'] ) ) {
+                               $text .= $frame['class'] . $frame['type'] . $frame['function'];
                        } else {
-                               $finalExceptionText .= $call['function'];
+                               $text .= $frame['function'];
                        }
-                       $args = array();
-                       if ( isset( $call['args'] ) ) {
-                               foreach ( $call['args'] as $arg ) {
-                                       if ( $arg === $redacted ) {
-                                               $args[] = 'REDACTED';
-                                       } elseif ( is_object( $arg ) ) {
-                                               $args[] = 'Object(' . get_class( $arg ) . ')';
-                                       } elseif( is_array( $arg ) ) {
-                                               $args[] = 'Array';
-                                       } else {
-                                               $args[] = var_export( $arg, true );
-                                       }
-                               }
+
+                       if ( isset( $frame['args'] ) ) {
+                               $text .= '(' . implode( ', ', $frame['args'] ) . ")\n";
+                       } else {
+                               $text .= "()\n";
                        }
-                       $finalExceptionText .= '(' . implode( ', ', $args ) . ")\n";
                }
-               return $finalExceptionText . '#' . ( $i + 1 ) . ' {main}';
+
+               $level = $level + 1;
+               $text .= "#{$level} {main}";
+
+               return $text;
+       }
+
+       /**
+        * Return a copy of an exception's backtrace as an array.
+        *
+        * Like Exception::getTrace, but replaces each element in each frame's
+        * argument array with the name of its class (if the element is an object)
+        * or its type (if the element is a PHP primitive).
+        *
+        * @since 1.22
+        * @param Exception $e
+        * @return array
+        */
+       public static function getRedactedTrace( Exception $e ) {
+               return array_map( function ( $frame ) {
+                       if ( isset( $frame['args'] ) ) {
+                               $frame['args'] = array_map( function ( $arg ) {
+                                       return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
+                               }, $frame['args'] );
+                       }
+                       return $frame;
+               }, $e->getTrace() );
        }
 
        /**
@@ -781,6 +779,21 @@ class MWExceptionHandler {
                return $e->_mwLogId;
        }
 
+       /**
+        * If the exception occurred in the course of responding to a request,
+        * returns the requested URL. Otherwise, returns false.
+        *
+        * @since 1.23
+        * @return string|bool
+        */
+       public static function getURL() {
+               global $wgRequest;
+               if ( !isset( $wgRequest ) || $wgRequest instanceof FauxRequest ) {
+                       return false;
+               }
+               return $wgRequest->getRequestURL();
+       }
+
        /**
         * Return the requested URL and point to file and line number from which the
         * exception occurred.
@@ -790,23 +803,88 @@ class MWExceptionHandler {
         * @return string
         */
        public static function getLogMessage( Exception $e ) {
-               global $wgRequest;
-
                $id = self::getLogId( $e );
                $file = $e->getFile();
                $line = $e->getLine();
                $message = $e->getMessage();
+               $url = self::getURL() ?: '[no req]';
 
-               if ( isset( $wgRequest ) && !$wgRequest instanceof FauxRequest ) {
-                       $url = $wgRequest->getRequestURL();
-                       if ( !$url ) {
-                               $url = '[no URL]';
-                       }
-               } else {
-                       $url = '[no req]';
+               return "[$id] $url   Exception from line $line of $file: $message";
+       }
+
+       /**
+        * Serialize an Exception object to JSON.
+        *
+        * The JSON object will have keys 'id', 'file', 'line', 'message', and
+        * 'url'. These keys map to string values, with the exception of 'line',
+        * which is a number, and 'url', which may be either a string URL or or
+        * null if the exception did not occur in the context of serving a web
+        * request.
+        *
+        * If $wgLogExceptionBacktrace is true, it will also have a 'backtrace'
+        * key, mapped to the array return value of Exception::getTrace, but with
+        * each element in each frame's "args" array (if set) replaced with the
+        * argument's class name (if the argument is an object) or type name (if
+        * the argument is a PHP primitive).
+        *
+        * @par Sample JSON record ($wgLogExceptionBacktrace = false):
+        * @code
+        *  {
+        *    "id": "c41fb419",
+        *    "file": "/var/www/mediawiki/includes/cache/MessageCache.php",
+        *    "line": 704,
+        *    "message": "Non-string key given",
+        *    "url": "/wiki/Main_Page"
+        *  }
+        * @endcode
+        *
+        * @par Sample JSON record ($wgLogExceptionBacktrace = true):
+        * @code
+        *  {
+        *    "id": "dc457938",
+        *    "file": "/vagrant/mediawiki/includes/cache/MessageCache.php",
+        *    "line": 704,
+        *    "message": "Non-string key given",
+        *    "url": "/wiki/Main_Page",
+        *    "backtrace": [{
+        *      "file": "/vagrant/mediawiki/extensions/VisualEditor/VisualEditor.hooks.php",
+        *      "line": 80,
+        *      "function": "get",
+        *      "class": "MessageCache",
+        *      "type": "->",
+        *      "args": ["array"]
+        *    }]
+        *  }
+        * @endcode
+        *
+        * @since 1.23
+        * @param Exception $e
+        * @param bool $pretty Add non-significant whitespace to improve readability (default: false).
+        * @param int $escaping Bitfield consisting of FormatJson::.*_OK class constants.
+        * @return string|bool: JSON string if successful; false upon failure
+        */
+       public static function jsonSerializeException( Exception $e, $pretty = false, $escaping = 0 ) {
+               global $wgLogExceptionBacktrace;
+
+               $exceptionData = array(
+                       'id' => self::getLogId( $e ),
+                       'file' => $e->getFile(),
+                       'line' => $e->getLine(),
+                       'message' => $e->getMessage(),
+               );
+
+               // 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
+               // fact visible and explicit.
+               $exceptionData['url'] = self::getURL() ?: null;
+
+               if ( $wgLogExceptionBacktrace ) {
+                       // Argument values may not be serializable, so redact them.
+                       $exceptionData['backtrace'] = self::getRedactedTrace( $e );
                }
 
-               return "[$id] $url   Exception from line $line of $file: $message";
+               return FormatJson::encode( $exceptionData, $pretty, $escaping );
        }
 
        /**
@@ -824,11 +902,17 @@ class MWExceptionHandler {
                if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
                        $log = self::getLogMessage( $e );
                        if ( $wgLogExceptionBacktrace ) {
-                               wfDebugLog( 'exception', $log . "\n" . self::formatRedactedTrace( $e ) . "\n" );
+                               wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() . "\n" );
                        } else {
                                wfDebugLog( 'exception', $log );
                        }
+
+                       $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK );
+                       if ( $json !== false ) {
+                               wfDebugLog( 'exception-json', $json, false );
+                       }
                }
+
        }
 
 }