pcov icon indicating copy to clipboard operation
pcov copied to clipboard

Coverage information (wrongly?) affected by opcache

Open theseer opened this issue 2 years ago • 3 comments

It appears that code coverage information changes whether or not opcache is enabled. That's at least true for a line containing continue in the reproducer sample code.

I created a selfcontained repo here: https://github.com/theseer/infection-bug. (It's named infection bug because i originally believed that to be an issue with infection)

Having PHP 8.2.8 / PCOV 1.0.11 with all the other extension loaded that phpunit requires + opcache on Fedora 38 x86_64. The reproducer uses PHPUnit 10.2.6

With opcache effectively disabled:

$ php -d opache.enable=0 -d opcache.enable_cli=0 ./tools/phpunit --coverage-clover=php://stdout | grep -c 'num="9"'
1

With opcache enabled, also for CLI:

$ php -d opache.enable=1 -d opcache.enable_cli=1 ./tools/phpunit --coverage-clover=php://stdout | grep -c 'num="9"'
0

I believe this to be wrong.

theseer avatar Jul 26 '23 12:07 theseer

I can reproduce this:

$ cat /etc/os-release
NAME="Fedora Linux"
VERSION="38 (Workstation Edition)"
ID=fedora
VERSION_ID=38
VERSION_CODENAME=""
PLATFORM_ID="platform:f38"
PRETTY_NAME="Fedora Linux 38 (Workstation Edition)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:38"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://fedoraproject.org/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f38/system-administrators-guide/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=38
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=38
SUPPORT_END=2024-05-14
VARIANT="Workstation Edition"
VARIANT_ID=workstation
$ dnf info php-cli
Installed Packages
Name         : php-cli
Version      : 8.2.8
Release      : 1.fc38.remi
Architecture : x86_64
Size         : 24 M
Source       : php-8.2.8-1.fc38.remi.src.rpm
Repository   : @System
From repo    : remi-modular
Summary      : Command-line interface for PHP
URL          : http://www.php.net/
License      : PHP-3.01 AND Zend-2.0 AND BSD-2-Clause AND MIT AND Apache-1.0 AND NCSA AND PostgreSQL
Description  : The php-cli package contains the command-line interface
             : executing PHP scripts, /usr/bin/php, and the CGI interface.
$ dnf info php-opcache
Installed Packages
Name         : php-opcache
Version      : 8.2.8
Release      : 1.fc38.remi
Architecture : x86_64
Size         : 2.0 M
Source       : php-8.2.8-1.fc38.remi.src.rpm
Repository   : @System
From repo    : remi-modular
Summary      : The Zend OPcache
URL          : http://www.php.net/
License      : PHP-3.01
Description  : The Zend OPcache provides faster PHP execution through opcode caching and
             : optimization. It improves PHP performance by storing precompiled script
             : bytecode in the shared memory. This eliminates the stages of reading code from
             : the disk and compiling it on future access. In addition, it applies a few
             : bytecode optimization patterns that make code execution faster.
