Coverage Report

Created: 2026-07-25 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/spl/spl_array.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
   | Authors: Marcus Boerger <helly@php.net>                              |
12
   +----------------------------------------------------------------------+
13
*/
14
15
#ifdef HAVE_CONFIG_H
16
# include "config.h"
17
#endif
18
19
#include "php.h"
20
#include "ext/standard/php_var.h"
21
#include "zend_smart_str.h"
22
#include "zend_interfaces.h"
23
#include "zend_exceptions.h"
24
25
#include "spl_iterators.h"
26
#include "spl_array.h"
27
#include "spl_array_arginfo.h"
28
#include "spl_exceptions.h"
29
#include "spl_functions.h" /* For spl_set_private_debug_info_property() */
30
31
/* Defined later in the file */
32
PHPAPI zend_class_entry  *spl_ce_ArrayIterator;
33
PHPAPI zend_class_entry  *spl_ce_RecursiveArrayIterator;
34
35
/* ArrayObject class */
36
static zend_object_handlers spl_handler_ArrayObject;
37
PHPAPI zend_class_entry  *spl_ce_ArrayObject;
38
39
typedef struct _spl_array_object {
40
  zval              array;
41
  HashTable         *sentinel_array;
42
  uint32_t          ht_iter;
43
  int               ar_flags;
44
  unsigned char   nApplyCount;
45
  zend_function     *fptr_offset_get;
46
  zend_function     *fptr_offset_set;
47
  zend_function     *fptr_offset_has;
48
  zend_function     *fptr_offset_del;
49
  zend_function     *fptr_count;
50
  zend_class_entry* ce_get_iterator;
51
  zend_object       std;
52
} spl_array_object;
53
54
778k
#define spl_array_from_obj(obj) ZEND_CONTAINER_OF(obj, spl_array_object, std)
55
56
160k
#define Z_SPLARRAY_P(zv)  spl_array_from_obj(Z_OBJ_P((zv)))
57
58
2.81k
static inline HashTable **spl_array_get_hash_table_ptr(spl_array_object* intern) { /* {{{ */
59
  //??? TODO: Delay duplication for arrays; only duplicate for write operations
60
2.81k
  if (intern->ar_flags & SPL_ARRAY_IS_SELF) {
61
    /* rebuild properties */
62
0
    zend_std_get_properties_ex(&intern->std);
63
0
    return &intern->std.properties;
64
2.81k
  } else if (intern->ar_flags & SPL_ARRAY_USE_OTHER) {
65
0
    spl_array_object *other = Z_SPLARRAY_P(&intern->array);
66
0
    return spl_array_get_hash_table_ptr(other);
67
2.81k
  } else if (Z_TYPE(intern->array) == IS_ARRAY) {
68
2.66k
    return &Z_ARRVAL(intern->array);
69
2.66k
  } else {
70
147
    zend_object *obj = Z_OBJ(intern->array);
71
    /* Since we're directly playing with the properties table, we shall initialize the lazy object directly.
72
     * If we don't, it's possible to continue working with the wrong object in case we're using a proxy. */
73
147
    if (UNEXPECTED(zend_lazy_object_must_init(obj))) {
74
0
      obj = zend_lazy_object_init(obj);
75
0
      if (UNEXPECTED(!obj)) {
76
0
        if (!intern->sentinel_array) {
77
0
          intern->sentinel_array = zend_new_array(0);
78
0
        }
79
0
        return &intern->sentinel_array;
80
0
      }
81
0
    }
82
    /* should no longer be lazy */
83
147
    ZEND_ASSERT(!zend_lazy_object_must_init(obj));
84
    /* rebuild properties */
85
147
    zend_std_get_properties_ex(obj);
86
147
    if (GC_REFCOUNT(obj->properties) > 1) {
87
0
      if (EXPECTED(!(GC_FLAGS(obj->properties) & IS_ARRAY_IMMUTABLE))) {
88
0
        GC_DELREF(obj->properties);
89
0
      }
90
0
      obj->properties = zend_array_dup(obj->properties);
91
0
    }
92
147
    return &obj->properties;
93
147
  }
94
2.81k
}
95
/* }}} */
96
97
2.81k
static inline HashTable *spl_array_get_hash_table(spl_array_object* intern) { /* {{{ */
98
2.81k
  return *spl_array_get_hash_table_ptr(intern);
99
2.81k
}
100
/* }}} */
101
102
static inline bool spl_array_is_object(spl_array_object *intern) /* {{{ */
103
1.33k
{
104
1.33k
  while (intern->ar_flags & SPL_ARRAY_USE_OTHER) {
105
0
    intern = Z_SPLARRAY_P(&intern->array);
106
0
  }
107
1.33k
  return (intern->ar_flags & SPL_ARRAY_IS_SELF) || Z_TYPE(intern->array) == IS_OBJECT;
108
1.33k
}
109
/* }}} */
110
111
static zend_result spl_array_skip_protected(spl_array_object *intern, HashTable *aht);
112
113
static zend_never_inline void spl_array_create_ht_iter(HashTable *ht, spl_array_object* intern) /* {{{ */
114
261
{
115
261
  intern->ht_iter = zend_hash_iterator_add(ht, zend_hash_get_current_pos(ht));
116
261
  zend_hash_internal_pointer_reset_ex(ht, &EG(ht_iterators)[intern->ht_iter].pos);
117
261
  spl_array_skip_protected(intern, ht);
118
261
}
119
/* }}} */
120
121
static zend_always_inline uint32_t *spl_array_get_pos_ptr(HashTable *ht, spl_array_object* intern) /* {{{ */
122
2.05k
{
123
2.05k
  if (UNEXPECTED(intern->ht_iter == (uint32_t)-1)) {
124
261
    spl_array_create_ht_iter(ht, intern);
125
261
  }
126
2.05k
  return &EG(ht_iterators)[intern->ht_iter].pos;
127
2.05k
}
128
/* }}} */
129
130
/* {{{ spl_array_object_free_storage */
131
static void spl_array_object_free_storage(zend_object *object)
132
158k
{
133
158k
  spl_array_object *intern = spl_array_from_obj(object);
134
135
158k
  if (intern->ht_iter != (uint32_t) -1) {
136
261
    zend_hash_iterator_del(intern->ht_iter);
137
261
  }
138
139
158k
  if (UNEXPECTED(intern->sentinel_array)) {
140
0
    zend_array_release(intern->sentinel_array);
141
0
  }
142
143
158k
  zend_object_std_dtor(&intern->std);
144
145
158k
  zval_ptr_dtor(&intern->array);
146
158k
}
147
/* }}} */
148
149
/* {{{ spl_array_object_new_ex */
150
static zend_object *spl_array_object_new_ex(zend_class_entry *class_type, zend_object *orig, int clone_orig)
151
158k
{
152
158k
  spl_array_object *intern;
153
158k
  const zend_class_entry *parent = class_type;
154
158k
  int inherited = 0;
155
156
158k
  intern = zend_object_alloc(sizeof(spl_array_object), parent);
157
158
158k
  zend_object_std_init(&intern->std, class_type);
159
158k
  object_properties_init(&intern->std, class_type);
160
161
158k
  intern->ar_flags = 0;
162
158k
  intern->ce_get_iterator = spl_ce_ArrayIterator;
163
158k
  if (orig) {
164
0
    spl_array_object *other = spl_array_from_obj(orig);
165
166
0
    intern->ar_flags &= ~ SPL_ARRAY_CLONE_MASK;
167
0
    intern->ar_flags |= (other->ar_flags & SPL_ARRAY_CLONE_MASK);
168
0
    intern->ce_get_iterator = other->ce_get_iterator;
169
0
    if (clone_orig) {
170
0
      if (other->ar_flags & SPL_ARRAY_IS_SELF) {
171
0
        ZVAL_UNDEF(&intern->array);
172
0
      } else if (instanceof_function(class_type, spl_ce_ArrayObject)) {
173
0
        ZVAL_ARR(&intern->array,
174
0
          zend_array_dup(spl_array_get_hash_table(other)));
175
0
      } else {
176
0
        #if ZEND_DEBUG
177
        /* This is because the call to instanceof_function will remain because
178
         * the compiler can't prove in this compile unit that this function is
179
         * side-effect-free.
180
         * See https://github.com/php/php-src/pull/14518#discussion_r1638740932 */
181
0
        ZEND_ASSERT(instanceof_function(class_type, spl_ce_ArrayIterator));
182
0
        #endif
183
184
0
        ZVAL_OBJ_COPY(&intern->array, orig);
185
0
        intern->ar_flags |= SPL_ARRAY_USE_OTHER;
186
0
      }
187
0
    } else {
188
0
      ZVAL_OBJ_COPY(&intern->array, orig);
189
0
      intern->ar_flags |= SPL_ARRAY_USE_OTHER;
190
0
    }
191
158k
  } else {
192
158k
    array_init(&intern->array);
193
158k
  }
194
195
158k
  while (parent) {
196
158k
    if (parent == spl_ce_ArrayIterator || parent == spl_ce_RecursiveArrayIterator || parent == spl_ce_ArrayObject) {
197
158k
      break;
198
158k
    }
199
55
    parent = parent->parent;
200
55
    inherited = 1;
201
55
  }
202
203
158k
  ZEND_ASSERT(parent);
204
205
158k
  if (inherited) {
206
55
    intern->fptr_offset_get = zend_hash_str_find_ptr(&class_type->function_table, "offsetget", sizeof("offsetget") - 1);
207
55
    if (intern->fptr_offset_get->common.scope == parent) {
208
55
      intern->fptr_offset_get = NULL;
209
55
    }
210
55
    intern->fptr_offset_set = zend_hash_str_find_ptr(&class_type->function_table, "offsetset", sizeof("offsetset") - 1);
211
55
    if (intern->fptr_offset_set->common.scope == parent) {
212
55
      intern->fptr_offset_set = NULL;
213
55
    }
214
55
    intern->fptr_offset_has = zend_hash_str_find_ptr(&class_type->function_table, "offsetexists", sizeof("offsetexists") - 1);
215
55
    if (intern->fptr_offset_has->common.scope == parent) {
216
55
      intern->fptr_offset_has = NULL;
217
55
    }
218
55
    intern->fptr_offset_del = zend_hash_str_find_ptr(&class_type->function_table, "offsetunset",  sizeof("offsetunset") - 1);
219
55
    if (intern->fptr_offset_del->common.scope == parent) {
220
55
      intern->fptr_offset_del = NULL;
221
55
    }
222
    /* Find count() method */
223
55
    intern->fptr_count = zend_hash_find_ptr(&class_type->function_table, ZSTR_KNOWN(ZEND_STR_COUNT));
224
55
    if (intern->fptr_count->common.scope == parent) {
225
55
      intern->fptr_count = NULL;
226
55
    }
227
55
  }
228
229
158k
  intern->ht_iter = (uint32_t)-1;
230
158k
  return &intern->std;
231
158k
}
232
/* }}} */
233
234
/* {{{ spl_array_object_new */
235
static zend_object *spl_array_object_new(zend_class_entry *class_type)
236
158k
{
237
158k
  return spl_array_object_new_ex(class_type, NULL, 0);
238
158k
}
239
/* }}} */
240
241
/* {{{ spl_array_object_clone */
242
static zend_object *spl_array_object_clone(zend_object *old_object)
243
0
{
244
0
  zend_object *new_object;
245
246
0
  new_object = spl_array_object_new_ex(old_object->ce, old_object, 1);
247
248
0
  zend_objects_clone_members(new_object, old_object);
249
250
0
  return new_object;
251
0
}
252
/* }}} */
253
254
typedef struct {
255
  zend_string *key;
256
  zend_ulong h;
257
  bool release_key;
258
} spl_hash_key;
259
260
118
static void spl_hash_key_release(spl_hash_key *key) {
261
118
  if (key->release_key) {
262
0
    zend_string_release_ex(key->key, 0);
263
0
  }
264
118
}
265
266
/* This function does not throw any exceptions for illegal offsets, calls to
267
 * zend_illegal_container_offset(); need to be made if the return value is FAILURE */
268
static zend_result get_hash_key(spl_hash_key *key, spl_array_object *intern, zval *offset)
269
708
{
270
708
  key->release_key = false;
271
708
try_again:
272
708
  switch (Z_TYPE_P(offset)) {
273
0
  case IS_NULL:
274
0
    zend_error(E_DEPRECATED, "Using null as an array offset is deprecated, use an empty string instead");
275
0
    key->key = ZSTR_EMPTY_ALLOC();
276
0
    return SUCCESS;
277
150
  case IS_STRING:
278
150
    key->key = Z_STR_P(offset);
279
150
    if (ZEND_HANDLE_NUMERIC(key->key, key->h)) {
280
32
      key->key = NULL;
281
32
      break;
282
32
    }
283
118
    return SUCCESS;
284
0
  case IS_RESOURCE:
285
0
    zend_use_resource_as_offset(offset);
286
0
    key->key = NULL;
287
0
    key->h = Z_RES_P(offset)->handle;
288
0
    break;
289
16
  case IS_DOUBLE:
290
16
    key->key = NULL;
291
16
    key->h = zend_dval_to_lval_safe(Z_DVAL_P(offset));
292
16
    break;
293
0
  case IS_FALSE:
294
0
    key->key = NULL;
295
0
    key->h = 0;
296
0
    break;
297
0
  case IS_TRUE:
298
0
    key->key = NULL;
299
0
    key->h = 1;
300
0
    break;
301
542
  case IS_LONG:
302
542
    key->key = NULL;
303
542
    key->h = Z_LVAL_P(offset);
304
542
    break;
305
0
  case IS_REFERENCE:
306
0
    ZVAL_DEREF(offset);
307
0
    goto try_again;
308
0
  default:
309
0
    return FAILURE;
310
708
  }
311
312
590
  if (spl_array_is_object(intern)) {
313
0
    key->key = zend_long_to_str(key->h);
314
0
    key->release_key = true;
315
0
  }
316
590
  return SUCCESS;
317
708
}
318
319
static zval *spl_array_get_dimension_ptr(bool check_inherited, spl_array_object *intern, const zend_string *ce_name,
320
  zval *offset, int type) /* {{{ */
321
302
{
322
302
  zval *retval;
323
302
  spl_hash_key key;
324
302
  HashTable *ht = spl_array_get_hash_table(intern);
325
326
302
  if (!offset || Z_ISUNDEF_P(offset) || !ht) {
327
0
    return &EG(uninitialized_zval);
328
0
  }
329
330
302
  if ((type == BP_VAR_W || type == BP_VAR_RW) && intern->nApplyCount > 0) {
331
0
    zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited");
332
0
    return &EG(error_zval);
333
0
  }
334
335
302
  if (get_hash_key(&key, intern, offset) == FAILURE) {
336
0
    zend_illegal_container_offset(ce_name, offset, type);
337
0
    return (type == BP_VAR_W || type == BP_VAR_RW) ?
338
0
      &EG(error_zval) : &EG(uninitialized_zval);
339
0
  }
340
341
302
  if (key.key) {
342
46
    retval = zend_hash_find(ht, key.key);
343
46
    if (retval) {
344
40
      if (Z_TYPE_P(retval) == IS_INDIRECT) {
345
0
        retval = Z_INDIRECT_P(retval);
346
0
        if (Z_TYPE_P(retval) == IS_UNDEF) {
347
0
          switch (type) {
348
0
            case BP_VAR_R:
349
0
              zend_error(E_WARNING, "Undefined array key \"%s\"", ZSTR_VAL(key.key));
350
0
              ZEND_FALLTHROUGH;
351
0
            case BP_VAR_UNSET:
352
0
            case BP_VAR_IS:
353
0
              retval = &EG(uninitialized_zval);
354
0
              break;
355
0
            case BP_VAR_RW:
356
0
              zend_error(E_WARNING,"Undefined array key \"%s\"", ZSTR_VAL(key.key));
357
0
              ZEND_FALLTHROUGH;
358
0
            case BP_VAR_W: {
359
0
              ZVAL_NULL(retval);
360
0
            }
361
0
          }
362
0
        }
363
0
      }
364
40
    } else {
365
6
      switch (type) {
366
6
        case BP_VAR_R:
367
6
          zend_error(E_WARNING, "Undefined array key \"%s\"", ZSTR_VAL(key.key));
368
6
          ZEND_FALLTHROUGH;
369
6
        case BP_VAR_UNSET:
370
6
        case BP_VAR_IS:
371
6
          retval = &EG(uninitialized_zval);
372
6
          break;
373
0
        case BP_VAR_RW:
374
0
          zend_error(E_WARNING,"Undefined array key \"%s\"", ZSTR_VAL(key.key));
375
0
          ZEND_FALLTHROUGH;
376
0
        case BP_VAR_W: {
377
0
            zval value;
378
0
          ZVAL_NULL(&value);
379
0
            retval = zend_hash_update(ht, key.key, &value);
380
0
        }
381
6
      }
382
6
    }
383
46
    spl_hash_key_release(&key);
384
256
  } else {
385
256
    if ((retval = zend_hash_index_find(ht, key.h)) == NULL) {
386
149
      switch (type) {
387
32
        case BP_VAR_R:
388
32
          zend_error(E_WARNING, "Undefined array key " ZEND_LONG_FMT, key.h);
389
32
          ZEND_FALLTHROUGH;
390
32
        case BP_VAR_UNSET:
391
32
        case BP_VAR_IS:
392
32
          retval = &EG(uninitialized_zval);
393
32
          break;
394
0
        case BP_VAR_RW:
395
0
          zend_error(E_WARNING, "Undefined array key " ZEND_LONG_FMT, key.h);
396
0
          ZEND_FALLTHROUGH;
397
117
        case BP_VAR_W: {
398
117
            zval value;
399
117
          ZVAL_NULL(&value);
400
117
          retval = zend_hash_index_update(ht, key.h, &value);
401
117
         }
402
149
      }
403
149
    }
404
256
  }
405
302
  return retval;
406
302
} /* }}} */
407
408
static int spl_array_has_dimension(zend_object *object, zval *offset, int check_empty);
409
410
static zval *spl_array_read_dimension_ex(int check_inherited, zend_object *object, zval *offset, int type, zval *rv) /* {{{ */
411
302
{
412
302
  spl_array_object *intern = spl_array_from_obj(object);
413
302
  zval *ret;
414
415
302
  if (check_inherited &&
416
251
      (intern->fptr_offset_get || (type == BP_VAR_IS && intern->fptr_offset_has))) {
417
0
    if (type == BP_VAR_IS) {
418
0
      if (!spl_array_has_dimension(object, offset, 0)) {
419
0
        return &EG(uninitialized_zval);
420
0
      }
421
0
    }
422
423
0
    if (intern->fptr_offset_get) {
424
0
      zval tmp;
425
0
      if (!offset) {
426
0
        ZVAL_UNDEF(&tmp);
427
0
        offset = &tmp;
428
0
      }
429
0
      zend_call_method_with_1_params(object, object->ce, &intern->fptr_offset_get, "offsetGet", rv, offset);
430
431
0
      if (!Z_ISUNDEF_P(rv)) {
432
0
        return rv;
433
0
      }
434
0
      return &EG(uninitialized_zval);
435
0
    }
436
0
  }
437
438
302
  ret = spl_array_get_dimension_ptr(check_inherited, intern, object->ce->name, offset, type);
439
440
  /* When in a write context,
441
   * ZE has to be fooled into thinking this is in a reference set
442
   * by separating (if necessary) and returning as IS_REFERENCE (with refcount == 1)
443
   */
444
445
302
  if ((type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) &&
446
302
      !Z_ISREF_P(ret) &&
447
162
      EXPECTED(ret != &EG(uninitialized_zval))) {
448
162
    ZVAL_NEW_REF(ret, ret);
449
162
  }
450
451
302
  return ret;
452
302
} /* }}} */
453
454
static zval *spl_array_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */
455
251
{
456
251
  return spl_array_read_dimension_ex(1, object, offset, type, rv);
457
251
} /* }}} */
458
459
static void spl_array_write_dimension_ex(int check_inherited, zend_object *object, zval *offset, zval *value) /* {{{ */
460
177
{
461
177
  spl_array_object *intern = spl_array_from_obj(object);
462
177
  HashTable *ht;
463
177
  spl_hash_key key;
464
465
177
  if (check_inherited && intern->fptr_offset_set) {
466
0
    zval tmp;
467
468
0
    if (!offset) {
469
0
      ZVAL_NULL(&tmp);
470
0
      offset = &tmp;
471
0
    }
472
0
    zend_call_method_with_2_params(object, object->ce, &intern->fptr_offset_set, "offsetSet", NULL, offset, value);
473
0
    return;
474
0
  }
475
476
177
  if (intern->nApplyCount > 0) {
477
0
    zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited");
478
0
    return;
479
0
  }
480
481
177
  Z_TRY_ADDREF_P(value);
482
177
  if (!offset || Z_TYPE_P(offset) == IS_NULL) {
483
11
    ht = spl_array_get_hash_table(intern);
484
11
    if (UNEXPECTED(ht == intern->sentinel_array)) {
485
0
      return;
486
0
    }
487
11
    zend_hash_next_index_insert(ht, value);
488
11
    return;
489
11
  }
490
491
166
  if (get_hash_key(&key, intern, offset) == FAILURE) {
492
0
    zend_illegal_container_offset(object->ce->name, offset, BP_VAR_W);
493
0
    zval_ptr_dtor(value);
494
0
    return;
495
0
  }
496
497
166
  ht = spl_array_get_hash_table(intern);
498
166
  if (UNEXPECTED(ht == intern->sentinel_array)) {
499
0
    spl_hash_key_release(&key);
500
0
    return;
501
0
  }
502
503
166
  if (key.key) {
504
63
    zend_hash_update_ind(ht, key.key, value);
505
63
    spl_hash_key_release(&key);
506
103
  } else {
507
103
    zend_hash_index_update(ht, key.h, value);
508
103
  }
509
166
} /* }}} */
510
511
static void spl_array_write_dimension(zend_object *object, zval *offset, zval *value) /* {{{ */
512
177
{
513
177
  spl_array_write_dimension_ex(1, object, offset, value);
514
177
} /* }}} */
515
516
static void spl_array_unset_dimension_ex(int check_inherited, zend_object *object, zval *offset) /* {{{ */
517
120
{
518
120
  HashTable *ht;
519
120
  spl_array_object *intern = spl_array_from_obj(object);
520
120
  spl_hash_key key;
521
522
120
  if (check_inherited && intern->fptr_offset_del) {
523
0
    zend_call_method_with_1_params(object, object->ce, &intern->fptr_offset_del, "offsetUnset", NULL, offset);
524
0
    return;
525
0
  }
526
527
120
  if (intern->nApplyCount > 0) {
528
0
    zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited");
529
0
    return;
530
0
  }
531
532
120
  if (get_hash_key(&key, intern, offset) == FAILURE) {
533
0
    zend_illegal_container_offset(object->ce->name, offset, BP_VAR_UNSET);
534
0
    return;
535
0
  }
536
537
120
  ht = spl_array_get_hash_table(intern);
538
120
  if (key.key) {
539
9
    zval *data = zend_hash_find(ht, key.key);
540
9
    if (data) {
541
0
      if (Z_TYPE_P(data) == IS_INDIRECT) {
542
0
        data = Z_INDIRECT_P(data);
543
0
        if (Z_TYPE_P(data) != IS_UNDEF) {
544
0
          zval garbage;
545
0
          ZVAL_COPY_VALUE(&garbage, data);
546
0
          ZVAL_UNDEF(data);
547
0
          HT_FLAGS(ht) |= HASH_FLAG_HAS_EMPTY_IND;
548
0
          zend_hash_move_forward_ex(ht, spl_array_get_pos_ptr(ht, intern));
549
0
          if (spl_array_is_object(intern)) {
550
0
            spl_array_skip_protected(intern, ht);
551
0
          }
552
0
          zval_ptr_dtor(&garbage);
553
0
        }
554
0
      } else {
555
0
        zend_hash_del(ht, key.key);
556
0
      }
557
0
    }
558
9
    spl_hash_key_release(&key);
559
111
  } else {
560
111
    zend_hash_index_del(ht, key.h);
561
111
  }
562
120
} /* }}} */
563
564
static void spl_array_unset_dimension(zend_object *object, zval *offset) /* {{{ */
565
111
{
566
111
  spl_array_unset_dimension_ex(1, object, offset);
567
111
} /* }}} */
568
569
/* check_empty can take value 0, 1, or 2
570
 * 0/1 are used as normal boolean, but 2 is used for the case when this function is called from
571
 * the offsetExists() method, in which case it needs to report the offset exist even if the value is null */
