xref: /PHP-8.3/ext/spl/spl_fixedarray.c (revision 8494058a)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Antony Dovgal <tony@daylessday.org>                          |
14   |         Etienne Kneuss <colder@php.net>                              |
15   +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include "php.h"
23 #include "php_ini.h"
24 #include "ext/standard/info.h"
25 #include "zend_exceptions.h"
26 
27 #include "php_spl.h"
28 #include "spl_fixedarray_arginfo.h"
29 #include "spl_functions.h"
30 #include "spl_engine.h"
31 #include "spl_fixedarray.h"
32 #include "spl_exceptions.h"
33 #include "spl_iterators.h"
34 #include "ext/json/php_json.h"
35 
36 zend_object_handlers spl_handler_SplFixedArray;
37 PHPAPI zend_class_entry *spl_ce_SplFixedArray;
38 
39 #ifdef COMPILE_DL_SPL_FIXEDARRAY
40 ZEND_GET_MODULE(spl_fixedarray)
41 #endif
42 
43 /* Check if the object is an instance of a subclass of SplFixedArray that overrides method's implementation.
44  * Expect subclassing SplFixedArray to be rare and check that first. */
45 #define HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, method) UNEXPECTED((object)->ce != spl_ce_SplFixedArray && (object)->ce->arrayaccess_funcs_ptr->method->common.scope != spl_ce_SplFixedArray)
46 
47 typedef struct _spl_fixedarray {
48 	zend_long size;
49 	/* It is possible to resize this, so this can't be combined with the object */
50 	zval *elements;
51 	/* If positive, it's a resize within a resize and the value gives the desired size. If -1, it's not. */
52 	zend_long cached_resize;
53 } spl_fixedarray;
54 
55 typedef struct _spl_fixedarray_object {
56 	spl_fixedarray          array;
57 	zend_function          *fptr_count;
58 	zend_object             std;
59 } spl_fixedarray_object;
60 
61 typedef struct _spl_fixedarray_it {
62 	zend_object_iterator intern;
63 	zend_long            current;
64 } spl_fixedarray_it;
65 
spl_fixed_array_from_obj(zend_object * obj)66 static spl_fixedarray_object *spl_fixed_array_from_obj(zend_object *obj)
67 {
68 	return (spl_fixedarray_object*)((char*)(obj) - XtOffsetOf(spl_fixedarray_object, std));
69 }
70 
71 #define Z_SPLFIXEDARRAY_P(zv)  spl_fixed_array_from_obj(Z_OBJ_P((zv)))
72 
73 /* Helps enforce the invariants in debug mode:
74  *   - if size == 0, then elements == NULL
75  *   - if size > 0, then elements != NULL
76  *   - size is not less than 0
77  */
spl_fixedarray_empty(spl_fixedarray * array)78 static bool spl_fixedarray_empty(spl_fixedarray *array)
79 {
80 	if (array->elements) {
81 		ZEND_ASSERT(array->size > 0);
82 		return false;
83 	}
84 	ZEND_ASSERT(array->size == 0);
85 	return true;
86 }
87 
spl_fixedarray_default_ctor(spl_fixedarray * array)88 static void spl_fixedarray_default_ctor(spl_fixedarray *array)
89 {
90 	array->size = 0;
91 	array->elements = NULL;
92 	array->cached_resize = -1;
93 }
94 
95 /* Initializes the range [from, to) to null. Does not dtor existing elements. */
spl_fixedarray_init_elems(spl_fixedarray * array,zend_long from,zend_long to)96 static void spl_fixedarray_init_elems(spl_fixedarray *array, zend_long from, zend_long to)
97 {
98 	ZEND_ASSERT(from <= to);
99 	zval *begin = array->elements + from, *end = array->elements + to;
100 
101 	while (begin != end) {
102 		ZVAL_NULL(begin++);
103 	}
104 }
105 
spl_fixedarray_init_non_empty_struct(spl_fixedarray * array,zend_long size)106 static void spl_fixedarray_init_non_empty_struct(spl_fixedarray *array, zend_long size)
107 {
108 	array->size = 0; /* reset size in case ecalloc() fails */
109 	array->elements = size ? safe_emalloc(size, sizeof(zval), 0) : NULL;
110 	array->size = size;
111 	array->cached_resize = -1;
112 }
113 
spl_fixedarray_init(spl_fixedarray * array,zend_long size)114 static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
115 {
116 	if (size > 0) {
117 		spl_fixedarray_init_non_empty_struct(array, size);
118 		spl_fixedarray_init_elems(array, 0, size);
119 	} else {
120 		spl_fixedarray_default_ctor(array);
121 	}
122 }
123 
124 /* Copies the range [begin, end) into the fixedarray, beginning at `offset`.
125  * Does not dtor the existing elements.
126  */
spl_fixedarray_copy_range(spl_fixedarray * array,zend_long offset,zval * begin,zval * end)127 static void spl_fixedarray_copy_range(spl_fixedarray *array, zend_long offset, zval *begin, zval *end)
128 {
129 	ZEND_ASSERT(offset >= 0);
130 	ZEND_ASSERT(array->size - offset >= end - begin);
131 
132 	zval *to = &array->elements[offset];
133 	while (begin != end) {
134 		ZVAL_COPY(to++, begin++);
135 	}
136 }
137 
spl_fixedarray_copy_ctor(spl_fixedarray * to,spl_fixedarray * from)138 static void spl_fixedarray_copy_ctor(spl_fixedarray *to, spl_fixedarray *from)
139 {
140 	zend_long size = from->size;
141 	spl_fixedarray_init(to, size);
142 	if (size != 0) {
143 		zval *begin = from->elements, *end = from->elements + size;
144 		spl_fixedarray_copy_range(to, 0, begin, end);
145 	}
146 }
147 
148 /* Destructs the elements in the range [from, to).
149  * Caller is expected to bounds check.
150  */
spl_fixedarray_dtor_range(spl_fixedarray * array,zend_long from,zend_long to)151 static void spl_fixedarray_dtor_range(spl_fixedarray *array, zend_long from, zend_long to)
152 {
153 	array->size = from;
154 	zval *begin = array->elements + from, *end = array->elements + to;
155 	while (begin != end) {
156 		zval_ptr_dtor(begin++);
157 	}
158 }
159 
160 /* Destructs and frees contents but not the array itself.
161  * If you want to re-use the array then you need to re-initialize it.
162  */
spl_fixedarray_dtor(spl_fixedarray * array)163 static void spl_fixedarray_dtor(spl_fixedarray *array)
164 {
165 	if (!spl_fixedarray_empty(array)) {
166 		zval *begin = array->elements, *end = array->elements + array->size;
167 		array->elements = NULL;
168 		array->size = 0;
169 		while (begin != end) {
170 			zval_ptr_dtor(--end);
171 		}
172 		efree(begin);
173 	}
174 }
175 
spl_fixedarray_resize(spl_fixedarray * array,zend_long size)176 static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
177 {
178 	if (size == array->size) {
179 		/* nothing to do */
180 		return;
181 	}
182 
183 	/* first initialization */
184 	if (array->size == 0) {
185 		spl_fixedarray_init(array, size);
186 		return;
187 	}
188 
189 	if (UNEXPECTED(array->cached_resize >= 0)) {
190 		/* We're already resizing, so just remember the desired size.
191 		 * The resize will happen later. */
192 		array->cached_resize = size;
193 		return;
194 	}
195 	array->cached_resize = size;
196 
197 	/* clearing the array */
198 	if (size == 0) {
199 		spl_fixedarray_dtor(array);
200 		array->elements = NULL;
201 		array->size = 0;
202 	} else if (size > array->size) {
203 		array->elements = safe_erealloc(array->elements, size, sizeof(zval), 0);
204 		spl_fixedarray_init_elems(array, array->size, size);
205 		array->size = size;
206 	} else { /* size < array->size */
207 		/* Size set in spl_fixedarray_dtor_range() */
208 		spl_fixedarray_dtor_range(array, size, array->size);
209 		array->elements = erealloc(array->elements, sizeof(zval) * size);
210 	}
211 
212 	/* If resized within the destructor, take the last resize command and perform it */
213 	zend_long cached_resize = array->cached_resize;
214 	array->cached_resize = -1;
215 	if (cached_resize != size) {
216 		spl_fixedarray_resize(array, cached_resize);
217 	}
218 }
219 
spl_fixedarray_object_get_gc(zend_object * obj,zval ** table,int * n)220 static HashTable* spl_fixedarray_object_get_gc(zend_object *obj, zval **table, int *n)
221 {
222 	spl_fixedarray_object *intern = spl_fixed_array_from_obj(obj);
223 	HashTable *ht = zend_std_get_properties(obj);
224 
225 	*table = intern->array.elements;
226 	*n = (int)intern->array.size;
227 
228 	return ht;
229 }
230 
spl_fixedarray_object_get_properties_for(zend_object * obj,zend_prop_purpose purpose)231 static HashTable* spl_fixedarray_object_get_properties_for(zend_object *obj, zend_prop_purpose purpose)
232 {
233 	/* This has __serialize, so the purpose is not ZEND_PROP_PURPOSE_SERIALIZE, which would expect a non-null return value */
234 	ZEND_ASSERT(purpose != ZEND_PROP_PURPOSE_SERIALIZE);
235 
236 	const spl_fixedarray_object *intern = spl_fixed_array_from_obj(obj);
237 	/*
238 	 * SplFixedArray can be subclassed or have dynamic properties (With or without AllowDynamicProperties in subclasses).
239 	 * Instances of subclasses with declared properties may have properties but not yet have a property table.
240 	 */
241 	HashTable *source_properties = obj->properties ? obj->properties : (obj->ce->default_properties_count ? zend_std_get_properties(obj) : NULL);
242 
243 	const zend_long size = intern->array.size;
244 	if (size == 0 && (!source_properties || !zend_hash_num_elements(source_properties))) {
245 		return NULL;
246 	}
247 	zval *const elements = intern->array.elements;
248 	HashTable *ht = zend_new_array(size);
249 
250 	for (zend_long i = 0; i < size; i++) {
251 		Z_TRY_ADDREF_P(&elements[i]);
252 		zend_hash_next_index_insert(ht, &elements[i]);
253 	}
254 	if (source_properties && zend_hash_num_elements(source_properties) > 0) {
255 		zend_long nkey;
256 		zend_string *skey;
257 		zval *value;
258 		ZEND_HASH_MAP_FOREACH_KEY_VAL_IND(source_properties, nkey, skey, value) {
259 			Z_TRY_ADDREF_P(value);
260 			if (skey) {
261 				zend_hash_add_new(ht, skey, value);
262 			} else {
263 				zend_hash_index_update(ht, nkey, value);
264 			}
265 		} ZEND_HASH_FOREACH_END();
266 	}
267 
268 	return ht;
269 }
270 
spl_fixedarray_object_free_storage(zend_object * object)271 static void spl_fixedarray_object_free_storage(zend_object *object)
272 {
273 	spl_fixedarray_object *intern = spl_fixed_array_from_obj(object);
274 	spl_fixedarray_dtor(&intern->array);
275 	zend_object_std_dtor(&intern->std);
276 }
277 
spl_fixedarray_object_new_ex(zend_class_entry * class_type,zend_object * orig,bool clone_orig)278 static zend_object *spl_fixedarray_object_new_ex(zend_class_entry *class_type, zend_object *orig, bool clone_orig)
279 {
280 	spl_fixedarray_object *intern;
281 	zend_class_entry      *parent = class_type;
282 	bool                   inherited = false;
283 
284 	intern = zend_object_alloc(sizeof(spl_fixedarray_object), parent);
285 
286 	zend_object_std_init(&intern->std, class_type);
287 	object_properties_init(&intern->std, class_type);
288 
289 	if (orig && clone_orig) {
290 		spl_fixedarray_object *other = spl_fixed_array_from_obj(orig);
291 		spl_fixedarray_copy_ctor(&intern->array, &other->array);
292 	}
293 
294 	while (parent) {
295 		if (parent == spl_ce_SplFixedArray) {
296 			break;
297 		}
298 
299 		parent = parent->parent;
300 		inherited = true;
301 	}
302 
303 	ZEND_ASSERT(parent);
304 
305 	if (UNEXPECTED(inherited)) {
306 		/* Find count() method */
307 		zend_function *fptr_count = zend_hash_find_ptr(&class_type->function_table, ZSTR_KNOWN(ZEND_STR_COUNT));
308 		if (fptr_count->common.scope == parent) {
309 			fptr_count = NULL;
310 		}
311 		intern->fptr_count = fptr_count;
312 	}
313 
314 	return &intern->std;
315 }
316 
spl_fixedarray_new(zend_class_entry * class_type)317 static zend_object *spl_fixedarray_new(zend_class_entry *class_type)
318 {
319 	return spl_fixedarray_object_new_ex(class_type, NULL, 0);
320 }
321 
spl_fixedarray_object_clone(zend_object * old_object)322 static zend_object *spl_fixedarray_object_clone(zend_object *old_object)
323 {
324 	zend_object *new_object = spl_fixedarray_object_new_ex(old_object->ce, old_object, 1);
325 
326 	zend_objects_clone_members(new_object, old_object);
327 
328 	return new_object;
329 }
330 
spl_offset_convert_to_long(zval * offset)331 static zend_long spl_offset_convert_to_long(zval *offset) /* {{{ */
332 {
333 	try_again:
334 	switch (Z_TYPE_P(offset)) {
335 		case IS_STRING: {
336 			zend_ulong index;
337 			if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), index)) {
338 				return (zend_long) index;
339 			}
340 			break;
341 		}
342 		case IS_DOUBLE:
343 			return zend_dval_to_lval_safe(Z_DVAL_P(offset));
344 		case IS_LONG:
345 			return Z_LVAL_P(offset);
346 		case IS_FALSE:
347 			return 0;
348 		case IS_TRUE:
349 			return 1;
350 		case IS_REFERENCE:
351 			offset = Z_REFVAL_P(offset);
352 			goto try_again;
353 		case IS_RESOURCE:
354 			zend_use_resource_as_offset(offset);
355 			return Z_RES_HANDLE_P(offset);
356 	}
357 
358 	/* Use SplFixedArray name from the CE */
359 	zend_illegal_container_offset(spl_ce_SplFixedArray->name, offset, BP_VAR_R);
360 	return 0;
361 }
362 
spl_fixedarray_object_read_dimension_helper(spl_fixedarray_object * intern,zval * offset)363 static zval *spl_fixedarray_object_read_dimension_helper(spl_fixedarray_object *intern, zval *offset)
364 {
365 	zend_long index;
366 
367 	/* we have to return NULL on error here to avoid memleak because of
368 	 * ZE duplicating uninitialized_zval_ptr */
369 	if (!offset) {
370 		zend_throw_error(NULL, "[] operator not supported for SplFixedArray");
371 		return NULL;
372 	}
373 
374 	index = spl_offset_convert_to_long(offset);
375 	if (EG(exception)) {
376 		return NULL;
377 	}
378 
379 	if (index < 0 || index >= intern->array.size) {
380 		// TODO Change error message and use OutOfBound SPL Exception?
381 		zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0);
382 		return NULL;
383 	} else {
384 		return &intern->array.elements[index];
385 	}
386 }
387 
388 static int spl_fixedarray_object_has_dimension(zend_object *object, zval *offset, int check_empty);
389 
spl_fixedarray_object_read_dimension(zend_object * object,zval * offset,int type,zval * rv)390 static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
391 {
392 	if (type == BP_VAR_IS && !spl_fixedarray_object_has_dimension(object, offset, 0)) {
393 		return &EG(uninitialized_zval);
394 	}
395 
396 	if (HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, zf_offsetget)) {
397 		zval tmp;
398 		if (!offset) {
399 			ZVAL_NULL(&tmp);
400 			offset = &tmp;
401 		}
402 		zend_call_known_instance_method_with_1_params(object->ce->arrayaccess_funcs_ptr->zf_offsetget, object, rv, offset);
403 		if (!Z_ISUNDEF_P(rv)) {
404 			return rv;
405 		}
406 		return &EG(uninitialized_zval);
407 	}
408 
409 	spl_fixedarray_object *intern = spl_fixed_array_from_obj(object);
410 	return spl_fixedarray_object_read_dimension_helper(intern, offset);
411 }
412 
spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object * intern,zval * offset,zval * value)413 static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *intern, zval *offset, zval *value)
414 {
415 	zend_long index;
416 
417 	if (!offset) {
418 		/* '$array[] = value' syntax is not supported */
419 		zend_throw_error(NULL, "[] operator not supported for SplFixedArray");
420 		return;
421 	}
422 
423 	index = spl_offset_convert_to_long(offset);
424 	if (EG(exception)) {
425 		return;
426 	}
427 
428 	if (index < 0 || index >= intern->array.size) {
429 		// TODO Change error message and use OutOfBound SPL Exception?
430 		zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0);
431 		return;
432 	} else {
433 		/* Fix #81429 */
434 		zval *ptr = &(intern->array.elements[index]);
435 		zval tmp;
436 		ZVAL_COPY_VALUE(&tmp, ptr);
437 		ZVAL_COPY_DEREF(ptr, value);
438 		zval_ptr_dtor(&tmp);
439 	}
440 }
441 
spl_fixedarray_object_write_dimension(zend_object * object,zval * offset,zval * value)442 static void spl_fixedarray_object_write_dimension(zend_object *object, zval *offset, zval *value)
443 {
444 	if (HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, zf_offsetset)) {
445 		zval tmp;
446 
447 		if (!offset) {
448 			ZVAL_NULL(&tmp);
449 			offset = &tmp;
450 		}
451 		zend_call_known_instance_method_with_2_params(object->ce->arrayaccess_funcs_ptr->zf_offsetset, object, NULL, offset, value);
452 		return;
453 	}
454 
455 	spl_fixedarray_object *intern = spl_fixed_array_from_obj(object);
456 	spl_fixedarray_object_write_dimension_helper(intern, offset, value);
457 }
458 
spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object * intern,zval * offset)459 static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *intern, zval *offset)
460 {
461 	zend_long index;
462 
463 	index = spl_offset_convert_to_long(offset);
464 	if (EG(exception)) {
465 		return;
466 	}
467 
468 	if (index < 0 || index >= intern->array.size) {
469 		// TODO Change error message and use OutOfBound SPL Exception?
470 		zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0);
471 		return;
472 	} else {
473 		zval_ptr_dtor(&(intern->array.elements[index]));
474 		ZVAL_NULL(&intern->array.elements[index]);
475 	}
476 }
477 
spl_fixedarray_object_unset_dimension(zend_object * object,zval * offset)478 static void spl_fixedarray_object_unset_dimension(zend_object *object, zval *offset)
479 {
480 	if (UNEXPECTED(HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, zf_offsetunset))) {
481 		zend_call_known_instance_method_with_1_params(object->ce->arrayaccess_funcs_ptr->zf_offsetunset, object, NULL, offset);
482 		return;
483 	}
484 
485 	spl_fixedarray_object *intern = spl_fixed_array_from_obj(object);
486 	spl_fixedarray_object_unset_dimension_helper(intern, offset);
487 }
488 
spl_fixedarray_object_has_dimension_helper(spl_fixedarray_object * intern,zval * offset,bool check_empty)489 static bool spl_fixedarray_object_has_dimension_helper(spl_fixedarray_object *intern, zval *offset, bool check_empty)
490 {
491 	zend_long index;
492 
493 	index = spl_offset_convert_to_long(offset);
494 	if (EG(exception)) {
495 		return false;
496 	}
497 
498 	if (index < 0 || index >= intern->array.size) {
499 		return false;
500 	}
501 
502 	if (check_empty) {
503 		return zend_is_true(&intern->array.elements[index]);
504 	}
505 
506 	return Z_TYPE(intern->array.elements[index]) != IS_NULL;
507 }
508 
spl_fixedarray_object_has_dimension(zend_object * object,zval * offset,int check_empty)509 static int spl_fixedarray_object_has_dimension(zend_object *object, zval *offset, int check_empty)
510 {
511 	if (HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, zf_offsetexists)) {
512 		zval rv;
513 
514 		zend_call_known_instance_method_with_1_params(object->ce->arrayaccess_funcs_ptr->zf_offsetexists, object, &rv, offset);
515 		bool result = zend_is_true(&rv);
516 		zval_ptr_dtor(&rv);
517 		return result;
518 	}
519 
520 	spl_fixedarray_object *intern = spl_fixed_array_from_obj(object);
521 
522 	return spl_fixedarray_object_has_dimension_helper(intern, offset, check_empty);
523 }
524 
spl_fixedarray_object_count_elements(zend_object * object,zend_long * count)525 static zend_result spl_fixedarray_object_count_elements(zend_object *object, zend_long *count)
526 {
527 	spl_fixedarray_object *intern;
528 
529 	intern = spl_fixed_array_from_obj(object);
530 	if (UNEXPECTED(intern->fptr_count)) {
531 		zval rv;
532 		zend_call_known_instance_method_with_0_params(intern->fptr_count, object, &rv);
533 		if (!Z_ISUNDEF(rv)) {
534 			*count = zval_get_long(&rv);
535 			zval_ptr_dtor(&rv);
536 		} else {
537 			*count = 0;
538 		}
539 	} else {
540 		*count = intern->array.size;
541 	}
542 	return SUCCESS;
543 }
544 
PHP_METHOD(SplFixedArray,__construct)545 PHP_METHOD(SplFixedArray, __construct)
546 {
547 	zval *object = ZEND_THIS;
548 	spl_fixedarray_object *intern;
549 	zend_long size = 0;
550 
551 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &size) == FAILURE) {
552 		RETURN_THROWS();
553 	}
554 
555 	if (size < 0) {
556 		zend_argument_value_error(1, "must be greater than or equal to 0");
557 		RETURN_THROWS();
558 	}
559 
560 	intern = Z_SPLFIXEDARRAY_P(object);
561 
562 	if (!spl_fixedarray_empty(&intern->array)) {
563 		/* called __construct() twice, bail out */
564 		return;
565 	}
566 
567 	spl_fixedarray_init(&intern->array, size);
568 }
569 
PHP_METHOD(SplFixedArray,__wakeup)570 PHP_METHOD(SplFixedArray, __wakeup)
571 {
572 	spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
573 	HashTable *intern_ht = zend_std_get_properties(Z_OBJ_P(ZEND_THIS));
574 	zval *data;
575 
576 	if (zend_parse_parameters_none() == FAILURE) {
577 		RETURN_THROWS();
578 	}
579 
580 	if (intern->array.size == 0) {
581 		int index = 0;
582 		int size = zend_hash_num_elements(intern_ht);
583 
584 		spl_fixedarray_init(&intern->array, size);
585 
586 		ZEND_HASH_FOREACH_VAL(intern_ht, data) {
587 			ZVAL_COPY(&intern->array.elements[index], data);
588 			index++;
589 		} ZEND_HASH_FOREACH_END();
590 
591 		/* Remove the unserialised properties, since we now have the elements
592 		 * within the spl_fixedarray_object structure. */
593 		zend_hash_clean(intern_ht);
594 	}
595 }
596 
PHP_METHOD(SplFixedArray,__serialize)597 PHP_METHOD(SplFixedArray, __serialize)
598 {
599 	spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
600 	zval *current;
601 	zend_string *key;
602 
603 	if (zend_parse_parameters_none() == FAILURE) {
604 		RETURN_THROWS();
605 	}
606 
607 	HashTable *ht = zend_std_get_properties(&intern->std);
608 	uint32_t num_properties = zend_hash_num_elements(ht);
609 	array_init_size(return_value, intern->array.size + num_properties);
610 
611 	/* elements */
612 	for (zend_long i = 0; i < intern->array.size; i++) {
613 		current = &intern->array.elements[i];
614 		zend_hash_next_index_insert(Z_ARRVAL_P(return_value), current);
615 		Z_TRY_ADDREF_P(current);
616 	}
617 
618 	/* members */
619 	ZEND_HASH_FOREACH_STR_KEY_VAL_IND(ht, key, current) {
620 		/* If the properties table was already rebuild, it will also contain the
621 		 * array elements. The array elements are already added in the above loop.
622 		 * We can detect array elements by the fact that their key == NULL. */
623 		if (key != NULL) {
624 			zend_hash_add_new(Z_ARRVAL_P(return_value), key, current);
625 			Z_TRY_ADDREF_P(current);
626 		}
627 	} ZEND_HASH_FOREACH_END();
628 }
629 
PHP_METHOD(SplFixedArray,__unserialize)630 PHP_METHOD(SplFixedArray, __unserialize)
631 {
632 	spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
633 	HashTable *data;
634 	zval members_zv, *elem;
635 	zend_string *key;
636 	zend_long size;
637 
638 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
639 		RETURN_THROWS();
640 	}
641 
642 	if (intern->array.size == 0) {
643 		size = zend_hash_num_elements(data);
644 		spl_fixedarray_init_non_empty_struct(&intern->array, size);
645 		if (!size) {
646 			return;
647 		}
648 		array_init(&members_zv);
649 
650 		intern->array.size = 0;
651 		ZEND_HASH_FOREACH_STR_KEY_VAL(data, key, elem) {
652 			if (key == NULL) {
653 				ZVAL_COPY(&intern->array.elements[intern->array.size], elem);
654 				intern->array.size++;
655 			} else {
656 				Z_TRY_ADDREF_P(elem);
657 				zend_hash_add(Z_ARRVAL(members_zv), key, elem);
658 			}
659 		} ZEND_HASH_FOREACH_END();
660 
661 		if (intern->array.size != size) {
662 			if (intern->array.size) {
663 				intern->array.elements = erealloc(intern->array.elements, sizeof(zval) * intern->array.size);
664 			} else {
665 				efree(intern->array.elements);
666 				intern->array.elements = NULL;
667 			}
668 		}
669 
670 		object_properties_load(&intern->std, Z_ARRVAL(members_zv));
671 		zval_ptr_dtor(&members_zv);
672 	}
673 }
674 
PHP_METHOD(SplFixedArray,count)675 PHP_METHOD(SplFixedArray, count)
676 {
677 	zval *object = ZEND_THIS;
678 	spl_fixedarray_object *intern;
679 
680 	if (zend_parse_parameters_none() == FAILURE) {
681 		RETURN_THROWS();
682 	}
683 
684 	intern = Z_SPLFIXEDARRAY_P(object);
685 	RETURN_LONG(intern->array.size);
686 }
687 
PHP_METHOD(SplFixedArray,toArray)688 PHP_METHOD(SplFixedArray, toArray)
689 {
690 	spl_fixedarray_object *intern;
691 
692 	if (zend_parse_parameters_none() == FAILURE) {
693 		RETURN_THROWS();
694 	}
695 
696 	intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
697 
698 	if (!spl_fixedarray_empty(&intern->array)) {
699 		array_init(return_value);
700 		for (zend_long i = 0; i < intern->array.size; i++) {
701 			zend_hash_index_update(Z_ARRVAL_P(return_value), i, &intern->array.elements[i]);
702 			Z_TRY_ADDREF(intern->array.elements[i]);
703 		}
704 	} else {
705 		RETURN_EMPTY_ARRAY();
706 	}
707 }
708 
PHP_METHOD(SplFixedArray,fromArray)709 PHP_METHOD(SplFixedArray, fromArray)
710 {
711 	zval *data;
712 	spl_fixedarray array;
713 	spl_fixedarray_object *intern;
714 	int num;
715 	bool save_indexes = 1;
716 
717 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|b", &data, &save_indexes) == FAILURE) {
718 		RETURN_THROWS();
719 	}
720 
721 	num = zend_hash_num_elements(Z_ARRVAL_P(data));
722 
723 	if (num > 0 && save_indexes) {
724 		zval *element;
725 		zend_string *str_index;
726 		zend_ulong num_index, max_index = 0;
727 		zend_long tmp;
728 
729 		ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(data), num_index, str_index) {
730 			if (str_index != NULL || (zend_long)num_index < 0) {
731 				zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "array must contain only positive integer keys");
732 				RETURN_THROWS();
733 			}
734 
735 			if (num_index > max_index) {
736 				max_index = num_index;
737 			}
738 		} ZEND_HASH_FOREACH_END();
739 
740 		tmp = max_index + 1;
741 		if (tmp <= 0) {
742 			zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "integer overflow detected");
743 			RETURN_THROWS();
744 		}
745 		spl_fixedarray_init(&array, tmp);
746 
747 		ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(data), num_index, str_index, element) {
748 			ZVAL_COPY_DEREF(&array.elements[num_index], element);
749 		} ZEND_HASH_FOREACH_END();
750 
751 	} else if (num > 0 && !save_indexes) {
752 		zval *element;
753 		zend_long i = 0;
754 
755 		spl_fixedarray_init(&array, num);
756 
757 		ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(data), element) {
758 			ZVAL_COPY_DEREF(&array.elements[i], element);
759 			i++;
760 		} ZEND_HASH_FOREACH_END();
761 	} else {
762 		spl_fixedarray_init(&array, 0);
763 	}
764 
765 	object_init_ex(return_value, spl_ce_SplFixedArray);
766 
767 	intern = Z_SPLFIXEDARRAY_P(return_value);
768 	intern->array = array;
769 }
770 
PHP_METHOD(SplFixedArray,getSize)771 PHP_METHOD(SplFixedArray, getSize)
772 {
773 	zval *object = ZEND_THIS;
774 	spl_fixedarray_object *intern;
775 
776 	if (zend_parse_parameters_none() == FAILURE) {
777 		RETURN_THROWS();
778 	}
779 
780 	intern = Z_SPLFIXEDARRAY_P(object);
781 	RETURN_LONG(intern->array.size);
782 }
783 
PHP_METHOD(SplFixedArray,setSize)784 PHP_METHOD(SplFixedArray, setSize)
785 {
786 	zval *object = ZEND_THIS;
787 	spl_fixedarray_object *intern;
788 	zend_long size;
789 
790 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &size) == FAILURE) {
791 		RETURN_THROWS();
792 	}
793 
794 	if (size < 0) {
795 		zend_argument_value_error(1, "must be greater than or equal to 0");
796 		RETURN_THROWS();
797 	}
798 
799 	intern = Z_SPLFIXEDARRAY_P(object);
800 
801 	spl_fixedarray_resize(&intern->array, size);
802 	RETURN_TRUE;
803 }
804 
805 /* Returns whether the requested $index exists. */
PHP_METHOD(SplFixedArray,offsetExists)806 PHP_METHOD(SplFixedArray, offsetExists)
807 {
808 	zval                  *zindex;
809 	spl_fixedarray_object  *intern;
810 
811 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zindex) == FAILURE) {
812 		RETURN_THROWS();
813 	}
814 
815 	intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
816 
817 	RETURN_BOOL(spl_fixedarray_object_has_dimension_helper(intern, zindex, 0));
818 }
819 
820 /* Returns the value at the specified $index. */
PHP_METHOD(SplFixedArray,offsetGet)821 PHP_METHOD(SplFixedArray, offsetGet)
822 {
823 	zval *zindex, *value;
824 	spl_fixedarray_object  *intern;
825 
826 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zindex) == FAILURE) {
827 		RETURN_THROWS();
828 	}
829 
830 	intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
831 	value = spl_fixedarray_object_read_dimension_helper(intern, zindex);
832 
833 	if (value) {
834 		RETURN_COPY_DEREF(value);
835 	} else {
836 		RETURN_NULL();
837 	}
838 }
839 
840 /* Sets the value at the specified $index to $newval. */
PHP_METHOD(SplFixedArray,offsetSet)841 PHP_METHOD(SplFixedArray, offsetSet)
842 {
843 	zval                  *zindex, *value;
844 	spl_fixedarray_object  *intern;
845 
846 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &zindex, &value) == FAILURE) {
847 		RETURN_THROWS();
848 	}
849 
850 	intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
851 	spl_fixedarray_object_write_dimension_helper(intern, zindex, value);
852 
853 }
854 
855 /* Unsets the value at the specified $index. */
PHP_METHOD(SplFixedArray,offsetUnset)856 PHP_METHOD(SplFixedArray, offsetUnset)
857 {
858 	zval                  *zindex;
859 	spl_fixedarray_object  *intern;
860 
861 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zindex) == FAILURE) {
862 		RETURN_THROWS();
863 	}
864 
865 	intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
866 	spl_fixedarray_object_unset_dimension_helper(intern, zindex);
867 
868 }
869 
870 /* Create a new iterator from a SplFixedArray instance. */
PHP_METHOD(SplFixedArray,getIterator)871 PHP_METHOD(SplFixedArray, getIterator)
872 {
873 	if (zend_parse_parameters_none() == FAILURE) {
874 		RETURN_THROWS();
875 	}
876 
877 	zend_create_internal_iterator_zval(return_value, ZEND_THIS);
878 }
879 
PHP_METHOD(SplFixedArray,jsonSerialize)880 PHP_METHOD(SplFixedArray, jsonSerialize)
881 {
882 	ZEND_PARSE_PARAMETERS_NONE();
883 
884 	spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(ZEND_THIS);
885 	array_init_size(return_value, intern->array.size);
886 	for (zend_long i = 0; i < intern->array.size; i++) {
887 		zend_hash_next_index_insert_new(Z_ARR_P(return_value), &intern->array.elements[i]);
888 		Z_TRY_ADDREF(intern->array.elements[i]);
889 	}
890 }
891 
spl_fixedarray_it_dtor(zend_object_iterator * iter)892 static void spl_fixedarray_it_dtor(zend_object_iterator *iter)
893 {
894 	zval_ptr_dtor(&iter->data);
895 }
896 
spl_fixedarray_it_rewind(zend_object_iterator * iter)897 static void spl_fixedarray_it_rewind(zend_object_iterator *iter)
898 {
899 	((spl_fixedarray_it*)iter)->current = 0;
900 }
901 
spl_fixedarray_it_valid(zend_object_iterator * iter)902 static int spl_fixedarray_it_valid(zend_object_iterator *iter)
903 {
904 	spl_fixedarray_it     *iterator = (spl_fixedarray_it*)iter;
905 	spl_fixedarray_object *object   = Z_SPLFIXEDARRAY_P(&iter->data);
906 
907 	if (iterator->current >= 0 && iterator->current < object->array.size) {
908 		return SUCCESS;
909 	}
910 
911 	return FAILURE;
912 }
913 
spl_fixedarray_it_get_current_data(zend_object_iterator * iter)914 static zval *spl_fixedarray_it_get_current_data(zend_object_iterator *iter)
915 {
916 	zval zindex, *data;
917 	spl_fixedarray_it     *iterator = (spl_fixedarray_it*)iter;
918 	spl_fixedarray_object *object   = Z_SPLFIXEDARRAY_P(&iter->data);
919 
920 	ZVAL_LONG(&zindex, iterator->current);
921 	data = spl_fixedarray_object_read_dimension_helper(object, &zindex);
922 
923 	if (data == NULL) {
924 		data = &EG(uninitialized_zval);
925 	}
926 	return data;
927 }
928 
spl_fixedarray_it_get_current_key(zend_object_iterator * iter,zval * key)929 static void spl_fixedarray_it_get_current_key(zend_object_iterator *iter, zval *key)
930 {
931 	ZVAL_LONG(key, ((spl_fixedarray_it*)iter)->current);
932 }
933 
spl_fixedarray_it_move_forward(zend_object_iterator * iter)934 static void spl_fixedarray_it_move_forward(zend_object_iterator *iter)
935 {
936 	((spl_fixedarray_it*)iter)->current++;
937 }
938 
939 /* iterator handler table */
940 static const zend_object_iterator_funcs spl_fixedarray_it_funcs = {
941 	spl_fixedarray_it_dtor,
942 	spl_fixedarray_it_valid,
943 	spl_fixedarray_it_get_current_data,
944 	spl_fixedarray_it_get_current_key,
945 	spl_fixedarray_it_move_forward,
946 	spl_fixedarray_it_rewind,
947 	NULL,
948 	NULL, /* get_gc */
949 };
950 
spl_fixedarray_get_iterator(zend_class_entry * ce,zval * object,int by_ref)951 static zend_object_iterator *spl_fixedarray_get_iterator(zend_class_entry *ce, zval *object, int by_ref)
952 {
953 	spl_fixedarray_it *iterator;
954 
955 	if (by_ref) {
956 		zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
957 		return NULL;
958 	}
959 
960 	iterator = emalloc(sizeof(spl_fixedarray_it));
961 
962 	zend_iterator_init((zend_object_iterator*)iterator);
963 
964 	ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object));
965 	iterator->intern.funcs = &spl_fixedarray_it_funcs;
966 
967 	return &iterator->intern;
968 }
969 
PHP_MINIT_FUNCTION(spl_fixedarray)970 PHP_MINIT_FUNCTION(spl_fixedarray)
971 {
972 	spl_ce_SplFixedArray = register_class_SplFixedArray(
973 		zend_ce_aggregate, zend_ce_arrayaccess, zend_ce_countable, php_json_serializable_ce);
974 	spl_ce_SplFixedArray->create_object = spl_fixedarray_new;
975 	spl_ce_SplFixedArray->default_object_handlers = &spl_handler_SplFixedArray;
976 	spl_ce_SplFixedArray->get_iterator = spl_fixedarray_get_iterator;
977 
978 	memcpy(&spl_handler_SplFixedArray, &std_object_handlers, sizeof(zend_object_handlers));
979 
980 	spl_handler_SplFixedArray.offset          = XtOffsetOf(spl_fixedarray_object, std);
981 	spl_handler_SplFixedArray.clone_obj       = spl_fixedarray_object_clone;
982 	spl_handler_SplFixedArray.read_dimension  = spl_fixedarray_object_read_dimension;
983 	spl_handler_SplFixedArray.write_dimension = spl_fixedarray_object_write_dimension;
984 	spl_handler_SplFixedArray.unset_dimension = spl_fixedarray_object_unset_dimension;
985 	spl_handler_SplFixedArray.has_dimension   = spl_fixedarray_object_has_dimension;
986 	spl_handler_SplFixedArray.count_elements  = spl_fixedarray_object_count_elements;
987 	spl_handler_SplFixedArray.get_properties_for = spl_fixedarray_object_get_properties_for;
988 	spl_handler_SplFixedArray.get_gc          = spl_fixedarray_object_get_gc;
989 	spl_handler_SplFixedArray.free_obj        = spl_fixedarray_object_free_storage;
990 
991 	return SUCCESS;
992 }
993