commit b237c017e26520be79413c68f02e7c272deded2d
Author: Damien Tournoud <damien@tournoud.net>
Date:   Mon Sep 8 12:56:56 2008 +0200

    error-handler-rework

diff --git includes/common.inc includes/common.inc
index 243e84c..7379ccc 100644
--- includes/common.inc
+++ includes/common.inc
@@ -595,36 +595,81 @@ function drupal_error_handler($errno, $message, $filename, $line, $context) {
   }
 
   if ($errno & (E_ALL)) {
-    $types = array(1 => 'error', 2 => 'warning', 4 => 'parse error', 8 => 'notice', 16 => 'core error', 32 => 'core warning', 64 => 'compile error', 128 => 'compile warning', 256 => 'user error', 512 => 'user warning', 1024 => 'user notice', 2048 => 'strict warning', 4096 => 'recoverable fatal error');
-
-    // For database errors, we want the line number/file name of the place that
-    // the query was originally called, not _db_query().
-    if (isset($context[DB_ERROR])) {
-      $backtrace = array_reverse(debug_backtrace());
-
-      // List of functions where SQL queries can originate.
-      $query_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
-
-      // Determine where query function was called, and adjust line/file
-      // accordingly.
-      foreach ($backtrace as $index => $function) {
-        if (in_array($function['function'], $query_functions)) {
-          $line = $backtrace[$index]['line'];
-          $filename = $backtrace[$index]['file'];
-          break;
-        }
-      }
-    }
+    $types = array(1 => 'Error', 2 => 'Warning', 4 => 'Parse error', 8 => 'Notice', 16 => 'Core error', 32 => 'Core warning', 64 => 'Compile error', 128 => 'Compile warning', 256 => 'User error', 512 => 'User warning', 1024 => 'User notice', 2048 => 'Strict warning', 4096 => 'Recoverable fatal error');
+    $backtrace = debug_backtrace();
+    _drupal_log_error($types[$errno], $message, $backtrace);
+  }
+}
 
-    $entry = $types[$errno] . ': ' . $message . ' in ' . $filename . ' on line ' . $line . '.';
+/**
+ * Log uncaught exceptions and output a meaningful error message to the user.
+ */
+function drupal_exception_handler($exception) {
+  // This is an exception handler.
+  $errno = -1;
+
+  $backtrace = $exception->getTrace();
+  // Add the line throwing the exception to the backtrace.
+  array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
 
-    // Force display of error messages in update.php.
-    if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) {
-      drupal_set_message($entry, 'error');
+  // For PDOException errors, we try to return the initial caller,
+  // not internal functions of the database layer.
+  if ($exception instanceof PDOException) {
+    // The first element is the call. The second element is the caller.
+    // We skip calls that occured in one of the classes of the database layer
+    // or in one of its global functions.
+    $db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
+    while (($caller = $backtrace[1]) &&
+         ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE)) ||
+         in_array($caller['function'], $db_functions))) {
+      // We remove that call.
+      array_shift($backtrace);
     }
+  }
 
-    watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR);
+  _drupal_log_error('uncaught exception', $exception->getMessage(), $backtrace);
+
+  // Uncaught exceptions are always fatal in PHP.
+  // We try to display an error message.
+  if (!isset($GLOBALS['theme'])) {
+    drupal_maintenance_theme();
+    $type = 'maintenance_page';
+  }
+  else {
+    $type = 'page';
   }
+  drupal_set_header('HTTP/1.1 503 Service unavailable');
+  drupal_set_title(t('Error'));
+  print theme($type, t('The website encountered an unexpected error. Please try again later.'));
+}
+
+function _drupal_log_error($type, $message, $backtrace) {
+  $caller = _drupal_get_last_caller($backtrace);
+
+  // Force display of error messages in update.php.
+  if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) {
+    drupal_set_message(t('@type: %message in %function (line %line of %file).', array('@type' => $type, '%message' => $message, '%function' => $caller['function'], '%line' => $caller['line'], '%file' => $caller['file'])), 'error');
+  }
+
+  // When running inside the testing framework, we relay the errors
+  // to the tested site by the way of HTTP headers.
+  if (preg_match("/^simpletest\d+/", $GLOBALS['db_prefix'])) {
+    static $number = 0;
+    $error_header = array(
+      'type' => $type,
+      'message' => $message,
+      'file' => $caller['file'],
+      'line' => $caller['line'],
+      'function' => $caller['function'],
+    );
+    foreach ($error_header as $k => $v) {
+      $error_header[$k] = rawurlencode($k) . '=' . rawurlencode($v);
+    }
+    header('X-Error-' . $number . ': ' . implode('&', $error_header));
+    $number++;
+  }
+
+  watchdog('php', '%error: %message in %function (line %line of %file).', array('%error' => $type, '%message' => $message, '%function' => $caller['function'], '%file' => $caller['file'], '%line' => $caller['line']), WATCHDOG_ERROR);  
 }
 
 /**
@@ -636,10 +681,15 @@ function drupal_error_handler($errno, $message, $filename, $line, $context) {
  *  An associative array with keys 'file', 'line' and 'function'.
  */
 function _drupal_get_last_caller($backtrace) {
+  // Errors that occurs inside PHP internal functions
+  // does not generate informations about file and line.
+  while (!isset($backtrace[0]['line'])) {
+    array_shift($backtrace);
+  }
   // The first trace is the call itself.
   // It gives us the line and the file of the last call.
   $call = $backtrace[0];
-  
+
   // The second call give us the function where the call originated.
   if (isset($backtrace[1])) {
     if (isset($backtrace[1]['class'])) {
@@ -2507,6 +2557,7 @@ function _drupal_bootstrap_full() {
   require_once './includes/actions.inc';
   // Set the Drupal custom error handler.
   set_error_handler('drupal_error_handler');
+  set_exception_handler('drupal_exception_handler');
   // Emit the correct charset HTTP header.
   drupal_set_header('Content-Type: text/html; charset=utf-8');
   // Detect string handling method
