mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
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
This commit is contained in:
commit
8c443016e9
7 changed files with 175 additions and 36 deletions
|
@ -2614,6 +2614,11 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
|
||||||
goto jit_failure;
|
goto jit_failure;
|
||||||
}
|
}
|
||||||
goto done;
|
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:
|
case ZEND_INIT_METHOD_CALL:
|
||||||
if (opline->op2_type != IS_CONST
|
if (opline->op2_type != IS_CONST
|
||||||
|| Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING) {
|
|| 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 jit_failure;
|
||||||
}
|
}
|
||||||
goto done;
|
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:
|
default:
|
||||||
break;
|
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_R:
|
||||||
case ZEND_FE_FETCH_RW:
|
case ZEND_FE_FETCH_RW:
|
||||||
case ZEND_BIND_INIT_STATIC_OR_JMP:
|
case ZEND_BIND_INIT_STATIC_OR_JMP:
|
||||||
|
case ZEND_JMP_FRAMELESS:
|
||||||
if (!zend_jit_handler(&ctx, opline,
|
if (!zend_jit_handler(&ctx, opline,
|
||||||
zend_may_throw(opline, ssa_op, op_array, ssa)) ||
|
zend_may_throw(opline, ssa_op, op_array, ssa)) ||
|
||||||
!zend_jit_cond_jmp(&ctx, opline + 1, ssa->cfg.blocks[b].successors[0])) {
|
!zend_jit_cond_jmp(&ctx, opline + 1, ssa->cfg.blocks[b].successors[0])) {
|
||||||
goto jit_failure;
|
goto jit_failure;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ZEND_JMP_FRAMELESS:
|
|
||||||
if (!zend_jit_jmp_frameless(&ctx, opline, /* exit_addr */ NULL, /* guard */ 0)) {
|
|
||||||
goto jit_failure;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ZEND_NEW:
|
case ZEND_NEW:
|
||||||
if (!zend_jit_handler(&ctx, opline, 1)) {
|
if (!zend_jit_handler(&ctx, opline, 1)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -2830,23 +2848,6 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
|
||||||
call_level--;
|
call_level--;
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
if (!zend_jit_handler(&ctx, opline,
|
if (!zend_jit_handler(&ctx, opline,
|
||||||
zend_may_throw(opline, ssa_op, op_array, ssa))) {
|
zend_may_throw(opline, ssa_op, op_array, ssa))) {
|
||||||
|
|
|
@ -8507,18 +8507,16 @@ static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, co
|
||||||
} else {
|
} else {
|
||||||
ir_ref num_args_ref;
|
ir_ref num_args_ref;
|
||||||
ir_ref if_internal_func = IR_UNUSED;
|
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 = (ZEND_CALL_FRAME_SLOT + opline->extended_value + ZEND_OBSERVER_ENABLED) * sizeof(zval);
|
||||||
used_stack_ref = ir_CONST_ADDR(used_stack);
|
used_stack_ref = ir_CONST_ADDR(used_stack);
|
||||||
|
|
||||||
if (!is_closure) {
|
|
||||||
used_stack_ref = ir_HARD_COPY_A(used_stack_ref); /* load constant once */
|
used_stack_ref = ir_HARD_COPY_A(used_stack_ref); /* load constant once */
|
||||||
|
|
||||||
// JIT: if (EXPECTED(ZEND_USER_CODE(func->type))) {
|
// JIT: if (EXPECTED(ZEND_USER_CODE(func->type))) {
|
||||||
ir_ref tmp = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, offsetof(zend_function, 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)));
|
if_internal_func = ir_IF(ir_AND_U8(tmp, ir_CONST_U8(1)));
|
||||||
ir_IF_FALSE(if_internal_func);
|
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);
|
// 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);
|
num_args_ref = ir_CONST_U32(opline->extended_value);
|
||||||
|
@ -8545,13 +8543,9 @@ static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, co
|
||||||
}
|
}
|
||||||
ref = ir_SUB_A(used_stack_ref, ref);
|
ref = ir_SUB_A(used_stack_ref, ref);
|
||||||
|
|
||||||
if (is_closure) {
|
|
||||||
used_stack_ref = ref;
|
|
||||||
} else {
|
|
||||||
ir_MERGE_WITH_EMPTY_TRUE(if_internal_func);
|
ir_MERGE_WITH_EMPTY_TRUE(if_internal_func);
|
||||||
used_stack_ref = ir_PHI_2(IR_ADDR, ref, used_stack_ref);
|
used_stack_ref = ir_PHI_2(IR_ADDR, ref, used_stack_ref);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
zend_jit_start_reuse_ip(jit);
|
zend_jit_start_reuse_ip(jit);
|
||||||
|
|
||||||
|
|
24
ext/opcache/tests/jit/gh15981.phpt
Normal file
24
ext/opcache/tests/jit/gh15981.phpt
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15981 (Segfault with frameless jumps and minimal JIT)
|
||||||
|
--EXTENSIONS--
|
||||||
|
opcache
|
||||||
|
--INI--
|
||||||
|
opcache.jit=1111
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NS { // Namespace is important to reproduce the issue
|
||||||
|
class Tester {
|
||||||
|
static public function findExecutable(): string {
|
||||||
|
return dirname(__DIR__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
var_dump(NS\Tester::findExecutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
string(%d) "%s"
|
32
ext/opcache/tests/jit/gh17307.phpt
Normal file
32
ext/opcache/tests/jit/gh17307.phpt
Normal file
|
@ -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
|
|
@ -40,6 +40,7 @@ PHPAPI zend_class_entry *spl_ce_ArrayObject;
|
||||||
|
|
||||||
typedef struct _spl_array_object {
|
typedef struct _spl_array_object {
|
||||||
zval array;
|
zval array;
|
||||||
|
HashTable *sentinel_array;
|
||||||
uint32_t ht_iter;
|
uint32_t ht_iter;
|
||||||
int ar_flags;
|
int ar_flags;
|
||||||
unsigned char nApplyCount;
|
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);
|
return &Z_ARRVAL(intern->array);
|
||||||
} else {
|
} else {
|
||||||
zend_object *obj = Z_OBJ(intern->array);
|
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 */
|
/* rebuild properties */
|
||||||
zend_std_get_properties_ex(obj);
|
zend_std_get_properties_ex(obj);
|
||||||
if (GC_REFCOUNT(obj->properties) > 1) {
|
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);
|
zend_hash_iterator_del(intern->ht_iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (UNEXPECTED(intern->sentinel_array)) {
|
||||||
|
zend_array_release(intern->sentinel_array);
|
||||||
|
}
|
||||||
|
|
||||||
zend_object_std_dtor(&intern->std);
|
zend_object_std_dtor(&intern->std);
|
||||||
|
|
||||||
zval_ptr_dtor(&intern->array);
|
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;
|
uint32_t refcount = 0;
|
||||||
if (!offset || Z_TYPE_P(offset) == IS_NULL) {
|
if (!offset || Z_TYPE_P(offset) == IS_NULL) {
|
||||||
ht = spl_array_get_hash_table(intern);
|
ht = spl_array_get_hash_table(intern);
|
||||||
|
if (UNEXPECTED(ht == intern->sentinel_array)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
|
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
|
||||||
zend_hash_next_index_insert(ht, value);
|
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);
|
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);
|
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
|
||||||
if (key.key) {
|
if (key.key) {
|
||||||
zend_hash_update_ind(ht, key.key, value);
|
zend_hash_update_ind(ht, key.key, value);
|
||||||
|
|
22
ext/spl/tests/gh15833_1.phpt
Normal file
22
ext/spl/tests/gh15833_1.phpt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15833 (Segmentation fault (access null pointer) in ext/spl/spl_array.c)
|
||||||
|
--CREDITS--
|
||||||
|
YuanchengJiang
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
class C {
|
||||||
|
public int $a = 1;
|
||||||
|
}
|
||||||
|
$reflector = new ReflectionClass(C::class);
|
||||||
|
$obj = $reflector->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
|
41
ext/spl/tests/gh15833_2.phpt
Normal file
41
ext/spl/tests/gh15833_2.phpt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15833 (Segmentation fault (access null pointer) in ext/spl/spl_array.c)
|
||||||
|
--CREDITS--
|
||||||
|
YuanchengJiang
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
class C {
|
||||||
|
public int $a = 1;
|
||||||
|
}
|
||||||
|
$reflector = new ReflectionClass(C::class);
|
||||||
|
$obj = $reflector->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
|
Loading…
Add table
Add a link
Reference in a new issue