From baf5c6b7733def2adb21deca3a461d7b042783a6 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 1 Aug 2026 02:59:33 +0800 Subject: [PATCH 1/3] Fix GH-17773: Allow references returned by functions in arrays --- NEWS | 4 +++ Zend/tests/gh17773.phpt | 52 +++++++++++++++++++++++++++++++ Zend/tests/gh17773_builtin.phpt | 8 +++++ Zend/tests/gh17773_exception.phpt | 23 ++++++++++++++ Zend/tests/gh17773_globals.phpt | 8 +++++ Zend/tests/gh17773_nullsafe.phpt | 16 ++++++++++ Zend/zend_compile.c | 13 +++++++- Zend/zend_vm_def.h | 8 +++++ Zend/zend_vm_execute.h | 34 ++++++++++++++++++++ 9 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/gh17773.phpt create mode 100644 Zend/tests/gh17773_builtin.phpt create mode 100644 Zend/tests/gh17773_exception.phpt create mode 100644 Zend/tests/gh17773_globals.phpt create mode 100644 Zend/tests/gh17773_nullsafe.phpt diff --git a/NEWS b/NEWS index 4364d69650e5..10444ef2062f 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.6.0beta1 +- Core: + . Fixed bug GH-17773 (Initializing array elements with references returned by + functions was not possible). (Matthias Görgens) + - GMP: . Added optional $definitely_prime output parameter to gmp_prevprime(). (Weilin Du) diff --git a/Zend/tests/gh17773.phpt b/Zend/tests/gh17773.phpt new file mode 100644 index 000000000000..20e313aade77 --- /dev/null +++ b/Zend/tests/gh17773.phpt @@ -0,0 +1,52 @@ +--TEST-- +GH-17773 (Initializing array element with reference returned by function) +--FILE-- +value; + } + + public static function &getStaticReference() { + return self::$staticValue; + } +} + +$test = new Test(); +$array = [&$test->getReference(), &Test::getStaticReference(), [&get_reference()]]; +$unpacked = [...$array]; +$unpacked[0] = 43; +$unpacked[1] = 44; +$unpacked[2][0] = 45; +var_dump($test->value, Test::$staticValue, get_reference()); + +function get_value() { + return 42; +} + +$array = [&get_value()]; +var_dump($array); +?> +--EXPECTF-- +int(42) +int(43) +int(44) +int(45) + +Notice: Only variables should be assigned by reference in %s on line %d +array(1) { + [0]=> + int(42) +} diff --git a/Zend/tests/gh17773_builtin.phpt b/Zend/tests/gh17773_builtin.phpt new file mode 100644 index 000000000000..5aeb44a1601a --- /dev/null +++ b/Zend/tests/gh17773_builtin.phpt @@ -0,0 +1,8 @@ +--TEST-- +GH-17773 (Cannot initialize array element with reference returned by built-in function) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use result of built-in function in write context in %s on line %d diff --git a/Zend/tests/gh17773_exception.phpt b/Zend/tests/gh17773_exception.phpt new file mode 100644 index 000000000000..9bee8d6893ed --- /dev/null +++ b/Zend/tests/gh17773_exception.phpt @@ -0,0 +1,23 @@ +--TEST-- +GH-17773 (Exception while reporting by-value function result used by reference) +--FILE-- +getMessage(), "\n"; +} + +echo "Done\n"; +?> +--EXPECT-- +Only variables should be assigned by reference +Done diff --git a/Zend/tests/gh17773_globals.phpt b/Zend/tests/gh17773_globals.phpt new file mode 100644 index 000000000000..683780a24aab --- /dev/null +++ b/Zend/tests/gh17773_globals.phpt @@ -0,0 +1,8 @@ +--TEST-- +GH-17773 (Cannot initialize array element with reference to $GLOBALS) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot acquire reference to $GLOBALS in %s on line %d diff --git a/Zend/tests/gh17773_nullsafe.phpt b/Zend/tests/gh17773_nullsafe.phpt new file mode 100644 index 000000000000..e3335c6771c8 --- /dev/null +++ b/Zend/tests/gh17773_nullsafe.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-17773 (Cannot initialize array element with reference to nullsafe chain) +--FILE-- +getReference()]; +?> +--EXPECTF-- +Fatal error: Cannot take reference of a nullsafe chain in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 088573a50ee8..ab6fc8348a09 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11537,8 +11537,19 @@ static void zend_compile_array(znode *result, zend_ast *ast) /* {{{ */ } if (by_ref) { - zend_ensure_writable_variable(value_ast); + zend_assert_not_short_circuited(value_ast); + if (is_globals_fetch(value_ast)) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot acquire reference to $GLOBALS"); + } zend_compile_var(&value_node, value_ast, BP_VAR_W, true); + if (value_node.op_type != IS_VAR && zend_is_call(value_ast)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use result of built-in function in write context"); + } + if (zend_is_call(value_ast)) { + opline = zend_emit_op(&value_node, ZEND_MAKE_REF, &value_node, NULL); + opline->extended_value = ZEND_RETURNS_FUNCTION; + } } else { zend_compile_expr(&value_node, value_ast); } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index d14230514b34..377de66eb673 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9393,6 +9393,14 @@ ZEND_VM_HANDLER(140, ZEND_MAKE_REF, VAR|CV, UNUSED) } ZVAL_REF(EX_VAR(opline->result.var), Z_REF_P(op1)); } else { + if (UNEXPECTED(opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(op1))) { + SAVE_OPLINE(); + zend_error(E_NOTICE, "Only variables should be assigned by reference"); + if (UNEXPECTED(EG(exception))) { + FREE_OP1(); + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 53bcdccd9719..dfa0f1e43a77 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -30512,6 +30512,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_MAKE_REF_SPEC } ZVAL_REF(EX_VAR(opline->result.var), Z_REF_P(op1)); } else { + if (UNEXPECTED(opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(op1))) { + SAVE_OPLINE(); + zend_error(E_NOTICE, "Only variables should be assigned by reference"); + if (UNEXPECTED(EG(exception))) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE(); @@ -50073,6 +50081,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_MAKE_REF_SPEC } ZVAL_REF(EX_VAR(opline->result.var), Z_REF_P(op1)); } else { + if (UNEXPECTED(opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(op1))) { + SAVE_OPLINE(); + zend_error(E_NOTICE, "Only variables should be assigned by reference"); + if (UNEXPECTED(EG(exception))) { + + + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE(); @@ -83142,6 +83159,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_MAKE_REF_SPEC_VAR_ } ZVAL_REF(EX_VAR(opline->result.var), Z_REF_P(op1)); } else { + if (UNEXPECTED(opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(op1))) { + SAVE_OPLINE(); + zend_error(E_NOTICE, "Only variables should be assigned by reference"); + if (UNEXPECTED(EG(exception))) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE(); @@ -102601,6 +102626,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_MAKE_REF_SPEC_CV_U } ZVAL_REF(EX_VAR(opline->result.var), Z_REF_P(op1)); } else { + if (UNEXPECTED(opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(op1))) { + SAVE_OPLINE(); + zend_error(E_NOTICE, "Only variables should be assigned by reference"); + if (UNEXPECTED(EG(exception))) { + + + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE(); From 2d2e05341a2583bb7bd7b5702e1f7c7692b9be5c Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 14 Aug 2026 14:53:19 +0800 Subject: [PATCH 2/3] Fix GH-17773: JIT crash when MAKE_REF notice turns into an exception zend_may_throw_ex() reported ZEND_MAKE_REF as non-throwing, but with ZEND_RETURNS_FUNCTION it raises an E_NOTICE that a userland error handler can turn into an exception. The JIT then emitted no exception check after calling the interpreter handler and continued with a corrupted instruction pointer, writing wild VM state. --- Zend/Optimizer/zend_inference.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 2e6cc70ec254..9dd833551a20 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -4923,6 +4923,12 @@ ZEND_API zend_result zend_ssa_inference(zend_arena **arena, const zend_op_array ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, const zend_ssa *ssa, uint32_t t1, uint32_t t2) { + if (opline->opcode == ZEND_MAKE_REF && opline->extended_value == ZEND_RETURNS_FUNCTION) { + /* A by-value function result raises an E_NOTICE, which a userland + * error handler can turn into an exception. */ + return 1; + } + if (opline->op1_type == IS_CV) { if (t1 & MAY_BE_UNDEF) { switch (opline->opcode) { From 165b41d4a284d994d9fb58fe1cb1a04306f82165 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 14 Aug 2026 14:53:25 +0800 Subject: [PATCH 3/3] Fix GH-17773: define MAKE_REF result before HANDLE_EXCEPTION When the exception escapes uncaught, ZEND_HANDLE_EXCEPTION frees the throwing opcode's result slot, which the handler had left uninitialised. Initialise it with ZVAL_UNDEF() so the unwinder sees a defined zval. --- Zend/tests/gh17773_uncaught.phpt | 19 +++++++++++++++++++ Zend/zend_vm_def.h | 1 + Zend/zend_vm_execute.h | 4 ++++ 3 files changed, 24 insertions(+) create mode 100644 Zend/tests/gh17773_uncaught.phpt diff --git a/Zend/tests/gh17773_uncaught.phpt b/Zend/tests/gh17773_uncaught.phpt new file mode 100644 index 000000000000..8851d632291b --- /dev/null +++ b/Zend/tests/gh17773_uncaught.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-17773 (Uncaught exception from by-value function result used by reference) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception: Only variables should be assigned by reference in %s:%d +Stack trace: +#0 %s(%d): {closure:%s:%d}(8, 'Only variables ...', '%s', 8) +#1 {main} + thrown in %s on line %d diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 377de66eb673..56167aaa9d45 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9398,6 +9398,7 @@ ZEND_VM_HANDLER(140, ZEND_MAKE_REF, VAR|CV, UNUSED) zend_error(E_NOTICE, "Only variables should be assigned by reference"); if (UNEXPECTED(EG(exception))) { FREE_OP1(); + ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index dfa0f1e43a77..97a3344adfa6 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -30517,6 +30517,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_MAKE_REF_SPEC zend_error(E_NOTICE, "Only variables should be assigned by reference"); if (UNEXPECTED(EG(exception))) { zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } @@ -50087,6 +50088,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_MAKE_REF_SPEC if (UNEXPECTED(EG(exception))) { + ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } @@ -83164,6 +83166,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_MAKE_REF_SPEC_VAR_ zend_error(E_NOTICE, "Only variables should be assigned by reference"); if (UNEXPECTED(EG(exception))) { zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } @@ -102632,6 +102635,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_MAKE_REF_SPEC_CV_U if (UNEXPECTED(EG(exception))) { + ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } }