Skip to content

Fibers start with error_reporting = 0 when the error_reporting INI directive is not set #23921

Description

@diolektor

Description

Since PHP 8.6.0 (tested on 8.6.0RC2, and present on the current PHP-8.6 and master branches), a new Fiber starts with error_reporting set to 0 whenever the error_reporting INI directive has no value. That is the default: without a php.ini (e.g. the official Docker images, or php -n), the directive is unset. All diagnostics raised inside the fiber are then silently suppressed. Outside the fiber, error_reporting() is E_ALL as expected.

The following code:

<?php
echo error_reporting(), "\n";
(new Fiber(function () {
    echo error_reporting(), "\n";
    echo $undefined;
    echo "after\n";
}))->start();

Run as php -n -d display_errors=1 test.php, it resulted in this output:

30719
0
after

But I expected this output instead (what PHP 8.5 prints):

30719
30719

Warning: Undefined variable $undefined in /test.php on line 5
after

Behaviour matrix, error_reporting() as seen inside the fiber (same results on NTS and ZTS builds):

Setup 8.5 8.6.0RC2
no php.ini (official image) 30719 0
php -n 30719 0
-d error_reporting=0 0 0
-d error_reporting=E_ALL 30719 30719
error_reporting(E_ALL) called at runtime before the fiber, directive unset 30719 0

Cause

Introduced by cd75300 (#21146).

zend_fiber_execute() seeds the fiber's EG(error_reporting) from the directive, falling back to E_ALL when the directive is unset:

/* Determine the current error_reporting ini setting. */
zend_long error_reporting = zend_ini_long_literal("error_reporting");
/* If error_reporting is 0 and not explicitly set to 0, zend_ini_str returns a null pointer. */
if (!error_reporting && !zend_ini_str_literal("error_reporting")) {
	error_reporting = E_ALL;
}

That commit ("Audit INI functions and macros, and replace them with better alternatives") replaced INI_INT()/INI_STR() with zend_ini_long_literal()/zend_ini_str_literal(). The old INI_STR() expanded to zend_ini_string_ex(name, len, 0, NULL), which returns NULL when the entry exists but has no value. error_reporting is registered with a NULL default in Zend/zend.c. The new zend_ini_str() returns ZSTR_EMPTY_ALLOC() in that case and returns NULL only when the directive does not exist at all. So the !zend_ini_str_literal(...) check is never true for error_reporting, and the E_ALL fallback is dead code. The comment above it still describes the old behaviour.

zend_ini_string() has the same empty-string mapping, so it is not a drop-in fix either. zend_ini_str_ex() keeps the old semantics:

if (!error_reporting && !zend_ini_str_ex("error_reporting", sizeof("error_reporting") - 1, false, NULL)) {
	error_reporting = E_ALL;
}

I grepped PHP-8.6 for other callers that null-check the result of zend_ini_str() / zend_ini_str_literal(). The only other ones (ext/com_dotnet, ext/standard/basic_functions.c) use NULL to mean "directive does not exist", which the new function still reports correctly. zend_fibers.c appears to be the only affected site. The "Unset directive" behaviour change of zend_ini_str() is not mentioned in UPGRADING.INTERNALS, so third-party code that migrated INI_STR() the same way may have the same issue.

PHP Version

PHP 8.6.0RC2 (also present on PHP-8.6 at 6f49ae8 and master at 99c2dba). Not affected: PHP 8.5.

Operating System

Alpine Linux 3.23 (official php:8.6.0RC2-cli-alpine and php:8.6.0RC2-zts-alpine3.23 images); not OS-specific.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions