php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #63206 restore_error_handler does not restore previous errors mask
Submitted: 2012-10-03 09:59 UTC Modified: 2017-12-28 18:07 UTC
Votes:21
Avg. Score:4.5 ± 0.7
Reproduced:18 of 20 (90.0%)
Same Version:2 (11.1%)
Same OS:2 (11.1%)
From: gwarnants at gmail dot com Assigned:
Status: Closed Package: *General Issues
PHP Version: 5.4.7 OS: Windows XP
Private report: No CVE-ID: None
 [2012-10-03 09:59 UTC] gwarnants at gmail dot com
Description:
------------
Dear PHP Team,

I don't know if it's a bug but I discovered a strange behavior :

When setting a custom error handler, we can choose an error mask as a 2nd parameter to catch only some kinds of errors.
If I set 2 error handlers with differents masks, and trying to restore back my first handler by calling restore_error_handler(), my first handler is now running with the second error mask

Perhaps it is a normal behavior because set_error_handler() is setting a "global" error mask, but if it is, I think there is no
way to know the previous error_mask to restore it properly (that value isn't returned by error_reporting())

Regards,
Geoffray

Test script:
---------------
class CustomErrorHandler
{
    public static function handler($errrno, $errstr)
    {
        echo "HANDLING: $errstr\n";

        // i set an internal error_handler other catch WARNINGS ONLY
        set_error_handler(array('CustomErrorHandler', 'internal_handler'), E_WARNING|E_USER_WARNING);
        fopen('file_not_found.dat', 'r');   // will trigger a E_WARNING
        restore_error_handler();    // doing this i think previous handler will be restored to E_ALL... but it's not ??
    }

    private static function internal_handler($errrno, $errstr)
    {
        echo "  INTERNAL HANDLER: $errstr\n";
    }
}


set_error_handler(array('CustomErrorHandler', 'handler'), E_ALL);

trigger_error('User notice 1',  E_USER_NOTICE);
trigger_error('User warning 1', E_USER_WARNING);
trigger_error('User notice 2',  E_USER_NOTICE); // will not be caught !
trigger_error('User warning 2', E_USER_WARNING);

Expected result:
----------------
HANDLING: User notice 1
  INTERNAL HANDLER: fopen(file_not_found.dat): failed to open stream: No such file or directory
HANDLING: User warning 1
  INTERNAL HANDLER: fopen(file_not_found.dat): failed to open stream: No such file or directory
HANDLING: User notice 2
  INTERNAL HANDLER: fopen(file_not_found.dat): failed to open stream: No such file or directory
HANDLING: User warning 2
  INTERNAL HANDLER: fopen(file_not_found.dat): failed to open stream: No such file or directory

Actual result:
--------------
HANDLING: User notice 1
  INTERNAL HANDLER: fopen(file_not_found.dat): failed to open stream: No such file or directory
HANDLING: User warning 1
  INTERNAL HANDLER: fopen(file_not_found.dat): failed to open stream: No such file or directory


Notice:  User notice 2 in D:\wamp\www\custom_error_handler.php on line 29

HANDLING: User warning 2
  INTERNAL HANDLER: fopen(file_not_found.dat): failed to open stream: No such file or directory

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2012-10-17 04:57 UTC] gwarnants at gmail dot com
Any feedback ?
 [2012-11-15 08:57 UTC] gwarnants at gmail dot com
Here is a simpler example to illustrate the problem. As you see, the 2nd trigger_error will not be caught because the error reporting isn't restored to its initial state.

set_error_handler('handler1', E_ALL);
trigger_error('notice 1',  E_USER_NOTICE); // OK
trigger_error('notice 2',  E_USER_NOTICE); // not caught !

function handler1($errrno, $errstr)
{
    echo "HANDLER1 : $errstr\n";
    set_error_handler('handler2', E_USER_WARNING);
    restore_error_handler();
}

function handler2($errrno, $errstr)
{
}
 [2013-01-02 14:14 UTC] joe dot bowman at edigitalresearch dot com
Same problem in 5.4.8 on CentOS 6.3.
 [2013-10-06 06:55 UTC] gwarnants at gmail dot com
Same problem in 5.3.4 on Windows
 [2016-01-14 02:38 UTC] hujuice at inserviblie dot org
Same here for PHP 5.6.14 on Linux
 [2016-06-03 14:06 UTC] a at ustimen dot co
Currently repeats at 5.4, 5.5 and 5.6

Seems fixed in 7.0, hhvm and nightly.

https://travis-ci.org/garex/php-error-handler-bug
 [2016-06-03 14:31 UTC] a at ustimen dot co
Sorry, my bad -- test script was not robust.

Currently it repeats at PHP from 5.2 to nightly (7.x).

Only under hhvm not repeats.

https://travis-ci.org/garex/php-error-handler-bug/builds/135038478
 [2017-12-28 18:07 UTC] nikic@php.net
Generally restore_error_handler() does restore the mask as well. What happens here is that while the error handler is called, the error handler is temporarily removed to prevent recursion. The inner set_error_handler() call then thinks that there is no error handler registered and does not save the mask.

Ideally we would not temporarily remove the error handler and instead track that it is currently running in a different way.
 [2020-02-24 18:15 UTC] not-implemented at mark-plomer dot de
The problem is not only about the "error mask". It affects the error-handler itself. Calling set/restore_error_handler() inside the error-handler "corrupts" the "old-error-handler" stack, when there are already >= 2 registered handlers. For future errors, the wrong error-handler is called.

I run into this problem while debugging this issue in Sentry: https://github.com/getsentry/sentry-php/issues/976

Problem still exists in 7.4.3 (and probably "master").

Simpler test-script which also shows the use-case:

Test script:
------------
set_error_handler(function() {
    echo 'First handler' . PHP_EOL;
});

set_error_handler(function() {
    echo 'Second handler' . PHP_EOL;

    set_error_handler(function() {
        echo 'Internal handler' . PHP_EOL;
    });

    $triggerInternalNotice++; // warnings while handling the error should go into internal handler

    restore_error_handler();
});

$triggerNotice1++;
$triggerNotice2++;


Expected result:
----------------
Second handler
Internal handler
Second handler
Internal handler

Actual result:
--------------
Second handler
Internal handler
First handler
 [2020-02-24 21:26 UTC] not-implemented at mark-plomer dot de
I created a draft-pull-request for this issue:
https://github.com/php/php-src/pull/5206

@nikic: Independently from your proposal to not remove the error-handler while error-handling (which also makes sense), I think we always have to push the "empty" handler to the stack to have a consistent behaviour.

This would fix this issue, but I'm not fully aware of the consequences. What do you think?
 [2020-02-25 11:45 UTC] nikic@php.net
Automatic comment on behalf of mark.plomer@boerse-go.de
Revision: http://git.php.net/?p=php-src.git;a=commit;h=8c6a7c3326f000af0a8ea266143059e5a463e626
Log: Fix #63206: Fully support error/exception_handler stacking, even with null or inside the handler
 [2020-02-25 11:45 UTC] nikic@php.net
-Status: Open +Status: Closed
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Fri Jul 10 13:01:25 2020 UTC