$ php -i | grep opcache
Additional .ini files parsed => /etc/php.d/10-opcache.ini,
opcache.blacklist_filename => /etc/php.d/opcache*.blacklist => /etc/php.d/opcache*.blacklist
opcache.consistency_checks => 0 => 0
opcache.dups_fix => Off => Off
opcache.enable => On => On
opcache.enable_cli => On => On
opcache.enable_file_override => Off => Off
opcache.error_log => no value => no value
opcache.file_cache => no value => no value
opcache.file_cache_consistency_checks => On => On
opcache.file_cache_only => Off => Off
opcache.file_update_protection => 2 => 2
opcache.force_restart_timeout => 180 => 180
opcache.huge_code_pages => Off => Off
opcache.interned_strings_buffer => 8 => 8
opcache.jit => tracing => tracing
opcache.jit_bisect_limit => 0 => 0
opcache.jit_blacklist_root_trace => 16 => 16
opcache.jit_blacklist_side_trace => 8 => 8
opcache.jit_buffer_size => 0 => 0
opcache.jit_debug => 0 => 0
opcache.jit_hot_func => 127 => 127
opcache.jit_hot_loop => 64 => 64
opcache.jit_hot_return => 8 => 8
opcache.jit_hot_side_exit => 8 => 8
opcache.jit_max_exit_counters => 8192 => 8192
opcache.jit_max_loop_unrolls => 8 => 8
opcache.jit_max_polymorphic_calls => 2 => 2
opcache.jit_max_recursive_calls => 2 => 2
opcache.jit_max_recursive_returns => 2 => 2
opcache.jit_max_root_traces => 1024 => 1024
opcache.jit_max_side_traces => 128 => 128
opcache.jit_prof_threshold => 0.005 => 0.005
opcache.lockfile_path => /tmp => /tmp
opcache.log_verbosity_level => 1 => 1
opcache.max_accelerated_files => 10000 => 10000
opcache.max_file_size => 0 => 0
opcache.max_wasted_percentage => 5 => 5
opcache.memory_consumption => 128 => 128
opcache.opt_debug_level => 0 => 0
opcache.optimization_level => 0x7FFEBFFF => 0x7FFEBFFF
opcache.preferred_memory_model => no value => no value
opcache.preload => no value => no value
opcache.preload_user => no value => no value
opcache.protect_memory => Off => Off
opcache.record_warnings => Off => Off
opcache.restrict_api => no value => no value
opcache.revalidate_freq => 2 => 2
opcache.revalidate_path => Off => Off
opcache.save_comments => On => On
opcache.use_cwd => On => On
opcache.validate_permission => Off => Off
opcache.validate_root => Off => Off
opcache.validate_timestamps => On => On

Disabling just OpCache's optimizer works around the issue just as disabling OpCache entirely does.

sebastianbergmann avatar Jul 26 '23 13:07 sebastianbergmann

We cannot reproduce this with Xdebug. According to @derickr, this is because "Xdebug sets the ZEND_COMPILE_EXTENDED_STMT compiler options flag, which makes things emit EXT_STMT and hence opcache can't optimise in this specific case".

The patch shown below fixes the problem for me:

diff --git a/pcov.c b/pcov.c
index f04ba9c..c354f72 100644
--- a/pcov.c
+++ b/pcov.c
@@ -441,6 +441,8 @@ PHP_RINIT_FUNCTION(pcov)
        CG(compiler_options) |= ZEND_COMPILE_NO_JUMPTABLES;
 #endif
 
+       CG(compiler_options) |= ZEND_COMPILE_EXTENDED_STMT;
+
        if  (!zend_compile_file_function) {
                zend_compile_file_function = zend_compile_file;
                zend_compile_file          = php_pcov_compile_file;

sebastianbergmann avatar Jul 26 '23 13:07 sebastianbergmann

The patch looks sensible to me. However, ZEND_COMPILE_EXTENDED_STMT is only available as of PHP 7.4.0, while pcov is still supposed to support PHP 7.1+. As such either that line would need to be enclosed in #if PHP_VERSION_ID >= 70400 … #endif, or the version requirements of pcov would need to be bumped. Probably the latter.

tests/gh101.phpt
--TEST--
GH-101 (Coverage information affected by opcache)
--SKIPIF--
<?php if (!extension_loaded("pcov")) print "skip"; ?>
--INI--
pcov.enabled = 1
--FILE--
<?php
function test(array $data) {
    $result = [];
    foreach($data as $value) {
        if (!isOkay($value)) {
            continue;
        }
        $result[] = $value;
    }
    return $result;
}

function isOkay(string $input): bool {
    return $input !== 'b';
}
\pcov\start();
test(["a", "b", "c"]);
\pcov\stop();

var_dump(\pcov\collect());
?>
--EXPECTF--
array(1) {
  ["%s%egh101.php"]=>
  array(12) {
    [16]=>
    int(-1)
    [17]=>
    int(1)
    [18]=>
    int(1)
    [20]=>
    int(-1)
    [22]=>
    int(-1)
    [3]=>
    int(1)
    [4]=>
    int(1)
    [5]=>
    int(1)
    [6]=>
    int(1)
    [8]=>
    int(1)
    [10]=>
    int(1)
    [14]=>
    int(1)
  }
}

cmb69 avatar Aug 07 '24 17:08 cmb69