Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0f1399b
[RFC] ext-user_cache: initial implementation
zeriyoshi Jul 13, 2026
e98aeaf
fix: include pthread.h
zeriyoshi Jul 14, 2026
f0247ea
fix: void ptr cast
zeriyoshi Jul 14, 2026
e76e44d
fix: skip 32-bit over shm size test
zeriyoshi Jul 14, 2026
88f65b8
fix: repeat test failure
zeriyoshi Jul 14, 2026
4e3be3b
fix: add CONFLICTS for Windows
zeriyoshi Jul 14, 2026
b51b1db
chore: coding style
zeriyoshi Jul 15, 2026
6909a95
chore: coding style 2
zeriyoshi Jul 15, 2026
3772ba2
test: add CONFLICTS all into user_cache fpm tests
zeriyoshi Jul 15, 2026
996554f
fix: minimize upstream changes and add support DatePeriod and DateInt…
zeriyoshi Jul 15, 2026
38e5236
fix: UAF on DatePeriod safe-direct path
zeriyoshi Jul 15, 2026
1040112
fix: cant drop entry if __unserialize throwed Exception
zeriyoshi Jul 15, 2026
3e7cbdc
refactor: comment and remove dropped internal functions
zeriyoshi Jul 18, 2026
676f05d
refactor: api and more secure memory management
zeriyoshi Jul 18, 2026
7803325
refactor: API and structure changes
zeriyoshi Jul 19, 2026
de00812
fix: Windows build
zeriyoshi Jul 19, 2026
a1e10e6
chore: fix author section
zeriyoshi Jul 19, 2026
69f7b3d
fix: OPcache User Cache -> UserCache
zeriyoshi Jul 19, 2026
7aafc3b
fix: fix repeat tests
zeriyoshi Jul 19, 2026
2be943a
fix: strict memory checking
zeriyoshi Jul 22, 2026
820c5c1
feat: add support DatePeriod and DateInterval
zeriyoshi Jul 23, 2026
6a9ee92
fix: index memory usage and LRU based memory eviction
zeriyoshi Jul 26, 2026
556ab89
fix: memory management and memory structures
zeriyoshi Jul 26, 2026
d84c752
fix: fix LRU impl and more
zeriyoshi Jul 27, 2026
f3cc192
fix: fix SEGV on many patterns
zeriyoshi Jul 31, 2026
1d3a212
fix: cache boundary
zeriyoshi Aug 12, 2026
6eba7e6
fix: dtest: deprecated spl_object_hash
zeriyoshi Aug 12, 2026
6a0525a
fix: test: repeat test needs reset
zeriyoshi Aug 12, 2026
a1a6f9a
fix: adopt upstream changes
zeriyoshi Aug 17, 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