diff --git a/Zend/zend_string.c b/Zend/zend_string.c index c24d5dfcdb478..1b5334f3fe77a 100644 --- a/Zend/zend_string.c +++ b/Zend/zend_string.c @@ -148,22 +148,7 @@ static zend_always_inline zend_string *zend_interned_string_ht_lookup_ex(zend_ul static zend_always_inline zend_string *zend_interned_string_ht_lookup(zend_string *str, HashTable *interned_strings) { - zend_ulong h = ZSTR_H(str); - uint32_t nIndex; - uint32_t idx; - Bucket *p; - - nIndex = h | interned_strings->nTableMask; - idx = HT_HASH(interned_strings, nIndex); - while (idx != HT_INVALID_IDX) { - p = HT_HASH_TO_BUCKET(interned_strings, idx); - if ((p->h == h) && zend_string_equal_content(p->key, str)) { - return p->key; - } - idx = Z_NEXT(p->val); - } - - return NULL; + return zend_interned_string_ht_lookup_ex(ZSTR_H(str), ZSTR_VAL(str), ZSTR_LEN(str), interned_strings); } /* This function might be not thread safe at least because it would update the diff --git a/ext/spl/config.m4 b/ext/spl/config.m4 index 589e8681d70ec..5ca3afdce7941 100644 --- a/ext/spl/config.m4 +++ b/ext/spl/config.m4 @@ -1,5 +1,5 @@ -PHP_NEW_EXTENSION(spl, php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c, no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) -PHP_INSTALL_HEADERS([ext/spl], [php_spl.h spl_array.h spl_directory.h spl_engine.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h]) +PHP_NEW_EXTENSION(spl, php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c spl_vector.c, no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) +PHP_INSTALL_HEADERS([ext/spl], [php_spl.h spl_array.h spl_directory.h spl_engine.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h spl_vector.h]) PHP_ADD_EXTENSION_DEP(spl, pcre, true) PHP_ADD_EXTENSION_DEP(spl, standard, true) PHP_ADD_EXTENSION_DEP(spl, json) diff --git a/ext/spl/config.w32 b/ext/spl/config.w32 index 92659aa86d2ab..a6af323c6c4f4 100644 --- a/ext/spl/config.w32 +++ b/ext/spl/config.w32 @@ -1,5 +1,5 @@ // vim:ft=javascript -EXTENSION("spl", "php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c", false /*never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); +EXTENSION("spl", "php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c spl_vector.c", false /*never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); PHP_SPL="yes"; -PHP_INSTALL_HEADERS("ext/spl", "php_spl.h spl_array.h spl_directory.h spl_engine.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h"); +PHP_INSTALL_HEADERS("ext/spl", "php_spl.h spl_array.h spl_directory.h spl_engine.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h spl_vector.h"); diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index c6b7e02a00ce7..d233552f8e3d9 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -33,6 +33,7 @@ #include "spl_observer.h" #include "spl_dllist.h" #include "spl_fixedarray.h" +#include "spl_vector.h" #include "spl_heap.h" #include "zend_exceptions.h" #include "zend_interfaces.h" @@ -708,6 +709,7 @@ PHP_MINIT_FUNCTION(spl) PHP_MINIT(spl_dllist)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_heap)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_fixedarray)(INIT_FUNC_ARGS_PASSTHRU); + PHP_MINIT(spl_vector)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_observer)(INIT_FUNC_ARGS_PASSTHRU); return SUCCESS; diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index 0fd5717224bec..3044b37de559f 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -31,6 +31,7 @@ #include "spl_fixedarray.h" #include "spl_exceptions.h" #include "spl_iterators.h" +#include "spl_util.h" #include "ext/json/php_json.h" zend_object_handlers spl_handler_SplFixedArray; @@ -310,37 +311,6 @@ static zend_object *spl_fixedarray_object_clone(zend_object *old_object) return new_object; } -static zend_long spl_offset_convert_to_long(zval *offset) /* {{{ */ -{ - try_again: - switch (Z_TYPE_P(offset)) { - case IS_STRING: { - zend_ulong index; - if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), index)) { - return (zend_long) index; - } - break; - } - case IS_DOUBLE: - return zend_dval_to_lval_safe(Z_DVAL_P(offset)); - case IS_LONG: - return Z_LVAL_P(offset); - case IS_FALSE: - return 0; - case IS_TRUE: - return 1; - case IS_REFERENCE: - offset = Z_REFVAL_P(offset); - goto try_again; - case IS_RESOURCE: - zend_use_resource_as_offset(offset); - return Z_RES_HANDLE_P(offset); - } - - zend_type_error("Illegal offset type"); - return 0; -} - static zval *spl_fixedarray_object_read_dimension_helper(spl_fixedarray_object *intern, zval *offset) { zend_long index; diff --git a/ext/spl/spl_util.h b/ext/spl/spl_util.h new file mode 100644 index 0000000000000..21727a8970b68 --- /dev/null +++ b/ext/spl/spl_util.h @@ -0,0 +1,37 @@ +#ifndef SPL_UTIL +#define SPL_UTIL + +#include "zend_types.h" + +static zend_long spl_offset_convert_to_long(zval *offset) /* {{{ */ +{ + try_again: + switch (Z_TYPE_P(offset)) { + case IS_STRING: { + zend_ulong index; + if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), index)) { + return (zend_long) index; + } + break; + } + case IS_DOUBLE: + return zend_dval_to_lval_safe(Z_DVAL_P(offset)); + case IS_LONG: + return Z_LVAL_P(offset); + case IS_FALSE: + return 0; + case IS_TRUE: + return 1; + case IS_REFERENCE: + offset = Z_REFVAL_P(offset); + goto try_again; + case IS_RESOURCE: + zend_use_resource_as_offset(offset); + return Z_RES_HANDLE_P(offset); + } + + zend_type_error("Illegal offset type"); + return 0; +} + +#endif diff --git a/ext/spl/spl_vector.c b/ext/spl/spl_vector.c new file mode 100644 index 0000000000000..f349a6fea85b8 --- /dev/null +++ b/ext/spl/spl_vector.c @@ -0,0 +1,1351 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Tyson Andre | + +----------------------------------------------------------------------+ +*/ + +/* This is based on spl_fixedarray.c but has lower overhead (when size is known) and is more efficient to push and remove elements from the end of the list */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" + +#include "php_spl.h" +#include "spl_vector_arginfo.h" +#include "spl_vector.h" +#include "spl_exceptions.h" +#include "spl_iterators.h" +#include "spl_util.h" +#include "ext/json/php_json.h" + +#include + +/* Though rare, it is possible to have 64-bit zend_longs and a 32-bit size_t. */ +#define MAX_ZVAL_COUNT ((SIZE_MAX / sizeof(zval)) - 1) +#define MAX_VALID_OFFSET ((size_t)(MAX_ZVAL_COUNT > ZEND_LONG_MAX ? ZEND_LONG_MAX : MAX_ZVAL_COUNT)) + +/* Miscellaneous utility functions */ +static inline zval *spl_zval_copy_range(const zval *original, size_t n) { + const size_t bytes = n * sizeof(zval); + zval *copy = emalloc(bytes); + memcpy(copy, original, bytes); + return copy; +} + +static inline void spl_zval_dtor_range(zval *it, size_t n) { + for (zval *end = it + n; it < end; it++) { + zval_ptr_dtor(it); + } +} + +#define CONVERT_OFFSET_TO_LONG_OR_THROW(index, zv) do { \ + if (Z_TYPE_P(offset_zv) != IS_LONG) { \ + index = spl_offset_convert_to_long(offset_zv); \ + if (UNEXPECTED(EG(exception))) { \ + return; \ + } \ + } else { \ + index = Z_LVAL_P(offset_zv); \ + } \ +} while(0) + +#define CONVERT_OFFSET_TO_LONG_OR_THROW_RETURN_NULLPTR(index, zv) do { \ + if (Z_TYPE_P(offset_zv) != IS_LONG) { \ + index = spl_offset_convert_to_long(offset_zv); \ + if (UNEXPECTED(EG(exception))) { \ + return NULL; \ + } \ + } else { \ + index = Z_LVAL_P(offset_zv); \ + } \ +} while(0) + +static zend_always_inline void spl_convert_zval_list_to_php_array_list(zval *return_value, zval *entries, size_t len) +{ + if (!len) { + RETURN_EMPTY_ARRAY(); + } + + zend_array *values = zend_new_array(len); + /* Initialize return array */ + zend_hash_real_init_packed(values); + + /* Go through values and add values to the return array */ + ZEND_HASH_FILL_PACKED(values) { + for (size_t i = 0; i < len; i++) { + Z_TRY_ADDREF_P(&entries[i]); + ZEND_HASH_FILL_ADD(&entries[i]); + } + } ZEND_HASH_FILL_END(); + + RETURN_ARR(values); +} + +/* Vector implementation details */ + +zend_object_handlers spl_handler_Vector; +zend_class_entry *spl_ce_Vector; + +/* This is a placeholder value to distinguish between empty and uninitialized Vector instances. + * Compilers require at least one element. Make this constant - reads/writes should be impossible. */ +static const zval empty_entry_list[1]; + +typedef struct _spl_vector_entries { + size_t size; + size_t capacity; + zval *entries; +} spl_vector_entries; + +typedef struct _spl_vector { + spl_vector_entries array; + zend_object std; +} spl_vector; + +/* Used by InternalIterator returned by Vector->getIterator() */ +typedef struct _spl_vector_it { + zend_object_iterator intern; + zend_long current; +} spl_vector_it; + +static void spl_vector_raise_capacity(spl_vector *intern, const size_t new_capacity); + +/* + * If a size this large is encountered, assume the allocation will likely fail or + * future changes to the capacity will overflow. + */ +static ZEND_COLD void spl_error_noreturn_max_vector_capacity() +{ + zend_error_noreturn(E_ERROR, "exceeded max valid Vector capacity"); +} + +static spl_vector *spl_vector_from_object(zend_object *obj) +{ + return (spl_vector*)((char*)(obj) - XtOffsetOf(spl_vector, std)); +} + +#define Z_VECTOR_P(zv) spl_vector_from_object(Z_OBJ_P((zv))) + +/* Helps enforce the invariants in debug mode: + * - if size == 0, then entries == NULL + * - if size > 0, then entries != NULL + * - size is not less than 0 + */ +static bool spl_vector_entries_empty_size(const spl_vector_entries *array) +{ + if (array->size > 0) { + ZEND_ASSERT(array->entries != empty_entry_list); + ZEND_ASSERT(array->capacity >= array->size); + return false; + } + // This vector may have reserved capacity. + return true; +} + +static bool spl_vector_entries_empty_capacity(const spl_vector_entries *array) +{ + if (array->capacity > 0) { + ZEND_ASSERT(array->entries != empty_entry_list); + return false; + } + // This vector may have reserved capacity. + return true; +} + +static bool spl_vector_entries_uninitialized(const spl_vector_entries *array) +{ + if (array->entries == NULL) { + ZEND_ASSERT(array->size == 0); + ZEND_ASSERT(array->capacity == 0); + return true; + } + ZEND_ASSERT((array->entries == empty_entry_list && array->capacity == 0) || array->capacity > 0); + return false; +} + +static void spl_vector_raise_capacity(spl_vector *intern, const size_t new_capacity) { + ZEND_ASSERT(new_capacity > intern->array.capacity); + if (intern->array.capacity == 0) { + intern->array.entries = safe_emalloc(new_capacity, sizeof(zval), 0); + } else { + intern->array.entries = safe_erealloc(intern->array.entries, new_capacity, sizeof(zval), 0); + } + intern->array.capacity = new_capacity; + ZEND_ASSERT(intern->array.entries != NULL); +} + +static inline void spl_vector_shrink_capacity(spl_vector_entries *array, size_t size, size_t capacity, zval *old_entries) { + ZEND_ASSERT(size <= capacity); + ZEND_ASSERT(size == array->size); + ZEND_ASSERT(capacity > 0); + ZEND_ASSERT(capacity < array->capacity); + ZEND_ASSERT(old_entries == array->entries); + zval *new_entries = safe_emalloc(capacity, sizeof(zval), 0); + ZEND_ASSERT(new_entries != NULL); + memcpy(new_entries, old_entries, size * sizeof(zval)); + + array->entries = new_entries; + array->capacity = capacity; + efree(old_entries); +} + +/* Initializes the range [from, to) to null. Does not dtor existing entries. */ +/* TODO: Delete if this isn't used in the final version +static void spl_vector_entries_init_elems(spl_vector_entries *array, zend_long from, zend_long to) +{ + ZEND_ASSERT(from <= to); + zval *begin = &array->entries[from]; + zval *end = &array->entries[to]; + + while (begin != end) { + ZVAL_NULL(begin++); + } +} +*/ + +static zend_always_inline void spl_vector_entries_set_empty_list(spl_vector_entries *array) { + array->size = 0; + array->capacity = 0; + array->entries = (zval *)empty_entry_list; +} + +static void spl_vector_entries_init_from_traversable(spl_vector_entries *array, zend_object *obj) +{ + zend_class_entry *ce = obj->ce; + zend_object_iterator *iter; + size_t size = 0, capacity = 0; + array->size = 0; + array->entries = NULL; + zval *entries = NULL; + zval tmp_obj; + ZVAL_OBJ(&tmp_obj, obj); + iter = ce->get_iterator(ce, &tmp_obj, 0); + + if (UNEXPECTED(EG(exception))) { + return; + } + + const zend_object_iterator_funcs *funcs = iter->funcs; + + if (funcs->rewind) { + funcs->rewind(iter); + if (UNEXPECTED(EG(exception))) { + return; + } + } + + /* Reindex keys from 0. */ + while (funcs->valid(iter) == SUCCESS) { + if (EG(exception)) { + break; + } + zval *value = funcs->get_current_data(iter); + if (UNEXPECTED(EG(exception))) { + break; + } + if (UNEXPECTED(EG(exception))) { + break; + } + + if (size >= capacity) { + /* Not using Countable::count(), that would potentially have side effects or throw UnsupportedOperationException or be slow to compute */ + if (entries) { + /* The safe_erealloc macro emits its own fatal error on integer overflow */ + capacity *= 2; + entries = safe_erealloc(entries, capacity, sizeof(zval), 0); + } else { + capacity = 4; + entries = safe_emalloc(capacity, sizeof(zval), 0); + } + } + ZVAL_COPY_DEREF(&entries[size], value); + size++; + + iter->index++; + funcs->move_forward(iter); + if (UNEXPECTED(EG(exception))) { + break; + } + } + if (capacity > size) { + /* Shrink allocated value to actual required size */ + entries = erealloc(entries, size * sizeof(zval)); + } + + array->size = size; + array->capacity = size; + array->entries = entries; + if (iter) { + zend_iterator_dtor(iter); + } +} + +/* Copies the range [begin, end) into the vector, beginning at `offset`. + * Does not dtor the existing elements. + */ +static void spl_vector_copy_range(spl_vector_entries *array, size_t offset, zval *begin, zval *end) +{ + ZEND_ASSERT(offset <= array->size); + ZEND_ASSERT(begin <= end); + ZEND_ASSERT(array->size - offset >= (size_t)(end - begin)); + + zval *to = &array->entries[offset]; + while (begin != end) { + ZVAL_COPY(to, begin); + begin++; + to++; + } +} + +static void spl_vector_entries_copy_ctor(spl_vector_entries *to, const spl_vector_entries *from) +{ + zend_long size = from->size; + if (!size) { + spl_vector_entries_set_empty_list(to); + return; + } + + to->size = 0; /* reset size in case emalloc() fails */ + to->capacity = 0; + to->entries = safe_emalloc(size, sizeof(zval), 0); + to->size = size; + to->capacity = size; + + zval *begin = from->entries, *end = from->entries + size; + spl_vector_copy_range(to, 0, begin, end); +} + +/* Destructs the entries in the range [from, to). + * Caller is expected to bounds check. + */ +static void spl_vector_entries_dtor_range(spl_vector_entries *array, size_t from, size_t to) +{ + zval *begin = array->entries + from, *end = array->entries + to; + while (begin != end) { + zval_ptr_dtor(begin); + begin++; + } +} + +/* Destructs and frees contents but not the array itself. + * If you want to re-use the array then you need to re-initialize it. + */ +static void spl_vector_entries_dtor(spl_vector_entries *array) +{ + if (!spl_vector_entries_empty_capacity(array)) { + spl_vector_entries_dtor_range(array, 0, array->size); + efree(array->entries); + } +} + +static HashTable* spl_vector_get_gc(zend_object *obj, zval **table, int *n) +{ + spl_vector *intern = spl_vector_from_object(obj); + + *table = intern->array.entries; + *n = (int)intern->array.size; + + // Returning the object's properties is redundant if dynamic properties are not allowed, + // and this can't be subclassed. + return NULL; +} + +static HashTable* spl_vector_get_properties(zend_object *obj) +{ + spl_vector *intern = spl_vector_from_object(obj); + HashTable *ht = zend_std_get_properties(obj); + + /* Re-initialize properties array */ + + // Note that destructors may mutate the original array, + // so we fetch the size and circular buffer each time to avoid invalid memory accesses. + for (size_t i = 0; i < intern->array.size; i++) { + zval *elem = &intern->array.entries[i]; + Z_TRY_ADDREF_P(elem); + zend_hash_index_update(ht, i, elem); + } + const size_t properties_size = zend_hash_num_elements(ht); + if (UNEXPECTED(properties_size > intern->array.size)) { + for (size_t i = intern->array.size; i < properties_size; i++) { + zend_hash_index_del(ht, i); + } + } + + return ht; +} + +static void spl_vector_free_storage(zend_object *object) +{ + spl_vector *intern = spl_vector_from_object(object); + spl_vector_entries_dtor(&intern->array); + zend_object_std_dtor(&intern->std); +} + +static zend_object *spl_vector_new_ex(zend_class_entry *class_type, zend_object *orig, bool clone_orig) +{ + spl_vector *intern; + + intern = zend_object_alloc(sizeof(spl_vector), class_type); + /* This is a final class */ + ZEND_ASSERT(class_type == spl_ce_Vector); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + intern->std.handlers = &spl_handler_Vector; + + if (orig && clone_orig) { + spl_vector *other = spl_vector_from_object(orig); + spl_vector_entries_copy_ctor(&intern->array, &other->array); + } else { + intern->array.entries = NULL; + } + + return &intern->std; +} + +static zend_object *spl_vector_new(zend_class_entry *class_type) +{ + return spl_vector_new_ex(class_type, NULL, 0); +} + + +static zend_object *spl_vector_clone(zend_object *old_object) +{ + zend_object *new_object = spl_vector_new_ex(old_object->ce, old_object, 1); + + zend_objects_clone_members(new_object, old_object); + + return new_object; +} + +static int spl_vector_count_elements(zend_object *object, zend_long *count) +{ + const spl_vector *intern = spl_vector_from_object(object); + *count = intern->array.size; + return SUCCESS; +} + +/* Get number of entries in this vector */ +PHP_METHOD(Vector, count) +{ + zval *object = ZEND_THIS; + + ZEND_PARSE_PARAMETERS_NONE(); + + const spl_vector *intern = Z_VECTOR_P(object); + RETURN_LONG(intern->array.size); +} + +/* Free elements and backing storage of this vector */ +PHP_METHOD(Vector, clear) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + if (intern->array.capacity == 0) { + /* Nothing to clear */ + return; + } + /* Immediately make the original storage inaccessible and set count/capacity to 0 in case destructors modify the vector */ + const size_t old_size = intern->array.size; + zval *const old_entries = intern->array.entries; + spl_vector_entries_set_empty_list(&intern->array); + spl_zval_dtor_range(old_entries, old_size); + efree(old_entries); +} + +/* Set size of this Vector */ +PHP_METHOD(Vector, setSize) +{ + zend_long size; + zval *default_zval = NULL; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(size) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(default_zval) + ZEND_PARSE_PARAMETERS_END(); + + if (UNEXPECTED((zend_ulong)size > MAX_VALID_OFFSET + 1)) { + if (size < 0) { + zend_argument_value_error(1, "must be greater than or equal to 0"); + } else { + spl_error_noreturn_max_vector_capacity(); + ZEND_UNREACHABLE(); + } + RETURN_THROWS(); + } + + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + const size_t old_size = intern->array.size; + if ((zend_ulong) size > old_size) { + /* Raise the capacity as needed and fill the space with nulls */ + if ((zend_ulong) size > intern->array.capacity) { + spl_vector_raise_capacity(intern, size); + } + intern->array.size = size; + zval * const entries = intern->array.entries; + if (default_zval == NULL || Z_ISNULL_P(default_zval)) { + for (zend_long i = old_size; i < size; i++) { + ZVAL_NULL(&entries[i]); + } + } else { + for (zend_long i = old_size; i < size; i++) { + ZVAL_COPY(&entries[i], default_zval); + } + } + return; + } + /* Reduce the size and invalidate memory. If a destructor unexpectedly changes the size then read the new size and keep removing elements. */ + const size_t entries_to_remove = old_size - size; + if (entries_to_remove == 0) { + return; + } + zval *const old_entries = intern->array.entries; + zval * old_copy; + if (size == 0) { + old_copy = old_entries; + spl_vector_entries_set_empty_list(&intern->array); + } else { + old_copy = spl_zval_copy_range(&old_entries[size], entries_to_remove); + intern->array.size = size; + + /* If only a quarter of the reserved space is used, then shrink the capacity but leave some room to grow. */ + if ((zend_ulong) size < (intern->array.capacity >> 2)) { + const size_t size = old_size - 1; + const size_t capacity = size > 2 ? size * 2 : 4; + if (capacity < intern->array.capacity) { + spl_vector_shrink_capacity(&intern->array, size, capacity, old_entries); + } + } + } + + spl_zval_dtor_range(old_copy, entries_to_remove); + efree(old_copy); +} + +/* Create this from an optional iterable */ +PHP_METHOD(Vector, __construct) +{ + zval *object = ZEND_THIS; + zval* iterable = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ITERABLE(iterable) + ZEND_PARSE_PARAMETERS_END(); + + spl_vector *intern = Z_VECTOR_P(object); + + if (UNEXPECTED(!spl_vector_entries_uninitialized(&intern->array))) { + zend_throw_exception(spl_ce_RuntimeException, "Called Vector::__construct twice", 0); + /* called __construct() twice, bail out */ + RETURN_THROWS(); + } + uint32_t num_elements; + HashTable *values; + if (!iterable) { +set_empty_list: + spl_vector_entries_set_empty_list(&intern->array); + return; + } + + switch (Z_TYPE_P(iterable)) { + case IS_ARRAY: + values = Z_ARRVAL_P(iterable); + num_elements = zend_hash_num_elements(values); + if (num_elements == 0) { + goto set_empty_list; + } + + zval *val; + zval *entries; + spl_vector_entries *array = &intern->array; + + array->size = 0; /* reset size in case emalloc() fails */ + size_t i = 0; + array->entries = entries = safe_emalloc(num_elements, sizeof(zval), 0); + array->size = num_elements; + array->capacity = num_elements; + ZEND_HASH_FOREACH_VAL(values, val) { + ZEND_ASSERT(i < num_elements); + ZVAL_COPY_DEREF(&entries[i], val); + i++; + } ZEND_HASH_FOREACH_END(); + return; + case IS_OBJECT: + spl_vector_entries_init_from_traversable(&intern->array, Z_OBJ_P(iterable)); + return; + EMPTY_SWITCH_DEFAULT_CASE(); + } +} + +PHP_METHOD(Vector, getIterator) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_create_internal_iterator_zval(return_value, ZEND_THIS); +} + +static void spl_vector_it_dtor(zend_object_iterator *iter) +{ + zval_ptr_dtor(&iter->data); +} + +static void spl_vector_it_rewind(zend_object_iterator *iter) +{ + ((spl_vector_it*)iter)->current = 0; +} + +static int spl_vector_it_valid(zend_object_iterator *iter) +{ + spl_vector_it *iterator = (spl_vector_it*)iter; + spl_vector *object = Z_VECTOR_P(&iter->data); + + if (iterator->current >= 0 && ((zend_ulong) iterator->current) < object->array.size) { + return SUCCESS; + } + + return FAILURE; +} + +static zval *spl_vector_read_offset_helper(spl_vector *intern, size_t offset) +{ + /* we have to return NULL on error here to avoid memleak because of + * ZE duplicating uninitialized_zval_ptr */ + if (UNEXPECTED(offset >= intern->array.size)) { + zend_throw_exception(spl_ce_OutOfBoundsException, "Index out of range", 0); + return NULL; + } else { + return &intern->array.entries[offset]; + } +} + +static zval *spl_vector_it_get_current_data(zend_object_iterator *iter) +{ + spl_vector_it *iterator = (spl_vector_it*)iter; + spl_vector *object = Z_VECTOR_P(&iter->data); + + zval *data = spl_vector_read_offset_helper(object, iterator->current); + + if (UNEXPECTED(data == NULL)) { + return &EG(uninitialized_zval); + } else { + return data; + } +} + +static void spl_vector_it_get_current_key(zend_object_iterator *iter, zval *key) +{ + spl_vector_it *iterator = (spl_vector_it*)iter; + spl_vector *object = Z_VECTOR_P(&iter->data); + + size_t offset = iterator->current; + if (offset >= object->array.size) { + ZVAL_NULL(key); + } else { + ZVAL_LONG(key, offset); + } +} + +static void spl_vector_it_move_forward(zend_object_iterator *iter) +{ + ((spl_vector_it*)iter)->current++; +} + +/* iterator handler table */ +static const zend_object_iterator_funcs spl_vector_it_funcs = { + spl_vector_it_dtor, + spl_vector_it_valid, + spl_vector_it_get_current_data, + spl_vector_it_get_current_key, + spl_vector_it_move_forward, + spl_vector_it_rewind, + NULL, + NULL, /* get_gc */ +}; + + +zend_object_iterator *spl_vector_get_iterator(zend_class_entry *ce, zval *object, int by_ref) +{ + // This is final + ZEND_ASSERT(ce == spl_ce_Vector); + spl_vector_it *iterator; + + if (UNEXPECTED(by_ref)) { + zend_throw_error(NULL, "An iterator cannot be used with foreach by reference"); + return NULL; + } + + iterator = emalloc(sizeof(spl_vector_it)); + + zend_iterator_init((zend_object_iterator*)iterator); + + ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); + iterator->intern.funcs = &spl_vector_it_funcs; + + return &iterator->intern; +} + +PHP_METHOD(Vector, __unserialize) +{ + HashTable *raw_data; + zval *val; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &raw_data) == FAILURE) { + RETURN_THROWS(); + } + + const size_t num_entries = zend_hash_num_elements(raw_data); + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + if (UNEXPECTED(!spl_vector_entries_uninitialized(&intern->array))) { + zend_throw_exception(spl_ce_RuntimeException, "Already unserialized", 0); + RETURN_THROWS(); + } + + ZEND_ASSERT(intern->array.entries == NULL); + + zval *const entries = safe_emalloc(num_entries, sizeof(zval), 0); + zval *it = entries; + + zend_string *str; + + ZEND_HASH_FOREACH_STR_KEY_VAL(raw_data, str, val) { + if (UNEXPECTED(str)) { + for (zval *deleteIt = entries; deleteIt < it; deleteIt++) { + zval_ptr_dtor_nogc(deleteIt); + } + efree(entries); + zend_throw_exception(spl_ce_UnexpectedValueException, "Vector::__unserialize saw unexpected string key, expected sequence of values", 0); + RETURN_THROWS(); + } + ZVAL_COPY_DEREF(it++, val); + } ZEND_HASH_FOREACH_END(); + ZEND_ASSERT(it == entries + num_entries); + + intern->array.size = num_entries; + intern->array.capacity = num_entries; + intern->array.entries = entries; +} + +static void spl_vector_entries_init_from_array_values(spl_vector_entries *array, zend_array *raw_data) +{ + size_t num_entries = zend_hash_num_elements(raw_data); + if (num_entries == 0) { + spl_vector_entries_set_empty_list(array); + return; + } + zval *entries = safe_emalloc(num_entries, sizeof(zval), 0); + size_t actual_size = 0; + zval *val; + ZEND_HASH_FOREACH_VAL(raw_data, val) { + ZVAL_COPY_DEREF(&entries[actual_size], val); + actual_size++; + } ZEND_HASH_FOREACH_END(); + + ZEND_ASSERT(actual_size <= num_entries); + if (UNEXPECTED(!actual_size)) { + efree(entries); + entries = NULL; + num_entries = 0; + } + + array->entries = entries; + array->size = actual_size; + array->capacity = num_entries; +} + +PHP_METHOD(Vector, __set_state) +{ + zend_array *array_ht; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(array_ht) + ZEND_PARSE_PARAMETERS_END(); + zend_object *object = spl_vector_new(spl_ce_Vector); + spl_vector *intern = spl_vector_from_object(object); + spl_vector_entries_init_from_array_values(&intern->array, array_ht); + + RETURN_OBJ(object); +} + +PHP_METHOD(Vector, __serialize) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + + if (spl_vector_entries_empty_size(&intern->array)) { + RETURN_EMPTY_ARRAY(); + } + zval *entries = intern->array.entries; + size_t len = intern->array.size; + zend_array *flat_entries_array = zend_new_array(len * 2); + /* Initialize return array */ + zend_hash_real_init_packed(flat_entries_array); + + /* Go through entries and add keys and values to the return array */ + ZEND_HASH_FILL_PACKED(flat_entries_array) { + for (size_t i = 0; i < len; i++) { + zval *tmp = &entries[i]; + Z_TRY_ADDREF_P(tmp); + ZEND_HASH_FILL_ADD(tmp); + } + } ZEND_HASH_FILL_END(); + + RETURN_ARR(flat_entries_array); +} + +PHP_METHOD(Vector, toArray) +{ + ZEND_PARSE_PARAMETERS_NONE(); + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + size_t len = intern->array.size; + if (!len) { + RETURN_EMPTY_ARRAY(); + } + zval *entries = intern->array.entries; + zend_array *values = zend_new_array(len); + /* Initialize return array */ + zend_hash_real_init_packed(values); + + /* Go through values and add values to the return array */ + ZEND_HASH_FILL_PACKED(values) { + for (size_t i = 0; i < len; i++) { + zval *tmp = &entries[i]; + Z_TRY_ADDREF_P(tmp); + ZEND_HASH_FILL_ADD(tmp); + } + } ZEND_HASH_FILL_END(); + RETURN_ARR(values); +} + +static zend_always_inline void spl_vector_get_value_at_offset(zval *return_value, const zval *zval_this, zend_long offset) +{ + spl_vector *intern = Z_VECTOR_P(zval_this); + size_t len = intern->array.size; + if (UNEXPECTED((zend_ulong) offset >= len)) { + zend_throw_exception(spl_ce_OutOfBoundsException, "Index out of range", 0); + RETURN_THROWS(); + } + RETURN_COPY(&intern->array.entries[offset]); +} + +PHP_METHOD(Vector, get) +{ + zend_long offset; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END(); + + spl_vector_get_value_at_offset(return_value, ZEND_THIS, offset); +} + +PHP_METHOD(Vector, offsetGet) +{ + zval *offset_zv; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(offset_zv) + ZEND_PARSE_PARAMETERS_END(); + + zend_long offset; + CONVERT_OFFSET_TO_LONG_OR_THROW(offset, offset_zv); + + spl_vector_get_value_at_offset(return_value, ZEND_THIS, offset); +} + +PHP_METHOD(Vector, offsetExists) +{ + zval *offset_zv; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(offset_zv) + ZEND_PARSE_PARAMETERS_END(); + + zend_long offset; + CONVERT_OFFSET_TO_LONG_OR_THROW(offset, offset_zv); + + const spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + const size_t len = intern->array.size; + RETURN_BOOL((zend_ulong) offset < len); +} + +PHP_METHOD(Vector, indexOf) +{ + zval *value; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + const spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + const size_t len = intern->array.size; + zval *entries = intern->array.entries; + for (size_t i = 0; i < len; i++) { + if (zend_is_identical(value, &entries[i])) { + RETURN_LONG(i); + } + } + RETURN_NULL(); +} + +PHP_METHOD(Vector, filter) +{ + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fci_cache = empty_fcall_info_cache; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_FUNC_OR_NULL(fci, fci_cache) + ZEND_PARSE_PARAMETERS_END(); + + const spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + size_t size = 0; + size_t capacity = 0; + zval *entries = NULL; + zval operand; + if (intern->array.size == 0) { + /* do nothing */ + } else if (ZEND_FCI_INITIALIZED(fci)) { + zval retval; + + fci.params = &operand; + fci.param_count = 1; + fci.retval = &retval; + + for (size_t i = 0; i < intern->array.size; i++) { + ZVAL_COPY(&operand, &intern->array.entries[i]); + const int result = zend_call_function(&fci, &fci_cache); + if (UNEXPECTED(result != SUCCESS || EG(exception))) { + zval_ptr_dtor(&operand); +cleanup: + if (entries) { + for (; size > 0; size--) { + zval_ptr_dtor(&entries[size]); + } + efree(entries); + } + return; + } + const bool is_true = zend_is_true(&retval); + zval_ptr_dtor(&retval); + if (UNEXPECTED(EG(exception))) { + goto cleanup; + } + if (!is_true) { + zval_ptr_dtor(&operand); + if (UNEXPECTED(EG(exception))) { + goto cleanup; + } + continue; + } + + if (size >= capacity) { + if (entries) { + capacity = size + intern->array.size - i; + entries = safe_erealloc(entries, capacity, sizeof(zval), 0); + } else { + ZEND_ASSERT(size == 0); + ZEND_ASSERT(capacity == 0); + capacity = intern->array.size > i ? intern->array.size - i : 1; + entries = safe_emalloc(capacity, sizeof(zval), 0); + } + } + ZEND_ASSERT(size < capacity); + ZVAL_COPY_VALUE(&entries[size], &operand); + size++; + } + } else { + for (size_t i = 0; i < intern->array.size; i++) { + ZVAL_COPY(&operand, &intern->array.entries[i]); + const bool is_true = zend_is_true(&operand); + if (!is_true) { + zval_ptr_dtor(&operand); + if (UNEXPECTED(EG(exception))) { + goto cleanup; + } + continue; + } + + if (size >= capacity) { + if (entries) { + capacity = size + intern->array.size - i; + entries = safe_erealloc(entries, capacity, sizeof(zval), 0); + } else { + ZEND_ASSERT(size == 0); + ZEND_ASSERT(capacity == 0); + capacity = intern->array.size > i ? intern->array.size - i : 1; + entries = safe_emalloc(capacity, sizeof(zval), 0); + } + } + ZEND_ASSERT(size < capacity); + ZVAL_COPY_VALUE(&entries[size], &operand); + size++; + } + } + + zend_object *new_object = spl_vector_new_ex(spl_ce_Vector, NULL, 0); + spl_vector *new_intern = spl_vector_from_object(new_object); + if (size == 0) { + ZEND_ASSERT(!entries); + spl_vector_entries_set_empty_list(&new_intern->array); + RETURN_OBJ(new_object); + } + if (capacity > size) { + /* Shrink allocated value to actual required size */ + entries = erealloc(entries, size * sizeof(zval)); + } + + new_intern->array.entries = entries; + new_intern->array.size = size; + new_intern->array.capacity = size; + RETURN_OBJ(new_object); +} + +PHP_METHOD(Vector, map) +{ + zend_fcall_info fci; + zend_fcall_info_cache fci_cache; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC(fci, fci_cache) + ZEND_PARSE_PARAMETERS_END(); + + const spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + size_t new_capacity = intern->array.size; + + if (new_capacity == 0) { + zend_object *new_object = spl_vector_new_ex(spl_ce_Vector, NULL, 0); + spl_vector *new_intern = spl_vector_from_object(new_object); + spl_vector_entries_set_empty_list(&new_intern->array); + RETURN_OBJ(new_object); + } + + zval *entries = emalloc(new_capacity * sizeof(zval)); + size_t size = 0; + + zval operand; + fci.params = &operand; + fci.param_count = 1; + + do { + if (UNEXPECTED(size >= new_capacity)) { + if (entries) { + new_capacity = size + 1; + entries = safe_erealloc(entries, new_capacity, sizeof(zval), 0); + } else { + ZEND_ASSERT(size == 0); + ZEND_ASSERT(new_capacity == 0); + new_capacity = intern->array.size; + entries = safe_emalloc(new_capacity, sizeof(zval), 0); + } + } + fci.retval = &entries[size]; + ZVAL_COPY(&operand, &intern->array.entries[size]); + const int result = zend_call_function(&fci, &fci_cache); + fci.retval = &entries[size]; + zval_ptr_dtor(&operand); + if (UNEXPECTED(result != SUCCESS || EG(exception))) { + if (entries) { + for (; size > 0; size--) { + zval_ptr_dtor(&entries[size]); + } + efree(entries); + } + return; + } + size++; + } while (size < intern->array.size); + + zend_object *new_object = spl_vector_new_ex(spl_ce_Vector, NULL, 0); + spl_vector *new_intern = spl_vector_from_object(new_object); + if (size == 0) { + ZEND_ASSERT(!entries); + spl_vector_entries_set_empty_list(&new_intern->array); + RETURN_OBJ(new_object); + } + if (UNEXPECTED(new_capacity > size)) { + /* Shrink allocated value to actual required size */ + entries = erealloc(entries, size * sizeof(zval)); + } + + new_intern->array.entries = entries; + new_intern->array.size = size; + new_intern->array.capacity = size; + RETURN_OBJ(new_object); +} + +PHP_METHOD(Vector, contains) +{ + zval *value; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + const spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + const size_t len = intern->array.size; + zval *entries = intern->array.entries; + for (size_t i = 0; i < len; i++) { + if (zend_is_identical(value, &entries[i])) { + RETURN_TRUE; + } + } + RETURN_FALSE; +} + +static zend_always_inline void spl_vector_set_value_at_offset(zend_object *object, zend_long offset, zval *value) { + const spl_vector *intern = spl_vector_from_object(object); + zval *const ptr = &intern->array.entries[offset]; + + size_t len = intern->array.size; + if (UNEXPECTED((zend_ulong) offset >= len)) { + zend_throw_exception(spl_ce_OutOfBoundsException, "Index out of range", 0); + return; + } + zval tmp; + ZVAL_COPY_VALUE(&tmp, ptr); + ZVAL_COPY(ptr, value); + zval_ptr_dtor(&tmp); +} + +PHP_METHOD(Vector, set) +{ + zend_long offset; + zval *value; + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG(offset) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + spl_vector_set_value_at_offset(Z_OBJ_P(ZEND_THIS), offset, value); +} + +PHP_METHOD(Vector, offsetSet) +{ + zval *offset_zv, *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(offset_zv) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + zend_long offset; + CONVERT_OFFSET_TO_LONG_OR_THROW(offset, offset_zv); + + spl_vector_set_value_at_offset(Z_OBJ_P(ZEND_THIS), offset, value); +} + +static zend_always_inline void spl_vector_push(spl_vector *intern, zval *value) +{ + const size_t old_size = intern->array.size; + const size_t old_capacity = intern->array.capacity; + + if (old_size >= old_capacity) { + ZEND_ASSERT(old_size == old_capacity); + spl_vector_raise_capacity(intern, old_size > 2 ? old_size * 2 : 4); + } + ZVAL_COPY(&intern->array.entries[old_size], value); + intern->array.size++; +} + +/* Based on array_push */ +PHP_METHOD(Vector, push) +{ + const zval *args; + uint32_t argc; + + ZEND_PARSE_PARAMETERS_START(0, -1) + Z_PARAM_VARIADIC('+', args, argc) + ZEND_PARSE_PARAMETERS_END(); + + if (argc == 0) { + return; + } + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + const size_t old_size = intern->array.size; + const size_t new_size = old_size + argc; + /* The compiler will type check but eliminate dead code on platforms where size_t is 32 bits (4 bytes) */ + if (SIZEOF_SIZE_T < 8 && UNEXPECTED(new_size > MAX_VALID_OFFSET + 1 || new_size < old_size)) { + spl_error_noreturn_max_vector_capacity(); + ZEND_UNREACHABLE(); + } + const size_t old_capacity = intern->array.capacity; + if (new_size > old_capacity) { + const size_t new_capacity = new_size >= 3 ? (new_size - 1) * 2 : 4; + ZEND_ASSERT(new_capacity >= new_size); + spl_vector_raise_capacity(intern, new_capacity); + } + zval *entries = intern->array.entries; + + for (uint32_t i = 0; i < argc; i++) { + ZVAL_COPY(&entries[old_size + i], &args[i]); + } + intern->array.size = new_size; +} + +PHP_METHOD(Vector, pop) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + const size_t old_size = intern->array.size; + if (old_size == 0) { + zend_throw_exception(spl_ce_UnderflowException, "Cannot pop from empty Vector", 0); + RETURN_THROWS(); + } + const size_t old_capacity = intern->array.capacity; + intern->array.size--; + RETVAL_COPY_VALUE(&intern->array.entries[intern->array.size]); + if (old_size * 4 < old_capacity) { + /* Shrink the storage if only a quarter of the capacity is used */ + const size_t size = old_size - 1; + zval *old_entries = intern->array.entries; + const size_t capacity = size > 2 ? size * 2 : 4; + if (capacity < old_capacity) { + spl_vector_shrink_capacity(&intern->array, size, capacity, old_entries); + } + } +} + +PHP_METHOD(Vector, shrinkToFit) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + const size_t size = intern->array.size; + const size_t old_capacity = intern->array.capacity; + if (size >= old_capacity) { + ZEND_ASSERT(size == old_capacity); + return; + } + + if (size == 0) { + efree(intern->array.entries); + intern->array.entries = (zval *)empty_entry_list; + } else { + intern->array.entries = safe_erealloc(intern->array.entries, size, sizeof(zval), 0); + } + intern->array.capacity = size; +} + +PHP_METHOD(Vector, offsetUnset) +{ + zval *offset_zv; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &offset_zv) == FAILURE) { + RETURN_THROWS(); + } + zend_throw_exception(spl_ce_RuntimeException, "Vector does not support offsetUnset - elements must be set to null or removed by resizing", 0); + RETURN_THROWS(); +} + +static void spl_vector_return_list(zval *return_value, spl_vector *intern) +{ + spl_convert_zval_list_to_php_array_list(return_value, intern->array.entries, intern->array.size); +} + +PHP_METHOD(Vector, jsonSerialize) +{ + /* json_encoder.c will always encode objects as {"0":..., "1":...}, and detects recursion if an object returns its internal property array, so we have to return a new array */ + ZEND_PARSE_PARAMETERS_NONE(); + spl_vector *intern = Z_VECTOR_P(ZEND_THIS); + spl_vector_return_list(return_value, intern); +} + +static void spl_vector_write_dimension(zend_object *object, zval *offset_zv, zval *value) +{ + spl_vector *intern = spl_vector_from_object(object); + if (!offset_zv) { + spl_vector_push(intern, value); + return; + } + + zend_long offset; + CONVERT_OFFSET_TO_LONG_OR_THROW(offset, offset_zv); + + if (offset < 0 || (zend_ulong) offset >= intern->array.size) { + zend_throw_exception(spl_ce_OutOfBoundsException, "Index invalid or out of range", 0); + return; + } + ZVAL_DEREF(value); + spl_vector_set_value_at_offset(object, offset, value); +} + +static zval *spl_vector_read_dimension(zend_object *object, zval *offset_zv, int type, zval *rv) +{ + if (UNEXPECTED(!offset_zv || Z_ISUNDEF_P(offset_zv))) { + return &EG(uninitialized_zval); + } + + zend_long offset; + CONVERT_OFFSET_TO_LONG_OR_THROW_RETURN_NULLPTR(offset, offset_zv); + + const spl_vector *intern = spl_vector_from_object(object); + + if (UNEXPECTED(offset < 0 || (zend_ulong) offset >= intern->array.size)) { + if (type != BP_VAR_IS) { + zend_throw_exception(spl_ce_OutOfBoundsException, "Index out of range", 0); + } + return NULL; + } else { + return &intern->array.entries[offset]; + } +} + +static int spl_vector_has_dimension(zend_object *object, zval *offset_zv, int check_empty) +{ + zend_long offset; + if (UNEXPECTED(Z_TYPE_P(offset_zv) != IS_LONG)) { + offset = spl_offset_convert_to_long(offset_zv); + if (UNEXPECTED(EG(exception))) { + return 0; + } + } else { + offset = Z_LVAL_P(offset_zv); + } + + const spl_vector *intern = spl_vector_from_object(object); + + if (UNEXPECTED(((zend_ulong) offset) >= intern->array.size || offset < 0)) { + return 0; + } + + zval *val = &intern->array.entries[offset]; + if (check_empty) { + return zend_is_true(val); + } + return Z_TYPE_P(val) != IS_NULL; +} + +PHP_MINIT_FUNCTION(spl_vector) +{ + spl_ce_Vector = register_class_Vector(zend_ce_aggregate, zend_ce_countable, php_json_serializable_ce, zend_ce_arrayaccess); + spl_ce_Vector->create_object = spl_vector_new; + + memcpy(&spl_handler_Vector, &std_object_handlers, sizeof(zend_object_handlers)); + + spl_handler_Vector.offset = XtOffsetOf(spl_vector, std); + spl_handler_Vector.clone_obj = spl_vector_clone; + spl_handler_Vector.count_elements = spl_vector_count_elements; + spl_handler_Vector.get_properties = spl_vector_get_properties; + spl_handler_Vector.get_gc = spl_vector_get_gc; + spl_handler_Vector.free_obj = spl_vector_free_storage; + + spl_handler_Vector.read_dimension = spl_vector_read_dimension; + spl_handler_Vector.write_dimension = spl_vector_write_dimension; + spl_handler_Vector.has_dimension = spl_vector_has_dimension; + + spl_ce_Vector->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NO_DYNAMIC_PROPERTIES; + spl_ce_Vector->get_iterator = spl_vector_get_iterator; + + return SUCCESS; +} diff --git a/ext/spl/spl_vector.h b/ext/spl/spl_vector.h new file mode 100644 index 0000000000000..d7807105a784e --- /dev/null +++ b/ext/spl/spl_vector.h @@ -0,0 +1,24 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Tyson Andre | + +----------------------------------------------------------------------+ +*/ + +#ifndef SPL_VECTOR_H +#define SPL_VECTOR_H + +extern PHPAPI zend_class_entry *spl_ce_Vector; + +PHP_MINIT_FUNCTION(spl_vector); + +#endif /* SPL_VECTOR_H */ diff --git a/ext/spl/spl_vector.stub.php b/ext/spl/spl_vector.stub.php new file mode 100644 index 0000000000000..3f3cbc4fd4d39 --- /dev/null +++ b/ext/spl/spl_vector.stub.php @@ -0,0 +1,108 @@ += count() + */ + public function getIterator(): InternalIterator {} + /** + * Returns the number of values in this Vector + */ + public function count(): int {} + /** + * Reduces the Vector's capacity to its size, freeing any extra unused memory. + */ + public function shrinkToFit(): void {} + /** + * Remove all elements from the array and free all reserved capacity. + */ + public function clear(): void {} + + /** + * If $size is greater than the current size, raise the size and fill the free space with $value + * If $size is less than the current size, reduce the size and discard elements. + */ + public function setSize(int $size, mixed $value = null): void {} + + public function __serialize(): array {} + public function __unserialize(array $data): void {} + public static function __set_state(array $array): Vector {} + + public function push(mixed ...$values): void {} + public function pop(): mixed {} + + public function toArray(): array {} + // Strictly typed, unlike offsetGet/offsetSet + public function get(int $offset): mixed {} + public function set(int $offset, mixed $value): void {} + + /** + * Returns the value at (int)$offset. + * @throws OutOfBoundsException if the value of (int)$offset is not within the bounds of this vector + */ + public function offsetGet(mixed $offset): mixed {} + + /** + * Returns true if `0 <= (int)$offset && (int)$offset < $this->count(). + */ + public function offsetExists(mixed $offset): bool {} + + /** + * Sets the value at offset (int)$offset to $value + * @throws \OutOfBoundsException if the value of (int)$offset is not within the bounds of this vector + */ + public function offsetSet(mixed $offset, mixed $value): void {} + + /** + * @throws \RuntimeException unconditionally because unset and null are different things, unlike SplFixedArray + */ + public function offsetUnset(mixed $offset): void {} + + /** + * Returns the offset of a value that is === $value, or returns null. + */ + public function indexOf(mixed $value): ?int {} + /** + * @return bool true if there exists a value === $value in this vector. + */ + public function contains(mixed $value): bool {} + + /** + * Returns a new Vector instance created from the return values of $callable($element) + * being applied to each element of this vector. + * + * (at)param null|callable(mixed):mixed $callback + */ + public function map(callable $callback): Vector {} + /** + * Returns the subset of elements of the Vector satisfying the predicate. + * + * If the value returned by the callback is truthy + * (e.g. true, non-zero number, non-empty array, truthy object, etc.), + * this is treated as satisfying the predicate. + * + * (at)param null|callable(mixed):bool $callback + */ + public function filter(?callable $callback = null): Vector {} + + public function jsonSerialize(): array {} +} diff --git a/ext/spl/spl_vector_arginfo.h b/ext/spl/spl_vector_arginfo.h new file mode 100644 index 0000000000000..1a5eb739c2a51 --- /dev/null +++ b/ext/spl/spl_vector_arginfo.h @@ -0,0 +1,151 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 92769a3b5fa4af0222ab47445b174406288c303d */ + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Vector___construct, 0, 0, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, iterator, IS_ITERABLE, 0, "[]") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Vector_getIterator, 0, 0, InternalIterator, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_count, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_shrinkToFit, 0, 0, IS_VOID, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Vector_clear arginfo_class_Vector_shrinkToFit + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_setSize, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, value, IS_MIXED, 0, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector___serialize, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector___unserialize, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Vector___set_state, 0, 1, Vector, 0) + ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_push, 0, 0, IS_VOID, 0) + ZEND_ARG_VARIADIC_TYPE_INFO(0, values, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_pop, 0, 0, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Vector_toArray arginfo_class_Vector___serialize + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_get, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_set, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_offsetGet, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_offsetExists, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_offsetSet, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_offsetUnset, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_indexOf, 0, 1, IS_LONG, 1) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_contains, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Vector_map, 0, 1, Vector, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Vector_filter, 0, 0, Vector, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, callback, IS_CALLABLE, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_Vector_jsonSerialize arginfo_class_Vector___serialize + + +ZEND_METHOD(Vector, __construct); +ZEND_METHOD(Vector, getIterator); +ZEND_METHOD(Vector, count); +ZEND_METHOD(Vector, shrinkToFit); +ZEND_METHOD(Vector, clear); +ZEND_METHOD(Vector, setSize); +ZEND_METHOD(Vector, __serialize); +ZEND_METHOD(Vector, __unserialize); +ZEND_METHOD(Vector, __set_state); +ZEND_METHOD(Vector, push); +ZEND_METHOD(Vector, pop); +ZEND_METHOD(Vector, toArray); +ZEND_METHOD(Vector, get); +ZEND_METHOD(Vector, set); +ZEND_METHOD(Vector, offsetGet); +ZEND_METHOD(Vector, offsetExists); +ZEND_METHOD(Vector, offsetSet); +ZEND_METHOD(Vector, offsetUnset); +ZEND_METHOD(Vector, indexOf); +ZEND_METHOD(Vector, contains); +ZEND_METHOD(Vector, map); +ZEND_METHOD(Vector, filter); +ZEND_METHOD(Vector, jsonSerialize); + + +static const zend_function_entry class_Vector_methods[] = { + ZEND_ME(Vector, __construct, arginfo_class_Vector___construct, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, getIterator, arginfo_class_Vector_getIterator, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, count, arginfo_class_Vector_count, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, shrinkToFit, arginfo_class_Vector_shrinkToFit, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, clear, arginfo_class_Vector_clear, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, setSize, arginfo_class_Vector_setSize, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, __serialize, arginfo_class_Vector___serialize, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, __unserialize, arginfo_class_Vector___unserialize, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, __set_state, arginfo_class_Vector___set_state, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(Vector, push, arginfo_class_Vector_push, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, pop, arginfo_class_Vector_pop, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, toArray, arginfo_class_Vector_toArray, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, get, arginfo_class_Vector_get, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, set, arginfo_class_Vector_set, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, offsetGet, arginfo_class_Vector_offsetGet, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, offsetExists, arginfo_class_Vector_offsetExists, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, offsetSet, arginfo_class_Vector_offsetSet, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, offsetUnset, arginfo_class_Vector_offsetUnset, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, indexOf, arginfo_class_Vector_indexOf, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, contains, arginfo_class_Vector_contains, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, map, arginfo_class_Vector_map, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, filter, arginfo_class_Vector_filter, ZEND_ACC_PUBLIC) + ZEND_ME(Vector, jsonSerialize, arginfo_class_Vector_jsonSerialize, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static zend_class_entry *register_class_Vector(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_JsonSerializable, zend_class_entry *class_entry_ArrayAccess) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "Vector", class_Vector_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL; + zend_class_implements(class_entry, 4, class_entry_IteratorAggregate, class_entry_Countable, class_entry_JsonSerializable, class_entry_ArrayAccess); + + return class_entry; +} diff --git a/ext/spl/tests/Vector/Vector.phpt b/ext/spl/tests/Vector/Vector.phpt new file mode 100644 index 0000000000000..cec090ee0bc29 --- /dev/null +++ b/ext/spl/tests/Vector/Vector.phpt @@ -0,0 +1,60 @@ +--TEST-- +Vector constructed from array +--FILE-- + 'x', 'second' => new stdClass()]); +foreach ($vec as $key => $value) { + printf("Key: %s\nValue: %s\n", var_export($key, true), var_export($value, true)); +} +var_dump($vec); +var_dump((array)$vec); + +$vec = new Vector([]); +var_dump($vec); +var_dump((array)$vec); +foreach ($vec as $key => $value) { + echo "Unreachable\n"; +} + +// Vector will always reindex keys in the order of iteration, like array_values() does. +$vec = new Vector([2 => 'a', 0 => 'b']); +var_dump($vec); + +var_dump(new Vector([-1 => new stdClass()])); +?> +--EXPECT-- +Key: 0 +Value: 'x' +Key: 1 +Value: (object) array( +) +object(Vector)#1 (2) { + [0]=> + string(1) "x" + [1]=> + object(stdClass)#2 (0) { + } +} +array(2) { + [0]=> + string(1) "x" + [1]=> + object(stdClass)#2 (0) { + } +} +object(Vector)#3 (0) { +} +array(0) { +} +object(Vector)#1 (2) { + [0]=> + string(1) "a" + [1]=> + string(1) "b" +} +object(Vector)#3 (1) { + [0]=> + object(stdClass)#4 (0) { + } +} \ No newline at end of file diff --git a/ext/spl/tests/Vector/aggregate.phpt b/ext/spl/tests/Vector/aggregate.phpt new file mode 100644 index 0000000000000..aa0cde1acac6b --- /dev/null +++ b/ext/spl/tests/Vector/aggregate.phpt @@ -0,0 +1,17 @@ +--TEST-- +Vector is an IteratorAggregate +--FILE-- + 'value']]); +foreach ($vector as $k => $v) { + foreach ($vector as $k2 => $v2) { + printf("k=%s k2=%s v=%s v2=%s\n", json_encode($k), json_encode($k2), json_encode($v), json_encode($v2)); + } +} +?> +--EXPECT-- +k=0 k2=0 v="x" v2="x" +k=0 k2=1 v="x" v2={"key":"value"} +k=1 k2=0 v={"key":"value"} v2="x" +k=1 k2=1 v={"key":"value"} v2={"key":"value"} \ No newline at end of file diff --git a/ext/spl/tests/Vector/arrayCast.phpt b/ext/spl/tests/Vector/arrayCast.phpt new file mode 100644 index 0000000000000..6dd1af66b369e --- /dev/null +++ b/ext/spl/tests/Vector/arrayCast.phpt @@ -0,0 +1,30 @@ +--TEST-- +Vector to array +--FILE-- +pop(); +var_dump($vec); + + +?> +--EXPECT-- +array(1) { + [0]=> + string(4) "TEST" +} +array(1) { + [0]=> + string(5) "TEST2" +} +object(Vector)#1 (1) { + [0]=> + string(5) "TEST2" +} +object(Vector)#1 (0) { +} diff --git a/ext/spl/tests/Vector/clear.phpt b/ext/spl/tests/Vector/clear.phpt new file mode 100644 index 0000000000000..3164df2e66d88 --- /dev/null +++ b/ext/spl/tests/Vector/clear.phpt @@ -0,0 +1,25 @@ +--TEST-- +Vector clear +--FILE-- +toArray()); +var_dump($vec->count()); +$vec->clear(); +foreach ($vec as $value) { + echo "Not reached\n"; +} +var_dump($vec->toArray()); +var_dump($vec->count()); +?> +--EXPECT-- +array(1) { + [0]=> + object(stdClass)#2 (0) { + } +} +int(1) +array(0) { +} +int(0) diff --git a/ext/spl/tests/Vector/clone.phpt b/ext/spl/tests/Vector/clone.phpt new file mode 100644 index 0000000000000..6106d34bbaadc --- /dev/null +++ b/ext/spl/tests/Vector/clone.phpt @@ -0,0 +1,26 @@ +--TEST-- +Vector can be cloned +--FILE-- + $value) { + echo "Saw entry:\n"; + var_dump($key, $value); +} + +?> +--EXPECT-- +Saw entry: +int(0) +object(stdClass)#2 (0) { +} +Saw entry: +int(1) +object(ArrayObject)#3 (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } +} \ No newline at end of file diff --git a/ext/spl/tests/Vector/contains.phpt b/ext/spl/tests/Vector/contains.phpt new file mode 100644 index 0000000000000..3d9f4fb60e1a1 --- /dev/null +++ b/ext/spl/tests/Vector/contains.phpt @@ -0,0 +1,17 @@ +--TEST-- +Vector contains()/indexOf(); +--FILE-- +contains($value)), json_encode($vec->indexOf($value))); +} +?> +--EXPECT-- +null: contains=false, indexOf=null +"o": contains=false, indexOf=null +{}: contains=true, indexOf=1 +{}: contains=false, indexOf=null +"first": contains=true, indexOf=0 diff --git a/ext/spl/tests/Vector/exceptionhandler.phpt b/ext/spl/tests/Vector/exceptionhandler.phpt new file mode 100644 index 0000000000000..27cf372323798 --- /dev/null +++ b/ext/spl/tests/Vector/exceptionhandler.phpt @@ -0,0 +1,36 @@ +--TEST-- +Vector constructed from Traversable throwing +--FILE-- +value\n"; + } +} + +function yields_and_throws() { + yield 123 => new HasDestructor('in value'); + yield new HasDestructor('in key') => 123; + yield 123 => new HasDestructor('in value'); + yield 'first' => 'second'; + + throw new RuntimeException('test'); + + echo "Unreachable\n"; +} +try { + $vec = new Vector(yields_and_throws()); +} catch (RuntimeException $e) { + echo "Caught " . $e->getMessage() . "\n"; +} +gc_collect_cycles(); +echo "Done\n"; +?> +--EXPECT-- +in HasDestructor::__destruct in key +in HasDestructor::__destruct in value +in HasDestructor::__destruct in value +Caught test +Done diff --git a/ext/spl/tests/Vector/filter.phpt b/ext/spl/tests/Vector/filter.phpt new file mode 100644 index 0000000000000..5fda5aae967d2 --- /dev/null +++ b/ext/spl/tests/Vector/filter.phpt @@ -0,0 +1,58 @@ +--TEST-- +Vector filter() +--FILE-- +count()); + foreach ($v as $i => $value) { + printf("Value #%d: %s\n", $i, var_export($value, true)); + } +} +dump_vector((new Vector([false]))->filter()); +dump_vector((new Vector([true]))->filter()); +dump_vector((new Vector([strtoupper('test'), true, false, new stdClass()]))->filter()); +echo "Test functions\n"; +$negate = fn($x) => !$x; +dump_vector((new Vector([]))->filter($negate)); +dump_vector((new Vector([true]))->filter($negate)); +dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->filter($negate)); +$identity = fn($x) => $x; +dump_vector((new Vector([]))->filter($identity)); +dump_vector((new Vector([true]))->filter($identity)); +dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->filter($identity)); +try { + (new Vector([strtoupper('first'), 'not reached']))->filter(function ($parameter) { + var_dump($parameter); + throw new RuntimeException("test"); + }); + echo "Should throw\n"; +} catch (Exception $e) { + printf("Caught %s: %s\n", get_class($e), $e->getMessage()); +} +?> +--EXPECT-- +count=0 +count=1 +Value #0: true +count=3 +Value #0: 'TEST' +Value #1: true +Value #2: (object) array( +) +Test functions +count=0 +count=0 +count=2 +Value #0: false +Value #1: array ( +) +count=0 +count=1 +Value #0: true +count=3 +Value #0: 'TEST' +Value #1: true +Value #2: (object) array( +) +string(5) "FIRST" +Caught RuntimeException: test diff --git a/ext/spl/tests/Vector/isset.phpt b/ext/spl/tests/Vector/isset.phpt new file mode 100644 index 0000000000000..3d020ba3bfee5 --- /dev/null +++ b/ext/spl/tests/Vector/isset.phpt @@ -0,0 +1,21 @@ +--TEST-- +Vector isset +--FILE-- + +--EXPECTF-- +offset=0 isset=false empty=true value=null +offset=1 isset=true empty=true value=false +offset=2 isset=true empty=false value={} +offset=3 isset=false empty=true value=null +offset=-1 isset=false empty=true value=null +offset="1" isset=true empty=true value=false +offset=-%d isset=false empty=true value=null +offset=%d isset=false empty=true value=null +offset=1 isset=true empty=true value=false +offset=false isset=false empty=true value=null +offset=true isset=true empty=true value=false diff --git a/ext/spl/tests/Vector/map.phpt b/ext/spl/tests/Vector/map.phpt new file mode 100644 index 0000000000000..1e3848a6328c3 --- /dev/null +++ b/ext/spl/tests/Vector/map.phpt @@ -0,0 +1,40 @@ +--TEST-- +Vector map() +--FILE-- +count()); + foreach ($v as $i => $value) { + printf("Value #%d: %s\n", $i, json_encode($value)); + } +} +$create_array = fn($x) => [$x]; +dump_vector((new Vector())->map($create_array)); +dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->map($create_array)); +$identity = fn($x) => $x; +dump_vector((new Vector())->map($identity)); +dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->map($identity)); +$false = fn() => false; +dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->map($false)); +?> +--EXPECT-- +count=0 +count=5 +Value #0: ["TEST"] +Value #1: [true] +Value #2: [false] +Value #3: [{}] +Value #4: [[]] +count=0 +count=5 +Value #0: "TEST" +Value #1: true +Value #2: false +Value #3: {} +Value #4: [] +count=5 +Value #0: false +Value #1: false +Value #2: false +Value #3: false +Value #4: false \ No newline at end of file diff --git a/ext/spl/tests/Vector/offsetGet.phpt b/ext/spl/tests/Vector/offsetGet.phpt new file mode 100644 index 0000000000000..3250e2bd7e9f2 --- /dev/null +++ b/ext/spl/tests/Vector/offsetGet.phpt @@ -0,0 +1,80 @@ +--TEST-- +Vector offsetGet/get +--FILE-- +getMessage()); + } +} +expect_throws(fn() => (new ReflectionClass(Vector::class))->newInstanceWithoutConstructor()); +$vec = new Vector([new stdClass()]); +var_dump($vec->offsetGet(0)); +var_dump($vec->get(0)); +expect_throws(fn() => $vec->offsetSet(1,'x')); +expect_throws(fn() => $vec->offsetUnset(0)); +var_dump($vec->offsetGet('0')); +echo "offsetExists checks\n"; +var_dump($vec->offsetExists(1)); +var_dump($vec->offsetExists('1')); +var_dump($vec->offsetExists(PHP_INT_MAX)); +var_dump($vec->offsetExists(PHP_INT_MIN)); +expect_throws(fn() => $vec->get(1)); +expect_throws(fn() => $vec->get(-1)); +echo "Invalid offsetGet calls\n"; +expect_throws(fn() => $vec->offsetGet(PHP_INT_MAX)); +expect_throws(fn() => $vec->offsetGet(PHP_INT_MIN)); +expect_throws(fn() => $vec->offsetGet(1)); +expect_throws(fn() => $vec->get(PHP_INT_MAX)); +expect_throws(fn() => $vec->get(PHP_INT_MIN)); +expect_throws(fn() => $vec->get(1)); +expect_throws(fn() => $vec->get(-1)); +expect_throws(fn() => $vec->offsetGet(1)); +expect_throws(fn() => $vec->offsetGet('1')); +expect_throws(fn() => $vec->offsetGet('invalid')); +expect_throws(fn() => $vec->get('invalid')); +expect_throws(fn() => $vec[['invalid']]); +expect_throws(fn() => $vec->offsetUnset(PHP_INT_MAX)); +expect_throws(fn() => $vec->offsetSet(PHP_INT_MAX,'x')); +expect_throws(function () use ($vec) { unset($vec[0]); }); +var_dump($vec->getIterator()); +?> +--EXPECT-- +Caught ReflectionException: Class Vector is an internal class marked as final that cannot be instantiated without invoking its constructor +object(stdClass)#1 (0) { +} +object(stdClass)#1 (0) { +} +Caught OutOfBoundsException: Index out of range +Caught RuntimeException: Vector does not support offsetUnset - elements must be set to null or removed by resizing +object(stdClass)#1 (0) { +} +offsetExists checks +bool(false) +bool(false) +bool(false) +bool(false) +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Invalid offsetGet calls +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught TypeError: Illegal offset type +Caught TypeError: Vector::get(): Argument #1 ($offset) must be of type int, string given +Caught TypeError: Illegal offset type +Caught RuntimeException: Vector does not support offsetUnset - elements must be set to null or removed by resizing +Caught OutOfBoundsException: Index out of range +Caught RuntimeException: Vector does not support offsetUnset - elements must be set to null or removed by resizing +object(InternalIterator)#2 (0) { +} \ No newline at end of file diff --git a/ext/spl/tests/Vector/push_pop.phpt b/ext/spl/tests/Vector/push_pop.phpt new file mode 100644 index 0000000000000..f8abd46f33511 --- /dev/null +++ b/ext/spl/tests/Vector/push_pop.phpt @@ -0,0 +1,76 @@ +--TEST-- +Vector push(...$args)/pop +--FILE-- +getMessage()); + } +} + +echo "Test empty vector\n"; +$vec = new Vector([]); +printf("count=%d\n", $vec->count()); +expect_throws(fn() => $vec->pop()); +expect_throws(fn() => $vec->pop()); +$vec->push(strtoupper('test')); +$vec->push(['literal']); +$vec->push(new stdClass()); +$vec[] = strtoupper('test2'); +$vec[] = null; +echo json_encode($vec), "\n"; +printf("count=%d\n", count($vec)); +var_dump($vec->pop()); +var_dump($vec->pop()); +var_dump($vec->pop()); +var_dump($vec->pop()); +echo "After popping 4 elements: ", json_encode($vec->toArray()), "\n"; +var_dump($vec->pop()); +echo json_encode($vec), "\n"; +printf("count=%d\n", count($vec)); +echo "After shrinkToFit\n"; +$vec->shrinkToFit(); +echo json_encode($vec), "\n"; +printf("count=%d\n", count($vec)); +echo "After pushing variadic args\n"; +$vec->push(strtoupper('test'), 'other', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); +echo json_encode($vec), "\n"; +printf("count=%d\n", count($vec)); +$vec->push('a', 'b'); +echo json_encode($vec), "\n"; +// After pushing no args (like array_push, e.g. for variadic argument lists) +$vec->push(); +echo json_encode($vec), "\n"; + +?> +--EXPECT-- +Test empty vector +count=0 +Caught UnderflowException: Cannot pop from empty Vector +Caught UnderflowException: Cannot pop from empty Vector +["TEST",["literal"],{},"TEST2",null] +count=5 +NULL +string(5) "TEST2" +object(stdClass)#2 (0) { +} +array(1) { + [0]=> + string(7) "literal" +} +After popping 4 elements: ["TEST"] +string(4) "TEST" +[] +count=0 +After shrinkToFit +[] +count=0 +After pushing variadic args +["TEST","other",1,2,3,4,5,6,7,8,9,10] +count=12 +["TEST","other",1,2,3,4,5,6,7,8,9,10,"a","b"] +["TEST","other",1,2,3,4,5,6,7,8,9,10,"a","b"] \ No newline at end of file diff --git a/ext/spl/tests/Vector/reinit_forbidden.phpt b/ext/spl/tests/Vector/reinit_forbidden.phpt new file mode 100644 index 0000000000000..694dcf1f0fefe --- /dev/null +++ b/ext/spl/tests/Vector/reinit_forbidden.phpt @@ -0,0 +1,29 @@ +--TEST-- +Vector cannot be re-initialized +--FILE-- +__construct(['first']); + echo "Unexpectedly called constructor\n"; +} catch (Throwable $t) { + printf("Caught %s: %s\n", $t::class, $t->getMessage()); +} +var_dump($vec); +try { + $vec->__unserialize([new ArrayObject(), new stdClass()]); + echo "Unexpectedly called __unserialize\n"; +} catch (Throwable $t) { + printf("Caught %s: %s\n", $t::class, $t->getMessage()); +} +var_dump($vec); +?> +--EXPECT-- +Caught RuntimeException: Called Vector::__construct twice +object(Vector)#1 (0) { +} +Caught RuntimeException: Already unserialized +object(Vector)#1 (0) { +} \ No newline at end of file diff --git a/ext/spl/tests/Vector/serialization.phpt b/ext/spl/tests/Vector/serialization.phpt new file mode 100644 index 0000000000000..af300972901e9 --- /dev/null +++ b/ext/spl/tests/Vector/serialization.phpt @@ -0,0 +1,34 @@ +--TEST-- +Vector can be serialized and unserialized +--FILE-- +dynamicProp = 123; +} catch (Throwable $t) { + printf("Caught %s: %s\n", $t::class, $t->getMessage()); +} +$ser = serialize($vec); +echo $ser, "\n"; +foreach (unserialize($ser) as $key => $value) { + echo "Entry:\n"; + var_dump($key, $value); +} +var_dump($ser === serialize($vec)); +echo "Done\n"; +$x = 123; +$vec = new Vector([]); +var_dump($vec->__serialize()); +?> +--EXPECT-- +Caught Error: Cannot create dynamic property Vector::$dynamicProp +O:6:"Vector":1:{i:0;O:8:"stdClass":0:{}} +Entry: +int(0) +object(stdClass)#5 (0) { +} +bool(true) +Done +array(0) { +} \ No newline at end of file diff --git a/ext/spl/tests/Vector/setSize.phpt b/ext/spl/tests/Vector/setSize.phpt new file mode 100644 index 0000000000000..6b9e5e5341c8a --- /dev/null +++ b/ext/spl/tests/Vector/setSize.phpt @@ -0,0 +1,32 @@ +--TEST-- +Vector setSize +--FILE-- +count()); + var_dump($vec->toArray()); +} + +$vec = new Vector(); +show($vec); +$vec->setSize(2); +$vec[0] = new stdClass(); +show($vec); +$vec->setSize(0); +show($vec); +?> +--EXPECT-- +count=0 +array(0) { +} +count=2 +array(2) { + [0]=> + object(stdClass)#2 (0) { + } + [1]=> + NULL +} +count=0 +array(0) { +} diff --git a/ext/spl/tests/Vector/setSize_default.phpt b/ext/spl/tests/Vector/setSize_default.phpt new file mode 100644 index 0000000000000..30dcb04f6195c --- /dev/null +++ b/ext/spl/tests/Vector/setSize_default.phpt @@ -0,0 +1,24 @@ +--TEST-- +Vector setSize with $default +--FILE-- +count()); +} + +$vec = new Vector(); +show($vec); +$vec->setSize(2, 0); +$vec[0] = new stdClass(); +$vec->setSize(3, null); +show($vec); +$vec->setSize(0, new stdClass()); +show($vec); +$vec->setSize(5, strtoupper('na')); +show($vec); +?> +--EXPECT-- +value=[] count=0 +value=[{},0,null] count=3 +value=[] count=0 +value=["NA","NA","NA","NA","NA"] count=5 diff --git a/ext/spl/tests/Vector/setValueAt.phpt b/ext/spl/tests/Vector/setValueAt.phpt new file mode 100644 index 0000000000000..9c62a5608594d --- /dev/null +++ b/ext/spl/tests/Vector/setValueAt.phpt @@ -0,0 +1,39 @@ +--TEST-- +Vector offsetSet/set +--FILE-- +getMessage()); + } +} + +echo "Test empty vector\n"; +$vec = new Vector([]); +expect_throws(fn() => $vec->offsetSet(0, strtoupper('value'))); +expect_throws(fn() => $vec->set(0, strtoupper('value'))); + +echo "Test short vector\n"; +$str = 'Test short vector'; +$vec = new Vector(explode(' ', $str)); +$vec->set(0, 'new'); +$vec->offsetSet(2, strtoupper('test')); +echo json_encode($vec), "\n"; +expect_throws(fn() => $vec->set(-1, strtoupper('value'))); +expect_throws(fn() => $vec->set(3, 'end')); +expect_throws(fn() => $vec->set(PHP_INT_MAX, 'end')); + +?> +--EXPECT-- +Test empty vector +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Test short vector +["new","short","TEST"] +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range +Caught OutOfBoundsException: Index out of range \ No newline at end of file diff --git a/ext/spl/tests/Vector/set_state.phpt b/ext/spl/tests/Vector/set_state.phpt new file mode 100644 index 0000000000000..7150edb2a4b97 --- /dev/null +++ b/ext/spl/tests/Vector/set_state.phpt @@ -0,0 +1,83 @@ +--TEST-- +Vector::__set_state +--FILE-- + 'x']); +$vec = Vector::__set_state([strtoupper('a literal'), ['first', 'x'], [(object)['key' => 'value'], null]]); +foreach ($vec as $key => $value) { + printf("key=%s value=%s\n", json_encode($key), json_encode($value)); +} +dump_repr($vec); +var_dump($vec); +var_dump((array)$vec); + +?> +--EXPECT-- +Vector::__set_state(array( +)) +key=0 value="A LITERAL" +key=1 value=["first","x"] +key=2 value=[{"key":"value"},null] +Vector::__set_state(array( + 0 => 'A LITERAL', + 1 => + array ( + 0 => 'first', + 1 => 'x', + ), + 2 => + array ( + 0 => + (object) array( + 'key' => 'value', + ), + 1 => NULL, + ), +)) +object(Vector)#2 (3) { + [0]=> + string(9) "A LITERAL" + [1]=> + array(2) { + [0]=> + string(5) "first" + [1]=> + string(1) "x" + } + [2]=> + array(2) { + [0]=> + object(stdClass)#1 (1) { + ["key"]=> + string(5) "value" + } + [1]=> + NULL + } +} +array(3) { + [0]=> + string(9) "A LITERAL" + [1]=> + array(2) { + [0]=> + string(5) "first" + [1]=> + string(1) "x" + } + [2]=> + array(2) { + [0]=> + object(stdClass)#1 (1) { + ["key"]=> + string(5) "value" + } + [1]=> + NULL + } +} \ No newline at end of file diff --git a/ext/spl/tests/Vector/shrink_capacity.phpt b/ext/spl/tests/Vector/shrink_capacity.phpt new file mode 100644 index 0000000000000..4d2cb86f0b499 --- /dev/null +++ b/ext/spl/tests/Vector/shrink_capacity.phpt @@ -0,0 +1,40 @@ +--TEST-- +Vector pop() reduces count +--FILE-- + 0) { + var_dump($vec->pop()); + printf("new count=%d\n", count($vec)); +} +$vec->shrinkToFit(); +printf("shrinkToFit\n"); +printf("shrinkToFit\n"); +$vec->push(new stdClass()); +printf("it=%s count=%d\n", json_encode($vec), count($vec)); +$vec->shrinkToFit(); +printf("it=%s count=%d\n", json_encode($vec), count($vec)); +?> +--EXPECT-- +string(4) "test" +new count=7 +string(4) "test" +new count=6 +string(4) "test" +new count=5 +string(4) "test" +new count=4 +string(4) "test" +new count=3 +string(4) "test" +new count=2 +string(3) "123" +new count=1 +string(4) "test" +new count=0 +shrinkToFit +shrinkToFit +it=[{}] count=1 +it=[{}] count=1 \ No newline at end of file diff --git a/ext/spl/tests/Vector/toArray.phpt b/ext/spl/tests/Vector/toArray.phpt new file mode 100644 index 0000000000000..392ca6cdbb04d --- /dev/null +++ b/ext/spl/tests/Vector/toArray.phpt @@ -0,0 +1,51 @@ +--TEST-- +Vector toArray() +--FILE-- +toArray()); +var_dump($vec->toArray()); +$vec = new Vector([]); +var_dump($vec->toArray()); +var_dump($vec->toArray()); +$vec->setSize(2, strtoupper('test')); +var_dump($vec->toArray()); +var_dump($vec->toArray()); +var_dump(Vector::__set_state([])->toArray()); +var_dump(Vector::__set_state([(object)[]])->toArray()); +?> +--EXPECT-- +array(1) { + [0]=> + object(stdClass)#2 (0) { + } +} +array(1) { + [0]=> + object(stdClass)#2 (0) { + } +} +array(0) { +} +array(0) { +} +array(2) { + [0]=> + string(4) "TEST" + [1]=> + string(4) "TEST" +} +array(2) { + [0]=> + string(4) "TEST" + [1]=> + string(4) "TEST" +} +array(0) { +} +array(1) { + [0]=> + object(stdClass)#1 (0) { + } +} \ No newline at end of file diff --git a/ext/spl/tests/Vector/traversable.phpt b/ext/spl/tests/Vector/traversable.phpt new file mode 100644 index 0000000000000..7fa78e66fabe4 --- /dev/null +++ b/ext/spl/tests/Vector/traversable.phpt @@ -0,0 +1,101 @@ +--TEST-- +Vector constructed from Traversable +--FILE-- + "s$i"; + } + $o = (object)['key' => 'value']; + yield $o => $o; + yield 0 => 1; + yield 0 => 2; + echo "Done evaluating the generator\n"; +} + +// Vector eagerly evaluates the passed in Traversable +$vec = new Vector(yields_values()); +foreach ($vec as $key => $value) { + printf("Key: %s\nValue: %s\n", var_export($key, true), var_export($value, true)); +} +echo "Rewind and iterate again starting from r0\n"; +foreach ($vec as $key => $value) { + printf("Key: %s\nValue: %s\n", var_export($key, true), var_export($value, true)); +} +unset($vec); + +$emptyIt = new Vector(new ArrayObject()); +var_dump($emptyIt); +foreach ($emptyIt as $key => $value) { + echo "Unreachable\n"; +} +foreach ($emptyIt as $key => $value) { + echo "Unreachable\n"; +} +echo "Done\n"; +unset($emptyIt); + +?> +--EXPECT-- +Done evaluating the generator +Key: 0 +Value: 's0' +Key: 1 +Value: 's1' +Key: 2 +Value: 's2' +Key: 3 +Value: 's3' +Key: 4 +Value: 's4' +Key: 5 +Value: 's5' +Key: 6 +Value: 's6' +Key: 7 +Value: 's7' +Key: 8 +Value: 's8' +Key: 9 +Value: 's9' +Key: 10 +Value: (object) array( + 'key' => 'value', +) +Key: 11 +Value: 1 +Key: 12 +Value: 2 +Rewind and iterate again starting from r0 +Key: 0 +Value: 's0' +Key: 1 +Value: 's1' +Key: 2 +Value: 's2' +Key: 3 +Value: 's3' +Key: 4 +Value: 's4' +Key: 5 +Value: 's5' +Key: 6 +Value: 's6' +Key: 7 +Value: 's7' +Key: 8 +Value: 's8' +Key: 9 +Value: 's9' +Key: 10 +Value: (object) array( + 'key' => 'value', +) +Key: 11 +Value: 1 +Key: 12 +Value: 2 +object(Vector)#1 (0) { +} +Done \ No newline at end of file diff --git a/ext/spl/tests/Vector/unserialize.phpt b/ext/spl/tests/Vector/unserialize.phpt new file mode 100644 index 0000000000000..b5bac18343ea4 --- /dev/null +++ b/ext/spl/tests/Vector/unserialize.phpt @@ -0,0 +1,16 @@ +--TEST-- +Vector unserialize error handling +--FILE-- +getMessage()); + } +}); +?> +--EXPECT-- +Caught UnexpectedValueException: Vector::__unserialize saw unexpected string key, expected sequence of values