Coverage Report

Created: 2026-10-28 06:20

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