From 7fb919a91c1c2ad5236af69c520300953887986e Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:34:28 +0200 Subject: [PATCH] Fix GH-14873: PHP 8.4 min function fails on typed integer The DFA pass may cause the op1 and result argument to be equal to each other. In the VM we always use ZVAL_NULL(result) first, which will also destroy the first argument. Use a temporary result to fix the issue. --- Zend/zend_execute.c | 5 ++- Zend/zend_vm_def.h | 21 +++++++---- Zend/zend_vm_execute.h | 42 +++++++++++++++------- ext/opcache/jit/zend_jit_ir.c | 51 +++++++++++++++++++++++---- ext/standard/tests/array/gh14873.phpt | 29 +++++++++++++++ 5 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 ext/standard/tests/array/gh14873.phpt diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index b92c4c1174cdc..db25ed506bd15 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1552,6 +1552,8 @@ ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data) uint8_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode); zend_function *fbc = ZEND_FLF_FUNC(opline); zval *result = EX_VAR(opline->result.var); + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_execute_data *call = zend_vm_stack_push_call_frame_ex(zend_vm_calc_used_stack(num_args, fbc), ZEND_CALL_NESTED_FUNCTION, fbc, num_args, NULL); call->prev_execute_data = execute_data; @@ -1565,7 +1567,8 @@ ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data) EG(current_execute_data) = call; zend_observer_fcall_begin_prechecked(call, ZEND_OBSERVER_DATA(fbc)); - fbc->internal_function.handler(call, result); + fbc->internal_function.handler(call, &tmp_result); + ZVAL_COPY_VALUE(result, &tmp_result); zend_observer_fcall_end(call, result); EG(current_execute_data) = execute_data; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b710fa874af15..9f6ce56b0eb8d 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9638,9 +9638,9 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER)) SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP1(); HANDLE_EXCEPTION(); } @@ -9651,8 +9651,11 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER)) } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); - function(result, arg1); + function(&tmp_result, arg1); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP1(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -9664,10 +9667,10 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER)) SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R); zval *arg2 = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP1(); FREE_OP2(); HANDLE_EXCEPTION(); @@ -9679,8 +9682,11 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER)) } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2); + function(&tmp_result, arg1, arg2); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP1(); @@ -9698,11 +9704,11 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER)) SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R); zval *arg2 = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R); zval *arg3 = GET_OP_DATA_ZVAL_PTR_DEREF(BP_VAR_R); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP1(); FREE_OP2(); FREE_OP_DATA(); @@ -9715,8 +9721,11 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER)) } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2, arg3); + function(&tmp_result, arg1, arg2, arg3); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP1(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 6cb6ff41ec8d9..a9bd2bbbf3874 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3738,10 +3738,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_2_SPEC_HANDLER SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = get_zval_ptr_deref(opline->op1_type, opline->op1, BP_VAR_R); zval *arg2 = get_zval_ptr_deref(opline->op2_type, opline->op2, BP_VAR_R); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP(opline->op1_type, opline->op1.var); FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); @@ -3753,8 +3753,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_2_SPEC_HANDLER } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2); + function(&tmp_result, arg1, arg2); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP(opline->op1_type, opline->op1.var); @@ -3772,10 +3775,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_2_SPEC_OBSERVE SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = get_zval_ptr_deref(opline->op1_type, opline->op1, BP_VAR_R); zval *arg2 = get_zval_ptr_deref(opline->op2_type, opline->op2, BP_VAR_R); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP(opline->op1_type, opline->op1.var); FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); @@ -3787,8 +3790,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_2_SPEC_OBSERVE } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2); + function(&tmp_result, arg1, arg2); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP(opline->op1_type, opline->op1.var); @@ -3806,11 +3812,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_3_SPEC_HANDLER SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = get_zval_ptr_deref(opline->op1_type, opline->op1, BP_VAR_R); zval *arg2 = get_zval_ptr_deref(opline->op2_type, opline->op2, BP_VAR_R); zval *arg3 = get_op_data_zval_ptr_deref_r((opline+1)->op1_type, (opline+1)->op1); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP(opline->op1_type, opline->op1.var); FREE_OP(opline->op2_type, opline->op2.var); FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -3823,8 +3829,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_3_SPEC_HANDLER } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2, arg3); + function(&tmp_result, arg1, arg2, arg3); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP(opline->op1_type, opline->op1.var); @@ -3846,11 +3855,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVE SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = get_zval_ptr_deref(opline->op1_type, opline->op1, BP_VAR_R); zval *arg2 = get_zval_ptr_deref(opline->op2_type, opline->op2, BP_VAR_R); zval *arg3 = get_op_data_zval_ptr_deref_r((opline+1)->op1_type, (opline+1)->op1); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP(opline->op1_type, opline->op1.var); FREE_OP(opline->op2_type, opline->op2.var); FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -3863,8 +3872,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVE } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2, arg3); + function(&tmp_result, arg1, arg2, arg3); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP(opline->op1_type, opline->op1.var); @@ -4275,9 +4287,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_ SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = get_zval_ptr_deref(opline->op1_type, opline->op1, BP_VAR_R); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP(opline->op1_type, opline->op1.var); HANDLE_EXCEPTION(); } @@ -4288,8 +4300,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_ } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); - function(result, arg1); + function(&tmp_result, arg1); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP(opline->op1_type, opline->op1.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -4301,9 +4316,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_1_SPEC_OBSERVE SAVE_OPLINE(); zval *result = EX_VAR(opline->result.var); - ZVAL_NULL(result); zval *arg1 = get_zval_ptr_deref(opline->op1_type, opline->op1, BP_VAR_R); if (EG(exception)) { + ZVAL_NULL(result); FREE_OP(opline->op1_type, opline->op1.var); HANDLE_EXCEPTION(); } @@ -4314,8 +4329,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_1_SPEC_OBSERVE } else #endif { + zval tmp_result; + ZVAL_NULL(&tmp_result); zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); - function(result, arg1); + function(&tmp_result, arg1); + ZVAL_COPY_VALUE(result, &tmp_result); } FREE_OP(opline->op1_type, opline->op1.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 34d383c7f5f7c..54fe7b3818ba4 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -17103,20 +17103,33 @@ static void jit_frameless_icall1(zend_jit_ctx *jit, const zend_op *opline, uint3 zend_jit_addr op1_addr = OP1_ADDR(); ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr); ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr); - jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + ir_ref tmp_result_ref = IR_UNUSED, result_ref; if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) { zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1); } if (op1_info & MAY_BE_REF) { op1_ref = jit_ZVAL_DEREF_ref(jit, op1_ref); } + if (op1_addr == res_addr) { + tmp_result_ref = ir_ALLOCA(ir_CONST_ADDR(sizeof(zval))); + jit_set_Z_TYPE_INFO(jit, ZEND_ADDR_REF_ZVAL(tmp_result_ref), IS_NULL); + result_ref = tmp_result_ref; + } else { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + result_ref = res_ref; + } ir_ref skip_observer = IR_UNUSED; if (ZEND_OBSERVER_ENABLED) { skip_observer = jit_frameless_observer(jit, opline); } - ir_CALL_2(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref); + ir_CALL_2(IR_VOID, ir_CONST_ADDR((size_t)function), result_ref, op1_ref); + + if (op1_addr == res_addr) { + jit_ZVAL_COPY(jit, res_addr, MAY_BE_ANY, ZEND_ADDR_REF_ZVAL(tmp_result_ref), MAY_BE_ANY, 0); + ir_AFREE(ir_CONST_ADDR(sizeof(zval))); + } if (skip_observer != IR_UNUSED) { ir_MERGE_WITH(skip_observer); @@ -17145,7 +17158,7 @@ static void jit_frameless_icall2(zend_jit_ctx *jit, const zend_op *opline, uint3 ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr); ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr); ir_ref op2_ref = jit_ZVAL_ADDR(jit, op2_addr); - jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + ir_ref tmp_result_ref = IR_UNUSED, result_ref; if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) { zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1); } @@ -17158,13 +17171,26 @@ static void jit_frameless_icall2(zend_jit_ctx *jit, const zend_op *opline, uint3 if (op2_info & MAY_BE_REF) { op2_ref = jit_ZVAL_DEREF_ref(jit, op2_ref); } + if (op1_addr == res_addr || op2_addr == res_addr) { + tmp_result_ref = ir_ALLOCA(ir_CONST_ADDR(sizeof(zval))); + jit_set_Z_TYPE_INFO(jit, ZEND_ADDR_REF_ZVAL(tmp_result_ref), IS_NULL); + result_ref = tmp_result_ref; + } else { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + result_ref = res_ref; + } ir_ref skip_observer = IR_UNUSED; if (ZEND_OBSERVER_ENABLED) { skip_observer = jit_frameless_observer(jit, opline); } - ir_CALL_3(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref, op2_ref); + ir_CALL_3(IR_VOID, ir_CONST_ADDR((size_t)function), result_ref, op1_ref, op2_ref); + + if (op1_addr == res_addr || op2_addr == res_addr) { + jit_ZVAL_COPY(jit, res_addr, MAY_BE_ANY, ZEND_ADDR_REF_ZVAL(tmp_result_ref), MAY_BE_ANY, 0); + ir_AFREE(ir_CONST_ADDR(sizeof(zval))); + } if (skip_observer != IR_UNUSED) { ir_MERGE_WITH(skip_observer); @@ -17204,7 +17230,7 @@ static void jit_frameless_icall3(zend_jit_ctx *jit, const zend_op *opline, uint3 ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr); ir_ref op2_ref = jit_ZVAL_ADDR(jit, op2_addr); ir_ref op3_ref = jit_ZVAL_ADDR(jit, op3_addr); - jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + ir_ref tmp_result_ref = IR_UNUSED, result_ref; if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) { zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1); } @@ -17223,13 +17249,26 @@ static void jit_frameless_icall3(zend_jit_ctx *jit, const zend_op *opline, uint3 if (op1_data_info & MAY_BE_REF) { op3_ref = jit_ZVAL_DEREF_ref(jit, op3_ref); } + if (op1_addr == res_addr || op2_addr == res_addr || op3_addr == res_addr) { + tmp_result_ref = ir_ALLOCA(ir_CONST_ADDR(sizeof(zval))); + jit_set_Z_TYPE_INFO(jit, ZEND_ADDR_REF_ZVAL(tmp_result_ref), IS_NULL); + result_ref = tmp_result_ref; + } else { + jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); + result_ref = res_ref; + } ir_ref skip_observer = IR_UNUSED; if (ZEND_OBSERVER_ENABLED) { skip_observer = jit_frameless_observer(jit, opline); } - ir_CALL_4(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref, op2_ref, op3_ref); + ir_CALL_4(IR_VOID, ir_CONST_ADDR((size_t)function), result_ref, op1_ref, op2_ref, op3_ref); + + if (op1_addr == res_addr || op2_addr == res_addr || op3_addr == res_addr) { + jit_ZVAL_COPY(jit, res_addr, MAY_BE_ANY, ZEND_ADDR_REF_ZVAL(tmp_result_ref), MAY_BE_ANY, 0); + ir_AFREE(ir_CONST_ADDR(sizeof(zval))); + } if (skip_observer != IR_UNUSED) { ir_MERGE_WITH(skip_observer); diff --git a/ext/standard/tests/array/gh14873.phpt b/ext/standard/tests/array/gh14873.phpt new file mode 100644 index 0000000000000..f1680efd5a3f2 --- /dev/null +++ b/ext/standard/tests/array/gh14873.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-14873 (PHP 8.4 min function fails on typed integer) +--FILE-- + +--EXPECT-- +string(3) "boo" +int(5) +int(5)