Coverage Report

Created: 2026-06-02 06:39

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