572
static bool spl_array_has_dimension_ex(bool check_inherited, zend_object *object, zval *offset, int check_empty) /* {{{ */
573
120
{
574
120
  spl_array_object *intern = spl_array_from_obj(object);
575
120
  zval rv, *value = NULL, *tmp;
576
577
120
  if (check_inherited && intern->fptr_offset_has) {
578
0
    zend_call_method_with_1_params(object, object->ce, &intern->fptr_offset_has, "offsetExists", &rv, offset);
579
580
0
    if (!zend_is_true(&rv)) {
581
0
      zval_ptr_dtor(&rv);
582
0
      return false;
583
0
    }
584
0
    zval_ptr_dtor(&rv);
585
586
    /* For isset calls we don't need to check the value, so return early */
587
0
    if (!check_empty) {
588
0
      return true;
589
0
    } else if (intern->fptr_offset_get) {
590
0
      value = spl_array_read_dimension_ex(1, object, offset, BP_VAR_R, &rv);
591
0
    }
592
0
  }
593
594
120
  if (!value) {
595
120
    HashTable *ht = spl_array_get_hash_table(intern);
596
120
    spl_hash_key key;
597
598
120
    if (get_hash_key(&key, intern, offset) == FAILURE) {
599
0
      zend_illegal_container_offset(object->ce->name, offset, BP_VAR_IS);
600
0
      return false;
601
0
    }
602
603
120
    if (key.key) {
604
0
      tmp = zend_hash_find(ht, key.key);
605
0
      spl_hash_key_release(&key);
606
120
    } else {
607
120
      tmp = zend_hash_index_find(ht, key.h);
608
120
    }
609
610
120
    if (!tmp) {
611
4
      return false;
612
4
    }
613
614
    /* check_empty is only equal to 2 if it is called from offsetExists on this class,
615
     * where it needs to report an offset exists even if the value is null */
616
116
    if (check_empty == 2) {
617
0
      return true;
618
0
    }
619
620
116
    if (check_empty && check_inherited && intern->fptr_offset_get) {
621
0
      value = spl_array_read_dimension_ex(1, object, offset, BP_VAR_R, &rv);
622
116
    } else {
623
116
      value = tmp;
624
116
    }
625
116
  }
626
627
  /* empty() check the value is not falsy, isset() only check it is not null */
628
120
  bool result = check_empty ? zend_is_true(value) : Z_TYPE_P(value) != IS_NULL;
629
630
116
  if (value == &rv) {
631
0
    zval_ptr_dtor(&rv);
632
0
  }
633
634
116
  return result;
635
120
} /* }}} */
636
637
static int spl_array_has_dimension(zend_object *object, zval *offset, int check_empty) /* {{{ */
638
120
{
639
120
  return spl_array_has_dimension_ex(/* check_inherited */ true, object, offset, check_empty);
640
120
} /* }}} */
641
642
/* {{{ Returns whether the requested $index exists. */
643
PHP_METHOD(ArrayObject, offsetExists)
644
0
{
645
0
  zval *index;
646
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &index) == FAILURE) {
647
0
    RETURN_THROWS();
648
0
  }
649
0
  RETURN_BOOL(spl_array_has_dimension_ex(/* check_inherited */ false, Z_OBJ_P(ZEND_THIS), index, 2));
650
0
} /* }}} */
651
652
/* {{{ Returns the value at the specified $index. */
653
PHP_METHOD(ArrayObject, offsetGet)
654
51
{
655
51
  zval *value, *index;
656
51
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &index) == FAILURE) {
657
0
    RETURN_THROWS();
658
0
  }
659
51
  value = spl_array_read_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index, BP_VAR_R, return_value);
660
51
  if (value != return_value) {
661
51
    RETURN_COPY_DEREF(value);
662
51
  }
663
51
} /* }}} */
664
665
/* {{{ Sets the value at the specified $index to $newval. */
666
PHP_METHOD(ArrayObject, offsetSet)
667
0
{
668
0
  zval *index, *value;
669
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &index, &value) == FAILURE) {
670
0
    RETURN_THROWS();
671
0
  }
672
0
  spl_array_write_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index, value);
673
0
} /* }}} */
674
675
void spl_array_iterator_append(zval *object, zval *append_value) /* {{{ */
676
0
{
677
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
678
679
0
  if (spl_array_is_object(intern)) {
680
0
    zend_throw_error(NULL, "Cannot append properties to objects, use %s::offsetSet() instead", ZSTR_VAL(Z_OBJCE_P(object)->name));
681
0
    return;
682
0
  }
683
684
0
  spl_array_write_dimension(Z_OBJ_P(object), NULL, append_value);
685
0
} /* }}} */
686
687
/* {{{ Appends the value (cannot be called for objects). */
688
PHP_METHOD(ArrayObject, append)
689
0
{
690
0
  zval *value;
691
692
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &value) == FAILURE) {
693
0
    RETURN_THROWS();
694
0
  }
695
0
  spl_array_iterator_append(ZEND_THIS, value);
696
0
} /* }}} */
697
698
/* {{{ Unsets the value at the specified $index. */
699
PHP_METHOD(ArrayObject, offsetUnset)
700
9
{
701
9
  zval *index;
702
9
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &index) == FAILURE) {
703
0
    RETURN_THROWS();
704
0
  }
705
9
  spl_array_unset_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index);
