Commit 8fc9e7c8e8f for php
commit 8fc9e7c8e8f771ab55671cec00c4e468ec511efe
Author: Bob Weinand <bobwei9@hotmail.com>
Date: Fri Oct 30 15:34:17 2026 +0100
Fix assertion in VM when taking ref-returning fcall by-value
zend_return_unwrap_ref() is expected to be called now, but internal functions returning by reference were failing the debug assertion, expecting that they would unconditionally return by reference.
Adjusted the assertion.
Signed-off-by: Bob Weinand <bobwei9@hotmail.com>
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 3133caea2e0..e929bc41a03 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -6004,26 +6004,7 @@ ZEND_API zval *zend_get_zval_ptr(const zend_op *opline, int op_type, const znode
ZEND_API void zend_return_unwrap_ref(zend_execute_data *execute_data, zval *return_value)
{
- if (!return_value || !Z_ISREF_P(return_value)) {
- return;
- }
-
- zend_execute_data *prev_ex = EX(prev_execute_data);
- if (!prev_ex || !prev_ex->func || !ZEND_USER_CODE(prev_ex->func->type)) {
- return;
+ if (return_value && Z_ISREF_P(return_value) && zend_return_should_unwrap_ref(execute_data)) {
+ zend_unwrap_reference(return_value);
}
-
- const zend_op *do_opline = prev_ex->opline;
- if (do_opline->result_type != IS_TMP_VAR) {
- return;
- }
-
- if (do_opline->opcode != ZEND_DO_FCALL
- && do_opline->opcode != ZEND_DO_FCALL_BY_NAME
- && do_opline->opcode != ZEND_DO_ICALL
- && do_opline->opcode != ZEND_DO_UCALL) {
- return;
- }
-
- zend_unwrap_reference(return_value);
}
diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h
index 14c1e6701f0..bdb4f66cb2f 100644
--- a/Zend/zend_execute.h
+++ b/Zend/zend_execute.h
@@ -676,6 +676,24 @@ static zend_always_inline void *zend_get_bad_ptr(void)
return NULL;
}
+static zend_always_inline bool zend_return_should_unwrap_ref(const zend_execute_data *call)
+{
+ const zend_execute_data *prev_ex = call->prev_execute_data;
+ if (!prev_ex || !prev_ex->func || !ZEND_USER_CODE(prev_ex->func->type)) {
+ return false;
+ }
+
+ const zend_op *do_opline = prev_ex->opline;
+ if (do_opline->result_type != IS_TMP_VAR) {
+ return false;
+ }
+
+ return do_opline->opcode == ZEND_DO_FCALL
+ || do_opline->opcode == ZEND_DO_FCALL_BY_NAME
+ || do_opline->opcode == ZEND_DO_ICALL
+ || do_opline->opcode == ZEND_DO_UCALL;
+}
+
ZEND_API void zend_return_unwrap_ref(zend_execute_data *call, zval *return_value);
END_EXTERN_C()
diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
index b9e992ff8a0..2c361bb4950 100644
--- a/Zend/zend_execute_API.c
+++ b/Zend/zend_execute_API.c
@@ -1106,7 +1106,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
bool result = zend_verify_internal_return_type(call->func, fci->retval);
ZEND_ASSERT(result);
}
- ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ ZEND_ASSERT(((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) && !zend_return_should_unwrap_ref(call))
? Z_ISREF_P(fci->retval) : !Z_ISREF_P(fci->retval));
ZEND_ASSERT(!(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
}
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 4375b7ad3f3..b3d0597374c 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -4158,7 +4158,7 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
bool result = zend_verify_internal_return_type(call->func, ret);
ZEND_ASSERT(result);
}
- ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ ZEND_ASSERT(((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) && !zend_return_should_unwrap_ref(call))
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
zend_verify_internal_func_info(call->func, ret);
ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC)
@@ -4291,7 +4291,7 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER))
bool result = zend_verify_internal_return_type(call->func, ret);
ZEND_ASSERT(result);
}
- ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ ZEND_ASSERT(((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) && !zend_return_should_unwrap_ref(call))
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
zend_verify_internal_func_info(call->func, ret);
ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC)
@@ -4424,7 +4424,7 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
bool result = zend_verify_internal_return_type(call->func, ret);
ZEND_ASSERT(result);
}
- ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ ZEND_ASSERT(((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) && !zend_return_should_unwrap_ref(call))
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
zend_verify_internal_func_info(call->func, ret);
ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC)
@@ -9182,7 +9182,7 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER))
bool result = zend_verify_internal_return_type(call->func, ret);
ZEND_ASSERT(result);
}
- ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ ZEND_ASSERT(((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) && !zend_return_should_unwrap_ref(call))
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
zend_verify_internal_func_info(call->func, ret);
ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC)
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 59891e70d9a..74ed4301047 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index c1ede5c6043..02f09a5a7ab 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -132,6 +132,18 @@ static void pass2(zend_script *script, void *context)
php_printf("pass2\n");
}
+static ZEND_FUNCTION(zend_test_forward_reference)
+{
+ zval *value;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_ZVAL(value)
+ ZEND_PARSE_PARAMETERS_END();
+
+ ZVAL_COPY(return_value, value);
+ zend_return_unwrap_ref(execute_data, return_value);
+}
+
static ZEND_FUNCTION(zend_test_deprecated)
{
zval *arg1;
diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
index 8137b911a35..8719c6935a3 100644
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@ -265,6 +265,8 @@ function zend_test_nullable_array_return(): null|array {}
function zend_test_void_return(): void {}
+ function &zend_test_forward_reference(mixed &$value): mixed {}
+
function zend_test_compile_string(string $source_string, string $filename, int $position): void {}
/** @deprecated */
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
index 184bcdbae14..d6fa8896c42 100644
Binary files a/ext/zend_test/test_arginfo.h and b/ext/zend_test/test_arginfo.h differ
diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h
index cab05e40a40..35e929d1a30 100644
Binary files a/ext/zend_test/test_decl.h and b/ext/zend_test/test_decl.h differ
diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h
index d58dc5d2622..9bb20c85e95 100644
Binary files a/ext/zend_test/test_legacy_arginfo.h and b/ext/zend_test/test_legacy_arginfo.h differ