[php-src] Issue #21691: PHP 8.6 OPcache: DO_UCALL breaks reference returns (segfault / invalid opcode)

[email protected] (andypost)
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/21691
Author: andypost

### Description

## Summary

The `DO_UCALL` opcode handler hardcodes `return_reference=0` when calling
`i_init_func_execute_data()`, while `DO_FCALL` passes `1`. When the optimizer
(in `zend_get_call_op()`) converts `DO_FCALL` to `DO_UCALL` for a user function
that returns by reference, the `ASSIGN_REF` that consumes the result gets an
incorrectly initialized return value, producing "Invalid opcode" errors or segfaults.

**Affected:** PHP 8.6.0-dev (the `DO_UCALL` opcode was introduced in 2015, but
`zend_get_call_op()` in the optimizer only recently started converting method calls
to `DO_UCALL`). The bug reproduces on PHP 8.5 as well with the same optimizer behavior.

## Root Cause

Two files are involved:

### 1. `Zend/zend_compile.c` — `zend_get_call_op()` (line ~3989)

```c
} else if (!(CG(compiler_options) & ZEND_COMPILE_IGNORE_USER_FUNCTIONS)){
    if (zend_execute_ex == execute_ex) {
        if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard))) {
            return ZEND_DO_UCALL;  // BUG: ignores ZEND_ACC_RETURN_REFERENCE
        }
    }
}
```

Returns `ZEND_DO_UCALL` without checking `ZEND_ACC_RETURN_REFERENCE`.

### 2. `Zend/zend_vm_def.h` — `ZEND_DO_UCALL` handler (line ~4210)

```c
i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC);
//                                                      ^ always 0
```

The third argument (`return_reference`) is hardcoded to `0`. Compare with
`ZEND_DO_FCALL` (line ~4371):

```c
i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC);
//                                                      ^ passes 1
```

## Self-Contained Reproducer (no dependencies)

```php
<?php
// php -d opcache.enable_cli=1 reproducer.php
// Expected: array(1) { ["value"]=> int(42) }
// Actual:   PHP Fatal error: Invalid opcode 43/4/0.

class Base {
    protected function &getData(): array {
        $x = [];
        return $x;
    }

    public function process(): array {
        if ($data = &$this->getData() && !isset($data['key'])) {
            // unreachable
        }
        return $data;
    }
}

class Child extends Base {
    protected function &getData(): array {
        static $x = ['value' => 42];
        return $x;
    }
}

$child = new Child();
$result = $child->process();
var_dump($result);
```

## Verified Behavior

| PHP | OPcache | Result |
|-----|---------|--------|
| 8.6.0-dev | enabled (default) | `Fatal error: Invalid opcode 43/4/0` |
| 8.6.0-dev | `opcache.enable_cli=0` | OK |
| 8.6.0-dev | `opcache.optimization_level=0x7FFEBFEF` (no pass 5) | OK |
| 8.5.x | enabled | OK |

## Opcode Diff

**Before optimization** (`DO_FCALL`):
```
INIT_METHOD_CALL 0 THIS string("getData")
V6 = DO_FCALL
V5 = ASSIGN_REF (function) CV2($batch) V6
```

**After optimization** (`DO_UCALL`):
```
INIT_METHOD_CALL 0 THIS string("getData")
V6 = DO_UCALL      <-- broken: return_reference=0
V5 = ASSIGN_REF (function) CV2($batch) V6
```

## Suggested Fixes

### Option A: Fix `zend_get_call_op()` — don't use DO_UCALL for ref-returning functions

```c
// In zend_get_call_op():
if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard|ZEND_ACC_RETURN_REFERENCE))) {
    return ZEND_DO_UCALL;
}
```

### Option B: Fix `DO_UCALL` handler — honor return_reference flag

```c
// In ZEND_DO_UCALL handler:
i_init_func_execute_data(
    &fbc->op_array, ret,
    (fbc->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0
    EXECUTE_DATA_CC
);
```

Option A is simpler and has no runtime cost. Option B is more correct long-term.

## Proposed Test

[func_call_ref_return_overridden.phpt.txt](https://github.com/user-attachments/files/26609313/func_call_ref_return_overridden.phpt.txt)

### PHP Version

```plain
PHP 8.6.0-dev (cli) (built: Apr  9 2026 16:11:38) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.6.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.6.0-dev, Copyright (c), by Zend Technologies
```

### Operating System

ubuntu:resolute
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.