706
9
} /* }}} */
707
708
/* {{{ Return a copy of the contained array */
709
PHP_METHOD(ArrayObject, getArrayCopy)
710
0
{
711
0
  zval *object = ZEND_THIS;
712
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
713
714
0
  ZEND_PARSE_PARAMETERS_NONE();
715
716
0
  RETURN_ARR(zend_array_dup(spl_array_get_hash_table(intern)));
717
0
} /* }}} */
718
719
static HashTable *spl_array_get_properties_for(zend_object *object, zend_prop_purpose purpose) /* {{{ */
720
65
{
721
65
  spl_array_object *intern = spl_array_from_obj(object);
722
65
  HashTable *ht;
723
65
  bool dup;
724
725
65
  if (intern->ar_flags & SPL_ARRAY_STD_PROP_LIST) {
726
0
    return zend_std_get_properties_for(object, purpose);
727
0
  }
728
729
  /* We are supposed to be the only owner of the internal hashtable.
730
   * The "dup" flag decides whether this is a "long-term" use where
731
   * we need to duplicate, or a "temporary" one, where we can expect
732
   * that no operations on the ArrayObject will be performed in the
733
   * meantime. */
734
65
  switch (purpose) {
735
7
    case ZEND_PROP_PURPOSE_ARRAY_CAST:
736
7
      dup = true;
737
7
      break;
738
0
    case ZEND_PROP_PURPOSE_VAR_EXPORT:
739
0
    case ZEND_PROP_PURPOSE_JSON:
740
0
      dup = false;
741
0
      break;
742
58
    default:
743
58
      return zend_std_get_properties_for(object, purpose);
744
65
  }
745
746
7
  ht = spl_array_get_hash_table(intern);
747
7
  if (dup) {
748
7
    ht = zend_array_dup(ht);
749
7
  } else {
750
0
    GC_ADDREF(ht);
751
0
  }
752
7
  return ht;
753
65
} /* }}} */
754
755
static inline HashTable* spl_array_get_debug_info(zend_object *obj) /* {{{ */
756
58
{
757
58
  spl_array_object *intern = spl_array_from_obj(obj);
758
58
  HashTable *properties = zend_std_get_properties_ex(&intern->std);
759
760
58
  if (intern->ar_flags & SPL_ARRAY_IS_SELF) {
761
0
    return zend_array_dup(properties);
762
58
  } else {
763
58
    HashTable *debug_info;
764
765
58
    debug_info = zend_new_array(zend_hash_num_elements(properties) + 1);
766
58
    zend_hash_copy(debug_info, properties, (copy_ctor_func_t) zval_add_ref);
767
768
58
    zval *storage = &intern->array;
769
58
    Z_TRY_ADDREF_P(storage);
770
771
58
    const zend_class_entry *base_class_ce = instanceof_function(obj->ce, spl_ce_ArrayIterator)
772
58
      ? spl_ce_ArrayIterator : spl_ce_ArrayObject;
773
774
58
    spl_set_private_debug_info_property(base_class_ce, "storage", strlen("storage"), debug_info, storage);
775
776
58
    return debug_info;
777
58
  }
778
58
}
779
/* }}} */
780
781
static HashTable *spl_array_get_gc(zend_object *obj, zval **gc_data, int *gc_data_count) /* {{{ */
782
458k
{
783
458k
  spl_array_object *intern = spl_array_from_obj(obj);
784
458k
  *gc_data = &intern->array;
785
458k
  *gc_data_count = 1;
786
458k
  return zend_std_get_properties(obj);
787
458k
}
788
/* }}} */
789
790
static zval *spl_array_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv) /* {{{ */
791
2
{
792
2
  spl_array_object *intern = spl_array_from_obj(object);
793
794
2
  if ((intern->ar_flags & SPL_ARRAY_ARRAY_AS_PROPS) != 0
795
0
    && !zend_std_has_property(object, name, ZEND_PROPERTY_EXISTS, NULL)) {
796
0
    zval member;
797
0
    ZVAL_STR(&member, name);
798
0
    return spl_array_read_dimension(object, &member, type, rv);
799
0
  }
800
2
  return zend_std_read_property(object, name, type, cache_slot, rv);
801
2
} /* }}} */
802
803
static zval *spl_array_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot) /* {{{ */
804
15
{
805
15
  spl_array_object *intern = spl_array_from_obj(object);
806
807
15
  if ((intern->ar_flags & SPL_ARRAY_ARRAY_AS_PROPS) != 0
808
0
  && !zend_std_has_property(object, name, ZEND_PROPERTY_EXISTS, NULL)) {
809
0
    zval member;
810
0
    ZVAL_STR(&member, name);
811
0
    spl_array_write_dimension(object, &member, value);
812
0
    return value;
813
0
  }
814
15
  return zend_std_write_property(object, name, value, cache_slot);
815
15
} /* }}} */
816
817
static zval *spl_array_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot) /* {{{ */
818
1
{
819
1
  spl_array_object *intern = spl_array_from_obj(object);
820
821
1
  if ((intern->ar_flags & SPL_ARRAY_ARRAY_AS_PROPS) != 0
822
0
    && !zend_std_has_property(object, name, ZEND_PROPERTY_EXISTS, NULL)) {
823
0
    if (cache_slot) {
824
0
      cache_slot[0] = cache_slot[1] = cache_slot[2] = NULL;
825
0
    }
826
827
    /* If object has offsetGet() overridden, then fallback to read_property,
828
     * which will call offsetGet(). */
829
0
    zval member;
830
0
    if (intern->fptr_offset_get) {
831
0
      return NULL;
832
0
    }
833
0
    ZVAL_STR(&member, name);
834
0
    return spl_array_get_dimension_ptr(true, intern, object->ce->name, &member, type);
835
0
  }
836
1
  return zend_std_get_property_ptr_ptr(object, name, type, cache_slot);
837
1
} /* }}} */
838
839
static int spl_array_has_property(zend_object *object, zend_string *name, int has_set_exists, void **cache_slot) /* {{{ */
840
7
{
841
7
  spl_array_object *intern = spl_array_from_obj(object);
842
843
7
  if ((intern->ar_flags & SPL_ARRAY_ARRAY_AS_PROPS) != 0
844
0
    && !zend_std_has_property(object, name, ZEND_PROPERTY_EXISTS, NULL)) {
845
0
    zval member;
846
0
    ZVAL_STR(&member, name);
847
0
    return spl_array_has_dimension(object, &member, has_set_exists);
848
0
  }
849
7
  return zend_std_has_property(object, name, has_set_exists, cache_slot);
850
7
} /* }}} */
851
852
static void spl_array_unset_property(zend_object *object, zend_string *name, void **cache_slot) /* {{{ */
853
0
{
854
0
  spl_array_object *intern = spl_array_from_obj(object);
855
856
0
  if ((intern->ar_flags & SPL_ARRAY_ARRAY_AS_PROPS) != 0
857
0
    && !zend_std_has_property(object, name, ZEND_PROPERTY_EXISTS, NULL)) {
858
0
    zval member;
859
0
    ZVAL_STR(&member, name);
860
0
    spl_array_unset_dimension(object, &member);
861
0
    return;
862
0
  }
863
0
  zend_std_unset_property(object, name, cache_slot);
864
0
} /* }}} */
865
866
static int spl_array_compare_objects(zval *o1, zval *o2) /* {{{ */
867
21
{
868
21
  HashTable     *ht1,
869
21
            *ht2;
870
21
  spl_array_object  *intern1,
871
21
            *intern2;
872
21
  int         result  = 0;
873
874
21
  ZEND_COMPARE_OBJECTS_FALLBACK(o1, o2);
875
876
0
  intern1 = Z_SPLARRAY_P(o1);
877
0
  intern2 = Z_SPLARRAY_P(o2);
878
0
  ht1   = spl_array_get_hash_table(intern1);
879
0
  ht2   = spl_array_get_hash_table(intern2);
880
881
0
  result = zend_compare_symbol_tables(ht1, ht2);
882
  /* if we just compared std.properties, don't do it again */
883
0
  if (result == 0 &&
884
0
      !(ht1 == intern1->std.properties && ht2 == intern2->std.properties)) {
885
0
    result = zend_std_compare_objects(o1, o2);
886
0
  }
887
0
  return result;
888
21
} /* }}} */
889
890
static zend_result spl_array_skip_protected(spl_array_object *intern, HashTable *aht) /* {{{ */
891
318
{
892
318
  zend_string *string_key;
893
318
  zend_ulong num_key;
894
318
  zval *data;
895
896
318
  if (spl_array_is_object(intern)) {
897
58
    uint32_t *pos_ptr = spl_array_get_pos_ptr(aht, intern);
898
899
81
    do {
900
81
      if (zend_hash_get_current_key_ex(aht, &string_key, &num_key, pos_ptr) == HASH_KEY_IS_STRING) {
901
56
        data = zend_hash_get_current_data_ex(aht, pos_ptr);
902
56
        if (data && Z_TYPE_P(data) == IS_INDIRECT &&
903
43
            Z_TYPE_P(data = Z_INDIRECT_P(data)) == IS_UNDEF) {
904
          /* skip */
905
56
        } else if (!ZSTR_LEN(string_key) || ZSTR_VAL(string_key)[0]) {
906
33
          return SUCCESS;
907
33
        }
908
56
      } else {
909
25
        return SUCCESS;
910
25
      }
911
23
      if (zend_hash_has_more_elements_ex(aht, pos_ptr) != SUCCESS) {
912
0
        return FAILURE;
913
0
      }
914
23
      zend_hash_move_forward_ex(aht, pos_ptr);
915
23
    } while (1);
916
58
  }
917
260
  return FAILURE;
918
318
} /* }}} */
919
920
/* {{{ spl_array_set_array */
921
2.26k
static void spl_array_set_array(zval *object, spl_array_object *intern, zval *array, zend_long ar_flags, bool just_array) {
922
  /* Handled by ZPP prior to this, or for __unserialize() before passing to here */
923
2.26k
  ZEND_ASSERT(Z_TYPE_P(array) == IS_ARRAY || Z_TYPE_P(array) == IS_OBJECT);
924
2.26k
  if (intern->nApplyCount > 0) {
925
0
    zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited");
926
0
    return;
927
0
  }
928
2.26k
  zval garbage;
929
2.26k
  ZVAL_UNDEF(&garbage);
930
2.26k
  if (Z_TYPE_P(array) == IS_ARRAY) {
931
1.14k
    ZVAL_COPY_VALUE(&garbage, &intern->array);
932
1.14k
    if (Z_REFCOUNT_P(array) == 1) {
933
32
      ZVAL_COPY(&intern->array, array);
934
1.11k
    } else {
935
      //??? TODO: try to avoid array duplication
936
1.11k
      ZVAL_ARR(&intern->array, zend_array_dup(Z_ARR_P(array)));
937
1.11k
    }
938
1.14k
  } else {
939
1.11k
    php_error_docref(NULL, E_DEPRECATED,
940
1.11k
      "Using an object as a backing array for %s is deprecated, as it allows violating class constraints and invariants",
941
1.11k
      instanceof_function(Z_OBJCE_P(object), spl_ce_ArrayIterator) ? "ArrayIterator" : "ArrayObject");
942
1.11k
    if (UNEXPECTED(EG(exception))) {
943
0
      return;
944
0
    }
945
1.11k
    if (Z_OBJ_HT_P(array) == &spl_handler_ArrayObject) {
946
319
      ZVAL_COPY_VALUE(&garbage, &intern->array);
947
319
      if (just_array) {
948
319
        spl_array_object *other = Z_SPLARRAY_P(array);
949
319
        ar_flags = other->ar_flags & ~SPL_ARRAY_INT_MASK;
950
319
      }
951
319
      if (Z_OBJ_P(object) == Z_OBJ_P(array)) {
952
117
        ar_flags |= SPL_ARRAY_IS_SELF;
953
117
        ZVAL_UNDEF(&intern->array);
954
202
      } else {
955
202
        ar_flags |= SPL_ARRAY_USE_OTHER;
956
202
        ZVAL_COPY(&intern->array, array);
957
202
      }
958
794
    } else {
959
794
      zend_object_get_properties_t handler = Z_OBJ_HANDLER_P(array, get_properties);
960
794
      if (handler != zend_std_get_properties || Z_OBJ_HANDLER_P(array, get_properties_for)) {
961
3
        zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0,
962
3
          "Overloaded object of type %s is not compatible with %s",
963
3
          ZSTR_VAL(Z_OBJCE_P(array)->name), ZSTR_VAL(intern->std.ce->name));
964
3
        ZEND_ASSERT(Z_TYPE(garbage) == IS_UNDEF);
965
3
        return;
966
3
      }
967
791
      if (UNEXPECTED(Z_OBJCE_P(array)->ce_flags & ZEND_ACC_ENUM)) {
968
0
        zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0,
969
0
          "Enums are not compatible with %s",
970
0
          ZSTR_VAL(intern->std.ce->name));
971
0
        ZEND_ASSERT(Z_TYPE(garbage) == IS_UNDEF);
972
0
        return;
973
0
      }
974
791
      ZVAL_COPY_VALUE(&garbage, &intern->array);
975
791
      ZVAL_COPY(&intern->array, array);
976
791
    }
