Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f4ffaeb
[RFC] ext-user_cache: initial implementation
zeriyoshi Jul 13, 2026
78281a5
fix: include pthread.h
zeriyoshi Jul 14, 2026
8dc6dcf
fix: void ptr cast
zeriyoshi Jul 14, 2026
0e46eb6
fix: skip 32-bit over shm size test
zeriyoshi Jul 14, 2026
551983c
fix: repeat test failure
zeriyoshi Jul 14, 2026
6d062d0
fix: add CONFLICTS for Windows
zeriyoshi Jul 14, 2026
6bbb014
chore: coding style
zeriyoshi Jul 15, 2026
fa37321
chore: coding style 2
zeriyoshi Jul 15, 2026
9e03a65
test: add CONFLICTS all into user_cache fpm tests
zeriyoshi Jul 15, 2026
e176427
fix: minimize upstream changes and add support DatePeriod and DateInt…
zeriyoshi Jul 15, 2026
03fe015
fix: UAF on DatePeriod safe-direct path
zeriyoshi Jul 15, 2026
66589a9
fix: cant drop entry if __unserialize throwed Exception
zeriyoshi Jul 15, 2026
627bbff
refactor: comment and remove dropped internal functions
zeriyoshi Jul 18, 2026
4f77a21
refactor: api and more secure memory management
zeriyoshi Jul 18, 2026
8fb8fdb
refactor: API and structure changes
zeriyoshi Jul 19, 2026
73f916e
fix: Windows build
zeriyoshi Jul 19, 2026
29a5f2f
chore: fix author section
zeriyoshi Jul 19, 2026
26a16ca
fix: OPcache User Cache -> UserCache
zeriyoshi Jul 19, 2026
edeaa07
fix: fix repeat tests
zeriyoshi Jul 19, 2026
e15694a
fix: strict memory checking
zeriyoshi Jul 22, 2026
2581ae6
feat: add support DatePeriod and DateInterval
zeriyoshi Jul 23, 2026
6fdf14b
fix: index memory usage and LRU based memory eviction
zeriyoshi Jul 26, 2026
6695185
fix: memory management and memory structures
zeriyoshi Jul 26, 2026
76411b8
fix: fix LRU impl and more
zeriyoshi Jul 27, 2026
84194b1
fix: fix SEGV on many patterns
zeriyoshi Jul 31, 2026
ed5cf5a
fix: cache boundary
zeriyoshi Aug 12, 2026
8aa1f7b
fix: dtest: deprecated spl_object_hash
zeriyoshi Aug 12, 2026
ba57146
fix: test: repeat test needs reset
zeriyoshi Aug 12, 2026
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
740 changes: 740 additions & 0 deletions ext/date/php_date.c

Large diffs are not rendered by default.

227 changes: 188 additions & 39 deletions ext/spl/spl_array.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "php.h"
#include "ext/standard/php_var.h"
#include "zend_attributes.h"
#include "ext/user_cache/php_user_cache.h" /* For user_cache safe direct path */
#include "zend_smart_str.h"
#include "zend_interfaces.h"
#include "zend_exceptions.h"
Expand Down Expand Up @@ -1400,14 +1401,13 @@ PHP_METHOD(ArrayObject, unserialize)

} /* }}} */

/* {{{ */
PHP_METHOD(ArrayObject, __serialize)
/* Builds the state array shared by __serialize() and the user-cache safe-direct
* path. The members slot only exists in the __serialize() format. */
static void spl_array_object_serialize_state(zval *object, zval *return_value, bool with_members)
{
spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
spl_array_object *intern = Z_SPLARRAY_P(object);
zval tmp;

ZEND_PARSE_PARAMETERS_NONE();

array_init(return_value);

/* flags */
Expand All @@ -1423,45 +1423,51 @@ PHP_METHOD(ArrayObject, __serialize)
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);

/* members */
ZVAL_ARR(&tmp, zend_proptable_to_symtable(
zend_std_get_properties(&intern->std), /* always_duplicate */ 1));
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
if (with_members) {
ZVAL_ARR(
&tmp,
zend_proptable_to_symtable(
zend_std_get_properties(&intern->std),
/* always_duplicate */ 1
)
);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
}

/* iterator class */
if (intern->ce_get_iterator == spl_ce_ArrayIterator) {
ZVAL_NULL(&tmp);
} else {
ZVAL_STR_COPY(&tmp, intern->ce_get_iterator->name);
}

zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
}
/* }}} */


/* {{{ */
PHP_METHOD(ArrayObject, __unserialize)
/* Restores the state array built above. Throws and returns false on malformed
* data; the caller decides how to propagate the failure. */
static PHP_USER_CACHE_HOT bool spl_array_object_unserialize_state(zval *object, HashTable *data, bool with_members)
{
spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
HashTable *data;
zval *flags_zv, *storage_zv, *members_zv, *iterator_class_zv;
spl_array_object *intern = Z_SPLARRAY_P(object);
zend_long flags;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
RETURN_THROWS();
}
zend_class_entry *ce;
zval *flags_zv, *storage_zv, *members_zv, *iterator_class_zv;

flags_zv = zend_hash_index_find(data, 0);
storage_zv = zend_hash_index_find(data, 1);
members_zv = zend_hash_index_find(data, 2);
iterator_class_zv = zend_hash_index_find(data, 3);

if (!flags_zv || !storage_zv || !members_zv ||
Z_TYPE_P(flags_zv) != IS_LONG || Z_TYPE_P(members_zv) != IS_ARRAY ||
(iterator_class_zv && (Z_TYPE_P(iterator_class_zv) != IS_NULL &&
Z_TYPE_P(iterator_class_zv) != IS_STRING))) {
members_zv = with_members ? zend_hash_index_find(data, 2) : NULL;
iterator_class_zv = zend_hash_index_find(data, with_members ? 3 : 2);

if (!flags_zv || !storage_zv || (with_members && !members_zv) ||
Z_TYPE_P(flags_zv) != IS_LONG ||
(with_members && Z_TYPE_P(members_zv) != IS_ARRAY) ||
(iterator_class_zv && (Z_TYPE_P(iterator_class_zv) != IS_NULL && Z_TYPE_P(iterator_class_zv) != IS_STRING))
) {
zend_throw_exception(spl_ce_UnexpectedValueException,
"Incomplete or ill-typed serialization data", 0);
RETURN_THROWS();
"Incomplete or ill-typed serialization data", 0
);

return false;
}

flags = Z_LVAL_P(flags_zv);
Expand All @@ -1470,43 +1476,183 @@ PHP_METHOD(ArrayObject, __unserialize)

if (flags & SPL_ARRAY_IS_SELF) {
zval_ptr_dtor(&intern->array);

ZVAL_UNDEF(&intern->array);
} else {
if (Z_TYPE_P(storage_zv) != IS_OBJECT && Z_TYPE_P(storage_zv) != IS_ARRAY) {
/* TODO Use UnexpectedValueException instead? And better error message? */
zend_throw_exception(spl_ce_InvalidArgumentException, "Passed variable is not an array or object", 0);
RETURN_THROWS();

return false;
}
spl_array_set_array(ZEND_THIS, intern, storage_zv, 0L, true);

spl_array_set_array(object, intern, storage_zv, 0L, true);
}

object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
if (EG(exception)) {
RETURN_THROWS();
if (with_members) {
object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
if (EG(exception)) {
return false;
}
}

if (iterator_class_zv && Z_TYPE_P(iterator_class_zv) == IS_STRING) {
zend_class_entry *ce = zend_lookup_class(Z_STR_P(iterator_class_zv));
ce = zend_lookup_class(Z_STR_P(iterator_class_zv));

if (!ce) {
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0,
"Cannot deserialize ArrayObject with iterator class '%s'; no such class exists",
ZSTR_VAL(Z_STR_P(iterator_class_zv)));
RETURN_THROWS();
ZSTR_VAL(Z_STR_P(iterator_class_zv))
);

return false;
}

if (!instanceof_function(ce, spl_ce_ArrayIterator)) {
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0,
"Cannot deserialize ArrayObject with iterator class '%s'; this class is not derived from ArrayIterator",
ZSTR_VAL(Z_STR_P(iterator_class_zv)));
RETURN_THROWS();
ZSTR_VAL(Z_STR_P(iterator_class_zv))
);

return false;
}

intern->ce_get_iterator = ce;
}

return true;
}

/* {{{ */
PHP_METHOD(ArrayObject, __serialize)
{
ZEND_PARSE_PARAMETERS_NONE();

spl_array_object_serialize_state(ZEND_THIS, return_value, /* with_members */ true);
}
/* }}} */

