diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index 2b925a9cea1..756db22ace9 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -2614,6 +2614,11 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
goto jit_failure;
}
goto done;
+ case ZEND_JMP_FRAMELESS:
+ if (!zend_jit_jmp_frameless(&ctx, opline, /* exit_addr */ NULL, /* guard */ 0)) {
+ goto jit_failure;
+ }
+ goto done;
case ZEND_INIT_METHOD_CALL:
if (opline->op2_type != IS_CONST
|| 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
goto jit_failure;
}
goto done;
+ case ZEND_FRAMELESS_ICALL_0:
+ jit_frameless_icall0(jit, opline);
+ goto done;
+ case ZEND_FRAMELESS_ICALL_1:
+ op1_info = OP1_INFO();
+ jit_frameless_icall1(jit, opline, op1_info);
+ goto done;
+ case ZEND_FRAMELESS_ICALL_2:
+ op1_info = OP1_INFO();
+ op2_info = OP2_INFO();
+ jit_frameless_icall2(jit, opline, op1_info, op2_info);
+ goto done;
+ case ZEND_FRAMELESS_ICALL_3:
+ op1_info = OP1_INFO();
+ op2_info = OP2_INFO();
+ jit_frameless_icall3(jit, opline, op1_info, op2_info, OP1_DATA_INFO());
+ goto done;
default:
break;
}
@@ -2782,17 +2804,13 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
case ZEND_BIND_INIT_STATIC_OR_JMP:
+ case ZEND_JMP_FRAMELESS:
if (!zend_jit_handler(&ctx, opline,
zend_may_throw(opline, ssa_op, op_array, ssa)) ||
!zend_jit_cond_jmp(&ctx, opline + 1, ssa->cfg.blocks[b].successors[0])) {
goto jit_failure;
}
break;
- case ZEND_JMP_FRAMELESS:
- if (!zend_jit_jmp_frameless(&ctx, opline, /* exit_addr */ NULL, /* guard */ 0)) {
- goto jit_failure;
- }
- break;
case ZEND_NEW:
if (!zend_jit_handler(&ctx, opline, 1)) {
return 0;
@@ -2830,23 +2848,6 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
call_level--;
}
break;
- case ZEND_FRAMELESS_ICALL_0:
- jit_frameless_icall0(jit, opline);
- goto done;
- case ZEND_FRAMELESS_ICALL_1:
- op1_info = OP1_INFO();
- jit_frameless_icall1(jit, opline, op1_info);
- goto done;
- case ZEND_FRAMELESS_ICALL_2:
- op1_info = OP1_INFO();
- op2_info = OP2_INFO();
- jit_frameless_icall2(jit, opline, op1_info, op2_info);
- goto done;
- case ZEND_FRAMELESS_ICALL_3:
- op1_info = OP1_INFO();
- op2_info = OP2_INFO();
- jit_frameless_icall3(jit, opline, op1_info, op2_info, OP1_DATA_INFO());
- goto done;
default:
if (!zend_jit_handler(&ctx, opline,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 79ddfae4d6f..2158438aea0 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -8507,18 +8507,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);
@@ -8545,12 +8543,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);
diff --git a/ext/opcache/tests/jit/gh15981.phpt b/ext/opcache/tests/jit/gh15981.phpt
new file mode 100644
index 00000000000..823b6122dc1
--- /dev/null
+++ b/ext/opcache/tests/jit/gh15981.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-15981 (Segfault with frameless jumps and minimal JIT)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.jit=1111
+--FILE--
+
+--EXPECTF--
+string(%d) "%s"
diff --git a/ext/opcache/tests/jit/gh17307.phpt b/ext/opcache/tests/jit/gh17307.phpt
new file mode 100644
index 00000000000..292d695963c
--- /dev/null
+++ b/ext/opcache/tests/jit/gh17307.phpt
@@ -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--
+");
+
+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
diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c
index af886944bc4..5021bc441f9 100644
--- a/ext/spl/spl_array.c
+++ b/ext/spl/spl_array.c
@@ -40,6 +40,7 @@ PHPAPI zend_class_entry *spl_ce_ArrayObject;
typedef struct _spl_array_object {
zval array;
+ HashTable *sentinel_array;
uint32_t ht_iter;
int ar_flags;
unsigned char nApplyCount;
@@ -74,6 +75,19 @@ static inline HashTable **spl_array_get_hash_table_ptr(spl_array_object* intern)
return &Z_ARRVAL(intern->array);
} else {
zend_object *obj = Z_OBJ(intern->array);
+ /* Since we're directly playing with the properties table, we shall initialize the lazy object directly.
+ * If we don't, it's possible to continue working with the wrong object in case we're using a proxy. */
+ if (UNEXPECTED(zend_lazy_object_must_init(obj))) {
+ obj = zend_lazy_object_init(obj);
+ if (UNEXPECTED(!obj)) {
+ if (!intern->sentinel_array) {
+ intern->sentinel_array = zend_new_array(0);
+ }
+ return &intern->sentinel_array;
+ }
+ }
+ /* should no longer be lazy */
+ ZEND_ASSERT(!zend_lazy_object_must_init(obj));
/* rebuild properties */
zend_std_get_properties_ex(obj);
if (GC_REFCOUNT(obj->properties) > 1) {
@@ -129,6 +143,10 @@ static void spl_array_object_free_storage(zend_object *object)
zend_hash_iterator_del(intern->ht_iter);
}
+ if (UNEXPECTED(intern->sentinel_array)) {
+ zend_array_release(intern->sentinel_array);
+ }
+
zend_object_std_dtor(&intern->std);
zval_ptr_dtor(&intern->array);
@@ -489,6 +507,9 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
uint32_t refcount = 0;
if (!offset || Z_TYPE_P(offset) == IS_NULL) {
ht = spl_array_get_hash_table(intern);
+ if (UNEXPECTED(ht == intern->sentinel_array)) {
+ return;
+ }
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
zend_hash_next_index_insert(ht, value);
@@ -505,6 +526,10 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
}
ht = spl_array_get_hash_table(intern);
+ if (UNEXPECTED(ht == intern->sentinel_array)) {
+ spl_hash_key_release(&key);
+ return;
+ }
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
if (key.key) {
zend_hash_update_ind(ht, key.key, value);
diff --git a/ext/spl/tests/gh15833_1.phpt b/ext/spl/tests/gh15833_1.phpt
new file mode 100644
index 00000000000..d658a770e80
--- /dev/null
+++ b/ext/spl/tests/gh15833_1.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-15833 (Segmentation fault (access null pointer) in ext/spl/spl_array.c)
+--CREDITS--
+YuanchengJiang
+--FILE--
+newLazyProxy(function ($obj) {
+ $obj = new C();
+ return $obj;
+});
+$recursiveArrayIterator = new RecursiveArrayIterator($obj);
+var_dump($recursiveArrayIterator->current());
+$recursiveArrayIterator->next();
+var_dump($recursiveArrayIterator->current());
+?>
+--EXPECT--
+int(1)
+NULL
diff --git a/ext/spl/tests/gh15833_2.phpt b/ext/spl/tests/gh15833_2.phpt
new file mode 100644
index 00000000000..8f30921741f
--- /dev/null
+++ b/ext/spl/tests/gh15833_2.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GH-15833 (Segmentation fault (access null pointer) in ext/spl/spl_array.c)
+--CREDITS--
+YuanchengJiang
+--FILE--
+newLazyProxy(function ($obj) {
+ static $counter = 0;
+ throw new Error('nope ' . ($counter++));
+});
+$recursiveArrayIterator = new RecursiveArrayIterator($obj);
+try {
+ var_dump($recursiveArrayIterator->current());
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ var_dump($recursiveArrayIterator->current());
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ $recursiveArrayIterator->next();
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ var_dump($recursiveArrayIterator->current());
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+nope 0
+nope 1
+nope 2
+nope 3