View Issue Details

IDProjectCategoryView StatusLast Update
0002419XdebugCode Coveragepublic2026-04-24 09:36
Reportersebastian Assigned Toderick  
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionnot fixable 
Product Version3.5.1 
Summary0002419: Line coverage does not report ternary branch lines when ternary is inside an array literal
Description

When a multiline ternary operator is used as a value inside an array literal, xdebug_get_code_coverage() does not report coverage data for the lines containing the true and false branch expressions. The same ternary in a simple variable assignment context reports both branch lines correctly.

This affects php-code-coverage (used by PHPUnit). When a ternary operator spans multiple lines inside an array literal, the branch expression lines do not appear in coverage reports at all. They are neither green (executed) nor red (not executed). They are simply not treated as executable lines.

Reported in https://github.com/sebastianbergmann/php-code-coverage/issues/1029

Steps To Reproduce

Scripts

ternary_assignment.php:

<?php
class Foo
{
    public function bar(bool $condition): array
    {
        $result = $condition
            ? ['yes']
            : ['no'];

        return $result;
    }
}

echo "true:\n";
xdebug_start_code_coverage(XDEBUG_CC_UNUSED);
(new Foo)->bar(true);
$c = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
ksort($c[__FILE__]);
foreach ($c[__FILE__] as $l => $s) printf("  Line %2d: %s\n", $l, $s === 1 ? 'EXECUTED' : 'NOT_EXECUTED');

echo "false:\n";
xdebug_start_code_coverage(XDEBUG_CC_UNUSED);
(new Foo)->bar(false);
$c = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
ksort($c[__FILE__]);
foreach ($c[__FILE__] as $l => $s) printf("  Line %2d: %s\n", $l, $s === 1 ? 'EXECUTED' : 'NOT_EXECUTED');

ternary_in_array.php:

<?php
class Foo
{
    public function bar(bool $condition): array
    {
        $result = [
            'key' => $condition
                ? ['yes']
                : ['no'],
        ];

        return $result;
    }
}

echo "true:\n";
xdebug_start_code_coverage(XDEBUG_CC_UNUSED);
(new Foo)->bar(true);
$c = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
ksort($c[__FILE__]);
foreach ($c[__FILE__] as $l => $s) printf("  Line %2d: %s\n", $l, $s === 1 ? 'EXECUTED' : 'NOT_EXECUTED');

echo "false:\n";
xdebug_start_code_coverage(XDEBUG_CC_UNUSED);
(new Foo)->bar(false);
$c = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
ksort($c[__FILE__]);
foreach ($c[__FILE__] as $l => $s) printf("  Line %2d: %s\n", $l, $s === 1 ? 'EXECUTED' : 'NOT_EXECUTED');

Output

ternary_assignment.php (simple variable assignment):

true:
  Line  6: EXECUTED          $result = $condition
  Line  7: EXECUTED              ? ['yes']
  Line  8: NOT_EXECUTED          : ['no'];
  Line 10: EXECUTED          return $result;
  Line 11: NOT_EXECUTED      }
false:
  Line  6: EXECUTED          $result = $condition
  Line  7: NOT_EXECUTED          ? ['yes']
  Line  8: EXECUTED              : ['no'];
  Line 10: EXECUTED          return $result;
  Line 11: NOT_EXECUTED      }

Both branch lines (7 and 8) are reported. Their status correctly reflects which branch was taken.

ternary_in_array.php (ternary as value in array literal):

true:
  Line  6: EXECUTED          $result = [
  Line  7: EXECUTED              'key' => $condition
  Line 12: EXECUTED          return $result;
  Line 13: NOT_EXECUTED      }
false:
  Line  6: EXECUTED          $result = [
  Line  7: EXECUTED              'key' => $condition
  Line 12: EXECUTED          return $result;
  Line 13: NOT_EXECUTED      }

Lines 8 (? ['yes']) and 9 (: ['no']) are absent from the coverage data in both calls. There is no way to determine from the reported data whether either branch was taken.

Analysis

The opcodes generated for bar() in both scripts are structurally identical (shown via opcache.opt_debug_level=0x20000):

ternary_assignment.php             ternary_in_array.php
---------------------              --------------------
RECV                               RECV
JMPZ -> false branch               JMPZ -> false branch
QM_ASSIGN array(...)               QM_ASSIGN array(...)
JMP -> after                       JMP -> after
QM_ASSIGN array(...)               QM_ASSIGN array(...)
QM_ASSIGN T2 (assign)              INIT_ARRAY 1 T2 "key"
RETURN                             RETURN

Both have the same QM_ASSIGN opcodes for the true and false branch values. The difference in coverage output suggests that the opcode-to-line mapping for the QM_ASSIGN opcodes differs between the two contexts: in the simple assignment case each QM_ASSIGN is mapped to its source line (7 and 8); in the array literal case both appear to be mapped to the condition line (7), making lines 8 and 9 invisible to line coverage.

TagsNo tags attached.
Operating System
PHP Version8.5.0-8.5.4

Activities

derick

2026-04-24 09:36

administrator   ~0007485

Xdebug does not assign line numbers to opcode, PHP does.

This is not a bug in Xdebug.

Issue History

Date Modified Username Field Change
2026-04-24 07:17 sebastian New Issue
2026-04-24 09:36 derick Assigned To => derick
2026-04-24 09:36 derick Status new => resolved
2026-04-24 09:36 derick Resolution open => not fixable
2026-04-24 09:36 derick Note Added: 0007485