977
1.11k
  }
978
979
2.25k
  intern->ar_flags &= ~SPL_ARRAY_IS_SELF & ~SPL_ARRAY_USE_OTHER;
980
2.25k
  intern->ar_flags |= ar_flags;
981
2.25k
  if (intern->ht_iter != (uint32_t)-1) {
982
0
    zend_hash_iterator_del(intern->ht_iter);
983
0
    intern->ht_iter = (uint32_t)-1;
984
0
  }
985
986
2.25k
  zval_ptr_dtor(&garbage);
987
2.25k
}
988
/* }}} */
989
990
/* {{{ Constructs a new array object from an array or object. */
991
PHP_METHOD(ArrayObject, __construct)
992
301
{
993
301
  zval *object = ZEND_THIS;
994
301
  spl_array_object *intern;
995
301
  zval *array;
996
301
  zend_long ar_flags = 0;
997
301
  zend_class_entry *ce_get_iterator = spl_ce_ArrayIterator;
998
999
301
  if (ZEND_NUM_ARGS() == 0) {
1000
222
    return; /* nothing to do */
1001
222
  }
1002
1003
79
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|AlC", &array, &ar_flags, &ce_get_iterator) == FAILURE) {
1004
23
    RETURN_THROWS();
1005
23
  }
1006
1007
56
  intern = Z_SPLARRAY_P(object);
1008
1009
56
  if (ZEND_NUM_ARGS() > 2) {
1010
0
    intern->ce_get_iterator = ce_get_iterator;
1011
0
  }
1012
1013
56
  ar_flags &= ~SPL_ARRAY_INT_MASK;
1014
1015
56
  spl_array_set_array(object, intern, array, ar_flags, ZEND_NUM_ARGS() == 1);
1016
56
}
1017
/* }}} */
1018
1019
/* {{{ Set the class used in getIterator. */
1020
PHP_METHOD(ArrayObject, setIteratorClass)
1021
0
{
1022
0
  zval *object = ZEND_THIS;
1023
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
1024
0
  zend_class_entry *ce_get_iterator = spl_ce_ArrayIterator;
1025
1026
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
1027
0
    Z_PARAM_CLASS(ce_get_iterator)
1028
0
  ZEND_PARSE_PARAMETERS_END();
1029
1030
0
  intern->ce_get_iterator = ce_get_iterator;
1031
0
}
1032
/* }}} */
1033
1034
/* {{{ Get the class used in getIterator. */
1035
PHP_METHOD(ArrayObject, getIteratorClass)
1036
0
{
1037
0
  zval *object = ZEND_THIS;
1038
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
1039
1040
0
  ZEND_PARSE_PARAMETERS_NONE();
1041
1042
0
  zend_string_addref(intern->ce_get_iterator->name);
1043
0
  RETURN_STR(intern->ce_get_iterator->name);
1044
0
}
1045
/* }}} */
1046
1047
/* {{{ Get flags */
1048
PHP_METHOD(ArrayObject, getFlags)
1049
0
{
1050
0
  zval *object = ZEND_THIS;
1051
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
1052
1053
0
  ZEND_PARSE_PARAMETERS_NONE();
1054
1055
0
  RETURN_LONG(intern->ar_flags & ~SPL_ARRAY_INT_MASK);
1056
0
}
1057
/* }}} */
1058
1059
/* {{{ Set flags */
1060
PHP_METHOD(ArrayObject, setFlags)
1061
0
{
1062
0
  zval *object = ZEND_THIS;
1063
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
1064
0
  zend_long ar_flags = 0;
1065
1066
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &ar_flags) == FAILURE) {
1067
0
    RETURN_THROWS();
1068
0
  }
1069
1070
0
  intern->ar_flags = (intern->ar_flags & SPL_ARRAY_INT_MASK) | (ar_flags & ~SPL_ARRAY_INT_MASK);
1071
0
}
1072
/* }}} */
1073
1074
/* {{{ Replace the referenced array or object with a new one and return the old one (right now copy - to be changed) */
1075
PHP_METHOD(ArrayObject, exchangeArray)
1076
0
{
1077
0
  zval *object = ZEND_THIS, *array;
1078
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
1079
1080
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "A", &array) == FAILURE) {
1081
0
    RETURN_THROWS();
1082
0
  }
1083
1084
0
  if (intern->nApplyCount > 0) {
1085
0
    zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited");
1086
0
    RETURN_THROWS();
1087
0
  }
1088
1089
0
  RETVAL_ARR(zend_array_dup(spl_array_get_hash_table(intern)));
1090
0
  spl_array_set_array(object, intern, array, 0L, true);
1091
0
}
1092
/* }}} */
1093
1094
/* {{{ Create a new iterator from a ArrayObject instance */
1095
PHP_METHOD(ArrayObject, getIterator)
1096
0
{
1097
0
  zval *object = ZEND_THIS;
1098
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
1099
1100
0
  ZEND_PARSE_PARAMETERS_NONE();
1101
1102
0
  RETURN_OBJ(spl_array_object_new_ex(intern->ce_get_iterator, Z_OBJ_P(object), 0));
1103
0
}
1104
/* }}} */
1105
1106
static zend_long spl_array_object_count_elements_helper(spl_array_object *intern) /* {{{ */
1107
109
{
1108
109
  HashTable *aht = spl_array_get_hash_table(intern);
1109
109
  if (spl_array_is_object(intern)) {
1110
0
    zend_long count = 0;
1111
0
    zend_string *key;
1112
0
    zval *val;
1113
    /* Count public/dynamic properties */
1114
0
    ZEND_HASH_FOREACH_STR_KEY_VAL(aht, key, val) {
1115
0
      if (Z_TYPE_P(val) == IS_INDIRECT) {
1116
0
        if (Z_TYPE_P(Z_INDIRECT_P(val)) == IS_UNDEF) continue;
1117
0
        if (key && ZSTR_VAL(key)[0] == '\0') continue;
1118
0
      }
1119
0
      count++;
1120
0
    } ZEND_HASH_FOREACH_END();
1121
0
    return count;
1122
109
  } else {
1123
109
    return zend_hash_num_elements(aht);
1124
109
  }
1125
109
} /* }}} */
1126
1127
static zend_result spl_array_object_count_elements(zend_object *object, zend_long *count) /* {{{ */
1128
0
{
1129
0
  spl_array_object *intern = spl_array_from_obj(object);
1130
1131
0
  if (intern->fptr_count) {
1132
0
    zval rv;
1133
0
    zend_call_method_with_0_params(object, intern->std.ce, &intern->fptr_count, "count", &rv);
1134
0
    if (Z_TYPE(rv) != IS_UNDEF) {
1135
0
      *count = zval_get_long(&rv);
1136
0
      zval_ptr_dtor(&rv);
1137
0
      return SUCCESS;
1138
0
    }
1139
0
    *count = 0;
1140
0
    return FAILURE;
1141
0
  }
1142
0
  *count = spl_array_object_count_elements_helper(intern);
1143
0
  return SUCCESS;
1144
0
} /* }}} */
1145
1146
/* {{{ Return the number of elements in the Iterator. */
1147
PHP_METHOD(ArrayObject, count)
1148
109
{
1149
109
  spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
1150
1151
109
  ZEND_PARSE_PARAMETERS_NONE();
1152
1153
109
  RETURN_LONG(spl_array_object_count_elements_helper(intern));
1154
109
} /* }}} */
1155
1156
static void spl_array_method(zval *return_value, spl_array_object *intern, const char *fname, size_t fname_len, const zval *extra_arg) /* {{{ */
1157
0
{
1158
0
  HashTable **ht_ptr = spl_array_get_hash_table_ptr(intern);
1159
0
  HashTable *aht = *ht_ptr;
1160
0
  zval params[2];
1161
0
  uint32_t param_num = 1;
1162
1163
0
  zend_function *fn = zend_hash_str_find_ptr(EG(function_table), fname, fname_len);
1164
0
  if (UNEXPECTED(fn == NULL)) {
1165
0
    zend_throw_error(NULL, "Cannot call method %s when function %s is disabled", fname, fname);
1166
0
    RETURN_THROWS();
1167
0
  }
1168
1169
0
  ZVAL_NEW_EMPTY_REF(&params[0]);
1170
0
  ZVAL_ARR(Z_REFVAL(params[0]), aht);
1171
0
  GC_ADDREF(aht);
1172
1173
0
  if (extra_arg) {
1174
0
    param_num = 2;
1175
0
    ZVAL_COPY_VALUE(&params[1], extra_arg);
1176
0
  }
1177
0
  intern->nApplyCount++;
1178
0
  zend_call_known_function(fn, NULL, NULL, return_value, param_num, params, NULL);
1179
0
  intern->nApplyCount--;
1180
1181
0
  zval *ht_zv = Z_REFVAL(params[0]);
1182
0
  zend_array_release(*ht_ptr);
1183
0
  SEPARATE_ARRAY(ht_zv);
1184
0
  *ht_ptr = Z_ARRVAL_P(ht_zv);
1185
0
  ZVAL_NULL(ht_zv);
1186
0
  zval_ptr_dtor(&params[0]);
1187
0
} /* }}} */
1188
1189
/* Sort the entries by values. */
1190
PHP_METHOD(ArrayObject, asort)
1191
0
{
1192
0
  zend_long sort_flags = 0;
1193
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &sort_flags) == FAILURE) {
1194
0
    RETURN_THROWS();
1195
0
  }
1196
0
  zval sort_flag_param;
1197
0
  ZVAL_LONG(&sort_flag_param, sort_flags);
1198
1199
0
  spl_array_method(return_value, Z_SPLARRAY_P(ZEND_THIS), ZEND_STRL("asort"), &sort_flag_param);
1200
0
}
1201
1202
/* Sort the entries by key. */
1203
PHP_METHOD(ArrayObject, ksort)
1204
0
{
1205
0
  zend_long sort_flags = 0;
1206
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &sort_flags) == FAILURE) {
1207
0
    RETURN_THROWS();
1208
0
  }
1209
0
  zval sort_flag_param;
1210
0
  ZVAL_LONG(&sort_flag_param, sort_flags);