/* {{{ */
PHP_METHOD(ArrayObject, __unserialize)
{
HashTable *data;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
RETURN_THROWS();
}

if (!spl_array_object_unserialize_state(ZEND_THIS, data, /* with_members */ true)) {
RETURN_THROWS();
}
}
/* }}} */

static bool spl_array_object_copy_user_cache_state(
void *ctx,
zend_object *new_obj,
zend_object *old_obj,
php_user_cache_safe_direct_clone_value_func_t clone_value)
{
spl_array_object *old_intern, *new_intern;
zval new_zv, cloned_storage_zv;
bool result;

if (clone_value == NULL) {
return false;
}

result = false;
old_intern = spl_array_from_obj(old_obj);
new_intern = spl_array_from_obj(new_obj);

ZVAL_OBJ(&new_zv, new_obj);
ZVAL_UNDEF(&cloned_storage_zv);

new_intern->ar_flags &= ~SPL_ARRAY_CLONE_MASK;
new_intern->ar_flags |= old_intern->ar_flags & SPL_ARRAY_CLONE_MASK;
new_intern->ce_get_iterator = old_intern->ce_get_iterator;

if (old_intern->ar_flags & SPL_ARRAY_IS_SELF) {
zval_ptr_dtor(&new_intern->array);

ZVAL_UNDEF(&new_intern->array);

result = true;

goto cleanup;
}

if (!clone_value(ctx, &cloned_storage_zv, &old_intern->array) ||
(Z_TYPE(cloned_storage_zv) != IS_OBJECT && Z_TYPE(cloned_storage_zv) != IS_ARRAY)
) {
goto cleanup;
}

spl_array_set_array(&new_zv, new_intern, &cloned_storage_zv, old_intern->ar_flags & SPL_ARRAY_CLONE_MASK, true);
result = !EG(exception);

cleanup:
if (Z_TYPE(cloned_storage_zv) != IS_UNDEF) {
zval_ptr_dtor(&cloned_storage_zv);
}

return result;
}

static bool spl_array_object_user_cache_state_has_unstorable(
void *ctx,
const zval *object,
php_user_cache_safe_direct_value_has_unstorable_func_t value_has_unstorable)
{
spl_array_object *intern;

if (value_has_unstorable == NULL) {
return false;
}

intern = Z_SPLARRAY_P(object);
if (intern->ar_flags & SPL_ARRAY_IS_SELF) {
return false;
}

return value_has_unstorable(ctx, &intern->array);
}

static bool spl_array_object_serialize_user_cache_state(zval *state, const zval *object)
{
ZVAL_UNDEF(state);

spl_array_object_serialize_state((zval *) object, state, /* with_members */ false);

if (EG(exception) || Z_TYPE_P(state) != IS_ARRAY) {
if (Z_TYPE_P(state) != IS_UNDEF) {
zval_ptr_dtor(state);
}

ZVAL_UNDEF(state);

return false;
}

return true;
}

static PHP_USER_CACHE_HOT bool spl_array_object_unserialize_user_cache_state(zval *object, zval *state)
{
if (Z_TYPE_P(state) != IS_ARRAY) {
return false;
}

return spl_array_object_unserialize_state(object, Z_ARRVAL_P(state), /* with_members */ false)
&& !EG(exception);
}

static const php_user_cache_safe_direct_handlers spl_array_user_cache_handlers = {
.copy = spl_array_object_copy_user_cache_state,
.state_has_unstorable = spl_array_object_user_cache_state_has_unstorable,
.state_serialize = spl_array_object_serialize_user_cache_state,
.state_unserialize = spl_array_object_unserialize_user_cache_state,
};

/* {{{ */
PHP_METHOD(ArrayObject, __debugInfo)
{
Expand Down Expand Up @@ -1884,6 +2030,9 @@ PHP_MINIT_FUNCTION(spl_array)
spl_ce_RecursiveArrayIterator->create_object = spl_array_object_new;
spl_ce_RecursiveArrayIterator->get_iterator = spl_array_get_iterator;

php_user_cache_safe_direct_register_class(spl_ce_ArrayObject, &spl_array_user_cache_handlers);
php_user_cache_safe_direct_register_class(spl_ce_ArrayIterator, &spl_array_user_cache_handlers);

return SUCCESS;
}
/* }}} */
Loading
Loading