Skip to content

Commit e6ff7b4

Browse files
committed
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.
1 parent 0b3c506 commit e6ff7b4

File tree

5 files changed

+123
-25
lines changed

5 files changed

+123
-25
lines changed

Zend/zend_execute.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,8 @@ ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data)
15521552
uint8_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
15531553
zend_function *fbc = ZEND_FLF_FUNC(opline);
15541554
zval *result = EX_VAR(opline->result.var);
1555+
zval tmp_result;
1556+
ZVAL_NULL(&tmp_result);
15551557

15561558
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);
15571559
call->prev_execute_data = execute_data;
@@ -1565,7 +1567,8 @@ ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data)
15651567
EG(current_execute_data) = call;
15661568

15671569
zend_observer_fcall_begin_prechecked(call, ZEND_OBSERVER_DATA(fbc));
1568-
fbc->internal_function.handler(call, result);
1570+
fbc->internal_function.handler(call, &tmp_result);
1571+
ZVAL_COPY_VALUE(result, &tmp_result);
15691572
zend_observer_fcall_end(call, result);
15701573

15711574
EG(current_execute_data) = execute_data;

Zend/zend_vm_def.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9638,9 +9638,9 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER))
96389638
SAVE_OPLINE();
96399639

96409640
zval *result = EX_VAR(opline->result.var);
9641-
ZVAL_NULL(result);
96429641
zval *arg1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R);
96439642
if (EG(exception)) {
9643+
ZVAL_NULL(result);
96449644
FREE_OP1();
96459645
HANDLE_EXCEPTION();
96469646
}
@@ -9651,8 +9651,11 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER))
96519651
} else
96529652
#endif
96539653
{
9654+
zval tmp_result;
9655+
ZVAL_NULL(&tmp_result);
96549656
zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline);
9655-
function(result, arg1);
9657+
function(&tmp_result, arg1);
9658+
ZVAL_COPY_VALUE(result, &tmp_result);
96569659
}
96579660
FREE_OP1();
96589661
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
@@ -9664,10 +9667,10 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER))
96649667
SAVE_OPLINE();
96659668

96669669
zval *result = EX_VAR(opline->result.var);
9667-
ZVAL_NULL(result);
96689670
zval *arg1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R);
96699671
zval *arg2 = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R);
96709672
if (EG(exception)) {
9673+
ZVAL_NULL(result);
96719674
FREE_OP1();
96729675
FREE_OP2();
96739676
HANDLE_EXCEPTION();
@@ -9679,8 +9682,11 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER))
96799682
} else
96809683
#endif
96819684
{
9685+
zval tmp_result;
9686+
ZVAL_NULL(&tmp_result);
96829687
zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline);
9683-
function(result, arg1, arg2);
9688+
function(&tmp_result, arg1, arg2);
9689+
ZVAL_COPY_VALUE(result, &tmp_result);
96849690
}
96859691

96869692
FREE_OP1();
@@ -9698,11 +9704,11 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER))
96989704
SAVE_OPLINE();
96999705

97009706
zval *result = EX_VAR(opline->result.var);
9701-
ZVAL_NULL(result);
97029707
zval *arg1 = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R);
97039708
zval *arg2 = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R);
97049709
zval *arg3 = GET_OP_DATA_ZVAL_PTR_DEREF(BP_VAR_R);
97059710
if (EG(exception)) {
9711+
ZVAL_NULL(result);
97069712
FREE_OP1();
97079713
FREE_OP2();
97089714
FREE_OP_DATA();
@@ -9715,8 +9721,11 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER))
97159721
} else
97169722
#endif
97179723
{
9724+
zval tmp_result;
9725+
ZVAL_NULL(&tmp_result);
97189726
zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline);
9719-
function(result, arg1, arg2, arg3);
9727+
function(&tmp_result, arg1, arg2, arg3);
9728+
ZVAL_COPY_VALUE(result, &tmp_result);
97209729
}
97219730

97229731
FREE_OP1();

Zend/zend_vm_execute.h

Lines changed: 30 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/opcache/jit/zend_jit_ir.c

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17103,20 +17103,33 @@ static void jit_frameless_icall1(zend_jit_ctx *jit, const zend_op *opline, uint3
1710317103
zend_jit_addr op1_addr = OP1_ADDR();
1710417104
ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr);
1710517105
ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr);
17106-
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
17106+
ir_ref tmp_result_ref, result_ref;
1710717107
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
1710817108
zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1);
1710917109
}
1711017110
if (op1_info & MAY_BE_REF) {
1711117111
op1_ref = jit_ZVAL_DEREF_ref(jit, op1_ref);
1711217112
}
17113+
if (op1_addr == res_addr) {
17114+
tmp_result_ref = ir_ALLOCA(ir_CONST_ADDR(sizeof(zval)));
17115+
jit_set_Z_TYPE_INFO(jit, ZEND_ADDR_REF_ZVAL(tmp_result_ref), IS_NULL);
17116+
result_ref = tmp_result_ref;
17117+
} else {
17118+
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
17119+
result_ref = res_ref;
17120+
}
1711317121

1711417122
ir_ref skip_observer = IR_UNUSED;
1711517123
if (ZEND_OBSERVER_ENABLED) {
1711617124
skip_observer = jit_frameless_observer(jit, opline);
1711717125
}
1711817126

17119-
ir_CALL_2(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref);
17127+
ir_CALL_2(IR_VOID, ir_CONST_ADDR((size_t)function), result_ref, op1_ref);
17128+
17129+
if (op1_addr == res_addr) {
17130+
jit_ZVAL_COPY(jit, res_addr, MAY_BE_ANY, ZEND_ADDR_REF_ZVAL(tmp_result_ref), MAY_BE_ANY, 0);
17131+
ir_AFREE(ir_CONST_ADDR(sizeof(zval)));
17132+
}
1712017133

