Skip to content

Commit 8c44301

Browse files
committed
Merge branch 'PHP-8.4'
* PHP-8.4: Fix GH-17307: Internal closure causes JIT failure Generate inline frameless icall handlers only if the optimization level is set to inline Fix GH-15981: Segfault with frameless jumps and minimal JIT Fix GH-15833: Segmentation fault (access null pointer) in ext/spl/spl_array.c
2 parents 5d4707e + 28b448a commit 8c44301

File tree

7 files changed

+175
-36
lines changed

7 files changed

+175
-36
lines changed

ext/opcache/jit/zend_jit.c

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2614,6 +2614,11 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
26142614
goto jit_failure;
26152615
}
26162616
goto done;
2617+
case ZEND_JMP_FRAMELESS:
2618+
if (!zend_jit_jmp_frameless(&ctx, opline, /* exit_addr */ NULL, /* guard */ 0)) {
2619+
goto jit_failure;
2620+
}
2621+
goto done;
26172622
case ZEND_INIT_METHOD_CALL:
26182623
if (opline->op2_type != IS_CONST
26192624
|| Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING) {
@@ -2680,6 +2685,23 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
26802685
goto jit_failure;
26812686
}
26822687
goto done;
2688+
case ZEND_FRAMELESS_ICALL_0:
2689+
jit_frameless_icall0(jit, opline);
2690+
goto done;
2691+
case ZEND_FRAMELESS_ICALL_1:
2692+
op1_info = OP1_INFO();
2693+
jit_frameless_icall1(jit, opline, op1_info);
2694+
goto done;
2695+
case ZEND_FRAMELESS_ICALL_2:
2696+
op1_info = OP1_INFO();
2697+
op2_info = OP2_INFO();
2698+
jit_frameless_icall2(jit, opline, op1_info, op2_info);
2699+
goto done;
2700+
case ZEND_FRAMELESS_ICALL_3:
2701+
op1_info = OP1_INFO();
2702+
op2_info = OP2_INFO();
2703+
jit_frameless_icall3(jit, opline, op1_info, op2_info, OP1_DATA_INFO());
2704+
goto done;
26832705
default:
26842706
break;
26852707
}
@@ -2782,17 +2804,13 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
27822804
case ZEND_FE_FETCH_R:
27832805
case ZEND_FE_FETCH_RW:
27842806
case ZEND_BIND_INIT_STATIC_OR_JMP:
2807+
case ZEND_JMP_FRAMELESS:
27852808
if (!zend_jit_handler(&ctx, opline,
27862809
zend_may_throw(opline, ssa_op, op_array, ssa)) ||
27872810
!zend_jit_cond_jmp(&ctx, opline + 1, ssa->cfg.blocks[b].successors[0])) {
27882811
goto jit_failure;
27892812
}
27902813
break;
2791-
case ZEND_JMP_FRAMELESS:
2792-
if (!zend_jit_jmp_frameless(&ctx, opline, /* exit_addr */ NULL, /* guard */ 0)) {
2793-
goto jit_failure;
2794-
}
2795-
break;
27962814
case ZEND_NEW:
27972815
if (!zend_jit_handler(&ctx, opline, 1)) {
27982816
return 0;
@@ -2830,23 +2848,6 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
28302848
call_level--;
28312849
}
28322850
break;
2833-
case ZEND_FRAMELESS_ICALL_0:
2834-
jit_frameless_icall0(jit, opline);
2835-
goto done;
2836-
case ZEND_FRAMELESS_ICALL_1:
2837-
op1_info = OP1_INFO();
2838-
jit_frameless_icall1(jit, opline, op1_info);
2839-
goto done;
2840-
case ZEND_FRAMELESS_ICALL_2:
2841-
op1_info = OP1_INFO();
2842-
op2_info = OP2_INFO();
2843-
jit_frameless_icall2(jit, opline, op1_info, op2_info);
2844-
goto done;
2845-
case ZEND_FRAMELESS_ICALL_3:
2846-
op1_info = OP1_INFO();
2847-
op2_info = OP2_INFO();
2848-
jit_frameless_icall3(jit, opline, op1_info, op2_info, OP1_DATA_INFO());
2849-
goto done;
28502851
default:
28512852
if (!zend_jit_handler(&ctx, opline,
28522853
zend_may_throw(opline, ssa_op, op_array, ssa))) {

ext/opcache/jit/zend_jit_ir.c

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8507,18 +8507,16 @@ static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, co
85078507
} else {
85088508
ir_ref num_args_ref;
85098509
ir_ref if_internal_func = IR_UNUSED;
8510+
const size_t func_type_offset = is_closure ? offsetof(zend_closure, func.type) : offsetof(zend_function, type);
85108511

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

8514-
if (!is_closure) {
8515-
used_stack_ref = ir_HARD_COPY_A(used_stack_ref); /* load constant once */
8516-
8517-
// JIT: if (EXPECTED(ZEND_USER_CODE(func->type))) {
8518-
ir_ref tmp = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, offsetof(zend_function, type)));
8519-
if_internal_func = ir_IF(ir_AND_U8(tmp, ir_CONST_U8(1)));
8520-
ir_IF_FALSE(if_internal_func);
8521-
}
8516+
// JIT: if (EXPECTED(ZEND_USER_CODE(func->type))) {
8517+
ir_ref tmp = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, func_type_offset));
8518+
if_internal_func = ir_IF(ir_AND_U8(tmp, ir_CONST_U8(1)));
8519+
ir_IF_FALSE(if_internal_func);
85228520