1211
1212
0
  spl_array_method(return_value, Z_SPLARRAY_P(ZEND_THIS), ZEND_STRL("ksort"), &sort_flag_param);
1213
0
}
1214
1215
/* Sort the entries by values user defined function. */
1216
PHP_METHOD(ArrayObject, uasort)
1217
0
{
1218
0
  zval *callback = NULL;
1219
  /* TODO: Should check variable is callable */
1220
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callback) == FAILURE) {
1221
0
    RETURN_THROWS();
1222
0
  }
1223
0
  spl_array_method(return_value, Z_SPLARRAY_P(ZEND_THIS), ZEND_STRL("uasort"), callback);
1224
0
}
1225
1226
/* Sort the entries by key using user defined function. */
1227
PHP_METHOD(ArrayObject, uksort)
1228
0
{
1229
0
  zval *callback = NULL;
1230
  /* TODO: Should check variable is callable */
1231
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callback) == FAILURE) {
1232
0
    RETURN_THROWS();
1233
0
  }
1234
0
  spl_array_method(return_value, Z_SPLARRAY_P(ZEND_THIS), ZEND_STRL("uksort"), callback);
1235
0
}
1236
1237
/* Sort the entries by values using "natural order" algorithm. */
1238
PHP_METHOD(ArrayObject, natsort)
1239
0
{
1240
0
  ZEND_PARSE_PARAMETERS_NONE();
1241
1242
0
  spl_array_method(return_value, Z_SPLARRAY_P(ZEND_THIS), ZEND_STRL("natsort"), NULL);
1243
0
}
1244
1245
/* Sort the entries by key using case-insensitive "natural order" algorithm. */
1246
PHP_METHOD(ArrayObject, natcasesort)
1247
0
{
1248
0
  ZEND_PARSE_PARAMETERS_NONE();
1249
1250
0
  spl_array_method(return_value, Z_SPLARRAY_P(ZEND_THIS), ZEND_STRL("natcasesort"), NULL);
1251
0
}
1252
1253
/* {{{ Serialize the object */
1254
PHP_METHOD(ArrayObject, serialize)
1255
0
{
1256
0
  zval *object = ZEND_THIS;
1257
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
1258
0
  zval members, flags;
1259
0
  php_serialize_data_t var_hash;
1260
0
  smart_str buf = {0};
1261
1262
0
  ZEND_PARSE_PARAMETERS_NONE();
1263
1264
0
  PHP_VAR_SERIALIZE_INIT(var_hash);
1265
1266
0
  ZVAL_LONG(&flags, (intern->ar_flags & SPL_ARRAY_CLONE_MASK));
1267
1268
  /* storage */
1269
0
  smart_str_appendl(&buf, "x:", 2);
1270
0
  php_var_serialize(&buf, &flags, &var_hash);
1271
1272
0
  if (!(intern->ar_flags & SPL_ARRAY_IS_SELF)) {
1273
0
    php_var_serialize(&buf, &intern->array, &var_hash);
1274
0
    smart_str_appendc(&buf, ';');
1275
0
  }
1276
1277
  /* members */
1278
0
  smart_str_appendl(&buf, "m:", 2);
1279
1280
0
  ZVAL_ARR(&members, zend_std_get_properties_ex(&intern->std));
1281
1282
0
  php_var_serialize(&buf, &members, &var_hash); /* finishes the string */
1283
1284
  /* done */
1285
0
  PHP_VAR_SERIALIZE_DESTROY(var_hash);
1286
1287
0
  RETURN_STR(smart_str_extract(&buf));
1288
0
} /* }}} */
1289
1290
/* {{{ unserialize the object */
1291
PHP_METHOD(ArrayObject, unserialize)
1292
154k
{
1293
154k
  zval *object = ZEND_THIS;
1294
154k
  spl_array_object *intern = Z_SPLARRAY_P(object);
1295
1296
154k
  char *buf;
1297
154k
  size_t buf_len;
1298
154k
  const unsigned char *p, *s;
1299
154k
  php_unserialize_data_t var_hash;
1300
154k
  zval *members, *zflags, *array;
1301
154k
  zend_long flags;
1302
1303
154k
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &buf, &buf_len) == FAILURE) {
1304
0
    RETURN_THROWS();
1305
0
  }
1306
1307
154k
  if (buf_len == 0) {
1308
154k
    return;
1309
154k
  }
1310
1311
16
  if (intern->nApplyCount > 0) {
1312
0
    zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited");
1313
0
    RETURN_THROWS();
1314
0
  }
1315
1316
  /* storage */
1317
16
  s = p = (const unsigned char*)buf;
1318
16
  PHP_VAR_UNSERIALIZE_INIT(var_hash);
1319
1320
16
  if (*p!= 'x' || *++p != ':') {
1321
3
    goto outexcept;
1322
3
  }
1323
13
  ++p;
1324
1325
13
  zflags = var_tmp_var(&var_hash);
1326
13
  if (!php_var_unserialize(zflags, &p, s + buf_len, &var_hash) || Z_TYPE_P(zflags) != IS_LONG) {
1327
2
    goto outexcept;
1328
2
  }
1329
1330
11
  --p; /* for ';' */
1331
11
  flags = Z_LVAL_P(zflags);
1332
  /* flags needs to be verified and we also need to verify whether the next
1333
   * thing we get is ';'. After that we require an 'm' or something else
1334
   * where 'm' stands for members and anything else should be an array. If
1335
   * neither 'a' or 'm' follows we have an error. */
1336
1337
11
  if (*p != ';') {
1338
0
    goto outexcept;
1339
0
  }
1340
11
  ++p;
1341
1342
11
  if (flags & SPL_ARRAY_IS_SELF) {
1343
    /* If IS_SELF is used, the flags are not followed by an array/object */
1344
1
    intern->ar_flags &= ~SPL_ARRAY_CLONE_MASK;
1345
1
    intern->ar_flags |= flags & SPL_ARRAY_CLONE_MASK;
1346
1
    zval_ptr_dtor(&intern->array);
1347
1
    ZVAL_UNDEF(&intern->array);
1348
10
  } else {
1349
10
    if (*p!='a' && *p!='O' && *p!='C' && *p!='r') {
1350
4
      goto outexcept;
1351
4
    }
1352
1353
6
    array = var_tmp_var(&var_hash);
1354
6
    if (!php_var_unserialize(array, &p, s + buf_len, &var_hash)
1355
4
        || (Z_TYPE_P(array) != IS_ARRAY && Z_TYPE_P(array) != IS_OBJECT)) {
1356
2
      goto outexcept;
1357
2
    }
1358
1359
4
    intern->ar_flags &= ~SPL_ARRAY_CLONE_MASK;
1360
4
    intern->ar_flags |= flags & SPL_ARRAY_CLONE_MASK;
1361
1362
4
    if (Z_TYPE_P(array) == IS_ARRAY) {
1363
0
      zval_ptr_dtor(&intern->array);
1364
0
      ZVAL_COPY_VALUE(&intern->array, array);
1365
0
      ZVAL_NULL(array);
1366
0
      SEPARATE_ARRAY(&intern->array);
1367
4
    } else {
1368
4
      spl_array_set_array(object, intern, array, 0L, true);
1369
4
    }
1370
1371
4
    if (*p != ';') {
1372
1
      goto outexcept;
1373
1
    }
1374
3
    ++p;
1375
3
  }
1376
1377
  /* members */
1378
4
  if (*p!= 'm' || *++p != ':') {
1379
2
    goto outexcept;
1380
2
  }
1381
2
  ++p;
1382
1383
2
  members = var_tmp_var(&var_hash);
1384
2
  if (!php_var_unserialize(members, &p, s + buf_len, &var_hash) || Z_TYPE_P(members) != IS_ARRAY) {
1385
1
    goto outexcept;
1386
1
  }
1387
1388
  /* copy members */
1389
1
  object_properties_load(&intern->std, Z_ARRVAL_P(members));
1390
1391
  /* done reading $serialized */
1392
1
  PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1393
1
  return;
1394
1395
15
outexcept:
1396
15
  PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1397
15
  zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Error at offset " ZEND_LONG_FMT " of %zd bytes", (zend_long)((char*)p - buf), buf_len);
1398
15
  RETURN_THROWS();
1399
1400
15
} /* }}} */
1401
1402
/* {{{ */
1403
PHP_METHOD(ArrayObject, __serialize)
1404
0
{
1405
0
  spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
1406
0
  zval tmp;
1407
1408
0
  ZEND_PARSE_PARAMETERS_NONE();
1409
1410
0
  array_init(return_value);
1411
1412
  /* flags */
1413
0
  ZVAL_LONG(&tmp, (intern->ar_flags & SPL_ARRAY_CLONE_MASK));
1414
0
  zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1415
1416
  /* storage */
1417
0
  if (intern->ar_flags & SPL_ARRAY_IS_SELF) {
1418
0
    ZVAL_NULL(&tmp);
1419
0
  } else {
1420
0
    ZVAL_COPY(&tmp, &intern->array);
1421
0
  }
1422
0
  zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1423
1424
  /* members */
1425
0
  ZVAL_ARR(&tmp, zend_proptable_to_symtable(
1426
0
    zend_std_get_properties(&intern->std), /* always_duplicate */ 1));
1427
0
  zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1428
1429
  /* iterator class */
1430
0
  if (intern->ce_get_iterator == spl_ce_ArrayIterator) {
1431
0
    ZVAL_NULL(&tmp);
1432
0
  } else {
1433
0
    ZVAL_STR_COPY(&tmp, intern->ce_get_iterator->name);
1434
0
  }
1435
0
  zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
1436
0
}
1437
/* }}} */
1438
1439
1440
/* {{{ */
1441
PHP_METHOD(ArrayObject, __unserialize)
1442
1.70k
{
1443
1.70k
  spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS);
1444
1.70k
  HashTable *data;
1445
1.70k
  zval *flags_zv, *storage_zv, *members_zv, *iterator_class_zv;
1446
1.70k
  zend_long flags;
1447
1448
1.70k
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &data) == FAILURE) {
1449
0
    RETURN_THROWS();
1450
0
  }
1451
1452
1.70k
  flags_zv          = zend_hash_index_find(data, 0);
1453
1.70k
  storage_zv        = zend_hash_index_find(data, 1);
1454
1.70k
  members_zv        = zend_hash_index_find(data, 2);
1455
1.70k
  iterator_class_zv = zend_hash_index_find(data, 3);
