diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c index 154baddc65426..c54c531bcfc1a 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -38,6 +38,7 @@ typedef struct _optimizer_call_info { zend_function *func; zend_op *opline; + zend_op *last_check_func_arg_opline; bool is_prototype; bool try_inline; uint32_t func_arg_num; @@ -252,6 +253,14 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) if (call_stack[call - 1].func_arg_num != (uint32_t)-1 && has_known_send_mode(&call_stack[call - 1], call_stack[call - 1].func_arg_num)) { if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) { + /* There's no TMP specialization for FETCH_OBJ_W/FETCH_DIM_W. Avoid + * converting it and error at runtime in the FUNC_ARG variant. */ + if ((opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG) + && (opline->op1_type == IS_TMP_VAR || call_stack[call - 1].last_check_func_arg_opline == NULL)) { + /* Don't remove the associated CHECK_FUNC_ARG opcode. */ + call_stack[call - 1].last_check_func_arg_opline = NULL; + break; + } if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) { opline->opcode -= 9; } else { @@ -298,11 +307,21 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) { call_stack[call - 1].func_arg_num = opline->op2.num; - MAKE_NOP(opline); + call_stack[call - 1].last_check_func_arg_opline = opline; } break; - case ZEND_SEND_VAR_EX: case ZEND_SEND_FUNC_ARG: + /* Don't transform SEND_FUNC_ARG if any FETCH opcodes weren't transformed. */ + if (call_stack[call - 1].last_check_func_arg_opline == NULL) { + if (opline->op2_type == IS_CONST) { + call_stack[call - 1].try_inline = 0; + } + break; + } + MAKE_NOP(call_stack[call - 1].last_check_func_arg_opline); + call_stack[call - 1].last_check_func_arg_opline = NULL; + ZEND_FALLTHROUGH; + case ZEND_SEND_VAR_EX: if (opline->op2_type == IS_CONST) { call_stack[call - 1].try_inline = 0; break; diff --git a/Zend/tests/gh12102_1.phpt b/Zend/tests/gh12102_1.phpt new file mode 100644 index 0000000000000..1d548d6b28f3c --- /dev/null +++ b/Zend/tests/gh12102_1.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-12102: Incorrect "Cannot use temporary expression in write context" error for BP_VAR_FUNC_ARG +--FILE-- +getMessage(), "\n"; + } +} + +/* Intentionally declared after test() to avoid compile-time checking of ref args. */ + +function byVal($arg) { + var_dump($arg); +} + +function byRef(&$arg) { + var_dump($arg); +} + +test('y'); + +?> +--EXPECT-- +string(1) "y" +Cannot use temporary expression in write context diff --git a/Zend/tests/gh12102_2.phpt b/Zend/tests/gh12102_2.phpt new file mode 100644 index 0000000000000..dacc11c03081a --- /dev/null +++ b/Zend/tests/gh12102_2.phpt @@ -0,0 +1,43 @@ +--TEST-- +GH-12102: Incorrect "Cannot use temporary expression in write context" error for BP_VAR_FUNC_ARG +--FILE-- + +--EXPECTF-- +Warning: Undefined array key 0 in %s on line %d +array(0) { +} +array(1) { + [0]=> + array(1) { + [0]=> + int(42) + } +} diff --git a/Zend/tests/gh12102_3.phpt b/Zend/tests/gh12102_3.phpt new file mode 100644 index 0000000000000..741bce5ab1ba1 --- /dev/null +++ b/Zend/tests/gh12102_3.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-12102: Incorrect "Cannot use temporary expression in write context" error for BP_VAR_FUNC_ARG +--FILE-- +getMessage(), "\n"; + } +} + +/* Intentionally declared after test() to avoid compile-time checking of ref args. */ + +const C = ['foo']; + +function byVal($arg) { + var_dump($arg); +} + +function byRef(&$arg) { + var_dump($arg); +} + +test('y'); + +?> +--EXPECT-- +string(3) "foo" +Cannot use temporary expression in write context diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 75127b8b8d18a..c0cd3c97341f9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2816,7 +2816,11 @@ static zend_op *zend_compile_simple_var(znode *result, zend_ast *ast, uint32_t t static void zend_separate_if_call_and_write(znode *node, zend_ast *ast, uint32_t type) /* {{{ */ { - if (type != BP_VAR_R && type != BP_VAR_IS && zend_is_call(ast)) { + if (type != BP_VAR_R + && type != BP_VAR_IS + /* Whether a FUNC_ARG is R may only be determined at runtime. */ + && type != BP_VAR_FUNC_ARG + && zend_is_call(ast)) { if (node->op_type == IS_VAR) { zend_op *opline = zend_emit_op(NULL, ZEND_SEPARATE, node, NULL); opline->result_type = IS_VAR;