Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5f5c04c
[RFC] ext-user_cache: initial implementation
zeriyoshi Jul 13, 2026
c44b47b
fix: include pthread.h
zeriyoshi Jul 14, 2026
d58dbc1
fix: void ptr cast
zeriyoshi Jul 14, 2026
3c70a49
fix: skip 32-bit over shm size test
zeriyoshi Jul 14, 2026
827908c
fix: repeat test failure
zeriyoshi Jul 14, 2026
141e001
fix: add CONFLICTS for Windows
zeriyoshi Jul 14, 2026
f20b749
chore: coding style
zeriyoshi Jul 15, 2026
eee210e
chore: coding style 2
zeriyoshi Jul 15, 2026
39f0946
test: add CONFLICTS all into user_cache fpm tests
zeriyoshi Jul 15, 2026
189bc20
fix: minimize upstream changes and add support DatePeriod and DateInt…
zeriyoshi Jul 15, 2026
2918179
fix: UAF on DatePeriod safe-direct path
zeriyoshi Jul 15, 2026
245b074
fix: cant drop entry if __unserialize throwed Exception
zeriyoshi Jul 15, 2026
82bad1a
refactor: comment and remove dropped internal functions
zeriyoshi Jul 18, 2026
7290c4b
refactor: api and more secure memory management
zeriyoshi Jul 18, 2026
af743e8
refactor: API and structure changes
zeriyoshi Jul 19, 2026
e251548
fix: Windows build
zeriyoshi Jul 19, 2026
5b94def
chore: fix author section
zeriyoshi Jul 19, 2026
da4e9c9
fix: OPcache User Cache -> UserCache
zeriyoshi Jul 19, 2026
841be6f
fix: fix repeat tests
zeriyoshi Jul 19, 2026
0fa7430
fix: strict memory checking
zeriyoshi Jul 22, 2026
ef09702
feat: add support DatePeriod and DateInterval
zeriyoshi Jul 23, 2026
967d1d3
fix: index memory usage and LRU based memory eviction
zeriyoshi Jul 26, 2026
e1f1804
fix: memory management and memory structures
zeriyoshi Jul 26, 2026
5c3a415
fix: fix LRU impl and more
zeriyoshi Jul 27, 2026
9c3ab68
fix: fix SEGV on many patterns
zeriyoshi Jul 31, 2026
cc1b55b
fix: cache boundary
zeriyoshi Aug 12, 2026
e630261
fix: dtest: deprecated spl_object_hash
zeriyoshi Aug 12, 2026
ded7702
fix: test: repeat test needs reset
zeriyoshi Aug 12, 2026
79133bd
fix: adopt upstream changes
zeriyoshi Aug 17, 2026
d311c30
feat: more strict shm handling
zeriyoshi Aug 28, 2026
6b2e3a6
refactor: unify fetchMultiple teardown across success and failure paths
zeriyoshi Sep 11, 2026
791da0a
refactor and more consistency
zeriyoshi Sep 13, 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 @@ -18,6 +18,7 @@

#include "php.h"
#include "ext/standard/php_var.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 @@ -1399,14 +1400,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 @@ -1422,45 +1422,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 @@ -1469,43 +1475,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 @@ -1883,6 +2029,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