1456
1457
1.70k
  if (!flags_zv || !storage_zv || !members_zv ||
1458
1.60k
      Z_TYPE_P(flags_zv) != IS_LONG || Z_TYPE_P(members_zv) != IS_ARRAY ||
1459
1.60k
      (iterator_class_zv && (Z_TYPE_P(iterator_class_zv) != IS_NULL &&
1460
105
        Z_TYPE_P(iterator_class_zv) != IS_STRING))) {
1461
105
    zend_throw_exception(spl_ce_UnexpectedValueException,
1462
105
      "Incomplete or ill-typed serialization data", 0);
1463
105
    RETURN_THROWS();
1464
105
  }
1465
1466
1.60k
  flags = Z_LVAL_P(flags_zv);
1467
1.60k
  intern->ar_flags &= ~SPL_ARRAY_CLONE_MASK;
1468
1.60k
  intern->ar_flags |= flags & SPL_ARRAY_CLONE_MASK;
1469
1470
1.60k
  if (flags & SPL_ARRAY_IS_SELF) {
1471
530
    zval_ptr_dtor(&intern->array);
1472
530
    ZVAL_UNDEF(&intern->array);
1473
1.07k
  } else {
1474
1.07k
    if (Z_TYPE_P(storage_zv) != IS_OBJECT && Z_TYPE_P(storage_zv) != IS_ARRAY) {
1475
      /* TODO Use UnexpectedValueException instead? And better error message? */
1476
1
      zend_throw_exception(spl_ce_InvalidArgumentException, "Passed variable is not an array or object", 0);
1477
1
      RETURN_THROWS();
1478
1
    }
1479
1.07k
    spl_array_set_array(ZEND_THIS, intern, storage_zv, 0L, true);
1480
1.07k
  }
1481
1482
1.60k
  object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
1483
1.60k
  if (EG(exception)) {
1484
2
    RETURN_THROWS();
1485
2
  }
1486
1487
1.59k
  if (iterator_class_zv && Z_TYPE_P(iterator_class_zv) == IS_STRING) {
1488
0
    zend_class_entry *ce = zend_lookup_class(Z_STR_P(iterator_class_zv));
1489
1490
0
    if (!ce) {
1491
0
      zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0,
1492
0
        "Cannot deserialize ArrayObject with iterator class '%s'; no such class exists",
1493
0
        ZSTR_VAL(Z_STR_P(iterator_class_zv)));
1494
0
      RETURN_THROWS();
1495
0
    }
1496
1497
0
    if (!instanceof_function(ce, spl_ce_ArrayIterator)) {
1498
0
      zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0,
1499
0
        "Cannot deserialize ArrayObject with iterator class '%s'; this class is not derived from ArrayIterator",
1500
0
        ZSTR_VAL(Z_STR_P(iterator_class_zv)));
1501
0
      RETURN_THROWS();
1502
0
    }
1503
1504
0
    intern->ce_get_iterator = ce;
1505
0
  }
1506
1.59k
}
1507
/* }}} */
1508
1509
/* {{{ */
1510
PHP_METHOD(ArrayObject, __debugInfo)
1511
58
{
1512
58
  ZEND_PARSE_PARAMETERS_NONE();
1513
1514
58
  RETURN_ARR(spl_array_get_debug_info(Z_OBJ_P(ZEND_THIS)));
1515
58
} /* }}} */
1516
1517
/*** ArrayIterator class ***/
1518
typedef struct _spl_array_iterator {
1519
  zend_object_iterator it;
1520
  bool by_ref;
1521
} spl_array_iterator;
1522
1523
static zend_result spl_array_next_ex(spl_array_object *intern, HashTable *aht) /* {{{ */
1524
313
{
1525
313
  uint32_t *pos_ptr = spl_array_get_pos_ptr(aht, intern);
1526
1527
313
  zend_hash_move_forward_ex(aht, pos_ptr);
1528
313
  if (spl_array_is_object(intern)) {
1529
15
    return spl_array_skip_protected(intern, aht);
1530
298
  } else {
1531
298
    return zend_hash_has_more_elements_ex(aht, pos_ptr);
1532
298
  }
1533
313
} /* }}} */
1534
1535
static zend_result spl_array_next(spl_array_object *intern) /* {{{ */
1536
1
{
1537
1
  HashTable *aht = spl_array_get_hash_table(intern);
1538
1539
1
  return spl_array_next_ex(intern, aht);
1540
1541
1
} /* }}} */
1542
1543
static void spl_array_it_dtor(zend_object_iterator *iter) /* {{{ */
1544
233
{
1545
233
  zval_ptr_dtor(&iter->data);
1546
233
}
1547
/* }}} */
1548
1549
static zend_result spl_array_it_valid(zend_object_iterator *iter) /* {{{ */
1550
551
{
1551
551
  spl_array_object *object = Z_SPLARRAY_P(&iter->data);
1552
551
  HashTable *aht = spl_array_get_hash_table(object);
1553
551
  return zend_hash_has_more_elements_ex(aht, spl_array_get_pos_ptr(aht, object));
1554
551
}
1555
/* }}} */
1556
1557
static zval *spl_array_it_get_current_data(zend_object_iterator *iter) /* {{{ */
1558
379
{
1559
379
  spl_array_iterator *array_iter = (spl_array_iterator*)iter;
1560
379
  spl_array_object *object = Z_SPLARRAY_P(&iter->data);
1561
379
  HashTable *aht = spl_array_get_hash_table(object);
1562
379
  zval *data = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, object));
1563
379
  if (data && Z_TYPE_P(data) == IS_INDIRECT) {
1564
20
    data = Z_INDIRECT_P(data);
1565
20
  }
1566
  // ZEND_FE_FETCH_RW converts the value to a reference but doesn't know the source is a property.
1567
  // Typed properties must add a type source to the reference, and readonly properties must fail.
1568
379
  if (array_iter->by_ref
1569
20
   && Z_TYPE_P(data) != IS_REFERENCE
1570
15
   && Z_TYPE(object->array) == IS_OBJECT
1571
15
   && !(object->ar_flags & (SPL_ARRAY_IS_SELF|SPL_ARRAY_USE_OTHER))) {
1572
15
    zend_string *key;
1573
15
    zend_hash_get_current_key_ex(aht, &key, NULL, spl_array_get_pos_ptr(aht, object));
1574
15
    zend_class_entry *ce = Z_OBJCE(object->array);
1575
15
    zend_property_info *prop_info = zend_get_property_info(ce, key, true);
1576
15
    ZEND_ASSERT(prop_info != ZEND_WRONG_PROPERTY_INFO);
1577
15
    if (EXPECTED(prop_info != NULL) && ZEND_TYPE_IS_SET(prop_info->type)) {
1578
15
      if (prop_info->flags & ZEND_ACC_READONLY) {
1579
5
        zend_throw_error(NULL,
1580
5
          "Cannot acquire reference to readonly property %s::$%s",
1581
5
          ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(key));
1582
5
        return NULL;
1583
5
      }
1584
10
      ZVAL_NEW_REF(data, data);
1585
10
      ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), prop_info);
1586
10
    }
1587
15
  }
1588
374
  return data;
1589
379
}
1590
/* }}} */
1591
1592
static void spl_array_it_get_current_key(zend_object_iterator *iter, zval *key) /* {{{ */
1593
342
{
1594
342
  spl_array_object *object = Z_SPLARRAY_P(&iter->data);
1595
342
  HashTable *aht = spl_array_get_hash_table(object);
1596
342
  zend_hash_get_current_key_zval_ex(aht, key, spl_array_get_pos_ptr(aht, object));
1597
342
}
1598
/* }}} */
1599
1600
static void spl_array_it_move_forward(zend_object_iterator *iter) /* {{{ */
1601
299
{
1602
299
  spl_array_object *object = Z_SPLARRAY_P(&iter->data);
1603
299
  HashTable *aht = spl_array_get_hash_table(object);
1604
299
  spl_array_next_ex(object, aht);
1605
299
}
1606
/* }}} */
1607
1608
static void spl_array_rewind(spl_array_object *intern) /* {{{ */
1609
303
{
1610
303
  HashTable *aht = spl_array_get_hash_table(intern);
1611
1612
303
  if (intern->ht_iter == (uint32_t)-1) {
1613
261
    spl_array_get_pos_ptr(aht, intern);
1614
261
  } else {
1615
42
    zend_hash_internal_pointer_reset_ex(aht, spl_array_get_pos_ptr(aht, intern));
1616
42
    spl_array_skip_protected(intern, aht);
1617
42
  }
1618
303
}
1619
/* }}} */
1620
1621
static void spl_array_it_rewind(zend_object_iterator *iter) /* {{{ */
1622
222
{
1623
222
  spl_array_object *object = Z_SPLARRAY_P(&iter->data);
1624
222
  spl_array_rewind(object);
1625
222
}
1626
/* }}} */
1627
1628
static HashTable *spl_array_it_get_gc(zend_object_iterator *iter, zval **table, int *n)
1629
334
{
1630
334
  *n = 1;
1631
334
  *table = &iter->data;
1632
334
  return NULL;
1633
334
}
1634
1635
/* iterator handler table */
1636
static const zend_object_iterator_funcs spl_array_it_funcs = {
1637
  spl_array_it_dtor,
1638
  spl_array_it_valid,
1639
  spl_array_it_get_current_data,
1640
  spl_array_it_get_current_key,
1641
  spl_array_it_move_forward,
1642
  spl_array_it_rewind,
1643
  NULL,
1644
  spl_array_it_get_gc,
1645
};
1646
1647
static zend_object_iterator *spl_array_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
1648
233
{
1649
233
  spl_array_iterator *iterator = emalloc(sizeof(spl_array_iterator));
1650
233
  zend_iterator_init(&iterator->it);
1651
1652
233
  ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object));
1653
233
  iterator->it.funcs = &spl_array_it_funcs;
1654
233
  iterator->by_ref = by_ref;
1655
1656
233
  return &iterator->it;
1657
233
}
1658
/* }}} */
1659
1660
/* {{{ Constructs a new array iterator from an array or object. */
1661
PHP_METHOD(ArrayIterator, __construct)
1662
1.24k
{
1663
1.24k
  zval *object = ZEND_THIS;
1664
1.24k
  spl_array_object *intern;
1665
1.24k
  zval *array;
1666
1.24k
  zend_long ar_flags = 0;
1667
1668
1.24k
  if (ZEND_NUM_ARGS() == 0) {
1669
112
    return; /* nothing to do */
1670
112
  }
1671
1672
1.13k
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|Al", &array, &ar_flags) == FAILURE) {
1673
3
    RETURN_THROWS();
1674
3
  }
1675
1676
1.13k
  intern = Z_SPLARRAY_P(object);
1677
1678
1.13k
  ar_flags &= ~SPL_ARRAY_INT_MASK;
1679
1680
1.13k
  spl_array_set_array(object, intern, array, ar_flags, ZEND_NUM_ARGS() == 1);