85238521
// JIT: used_stack += (func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args)) * sizeof(zval);
85248522
num_args_ref = ir_CONST_U32(opline->extended_value);
@@ -8545,12 +8543,8 @@ static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, co
85458543
}
85468544
ref = ir_SUB_A(used_stack_ref, ref);
85478545

8548-
if (is_closure) {
8549-
used_stack_ref = ref;
8550-
} else {
8551-
ir_MERGE_WITH_EMPTY_TRUE(if_internal_func);
8552-
used_stack_ref = ir_PHI_2(IR_ADDR, ref, used_stack_ref);
8553-
}
8546+
ir_MERGE_WITH_EMPTY_TRUE(if_internal_func);
8547+
used_stack_ref = ir_PHI_2(IR_ADDR, ref, used_stack_ref);
85548548
}
85558549

85568550
zend_jit_start_reuse_ip(jit);

ext/opcache/tests/jit/gh15981.phpt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
GH-15981 (Segfault with frameless jumps and minimal JIT)
3+
--EXTENSIONS--
4+
opcache
5+
--INI--
6+
opcache.jit=1111
7+
--FILE--
8+
<?php
9+
10+
namespace NS { // Namespace is important to reproduce the issue
11+
class Tester {
12+
static public function findExecutable(): string {
13+
return dirname(__DIR__);
14+
}
15+
}
16+
}
17+
18+
namespace {
19+
var_dump(NS\Tester::findExecutable());
20+
}
21+
22+
?>
23+
--EXPECTF--
24+
string(%d) "%s"

ext/opcache/tests/jit/gh17307.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
GH-17307 (Internal closure causes JIT failure)
3+
--EXTENSIONS--
4+
opcache
5+
simplexml
6+
bcmath
7+
--INI--
8+
opcache.jit=1254
9+
opcache.jit_hot_func=1
10+
opcache.jit_buffer_size=32M
11+
--FILE--
12+
<?php
13+
14+
$simple = new SimpleXMLElement("<root><a/><b/></root>");
15+
16+
function run_loop($firstTerms, $closure) {
17+
foreach ($firstTerms as $firstTerm) {
18+
\debug_zval_dump($firstTerm);
19+
$closure($firstTerm, "10");
20+
}
21+
}
22+
23+
run_loop($simple, bcadd(...));
24+
echo "Done\n";
25+
26+
?>
27+
--EXPECTF--
28+
object(SimpleXMLElement)#%d (0) refcount(3){
29+
}
30+
object(SimpleXMLElement)#%d (0) refcount(3){
31+
}
32+
Done

ext/spl/spl_array.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ PHPAPI zend_class_entry *spl_ce_ArrayObject;
4040

