From 16f1d6ef0ff66b30fa3442e6025b8f323a459fa5 Mon Sep 17 00:00:00 2001 From: Andy Postnikov Date: Thu, 9 Apr 2026 21:02:45 +0200 Subject: [PATCH] Fix DO_UCALL may_be_trampoline flag to match DO_FCALL The ZEND_DO_UCALL handler hardcodes may_be_trampoline=0 when calling i_init_func_execute_data(), while ZEND_DO_FCALL passes 1. When the optimizer converts DO_FCALL to DO_UCALL for user functions that return by reference, the ASSIGN_REF opcode receives an incorrectly initialized return value, producing "Invalid opcode" errors or segfaults. Co-Authored-By: Claude Opus 4.6 --- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 12 +++--- .../func_call_ref_return_overridden.phpt | 41 +++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 ext/opcache/tests/func_call_ref_return_overridden.phpt diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 5a7e6d6d1165f..42e2590b4602f 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4209,7 +4209,7 @@ ZEND_VM_HOT_HANDLER(130, ZEND_DO_UCALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) call->prev_execute_data = execute_data; execute_data = call; - i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC); + i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC); LOAD_OPLINE_EX(); ZEND_OBSERVER_SAVE_OPLINE(); ZEND_OBSERVER_FCALL_BEGIN(execute_data); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a2a064377d8c4..b7c59ef03dc50 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1513,7 +1513,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D call->prev_execute_data = execute_data; execute_data = call; - i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC); + i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC); LOAD_OPLINE_EX(); @@ -1539,7 +1539,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D call->prev_execute_data = execute_data; execute_data = call; - i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC); + i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC); LOAD_OPLINE_EX(); @@ -1565,7 +1565,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ call->prev_execute_data = execute_data; execute_data = call; - i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC); + i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC); LOAD_OPLINE_EX(); SAVE_OPLINE(); zend_observer_fcall_begin_specialized(execute_data, false); @@ -54265,7 +54265,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_UCA call->prev_execute_data = execute_data; execute_data = call; - i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC); + i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC); LOAD_OPLINE_EX(); @@ -54291,7 +54291,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_UCA call->prev_execute_data = execute_data; execute_data = call; - i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC); + i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC); LOAD_OPLINE_EX(); @@ -54317,7 +54317,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_UC call->prev_execute_data = execute_data; execute_data = call; - i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC); + i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC); LOAD_OPLINE_EX(); SAVE_OPLINE(); zend_observer_fcall_begin_specialized(execute_data, false); diff --git a/ext/opcache/tests/func_call_ref_return_overridden.phpt b/ext/opcache/tests/func_call_ref_return_overridden.phpt new file mode 100644 index 0000000000000..0f1e5cf095218 --- /dev/null +++ b/ext/opcache/tests/func_call_ref_return_overridden.phpt @@ -0,0 +1,41 @@ +--TEST-- +DO_UCALL must not be used for functions returning by reference +--DESCRIPTION-- +The optimizer's zend_get_call_op() converts DO_FCALL to DO_UCALL for user +functions, but DO_UCALL hardcodes return_reference=0 in +i_init_func_execute_data(). When the called function returns by reference +(e.g. an overridden method using ASSIGN_REF), this produces invalid opcode +sequences. The fix is either to not use DO_UCALL when the function has +ZEND_ACC_RETURN_REFERENCE, or to make DO_UCALL honor it. +--FILE-- +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); +?> +--EXPECT-- +array(1) { + ["value"]=> + int(42) +}