1681
1.13k
}
1682
/* }}} */
1683
1684
/* {{{ Rewind array back to the start */
1685
PHP_METHOD(ArrayIterator, rewind)
1686
80
{
1687
80
  zval *object = ZEND_THIS;
1688
80
  spl_array_object *intern = Z_SPLARRAY_P(object);
1689
1690
80
  ZEND_PARSE_PARAMETERS_NONE();
1691
1692
80
  spl_array_rewind(intern);
1693
80
}
1694
/* }}} */
1695
1696
/* {{{ Seek to position. */
1697
PHP_METHOD(ArrayIterator, seek)
1698
1
{
1699
1
  zend_long opos, position;
1700
1
  zval *object = ZEND_THIS;
1701
1
  spl_array_object *intern = Z_SPLARRAY_P(object);
1702
1
  HashTable *aht = spl_array_get_hash_table(intern);
1703
1
  int result;
1704
1705
1
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &position) == FAILURE) {
1706
0
    RETURN_THROWS();
1707
0
  }
1708
1709
1
  opos = position;
1710
1711
1
  if (position >= 0) { /* negative values are not supported */
1712
1
    spl_array_rewind(intern);
1713
1
    result = SUCCESS;
1714
1715
1
    while (position-- > 0 && (result = spl_array_next(intern)) == SUCCESS);
1716
1717
1
    if (result == SUCCESS && zend_hash_has_more_elements_ex(aht, spl_array_get_pos_ptr(aht, intern)) == SUCCESS) {
1718
0
      return; /* ok */
1719
0
    }
1720
1
  }
1721
1
  zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Seek position " ZEND_LONG_FMT " is out of range", opos);
1722
1
} /* }}} */
1723
1724
/* {{{ Return current array entry */
1725
PHP_METHOD(ArrayIterator, current)
1726
30
{
1727
30
  zval *object = ZEND_THIS;
1728
30
  spl_array_object *intern = Z_SPLARRAY_P(object);
1729
30
  zval *entry;
1730
30
  HashTable *aht = spl_array_get_hash_table(intern);
1731
1732
30
  ZEND_PARSE_PARAMETERS_NONE();
1733
1734
30
  if ((entry = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, intern))) == NULL) {
1735
0
    RETURN_NULL();
1736
0
  }
1737
30
  if (Z_TYPE_P(entry) == IS_INDIRECT) {
1738
0
    entry = Z_INDIRECT_P(entry);
1739
0
    if (Z_TYPE_P(entry) == IS_UNDEF) {
1740
0
      RETURN_NULL();
1741
0
    }
1742
0
  }
1743
30
  RETURN_COPY_DEREF(entry);
1744
30
}
1745
/* }}} */
1746
1747
void spl_array_iterator_key(zval *object, zval *return_value) /* {{{ */
1748
0
{
1749
0
  spl_array_object *intern = Z_SPLARRAY_P(object);
1750
0
  HashTable *aht = spl_array_get_hash_table(intern);
1751
1752
0
  zend_hash_get_current_key_zval_ex(aht, return_value, spl_array_get_pos_ptr(aht, intern));
1753
0
}
1754
/* }}} */
1755
1756
/* {{{ Return current array key */
1757
PHP_METHOD(ArrayIterator, key)
1758
0
{
1759
0
  ZEND_PARSE_PARAMETERS_NONE();
1760
1761
0
  spl_array_iterator_key(ZEND_THIS, return_value);
1762
0
} /* }}} */
1763
1764
/* {{{ Move to next entry */
1765
PHP_METHOD(ArrayIterator, next)
1766
13
{
1767
13
  zval *object = ZEND_THIS;
1768
13
  spl_array_object *intern = Z_SPLARRAY_P(object);
1769
13
  HashTable *aht = spl_array_get_hash_table(intern);
1770
1771
13
  ZEND_PARSE_PARAMETERS_NONE();
1772
1773
13
  spl_array_next_ex(intern, aht);
1774
13
}
1775
/* }}} */
1776
1777
/* {{{ Check whether array contains more entries */
1778
PHP_METHOD(ArrayIterator, valid)
1779
32
{
1780
32
  zval *object = ZEND_THIS;
1781
32
  spl_array_object *intern = Z_SPLARRAY_P(object);
1782
32
  HashTable *aht = spl_array_get_hash_table(intern);
1783
1784
32
  ZEND_PARSE_PARAMETERS_NONE();
1785
1786
32
  RETURN_BOOL(zend_hash_has_more_elements_ex(aht, spl_array_get_pos_ptr(aht, intern)) == SUCCESS);
1787
32
}
1788
/* }}} */
1789
1790
/*** RecursiveArrayIterator methods ***/
1791
1792
/* {{{ Check whether current element has children (e.g. is an array) */
1793
PHP_METHOD(RecursiveArrayIterator, hasChildren)
1794
20
{
1795
20
  zval *object = ZEND_THIS, *entry;
1796
20
  spl_array_object *intern = Z_SPLARRAY_P(object);
1797
20
  HashTable *aht = spl_array_get_hash_table(intern);
1798
1799
20
  ZEND_PARSE_PARAMETERS_NONE();
1800
1801
20
  if ((entry = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, intern))) == NULL) {
1802
0
    RETURN_FALSE;
1803
0
  }
1804
1805
20
  if (Z_TYPE_P(entry) == IS_INDIRECT) {
1806
0
    entry = Z_INDIRECT_P(entry);
1807
0
  }
1808
1809
20
  ZVAL_DEREF(entry);
1810
20
  RETURN_BOOL(Z_TYPE_P(entry) == IS_ARRAY || (Z_TYPE_P(entry) == IS_OBJECT && (intern->ar_flags & SPL_ARRAY_CHILD_ARRAYS_ONLY) == 0));
1811
20
}
1812
/* }}} */
1813
1814
static void spl_instantiate_child_arg(zend_class_entry *pce, zval *retval, zval *arg1, zval *arg2) /* {{{ */
1815
9
{
1816
9
  object_init_ex(retval, pce);
1817
9
  zend_call_known_instance_method_with_2_params(pce->constructor, Z_OBJ_P(retval), NULL, arg1, arg2);
1818
9
}
1819
/* }}} */
1820
1821
/* {{{ Create a sub iterator for the current element (same class as $this) */
1822
PHP_METHOD(RecursiveArrayIterator, getChildren)
1823
9
{
1824
9
  zval *object = ZEND_THIS, *entry, flags;
1825
9
  spl_array_object *intern = Z_SPLARRAY_P(object);
1826
9
  HashTable *aht = spl_array_get_hash_table(intern);
1827
1828
9
  ZEND_PARSE_PARAMETERS_NONE();
1829
1830
9
  if ((entry = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, intern))) == NULL) {
1831
0
    RETURN_NULL();
1832
0
  }
1833
1834
9
  if (Z_TYPE_P(entry) == IS_INDIRECT) {
1835
0
    entry = Z_INDIRECT_P(entry);
1836
0
  }
1837
1838
9
  ZVAL_DEREF(entry);
1839
9
  if (Z_TYPE_P(entry) == IS_OBJECT) {
1840
0
    if ((intern->ar_flags & SPL_ARRAY_CHILD_ARRAYS_ONLY) != 0) {
1841
0
      RETURN_NULL();
1842
0
    }
1843
0
    if (instanceof_function(Z_OBJCE_P(entry), Z_OBJCE_P(ZEND_THIS))) {
1844
0
      RETURN_OBJ_COPY(Z_OBJ_P(entry));
1845
0
    }
1846
0
  }
1847
1848
9
  ZVAL_LONG(&flags, intern->ar_flags);
1849
9
  spl_instantiate_child_arg(Z_OBJCE_P(ZEND_THIS), return_value, entry, &flags);
1850
9
}
1851
/* }}} */
1852
1853
/* {{{ PHP_MINIT_FUNCTION(spl_array) */
1854
PHP_MINIT_FUNCTION(spl_array)
1855
16
{
1856
16
  spl_ce_ArrayObject = register_class_ArrayObject(zend_ce_aggregate, zend_ce_arrayaccess, zend_ce_serializable, zend_ce_countable);
1857
16
  spl_ce_ArrayObject->create_object = spl_array_object_new;
1858
16
  spl_ce_ArrayObject->default_object_handlers = &spl_handler_ArrayObject;
1859
1860
16
  memcpy(&spl_handler_ArrayObject, &std_object_handlers, sizeof(zend_object_handlers));
1861
1862
16
  spl_handler_ArrayObject.offset = offsetof(spl_array_object, std);
1863
1864
16
  spl_handler_ArrayObject.clone_obj = spl_array_object_clone;
1865
16
  spl_handler_ArrayObject.read_dimension = spl_array_read_dimension;
1866
16
  spl_handler_ArrayObject.write_dimension = spl_array_write_dimension;
1867
16
  spl_handler_ArrayObject.unset_dimension = spl_array_unset_dimension;
1868
16
  spl_handler_ArrayObject.has_dimension = spl_array_has_dimension;
1869
16
  spl_handler_ArrayObject.count_elements = spl_array_object_count_elements;
1870
1871
16
  spl_handler_ArrayObject.get_properties_for = spl_array_get_properties_for;
1872
16
  spl_handler_ArrayObject.get_gc = spl_array_get_gc;
1873
16
  spl_handler_ArrayObject.read_property = spl_array_read_property;
1874
16
  spl_handler_ArrayObject.write_property = spl_array_write_property;
1875
16
  spl_handler_ArrayObject.get_property_ptr_ptr = spl_array_get_property_ptr_ptr;
1876
16
  spl_handler_ArrayObject.has_property = spl_array_has_property;
1877
16
  spl_handler_ArrayObject.unset_property = spl_array_unset_property;
1878
1879
16
  spl_handler_ArrayObject.compare = spl_array_compare_objects;
1880
16
  spl_handler_ArrayObject.free_obj = spl_array_object_free_storage;
1881
1882
16
  spl_ce_ArrayIterator = register_class_ArrayIterator(spl_ce_SeekableIterator, zend_ce_arrayaccess, zend_ce_serializable, zend_ce_countable);
1883
16
  spl_ce_ArrayIterator->create_object = spl_array_object_new;
1884
16
  spl_ce_ArrayIterator->default_object_handlers = &spl_handler_ArrayObject;
1885
16
  spl_ce_ArrayIterator->get_iterator = spl_array_get_iterator;
1886
1887
16
  spl_ce_RecursiveArrayIterator = register_class_RecursiveArrayIterator(spl_ce_ArrayIterator, spl_ce_RecursiveIterator);
1888
16
  spl_ce_RecursiveArrayIterator->create_object = spl_array_object_new;
1889
16
  spl_ce_RecursiveArrayIterator->get_iterator = spl_array_get_iterator;
1890
1891
16
  return SUCCESS;
1892
16
}
1893
/* }}} */