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/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) { 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/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_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..56167aaa9d45 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9393,6 +9393,15 @@ 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(); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + 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..97a3344adfa6 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -30512,6 +30512,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))) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE(); @@ -50073,6 +50082,16 @@ 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_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE(); @@ -83142,6 +83161,15 @@ 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)); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE(); @@ -102601,6 +102629,16 @@ 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))) { + + + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } ZVAL_COPY_VALUE(EX_VAR(opline->result.var), op1); } ZEND_VM_NEXT_OPCODE();