4141
typedef struct _spl_array_object {
4242
zval array;
43+
HashTable *sentinel_array;
4344
uint32_t ht_iter;
4445
int ar_flags;
4546
unsigned char nApplyCount;
@@ -74,6 +75,19 @@ static inline HashTable **spl_array_get_hash_table_ptr(spl_array_object* intern)
7475
return &Z_ARRVAL(intern->array);
7576
} else {
7677
zend_object *obj = Z_OBJ(intern->array);
78+
/* Since we're directly playing with the properties table, we shall initialize the lazy object directly.
79+
* If we don't, it's possible to continue working with the wrong object in case we're using a proxy. */
80+
if (UNEXPECTED(zend_lazy_object_must_init(obj))) {
81+
obj = zend_lazy_object_init(obj);
82+
if (UNEXPECTED(!obj)) {
83+
if (!intern->sentinel_array) {
84+
intern->sentinel_array = zend_new_array(0);
85+
}
86+
return &intern->sentinel_array;
87+
}
88+
}
89+
/* should no longer be lazy */
90+
ZEND_ASSERT(!zend_lazy_object_must_init(obj));
7791
/* rebuild properties */
7892
zend_std_get_properties_ex(obj);
7993
if (GC_REFCOUNT(obj->properties) > 1) {
@@ -129,6 +143,10 @@ static void spl_array_object_free_storage(zend_object *object)
129143
zend_hash_iterator_del(intern->ht_iter);
130144
}
131145

146+
if (UNEXPECTED(intern->sentinel_array)) {
147+
zend_array_release(intern->sentinel_array);
148+
}
149+
132150
zend_object_std_dtor(&intern->std);
133151

134152
zval_ptr_dtor(&intern->array);
@@ -489,6 +507,9 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
489507
uint32_t refcount = 0;
490508
if (!offset || Z_TYPE_P(offset) == IS_NULL) {
491509
ht = spl_array_get_hash_table(intern);
510+
if (UNEXPECTED(ht == intern->sentinel_array)) {
511+
return;
512+
}
492513
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
493514
zend_hash_next_index_insert(ht, value);
494515

@@ -505,6 +526,10 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
505526
}
506527

507528
ht = spl_array_get_hash_table(intern);
529+
if (UNEXPECTED(ht == intern->sentinel_array)) {
530+
spl_hash_key_release(&key);
531+
return;
532+
}
508533
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
509534
if (key.key) {
510535
zend_hash_update_ind(ht, key.key, value);

ext/spl/tests/gh15833_1.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
GH-15833 (Segmentation fault (access null pointer) in ext/spl/spl_array.c)
3+
--CREDITS--
4+
YuanchengJiang
5+
--FILE--
6+
<?php
7+
class C {
8+
public int $a = 1;
9+
}
10+
$reflector = new ReflectionClass(C::class);
11+
$obj = $reflector->newLazyProxy(function ($obj) {
12+
$obj = new C();
13+
return $obj;
14+
});
15+
$recursiveArrayIterator = new RecursiveArrayIterator($obj);
16+
var_dump($recursiveArrayIterator->current());
17+
$recursiveArrayIterator->next();
18+
var_dump($recursiveArrayIterator->current());
19+
?>
20+
--EXPECT--
21+
int(1)
22+
NULL

ext/spl/tests/gh15833_2.phpt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
GH-15833 (Segmentation fault (access null pointer) in ext/spl/spl_array.c)
3+
--CREDITS--
4+
YuanchengJiang
5+
--FILE--
6+
<?php
7+
class C {
8+
public int $a = 1;
9+
}
10+
$reflector = new ReflectionClass(C::class);
11+
$obj = $reflector->newLazyProxy(function ($obj) {
12+
static $counter = 0;
13+
throw new Error('nope ' . ($counter++));
14+
});
15+
$recursiveArrayIterator = new RecursiveArrayIterator($obj);
16+
try {
17+
var_dump($recursiveArrayIterator->current());
18+
} catch (Error $e) {
19+
echo $e->getMessage(), "\n";
20+
}
21+
try {
22+
var_dump($recursiveArrayIterator->current());
23+
} catch (Error $e) {
24+
echo $e->getMessage(), "\n";
25+
}
26+
try {
27+
$recursiveArrayIterator->next();
28+
} catch (Error $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
try {
32+
var_dump($recursiveArrayIterator->current());
33+
} catch (Error $e) {
34+
echo $e->getMessage(), "\n";
35+
}
36+
?>
37+
--EXPECT--
38+
nope 0
39+
nope 1
40+
nope 2
41+
nope 3

0 commit comments

Comments
 (0)