Skip to content

Commit

Permalink
Fix phpGH-17307: Internal closure causes JIT failure
Browse files Browse the repository at this point in the history
`bcadd(...)` is a closure for an internal function, and
`zend_jit_push_call_frame` takes into account both last_var and the
difference in argument numbers not only for user code but also for
internal code. However, this is inconsistent with
`zend_vm_calc_used_stack`, causing argument corruption.
Making this consistent fixes the issue.

I could only reproduce the assertion failure when using Valgrind.
  • Loading branch information
nielsdos committed Jan 1, 2025
1 parent 862ed7e commit ac05bb7
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 14 deletions.
22 changes: 8 additions & 14 deletions ext/opcache/jit/zend_jit_ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -8503,18 +8503,16 @@ static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, co
} else {
ir_ref num_args_ref;
ir_ref if_internal_func = IR_UNUSED;
const size_t func_type_offset = is_closure ? offsetof(zend_closure, func.type) : offsetof(zend_function, type);

used_stack = (ZEND_CALL_FRAME_SLOT + opline->extended_value + ZEND_OBSERVER_ENABLED) * sizeof(zval);
used_stack_ref = ir_CONST_ADDR(used_stack);
used_stack_ref = ir_HARD_COPY_A(used_stack_ref); /* load constant once */

if (!is_closure) {
used_stack_ref = ir_HARD_COPY_A(used_stack_ref); /* load constant once */

// JIT: if (EXPECTED(ZEND_USER_CODE(func->type))) {
ir_ref tmp = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, offsetof(zend_function, type)));
if_internal_func = ir_IF(ir_AND_U8(tmp, ir_CONST_U8(1)));
ir_IF_FALSE(if_internal_func);
}
// JIT: if (EXPECTED(ZEND_USER_CODE(func->type))) {
ir_ref tmp = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, func_type_offset));
if_internal_func = ir_IF(ir_AND_U8(tmp, ir_CONST_U8(1)));
ir_IF_FALSE(if_internal_func);

// JIT: used_stack += (func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args)) * sizeof(zval);
num_args_ref = ir_CONST_U32(opline->extended_value);
Expand All @@ -8541,12 +8539,8 @@ static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, co
}
ref = ir_SUB_A(used_stack_ref, ref);

if (is_closure) {
used_stack_ref = ref;
} else {
ir_MERGE_WITH_EMPTY_TRUE(if_internal_func);
used_stack_ref = ir_PHI_2(IR_ADDR, ref, used_stack_ref);
}
ir_MERGE_WITH_EMPTY_TRUE(if_internal_func);
used_stack_ref = ir_PHI_2(IR_ADDR, ref, used_stack_ref);
}

zend_jit_start_reuse_ip(jit);
Expand Down
32 changes: 32 additions & 0 deletions ext/opcache/tests/jit/gh17307.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
GH-17307 (Internal closure causes JIT failure)
--EXTENSIONS--
opcache
simplexml
bcmath
--INI--
opcache.jit=1254
opcache.jit_hot_func=1
opcache.jit_buffer_size=32M
--FILE--
<?php

$simple = new SimpleXMLElement("<root><a/><b/></root>");

function run_loop($firstTerms, $closure) {
foreach ($firstTerms as $firstTerm) {
\debug_zval_dump($firstTerm);
$closure($firstTerm, "10");
}
}

run_loop($simple, bcadd(...));
echo "Done\n";

?>
--EXPECTF--
object(SimpleXMLElement)#%d (0) refcount(3){
}
object(SimpleXMLElement)#%d (0) refcount(3){
}
Done

0 comments on commit ac05bb7

Please sign in to comment.