Updated: Comment #0

Problem/Motivation

Currently, we attempt to treat E_RECOVERABLE_ERROR as a fatal. However, what we do with it is set the page title to "Error" (with a function we're trying to eliminate), create a Response object, return it... and then ignore it entirely. That means the program continues with no message other than the page title mysteriously being set to "Error". This is doubleplusungood.

The most common source of that error is a class name mismatch in a type hint, usually caused by a bad/missing "use" statement. That makes finding a common typo way harder than it should be.

Proposed resolution

Convert E_RECOVERABLE_ERROR to an ErrorException object and throw that. We already have an exception handling pipeline with ExceptionController, so it will get handled the same as any other exception and the message actually displayed. However, it also lets the calling code catch the exception and handle it more intelligently if it is so inclined. Currently it cannot do that.

Remaining tasks

Decide if this is still relevant.

We really ought to clean up the entirety of errors.inc, but that's for another time.

User interface changes

None.

API changes

None.

Comments

Crell’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new5.98 KB

And patch.

neclimdul’s picture

Clearly that's a valid problem. Seems like we're treating RECOVERABLE terribly. Granted its pretty akward to handle generically anyways. I'd have to look at this closer but my gut doesn't like the solution though. RECOVERABLE is its own brokenish thing and it doesn't seem like exceptions matches it correctly.

NOTE: The RFC this was based on didn't pass either. https://wiki.php.net/rfc/engine_exceptions.

Crell’s picture

"Errors you can handle intelligently in context to fail gracefully", in PHP, mean exceptions. Personally I think we should be treating type mismatches as fatal, which this does. (The current code pretends to, but lies.)

neclimdul’s picture

Ok well looking at the patch I've got these observations.

  1. +++ b/core/includes/errors.inc
    @@ -64,6 +64,24 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c
         _drupal_log_error(array(
    

    This log_error references E_RECOVERABLE but it will never be reached because we're throwing an exception now.

  2. +++ b/core/includes/errors.inc
    @@ -64,6 +64,24 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c
    +      throw new ErrorException($message, $error_level, 0, $filename, $line);
    

    This subtly changes PHP's behavior too because it stops code execution. Granted, code relying on being able to run after a E_RECOVERABLE happens is probably trouble but it was one of the reasons for blocking the RFC and it really is hard to expect how external libraries work.

neclimdul’s picture

+++ b/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php
@@ -91,6 +91,15 @@ function testErrorHandler() {
+    $this->verbose($this->content);

The request is already logged with verbose so I don't think this is needed.

Crell’s picture

#4.2: It changes PHP's behavior to what the code we already had was attempting to do; vis, it stops Drupal execution and shows an error page. Using an exception simply means that it actually happens now.

Crell’s picture

StatusFileSize
new6.22 KB

Attached patch addresses #4.1 and #5.

neclimdul’s picture

Bah, while I'm still not a fan, it seems you're correct. This is actually a regression in functionality.

http://drupalcode.org/project/drupal.git/blob/refs/heads/7.x:/includes/e...

dave reid’s picture

neclimdul’s picture

Yeah, that's exactly the calling code triggering the exit that stops code flow.

), $error_level == E_RECOVERABLE_ERROR);

This passes $fatal as true causing the code I linked to trigger exiting and stopping code flow.

sun’s picture

Thanks for creating this issue! — I ran into this scenario a couple of times myself already (especially in the installer), but didn't take the time to figure out what's going on/wrong :)

  1. +++ b/core/includes/errors.inc
    @@ -64,6 +64,24 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c
    +    if ($error_level == E_RECOVERABLE_ERROR) {
    ...
    +      throw new ErrorException($message, $error_level, 0, $filename, $line);
    

    I'm a bit confused: The error/exception handler handles an E_RECOVERABLE_ERROR and throws an ErrorException with code E_RECOVERABLE_ERROR — doesn't that result in infinite recursion?

    I guess PHP has some built-in magic for this use-case?

    Perhaps more interesting: Aren't we losing the (back)trace information, or is that retained as well?

    Lastly, the example usage on http://php.net/manual/en/class.errorexception.php passes the $error_level value as the third parameter instead of the second, which appears to comply with ErrorException::__construct():

    public __construct ([ string $message = "" [, int $code = 0 [, int $severity = 1 ...
    

    The example passes 0 as $code, and $error_level as $severity.

  2. +++ b/core/includes/errors.inc
    @@ -64,6 +64,24 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c
    +      // Initialize a maintenance theme if the bootstrap was not complete.
    +      // Do it early because drupal_set_message() triggers a drupal_theme_initialize().
    +      if (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL) {
    +        unset($GLOBALS['theme']);
    +        if (!defined('MAINTENANCE_MODE')) {
    +          define('MAINTENANCE_MODE', 'error');
    +        }
    +        drupal_maintenance_theme();
    +      }
    

    Actually, I wonder whether this is still necessary in D8...?

    DRUPAL_BOOTSTRAP_FULL ought to be a thing of the past, no? ;)

    I'd be eager to simply remove it and see what happens, but happy to defer that to a separate issue ;)

  3. +++ b/core/includes/errors.inc
    @@ -178,15 +196,6 @@ function error_displayable($error = NULL) {
     function _drupal_log_error($error, $fatal = FALSE) {
    

    It looks like the $fatal argument is obsolete with this change?

  4. +++ b/core/modules/system/tests/modules/error_test/lib/Drupal/error_test/Controller/ErrorTestController.php
    @@ -30,6 +31,22 @@ public function generateWarnings($collect_errors = FALSE) {
    +    } catch (\ErrorException $e) {
    ...
    +    }
    +    catch (\Exception $e) {
    

    I have a clear preference (Drupal's) and I normally wouldn't comment on this in favor of making progress, but within a single patch, it would be great to decide on one style? ;-)

Crell’s picture

1) No recursion. The PHP error handler doesn't touch exceptions; there's an entirely separate callback for that. Although... if that calls _drupal_log_error(), too, that could be a problem. We'd need to generate an error pre-kernel for that to be the case, though (since once we're in the kernel exceptions will get caught and never get to the global exception handler), so I don't know how to write a test module for that.

Although... we already have a try-catch block in index.php, so at least when using index.php *the global exception handler never gets called at all*. Oh Drupal...

2) DRUPAL_BOOTSTRAP_FULL isn't gone yet, AFAIK. I wasn't sure if that's still necessary or not. I just moved it around. I didn't test to see what happens if we remove it. (I'd be more than happy to see that global-state-aware code die if possible.)

3) Nope. _drupal_log_error() is, weirdly, called from one or two other places as well. I didn't want to get into a full refactor of our entire error handling here, although we're over-due for that, certainly.

4) The first is just me missing the enter key while typing. :-) Will fix in the next draft if someone doesn't beat me to it.

damien tournoud’s picture

All this is really messed up, and in dire need of clean-up.

Let just bite the bullet and transform all errors into exceptions, and remove all errors.inc completely.

Crell’s picture

My knee-jerk response is to agree, Damien, but that means every E_NOTICE becomes a probably-site-killing error. I've done that for my own projects before, but may not fly as well in Drupal. Also, some PHP native functions throw E_NOTICE or E_WARNING (rightly or wrongly), which means those become site-killing exceptions.

Crell’s picture

Related, someone just brought this question up on FIG: https://groups.google.com/forum/#!msg/php-fig/T4mtQc6qyaE/q0Wq1leM_KAJ

sun’s picture

Let just bite the bullet and transform all errors into exceptions, and remove all errors.inc completely.

That's more or less what I've done in #1247666-5: Replace @function calls with try/catch ErrorException blocks, so if we want to go that direction, then I think this issue should be marked as duplicate.

jhedstrom’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll
hitesh-jain’s picture

Assigned: Crell » Unassigned
Status: Needs work » Needs review
StatusFileSize
new5.53 KB

Rerolled the patch to latest Release.

Status: Needs review » Needs work

The last submitted patch, 18: convert-2165589-18.patch, failed testing.

Crell’s picture

Note that PHP 7 will do this automatically; or rather, E_RECOVERABLE_ERROR now throws an Error (which is not an exception but a sibling of it).

Crell’s picture

+++ b/core/modules/system/src/Tests/System/ErrorHandlerTest.php
@@ -115,6 +115,14 @@ function testErrorHandler() {
+  function testRecoverableErrors() {
+    $this->drupalGet('error-test/generate-recoverable-error');
+    $this->assertResponse(200, 'Received expected HTTP status code.');
+  }

That's definitely wrong. We should get a 500 error on E_RECOVERABLE_ERRORs, assuming they propagate up uncaught that far.

The last submitted patch, 7: 2165589-error-handling.patch, failed testing.

vlklavanya’s picture

StatusFileSize
new3.84 KB

Code has been re rolled and committed as patch referring #18 patch

siva_epari’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll +#DHCSprint

Queuing for review

Status: Needs review » Needs work

The last submitted patch, 24: convert-2165589-19.patch, failed testing.

Aki Tendo’s picture

https://wiki.php.net/rfc/engine_exceptions_for_php7

PHP 7 now throws specific exceptions for these errors. Perhaps it would be best to write a handler for these that throws the same exception as PHP 7 is going to throw in the PHP 5.x environment, allowing further error handling and logging to work in the same manner. For the assert tools patch I already wrote code to convert runtime assertions in this manner, but it would make sense to expand the scope of this out as much as possible.

As far as I can tell all E_RECOVERABLE_ERRORs are now Type Exceptions. So perhaps that is the route to take?

Crell’s picture

Many E_RECOVERABLE_ERRORs are now type exceptions, but not all. That's just the most common. Also, many of the newly thrown things are Errors, not Exceptions, so we'd have to shim Throwable, too.

Aki Tendo’s picture

In PHP 5.x though, was E_RECOVERABLE_ERROR used for anything but the type hints? I've been looking for awhile and haven't found an example.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Category: Bug report » Task
Issue summary: View changes
Status: Needs work » Needs review
Issue tags: +Bug Smash Initiative

This was a bugsmash triage issue of the day. It was discussed by lendude, longwave and DanielVeza.

They agreed this issue is a task. And longwave questioned if this is still valid, read comments #27

The question to determine if this is still valid.

mfb’s picture

Status: Needs review » Closed (outdated)

Yes, I would say this can be closed as outdated. This issue and all comments up thru #29 date from before php 7.0.0 was released, and by now php has converted almost all cases of E_RECOVERABLE_ERROR to throwing a Throwable - which is caught by Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber or, in cases where that event subscriber is not in play, by _drupal_exception_handler().

I tested one of the few remaining E_RECOVERABLE_ERROR still in existence with this code: $var = (bool) gmp_mul(1, 1);
The error is logged ("Recoverable fatal error: Object of class GMP could not be converted to bool") and the browser gets a 500 page reading "The website encountered an unexpected error. Please try again later."

The error handler could convert this to an ErrorException, allowing the calling code to catch it, but unless someone has a good reason to do so, it seems safer to wait for php itself to figure out if/how to convert it. Since I don't see any bug/task left to solve here, and this issue was inactive for so long, I'll go ahead and close, but feel free to reopen if I missed something.