Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6205ca8
[RFC] ext-user_cache: initial implementation
zeriyoshi Jul 13, 2026
78e5fa1
fix: include pthread.h
zeriyoshi Jul 14, 2026
3843c6f
fix: void ptr cast
zeriyoshi Jul 14, 2026
993eef2
fix: skip 32-bit over shm size test
zeriyoshi Jul 14, 2026
b00507a
fix: repeat test failure
zeriyoshi Jul 14, 2026
00dfed4
fix: add CONFLICTS for Windows
zeriyoshi Jul 14, 2026
ffe5910
chore: coding style
zeriyoshi Jul 15, 2026
7686959
chore: coding style 2
zeriyoshi Jul 15, 2026
68164ce
test: add CONFLICTS all into user_cache fpm tests
zeriyoshi Jul 15, 2026
5b1ee08
fix: minimize upstream changes and add support DatePeriod and DateInt…
zeriyoshi Jul 15, 2026
18f794f
fix: UAF on DatePeriod safe-direct path
zeriyoshi Jul 15, 2026
cdedf32
fix: cant drop entry if __unserialize throwed Exception
zeriyoshi Jul 15, 2026
b075a5d
refactor: comment and remove dropped internal functions
zeriyoshi Jul 18, 2026
b23ce98
refactor: api and more secure memory management
zeriyoshi Jul 18, 2026
837ce0d
refactor: API and structure changes
zeriyoshi Jul 19, 2026
28746f0
fix: Windows build
zeriyoshi Jul 19, 2026
4036cc9
chore: fix author section
zeriyoshi Jul 19, 2026
abc93bf
fix: OPcache User Cache -> UserCache
zeriyoshi Jul 19, 2026
5e38dcd
fix: fix repeat tests
zeriyoshi Jul 19, 2026
4fe058c
fix: strict memory checking
zeriyoshi Jul 22, 2026
d0c25e8
feat: add support DatePeriod and DateInterval
zeriyoshi Jul 23, 2026
9a32aa7
fix: index memory usage and LRU based memory eviction
zeriyoshi Jul 26, 2026
6c06861
fix: memory management and memory structures
zeriyoshi Jul 26, 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 @@ -1888,6 +2034,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