krakjoe / pcov

PCOV - CodeCoverage compatible driver for PHP
Other
696 stars 28 forks source link

Coverage information (wrongly?) affected by opcache #101

Open theseer opened 1 year ago

theseer commented 1 year ago

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.

sebastianbergmann commented 1 year ago

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 commented 1 year ago

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;
cmb69 commented 1 month ago

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 ````php --TEST-- GH-101 (Coverage information affected by opcache) --SKIPIF-- --INI-- pcov.enabled = 1 --FILE-- --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) } } ````