1712117134
if (skip_observer != IR_UNUSED) {
1712217135
ir_MERGE_WITH(skip_observer);
@@ -17145,7 +17158,7 @@ static void jit_frameless_icall2(zend_jit_ctx *jit, const zend_op *opline, uint3
1714517158
ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr);
1714617159
ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr);
1714717160
ir_ref op2_ref = jit_ZVAL_ADDR(jit, op2_addr);
17148-
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
17161+
ir_ref tmp_result_ref, result_ref;
1714917162
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
1715017163
zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1);
1715117164
}
@@ -17158,13 +17171,26 @@ static void jit_frameless_icall2(zend_jit_ctx *jit, const zend_op *opline, uint3
1715817171
if (op2_info & MAY_BE_REF) {
1715917172
op2_ref = jit_ZVAL_DEREF_ref(jit, op2_ref);
1716017173
}
17174+
if (op1_addr == res_addr || op2_addr == res_addr) {
17175+
tmp_result_ref = ir_ALLOCA(ir_CONST_ADDR(sizeof(zval)));
17176+
jit_set_Z_TYPE_INFO(jit, ZEND_ADDR_REF_ZVAL(tmp_result_ref), IS_NULL);
17177+
result_ref = tmp_result_ref;
17178+
} else {
17179+
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
17180+
result_ref = res_ref;
17181+
}
1716117182

1716217183
ir_ref skip_observer = IR_UNUSED;
1716317184
if (ZEND_OBSERVER_ENABLED) {
1716417185
skip_observer = jit_frameless_observer(jit, opline);
1716517186
}
1716617187

17167-
ir_CALL_3(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref, op2_ref);
17188+
ir_CALL_3(IR_VOID, ir_CONST_ADDR((size_t)function), result_ref, op1_ref, op2_ref);
17189+
17190+
if (op1_addr == res_addr || op2_addr == res_addr) {
17191+
jit_ZVAL_COPY(jit, res_addr, MAY_BE_ANY, ZEND_ADDR_REF_ZVAL(tmp_result_ref), MAY_BE_ANY, 0);
17192+
ir_AFREE(ir_CONST_ADDR(sizeof(zval)));
17193+
}
1716817194

1716917195
if (skip_observer != IR_UNUSED) {
1717017196
ir_MERGE_WITH(skip_observer);
@@ -17204,7 +17230,7 @@ static void jit_frameless_icall3(zend_jit_ctx *jit, const zend_op *opline, uint3
1720417230
ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr);
1720517231
ir_ref op2_ref = jit_ZVAL_ADDR(jit, op2_addr);
1720617232
ir_ref op3_ref = jit_ZVAL_ADDR(jit, op3_addr);
17207-
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
17233+
ir_ref tmp_result_ref, result_ref;
1720817234
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
1720917235
zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1);
1721017236
}
@@ -17223,13 +17249,26 @@ static void jit_frameless_icall3(zend_jit_ctx *jit, const zend_op *opline, uint3
1722317249
if (op1_data_info & MAY_BE_REF) {
1722417250
op3_ref = jit_ZVAL_DEREF_ref(jit, op3_ref);
1722517251
}
17252+
if (op1_addr == res_addr || op2_addr == res_addr || op3_addr == res_addr) {
17253+
tmp_result_ref = ir_ALLOCA(ir_CONST_ADDR(sizeof(zval)));
17254+
jit_set_Z_TYPE_INFO(jit, ZEND_ADDR_REF_ZVAL(tmp_result_ref), IS_NULL);
17255+
result_ref = tmp_result_ref;
17256+
} else {
17257+
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
17258+
result_ref = res_ref;
17259+
}
1722617260

1722717261
ir_ref skip_observer = IR_UNUSED;
1722817262
if (ZEND_OBSERVER_ENABLED) {
1722917263
skip_observer = jit_frameless_observer(jit, opline);
1723017264
}
1723117265

17232-
ir_CALL_4(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref, op2_ref, op3_ref);
17266+
ir_CALL_4(IR_VOID, ir_CONST_ADDR((size_t)function), result_ref, op1_ref, op2_ref, op3_ref);
17267+
17268+
if (op1_addr == res_addr || op2_addr == res_addr || op3_addr == res_addr) {
17269+
jit_ZVAL_COPY(jit, res_addr, MAY_BE_ANY, ZEND_ADDR_REF_ZVAL(tmp_result_ref), MAY_BE_ANY, 0);
17270+
ir_AFREE(ir_CONST_ADDR(sizeof(zval)));
17271+
}
1723317272

1723417273
if (skip_observer != IR_UNUSED) {
1723517274
ir_MERGE_WITH(skip_observer);

ext/standard/tests/array/gh14873.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GH-14873 (PHP 8.4 min function fails on typed integer)
3+
--FILE--
4+
<?php
5+
6+
function testTrim1(string $value): string {
7+
$value = trim($value);
8+
return $value;
9+
}
10+
11+
function testMin2(int $value): int {
12+
$value = min($value, 100);
13+
return $value;
14+
}
15+
16+
function testMin3(int $value): int {
17+
$value = min($value, 100, 200);
18+
return $value;
19+
}
20+
21+
var_dump(testTrim1(" boo "));
22+
var_dump(testMin2(5));
23+
var_dump(testMin3(5));
24+
25+
?>
26+
--EXPECT--
27+
string(3) "boo"
28+
int(5)
29+
int(5)

0 commit comments

Comments
 (0)