Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ PHP NEWS
. Fixed Collator attribute and strength methods not rejecting an
unconstructed Collator. (Ilia Alshanetsky)

- Standard:
. Improved performance of array_splice() when inserting without removing
elements. (mehmetcansahin)

<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@ PHP 8.7 UPGRADE NOTES
========================================
14. Performance Improvements
========================================

- Standard:
. Improved performance of array_splice() when inserting without removing
elements.
6 changes: 3 additions & 3 deletions Zend/Zend.m4
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ uintptr_t __attribute__((preserve_none,noinline,used)) fun(uintptr_t a, uintptr_
return (uintptr_t)const3;
}

uintptr_t __attribute__((preserve_none)) test(void) {
uintptr_t __attribute__((preserve_none,noinline)) test(void) {
uintptr_t ret;

#if defined(__x86_64__)
Expand All @@ -531,7 +531,7 @@ uintptr_t __attribute__((preserve_none)) test(void) {
#endif
: "=a" (ret)
: "r" (const1), "r" (const2), "r" (key)
: "r12", "r13"
: "r12", "r13", "memory", "cc"
);
#elif defined(__aarch64__)
__asm__ __volatile__(
Expand All @@ -547,7 +547,7 @@ uintptr_t __attribute__((preserve_none)) test(void) {
"mov %0, x0\n"
: "=r" (ret)
: "r" (const1), "r" (const2), "r" (key)
: "x0", "x21", "x22", "x30"
: "x0", "x20", "x21", "x30", "memory", "cc"
);
#else
# error
Expand Down
21 changes: 21 additions & 0 deletions Zend/tests/lazy_objects/oss_fuzz_536440507.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
OSS-Fuzz #536440507 (Immutable class incorrect assertion)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
--FILE--
<?php

class C {
// Something that emits a warning so it can't be folded at compile time
public mixed $b = 340282366920938463454151235394913%435650;
}
$o = (new ReflectionClass(C::class))->newLazyGhost(function ($obj) {});
var_dump($o->b);

?>
--EXPECTF--
Warning: The float 3.4028236692093845E+32 is not representable as an int, cast occurred in %s on line %d
int(%s)
28 changes: 21 additions & 7 deletions Zend/zend_lazy_objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ bool zend_lazy_object_decr_lazy_props(const zend_object *obj)
return info->lazy_properties_count == 0;
}

/* See zend_update_class_constants(). */
static zend_always_inline bool zend_class_constants_are_updated(const zend_class_entry *ce) {
if (ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED) {
return true;
}
if (ZEND_MAP_PTR(ce->mutable_data)) {
const zend_class_mutable_data *mutable_data = ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
return mutable_data && (mutable_data->ce_flags & ZEND_ACC_CONSTANTS_UPDATED);
}
return false;
}

/**
* Making objects lazy
*/
Expand Down Expand Up @@ -258,11 +270,9 @@ ZEND_API zend_object *zend_object_make_lazy(zend_object *obj,
return NULL;
}

if (UNEXPECTED(!(reflection_ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) {
if (UNEXPECTED(zend_update_class_constants(reflection_ce) != SUCCESS)) {
ZEND_ASSERT(EG(exception));
return NULL;
}
if (UNEXPECTED(zend_update_class_constants(reflection_ce) != SUCCESS)) {
ZEND_ASSERT(EG(exception));
return NULL;
}

obj = zend_objects_new(reflection_ce);
Expand Down Expand Up @@ -382,7 +392,9 @@ ZEND_API zend_object *zend_lazy_object_mark_as_initialized(zend_object *obj)

zend_class_entry *ce = obj->ce;

ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED);
#if ZEND_DEBUG
ZEND_ASSERT(zend_class_constants_are_updated(ce));
#endif

zval *default_properties_table = CE_DEFAULT_PROPERTIES_TABLE(ce);
zval *properties_table = obj->properties_table;
Expand Down Expand Up @@ -612,7 +624,9 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)

zend_class_entry *ce = obj->ce;

ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED);
#if ZEND_DEBUG
ZEND_ASSERT(zend_class_constants_are_updated(ce));
#endif

if (zend_object_is_lazy_proxy(obj)) {
return zend_lazy_object_init_proxy(obj);
Expand Down
20 changes: 20 additions & 0 deletions ext/intl/uchar/tests/enumCharNames_callback_exception.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
IntlChar::enumCharNames() propagates callback exceptions and remains usable
--EXTENSIONS--
intl
--FILE--
<?php
try {
IntlChar::enumCharNames(65, 68, static function ($codepoint) {
echo $codepoint, "\n";
throw new Exception('Stop enumeration');
});
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
var_dump(IntlChar::enumCharNames(65, 68, static fn() => false));
?>
--EXPECT--
65
Stop enumeration
bool(true)
2 changes: 1 addition & 1 deletion ext/intl/uchar/uchar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ static UBool enumCharNames_callback(enumCharNames_data *context,
}
zval_ptr_dtor(&retval);
zval_ptr_dtor_str(&args[2]);
return 1;
return !EG(exception);
}
IC_METHOD(enumCharNames) {
UChar32 start, limit;
Expand Down
5 changes: 5 additions & 0 deletions ext/opcache/jit/zend_jit_ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -14298,6 +14298,11 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
ZEND_ASSERT(Z_TYPE_P(member) == IS_STRING && Z_STRVAL_P(member)[0] != '\0');
prop_info = zend_get_known_property_info(op_array, ce, Z_STR_P(member), on_this, op_array->filename);

if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && prop_type == IS_UNDEF) {
prop_info = NULL;
trace_ce = NULL;
}

if (on_this) {
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
obj_ref = jit_Z_PTR(jit, this_addr);
Expand Down
7 changes: 5 additions & 2 deletions ext/opcache/jit/zend_jit_trace.c
Original file line number Diff line number Diff line change
Expand Up @@ -8739,10 +8739,13 @@ int ZEND_FASTCALL zend_jit_trace_exit(uint32_t exit_num, zend_jit_registers_buf
const zend_op *op = t->exit_info[exit_num].opline;
ZEND_ASSERT(op);
op--;
if (op->opcode == ZEND_FETCH_DIM_IS || op->opcode == ZEND_FETCH_OBJ_IS) {
if (op->opcode == ZEND_FETCH_DIM_IS) {
ZVAL_NULL(EX_VAR_NUM(i));
} else if (op->opcode == ZEND_FETCH_OBJ_IS
&& (Z_PROP_FLAG_P(val) & (IS_PROP_LAZY|IS_PROP_UNINIT)) == IS_PROP_UNINIT) {
ZVAL_NULL(EX_VAR_NUM(i));
} else {
ZEND_ASSERT(op->opcode == ZEND_FETCH_DIM_R || op->opcode == ZEND_FETCH_LIST_R || op->opcode == ZEND_FETCH_OBJ_R || op->opcode == ZEND_FETCH_DIM_FUNC_ARG || op->opcode == ZEND_FETCH_OBJ_FUNC_ARG);
ZEND_ASSERT(op->opcode == ZEND_FETCH_DIM_R || op->opcode == ZEND_FETCH_LIST_R || op->opcode == ZEND_FETCH_OBJ_R || op->opcode == ZEND_FETCH_OBJ_IS || op->opcode == ZEND_FETCH_DIM_FUNC_ARG || op->opcode == ZEND_FETCH_OBJ_FUNC_ARG);
repeat_last_opline = 1;
}
} else {
Expand Down
72 changes: 72 additions & 0 deletions ext/opcache/tests/jit/gh23628_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
--TEST--
GH-23628 001: Tracing JIT reads undefined property slots of a lazy proxy
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=tracing
opcache.jit_buffer_size=32M
opcache.jit_hot_loop=16
--EXTENSIONS--
opcache
--FILE--
<?php
final class Table {
protected array $map = ['start' => ['next' => 1]];
public int $count = 0;
public function parse(int $n): int {
$ok = 0;
for ($i = 0; $i < $n; $i++) {
if (isset($this->map['start']['next'])) {
$ok++;
} else {
throw new RuntimeException('isset false at ' . $i);
}
}
return $ok;
}
public function coalesce(int $n): int {
$sum = 0;
for ($i = 0; $i < $n; $i++) {
$sum += $this->map['start']['next'] ?? 100;
}
return $sum;
}
public function read(int $n): int {
$sum = 0;
for ($i = 0; $i < $n; $i++) {
$sum += $this->map['start']['next'];
}
return $sum;
}
public function write(int $n): int {
for ($i = 0; $i < $n; $i++) {
$this->map['start']['next'] = $i;
$this->count++;
}
return $this->map['start']['next'];
}
}

$reflector = new ReflectionClass(Table::class);

$proxy = $reflector->newLazyProxy(fn () => new Table());
var_dump($proxy->parse(100));
$proxy = $reflector->newLazyProxy(fn () => new Table());
var_dump($proxy->coalesce(100));
$proxy = $reflector->newLazyProxy(fn () => new Table());
var_dump($proxy->read(100));
$proxy = $reflector->newLazyProxy(fn () => new Table());
var_dump($proxy->write(100));
var_dump($proxy->count);

$ghost = $reflector->newLazyGhost(function (Table $table) {});
var_dump($ghost->parse(100));
?>
--EXPECT--
int(100)
int(100)
int(100)
int(99)
int(100)
int(100)
46 changes: 46 additions & 0 deletions ext/opcache/tests/jit/gh23628_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
GH-23628 002: Tracing JIT deoptimization on an undefined property slot of a lazy proxy
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=tracing
opcache.jit_buffer_size=32M
opcache.jit_hot_loop=16
--EXTENSIONS--
opcache
--FILE--
<?php
final class Table {
protected array $map = ['start' => ['next' => 1]];
public function parse(int $n): int {
$ok = 0;
for ($i = 0; $i < $n; $i++) {
if (isset($this->map['start']['next'])) {
$ok++;
} else {
throw new RuntimeException('isset false at ' . $i);
}
}
return $ok;
}
}

// The trace is recorded and compiled for a regular object, so that the
// property is read directly from the property slot...
var_dump((new Table())->parse(100));

// ... and later executed for a lazy proxy, whose property slot is undefined
// and has to be forwarded to the real instance during deoptimization.
$proxy = (new ReflectionClass(Table::class))->newLazyProxy(fn () => new Table());
var_dump($proxy->parse(100));

// ... and for an uninitialized lazy ghost, which is initialized on the first
// property access.
$ghost = (new ReflectionClass(Table::class))->newLazyGhost(function (Table $table) {});
var_dump($ghost->parse(100));
?>
--EXPECT--
int(100)
int(100)
int(100)
38 changes: 38 additions & 0 deletions ext/opcache/tests/jit/gh23628_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
GH-23628 003: Tracing JIT deoptimization on an unset() property served by __isset()/__get()
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=tracing
opcache.jit_buffer_size=32M
opcache.jit_hot_loop=16
--EXTENSIONS--
opcache
--FILE--
<?php
class A {
public $p = ['x' => 1];
public function __isset($n) { return true; }
public function __get($n) { return ['x' => 42]; }
function f($n) {
$s = 0;
for ($i = 0; $i < $n; $i++) {
$s += $this->p['x'] ?? 1000;
}
return $s;
}
}

// The trace is recorded and compiled while the property is initialized...
var_dump((new A)->f(100));

// ... and then executed after the property was unset(), so the fetch has to
// go through __isset()/__get() instead of yielding NULL.
$a = new A;
unset($a->p);
var_dump($a->f(100));
?>
--EXPECT--
int(100)
int(4200)
7 changes: 6 additions & 1 deletion ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -3280,8 +3280,13 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H
length = num_in - offset;
}

/* Number of entries in the output hash: the input entries that are kept
* plus the replacement entries. After clamping, a non-positive length
* removes nothing, so all input entries are kept. */
uint32_t num_out = num_in - MAX(length, 0) + (replace ? zend_hash_num_elements(replace) : 0);

/* Create and initialize output hash */
zend_hash_init(&out_hash, (length > 0 ? num_in - length : 0) + (replace ? zend_hash_num_elements(replace) : 0), NULL, ZVAL_PTR_DTOR, 0);
zend_hash_init(&out_hash, num_out, NULL, ZVAL_PTR_DTOR, 0);

if (HT_IS_PACKED(in_hash)) {
/* Start at the beginning of the input hash and copy entries to output hash until offset is reached */
Expand Down
16 changes: 5 additions & 11 deletions ext/standard/formatted_print.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

#include <locale.h>
#ifdef ZTS
#include "ext/standard/php_string.h" /* for localeconv_r() */
#define LCONV_DECIMAL_POINT (*lconv.decimal_point)
#include "ext/standard/php_string.h" /* for localeconv_decimal_point() */
#define LCONV_DECIMAL_POINT localeconv_decimal_point()
#else
#define LCONV_DECIMAL_POINT (*lconv->decimal_point)
#endif
Expand Down Expand Up @@ -221,9 +221,7 @@ php_sprintf_appenddouble(zend_string **buffer, size_t *pos,
char *s = NULL;
size_t s_len = 0;
bool is_negative = false;
#ifdef ZTS
struct lconv lconv;
#else
#ifndef ZTS
struct lconv *lconv;
#endif

Expand Down Expand Up @@ -256,9 +254,7 @@ php_sprintf_appenddouble(zend_string **buffer, size_t *pos,
case 'E':
case 'f':
case 'F':
#ifdef ZTS
localeconv_r(&lconv);
#else
#ifndef ZTS
lconv = localeconv();
#endif
s = php_conv_fp((fmt == 'f')?'F':fmt, number, 0, precision,
Expand All @@ -285,9 +281,7 @@ php_sprintf_appenddouble(zend_string **buffer, size_t *pos,

char decimal_point = '.';
if (fmt == 'g' || fmt == 'G') {
#ifdef ZTS
localeconv_r(&lconv);
#else
#ifndef ZTS
lconv = localeconv();
#endif
decimal_point = LCONV_DECIMAL_POINT;
Expand Down
Loading