Coverage Report

Created: 2025-11-16 06:23

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